高性能 React Context, 极简状态管理库 @bolt/reactAtomContext

本文仅供学习交流使用, 产线推荐使用 recoiljs.org

前文回顾: 多环境,在线调试,私有化 @bolt/env

复杂的宇宙万物其实是由最简单的单元构成的。

--『三体』

参考

各流派 React 状态管理对比和原理实现

前言

前端追求原子化

  • 原子化的函数取代类, 可以做打包时 tree-shaking

  • 原子化的 Context 取代 store, 可以减少组件的 render

原子化就是让你清楚你真正依赖什么, 而不是直接给你个全家桶.

你只想装个 360 右键管理, 给你安装了 360 全家桶, 你也不乐意的吧. 🐶

人家 360 都出极速版了, 你还在等什么.

对于全局状态来说, 可能 A 组件只 set, B 组件只 get.在 A 组件执行 set 之后, 所有依赖的组件都会 reRender, 其中也包括 A 组件.需要把只用到 setter 但没有用到 getter 的摘出来, 它就不 reRender 了.

假如我们拆分 Context

  • 每个状态进行 createContext

  • set 操作也需要单独 createContext

  • 封装 Provider

  • Provider 在 root 组件会形成嵌套地狱

简单的 @bolt/reactAtomContext 使用

  • 保持原子化的 context 拆分

  • 提供简洁的使用

  • 解决 Provider 嵌套地狱

创建原子化 context
TypeScript 复制代码
import { createAtomContext } from '@bolt/reactAtomContext'
import { ListTab, MineOrCEO } from "../types";

export const listTab = createAtomContext(ListTab.TodoApproval);
export const mineOrCEO = createAtomContext(MineOrCEO.Mine);
通过 Composer 解决嵌套地狱
TypeScript 复制代码
import { AtomProviderComposer } from '@bolt/reactAtomContext'

export default (props: Props) => {
  return (
    <AtomProviderComposer
      Providers={[listTab.SelfProvider, mineOrCEO.SelfProvider]}
    >
      <List></List>
    </AtomProviderComposer>
  );
};

使用 provider 的组件最好只 render 一次

你还可以使用高阶函数语法糖

TypeScript 复制代码
import { composeAtomProviders } from '@bolt/reactAtomContext'

const ComposedProvider = composeAtomProviders([listTab.SelfProvider, mineOrCEO.SelfProvider]);

export default (props: Props) => {
  return (
    <ComposedProvider>
      <List></List>
    </ComposedProvider>
  );
};
在组件中使用
TypeScript 复制代码
export const List = () => {
  const listTabState = listTab.useSelfContext();
  const setListTabState = listTab.useSelfAction();

  return <div>...</div>;
};

如果只是调用 set 方法, 而没有使用 state, 当前组件也不会被重新 render

完整 demo 示例: codebase.byted.org/repo/ea/app...

简单的 @bolt/reactAtomContext 原理

createAtomContext 仅是高阶函数封装
TypeScript 复制代码
export const createAtomContext = <T extends any>(defaultValue: T) => {
  const SelfContext = createContext(defaultValue);
  const SelfAction = createContext((value: T) => {});
  const SelfProvider = ({ children }: PropsWithChildren) => {
    const [value, setValue] = useState(defaultValue);
    return (
      <SelfContext.Provider value={value}>
        <SelfAction.Provider value={setValue}>{children}</SelfAction.Provider>
      </SelfContext.Provider>
    );
  };
  const useSelfContext = () => {
    return useContext(SelfContext);
  };
  const useSelfAction = () => {
    return useContext(SelfAction);
  };
  return {
    SelfContext,
    SelfAction,
    SelfProvider,
    useSelfContext,
    useSelfAction,
  };
};
高性能 composer 原理

参考 @koa/compose

github.com/koajs/compo...

TypeScript 复制代码
export const composeAtomProviders = (
  Providers: (({ children }: PropsWithChildren) => ReactElement)[]
) => {
  return ({ children }: PropsWithChildren) => {
    return dispatch(0);
    function dispatch(i: number) {
      const Provider = Providers[i];
      if (i === Providers.length - 1) {
        return <Provider>{children}</Provider>;
      } else {
        return <Provider>{dispatch(i + 1)}</Provider>;
      }
    }
  };
};

export const AtomProviderComposer = (
  props: PropsWithChildren<{
    Providers: (({ children }: PropsWithChildren) => ReactElement)[];
  }>
) => {
  const { Providers, children } = props;
  const Provider = composeAtomProviders(Providers);
  return <Provider>{children}</Provider>;
};
自动收集 Provider

在执行 createAtomContext 时, 可以将 Provider 收集到一个数组(CollectedArr)中, 在使用 ComposedProvider 时, 直接对 CollectedArr 进行 compose 转换, 就可以省去手动 composeAtomProviders.

TypeScript 复制代码
const collectedArr = [];
export const createCollectedContext = <T extends any>(defaultValue: T) => {
    const $ = createAtomContext(defaultValue);
    collectedArr.push($.Provider);
    return $;
}
export const CollectedProvider = ({ children }) => {
  const Provider = composeAtomProviders(collectedArr);
  return <Provider>{children}</Provider>;
}

性能小贴士

请不要在循环的 list-item 中使用 AtomContext
  • List 组件中, 将 arr.map(() => <Item />) 中的 Item 单独抽成组件

  • Item 使用 React.memo(), 仅 props 存在变化时触发渲染

  • 在 List 使用 AtomContext 拼装数据, 而不应该在 Item 中使用, 这样当 ContextState 发生变化时, 可以仅 List 以及需要变动的 Item 重新 render, 而不会造成所有 Item 重新 render.

Q & A

为什么不丢掉 Provider

Provider 是 Context 和 store 最大的不同点, 也可以说是 React 最大的优势所在.

Provider 可以让状态和逻辑实现解耦, 比如最常见的 Provider 用途就是给组件库设置 i18n 和主题色.

如果你的组件既要多实例使用, 又需要父子组件通讯, 单实例 store 与 useLocalStore 必然满足不了你的需求.

相关推荐
NeverSettle_1 天前
React工程实践面试题深度分析2025
javascript·react.js
学前端搞口饭吃1 天前
react reducx的使用
前端·react.js·前端框架
努力往上爬de蜗牛1 天前
react3面试题
javascript·react.js·面试
开心不就得了1 天前
React 进阶
前端·javascript·react.js
谢尔登1 天前
【React】React 哲学
前端·react.js·前端框架
学前端搞口饭吃1 天前
react context如何使用
前端·javascript·react.js
GDAL1 天前
为什么Cesium不使用vue或者react,而是 保留 Knockout
前端·vue.js·react.js
Dragon Wu2 天前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
YU大宗师2 天前
React面试题
前端·javascript·react.js
木兮xg2 天前
react基础篇
前端·react.js·前端框架