create-component.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /* @flow */
  2. import VNode from './vnode'
  3. import { normalizeChildren } from './helpers/index'
  4. import { resolveConstructorOptions } from '../instance/init'
  5. import { activeInstance, callHook } from '../instance/lifecycle'
  6. import { resolveSlots } from '../instance/render'
  7. import { createElement } from './create-element'
  8. import { warn, isObject, hasOwn, hyphenate, validateProp, bind } from '../util/index'
  9. const hooks = { init, prepatch, insert, destroy }
  10. const hooksToMerge = Object.keys(hooks)
  11. export function createComponent (
  12. Ctor: Class<Component> | Function | Object | void,
  13. data?: VNodeData,
  14. context: Component,
  15. children?: VNodeChildren,
  16. tag?: string
  17. ): VNode | void {
  18. if (!Ctor) {
  19. return
  20. }
  21. const baseCtor = context.$options._base
  22. if (isObject(Ctor)) {
  23. Ctor = baseCtor.extend(Ctor)
  24. }
  25. if (typeof Ctor !== 'function') {
  26. if (process.env.NODE_ENV !== 'production') {
  27. warn(`Invalid Component definition: ${String(Ctor)}`, context)
  28. }
  29. return
  30. }
  31. // async component
  32. if (!Ctor.cid) {
  33. if (Ctor.resolved) {
  34. Ctor = Ctor.resolved
  35. } else {
  36. Ctor = resolveAsyncComponent(Ctor, baseCtor, () => {
  37. // it's ok to queue this on every render because
  38. // $forceUpdate is buffered by the scheduler.
  39. context.$forceUpdate()
  40. })
  41. if (!Ctor) {
  42. // return nothing if this is indeed an async component
  43. // wait for the callback to trigger parent update.
  44. return
  45. }
  46. }
  47. }
  48. // resolve constructor options in case global mixins are applied after
  49. // component constructor creation
  50. resolveConstructorOptions(Ctor)
  51. data = data || {}
  52. // extract props
  53. const propsData = extractProps(data, Ctor)
  54. // functional component
  55. if (Ctor.options.functional) {
  56. return createFunctionalComponent(Ctor, propsData, data, context, children)
  57. }
  58. // extract listeners, since these needs to be treated as
  59. // child component listeners instead of DOM listeners
  60. const listeners = data.on
  61. // replace with listeners with .native modifier
  62. data.on = data.nativeOn
  63. if (Ctor.options.abstract) {
  64. // abstract components do not keep anything
  65. // other than props & listeners
  66. data = {}
  67. }
  68. // merge component management hooks onto the placeholder node
  69. mergeHooks(data)
  70. // return a placeholder vnode
  71. const name = Ctor.options.name || tag
  72. const vnode = new VNode(
  73. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  74. data, undefined, undefined, undefined, undefined, context,
  75. { Ctor, propsData, listeners, tag, children }
  76. )
  77. return vnode
  78. }
  79. function createFunctionalComponent (
  80. Ctor: Class<Component>,
  81. propsData: ?Object,
  82. data: VNodeData,
  83. context: Component,
  84. children?: VNodeChildren
  85. ): VNode | void {
  86. const props = {}
  87. const propOptions = Ctor.options.props
  88. if (propOptions) {
  89. for (const key in propOptions) {
  90. props[key] = validateProp(key, propOptions, propsData)
  91. }
  92. }
  93. const vnode = Ctor.options.render.call(
  94. null,
  95. // ensure the createElement function in functional components
  96. // gets a unique context - this is necessary for correct named slot check
  97. bind(createElement, { _self: Object.create(context) }),
  98. {
  99. props,
  100. data,
  101. parent: context,
  102. children: normalizeChildren(children),
  103. slots: () => resolveSlots(children, context)
  104. }
  105. )
  106. if (vnode instanceof VNode) {
  107. vnode.functionalContext = context
  108. if (data.slot) {
  109. (vnode.data || (vnode.data = {})).slot = data.slot
  110. }
  111. }
  112. return vnode
  113. }
  114. export function createComponentInstanceForVnode (
  115. vnode: any, // we know it's MountedComponentVNode but flow doesn't
  116. parent: any, // activeInstance in lifecycle state
  117. parentElm?: ?Node,
  118. refElm?: ?Node
  119. ): Component {
  120. const vnodeComponentOptions = vnode.componentOptions
  121. const options: InternalComponentOptions = {
  122. _isComponent: true,
  123. parent,
  124. propsData: vnodeComponentOptions.propsData,
  125. _componentTag: vnodeComponentOptions.tag,
  126. _parentVnode: vnode,
  127. _parentListeners: vnodeComponentOptions.listeners,
  128. _renderChildren: vnodeComponentOptions.children,
  129. _parentElm: parentElm || null,
  130. _refElm: refElm || null
  131. }
  132. // check inline-template render functions
  133. const inlineTemplate = vnode.data.inlineTemplate
  134. if (inlineTemplate) {
  135. options.render = inlineTemplate.render
  136. options.staticRenderFns = inlineTemplate.staticRenderFns
  137. }
  138. return new vnodeComponentOptions.Ctor(options)
  139. }
  140. function init (
  141. vnode: VNodeWithData,
  142. hydrating: boolean,
  143. parentElm: ?Node,
  144. refElm: ?Node
  145. ): ?boolean {
  146. if (!vnode.child || vnode.child._isDestroyed) {
  147. const child = vnode.child = createComponentInstanceForVnode(
  148. vnode,
  149. activeInstance,
  150. parentElm,
  151. refElm
  152. )
  153. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  154. } else if (vnode.data.keepAlive) {
  155. // kept-alive components, treat as a patch
  156. const mountedNode: any = vnode // work around flow
  157. prepatch(mountedNode, mountedNode)
  158. }
  159. }
  160. function prepatch (
  161. oldVnode: MountedComponentVNode,
  162. vnode: MountedComponentVNode
  163. ) {
  164. const options = vnode.componentOptions
  165. const child = vnode.child = oldVnode.child
  166. child._updateFromParent(
  167. options.propsData, // updated props
  168. options.listeners, // updated listeners
  169. vnode, // new parent vnode
  170. options.children // new children
  171. )
  172. }
  173. function insert (vnode: MountedComponentVNode) {
  174. if (!vnode.child._isMounted) {
  175. vnode.child._isMounted = true
  176. callHook(vnode.child, 'mounted')
  177. }
  178. if (vnode.data.keepAlive) {
  179. vnode.child._inactive = false
  180. callHook(vnode.child, 'activated')
  181. }
  182. }
  183. function destroy (vnode: MountedComponentVNode) {
  184. if (!vnode.child._isDestroyed) {
  185. if (!vnode.data.keepAlive) {
  186. vnode.child.$destroy()
  187. } else {
  188. vnode.child._inactive = true
  189. callHook(vnode.child, 'deactivated')
  190. }
  191. }
  192. }
  193. function resolveAsyncComponent (
  194. factory: Function,
  195. baseCtor: Class<Component>,
  196. cb: Function
  197. ): Class<Component> | void {
  198. if (factory.requested) {
  199. // pool callbacks
  200. factory.pendingCallbacks.push(cb)
  201. } else {
  202. factory.requested = true
  203. const cbs = factory.pendingCallbacks = [cb]
  204. let sync = true
  205. const resolve = (res: Object | Class<Component>) => {
  206. if (isObject(res)) {
  207. res = baseCtor.extend(res)
  208. }
  209. // cache resolved
  210. factory.resolved = res
  211. // invoke callbacks only if this is not a synchronous resolve
  212. // (async resolves are shimmed as synchronous during SSR)
  213. if (!sync) {
  214. for (let i = 0, l = cbs.length; i < l; i++) {
  215. cbs[i](res)
  216. }
  217. }
  218. }
  219. const reject = reason => {
  220. process.env.NODE_ENV !== 'production' && warn(
  221. `Failed to resolve async component: ${String(factory)}` +
  222. (reason ? `\nReason: ${reason}` : '')
  223. )
  224. }
  225. const res = factory(resolve, reject)
  226. // handle promise
  227. if (res && typeof res.then === 'function' && !factory.resolved) {
  228. res.then(resolve, reject)
  229. }
  230. sync = false
  231. // return in case resolved synchronously
  232. return factory.resolved
  233. }
  234. }
  235. function extractProps (data: VNodeData, Ctor: Class<Component>): ?Object {
  236. // we are only extracting raw values here.
  237. // validation and default values are handled in the child
  238. // component itself.
  239. const propOptions = Ctor.options.props
  240. if (!propOptions) {
  241. return
  242. }
  243. const res = {}
  244. const { attrs, props, domProps } = data
  245. if (attrs || props || domProps) {
  246. for (const key in propOptions) {
  247. const altKey = hyphenate(key)
  248. checkProp(res, props, key, altKey, true) ||
  249. checkProp(res, attrs, key, altKey) ||
  250. checkProp(res, domProps, key, altKey)
  251. }
  252. }
  253. return res
  254. }
  255. function checkProp (
  256. res: Object,
  257. hash: ?Object,
  258. key: string,
  259. altKey: string,
  260. preserve?: boolean
  261. ): boolean {
  262. if (hash) {
  263. if (hasOwn(hash, key)) {
  264. res[key] = hash[key]
  265. if (!preserve) {
  266. delete hash[key]
  267. }
  268. return true
  269. } else if (hasOwn(hash, altKey)) {
  270. res[key] = hash[altKey]
  271. if (!preserve) {
  272. delete hash[altKey]
  273. }
  274. return true
  275. }
  276. }
  277. return false
  278. }
  279. function mergeHooks (data: VNodeData) {
  280. if (!data.hook) {
  281. data.hook = {}
  282. }
  283. for (let i = 0; i < hooksToMerge.length; i++) {
  284. const key = hooksToMerge[i]
  285. const fromParent = data.hook[key]
  286. const ours = hooks[key]
  287. data.hook[key] = fromParent ? mergeHook(ours, fromParent) : ours
  288. }
  289. }
  290. function mergeHook (one: Function, two: Function): Function {
  291. return function (a, b, c, d) {
  292. one(a, b, c, d)
  293. two(a, b, c, d)
  294. }
  295. }