重学 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
大佬用心写的文档,让小编重新认识了自己的无知
。
最后欢迎关注一波:
以上。