likes
comments
collection
share

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

作者站长头像
站长
· 阅读数 37

自从 vue3 正式发布以来,网上一直在涌现各种 vue3 抄袭 react hook 的言论,虽说两者写法确实愈加相似,但是从本质来说两者依旧是天差地别,本文就从核心理念出发,来分析一下两大框架到底不同在哪里。

先来看一下这三个问题:

  • 为什么 vue3 的 setup 在生命周期中只执行一次,而 react hook 每次渲染都会重走一遍?
  • 为什么 setup 中返回的是方法和响应式数据,而 react hook 中返回的是 JSX?
  • 为什么 vue3 中需要使用 ref 来保存基本类型,而 react hook 不需要?

你可以先想想这三个问题背后的原因是什么,接下来我们会对 vue 和 react 的本质思想进行讲解,并由此得出这三个问题的答案。

vue3 的核心思路

让我们先从一个简单的组件开始,在 vue3 中,一个组件可以大致分为这三部分:

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

我们实例化这个组件的时候,props 里会传入一些数据,然后执行 setup,在 setup 里我们会拿这些数据使用 reactiverefcomputedwatch 等等方法生成响应式数据 ,然后从 setup 中返回出去,最后绑定在模板中,一个会随着数据变化而改变的 UI 就诞生了。

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

很简单对吧,现在我们把目光拉高一点,看一下整个组件树。我们会发现,不同组件之间通过 props 传递的其实绝大部分还是父组件里创建的响应式数据,子组件依赖这些响应式数据再创建新的响应式数据,绑定到视图层、或者继续传递给子组件:

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

并且 vue3 也可以在组件外创建响应式数据、彼此之间相互通过 computedwatch 进行关联,然后把这些数据 import 到组件上来绑定到视图,所以最终可能是这样的:

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

现在我们把次要的东西拿开,抛开 template、抛开生命周期、抛开组件、我们就会发现,每个 vue 应用都是一张由响应式数据构筑起来的网(单向无环图)。

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

每个响应式数据就是网上的一个节点,彼此之间的依赖关系则是网中的线。当某个节点被更新时,这些更新会像波纹一样随着彼此之间的依赖线抵达其他节点,最终完成更新。

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

注意,再强调一遍,这里传播变化的途径是 数据之间的依赖,而不是组件的嵌套关系。组件的作用只是用来创建响应式数据,并将这些数据绑定到对应的 UI 模板中。这也就成就了 vue 的“局部更新”功能:只精确的更新用到该数据的组件,没用到的子组件将不会触发。

到这里我们可以发现,vue 中的响应式数据和组件树并没有很强的关联关系,只不过我们常通过组件树来分发响应式数据,导致看起来像是响应式数据依赖组件树存在一样。

所以,你可以看到 vue3 的文档中有很多地方都会让你注意保持数据的响应性,例如不要直接解构一个组件的 props,使用 ref 来存放基础类型的数据。就是为了避免树中的连线被破坏(即失去响应性)。这个问题是 vue 中的头等大事,不可以忽略。

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

所以说,vue3 和 vue2 的核心思路是保持一致的,那就是 创建响应式数据,并通过相互之间的依赖更新视图。只不过在 vue2 里我们只能在组件 class 对应的 option 里才能创建这些数据,而 vue3 把这些核心的 api(ref、reactive、watch...)暴露了出来,让我们可以以更灵活的方式创建、组织这些数据。

并且因为 vue3 也是 setup 函数套一个个 use 函数,所以很多人就直接将组合式 api 称为 “vue 函数式编程”,这其实是不对的。vue3 用的编程思想其实就写在名字里:

组合式 API

组合式,就是 基于组合的 OOP,与基于继承的 OOP 相对,都是实现面向对象编程的范式。这两种 OOP 的渊源大家可以自行了解一下,这里就不赘述了。

我们可以看到,在 vue3 中,“实例化的组件”这种东西依旧是存在的,它就是 setup 返回出来的那个对象。只不过我们一般都是用对象解构的写法 return 这些内容的,所以没有意识到这一点。我举两个例子大家一看便知:

使用基于组合的 OOP 创建对象

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

再在 vue3 里实现一遍

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

不能说毫无关系,只能说一模一样。

至此,我们就可以得出如下结论:vue3 依旧沿用了 vue2 的数据响应式思想(关联数据并传播更新),只不过实现方式从基于继承的 oop 改为了基于组合的 oop。

react hook 的核心思路

看完了 vue,现在我们再来看看 react hook,首先还是单个组件,接受一些数据,调用一些 hook,然后直接返回了由 jsx 承载的视图:

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

然后看一下组件树。一个函数组件返回值里包含了另一个函数组件。这样在渲染的时候就会触发对应的组件函数,这个组件也返回一个 jsx,然后又会调用其他的组件函数:

不要再说 vue3 抄袭 react hook 了!从本质出发解读两大框架的区别

通过对这些组件(函数)进行嵌套调用,我们最终将会得到一个巨大的函数,当调用这个主函数时,它内部就会层层调用之前“预设”好的函数(子组件),最终吐出来一个完全由 html 组件组成的虚拟 dom 树。

而 react hook 的作用除了保持状态之外,还有就是重新触发“子函数调用”,除了调用的起点不同之外(调用主函数返回完整的树 > 变成了 > 调用子函数返回局部的树),和上面说的流程是一致的。

感受到和 vue3 的区别了么?在 react hook 中,我们操作的是组件本身(即函数),不同的组件像是一个个不同功能的机械臂,很多个组件组合在一起,形成了一条流水线,而数据(props 和 state)则是流水线上的材料,随着流水线的推进被不断的加工,最终产出一个完整的产品(最终的 UI)

而 react hook 的重新触发“子函数调用”,其实就是找到对应的组件,其作为流水线的起点,把新的材料(state)丢到流水线上重新生产出一个产品(或者产品的一部分)。

我们回顾一下这个流程,我们可以像 vue3 那样丢掉所有,只保留数据么?显然是不行的,没有流水线,你原材料扔地上它也不会自己组装好对吧。

也正是这个原因,react 不需要像 vue 一样对数据进行包装使其获得“响应性”。react 只需要把发生变更的数据重新丢回到对应的流水线节点上,它就会跟着流水线一步一步的被加工成对应的 UI。

而缺点也随之而来,对于流水线来说,它只知道有材料经过自己了,而不知道这个材料和上次加工的材料是不是一样的,流水线只会傻傻的在那里,你给他数据它就进行加工。就是我们常说的 react 无法精确触发局部更新,必须跑完整个子树后再 diff 检查发生了哪些变化。所以说我们需要给流水线加一些前置检查(shouldComponentUpdateReact.memo 等),如果满足条件的话就直接拿上次加工好的产品来用,这样就可以提升整个流水线的性能和速度。

现在我们总结一下,函数式组件作为构建完整 react 应用的基石,每个函数式组件都应该是一个简单的纯函数,接受入参,然后返回一个 React 元素。函数式组件没有状态,没有生命周期,没有副作用,它就是根据输入计算输出,也就是 UI = fn(state)

而 react hook 则为其提供了状态保持、副作用处理、上下文读写等能力,让这个函数式组件组成的纯函数应用可以更好更快的生产出可以看到的 UI 页面。

三个问题

讲完了两者的本质,现在回头看一下文章开头提到的三个问题:

为什么 vue3 的 setup 在生命周期中只执行一次,而 react hook 每次渲染都会重走一遍?

因为 vue3 只需要在实例化的时候建立响应式数据之间的关联即可,后续只需要通过响应式数据之间的关系就可以触发对应 UI 的更新。

而 react hook 则需要实际执行整个子树才能得知新的 state 会生成出什么样的 UI。

为什么 setup 中返回的是方法和响应式数据,而 react hook 中返回的是 JSX?

因为 setup 只是以组合式 oop 的形式实例化响应式数据,在模板里才会把这些数据绑定到 UI,从而建立 Model 层和 VIEW 层之间的关联。

而 react 的 UI = fn(state) 的函数式思想就决定了函数式组件应该接受状态、返回 UI。

为什么 vue3 中需要使用 ref 来保存基本类型,而 react hook 不需要?

因为 vue3 需要在响应式数据之间建立关联,从而实现传播更新的能力。不用 ref 这个容器的话,基本类型的数据就无法和其他数据建立关联。

而 react 传播更新并不需要一些拥有“特殊能力”的对象,而是通过函数式组件的嵌套调用,来确保所有用到该数据的组件都被重新渲染。

总结

现在我们再看看看文章标题,这两个框架真的一样么?

答案是否定的,虽然在编码方式上两者有相似之处,但是从本质出发,我们可以看到 vue 一直坚持响应式数据的理念,并用基于组合的 oop 思想设计了一套新的组合式 api,从而允许开发者以更灵活的形式组织响应式数据。而 react 也一直坚持函数式的思路,从 class 组件转型,一步一个脚印的带来了 hook。从本质上讲,这两者不仅不一样,甚至在朝一条路的两个方向渐行渐远。

转载自:https://juejin.cn/post/7239625431056990265
评论
请登录