一,前言
上篇,主要介绍了数组数据变化的观测情况,涉及以下几个点:
- 实现了数组数据变化被劫持后,已重写原型方法的具体逻辑;
- 数组各种数据变化时的观测情况分析;
到目前为止,已经完成了数据初始化操作,分析并实现了各种情况下的数据观测;接下来,需要将数据渲染到页面上;
本篇,主要介绍 Vue 的数据渲染流程;
二,如何将数据渲染到页面上
1,Vue 初始化流程回顾
目前,在 Vue 初始化流程中,会做两件事:
- 状态初始化:实现数据响应式;(已实现)
- 将数据挂载到页面上;(未实现)
// src/state.js
export function initMixin(Vue) {
Vue.prototype._init = function (options) {
const vm = this;
vm.$options = options;
// 1,状态初始化:实现数据劫持
initState(vm);
// 2,将数据挂载到页面上
if (vm.$options.el) {
// todo...
}
}
}
Vue
数据初始化完成后,就进入了Vue
的数据渲染流程,el
元素即为dom
的挂载点:
<body>
<div id=app>{{message}}</div>
<script>
let vm = new Vue({
el: '#app',
data() {
return { message: 'Brave' }
}
});
</script>
</body>
2,渲染问题分析
在 Vue 中,由于响应式数据特性,当数据变化时,会触发视图更新;
在编写 Vue 页面时,会大量使用到{{xxx}}
插值表达式、Vue 指令,比如:
<ul>
<li v-for="(item, index) in arr">{{item}} -- {{index}}</li>
</ul>
当数据发生变化时,Vue 并不会对整个视图全部重新渲染一遍,毕竟那样做性能太低了,也太笨了;
Vue 会对新老节点进行比对,根据比对结果,尽可能复用已经存在的节点,只更新那些变化的节点;
那么,这里就会有以下几个疑问:
- Vue 如何解析到模板中的插值表达式和指令?
- Vue 如何进行新老节点的比对?
- Vue 如何复用老节点,只更新变化的节点?
猜测一下:如果 Vue 使用更新后的数据和字符串模板,重新渲染出新 dom,再用新 dom 和老 dom 进行对比?
- 首先,使用更新后的数据和字符串模板,渲染出新 dom?
- 每次更新,都从字符串中解析出指令并完成值的更新?这些是非常困难且消耗性能的;
- 其次,新 dom 与老 dom 如何做比对?
- 用字符串做全量比对吗?这似乎不是一个好的方案,性能上也是不能接受的;
- 再有,尽可能复用老节点,只能更新变化的节点?
- 只更新需要更新的节点,目前好像也做不到…;(diff是没办法实现的)
为了实现以上几点特性,Vue 就“创造”了一套template
模板(类似于 React 的 jsx 语法)
3,template
与jsx
对比
Vue
同时支持template
和jsx
写法:
- 在日常开发中,大多都会采用
template
模板进行开发,template
即简单又方便; -
jsx
相比template
而言,更加灵活但语法稍显复杂;
在 vue3 中,由于
template
内部做了大量优化,性能其实会比jsx
更高一些,所以在 Vue3 中使用jsx
反而会丢掉一些性能优化;
三,Vue 的渲染流程
1,渲染流程分析
Vue
会对template
模板进行解析,将template
模板语法转变为javascript
语法;
在转化过程中,使用ast
语法树对html
语法结构进行描述,通过ast
的树形结构将代码重组成为js
语法;
ast
语法树:用于描述 css、js、html 等语法;虚拟
dom
:用于描述 dom 节点;
这样,后续对html
模板的操作,都可以直接通过js
来完成,不必再去解析字符串了;
那么,这里就涉及到了 “Vue 模板编译原理”
2,Vue 模板编译原理
- 将
template
模板编译为render
函数; - 通过
rander
函数处理后,返回虚拟dom
(老);
3,更新时,再次通过rander
函数生成虚拟dom
(新),使用diff
算法做新旧比对,根据比对结果更新真实dom
;
Vue 模板编译流程:
template
模板 ->render
函数 -> 虚拟dom
->diff
比对;
四,render
函数简介
vue2 模板解析工具:https://template-explorer.vuejs.org/
左侧的div
标签,最终将被编译成为右侧的render
函数:
-
_c
函数:相当于createElement
,创建div
元素,内部包含属性id
值为app
; -
_s
函数:相当于JSON.stringify
,如果模板的参数msg
为对象类型,_s
函数会将其转换为string
; -
_v
函数:创建文本,将_s(msg)
创建为一个文本;
五,Vue 数据渲染的核心流程
Vue 数据渲染,分为初渲染和更新渲染两部分;
1,初次渲染时
-
template
模板被编译为ast
语法树; - 通过
ast
语法树生成render
函数; - 通过
render
函数返回vnode
虚拟节点; - 使用
vnode
虚拟节点生成真实dom
并进行渲染;
2,视图更新时
- 调用
render
函数生成新的vnode
虚拟节点; - 通过
diff
算法对新老vnode
虚拟节点进行比对; - 根据新老虚拟节点的比对结果,最终更新真实
dom
;
综上分析,实现 Vue 数据渲染流程,需要实现以下功能点:
- 通过
template
模板,生成ast
语法树; - 根据
ast
语法树,生成render
函数; - 根据
render
函数,生成vnode
虚拟节点; - 根据
vnode
虚拟节点,创建真实节点; - 使用真实节点替换原始节点;
六,结尾
本篇,主要介绍了 vue 数据渲染的核心流程,初渲染与更新渲染;
- 通过
template
模板,生成ast
语法树; - 根据
ast
语法树,生成render
函数; - 根据
render
函数,生成vnode
虚拟节点; - 根据
vnode
虚拟节点,创建真实节点; - 使用真实节点替换原始节点;
下一篇,通过template
模板,生成ast
语法树;
维护日志
- 20230113: 重新梳理文章内容,调整目录结构,补充大量文字说明并优化部分内容语义,添加描述内容中的代码显示高亮,添加数据渲染核心流程的任务拆分;