对 React 一些零散的感受

从 Vue 转到 React 快要一年了,虽然写页面用哪个不是搬砖?但实际经历过用两种框架搬砖,还是有所感触的。本文不是什么源码解析,也不是要分个高低,甚至会刻意避开一些技术上的细节,单纯谈谈我个人从 Vue 转 React 后的一些感受上的变化,更偏向感性的认知,并没有什么技术干货。

设计理念

“我写 Vue 的时候,从来都不会考虑这些。”

这是我刚接触一两个月时,最直接的感受。在写 Vue 代码时,不会去想这是什么副作用,有请求就调用方法,有渲染没生效就 nextTick ,也没有受控组件的概念, UI = f(State) 这个公式,仿佛离我很远。

关于受控这块,有经历过困惑的时候,也知道 Vue 内部对原生 input 标签做了处理,但还是没有写受控组件的意识。之前在写树组件时,用 v-model 去控制了 selected 属性跟 checked 属性,但是 v-model 只能控制一个属性的 valueonChange ,多出来的属性,用 v-sync 总不是很舒服,而且当时本着需要传的 Props 能少就少的想法,提供了许多组件方法,让外部通过 this.$refs.xxx() 来控制子组件内部的状态。这个版本发布不久,我就后悔了,提供了这么多方法给外部来控制内部的状态,虽然当时也没有什么状态的概念,维护起来总有种莫名的难受,也可能是响应式带来的麻烦,我即想响应 value 属性,又想响应 data 属性,来决定选中的内容。

种种结合起来,虽然在 Vue 组件里最终都能解决,但总感觉不优雅,刚发布了 2.0 版本就想再重构,却无从下手。

Vue 对各种概念、场景都尽可能封装起来,帮你做了很多事情,所以你可以不用知道许多概念,就能直接上手。而且 Vue 的限制不多,以前开发表单这种有深层对象的东西,子组件直接修改传入的 Props 更是家常便饭,虽然我们都知道这是不好的。

或许是老外不用 996 ,又或许是自己真的菜, React 能有这么一套理念,这么多的概念可以推广。

刚上手 React 的那段时间,最经常遇到的疑惑就是:

  • 怎么又无限循环了?
  • styleclassName 为什么不能直接传进去?
  • re-render 了好多次耶,不过好像没啥影响,不管了吧?
  • 状态提升太难受了吧,业务稍微变一下,内部的状态要提升到天上去了
  • 每次 re-render 都创建了新的函数、新的对象,真的没事吗?
  • 又要把状态提出来,父组件直接调用子组件的方法不香吗?
  • 在请求返回后的 then 函数里调用两次 setState , effect 执行了两次?

这些问题一股脑地抛出来,对于刚从 Vue 保姆身边出来的人来说,什么 “我要好好学习它的思想” ,不存在的, “TMD 烦死了” 恐怕才是第一且真实的感受。

对于 “函数式编程” 这个概念,以前对它的印象就是:不是个坏东西;写工具来说挺重要的;但如果要我在业务里用,尤其是一连串函数 compose 起来,那让我死吧。

无知的时候总是会抗拒不了解的事务,不得不写 React ,倒逼了我去学习它的理念,也改善了我书写组件的方式。这个推动力, React 给了一部分,当然,身边的环境变化也给了一大部分。

现在写一个组件,不得不考虑的就是,状态,状态,还是状态,一个状态要放在哪里,谁可以动它。或许跟 Vue 里的 data 很像,但 React 函数式的思想使我不得不谨慎考虑,它没法让外部通过 this.$refs.xxx() 去干涉内部的状态,遇到业务变动就可能要提升一堆的状态,所以要考虑提升的成本(在 Vue 里,我可能就 this.$refs 了事),也开始考虑尽可能做成受控组件,同时尽量提供非受控用法,让外部使用起来更方便同时又灵活。

说了这么多,你们可能觉得我开始捧一踩一了,对着 React 一顿夸。其实我只是从我转变的角度去描述了这么一个变化,从另一个角度说, React 概念多、学习门槛比 Vue 高,不好上手这些网络上老生常谈的点,并不是不存在的。我依然存在抗拒 React 的想法,但相比刚上手时程度没这么严重,认识到自己的无知是很大的一个进步。

Angular:那我呢?

学习优秀框架的思想挺重要的,即使内心抗拒使用,抱着学习的心态,也一定会有收获的。在过去的近一年里,我也接触了身边的一些分享,或者跟 React 、Angular 这些框架有关,或者是一些通用的技术。老大不小的人了,希望从现在开始学习,永远不会太晚。

文档

在文档方面,两边都挺优秀的。

但在体验上,我在 Vue 文档更能快速找到想要的内容,或许是门槛低,没有那么多概念的解释,更像一本字典。

而看 React 文档,则要先理解一些概念,有种看国外产品翻译的文档特有的感觉,稍微有理解成本。

单向数据流

实际上,两个框架都希望数据流是单向的,从父组件流向子组件,天经地义,可维护。

但两边都没有强行限制子组件修改 Props 嵌套对象的行为。

不过在 React 中,修改状态需要用 setState ,不会直接用赋值的方式,这使得正常书写组件时,如果不是刻意去赋值,是不会修改到 Props 的。

而在 Vue 中, Props 跟 data 都挂载在 this 上,要修改到 Props 是相对比较容易发生的。

Hooks

Hooks ,或者说带有状态的函数,通过控制反转的方式,保留住了状态。

Hooks 提高了功能的复用能力,使得整个 React 项目代码都由函数组成,不用 class ,没有 options ,整个代码看起来就都是函数,十分纯粹。

但是有个限制就是实现的 Hooks 都强依赖了 React ,只能在 React 生态里使用。这也能理解,想要实现不限框架的可复用的工具,没有状态的就是个普通函数,有状态的用 class 就好。

Vue 3 也是向着这个方向转变的,不知道在后端层面是不是个好的实践,至少现在看起来还行,解决了以前复用代码时 Props 来源不明确、命名冲突等问题。

自写 React 以来,对于引用类型更加敏感了,因为 dependencies 数组,有点观察者的感觉,而且在 useEffect 中,很容易造成无限循环,刚上手时磕磕绊绊地溯源,可以发现大体上存在这些问题:

  1. 某个 dependency 没处理好,意外变化了,导致 useEffect 重新执行了
  2. dependencies 数组内容特别多,难以定位是哪个依赖引起的

所以随着代码量的增加,会更注意引用类型的变化、以及 dependencies 数组元素的个数,逐渐使代码优化到一个较好的、更可维护的状态。

虽然 Vue 就是基于观察者,但对于类似 useEffect 这类自动执行的函数,都是开发者通过 watch 配置的,整个 dependencies 都在掌控之中,因此不会特别在意其他每个引用类型变量的变化,只要管好 watch 的变量就好。而 React 推荐的 eslint 插件能自动补全 dependencies 数组,为了保持代码便于优化,一般都是让 dependencies 自动生成了,因此有时候对于不想 “监听” 的变量,需要有意识地使用 useRef 包裹起来。

JSX 与模板语法

关于 JSX ,以前经常看到有争议,我个人是没什么感觉的,其优缺点应该是很明了的。

JSX 编译过来就是 js ,十分灵活,用 JSX 描述的界面,可以拆分一小块出来作为一个变量,到处复用。而使用模板的 Vue ,只能再套一层组件。

但模板语法的优势就在于它在编译成 js 代码的过程中,可以 “加点料” ,进行一些优化,比如新出的,静态模板的优化等,这些 Vue 保姆都帮你做好了。

两者没什么好争的, Vue 也不是不能写 JSX ,写 ejs, handlebars 等模板时,也没什么人吐槽它们的学习成本吧,更多的是喜好问题。

“你看过源码吗?就知道bb”

这话不是其他人对我说的,而是这近一年来,对自己说的一句话。

去年 interview 之前的一段时间,有看了一部分 Vue 的源码,而在过去一年中,学习 React 源码的行动却一鸽再鸽,这不是什么好事。

不过有幸团队内部有分享过相关的内容,对 Fiber, React 如何利用 requestIdleCallback 和 Fiber 链表暂停渲染工作等也有了大致的一个印象。

后续还是希望能系统地去学习一下,看别人成套的文章,结合自己去看源码,以及 React 的新特性,了解在我们没有想象到的地方,它是如何抽象并解决现有的开发痛点的。