【内容梳理】<React性能优化>降低re-render之store

1. 前置说明

  • 无论是 contextreduxmobxZustand 还是 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

  1. 书写时和写Class相似
  2. 使用时,和静态函数类似
  3. 全局单实例,若要多实例,需要嵌套context
  • 主动调用

    • 使用方式和使用【函数】一样,获取的是调用时的对应 state 的值
    • 可以使用在任意的 js 文件中(jsjsxtstsx)
    ts 复制代码
    const { menuTree, menuList, getMenu } = useStoreMenu.getState();
  • 被动监听

    • 使用方式和使用【useSelector】一样,数据发生变化时,强制更新组件
    • 和其他 hooks 一样,只能使用在组件(jsxtsx)或其他 hooks 中
    ts 复制代码
    const menuTree = useStoreMenu((store) => store.menuTree); // 属性
    ...
    const getMenu = useStoreMenu((store) => store.getMenu); // 函数
相关推荐
化作繁星4 小时前
如何在 React 中测试高阶组件?
前端·javascript·react.js
初遇你时动了情4 小时前
react module.scss 避免全局冲突类似vue中scoped
vue.js·react.js·scss
Au_ust5 小时前
千峰React:函数组件使用(2)
前端·javascript·react.js
来一碗刘肉面5 小时前
React - ajax 配置代理
前端·react.js·ajax
界面开发小八哥8 小时前
可视化工具SciChart如何结合Deepseek快速创建一个React仪表板?
react.js·信息可视化·数据可视化·原生应用·scichart
Java知识技术分享10 小时前
使用LangChain构建第一个ReAct Agent
python·react.js·ai·语言模型·langchain
谢尔登12 小时前
【React】React 性能优化
前端·react.js·性能优化
Rowrey17 小时前
react+typescript,初始化与项目配置
javascript·react.js·typescript
谢尔登17 小时前
Vue 和 React 的异同点
前端·vue.js·react.js
风清云淡_A1 天前
【react18】如何使用useReducer和useContext来实现一个todoList功能
react.js