今回は、vue+update()の詳しい使い方と、vue+update()を使う際の注意点について、具体的な事例をお届けします。
はじめに
3 か月前、レスポンシブ データを実現する方法を分析するために vue のソース コードを読みました。最後に、Watcher の update() を分析しました。データが変更された後にメソッドが呼び出されます。それでは、3 か月後に何が行われたかを見てみましょう (私は過去 3 か月で React-native を使用したプロジェクトを実行しましたが、それを要約するつもりはありません)。
この記事の説明方法はツリーバインです。私がチェックした vue のバージョンは、コメントを記録するためにソースコードのコピーをフォークしたものです。
目的
調査の方向性を明確にすることによってのみ、目標を達成することができます。まず、ターゲットの動作: データ変更後にビューを更新するために実行されるメソッドについて説明します。方向を確認し、Vue ソース コードの入り口から答えを探し始めます
前の結論から始めましょう
まず前の結論を確認してみましょう:
Vue が構築されると、Observer オブジェクトがデータ (および一部) 上に作成されます。他のフィールド) ゲッターとセッターがインターセプトされます。ゲッターは依存関係の収集をトリガーし、セッターは通知をトリガーします。ウォッチを登録すると、ウォッチ オブジェクトのゲッターがトリガーされます。依存関係を現在の Watcher の deps に収集します。dep のセッターがトリガーされると、現在の Watcher は Watcher の update() メソッドを呼び出すように通知されます
次に、レンダリング関連の登録から開始します。 Watcher。 src/core/instance/lifecycle.js でファイルが見つかりました。
new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
レンダリング関連の Watcher が mountComponent() メソッドで呼び出され、そこでこのメソッドが呼び出される場所を検索します。 src/platforms/web/runtime/index.js と src/platforms/weex/runtime/index.js の 2 か所だけです。Web を例に挙げます。 Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
以下は、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 で isRenderWatcher を調べました:
if (isRenderWatcher) { vm._watcher = this }
はい、コピーして初めて Watcher で使用しました。パッチを適用するときの何か (コメントからは、それが何のためのものなのかまだわかりません)
それから、未解決の問題が 1 つだけあります。それは、Watcher 関数のコンストラクターの updateComponent
です。 2 番目のパラメータとして渡されると、この関数はウォッチャーのゲッターになります。賢明な方なら、ウォッチャー内で依存関係を確立するためにビュー内のすべてのデータのゲッターを呼び出す必要があることを推測しているはずです。ビューはデータの変更に応答します。updateComponent = () => { vm._update(vm._render(), hydrating) }
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 } }
vnode = render.call(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中文网其它相关文章!
推荐阅读:
以上がvue+update()の使い方の詳しい説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。