【内容梳理】<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); // 函数
相关推荐
打小就很皮...7 小时前
基于 Dify 实现 AI 流式对话:组件设计思路(React)
前端·react.js·dify·流式对话
一只小阿乐8 小时前
前端react 开发 图书列表分页
前端·react.js·react·ant-
IT古董8 小时前
在 React 项目中使用 Ky 与 TanStack Query 构建现代化数据请求层
前端·react.js·前端框架
小程故事多_808 小时前
LangGraph系列:多智能体终极方案,ReAct+MCP工业级供应链系统
人工智能·react.js·langchain
Java追光着10 小时前
React Native 自建 JS Bundle OTA 更新系统:从零到一的完整实现与踩坑记录
javascript·react native·react.js
努力往上爬de蜗牛10 小时前
react native 运行问题和调试 --持续更新
javascript·react native·react.js
用户904438163246011 小时前
React 5 个 “隐形坑”:上线前没注意,debug 到凌晨 3 点
前端·javascript·react.js
浮游本尊13 小时前
React 18.x 学习计划 - 第八天:React测试
前端·学习·react.js
浩星19 小时前
react的框架UmiJs(五米)
前端·javascript·react.js
格鸰爱童话1 天前
next.js学习——react入门
学习·react.js·node.js