ホームページ  >  記事  >  ウェブフロントエンド  >  Vue データ コントロール ビューのソース コード分析

Vue データ コントロール ビューのソース コード分析

亚连
亚连オリジナル
2018-05-28 16:49:371693ブラウズ

この記事では、Vue データ コントロール ビューのソース コードと注釈付きの重要なポイントの詳細な分析を提供します。興味のある方は参照してください。

vue がデータの変更を実装し、ビューを更新する方法を分析する

前書き

この記事は、レスポンシブ データを実現する方法を分析するために、vue のソース コードを読みました。データ変更後に Watcher の update() メソッドが呼び出されることが分析されたため、3 か月後に引き続き update() が何を行うかを見てみましょう (私は過去 3 年間、react-native を使用してプロジェクトを構築しました。 (単純すぎるので)

この記事の説明方法は、ソースコードを参照することです。私が確認した vue のバージョンは 2.5.2 です。コメントを記録するためにソース コードのコピーをフォークしました

目的

調査の方向性を明確にすることによってのみ、目標を達成することができます。つまり、その後ビューを更新するためにどのようなメソッドが実行されるかについて説明します。データが変更されたら、この方向で vue のソース コードの入り口から答えを探し始める準備をします。

前の結論から始めます

まず、前の結論を確認してください:

vue が構築されると、 Observer オブジェクトはデータ (およびその他のフィールド) 上に作成され、getter と setter がインターセプトされ、getter は依存関係のコレクションをトリガーし、setter は通知をトリガーします。


もう 1 つのオブジェクトは Watcher です。ウォッチを登録すると、ウォッチ オブジェクトは次のようになります。一度呼び出されると、監視オブジェクトの getter がトリガーされ、依存関係が現在の Watcher の deps に収集されます。いずれかの dep setter がトリガーされると、現在の Watcher に Watcher の update() メソッドを呼び出すように通知されます。レンダリング関連の Watcher を登録しています。


ファイルは src/core/instance/lifecycle.js にあります。

new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)

mountComponent

レンダリング関連の Watcher は、mountComponent() メソッドで呼び出されます。なので、このメソッドが呼び出される場所は 2 つだけです。Web を例にすると、src/platforms/web/runtime/index.js と src/platforms/weex/runtime/index.js です。 web と weex のレンダリング オブジェクト (何weex は以前に他の記事で紹介しました) とは異なるため、パブリッシュ時に別のファイルを導入する必要があり、最終的なパブリッシュは異なりません (この問題は後で vue のプロセス全体を検討することにします)。 以下は mountComponent メソッドです:

Vue.prototype.$mount = function (
 el?: string | Element,
 hydrating?: boolean
): Component {
 el = el && inBrowser ? query(el) : undefined
 return mountComponent(this, el, hydrating)
}

このコードは実際には 3 つのことだけを行います:

beforeMount フックを呼び出す

Watcher を作成する

マウントされたフックを呼び出す
  • ( (笑) そして、肝心なのは実際に Watcher を作成することです。
  • Watcher のパラメーターを見てください: vm はこれ、updateComponent は関数、noop は空、null は Empty、true は RenderWatcher を意味します

    Watcher の isRenderWatcher を調べました。
  • export function mountComponent (
     vm: Component,
     el: ?Element,
     hydrating?: boolean
    ): Component {
     vm.$el = el // 放一份el到自己的属性里
     if (!vm.$options.render) { // render应该经过处理了, 因为我们经常都是用template或者vue文件
     // 判断是否存在render函数, 如果没有就把render函数写成空VNode来避免红错, 并报出黄错
     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) {
     // 不看这里的代码了, 直接看else里的, 行为是一样的
     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
     // 注册一个Watcher
     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
    }

はい、ウォッチャーが初めてパッチを適用するときに何かを判断するためにコピーを作成しただけです(コメントから)、それが何のためのものなのかはまだわかりません)。

それから、1つだけあります未解決の問題、それが updateComponent です。

updateComponent

Watcher のコンストラクターの 2 番目のパラメーターに関数が渡され、この関数が Watcher のゲッターになることは、賢明な方ならおわかりでしょう。ビュー内のすべてのデータのゲッターは、この updateComponent で呼び出す必要があります。これにより、ウォッチャーで依存関係を確立し、ビューがデータの変更に応答できるようになります。

if (isRenderWatcher) {
  vm._watcher = this
 }

その後、 vm._update()と vm._render(). src/core/instance/render.js で ._render() メソッドを見つけました。

updateComponent = () => {
  vm._update(vm._render(), hydrating)
 }

このメソッドは次のことを行います:

render メソッドに基づいて VNode を生成します現在の VM の (レンダリング メソッドはテンプレートまたは vue ファイルからコンパイルされる可能性があるため、レンダリング メソッドを直接記述するのが最も効率的であると推測されます)

レンダリング メソッドに問題がある場合は、それを呼び出します。最初の renderError メソッドで、それでも失敗する場合は、最後の vnode または null を読み取り、親ノードがある場合は、それを独自の .parent 属性に入れます

したがって、核心は次の文です。
  • Vue.prototype._render = function (): VNode {
     const vm: Component = this
     const { render, _parentVnode } = vm.$options // todo: render和_parentVnode的由来
    
     // reset _rendered flag on slots for duplicate slot check
     if (process.env.NODE_ENV !== 'production') {
      for (const key in vm.$slots) {
      // $flow-disable-line
      vm.$slots[key]._rendered = false
      }
     }
    
     if (_parentVnode) {
      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject
     }
    
     // set parent vnode. this allows render functions to have access
     // to the data on the placeholder node.
     vm.$vnode = _parentVnode
     // render self
     let vnode
     try {
      vnode = render.call(vm._renderProxy, vm.$createElement)
     } catch (e) {
      // catch其实不需要看了, 都是做异常处理, _vnode是在vm._update的时候保存的, 也就是上次的状态或是null(init的时候给的)
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
      if (vm.$options.renderError) {
       try {
       vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
       } catch (e) {
       handleError(e, vm, `renderError`)
       vnode = vm._vnode
       }
      } else {
       vnode = vm._vnode
      }
      } else {
      vnode = vm._vnode
      }
     }
     // return empty vnode in case the render function errored out
     if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
      warn(
       'Multiple root nodes returned from render function. Render function ' +
       'should return a single root node.',
       vm
      )
      }
      vnode = createEmptyVNode()
     }
     // set parent
     vnode.parent = _parentVnode
     return vnode
     }
    }

  • render()、vm._renderProxy、vm.$createElement が何なのかわかりません。

    まず vm._renderProxy を見てみましょう。これは initMixin() 中に設定され、vm を返します。実稼働環境では、開発環境はエージェントを返します。その後、vm.$createElement のコードが vdom フォルダーにあるので、それをデバッグできると考えます。見てみると、VNode.
  • render は少し複雑です。簡単に言うと、テンプレートまたは vue の単一ファイルを解析して、ターゲットを render 関数にマウントするということです。

    簡単な要約: 現在の vm 関数のレンダリングに従って、vm._render() の戻り値は VNode です
  • 次に、vm._update() を見てみましょう

  • vnode = render.call(vm._renderProxy, vm.$createElement)

気になる部分実際には __patch() の部分です。 __patch() は dom に対して操作を実行し、それが _update() 内にあるかどうかを判断します。そうである場合は、新しい dom を作成します。そうでない場合は、古い dom と新しい dom を渡します。ノードを比較してから操作します。

結論

Vue のビューのレンダリングは特別な種類の Watcher であり、関数を実行するプロセスは render 関数を呼び出します (テンプレートには監視されたデータが含まれています)。そのため、テンプレート内の観測データが変更されると、Watcher の update() メソッドがトリガーされて、ビューが再レンダリングされます。

レガシー

レンダリング関数はどこでコンパイルされていますか
vue ソース コードがリリースされると、さまざまなプラットフォームが導入して最終的に印刷しました dist
__patch__ の作成プロセスと VNode

の分析について 以上、皆さんの参考になれば幸いです。

関連記事:

Ajaxクロスドメイン(基本ドメイン名は同じ)フォーム送信方法
​​

FirefoxをベースにしたAjax画像アップロード

Ajax読み込み外部ページポップアップレイヤー効果実装方法

以上がVue データ コントロール ビューのソース コード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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