本文仅供学习交流使用, 产线推荐使用 recoiljs.org
前文回顾: 多环境,在线调试,私有化 @bolt/env
复杂的宇宙万物其实是由最简单的单元构成的。
--『三体』
参考
前言
前端追求原子化
-
原子化的函数取代类, 可以做打包时 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
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 必然满足不了你的需求.