这次给大家带来vue+update()使用详解,vue+update()使用的注意事项有哪些,下面就是实战案例,一起来看一下。
前记
三个月前看了vue源码来分析如何做到响应式数据的, 文章名字叫vue源码之响应式数据, 最后分析到, 数据变化后会调用Watcher的update()方法. 那么时隔三月让我们继续看看做了什么. (这三个月用react-native做了个项目, 也无心总结了, 因为好像太简单了).
本文叙事方式为树藤摸瓜, 顺着看源码的逻辑走一遍, 查看的vue的版本为2.5.2. 我fork了一份源码用来记录注释.
目的
明确调查方向才能直至目标, 先说一下目标行为: 数据变化以后执行了什么方法来更新视图的. 那么准备开始以这个方向为目标从vue源码的入口开始找答案.
从之前的结论开始
先来复习一下之前的结论:
vue构造的时候会在data(和一些别的字段)上建立Observer对象, getter和setter被做了拦截, getter触发依赖收集, setter触发notify.
另一个对象是Watcher, 注册watch的时候会调用一次watch的对象, 这样触发了watch对象的getter, 把依赖收集到当前Watcher的deps里, 当任何dep的setter被触发就会notify当前Watcher来调用Watcher的update()方法.
那么这里就从注册渲染相关的Watcher开始.
找到了文件在src/core/instance/lifecycle.js中.
1 | new Watcher(vm, updateComponent, noop, null, true )
|
Salin selepas log masuk
mountComponent
渲染相关的Watcher是在mountComponent()这个方法中调用的, 那么我们搜一下这个方法是在哪里调用的. 只有2处, 分别是src/platforms/web/runtime/index.js和src/platforms/weex/runtime/index.js, 以web为例:
1 2 3 4 5 6 7 | Vue.prototype. $mount = function (
el?: string | Element,
hydrating?: boolean
): Component {
el = el && inBrowser ? query(el) : undefined
return mountComponent(this, el, hydrating)
}
|
Salin selepas log masuk
原来如此, 是$mount()方法调用了mountComponent(), (或者在vue构造时指定el字段也会自动调用$mount()方法), 因为web和weex(什么是weex?之前别的文章介绍过)渲染的标的物不同, 所以在发布的时候应该引入了不同的文件最后发不成不同的dist(这个问题留给之后来研究vue的整个流程).
下面是mountComponent方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | 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' ) {
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
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)
}
}
new Watcher(vm, updateComponent, noop, null, true )
hydrating = false
if (vm. $vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted' )
}
return vm
}
|
Salin selepas log masuk
这段代码其实只做了3件事:
调用beforeMount钩子
建立Watcher
调用mounted钩子
(哈哈哈)那么其实核心就是建立Watcher了.
看一下Watcher的参数: vm是this, updateComponent是一个函数, noop是空, null是空, true代表是RenderWatcher.
在Watcher里看了isRenderWatcher:
1 2 3 | if (isRenderWatcher) {
vm._watcher = this
}
|
Salin selepas log masuk
是的, 只是复制了一份用来在watcher第一次patch的时候判断一些东西(从注释里看的, 我现在还不知道是干嘛的).
那么只有一个问题没解决就是updateComponent是个什么东西.
updateComponent
在Watcher的构造函数的第二个参数传了function, 那么这个函数就成了watcher的getter. 聪明的你应该已经猜到, 在这个updateComponent里一定调用了视图中所有的数据的getter, 才能在watcher中建立依赖从而让视图响应数据的变化.
1 2 3 | updateComponent = () => {
vm._update(vm._render(), hydrating)
}
|
Salin selepas log masuk
那么就去找vm._update()和vm._render().
在src/core/instance/render.js找到了._render()方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | Vue.prototype._render = function (): VNode {
const vm: Component = this
const { render, _parentVnode } = vm. $options
if (process.env.NODE_ENV !== 'production' ) {
for ( const key in vm. $slots ) {
vm. $slots [key]._rendered = false
}
}
if (_parentVnode) {
vm. $scopedSlots = _parentVnode.data.scopedSlots || emptyObject
}
vm. $vnode = _parentVnode
let vnode
try {
vnode = render.call(vm._renderProxy, vm. $createElement )
} catch (e) {
handleError(e, vm, `render`)
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
}
}
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()
}
vnode.parent = _parentVnode
return vnode
}
}
|
Salin selepas log masuk
这个方法做了:
根据当前vm的render方法来生成VNode. (render方法可能是根据template或vue文件编译而来, 所以推论直接写render方法效率最高)
如果render方法有问题, 那么首先调用renderError方法, 再不行就读取上次的vnode或是null.
如果有父节点就放到自己的.parent属性里.
最后返回VNode
所以核心是这句:
1 | vnode = render.call(vm._renderProxy, vm. $createElement )
|
Salin selepas log masuk
其中的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()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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
if (!prevVnode) {
vm. $el = vm.__patch__(
vm. $el , vnode, hydrating, false ,
vm. $options ._parentElm,
vm. $options ._refElm
)
vm. $options ._parentElm = vm. $options ._refElm = null
} else {
vm. $el = vm.__patch__(prevVnode, vnode)
}
activeInstance = prevActiveInstance
if (prevEl) {
prevEl.__vue__ = null
}
if (vm. $el ) {
vm. $el .__vue__ = vm
}
if (vm. $vnode && vm. $parent && vm. $vnode === vm. $parent ._vnode) {
vm. $parent . $el = vm. $el
}
}
|
Salin selepas log masuk
我们关心的部分其实就是__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-cli axios请求与跨域
Atas ialah kandungan terperinci vue+update()使用详解. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!