803ea142f602d9c7b59aa455d40cc01fb31596d443dfe087b6caedba386cc31684e99c6d3536533570661dc82b9b150b66a38baf386569810e5181d9bd5cc1 7.6 KB

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