关于主流前端框架的本质区别
前言
前段时间,StackOverflow 发布了2021 调查报告,Svelte
作为一个非主流框架,技压React
、Vue
等众多前端框架,成为了 web 领域最受欢迎的框架,你听说过Svelte
吗?
从 React15 到 17,再到即将到来的 React18,再从 Vue2 到 Vue3,会不会有种前端框架发展太快?学不动了的感觉?
那么我们来探讨一下,这些主流框架之间的区别吧
tips:本文借鉴了卡颂老师在 B 站发布的视频,本文发布已征得本人同意,点击查看原视频:『货很干』主流前端框架的实现原理,懂完了你
组件化
主流前端框架都遵循组件化的开发模式,具体来说,框架开发的应用分为三级抽象:
应用 => 组件 => 节点
- 这里描述视图的节点可能是一个被
<ul>
标签包裹的<li>
节点 - 这里的业务逻辑是,当用户点击
<li>
节点时,需要跳转到对应的地方,并且更新列表组件
中的数据
总结一下:框架中最小的单位是节点,节点加业务逻辑构成组件,多个组件构成应用,这就是前端框架的抽象层级划分。
那么前端框架的工作原理是什么呢?我们可以用一个公式概括:UI = F(state)
- UI - 视图
- state - 状态
- F - 框架内部的运行机制
总结起来就是:框架内部的运行机制
根据状态
渲染视图
说到这里大家可能会有疑惑,既然主流前端框架的工作原理都是一样的,那它们有什么区别呢?
答案是:更新粒度的区别
刚才讲过,框架有三级抽象:应用
、组件
、节点
,同样的,主流前端框架也分为三种更新粒度,应用级
,组件级
,节点级
,接下来我们看看这些框架内部都由哪些技术构成:
节点级更新框架:Svelte
我们从最细的粒度,节点级更新的框架 Svelte 开始讲起(如果想了解这个框架,可以点击这里查看官网)
首先定义一个计数器组件,先定义组件逻辑:
- 定义变量
count
,初始值为 0 - 定义函数
handleClick
,每次点击时 count + 1 - 然后定义一个渲染组件的
<button>
节点,将点击事件绑定为<button>
节点的点击回调函数 - 最后定义一个
<p>
节点,内容为一段文本
经过渲染,视图上的数字初始值为 0,每次点击都会加 1
节点级更新框架的原理分为三步:
- 将
状态变化
可能导致的节点变化
编译为具体方法
- 监听
状态变化
- 当交互导致
状态变化
后,直接调用具体方法,改变对应视图
在我们上面的例子中:
-
变量
count
变化可能导致<button>
节点中的文本节点发生变化,由于<p>
节点不包含状态变化,所以它不会出现在编译后的 update 方法中(这种将状态变化可能导致的节点变化编译为具体方法的技术,被称为预编译
,网站中实际运行的代码,都是预编译
之后的代码) -
监听状态变化,具体方案是采用了
发布订阅
设计模式,当创建一个状态后,会为状态维护一张订阅了该状态变化
的表,所有需要监听变化
的回调函数都在表中进行了注册
,这就是发布订阅中的订阅
部分 -
每当状态变化,会
发布
这张表,将状态变了
这一消息发布出去,每个订阅该状态变化的回调函数
都会收到通知并执行,这就是发布订阅中发布
的部分 -
通过以上这种方式,框架能对每个状态化做出反应,这种精确到状态的更新被称为
细粒度更新
,这也就是节点级更新框架的工作原理。
应用级更新框架:React
讨论到应用级更新框架,这种框架会采用虚拟 DOM 的技术,我们通过 React
来举例,首先,我们用 React 重写刚才的 Counter 组件:
import React, { useState } from 'react';
const Counter: React.FC = () => {
const [count, setCount] = useState<number>(0);
const handleClick = () => setCount(count + 1);
return <button onClick={handleClick}>{count}</button>;
};
export default () => (
<>
<Counter />
<Counter />
</>
);
-
在父组件内使用两个
<Counter />
子组件,这两个 Counter 子组件可以独立计数 -
首屏渲染时,React 会根据应用的树结构,生成一颗对应的
虚拟DOM树
- 当点击
<button>
节点后,调用handleClick
,触发状态更新,react 会重新生成一颗完整的虚拟 DOM 树
- 新生成的
每个节点
会与之前的虚拟 DOM 树的同级节点
进行比较,如果存在差异,记录该差异
- 在上面的例子中,进行到左边的
<Counter />
组件的<button>
节点时,发现前后两个button
节点的内容不一致,就会记录下来,等新的虚拟DOM
生成完毕后,将记录的差别统一渲染到视图上
总结
刚才讲过,节点级
更新的框架需要监听状态
的变化,与节点级
框架不同,应用级
框架不关心是哪个状态发生了变化,应用中的任何组件中的任何一个状态变化,都会从 root根节点
创建一颗完整的虚拟DOM树
,然后通过前后两棵虚拟DOM树
的比较,找到变化的部分,最终将变化的部分更新到视图,主流的应用级更新框架就是React
提问
你可能会问:为什么状态变化后,要生成一颗完整的虚拟DOM树
,而不是只为改变的状态对应的组件
生成对应的虚拟DOM树
?这样一来,就能节省很多虚拟 DOM 操作的开销,从而降低内存的开销,达到性能优化的目的
- 如果你这么想,那么恭喜你,你发明了 Vue
组件级更新框架:Vue
作为组件级更新框架的代表,Vue2 采用虚拟 DOM + 细粒度更新实现,Vue3 则在此基础上引入了预编译,并升级了细粒度更新的实现。
复习一下吧
最后总结一下:主流前端框架的工作原理是 框架的运行机制
根据状态
渲染视图
,他们的区别是更新粒度不同,三种更新粒度的框架分别对应不同的技术点:
更新粒度 | 对应技术 | 代表作品 |
应用级 | 虚拟DOM | React |
组件级 | 细粒度更新、预编译、虚拟DOM | Vue2、Vue3 |
节点级 | 细粒度更新、预编译 | Svelte |
转载自:https://juejin.cn/post/7011713268297236494