ホームページ  >  記事  >  ウェブフロントエンド  >  Vue のライフサイクルとソースコード実装 (コード)

Vue のライフサイクルとソースコード実装 (コード)

不言
不言オリジナル
2018-09-07 16:57:482244ブラウズ

この記事が提供する内容は、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: ライフサイクル フック と呼ばれます。以下のライフ サイクル図を見てみましょう:

Vue のライフサイクルとソースコード実装 (コード)

上の図では、合計 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(&#39;hook:&#39; + 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 !== &#39;production&#39; && 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 === &#39;string&#39;) {        
    if (template.charAt(0) === &#39;#&#39;) {
          template = idToTemplate(template)          
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== &#39;production&#39; && !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 !== &#39;production&#39;) {
          warn(&#39;invalid template option:&#39; + template, this)
        }        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }    if (template) {      
    /* istanbul ignore if */
      if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
        mark(&#39;compile&#39;)
      }      
      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 !== &#39;production&#39; && config.performance && mark) {
        mark(&#39;compile end&#39;)
        measure(`vue ${this._name} compile`, &#39;compile&#39;, &#39;compile end&#39;)
      }
    }
  }  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(&#39;p&#39;)
    container.appendChild(el.cloneNode(true))    
    return container.innerHTML
  }
}

这一部分代码的主要作用:就是进行template模板的解析。从上面的代码中可以看出,el不允许被挂载到bodyhtml这样的根标签上面。然后判断是否有render函数 -> if (!options.render) {...},然后判断有没有template,template可以是string类型的idDOM节点。没有的话则解析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 !== &#39;production&#39;) {      
    /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== &#39;#&#39;) ||
        vm.$options.el || el) {
        warn(          
        &#39;You are using the runtime-only build of Vue where the template &#39; +          
        &#39;compiler is not available. Either pre-compile the templates into &#39; +          
        &#39;render functions, or use the compiler-included build.&#39;,
          vm
        )
      } else {
        warn(          
        &#39;Failed to mount component: template or render function not defined.&#39;,
          vm
        )
      }
    }
  }
  callHook(vm, &#39;beforeMount&#39;)

  let updateComponent  
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== &#39;production&#39; && 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&#39;s constructor
  // since the watcher&#39;s initial patch may call $forceUpdate (e.g. inside child
  // component&#39;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, &#39;mounted&#39;)
  }  
  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, &amp;#39;beforeUpdate&amp;#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 &amp;&amp; vm.$parent &amp;&amp; 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&amp;#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, &#39;updated&#39;)
    }
  }
}
🎜 主にライフサイクルに関連するコードを確認します。 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, &#39;beforeDestroy&#39;)
    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, &#39;destroyed&#39;)    
    // 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.jsLIFECYCLE_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, &#39;activated&#39;)
  }
}

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, &#39;deactivated&#39;)
  }
}
🎜 コードのこの部分の主な機能:template template を解析することです。上記のコードからわかるように、el は bodyhtml などのルート タグにマウントすることはできません。 次に、render 関数 -> if (!options.render) {...} があるかどうかを確認し、template があるかどうかを確認します。 . テンプレートは string type idDOM ノード にすることができます。そうでない場合は、eltemplate として解析します。上記のコードから、単一ファイル コンポーネント (.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 !== &#39;production&#39;) {      
    /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== &#39;#&#39;) ||
        vm.$options.el || el) {
        warn(          
        &#39;You are using the runtime-only build of Vue where the template &#39; +          
        &#39;compiler is not available. Either pre-compile the templates into &#39; +          
        &#39;render functions, or use the compiler-included build.&#39;,
          vm
        )
      } else {
        warn(          
        &#39;Failed to mount component: template or render function not defined.&#39;,
          vm
        )
      }
    }
  }
  callHook(vm, &#39;beforeMount&#39;)

  let updateComponent  
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== &#39;production&#39; && 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&#39;s constructor
  // since the watcher&#39;s initial patch may call $forceUpdate (e.g. inside child
  // component&#39;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, &#39;mounted&#39;)
  }  
  return vm
}

由上面的代码可以看出在执行vm._render()之前,调用了callHook(vm, 'beforeMount'),这个时候相关的 render 函数首次被调用,调用完成之后,执行了callHook(vm, 'mounted')方法,标记着el 被新创建的 vm.$el 替换,并被挂载到实例上。

然后就进入了我们页面正常交互的时间,也就是beforeUpdateupdated这两个回调钩子的执行时机。这两个钩子函数是在数据更新的时候进行回调的函数,Vue在/src/core/instance/lifecycle.js文件下有一个_update的原型声明:

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.
  }

我们可以看到在如果_isMountedture的话(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, &#39;updated&#39;)
    }
  }
}

当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, &#39;beforeDestroy&#39;)
    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, &#39;destroyed&#39;)    
    // 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, &#39;activated&#39;)
  }
}

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, &#39;deactivated&#39;)
  }
}

而对于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, &#39;errorCaptured hook&#39;)
          }
        }
      }
      
    }
  }
  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, &#39;config.errorHandler&#39;)
    }
  }
  logError(err, vm, info)
}function logError (err, vm, info) {
  if (process.env.NODE_ENV !== &#39;production&#39;) {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console !== &#39;undefined&#39;) {
    console.error(err)
  } else {    
  throw err
  }
}

他是唯一一个没有通过callHook方法来执行的钩子函数,而是直接通过遍历cur(vm).$options.errorCaptured,来执行config.errorHandler.call(null, err, vm, info)的钩子函数。整个逻辑的结构与callHook使非常类似的。

截止到目前Vue中所有的生命周期钩子我们都已经介绍完成了,其中涉及到了一些源码的基础,是因为我觉得配合源码来一起看的话,会对整个Vue的运行过程有个更好的理解。大家一定要下载下来Vue的源代码,对照着我们的讲解来走一遍这个流程。

相关推荐:

vue生命周期、vue实例、模板语法

图概PHP生命周期,PHP生命周期

以上がVue のライフサイクルとソースコード実装 (コード)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。