大家好,我是卡颂。
都说中国人讲究中庸之道,中国人造的框架讲究么?
本文会从原理层面讲解Vue
是如何在运行时与编译时之间保持中庸的平衡。
UI = fn(state)
几乎所有前端框架工作原理都能用如下公式概括:
UI = fn(state)
UI
(视图)可以通过state
(状态)经过fn
(框架)计算得出。
然而具体原理上,框架之间却千差万别。
可以按照更新粒度将他们分为三类:
- 树级更新
- 组件级更新
- 节点级更新
更新粒度没有优劣之分,只是对应不同的实现
接下来我们简单了解下不同粒度更新方式的实现原理。
假设有如下组件树,其中Cpn
是一个自定义组件,内部结构为ul>li*2
:
我们希望将Cpn
内的一个li
更新为黄色:
树级更新
树级更新的框架会再生成一棵完整虚拟DOM树,生成过程中与之前的虚拟DOM树对应节点进行比较:
找到变化的节点后,执行对应DOM
操作。
树级更新框架的特点是:
- 依赖虚拟DOM
- 不关心触发更新的节点(因为会通过虚拟DOM的全树对比找到他)
采用这种更新方式最有名的框架就是React
。
组件级更新
上面的例子,如果是组件级更新框架。
会找到触发更新节点所在组件,生成该组件的虚拟DOM树(而不是全树的虚拟DOM树),生成过程中与该组件之前的虚拟DOM树对应节点进行比较:
找到变化的节点后,执行对应DOM
操作。
组件级更新框架的特点是:
- 依赖虚拟DOM
- 关心触发更新的节点(虚拟DOM的对比会作用于该节点所在组件)
采用这种更新方式最有名的框架就是Vue
。
节点级更新
如果是节点级更新框架,在编译时会根据状态变化对应的DOM变化直接生成对应方法,当状态改变后直接调用对应方法。
上面的例子,在编译时会关联color
状态与changeColor
方法:
function changeColor(newColor) {
li.style.color = newColor;
}
当改变color
状态后直接调用changeColor
方法更新li
对应DOM
。
节点级更新框架的特点是:
- 不依赖虚拟DOM,依赖预编译(建立状态与改变
DOM
的方法之间的联系) - 关心触发更新的节点(节点状态与更新方法一一对应)
采用这种更新方式最有名的框架就是Svelte
。
中庸的Vue3
Vue
作为组件级更新代表,更新粒度介于树级与节点级之间。那到底是中间偏左呢,还是中间偏右呢?
Vue3
说:
我要反复横跳,两边我都要
Vue3
中包含三种树状结构:
描述视图的树 -> 虚拟DOM树 -> 真实DOM树
其中描述视图的树官方推荐使用模版语法,但也能使用JSX
。
不管是哪种方式,最终都会转化为虚拟DOM树。
当使用JSX
时,Vue3
拥有了React
运行时的灵活性,此时的Vue3
可以看作是加强版React + Mobx
当使用模版语法,Vue3
引入了预编译技术。此时可以将Vue3
的工作流程细化为四步:
描述视图的树 -> VNode树 -> Block数组 -> 真实DOM树
其中VNode树
就是常规意义的虚拟DOM树,Block数组
则为VNode树中对状态有依赖,会变化的那部分VNode
组成的数组。
比如,对于如下模版语法:
<template>
<section>
<div>i am</div>
<p>{{name}}</p>
</section>
</template>
section
与div
不包含状态,不会变化,所以会转化为VNode
,但是没有对应Block
。
p
包含状态name
,会转化为VNode
与Block
。
当该template
所在组件触发name
状态变化,之前需要依次创建section
、div
、p
的VNode
并进行比较。
有了预编译技术后只需要遍历Block数组
进行比较。
用上文的例子即只需要对比一个li
节点:
在这种情况下,虽然是使用虚拟DOM的组件级更新框架,但是已经很接近节点级更新框架了。
总结
本文按更新粒度介绍了三种不同类型的框架,以及Vue3
在选择JSX
与模版语法
时在灵活性与更新粒度间的灵活变化。
可以说是很符合中庸之道了。
有人会问,使用Vue3
的用户大部分都能接受模版语法,为什么不直接抛弃虚拟DOM,使用预编译技术,实现真正的节点级更新?
一方面,虚拟DOM使框架脱离具体的视图层,更方便跨端渲染。
另一方面,Vue2
社区生态积累了大量基于虚拟DOM的库。
如果是你,在这种情况下会作何选择呢?
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。