平时我们进行业务开发可能不会遇到这种状况。
但是当我们需要封装一个比较复杂一点的组件,比如 Tree、Table 等。因为基本不用zustand
或者react-redux
。
我们往往要在组件内部自己维护一个管理者
来帮我们处理组件内部的数据。
但是处理数据其实 react
不会感知到也不会重渲染,这里有一个套路。
那么当你学会了这个套路之后,开发组件就能变的更加游刃有余
🤖 例子:假设我们现在要封装一个 Tree
组件
🚀 第一步:
创建一个管理数据的管理者,把treeData
给TreeManager
保存
class TreeManager {
constructor(treeData) {
this.treeData = treeData;
}
}
treeManager
存在 Provider
里备用,通过useContext(TreeManagerContext)
在所有的子组件使用
const treeData = [{ name: 'A' }, { name: 'B' }];
function Tree() {
const [treeManager, _] = useState(() => new TreeManager(treeData));
return (
<TreeManagerContext.Provider value={treeManager}>
子组件都能拿到treeManager
</TreeManagerContext.Provider>
);
}
🚀 第二步:
创建一个row
组件
通过useTreeNode
,拿到TreeManager.treeData
数组里面对应的项,展示name
属性
function Row(props) {
const { index } = props;
const treeNode = useTreeNode(index);
return <div>{treeNode.name}</div>;
}
🚀 第三步:
我们现在写死这个树只有两个<Row/>
分别对应上面treeManager.treeData
的 [{ name: 'A' }, { name: 'B' }]
数组的两项
function Tree() {
const [treeManager, _] = useState(() => new TreeManager(treeData));
return (
<TreeManagerContext.Provider value={treeManager}>
+ <Row index={0} />
+ <Row index={1} />
</TreeManagerContext.Provider>
);
}
问题来了,现在我这么做,调用 treeManager.edit()
修改了 treeData
数组第一项的 name
从 A
改为 许泽川
先别看代码,我们要的效果是不是这样的,修改到的那一项对应的 Row
组件重渲染,没修改到的 Row
组件不重渲染,也就是说
-
<Row index={0} />
重渲染,从A
变成许泽川
-
而
<Row index={1} />
不重渲染还是显示B
class TreeManager {
constructor(treeData) {
this.treeData = treeData
}
+ edit() {
+ this.treeData[0].name = '许泽川'
+ }
}
function Tree() {
const [treeManager, _] = useState(() => new TreeManager(treeData));
+ treeManager.edit()
return (
<TreeManagerContext.Provider value={treeManager}>
<Row index={0} />
<Row index={1} />
</TreeManagerContext.Provider>
);
}
🤖 怎么做到这种按需渲染呢?
1. 看一下我的 useTreeNode
怎么实现的
function useTreeNode(index) {
const treeNodeManager = useContext(TreeManagerContext);
const treeNode = useSyncExternalStore(
treeNodeManager.subscribe,
() => treeNodeManager.data[index],
() => treeNodeManager.data[index]
);
return treeNode;
}
下面这段话就是本篇重点,看完大概 useSyncExternalStore
就了解了
- 里面用到了
useSyncExternalStore
,他的原理简单来说就是,useSyncExternalStore
内部会通过你传入的这个函数() => treeNodeManager.data[index]
拿到值给你,就是这么简单 - 但是
useSyncExternalStore
额外搞了一点小动作,他通过你提供的管理库的订阅函数treeNodeManager.subscribe
,订阅一个回调函数给treeNodeManager
- 只要你触发了这个回调,他就会重新调用
() => treeNodeManager.data[index]
拿到最新值,跟上一次拿到的值做对比,如果变了,就会让用到这个useSyncExternalStore
的函数组件重渲染
你看看,他聪明的,会做对比,变化了的项才重渲染,这不就是
按需渲染
吗?
2. 那怎么触发回调,什么时候触发回调呢?
所以我们需要稍微修改一下 treeNodeManager
,简简单单加一个观察者模式
修改完数据后,都调用一下这个回调
一切这不就齐活了,懂了吗您?
class TreeManager {
constructor(treeData) {
this.treeData = treeData
}
edit() {
this.treeData[0].name = '许泽川'
this.listeners.forEach(listener => listener())
}
listeners = []
subscribe(callback) {
this.listeners.push(callback)
}
}
🤖 相关
面试官:让你实现一个高性能 Tree 组件磕磕绊绊的,你好意思干了三年前端?我:😭
面试官:你说你做过组件库,肯定了解过复杂组件状态管理的useSyncExternalStore吧?我:😭
版权归许泽川所有
如需转载,请提前询问本人的许可