UNPKG

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