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