再看Vue和React的区别 -- 从响应式实现方式的角度来看

167 阅读3分钟

Vue的响应式是通过监听对数据修改的操作。-- listen

React的响应式是通过对比数据的差异。-- diff

先说Vue

Vue的composition不是函数式,所谓的hooks只会执行一遍。后续针对reactive对象的读取和写入操作都会被记录下来,以此维护UI界面的响应式。

早期阶段社区有种声音为:Vue ≈ React + Template + Mobx。这里的单独解释一下Mobx,Mobx v4的时候声称自己是TFRP(Transparent Functional Reactive Programming)。

TFRP = T + FRP = T + F + RP。T为透明、F为函数式、RP为响应式编程,也就是透明的基于函数式的响应式编程。

最终解释起来,就是把响应式编程中的set、get透明化,让调用者无需关心数据到应用的实时变化。

但是这种做法有个先天性的限制,就是响应式的数据对象需要提前创建好。因为监听一个对象,至少这个对象要存在。这也是为什么Vue2中操作数组有那么多限制和别扭的写法以及Vue.set、this.$set存在的根本原因。也正是因为Proxy提供了对对象读写操作的统一抽象,让Vue3摆脱了前述的限制。

虽然Vue生态的响应式是对调用者透明的,从使用的角度上减轻了上层应用开发人员的心智负担。但是对社区难免引入一些负面影响。简单来说,由于是追踪“修改”、“变更”,所以,数据天生偏向于mutable而不是immutable。这导致了一个很典型的问题: [] !== []

// 如果一个组件的状态是组合派生,类似于 listA = listB + listC + listD
// 那么直接写A = computed(() => [...B, ...C, ...D]))就无法正确工作。
// 而是要按照下面的方式来组织代码

const A = ref([]);
watch(B, (next, prev) => {
  unref(A).splice(0, prev.length, ...next);
});
watch(C, (next, prev) => {
  unref(A).splice(unref(B).length, prev.length, ...next);
});
watch(D, (next, prev) => {
  unref(A).splice(unref(C).length, prev.length, ...next);
});

// 如果是vue2.7,那么watch还需要加deep
watch(
  A,
  (next) => console.log("A changed!", next),
  { deep: isVue2 },
);

这是个人在Vue方面遇到的最头疼的问题:从外部数据源推送过来的重复数据中,如何高效的精准生成diff操作。至于React,只需要在缓存的基础上重建一份新的数据即可。

再说React

并不是说,React就没有痛点。React本身通过Object.is去比对数据是否有变化,以此来驱动页面的重新渲染。hooks中的setter只要调用就会触发re-render。

以此带来的性能开销在大数据量的情景也是非常的头疼。但是,由于React本身并不会修改数据本身,这样,上层应用的选择余地就比Vue多了不少。社区方案中Mobx、ImmutableJS等等第三方方案都可无缝集成。

综上,对于React和Vue最核心的区别是:

  • React:创建新的对象,或者改变对象的引用值
  • Vue:对现有数据进行修改

至于其他种种,template也好,SFC也罢。不过是语法糖而已。

后记

Hooks和Composition API的区别点也是由上述原因造成。

  • 因为React只是简单的对比对象的引用值,所以Hooks需要重复多次执行,以此来产生不同的数据副本。
  • 因为Vue捕获的是目标对象的访问,所以Setup代码只会执行一次。
  • 因为React的Hooks要反复的执行,所以会有闭包捕获值不更新问题。
  • 因为Vue捕获的是访问记录,所以watch、computed中需要在对应的位置进行正确的访问。