React Escape Hatches 阅读笔记
type
status
date
Feb 21, 2024 03:27 AM
slug
summary
tags
category
icon
password
总结
在技术文档中,"Escape Hatches"(逃生舱口)通常指的是在某个系统、框架或编程语言中提供的一种机制,允许开发者绕过或者规遍系统的某些限制、规定或者标准。这个术语强调了提供一种应对特殊情况或者解决问题的手段,即使这种手段可能破坏了原本的规则或者设计。
Referencing values with refs
当在组件中想“记住”一些信息,但不想这些信息触发组件的
re-render
时,可以使用ref
常用于存储:
- 定时器 ID
- DOM元素
- 其他不影响组件渲染结果的对象
You can access the current value of that ref through the
ref.current
property. This value is intentionally mutable, meaning you can both read and write to it. It’s like a secret pocket of your component that React doesn’t track.ref
可以指向任意类型的值,改变ref
的值不会触发组件re-render
。ref
与state
的区别refs | state |
定义: useRef(initialValue)
返回:{ current: initialValue } | 定义: useState(initialValue)
返回:[value, setValue] |
值改变时不触发组件重渲染 | 值改变时触发组件重渲染 |
Mutable 值可变,可直接修改 | Immutable 值不可变,需要通过setter 修改值 |
渲染过程中不要读写 current 值,不可控 | 随时可读,但只是某一轮渲染的快照值 |
Manipulating the DOM with refs
使用
ref
指向一个DOM节点以进行交互操作针对动态生成的复数DOM维护其
ref
,使用ref callback
函数组件暴露内部的DOM,使用
forwardRef
包裹如果想自定义暴露特定方法或属性,使用
useImperativeHandle
React在
commit phase
设置ref.current
的值;更新DOM之前,ref.current
设置为null
,更新DOM后,会立刻设置为对应的DOM引用。一般而言,只会在事件处理逻辑中访问
ref
。如果希望
state
更新后马上刷新DOM,使用flushSync
包裹使用
ref
操作DOM的最佳实践- 脱离React进行独立的逻辑控制,如:聚焦/失焦、滚动、几何信息测量或调用浏览器API等
- 避免操作由React管理的DOM,二者会发生冲突
Synchronizing with Effects
React组件中包含两种类型的逻辑
Rendering Code
渲染逻辑,纯函数计算,返回JSX
Event handlers
事件处理逻辑,处理用户交互,会产生副作用
Effects let you specify side effects that are caused by rendering itself, rather than by a particular event.
Effects run at the end of a commit after the screen updates.
Effects are typically used to “step out” of your React code and synchronize with some external system.
实现一个
Effect
应该包含3个步骤- 创建副作用声明
useEffect
- 确认副作用依赖
dependencies
- React使用
Object.is
进行依赖比较 - 依赖无法自主选择,由副作用内的代码决定,按
lint
提示进行补充 - 具备稳定一致性
stable identity
的值不需要添加为依赖,如:ref
- 按需添加副作用
cleanup
在
Strice Mode
启用的开发环境中,所有组件都会重新挂载一次,目的是帮助发现副作用中未处理的cleanup
逻辑。当
useEffect
在开发环境执行两次时,不同场景的不同处理场景 | 添加cleanup | 具体处理 |
非React组件、控件 | 是 | 调用对应的API进行清理操作 |
事件订阅 | 是 | 取消订阅 |
动效触发 | 是 | 重置样式 |
数据获取 | 是 | 中断请求 |
数据上报 | 否 | 不处理,开发环境不上报 |
应用初始化 | 否 | 只执行一次,移到组件外 |
购买请求 | 否 | 不属于副作用,移到 event handler 中 |
Each render has its own Effects.
You can think of
useEffect
as “attaching” a piece of behavior to the render output.每次副作用执行时,获取到的
state
和props
都是那一轮render
的快照值,并非实时值。You Might Not Need an Effect
副作用不可滥用,要根据实际情况合理使用,一般来说有两种反模式:
- 副作用不用于进行数据转换,数据转换应该在组件顶层进行,在副作用中进行会产生不必要的冗余计算
- 副作用不用于处理用户交互,副作用执行时,无法追踪事件触发源信息
具体的场景及最佳实践见原文,注意与
event handler
的区别。Lifecycle of Reactive Effects
每一个Effect都代表着一个独立的与外部系统同步的过程,不要把所有副作用逻辑都写在同一个Effect里,应互相独立维护。
All values inside the component (including props, state, and variables in your component’s body) are reactive.
Any reactive value can change on a re-render, so you need to include reactive values as Effect’s dependencies.
总结
- 组件会经历挂载、更新和卸载三个阶段
- 每个副作用的的生命周期与其所在上下文有关,与组件生命周期无关
- 每个副作用描述了一个可以开始与结束的同步过程
- 状态值
reactive values
应该触发副作用重新执行
- 副作用依赖由其内部代码决定,并有
linter
提示,不用尝试忽略或禁用提示,而应该重新思考副作用的实现是否存在问题
Separating Events from Effects
认清
Effect
与Event handlers
的区别- Event handlers run in response to specific interactions
- Logic inside event handlers is not reactive
- Effects run whenever synchronization is needed
- Logic inside Effects is reactive
要把
non-reactive
的逻辑从副作用中抽离出来。现阶段可以使用ahooks的useMemoizedFn作为workaround。
Removing Effect Dependencies
如果要移除副作用依赖,只需要确保依赖为
non-reactive value
即可。不要尝试欺骗react以规避
linter
错误提示,会存在潜在的且难以发现的bug。可以思考以下几个问题以确定是否可以移除对应的副作用依赖
- 是否可以处理为
event handler
?
- 是否可以拆分为多个独立的副作用 ?
- 是否读取了
state
以计算下个state
?
- 是否包含了
non-reactive
的逻辑(参考上节)?
总结
- 副作用依赖应该与其代码实现符合(不多也不少)
- 发现副作用依赖不太正常时,应该检查的是副作用的实现
- 不要忽略副作用依赖的
linter
提示
- 移除副作用依赖时,确保它是
non-reactive
的
- 如果副作用内的部分逻辑只响应用户交互,应该移到
event handler
中
- 副作用的粒度应该尽量小,每个副作用只处理一项同步外部系统的逻辑
- 如果要根据
state
计算nextState
,使用updater function
- 如果想要读取
state
的最新值且不想被其触发副作用重新执行,把它放到Effect Event
中
- 相同的对象和函数字面量实际上并不相等,作为依赖时仍会触发副作用重新执行
- 对象和函数不应该作为副作用依赖,考虑移到组件外部或副作用内部
Reusing Logic with Custom Hooks
总结
- 自定义hooks可以在组件间共享逻辑
- 自定义hooks的命名应该满足
useXxx
的形式
- 自定义hooks共享的是状态化的逻辑,而非状态值本身(每个hooks运行时都拥有独立的状态值)
- 自定义hooks间可以传递
reactive
的参数
- 组件重新渲染时,副作用都会重新执行
- 自定义hooks的代码应该
pure
- 自定义hooks的
event handlers
参数应该使用Effect Events
包裹
- 没必要创造类似
useMount
的自定义hooks,正常使用useEffect
实现即可
- 如何划定自定义hooks的范围和边界非常灵活,根据实际情况自行决定即可