加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 百科 > 正文

vue数据控制视图源码解析

发布时间:2020-12-17 02:29:16 所属栏目:百科 来源:网络整理
导读:分析vue是如何实现数据改变更新视图的. 前记 三个月前看了vue源码来分析如何做到响应式数据的,文章名字叫vue源码之响应式数据,最后分析到,数据变化后会调用Watcher的update()方法. 那么时隔三月让我们继续看看update()做了什么. (这三个月用react-native做了

分析vue是如何实现数据改变更新视图的.

前记

三个月前看了vue源码来分析如何做到响应式数据的,文章名字叫vue源码之响应式数据,最后分析到,数据变化后会调用Watcher的update()方法. 那么时隔三月让我们继续看看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中.

mountComponent

渲染相关的Watcher是在mountComponent()这个方法中调用的,那么我们搜一下这个方法是在哪里调用的. 只有2处,分别是src/platforms/web/runtime/index.js和src/platforms/weex/runtime/index.js,以web为例:

原来如此,是$mount()方法调用了mountComponent(),(或者在vue构造时指定el字段也会自动调用$mount()方法),因为web和weex(什么是weex?之前别的文章介绍过)渲染的标的物不同,所以在发布的时候应该引入了不同的文件最后发不成不同的dist(这个问题留给之后来研究vue的整个流程).

下面是mountComponent方法:

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,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,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
  • 调用mounted钩子

(哈哈哈)那么其实核心就是建立Watcher了.

看一下Watcher的参数: vm是this,updateComponent是一个函数,noop是空,null是空,true代表是RenderWatcher.

在Watcher里看了isRenderWatcher:

是的,只是复制了一份用来在watcher第一次patch的时候判断一些东西(从注释里看的,我现在还不知道是干嘛的).

那么只有一个问题没解决就是updateComponent是个什么东西.

updateComponent

在Watcher的构造函数的第二个参数传了function,那么这个函数就成了watcher的getter. 聪明的你应该已经猜到,在这个updateComponent里一定调用了视图中所有的数据的getter,才能在watcher中建立依赖从而让视图响应数据的变化.

{ vm._update(vm._render(),hydrating) }

那么就去找vm._update()和vm._render().

在src/core/instance/render.js找到了._render()方法.

// 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,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方法可能是根据template或vue文件编译而来,所以推论直接写render方法效率最高)
  • 如果render方法有问题,那么首先调用renderError方法,再不行就读取上次的vnode或是null.
  • 如果有父节点就放到自己的.parent属性里.
  • 最后返回VNode

所以核心是这句:

其中的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()

我们关心的部分其实就是__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的分析

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读