React 和 Vue 对比

300 阅读6分钟

面试经常会问到这个问题,所以就做个梳理总结,持续更新。

核心思想

Vue 是尽可能的降低前端开发的门槛,推崇灵活易用、渐进式的开发体验,数据可变,双向数据绑定。

Vue 会提供很多 API;状态管理库 Vuex、路由库 Vue-Router、脚手架 Vue-CLI 等都由官方主导开发和维护。

React 是声明式渲染和组件化,数据不可变,单向数据流。

React 只关注底层,API 非常少;状态管理库、路由库、脚手架等都是社区提供。

组件写法

Vue 推荐 template 单文件组件格式,保留 HTML、CSS、JS 分离的写法,更接近常用的 web 开发。

react 推荐 JSX + inline style,HTML、CSS 都可以放到 js 中。主张函数式编程,推荐使用纯函数。

Virtual Dom

都使用了 Virtual Dom

VDOM 是对真实 DOM 的抽象,以 JS 对象作为基础,对象属性描述节点。

都使用 VDOM,通过特定的 render 方法将其渲染成真实的 DOM 节点。当组件状态发生更新时,会创建新的 VDOM,通过 diff 得到改动点,再更新视图。

VDOM 的优点

  • 跨平台:VDOM 本质是 JS 对象,抽象了原本的渲染过程,实现了跨平台的能力
  • 最小代价更新视图:经过 diff 算法得出需要修改的最小单位,通过 patch 准确的转换为真实 DOM
  • 无需手动操作 DOM:框架会根据 VDOM 和数据的绑定关系,更新视图,极大的提高开发效率
  • 高效的 DOM 操作:在大量、频繁的数据更新下,能够对视图进行合理、高效的更新

响应式原理

Vue2

采用数据劫持(Object.definProperty) + 发布订阅模式实现数据响应式;getter 进行依赖收集,setter 派发更新。

image.png 包含以下几部分:

  • Compiler:指令解析器,解析每个元素节点的指令,初始化模版数据和订阅器,绑定相应的更新函数
  • Watcher:订阅者,订阅的数据变化时执行相应的回调函数更新视图
  • Dep:订阅器,收集订阅者,对 Observer 和 Watcher 进行统一的管理
  • Observer:监听数据属性,属性发生变化通知对应 Watcher

Object.defineProperty 是在对象属性层面进行数据劫持,存在几个缺点:

  • 初始化需要遍历对象的所有 key,当属性值是对象时,需要递归调用 Object.definProperty,层级多的情况下,性能有一定影响
  • 无法监听动态新增的属性,删除对象属性无法通知依赖更新视图,只能用 API 代替
  • 无法监听数组索引变化(监听的性能代价与用户体验不成正比,就没有实现)
  • 无法监听数组长度变化,需要改写原型链,覆盖会改变数组长度的方法,监测数组变化
  • 不支持 Map、Set 等数据结构

Vue3

Vue3 对响应式部分进行了重写。但是原理都一样,基于依赖收集。

使用 Proxy 做数据代理。同样访问时进行依赖收集,修改时触发更新,执行依赖的副作用。

image.png 包含以下部分:

  • track:收集依赖
  • trigger:触发更新,执行相应的副作用
  • effect:副作用,响应式数据变化后需要执行的操作,可以当作订阅者

Proxy 的优势:

  • 对象层面代理,可以代理所有的属性
  • 懒执行,内部属性值为对象,当访问该属性时才做响应式处理,按需实现响应式,减少性能消耗
  • 可以监听多种操作,包括动态新增、删除属性,has、apply 等操作
  • 可以监听数组的索引和长度等属性
  • 浏览器新标准,性能更好

React

React 认为数据是不可变的,通过调用 setState 来改变组件状态,setState 内部对 state 进行更新处理,把当前组件和所有子组件都 re-render,在进行 diff 后决定更新的部分。

对比

Vue 通过数据劫持/数据代理进行依赖收集,能够精确的更新变化,保证触发更新的组件最小化。

React 组件状态变化会重新渲染当前组件及全部子组件,可以使用 shouldComponentUpdate 等优化。

Diff 算法

Vue 的两大策略:

  1. 只会在同层级进行比较,不会跨层级
  2. 子节点的 diff 比较过程中,循环从两边向中间比较

React 16 之前有三大策略:

  1. tree diff:Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
  2. component diff:拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
  3. element diff:对于同一层级的一组子节点,它们可以通过唯一 id 进行区分

Vue 和 React 16 之前对比

相同:

  • Vue 和 React 都采用了只进行同层级对比的策略,减少对比次数,让 Diff 算法的时间复杂度降到了 O(n)。
  • 接着就是对相同位置的节点进行比较,判断是否同一个节点,以及需要更新的内容。
  • 当新旧节点都存在子节点时,会遍历子节点做对比,都会使用 key 尽可能的复用组件;
  • 不管是 Vue 还是 React 16 之前的版本,整个 Diff 过程都是自顶向下递归遍历,不可分割

差异:

  • 当节点类型相同,但是 className 不相同,Vue 会认为是不同类型的节点,删除重建,React 会认为是同类型的节点,只修改节点属性。【确认
  • 子节点对比的具体策略不同。Vue 第二点策略,React 第三点策略就是针对子节点的对比。Vue 采用首尾指针法,从两端向中间比较;React 从左到右依次比较。当子节点中,只是把最后一个节点移动到第一个,React 会把前面的节点依次向后移动,而 Vue 只是把最后一个移动到第一个。

Fiber

React 16 之后为了优化性能,解决 js 引擎长时间占用主线程的问题,将调和阶段进行了重构,扩展出了 Fiber。

Fiber 的核心思想是任务拆分和协同,主动把执行权交给主线程,使主线程有时间空档处理其它高优先级任务。

Diff 过程从递归遍历变成了循环遍历,借助 requestIdleCallback 实现任务拆分、中断、恢复。

数据流

Vue 认为数据是可变的,可以直接修改 state 更新组件;组件与 DOM 之间可以通过 v-model 实现双向数据流

React 认为数据不可变,通过 setState 更新组件状态;单向数据流,但是可以手动编写 onChange 等事件处理函数实现双向数据流。

组件通信

Vue

  • 父子组件:props
  • 兄弟组件:消息订阅-发布,redux
  • 跨级组件:消息订阅-发布,redux,Context

React

  • 父子组件:props
  • 兄弟组件:全局事件总栈,消息订阅-发布,vuex
  • 跨级组件:全局事件总栈,消息订阅-发布,vuex,provide/inject

组合方式

Vue mixins

React hoc

移动开发

React:React-native

Vue:weex

小程序

Vue:uni-app

React:Taro

其它

  • 都支持typescript
  • 都有对应的测试库,用于测试组件
  • 社区活跃

参考

Fiber