# 重学 react
## 简介
换工作后,从vue转react已经一年半了,代码写的不多,侥幸以为对react有点熟悉了。但看了「beta.reactjs.org」后依然有被震撼到,对react,自己之前的理解,原来一直都错的离谱。
今天,小编就讲讲理解react组件的心路历程,以及清空自身固有认知,尝试为未来的使用,构建一个不容易但简单的心智模型:
- 构建
react组件的基本心智模型 - 构建
useEffect的基本心智模型 - 自我反思
申明:本文是个人的官方文档「beta.reactjs.org」读后感,并不保证源码分析级别的严谨性,未来依然会不断调整,推荐大家有时间自己去读一遍文档。「原文档略啰嗦,但整体还是流畅易读的」。
## 构建react组件的基本心智模型
众所周知,react是围绕vdom和dom diff设计的,更新fiber架构后,也没有改变。小编对react组件(为了简化,这里跳过已经不值一提的class component,指的都是函数式组件)的理解一直是有许多疑问的:
初见,UI=f(state)简洁而优美的近乎「零」api公式,优雅,太优雅了!看起来以后一直可以这么优雅的写代码了?
如果没有类似immer等库的帮助,必须不厌其烦的手动展开Object和Array等常见引用类型,以达到给react组件投喂不可变数据的效果,「优雅的背后果然总少不了负重前行的实现」。
再见已翻看过dan经典的博文「overreacted.io/zh-hans/algebraic-effects-for-the-rest-of-us」,读后让人感到释怀,react组件留给小编的疑惑,果然并非是不是纯函数?这么简单。
react组件是用「多次不断执行组件的关联函数」的方式,做到了以「同步代码的形式」实现「异步中断和恢复的」的效果(被称之为代数效应)。有类似async函数的功能,却没有async函数传染性的缺点,不是异步,胜似异步!
回眸文档后,认识到react组件也不仅是代数效应这么简单,而是fiber树的位置和fiber树的diff结果紧密结合的产物。
即,react组件通过「多次执行」的方式:
- 实现了「零」api 抽象,每次执行都被传递到其
并不优雅的内部体系中,小编目前认为,这个纯函数带上各种约束,其实是一个内置 DSL。 - 实现了「纯」函数抽象,所以得手动保持
不可变。 - 实现了更多其他特殊能力抽象,
代数效应、状态管理、异步任务调度等 - 实现了
基于diff规则的自动状态管理,要仔细考量状态是否还能保持。
PS: 小编开始对用
react来实现「一切皆组件的思想」产生了一丝丝怀疑,因为react组件已经是一个深度定制的系统的内置 DSL,要以一个基础单元的形式,再去支撑其他维度的抽象系统有点重了,这点从react-router六大版本的变迁可以看出端倪。
## 构建useEffect的基本心智模型
小编对hooks的理解同样经历了几个阶段:
开始也是被优雅、简洁给惊艳,以至于对「不能在if中使用」等别扭的规则,感觉非常可以理解。这时候小编仅以为他是一个更聚合的组织代码的方式。
后来有人说hooks本质是一种reactive,深信了许久,~~于是小编去研究了很多reactive库,在遇见了solidjs之后感慨万千,vdom的路子跟十年前爆火的reactive思想比起来,在抽象程度、性能和包大小方面都没有什么优势,但是react已经无法走回头路了,~~最终小编的结论是,hooks和reactive不是直接关系,不应强行关联。
react组件是通过不断重复执行以获得超能力特殊能力的。所以配合上hooks保存状态的特性,整个函数体内申明的、所有的自由变量自然而然的,都拥有了reactive的特性了。无论是否是
useState包裹的变量,还是props,甚至一句普通的const b = a + 1,也天然成为了reactive,可以解读为a + 1的computed映射函数b的申明,所以不只是hooks才reactive。
最后,即然不是以reactive为目的设计,那么为啥useEffect要取这么一个名字呢?为啥dev阶段react非要「恶意」触发两次useEffect呢?
从新文档中小编获得了答案,useEffect本名其实应该是useImmutableReactiveAfterEveryRenderDangerEffect,解释为「用于不可变的、响应式的、危险的、react组件自身渲染后期的副作用」。小编取这样一个超级长的名字是契合官方,不鼓励多用、也防止大家滥用这个hooks。
Immutable和Reactive上面已经解释过了,AfterEveryRenderDangerEffect指的是组件被提交到渲染之后,可以自定义的行为,比如你想要组件一旦渲染后:
- 发送一个请求。
- 上报一个日志。
- 注册一个系统事件。
总之,只有在渲染后的这个时机内,需要一个「自动」处理「官方用词是同步」一下当前react组件和组件外部资源的危险的副作用的机制的时候,才不得不使用。大部分时候,你需要的主动处理逻辑都应该放在event callback中,现在,是否觉得自己滥用了呢?
- 由于每个
useEffect的功能、依赖的更新频率、设计目的都不相同,必须拆分大的useEffect为单一功能的多个小useEffect,才能防止更多的混乱。 - 而「恶意」触发两次
useEffect,是为了凸显忘记设置cleanup返回函数的危险性。
one more thing还有一个隐藏特别深的陷阱不得不提。你是否经常遇到,因为要实现某些功能,不得不给他添加设计之初以外的依赖,否则lint 规则就又无法通过了。
「lint 只会自动忽略组件外部的上下文变量、不可变的函数等,」手动忽略lint明显是一个走向失控的不负责的处理方式。那么,不打破设计意图还有哪些可控的解决办法呢?
- 把状态移出组件,或者移入
useImmutableReactiveAfterEveryRenderDangerEffect内部。 - 利用
setState(v => ...)的方式传入函数,以去除依赖数组中的reactive变量。 - 抽离一个
non-reactive effectFn函数「最新的useEvent api正是来自于此」,由这个函数去上下文重新捕获reactive变量,也可以去除依赖数组中的reactive变量。 - 「文档警告」
non-reactive effectFn函数是useImmutableReactiveAfterEveryRenderDangerEffect独有的作用域泄漏补丁方案,也请不要滥用。只能由对应的useImmutableReactiveAfterEveryRenderDangerEffect在内部调用,也不许传递给其他的hooks!
## 自我反思
本来还想写写re-render的心智模型,但是发现re-render的理论,其实业界聊得挺透彻,没啥新东西可说了,只是做的还不够好「共勉」。
小编发现以喷子的角色去吐槽FE的生态漏洞,收获的只有失落感,因为虽然意识到痛点和不足,却只能口嗨,无法去正确描述、正视并解决问题。这次读新文档,让人有一种解惑后的满足感,再感谢dan大佬用心写的文档,让小编重新认识了自己的无知。
最后欢迎关注一波:
以上。