create-component.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /* @flow */
  2. import VNode from './vnode'
  3. import { resolveConstructorOptions } from 'core/instance/init'
  4. import { queueActivatedComponent } from 'core/observer/scheduler'
  5. import { createFunctionalComponent } from './create-functional-component'
  6. import {
  7. warn,
  8. isDef,
  9. isUndef,
  10. isTrue,
  11. isObject
  12. } from '../util/index'
  13. import {
  14. resolveAsyncComponent,
  15. createAsyncPlaceholder,
  16. extractPropsFromVNodeData
  17. } from './helpers/index'
  18. import {
  19. callHook,
  20. activeInstance,
  21. updateChildComponent,
  22. activateChildComponent,
  23. deactivateChildComponent
  24. } from '../instance/lifecycle'
  25. import {
  26. isRecyclableComponent,
  27. renderRecyclableComponentTemplate
  28. } from 'weex/runtime/recycle-list/render-component-template'
  29. // inline hooks to be invoked on component VNodes during patch
  30. const componentVNodeHooks = {
  31. init (
  32. vnode: VNodeWithData,
  33. hydrating: boolean,
  34. parentElm: ?Node,
  35. refElm: ?Node
  36. ): ?boolean {
  37. if (
  38. vnode.componentInstance &&
  39. !vnode.componentInstance._isDestroyed &&
  40. vnode.data.keepAlive
  41. ) {
  42. // kept-alive components, treat as a patch
  43. const mountedNode: any = vnode // work around flow
  44. componentVNodeHooks.prepatch(mountedNode, mountedNode)
  45. } else {
  46. const child = vnode.componentInstance = createComponentInstanceForVnode(
  47. vnode,
  48. activeInstance,
  49. parentElm,
  50. refElm
  51. )
  52. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  53. }
  54. },
  55. prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
  56. const options = vnode.componentOptions
  57. const child = vnode.componentInstance = oldVnode.componentInstance
  58. updateChildComponent(
  59. child,
  60. options.propsData, // updated props
  61. options.listeners, // updated listeners
  62. vnode, // new parent vnode
  63. options.children // new children
  64. )
  65. },
  66. insert (vnode: MountedComponentVNode) {
  67. const { context, componentInstance } = vnode
  68. if (!componentInstance._isMounted) {
  69. componentInstance._isMounted = true
  70. callHook(componentInstance, 'mounted')
  71. }
  72. if (vnode.data.keepAlive) {
  73. if (context._isMounted) {
  74. // vue-router#1212
  75. // During updates, a kept-alive component's child components may
  76. // change, so directly walking the tree here may call activated hooks
  77. // on incorrect children. Instead we push them into a queue which will
  78. // be processed after the whole patch process ended.
  79. queueActivatedComponent(componentInstance)
  80. } else {
  81. activateChildComponent(componentInstance, true /* direct */)
  82. }
  83. }
  84. },
  85. destroy (vnode: MountedComponentVNode) {
  86. const { componentInstance } = vnode
  87. if (!componentInstance._isDestroyed) {
  88. if (!vnode.data.keepAlive) {
  89. componentInstance.$destroy()
  90. } else {
  91. deactivateChildComponent(componentInstance, true /* direct */)
  92. }
  93. }
  94. }
  95. }
  96. const hooksToMerge = Object.keys(componentVNodeHooks)
  97. export function createComponent (
  98. Ctor: Class<Component> | Function | Object | void,
  99. data: ?VNodeData,
  100. context: Component,
  101. children: ?Array<VNode>,
  102. tag?: string
  103. ): VNode | Array<VNode> | void {
  104. if (isUndef(Ctor)) {
  105. return
  106. }
  107. const baseCtor = context.$options._base
  108. // plain options object: turn it into a constructor
  109. if (isObject(Ctor)) {
  110. Ctor = baseCtor.extend(Ctor)
  111. }
  112. // if at this stage it's not a constructor or an async component factory,
  113. // reject.
  114. if (typeof Ctor !== 'function') {
  115. if (process.env.NODE_ENV !== 'production') {
  116. warn(`Invalid Component definition: ${String(Ctor)}`, context)
  117. }
  118. return
  119. }
  120. // async component
  121. let asyncFactory
  122. if (isUndef(Ctor.cid)) {
  123. asyncFactory = Ctor
  124. Ctor = resolveAsyncComponent(asyncFactory, baseCtor, context)
  125. if (Ctor === undefined) {
  126. // return a placeholder node for async component, which is rendered
  127. // as a comment node but preserves all the raw information for the node.
  128. // the information will be used for async server-rendering and hydration.
  129. return createAsyncPlaceholder(
  130. asyncFactory,
  131. data,
  132. context,
  133. children,
  134. tag
  135. )
  136. }
  137. }
  138. data = data || {}
  139. // resolve constructor options in case global mixins are applied after
  140. // component constructor creation
  141. resolveConstructorOptions(Ctor)
  142. // transform component v-model data into props & events
  143. if (isDef(data.model)) {
  144. transformModel(Ctor.options, data)
  145. }
  146. // extract props
  147. const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  148. // functional component
  149. if (isTrue(Ctor.options.functional)) {
  150. return createFunctionalComponent(Ctor, propsData, data, context, children)
  151. }
  152. // extract listeners, since these needs to be treated as
  153. // child component listeners instead of DOM listeners
  154. const listeners = data.on
  155. // replace with listeners with .native modifier
  156. // so it gets processed during parent component patch.
  157. data.on = data.nativeOn
  158. if (isTrue(Ctor.options.abstract)) {
  159. // abstract components do not keep anything
  160. // other than props & listeners & slot
  161. // work around flow
  162. const slot = data.slot
  163. data = {}
  164. if (slot) {
  165. data.slot = slot
  166. }
  167. }
  168. // install component management hooks onto the placeholder node
  169. installComponentHooks(data)
  170. // return a placeholder vnode
  171. const name = Ctor.options.name || tag
  172. const vnode = new VNode(
  173. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  174. data, undefined, undefined, undefined, context,
  175. { Ctor, propsData, listeners, tag, children },
  176. asyncFactory
  177. )
  178. // Weex specific: invoke recycle-list optimized @render function for
  179. // extracting cell-slot template.
  180. // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  181. /* istanbul ignore if */
  182. if (__WEEX__ && isRecyclableComponent(vnode)) {
  183. return renderRecyclableComponentTemplate(vnode)
  184. }
  185. return vnode
  186. }
  187. export function createComponentInstanceForVnode (
  188. vnode: any, // we know it's MountedComponentVNode but flow doesn't
  189. parent: any, // activeInstance in lifecycle state
  190. parentElm?: ?Node,
  191. refElm?: ?Node
  192. ): Component {
  193. const options: InternalComponentOptions = {
  194. _isComponent: true,
  195. parent,
  196. _parentVnode: vnode,
  197. _parentElm: parentElm || null,
  198. _refElm: refElm || null
  199. }
  200. // check inline-template render functions
  201. const inlineTemplate = vnode.data.inlineTemplate
  202. if (isDef(inlineTemplate)) {
  203. options.render = inlineTemplate.render
  204. options.staticRenderFns = inlineTemplate.staticRenderFns
  205. }
  206. return new vnode.componentOptions.Ctor(options)
  207. }
  208. function installComponentHooks (data: VNodeData) {
  209. const hooks = data.hook || (data.hook = {})
  210. for (let i = 0; i < hooksToMerge.length; i++) {
  211. const key = hooksToMerge[i]
  212. const existing = hooks[key]
  213. const toMerge = componentVNodeHooks[key]
  214. if (existing !== toMerge && !(existing && existing._merged)) {
  215. hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
  216. }
  217. }
  218. }
  219. function mergeHook (f1, f2) {
  220. const merged = (a, b, c, d) => {
  221. f1(a, b, c, d)
  222. f2(a, b, c, d)
  223. }
  224. merged._merged = true
  225. return merged
  226. }
  227. // transform component v-model info (value and callback) into
  228. // prop and event handler respectively.
  229. function transformModel (options, data: any) {
  230. const prop = (options.model && options.model.prop) || 'value'
  231. const event = (options.model && options.model.event) || 'input'
  232. ;(data.props || (data.props = {}))[prop] = data.model.value
  233. const on = data.on || (data.on = {})
  234. if (isDef(on[event])) {
  235. on[event] = [data.model.callback].concat(on[event])
  236. } else {
  237. on[event] = data.model.callback
  238. }
  239. }