UNPKG

9.94 kBJavaScriptView Raw
1/* @flow */
2
3import config from '../config'
4import Watcher from '../observer/watcher'
5import { mark, measure } from '../util/perf'
6import { createEmptyVNode } from '../vdom/vnode'
7import { updateComponentListeners } from './events'
8import { resolveSlots } from './render-helpers/resolve-slots'
9import { toggleObserving } from '../observer/index'
10import { pushTarget, popTarget } from '../observer/dep'
11
12import {
13 warn,
14 noop,
15 remove,
16 emptyObject,
17 validateProp,
18 invokeWithErrorHandling
19} from '../util/index'
20
21export let activeInstance: any = null
22export let isUpdatingChildComponent: boolean = false
23
24export function setActiveInstance(vm: Component) {
25 const prevActiveInstance = activeInstance
26 activeInstance = vm
27 return () => {
28 activeInstance = prevActiveInstance
29 }
30}
31
32export function initLifecycle (vm: Component) {
33 const options = vm.$options
34
35 // locate first non-abstract parent
36 let parent = options.parent
37 if (parent && !options.abstract) {
38 while (parent.$options.abstract && parent.$parent) {
39 parent = parent.$parent
40 }
41 parent.$children.push(vm)
42 }
43
44 vm.$parent = parent
45 vm.$root = parent ? parent.$root : vm
46
47 vm.$children = []
48 vm.$refs = {}
49
50 vm._watcher = null
51 vm._inactive = null
52 vm._directInactive = false
53 vm._isMounted = false
54 vm._isDestroyed = false
55 vm._isBeingDestroyed = false
56}
57
58export function lifecycleMixin (Vue: Class<Component>) {
59 Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
60 const vm: Component = this
61 const prevEl = vm.$el
62 const prevVnode = vm._vnode
63 const restoreActiveInstance = setActiveInstance(vm)
64 vm._vnode = vnode
65 // Vue.prototype.__patch__ is injected in entry points
66 // based on the rendering backend used.
67 if (!prevVnode) {
68 // initial render
69 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
70 } else {
71 // updates
72 vm.$el = vm.__patch__(prevVnode, vnode)
73 }
74 restoreActiveInstance()
75 // update __vue__ reference
76 if (prevEl) {
77 prevEl.__vue__ = null
78 }
79 if (vm.$el) {
80 vm.$el.__vue__ = vm
81 }
82 // if parent is an HOC, update its $el as well
83 if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
84 vm.$parent.$el = vm.$el
85 }
86 // updated hook is called by the scheduler to ensure that children are
87 // updated in a parent's updated hook.
88 }
89
90 Vue.prototype.$forceUpdate = function () {
91 const vm: Component = this
92 if (vm._watcher) {
93 vm._watcher.update()
94 }
95 }
96
97 Vue.prototype.$destroy = function () {
98 const vm: Component = this
99 if (vm._isBeingDestroyed) {
100 return
101 }
102 callHook(vm, 'beforeDestroy')
103 vm._isBeingDestroyed = true
104 // remove self from parent
105 const parent = vm.$parent
106 if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
107 remove(parent.$children, vm)
108 }
109 // teardown watchers
110 if (vm._watcher) {
111 vm._watcher.teardown()
112 }
113 let i = vm._watchers.length
114 while (i--) {
115 vm._watchers[i].teardown()
116 }
117 // remove reference from data ob
118 // frozen object may not have observer.
119 if (vm._data.__ob__) {
120 vm._data.__ob__.vmCount--
121 }
122 // call the last hook...
123 vm._isDestroyed = true
124 // invoke destroy hooks on current rendered tree
125 vm.__patch__(vm._vnode, null)
126 // fire destroyed hook
127 callHook(vm, 'destroyed')
128 // turn off all instance listeners.
129 vm.$off()
130 // remove __vue__ reference
131 if (vm.$el) {
132 vm.$el.__vue__ = null
133 }
134 // release circular reference (#6759)
135 if (vm.$vnode) {
136 vm.$vnode.parent = null
137 }
138 }
139}
140
141export function mountComponent (
142 vm: Component,
143 el: ?Element,
144 hydrating?: boolean
145): Component {
146 vm.$el = el
147 if (!vm.$options.render) {
148 vm.$options.render = createEmptyVNode
149 if (process.env.NODE_ENV !== 'production') {
150 /* istanbul ignore if */
151 if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
152 vm.$options.el || el) {
153 warn(
154 'You are using the runtime-only build of Vue where the template ' +
155 'compiler is not available. Either pre-compile the templates into ' +
156 'render functions, or use the compiler-included build.',
157 vm
158 )
159 } else {
160 warn(
161 'Failed to mount component: template or render function not defined.',
162 vm
163 )
164 }
165 }
166 }
167 callHook(vm, 'beforeMount')
168
169 let updateComponent
170 /* istanbul ignore if */
171 if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
172 updateComponent = () => {
173 const name = vm._name
174 const id = vm._uid
175 const startTag = `vue-perf-start:${id}`
176 const endTag = `vue-perf-end:${id}`
177
178 mark(startTag)
179 const vnode = vm._render()
180 mark(endTag)
181 measure(`vue ${name} render`, startTag, endTag)
182
183 mark(startTag)
184 vm._update(vnode, hydrating)
185 mark(endTag)
186 measure(`vue ${name} patch`, startTag, endTag)
187 }
188 } else {
189 updateComponent = () => {
190 vm._update(vm._render(), hydrating)
191 }
192 }
193
194 // we set this to vm._watcher inside the watcher's constructor
195 // since the watcher's initial patch may call $forceUpdate (e.g. inside child
196 // component's mounted hook), which relies on vm._watcher being already defined
197 new Watcher(vm, updateComponent, noop, {
198 before () {
199 if (vm._isMounted && !vm._isDestroyed) {
200 callHook(vm, 'beforeUpdate')
201 }
202 }
203 }, true /* isRenderWatcher */)
204 hydrating = false
205
206 // manually mounted instance, call mounted on self
207 // mounted is called for render-created child components in its inserted hook
208 if (vm.$vnode == null) {
209 vm._isMounted = true
210 callHook(vm, 'mounted')
211 }
212 return vm
213}
214
215export function updateChildComponent (
216 vm: Component,
217 propsData: ?Object,
218 listeners: ?Object,
219 parentVnode: MountedComponentVNode,
220 renderChildren: ?Array<VNode>
221) {
222 if (process.env.NODE_ENV !== 'production') {
223 isUpdatingChildComponent = true
224 }
225
226 // determine whether component has slot children
227 // we need to do this before overwriting $options._renderChildren.
228
229 // check if there are dynamic scopedSlots (hand-written or compiled but with
230 // dynamic slot names). Static scoped slots compiled from template has the
231 // "$stable" marker.
232 const newScopedSlots = parentVnode.data.scopedSlots
233 const oldScopedSlots = vm.$scopedSlots
234 const hasDynamicScopedSlot = !!(
235 (newScopedSlots && !newScopedSlots.$stable) ||
236 (oldScopedSlots !== emptyObject && !oldScopedSlots.$stable) ||
237 (newScopedSlots && vm.$scopedSlots.$key !== newScopedSlots.$key) ||
238 (!newScopedSlots && vm.$scopedSlots.$key)
239 )
240
241 // Any static slot children from the parent may have changed during parent's
242 // update. Dynamic scoped slots may also have changed. In such cases, a forced
243 // update is necessary to ensure correctness.
244 const needsForceUpdate = !!(
245 renderChildren || // has new static slots
246 vm.$options._renderChildren || // has old static slots
247 hasDynamicScopedSlot
248 )
249
250 vm.$options._parentVnode = parentVnode
251 vm.$vnode = parentVnode // update vm's placeholder node without re-render
252
253 if (vm._vnode) { // update child tree's parent
254 vm._vnode.parent = parentVnode
255 }
256 vm.$options._renderChildren = renderChildren
257
258 // update $attrs and $listeners hash
259 // these are also reactive so they may trigger child update if the child
260 // used them during render
261 vm.$attrs = parentVnode.data.attrs || emptyObject
262 vm.$listeners = listeners || emptyObject
263
264 // update props
265 if (propsData && vm.$options.props) {
266 toggleObserving(false)
267 const props = vm._props
268 const propKeys = vm.$options._propKeys || []
269 for (let i = 0; i < propKeys.length; i++) {
270 const key = propKeys[i]
271 const propOptions: any = vm.$options.props // wtf flow?
272 props[key] = validateProp(key, propOptions, propsData, vm)
273 }
274 toggleObserving(true)
275 // keep a copy of raw propsData
276 vm.$options.propsData = propsData
277 }
278
279 // update listeners
280 listeners = listeners || emptyObject
281 const oldListeners = vm.$options._parentListeners
282 vm.$options._parentListeners = listeners
283 updateComponentListeners(vm, listeners, oldListeners)
284
285 // resolve slots + force update if has children
286 if (needsForceUpdate) {
287 vm.$slots = resolveSlots(renderChildren, parentVnode.context)
288 vm.$forceUpdate()
289 }
290
291 if (process.env.NODE_ENV !== 'production') {
292 isUpdatingChildComponent = false
293 }
294}
295
296function isInInactiveTree (vm) {
297 while (vm && (vm = vm.$parent)) {
298 if (vm._inactive) return true
299 }
300 return false
301}
302
303export function activateChildComponent (vm: Component, direct?: boolean) {
304 if (direct) {
305 vm._directInactive = false
306 if (isInInactiveTree(vm)) {
307 return
308 }
309 } else if (vm._directInactive) {
310 return
311 }
312 if (vm._inactive || vm._inactive === null) {
313 vm._inactive = false
314 for (let i = 0; i < vm.$children.length; i++) {
315 activateChildComponent(vm.$children[i])
316 }
317 callHook(vm, 'activated')
318 }
319}
320
321export function deactivateChildComponent (vm: Component, direct?: boolean) {
322 if (direct) {
323 vm._directInactive = true
324 if (isInInactiveTree(vm)) {
325 return
326 }
327 }
328 if (!vm._inactive) {
329 vm._inactive = true
330 for (let i = 0; i < vm.$children.length; i++) {
331 deactivateChildComponent(vm.$children[i])
332 }
333 callHook(vm, 'deactivated')
334 }
335}
336
337export function callHook (vm: Component, hook: string) {
338 // #7573 disable dep collection when invoking lifecycle hooks
339 pushTarget()
340 const handlers = vm.$options[hook]
341 const info = `${hook} hook`
342 if (handlers) {
343 for (let i = 0, j = handlers.length; i < j; i++) {
344 invokeWithErrorHandling(handlers[i], vm, null, vm, info)
345 }
346 }
347 if (vm._hasHookEvent) {
348 vm.$emit('hook:' + hook)
349 }
350 popTarget()
351}