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

相关推荐
凯哥爱吃皮皮虾11 小时前
如何给 react 组件写单测
前端·react.js·jest
每一天,每一步13 小时前
react antd点击table单元格文字下载指定的excel路径
前端·react.js·excel
screct_demo1 天前
詳細講一下在RN(ReactNative)中,6個比較常用的組件以及詳細的用法
javascript·react native·react.js
光头程序员1 天前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
limit for me1 天前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者1 天前
如何构建一个简单的React应用?
前端·react.js·前端框架
VillanelleS1 天前
React进阶之高阶组件HOC、react hooks、自定义hooks
前端·react.js·前端框架
某哈压力大1 天前
基于react-vant实现弹窗搜索功能
前端·react.js
傻小胖1 天前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
flying robot2 天前
React的响应式
前端·javascript·react.js