ホームページ > 記事 > ウェブフロントエンド > Vue のライフサイクルとソースコード実装 (コード)
この記事が提供する内容は、Vue のライフサイクルとソース コード実装に関するものであり、必要な方は参考にしていただければ幸いです。
学習を通じて、次のような Vue の基本的な構文をすべて学習しました:
1、{{Mustache}} 構文
2. v-if、v-else、v-else-if、v-show
3.v-for
4.vバインド
5.vモデル
6. v-on
これらの文法をすでに覚えている場合は、読み続けてください。これらの文法にあまり習熟していない場合は、前の内容を復習してください。
この章では、Vue のライフサイクルについて学びます。まず、Vue のライフサイクルの定義を見てみましょう。
各 Vue インスタンスは、作成時に一連の初期化プロセスを実行する必要があります。たとえば、データ監視の設定、テンプレートのコンパイル、インスタンスの DOM へのマウント、データ変更時の DOM の更新などが必要です。同時に、ライフサイクルフックと呼ばれるいくつかの関数もこのプロセス中に実行され、ユーザーはさまざまな段階で独自のコードを追加する機会が得られます。
これは Vue 公式 Web サイトに記載されている説明情報です。 簡単に言うと、 Vue がインスタンスを作成してから最終的に完全に破棄するまでの過程で、Vue の現在の状態に対応する一連のメソッドが実行されます。これらのメソッドは It: ライフサイクル フック と呼ばれます。以下のライフ サイクル図を見てみましょう:
上の図では、合計 8 つのライフ サイクル フック関数 が Vue の実行サイクル全体を表しています。現時点では、Vue のバージョンは -2.5.16 です。 Vue には、先ほどの 8 つに加えて、コンポーネントに関するライフサイクル フックが 3 つあり、合計 11 個あります。 フック関数の説明をすべて見てみましょう。また、上の図を使用すると、Vue の実行サイクルをよりよく理解できるようになります。
1. beforeCreate: インスタンスの初期化後、データ オブザーバーおよびイベント/ウォッチャーのイベント設定の前に呼び出されます。
2. created: インスタンスが作成された直後に呼び出されます。このステップで、インスタンスはデータ オブザーバー、プロパティとメソッドの操作、監視/イベント イベント コールバックの構成を完了します。ただし、実装フェーズはまだ開始されておらず、$el
属性は現在表示されていません。
3. beforeMount: マウント前に呼び出されます: 関連するレンダリング関数が初めて呼び出されます。
4. マウント済み: el は新しく作成されます vm.$el
替换,并挂载到实例上去之后调用该钩子。如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$el
也在文档内(PS:注意 mounted 不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick
替换掉 mounted:)。vm.$nextTick
については、次の章で詳しく説明します。
5. beforeUpdate: データが更新されるときに呼び出されます。これは、仮想 DOM にパッチが適用される前に発生します。これは、追加されたイベント リスナーを手動で削除するなど、更新前に既存の DOM にアクセスする場合に適しています。
6. 更新: このフックは、データ変更により仮想 DOM が再レンダリングされ、パッチが適用された後に呼び出されます。このフックが呼び出されると、コンポーネント DOM が更新されるため、DOM に依存する操作を実行できるようになります。ただし、ほとんどの場合、この期間中に状態を変更することは避けるべきです。状態の変化に応答したい場合は、通常、代わりに計算プロパティまたはウォッチャーを使用するのが最善です (追記: 計算プロパティとウォッチャーについては後の章で紹介します)。
7. アクティブ化: キープアライブ コンポーネントがアクティブ化されたときに呼び出されます (追記: コンポーネントに関連して、キープアライブについてはコンポーネントを説明するときに紹介します)。
8. deactivated: keep-alive コンポーネントが非アクティブ化されたときに呼び出されます (追記: コンポーネントに関連して、keep-alive についてはコンポーネントを説明するときに紹介されます)。
9. beforeDestroy: インスタンスが破棄される前に呼び出されます。このステップでは、インスタンスはまだ完全に利用可能です。
10. destroy: Vue インスタンスが破棄された後に呼び出されます。呼び出されると、Vue インスタンスが指すすべてのバインドが解除され、すべてのイベント リスナーが削除され、すべての子インスタンスが破棄されます。
11. errorCaptured (2.5.0 以降の新機能): 子孫コンポーネントからのエラーがキャプチャされたときに呼び出されます。このフックは、エラー オブジェクト、エラーが発生したコンポーネント インスタンス、およびエラーの原因に関する情報を含む文字列の 3 つのパラメータを受け取ります。このフックは、エラーがさらに上方に伝播するのを防ぐために false を返すことができます。
これらは Vue (2.5.16) のライフサイクル フックのすべてです。誰でも理解しやすいように、creation から destroy までのプロセスが Vue のコードでどのように実装されるかを見てみましょう。ここをクリックして、Vue の最新コードをダウンロードできます。
Vue ソース コード
の基本構造を簡単に見てみましょう。 Vue源代码的基础结构
。
. ├── BACKERS.md ├── LICENSE ├── README.md├── benchmarks ├── dist ├── examples ├── flow ├── node_modules ├── package.json├── packages ├── scripts ├── src ├── test ├── types └── yarn.lock
这是下载下来代码之后的一级目录,dist文件夹下为Vue编译之后的代码,我们平时引入的Vue.js文件都在这里
,Vue使用了flow作为JavaScript静态类型检查工具,相关的代码都在flow文件夹下面
,scripts文件夹下面是代码构建的相关配置,Vue主要使用Rollup进行的代码构建
,src文件夹下面就是所有Vue的源代码
。我们这里不对其他的内容进行过多的描述,还是专注于我们的主题,Vue的声明周期代码是如何实现,我们看一下src
文件夹。
. ├── compiler :Vue编译相关 ├── core :Vue的核心代码 ├── platforms :web/weex平台支持,入口文件 ├── server :服务端 ├── sfc :解析.vue文件 └── shared :公共代码
这是我们src
文件夹下的目录结构,而我们Vue生成的地方就在/src/core/instance/index.js
中。
function Vue (options) { if (process.env.NODE_ENV !== 'production' && !(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword') } this._init(options) }
我们可以看到:Vue是一个方法,是一个使用Function来实现的构造函数,所以我们只能通过 new 的方式来去创建Vue的实例。然后通过Vue实例
的_init
方法来进行Vue的初始化。_init
是Vue通过prototype
来实现的一个原型属性
。我们来看一下他的_init
方法实现。
在/src/core/instance/init.js
文件夹下,Vue实现了_init
方法
Vue.prototype._init = function (options?: Object) { ... // expose real self vm._self = vm initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') ... if (vm.$options.el) { vm.$mount(vm.$options.el) } }
我主要看与它生命周期有关的代码,我们可以看到,Vue先调用了initLifecycle(vm)、initEvents(vm)、initRender(vm)
这三个方法,用于初始化生命周期、事件、渲染函数
,这些过程发生在Vue初始化的过程(_init方法)中
,并在调用beforeCreate钩子
之前。
然后Vue通过callHook (vm: Component, hook: string)
方法来去调用钩子函数(hook)
,它接收vm(Vue实例对象),hook(钩子函数名称)
来去执行生命周期函数
。在Vue中几乎所有的钩子(errorCaptured
除外)函数执行都是通过callHook (vm: Component, hook: string)
来调用的。我们来看一下callHook
的代码,在/src/core/instance/lifecycle.js
下:
export function callHook (vm: Component, hook: string) { // #7573 disable dep collection when invoking lifecycle hooks pushTarget() const handlers = vm.$options[hook] if (handlers) { for (let i = 0, j = handlers.length; i < j; i++) { try { handlers[i].call(vm) } catch (e) { handleError(e, vm, `${hook} hook`) } } } if (vm._hasHookEvent) { vm.$emit('hook:' + hook) } popTarget() }
它的逻辑也非常简单,根据传入的hook
从实例中拿到对应的回调函数数组(在/packages/vue-template-compiler/browser.js
下的LIFECYCLE_HOOKS
),然后便利执行。
然后在初始化生命周期、事件、渲染函数
之后调用了beforeCreate钩子
,在这个时候:我们还没有办法获取到data、props
等数据。
在调用了beforeCreate钩子
之后,Vue调用了initInjections(vm)、initState(vm)、initProvide(vm)
这三个方法用于初始化data、props、watcher
等等,在这些初始化执行完成之后,调用了created钩子函数
,在这个时候:我们已经可以获取到data、props
等数据了,但是Vue并没有开始渲染DOM
,所以我们还不能够访问DOM(PS:我们可以通过vm.$nextTick
来访问,在后面的章节我们会详细讲解)。
在调用了created钩子
之后,Vue开始进行DOM的挂载,执行vm.$mount(vm.$options.el)
,在Vue中DOM的挂载就是通过Vue.prototype.$mount
这个原型方法来去实现的。Vue.prototype.$mount
原型方法的声明是在/src/platforms/web/entry-runtime-with-compiler.js
,我们看一下这个代码的实现:
const mount = Vue.prototype.$mount Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && query(el) /* istanbul ignore if */ if (el === document.body || el === document.documentElement) { process.env.NODE_ENV !== 'production' && warn( `Do not mount Vue to <html> or <body> - mount to normal elements instead.` ) return this } const options = this.$options // resolve template/el and convert to render function if (!options.render) { let template = options.template if (template) { if (typeof template === 'string') { if (template.charAt(0) === '#') { template = idToTemplate(template) /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && !template) { warn( `Template element not found or is empty: ${options.template}`, this ) } } } else if (template.nodeType) { template = template.innerHTML } else { if (process.env.NODE_ENV !== 'production') { warn('invalid template option:' + template, this) } return this } } else if (el) { template = getOuterHTML(el) } if (template) { /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile') } const { render, staticRenderFns } = compileToFunctions(template, { shouldDecodeNewlines, shouldDecodeNewlinesForHref, delimiters: options.delimiters, comments: options.comments }, this) options.render = render options.staticRenderFns = staticRenderFns /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { mark('compile end') measure(`vue ${this._name} compile`, 'compile', 'compile end') } } } return mount.call(this, el, hydrating) } /** * Get outerHTML of elements, taking care * of SVG elements in IE as well. */function getOuterHTML (el: Element): string { if (el.outerHTML) { return el.outerHTML } else { const container = document.createElement('p') container.appendChild(el.cloneNode(true)) return container.innerHTML } }
这一部分代码的主要作用:就是进行template模板的解析
。从上面的代码中可以看出,el不允许被挂载到body
和html
这样的根标签上面。然后判断是否有render函数 -> if (!options.render) {...}
,然后判断有没有template
,template可以是string类型的id
、DOM节点
。没有的话则解析el
作为template
。由上面的代码可以看出我们无论是使用单文件组件(.Vue)
或是通过el、template属性
,它最终都会通过render
函数的形式来进行整个模板的解析。
由我们的图示可以看出模板解析完成之后,会调用beforeMount钩子
,那么这个beforeMount钩子
是在哪里被调用的呢?我们接着往下看。$mount
原型方法有一个可复用的设计,在/src/platforms/web/runtime/index.js
// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }これは、コードをダウンロードした後の最初のディレクトリです。
dist フォルダーは、通常紹介する Vue.js ファイルがすべてここにあります
。Vue は使用します。 flow は JavaScript の静的型チェック ツールです。関連するコードは flow フォルダー
の下にあり、Vue はコード構築に主に Rollup を使用します。 code>src フォルダーの下には、Vue のすべてのソース コード があります。ここでは他の内容についてはあまり説明しませんが、Vue のライフサイクル コードがどのように実装されるかというトピックに焦点を当てて、src
フォルダーを見てみましょう。 🎜export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }🎜これは
src
フォルダーの下のディレクトリ構造であり、Vue が生成される場所は /src/core/instance/index.js。 🎜<pre class="brush:js;toolbar:false;">Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
const vm: Component = this
if (vm._isMounted) {
callHook(vm, &#39;beforeUpdate&#39;)
} const prevEl = vm.$el
const prevVnode = vm._vnode
const prevActiveInstance = activeInstance
activeInstance = vm
vm._vnode = vnode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(
vm.$el, vnode, hydrating, false
/* removeOnly */,
vm.$options._parentElm,
vm.$options._refElm
)
// no need for the ref nodes after initial patch
// this prevents keeping a detached DOM tree in memory (#5851)
vm.$options._parentElm = vm.$options._refElm = null
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
} if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent&#39;s updated hook.
}</pre>🎜 わかります:<strong>Vue はメソッドであり、Function を使用して実装されたコンストラクターであるため、new を通じてのみ Vue のインスタンスを作成できます。 </strong>次に、<code>Vue インスタンス
の _init
メソッドを使用して Vue を初期化します。 _init
は、prototype
を通じて Vue によって実装される prototype 属性
です。彼の _init
メソッドの実装を見てみましょう。 🎜🎜 /src/core/instance/init.js
フォルダーで、Vue は _init
メソッドを実装します🎜/** * Flush both queues and run the watchers. */function flushSchedulerQueue () { ... callUpdatedHooks(updatedQueue) ...}function callUpdatedHooks (queue) { let i = queue.length while (i--) { const watcher = queue[i] const vm = watcher.vm if (vm._watcher === watcher && vm._isMounted) { callHook(vm, 'updated') } } }🎜 主にライフサイクルに関連するコードを確認します。
Vue が最初に 3 つのメソッド initLifecycle(vm)、initEvents(vm)、および initRender(vm)
を呼び出して、ライフサイクル、イベント、レンダリング関数
コードを初期化していることがわかります。 > の場合、これらのプロセスは Vue 初期化プロセス (_init メソッド)
内で、beforeCreate フック
を呼び出す前に発生します。 🎜🎜次に、Vue は callHook (vm: Component,ook: string)
メソッドを通じて hook function (hook)
を呼び出し、vm (Vue インスタンス オブジェクト) を受け取ります。 、フック(フック関数名)
でライフサイクル関数
を実行します。 Vue では、ほぼすべてのフック (errorCaptured
を除く) 関数の実行は callHook (vm: Component, hook: string)
を通じて呼び出されます。 /src/core/instance/lifecycle.js
にある callHook
のコードを見てみましょう。 🎜Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { return } callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true // remove self from parent const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // teardown watchers if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // call the last hook... vm._isDestroyed = true // invoke destroy hooks on current rendered tree vm.__patch__(vm._vnode, null) // fire destroyed hook callHook(vm, 'destroyed') // turn off all instance listeners. vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } // release circular reference (#6759) if (vm.$vnode) { vm.$vnode.parent = null } }🎜 受信したメッセージによると、そのロジックも非常に単純です。 hook は、インスタンス (
/packages/vue-template-compiler/browser.js
の LIFECYCLE_HOOKS
) から対応するコールバック関数配列を取得し、それを容易にします。実行。 🎜🎜その後、ライフサイクル、イベント、レンダリング関数
を初期化した後、beforeCreateフック
が呼び出されました。 現時点ではデータを取得する方法がありません。小道具
とその他のデータ。 🎜🎜 beforeCreate フック
を呼び出した後、Vue は 3 つのメソッド initInjections(vm)、initState(vm)、および initProvide(vm)
を呼び出して、 データ、プロパティを初期化します。 、ウォッチャー
など、これらの初期化が完了した後、作成されたフック関数
が呼び出されます。この時点で、すでに data、props
を取得できます。 >データを待っていますが、Vue は DOM のレンダリング
を開始していないため、まだ DOM にアクセスできません (追記: vm.$nextTick
を通じてアクセスできます。次の章で詳しく説明します)。 🎜🎜 作成されたフック
を呼び出した後、Vue は DOM のマウントを開始し、vm.$mount(vm.$options.el )、Vue での DOM マウントは、プロトタイプ メソッド <code>Vue.prototype.$mount
を通じて実装されます。 Vue.prototype.$mount
プロトタイプ メソッドの宣言は /src/platforms/web/entry-runtime-with-compiler.js
にあります。見てみましょう。このコードの実装:🎜export function activateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = false if (isInInactiveTree(vm)) { return } } else if (vm._directInactive) { return } if (vm._inactive || vm._inactive === null) { vm._inactive = false for (let i = 0; i < vm.$children.length; i++) { activateChildComponent(vm.$children[i]) } callHook(vm, 'activated') } } export function deactivateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = true if (isInInactiveTree(vm)) { return } } if (!vm._inactive) { vm._inactive = true for (let i = 0; i < vm.$children.length; i++) { deactivateChildComponent(vm.$children[i]) } callHook(vm, 'deactivated') } }🎜 コードのこの部分の主な機能:
template template
を解析することです。上記のコードからわかるように、el は body
や html
などのルート タグにマウントすることはできません。 次に、render 関数 -> if (!options.render) {...}
があるかどうかを確認し、template
があるかどうかを確認します。 . テンプレートは string type id
、DOM ノード
にすることができます。そうでない場合は、el
を template
として解析します。上記のコードから、単一ファイル コンポーネント (.Vue)
を使用しても、el、テンプレート属性
を使用しても、最終的には が渡されることがわかります。 render
関数を使用して、 テンプレート全体を解析します。 🎜🎜 図からわかるように、テンプレートの解析が完了すると、beforeMount フック
が呼び出されます。では、この beforeMount フック
はどこで呼び出されるのでしょうか。下を見てみましょう。 $mount
プロトタイプ メソッドは再利用可能な設計になっており、/src/platforms/web/runtime/index.js
の下にそのようなコードがあります🎜。// public mount method Vue.prototype.$mount = function ( el?: string | Element, hydrating?: boolean ): Component { el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating) }
这是一个公共的挂载方法,目的是为了被runtime only
版本的Vue直接使用,它调用了mountComponent
方法。我们看一下mountComponent
方法的实现,实现在/src/core/instance/lifecycle.js
下。
export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { vm.$el = el if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } callHook(vm, 'beforeMount') let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } } // we set this to vm._watcher inside the watcher's constructor // since the watcher's initial patch may call $forceUpdate (e.g. inside child // component's mounted hook), which relies on vm._watcher being already defined new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */) hydrating = false // manually mounted instance, call mounted on self // mounted is called for render-created child components in its inserted hook if (vm.$vnode == null) { vm._isMounted = true callHook(vm, 'mounted') } return vm }
由上面的代码可以看出在执行vm._render()
之前,调用了callHook(vm, 'beforeMount')
,这个时候相关的 render 函数首次被调用,调用完成之后,执行了callHook(vm, 'mounted')
方法,标记着el 被新创建的 vm.$el 替换,并被挂载到实例上。
然后就进入了我们页面正常交互的时间
,也就是beforeUpdate
和updated
这两个回调钩子的执行时机。这两个钩子函数是在数据更新的时候进行回调的函数,Vue在/src/core/instance/lifecycle.js
文件下有一个_update
的原型声明:
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { const vm: Component = this if (vm._isMounted) { callHook(vm, 'beforeUpdate') } const prevEl = vm.$el const prevVnode = vm._vnode const prevActiveInstance = activeInstance activeInstance = vm vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render vm.$el = vm.__patch__( vm.$el, vnode, hydrating, false /* removeOnly */, vm.$options._parentElm, vm.$options._refElm ) // no need for the ref nodes after initial patch // this prevents keeping a detached DOM tree in memory (#5851) vm.$options._parentElm = vm.$options._refElm = null } else { // updates vm.$el = vm.__patch__(prevVnode, vnode) } activeInstance = prevActiveInstance // update __vue__ reference if (prevEl) { prevEl.__vue__ = null } if (vm.$el) { vm.$el.__vue__ = vm } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }
我们可以看到在如果_isMounted
为ture
的话(DOM已经被挂载)则会调用callHook(vm, 'beforeUpdate')
方法,然后会对虚拟DOM进行重新渲染。然后在/src/core/observer/scheduler.js
下的flushSchedulerQueue()
函数中渲染DOM,在渲染完成调用callHook(vm, 'updated')
,代码如下:。
/** * Flush both queues and run the watchers. */function flushSchedulerQueue () { ... callUpdatedHooks(updatedQueue) ...}function callUpdatedHooks (queue) { let i = queue.length while (i--) { const watcher = queue[i] const vm = watcher.vm if (vm._watcher === watcher && vm._isMounted) { callHook(vm, 'updated') } } }
当Vue实例需要进行销毁的时候回调beforeDestroy 、destroyed
这两个函数钩子,它们的实现是在/src/core/instance/lifecycle.js
下的Vue.prototype.$destroy
中:
Vue.prototype.$destroy = function () { const vm: Component = this if (vm._isBeingDestroyed) { return } callHook(vm, 'beforeDestroy') vm._isBeingDestroyed = true // remove self from parent const parent = vm.$parent if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) { remove(parent.$children, vm) } // teardown watchers if (vm._watcher) { vm._watcher.teardown() } let i = vm._watchers.length while (i--) { vm._watchers[i].teardown() } // remove reference from data ob // frozen object may not have observer. if (vm._data.__ob__) { vm._data.__ob__.vmCount-- } // call the last hook... vm._isDestroyed = true // invoke destroy hooks on current rendered tree vm.__patch__(vm._vnode, null) // fire destroyed hook callHook(vm, 'destroyed') // turn off all instance listeners. vm.$off() // remove __vue__ reference if (vm.$el) { vm.$el.__vue__ = null } // release circular reference (#6759) if (vm.$vnode) { vm.$vnode.parent = null } }
在$destroy
这个原型函数中,执行了Vue的销毁操作
,我们可以看到在执行销毁操作之前调用了callHook(vm, 'beforeDestroy')
,然后执行了一系列的销毁操作,包括删除掉所有的自身(self)、_watcher、数据引用
等等,删除完成之后调用callHook(vm, 'destroyed')
。
截止到这里,整个Vue生命周期图示中的所有生命周期钩子
都已经被执行完成了。那么剩下的activated、deactivated、errorCaptured
这三个钩子函数是在何时被执行的呢?我们知道这三个函数都是针对于组件(component)
的钩子函数。其中activated、deactivated
这两个钩子函数分别是在keep-alive 组件激活和停用
之后回调的,它们不牵扯到整个Vue的生命周期之中,activated、deactivated
这两个钩子函数的实现代码都在/src/core/instance/lifecycle.js
下:
export function activateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = false if (isInInactiveTree(vm)) { return } } else if (vm._directInactive) { return } if (vm._inactive || vm._inactive === null) { vm._inactive = false for (let i = 0; i < vm.$children.length; i++) { activateChildComponent(vm.$children[i]) } callHook(vm, 'activated') } } export function deactivateChildComponent (vm: Component, direct?: boolean) { if (direct) { vm._directInactive = true if (isInInactiveTree(vm)) { return } } if (!vm._inactive) { vm._inactive = true for (let i = 0; i < vm.$children.length; i++) { deactivateChildComponent(vm.$children[i]) } callHook(vm, 'deactivated') } }
而对于errorCaptured
来说,它是在2.5.0之后新增的一个钩子函数,它的代码在/src/core/util/error.js
中:
export function handleError (err: Error, vm: any, info: string) { if (vm) { let cur = vm while ((cur = cur.$parent)) { const hooks = cur.$options.errorCaptured if (hooks) { for (let i = 0; i < hooks.length; i++) { try { const capture = hooks[i].call(cur, err, vm, info) === false if (capture) return } catch (e) { globalHandleError(e, cur, 'errorCaptured hook') } } } } } globalHandleError(err, vm, info) }function globalHandleError (err, vm, info) { if (config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info) } catch (e) { logError(e, null, 'config.errorHandler') } } logError(err, vm, info) }function logError (err, vm, info) { if (process.env.NODE_ENV !== 'production') { warn(`Error in ${info}: "${err.toString()}"`, vm) } /* istanbul ignore else */ if ((inBrowser || inWeex) && typeof console !== 'undefined') { console.error(err) } else { throw err } }
他是唯一一个没有通过callHook
方法来执行的钩子函数,而是直接通过遍历cur(vm).$options.errorCaptured
,来执行config.errorHandler.call(null, err, vm, info)
的钩子函数。整个逻辑的结构与callHook
使非常类似的。
截止到目前Vue中所有的生命周期钩子我们都已经介绍完成了,其中涉及到了一些源码的基础,是因为我觉得配合源码来一起看的话,会对整个Vue的运行过程有个更好的理解。大家一定要下载下来Vue的源代码,对照着我们的讲解来走一遍这个流程。
相关推荐:
以上がVue のライフサイクルとソースコード実装 (コード)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。