lifecycle.js 8.9 KB

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