高性能 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 必然满足不了你的需求.

相关推荐
张元清6 小时前
React useDropZone Hook:搭建一个文件拖放区(2026)
javascript·react.js
无人生还7 小时前
从 Vue3 到 React · 快速上手系列第 5 篇:事件处理与表单(受控组件 vs v-model)
前端·vue.js·react.js
光影少年7 小时前
RN Bridge 原理
前端·react native·react.js
moMo7 小时前
用 React 写一个 TodoList
react native·react.js
To_OC17 小时前
写了三遍 Todo List,我终于搞懂了 React 父子组件到底怎么通信
前端·javascript·react.js
kyriewen21 小时前
别再乱用useEffect了——你写的10个里有8个不该存在
前端·javascript·react.js
zandy10111 天前
衡石 Agentic BI的ReAct 推理框架在 Agentic BI 中的工程化实践
前端·javascript·react.js
朝阳391 天前
react19【系列实用教程】JS 项目升级为 typescript
javascript·react.js·typescript
labixiong1 天前
React Compiler 用 Rust 重写了,编译提速 10 倍——手写 useMemo 的日子到头了
react.js·rust
张元清1 天前
React useDeepCompareEffect:修复 useEffect 的对象依赖问题(2026)
javascript·react.js