Vue 和 React 渲染过程的区别
在 web 应用中,我们的页面是由 DOM 渲染而成。在早期,web 应用还不是很复杂,通常是直接编写 html ,通过 js 去操作 DOM。这种面向过程式的方式虽然简单方便,但是随着 web 应用的越来越复杂,越来不容易维护和管理。随后就陆续出现了一些组件式的框架,一个页面被拆分成多个组件,每个组件负责渲染一部分的视图,遵守单一职责原则,并且可以被很好被复用和组合。组件化的开发方式很适合现在的 web 场景,但是怎么去编写组件,组件又是怎样渲染,不同的框架有自己的一套设计理念。下面主要分析一下 vue 和 react 在渲染过程中的一些差异。
vue 和 react 的渲染过程如图。vue 在组件编写方式使用的的是 template 语法 而 react 选用的是 jsx 语法,当然 vue 也可以使用 jsx。
模版语法:template vs JSX
vue 的 template 模板语法延续了 html 的编写方式,更小的心智负担。并且提供了一些简便的模板语法,使得编写 template 更加的容易。 除了容易上手之外,vue 在 template 编译时也做了很多的优化。最为显著的是在 vue3 中,通过在编译时标记动态节点,在更新时只比较动态节点的变化,使得之前的树的 diff 优化为了数组的 diff, 并且通过 patchFlag 标识具体的某个属性的变化,在 DOM 比对时跳过其他属性的比较。可以看出 vue 在编译时做了很多的工作,使得运行时的性能更好。
JSX 是一个 javaScript 的语法扩展,具有 javaScript 的全部功能。相比于 template 固定的语法格式,JSX 则更像是使用 javaScript 去编写 UI,UI 只是作为 javaScript 中的一部分动态组合生成,所以 JSX 语法非常的灵活,更容易扩展和维护。
template | JSX |
---|---|
语法固定,写法简单 | 语法灵活,更容易组合、复用 |
编译时优化,提高运行时性能 | 无 |
抽象层:virtual DOM vs fiber
无论是编写 template 或者 JSX 都是为了编译生成最终的真实 DOM,但为什么在生成真实 DOM 之前还要创建虚拟 DOM 呢,直接生成真实 DOM 是不是性能更好。没错,在初次渲染时,直接生成 DOM 性能更好,但是在后续交互中频繁的创建新的 DOM 是一种很大的性能浪费。那么怎么复用之前 DOM 呢,就需要在 DOM 之上再抽象一层进行比较,比较之后映射真实 DOM 的更新。 并且抽象层作为真实 DOM 的描述,是完全的 javascript 对象,所以也就可以运行在任意支持 javascript 的平台,一次编译,多端运行 。典型的应用场景如:小程序,服务端渲染。 所以,虚拟 DOM 的优势:
- 复用 DOM 在组件更新时,通过虚拟 DOM 的比较,找出差异化的 DOM 进行更新,最大限度的复用老 DOM,减少不必要的性能浪费。
- 跨平台 虚拟 DOM 是对真实 DOM 的抽象描述的 javascript 对象,所以它也可以被其他平台接管,根据虚拟 DOM 创建符合平台的元素。
既然已经有了抽象层虚拟 DOM,为什么 react 还要引入 fiber 呢?fiber 的构建会不会加重渲染的过程。答案是肯定的,但 react 考虑更多的是稳定性和未来的发展。 那么,fiber 的优势又在哪呢? react16 之前虚拟 DOM 的创建过程是同步不可中断的,如果页面比较复杂,创建虚拟 DOM 时间过长,会阻塞 UI 的渲染导致掉帧。所以 react 重构了创建虚拟 DOM 的过程,将不可中断的同步构建 虚拟 DOM 树重构为异步可中断的构建 fiber 树。fiber 树是通过链表串联而成,每个 fiber 节点通过指针关联了父亲,儿子,兄弟 fiber 节点的关系。
这样的链表结构,使得 构建 fiber 树阶段 在某个 fiber 处中断,可以知道下一个要执行的 fiber 任务单元。所以一个渲染的长任务,也就可以切分成一个个片,在每一帧的空闲的时间去构建 fiber,解决了 UI 渲染阻塞的问题。
react 每一帧完成一部分 fiber 构建工作,那么会不会导致一部分的 UI 先渲染出来呢?
答案是不会。因为 fiber 树的构建是在内存进行的,DOM 的更新操作并没有进行,只是通过 fiber 标识了哪种类型的更新,当新的整个 fiber 树构建完成,才进入 commit 阶段更新真实 DOM。既然 fiber 是在内存中构建,并且可中断,那么 fiber 架构就有更大的应用场景?比如:为了更好的用户体验,react 为不同的更新设计了优先级 ,让高优先级的更新可以打断低优先级的更新,这样的设计使得 react 应用的更加的流畅。
又或者,未来是否可以利于其他的线程并行构建 fiber 呢?
相较于 react 的 fiber 架构,vue 又是怎么优化构建 DOM 的过程呢?
可以看出 vue 并不需要像 react 重新构建整个 fiber 树,因为 vue 可以追踪到具体的某个组件的变化,并且 vue3 的 template 编译时做的优化,可以更细致的定位到某个 DOM 的某个属性变化,更有针对性的靶向更新,减少更新的耗时。
所有总的来看 vue 虚拟 DOM 性能更好,而 react 的 fiber 架构的异步可中断,双缓冲,优先级设计,使 web 应用更加的平稳,有更好的发展前景。
vue virtual DOM | react fiber | |
---|---|---|
性能 | 优 | 一般 |
发展性 | 一般 | 优 |
数据驱动:自动挡 vs 手动挡
vue 和 react 都是数据驱动视图,组件对应了一部分的 UI,在 react 中可以通过 setState 手动触发组件更新,在组件更新后应用新的数据。而在 vue 则是通过改变响应式数据的值,自动触发组件更新,数据和组件是强依赖关系,省略了手动触发更新的过程。 那么,vue 又是如何建立数据和组件的关系呢,并在数据变化时,通知依赖的组件更新呢?
在 vue 的编译阶段会把 template 编译生成 render 函数,在组件的渲染的时候会执行 render 函数生成虚拟 DOM,在这个过程中会访问响应式数据 (vue2 中通过 Object.definePeropety 重写对象属性的 get,set 方式,vue3 中通过 proxy 拦截代理对象的 get,set 方法),会触发访问数据的 get 方法,在 get 方法中完成数据对当前组件的收集(vue2 中数据 dep 关联组件 watcher,vue3 通过 WeakMap 维护数据和组件的关系)。在后续响应式数据变化时,会触发数据的 set 方法,通知收集的组件更新。
整体来看,vue 的响应式系统是一种典型的观察者模式,初次渲染时组件会观察使用到的响应式数据,在响应式数据变化时通知组件的更新。这种模式的设计思路简单,也更容易理解。但是自动更新,会使更新源头不明确,可能导致不可预期的组件更新,也不容易排查。
自动挡 | 手动挡 |
---|---|
使用简单 | 稳定性更好 |