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

相关推荐
GISer_Jing17 小时前
React核心功能详解(一)
前端·react.js·前端框架
FØund40420 小时前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
疯狂的沙粒20 小时前
如何在 React 项目中应用 TypeScript?应该注意那些点?结合实际项目示例及代码进行讲解!
react.js·typescript
鑫宝Code21 小时前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
沉默璇年1 天前
react中useMemo的使用场景
前端·react.js·前端框架
红绿鲤鱼1 天前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
loey_ln1 天前
FIber + webWorker
javascript·react.js
zhenryx1 天前
前端-react(class组件和Hooks)
前端·react.js·前端框架
water1 天前
Nextjs系列——新版本路由的使用
前端·javascript·react.js
老码沉思录2 天前
React Native 全栈开发实战班 - 性能与调试之打包与发布
javascript·react native·react.js