Vue はデータ変更を操作し、ビューを更新します

php中世界最好的语言
リリース: 2018-05-03 10:28:05
オリジナル
2179 人が閲覧しました

今回はvueの操作データが変更されたときのビューの更新についてお届けします。 vueの操作データが変更されてビューが更新されたときの注意事項は次のとおりです。

はじめに

3 か月前、レスポンシブ データを実現する方法を分析するために vue のソース コードを読みました。最後に、Watcher の update() を分析しました。データが変更された後にこのメソッドが呼び出されます。それでは、3 か月後に update() が何をするのかを見てみましょう (私は過去 3 か月で React-native を使用したプロジェクトを実行しましたが、それを要約するつもりはありません。単純すぎるようです)。

この記事の説明スタイル Tree vine について詳しく知るために、私がチェックした vue のバージョンは 2.5.2 でした。コメントを記録するためのソース コード。

目的

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

前の結論から始めましょう

前の結論を復習しましょう 結論:

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

もう 1 つのオブジェクトは Watcher であり、watch を登録します。watch オブジェクトは 1 回呼び出され、これにより watch オブジェクトの getter がトリガーされ、収集が行われます。現在の Watcher の deps に依存関係を追加します。 dep セッターがトリガーされると、現在の Watcher は Watcher の update() メソッドを呼び出すように通知されます。

そこで、まずレンダリング関連の Watcher を登録します。 src/core/instance/lifecycle.js のファイル

new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
ログイン後にコピー

mountComponent

レンダリング関連の Watcher が mountComponent() メソッドで呼び出されているので、このメソッドが呼び出される場所は 2 か所だけです。 src/platforms/web/runtime/index.js と src/platforms/weex/runtime/index.js、Web を例に挙げます:

Vue.prototype.$mount = function (
 el?: string | Element,
 hydrating?: boolean
): Component {
 el = el && inBrowser ? query(el) : undefined
 return mountComponent(this, el, hydrating)
}
ログイン後にコピー

以上です。mountComponent() を呼び出す $mount() メソッドです。 (または、vue の構築中に el フィールドを指定すると、自動的に $mount() メソッドが呼び出されます)。web と weex (weex とは? 以前の他の記事で紹介されました) (合格) レンダリングの主題が異なるため、異なるファイルを使用する必要があります。パブリッシュ時に導入され、最終的には別の dist としてパブリッシュされます (この問題は後で vue のプロセス全体を検討する必要があります)

以下は mountComponent メソッドです:

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
}
ログイン後にコピー

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

    呼び出しbeforeMount フック
  • Watcher を作成します
  • マウントされたフックを呼び出します
  • (笑) 次に、中心となるのは Watcher を作成することです

見てください Watcher のパラメータ: vm はこれ、updateComponent は a function、noop は空、null は空、true は RenderWatcher を意味します。

Watcher で isRenderWatcher を調べました:

if (isRenderWatcher) {
  vm._watcher = this
 }
ログイン後にコピー

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

次に、未解決の問題が 1 つだけあります。それは、Watcher の

updateComponent

の 2 番目のパラメーターです。関数を使用すると、この関数がウォッチャーのゲッターになります。賢明な方であれば、ウォッチャーがビュー内で依存関係を確立する前に、ビュー内のすべてのデータのゲッターをこの updateComponent で呼び出す必要があることを推測しているはずです。データの変更に応答できます。

updateComponent = () => {
  vm._update(vm._render(), hydrating)
 }
ログイン後にコピー

次に、vm._update() と vm._render() に移動します。 src/core/instance/render.js に ._render() メソッドが見つかりました

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
 }
}
ログイン後にコピー

このメソッドは、 :

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

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

  • 最後に VNode に戻ります

  • それで、核心はこの文です:

    vnode = render.call(vm._renderProxy, vm.$createElement)
    ログイン後にコピー
  • render()、vm._renderProxy、vm.$createElement が何なのかわかりません。
  • 先看vm._renderProxy: 是initMixin()的时候设置的, 在生产环境返回vm, 开发环境返回代理, 那么我们认为他是一个可以debug的vm(就是vm), 细节之后再看.

    vm.$createElement的代码在vdom文件夹下, 看了下是一个方法, 返回值一个VNode.

    render有点复杂, 能不能以后研究, 总之就是把template或者vue单文件和mount目标parse成render函数.

    小总结: vm._render()的返回值是VNode, 根据当前vm的render函数

    接下来看vm._update()

    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
     const vm: Component = this
     if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')
     }
     // 记录update之前的状态
     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) { // 初次加载, 只有_update方法更新vm._vnode, 初始化是null
      // initial render
      vm.$el = vm.patch( // patch创建新dom
      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) // patch更新dom
     }
     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.
     }
    ログイン後にコピー

    我们关心的部分其实就是patch()的部分, patch()做了对dom的操作, 在_update()里判断了是否是初次调用, 如果是的话创建新dom, 不是的话传入新旧node进行比较再操作.

    结论

    vue的视图渲染是一种特殊的Watcher, watch的内容是一个函数, 函数运行的过程调用了render函数, render又是由template或者el的dom编译成的(template中含有一些被observe的数据). 所以template中被observe的数据有变化触发Watcher的update()方法就会重新渲染视图.

    遗留

    render函数是在哪里被编译的
    vue源码发布时引入不同平台最后打成dist的流程是什么
    patch和VNode的分析

    相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

    推荐阅读:

    jQuery实现输入文字超过规定行数时自动添加省略号

    ElTableColumn添加搜索归纳功能

以上がVue はデータ変更を操作し、ビューを更新しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート
私たちについて 免責事項 Sitemap
PHP中国語ウェブサイト:福祉オンライン PHP トレーニング,PHP 学習者の迅速な成長を支援します!