Keywords
代码运行的原因(react/sync)
, props 逐层传递
,状态提升
useEffectEvent
,forwardRef
+ useImperativeHandle
, flushSync
, useSyncExternalStore
场景
useEffectEvent
可以使用useRef
代替,将函数的引用
放在一个ref
中
在绝大多数情况下,你需要只是保持函数引用不变,所以推荐把函数挂到 useRef
上, 而不是使用 useCallback
包裹函数,useCallback
带来的隐式依赖问题会给你带来很大的麻烦
const onTick = useRef(null)
onTick.current = () => {
setCount(c => c + increment);
};
useEffect(() => {
const id = setInterval(() => {
onTick?.current();
}, 1000);
return () => {
clearInterval(id);
};
}, []);
const onTick = useEffectEvent(() => {
setCount(c => c + increment);
});
useEffect(() => {
const id = setInterval(() => {
onTick();
}, 1000);
return () => {
clearInterval(id);
};
}, []);
状态管理
State 数据结构
在设计state
的数据结构的时候, 需要关注几种情况: 是否关联,是否矛盾,是否冗余,是否重复,不要深度嵌套 state
. 不要 clone props
组件状态共享
当A组件和B组件的状态相关的时候,需要状态提升
. 在React
中要保证数据来源的唯一, 针对每一个的状态,需要选择一个地方去拥有这个状态,另外,可以任意地方使用.
场景: 点击左侧表单中的某控件,同时高亮右侧预览中的控件. 实现方式有两种:可以使用 Context
或者Redux
. (Notable: 将预览组件分成一个小的部分,这样就可以减少re-render
, 将active-id
计算好给到小组件,会减少render time
)
场景: videoCard
列表中只能存在一个video Instance
,activeId
计算后,不是当前视频直接销毁video
.
状态的preserve和reset
对React
来说,重要的是组件在UI
中的位置,也就是渲染出来的DOM树
的形状,而不是JSX
的位置. DOM 复用(bailout
)可以给Key
. 相同的组件相同的位置,给定不同的key
后才会在下一次渲染中重置,否则会复用.相同的位置不同的组件TopVideo
,不同的组件,相同的位置 Management MTable&ETable
state 改成 reducer
In programming, reducer refers to a function
which is responsible for updating app's state. 比如, 每一个动作对象可以描述一个用户交互 .Reducer
会接管所有动作导致的状态改变
. useReducer
设置动作, useState
在设置状态.
Context
为了解决状态提升
后,props 逐层传递
的问题, 使用useContext
进行重构. 其工作方式类似于CSS
属性继承. 其使用场景是: 远距离组件
,基础组件Radio
, storeProvider
,RTLContext
, I18nProvider
React 19 针对context
在使用上,写法做了一定的改变.可以使用use
,use
是可以使用在条件语句中
reducer + context
- 进行
Logic
和UI
分离: 提供state logic
放在一个文件中; - 专注展示内容的地方是一个文件. 详细来看, 可以将
TasksContext.Provider
抽离成一个Container
(状态组织, 供应,并接受children prop
), 如果没有使用还可以抛出Error
TaskApp
- reducer & dispatch
- AddTask
- TaskList
脱围机制
ref
const xxRef = useRef(xx)
Ref
本身是一个普通的JS Object
,因此,它的行为就像普通的对象一样.ref
的值的改变不会触发re-render
. ref
是可以在渲染快照之间存活的.
ref 操作 DOM
ref 的使用场景有: 需要暴露DOM
和需要使用React
没有暴露的API
. 比如: focus
API ,video play 和 pause API
useEffect 同步外部系统
Effect
代表组件出现在屏幕上,你可以认为Effect
是组件渲染输出的一部分. 它跳出了React
与外部系统交互, 比如:与浏览器API进行同步,向服务器发送请求, 修改页面URL
.React
中存在两种逻辑类型,渲染
和交互
.Effect
的使用需要先声明(在渲染期间不要修改DOM
),注意依赖数组和清理函数.
Object.is()
和全等
之间的唯一区别在于它们处理带符号的0
和NaN
清理函数
- fetch data needs either "cancel" or "ignore", 防止组件销毁之后依旧
setState
-
订阅事件
和动画
保证相同的视觉效果即可
Effect 的生命周期
Effect
的生命周期存在两个
阶段: 开始同步信息
和停止同步信息
.
从Effect
的视角来看,Effect
不仅仅同步一次,当依赖项改变的时候会进行同步.
依赖项的不正确设置会导致loop/frequency sync
. 因此,在设置依赖项的时候,检查Effect
是否代表了独立的同步,event
是否和effect
进行了分离, 渲染期间可以计算出来的对象和函数是否放在了effect
中.
- 在组件中,声明的所有变量都是响应式的,放在依赖之中就会触发render.
- 不建议将
Effect
的执行和组件的mount, unmount
联系在一起. - 在开发环境中,StrictMode.
Effect
会额外同步一次进行逻辑压测
移除不必要的 Effect 依赖
Effect 的依赖项和 Effect 中的代码是相呼应的, 移除依赖项, 要证明这不是依赖项. 下面列举一些不需要依赖响应值的场景:
代码移动到event handler
中; 拆分effect
, 计算state
, state callback
, 读 state
.但是不希望响应. 是否依赖对象.
移除不必要的 Effect
不需要Effect
的常见场景: 渲染期间可以计算得到,可以移动到事件处理函数中. 并且, 事件处理函数中的common logic
可以提成一个函数而不是放在Effect
中. 同一个事件处理函数中更新state
,可以将父子组件的state
,进行批量更新.
console.time('filter');
// a piece of code
console.timeEnd('filter');
Event & Effect
我们在实现了一段代码之后,放在 Event 中还是 Effect 中? 需要考虑代码运行的原因是交互
还是同步
. 有时候代码逻辑需要依赖一些响应值,但是我们不希望随着响应值的改变而同步运行,此时我们可以将这段逻辑放在useEffectEvent
这个试验性 API 中
自定义 hook
自定义hook在logic
而不是state
. 组件需要共享状态本身的时候,考虑状态提升. 最佳实践是将effect
包裹进自定义hook,让effect
的数据流更加清晰.
Keep
Custom Hooks
focused onconcrete high-level use cases
. 避免useMount
useEffectOnce
更新状态的时候增加回调
- the main difference of
import/require
method - context file structure
- Ref 在业务中的使用场景
- field-ref-card
- void-field-wrapper
- menu-container
- preview-video-list
- useIsInnerDeleteAction
- DurationTrack
- PreviewEffectContent
- AllList
- TimelineWrapper
- 放在
event/effect/ form event
中? - The main difference is that
event handlers
run in response to user interactions,whereas
Effect Events are triggered by you from Effects. - useEffectEvent 和 useCallback 的区别是什么?
一个冻结的定时器.
experimental_useEffectEvent
或者useRef + useState