面试经常会问到这个问题,所以就做个梳理总结,持续更新。
核心思想
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 派发更新。
包含以下几部分:
Compiler:指令解析器,解析每个元素节点的指令,初始化模版数据和订阅器,绑定相应的更新函数Watcher:订阅者,订阅的数据变化时执行相应的回调函数更新视图Dep:订阅器,收集订阅者,对 Observer 和 Watcher 进行统一的管理Observer:监听数据属性,属性发生变化通知对应 Watcher
Object.defineProperty 是在对象属性层面进行数据劫持,存在几个缺点:
- 初始化需要遍历对象的所有 key,当属性值是对象时,需要递归调用 Object.definProperty,层级多的情况下,性能有一定影响
- 无法监听动态新增的属性,删除对象属性无法通知依赖更新视图,只能用 API 代替
- 无法监听数组索引变化(监听的性能代价与用户体验不成正比,就没有实现)
- 无法监听数组长度变化,需要改写原型链,覆盖会改变数组长度的方法,监测数组变化
- 不支持 Map、Set 等数据结构
Vue3
Vue3 对响应式部分进行了重写。但是原理都一样,基于依赖收集。
使用 Proxy 做数据代理。同样访问时进行依赖收集,修改时触发更新,执行依赖的副作用。
包含以下部分:
- track:收集依赖
- trigger:触发更新,执行相应的副作用
- effect:副作用,响应式数据变化后需要执行的操作,可以当作订阅者
Proxy 的优势:
- 对象层面代理,可以代理所有的属性
- 懒执行,内部属性值为对象,当访问该属性时才做响应式处理,按需实现响应式,减少性能消耗
- 可以监听多种操作,包括动态新增、删除属性,has、apply 等操作
- 可以监听数组的索引和长度等属性
- 浏览器新标准,性能更好
React
React 认为数据是不可变的,通过调用 setState 来改变组件状态,setState 内部对 state 进行更新处理,把当前组件和所有子组件都 re-render,在进行 diff 后决定更新的部分。
对比
Vue 通过数据劫持/数据代理进行依赖收集,能够精确的更新变化,保证触发更新的组件最小化。
React 组件状态变化会重新渲染当前组件及全部子组件,可以使用 shouldComponentUpdate 等优化。
Diff 算法
Vue 的两大策略:
- 只会在同层级进行比较,不会跨层级
- 子节点的 diff 比较过程中,循环从两边向中间比较
React 16 之前有三大策略:
- tree diff:Web UI 中 DOM 节点跨层级的移动操作特别少,可以忽略不计
- component diff:拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构
- 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
- 都有对应的测试库,用于测试组件
- 社区活跃