59fef3efa8ebd8e0b84305032f275b518987612d16b5da375b6cdd103b0200f2c94a83caa137f8fb86cd4a8a47ae606a93d91822a4fa368d64b4fd5edb1a9c 9.7 KB

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