1. 前置说明
- 无论是
context
、redux
、mobx
、Zustand
还是recoil
,在监听的内容发生变化时,都会强制 re-render 本组件 - 目的都是在非父子组件之间优雅的共享数据;因此不需要共享的,不应该存储在上面
2. context
优点:
- 使用方便,React 内置。定义 Provider 后,value 即是共享的数据;子组件使用 useContext 即可消费
- 结合 reducer,可以使用 dispatch 来降低数据的传入,并进一步降低 re-render
多实例
使用不用额外添加心智负担,Provider 的组件被实例化多少次,value 也会被实例化多少次
缺点:
- 不具备处理【副作用】的能力,需要手动控制
- 性能问题:每一次对 state 的某个值变更,都会导致其他使用该 state 的组件 re-render,即使没有使用该值。可以使用
use-context-selector
来规避此问题,但是此插件内部会删除默认的 cunsumer,使用其他插件时,比如react-activation
,需要注意兼容性
ts
interface IRnRecordState {
// 数据
formValue: Record<string, any>; // 当前表单的值
singleRelations?: Map<string, Record<string, any>>; // 关联单条所关联的数据值,key为组件id,value为
// 事件
onChangeFormValue?: (formValue: Record<string, any>, changedValue?: Record<string, any>) => void; // 表单值改变时事件
}
type IRnRecordCtxAttr = [IRnRecordState, Dispatch<IRnRecordAction>];
export const RnRecordCtx = createContext<IRnRecordCtxAttr>([
{
formValue: {},
},
() => {},
])
...
const [storeRecord] = useContext(RnRecordCtx);
const { singleRelations } = storeRecord;
// 若使用纯context,在formValue被修改的同时,只引用singleRelations的组件也会被re-render
- 性能问题若想尽可能减少,就需要对 context 的 value 进行拆分,然而这又会会导致产生很多的 Provider 嵌套
tsx
<RnGlobalProvider
...
// 全局属性
appId={appId}
formId={formId}
formCode={formCode}
formName={formName}
...
>
{/* 内部设置数据 */}
<RnSettingProvider>
{/* 记录数据 */}
<RnRecordProvider initialData={curInitialData} onChangeFormValue={onChangeFormValue}>
<Form ref={outerRef} element={finalContent.elements[0]} initialData={curInitialData} />
</RnRecordProvider>
</RnSettingProvider>
</RnGlobalProvider>
3. redux
优点:
- 繁荣社区
- 可扩展性高,每次只需要增添对应的 model 层即可
- 若为单一数据源时,state 回溯方便(这边强烈不建议使用动态
namespace
)
缺点:
- 若想采用多数据源,配置麻烦,且上述的回溯功能会有问题
- 需要使用 Generator 函数的用法,put、select、call 的函数的用法等,增加了部分心智负担
- ts 飘红问题
- call 是阻塞流程,但是只能调用外部异步,不能调用其他 effect
- put 是非阻塞过程,要变阻塞,参考
- 若使用不当(即不该放 redux 上的数据也放 redux 上了),会导致大量的缓存在 redux 上,调试的噩梦
3.1 注意事项
- 使用 useSelector 时,尽量不要返回大对象;比如需要拿到大对象中的几个属性时,应该使用多个 useSelector 来分别获取
<math xmlns="http://www.w3.org/1998/Math/MathML"> 以下为错误用法 \color{red}{以下为错误用法} </math>以下为错误用法
ts
// 返回了大对象
const worksheetInfo = useSelector((state: { code: ICodeState }) => state.code.worksheetInfo);
// 返回了大对象并结构
const { modifyIndex, device } = useSelector((state) => state.customPage);
- 若必须返回一个大对象,则最好结合第二个 campare 函数,最方便使用
react-fase-compare
进行深度比较
tsx
import isEqula from 'react-fast-compare';
...
const autoLayoutContent = useSelector((state: IConnectState) => state.designer.autoLayoutContent || [], isEqula);
4. Zustand
- 书写时和写Class相似
- 使用时,和静态函数类似
- 全局单实例,若要多实例,需要嵌套context
-
主动调用
- 使用方式和使用【函数】一样,获取的是调用时的对应
state
的值 - 可以使用在任意的 js 文件中(
js
、jsx
、ts
、tsx
)
tsconst { menuTree, menuList, getMenu } = useStoreMenu.getState();
- 使用方式和使用【函数】一样,获取的是调用时的对应
-
被动监听
- 使用方式和使用【useSelector】一样,数据发生变化时,强制更新组件
- 和其他 hooks 一样,只能使用在组件(
jsx
、tsx
)或其他 hooks 中
tsconst menuTree = useStoreMenu((store) => store.menuTree); // 属性 ... const getMenu = useStoreMenu((store) => store.getMenu); // 函数