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 Hooksfocused onconcrete high-level use cases. 避免useMountuseEffectOnce
更新状态的时候增加回调
- the main difference of
import/requiremethod - 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 handlersrun in response to user interactions,whereasEffect Events are triggered by you from Effects. - useEffectEvent 和 useCallback 的区别是什么?
一个冻结的定时器.
experimental_useEffectEvent或者useRef + useState











网友评论