在 React 中,Context.Provider 嵌套过多会导致:
- 组件树层级变深,可读性变差
- Provider 维护困难
- 任意 Context 值变化时可能引起不必要的重新渲染
例如:
xml
<AuthProvider>
<ThemeProvider>
<UserProvider>
<ConfigProvider>
<App />
</ConfigProvider>
</UserProvider>
</ThemeProvider>
</AuthProvider>
方案一:封装 Provider 组合(推荐)
将多个 Provider 统一管理。
javascript
// providers/index.tsx
import { AuthProvider } from './AuthProvider';
import { ThemeProvider } from './ThemeProvider';
import { UserProvider } from './UserProvider';
import { ConfigProvider } from './ConfigProvider';
export default function AppProviders({
children,
}: {
children: React.ReactNode;
}) {
return (
<AuthProvider>
<ThemeProvider>
<UserProvider>
<ConfigProvider>
{children}
</ConfigProvider>
</UserProvider>
</ThemeProvider>
</AuthProvider>
);
}
使用:
javascript
ReactDOM.createRoot(document.getElementById('root')!).render(
<AppProviders>
<App />
</AppProviders>
);
方案二:使用 reduce 自动组合 Provider
Provider 很多时更优雅。
javascript
const providers = [
AuthProvider,
ThemeProvider,
UserProvider,
ConfigProvider,
];
export const AppProviders = ({
children,
}: {
children: React.ReactNode;
}) => {
return providers.reduceRight(
(acc, Provider) => <Provider>{acc}</Provider>,
children
);
};
效果等同于:
xml
<AuthProvider>
<ThemeProvider>
<UserProvider>
<ConfigProvider>
{children}
</ConfigProvider>
</UserProvider>
</ThemeProvider>
</AuthProvider>
方案三:拆分 Context,避免大 Context
很多项目会这样写:
php
const AppContext = createContext({
user: {},
theme: {},
language: '',
permissions: [],
});
问题:
scss
const { theme } = useContext(AppContext);
即使只使用 theme,当 user 变化时也会重新渲染。
优化
拆成多个 Context:
ini
const UserContext = createContext(null);
const ThemeContext = createContext(null);
const PermissionContext = createContext(null);
使用:
ini
const user = useContext(UserContext);
const theme = useContext(ThemeContext);
这样只会订阅对应 Context。
方案四:Context + useMemo
避免 Provider 每次渲染产生新对象。
错误写法:
xml
<UserContext.Provider
value={{
user,
updateUser,
}}
>
{children}
</UserContext.Provider>
每次都会创建新对象:
sql
{
user,
updateUser
}
优化:
ini
const value = useMemo(
() => ({
user,
updateUser,
}),
[user]
);
return (
<UserContext.Provider value={value}>
{children}
</UserContext.Provider>
);
方案五:Context Selector(性能最佳)
安装:
perl
npm install use-context-selector
创建:
javascript
import { createContext } from 'use-context-selector';
const UserContext = createContext(null);
消费:
ini
const userName = useContextSelector(
UserContext,
(state) => state.user.name
);
即使:
state.user.age
变化,也不会触发当前组件渲染。
适用于:
- 大型管理后台
- 数据看板
- 高频更新页面
方案六:状态管理库替代 Context
当出现以下情况时:
- Context 超过 5 个
- 状态共享复杂
- 频繁更新
建议直接使用:
- Redux Toolkit
- Zustand
- Jotai
- Recoil
例如 Zustand:
sql
import { create } from 'zustand';
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
组件:
ini
const user = useUserStore((state) => state.user);
天然支持按需订阅,比 Context 性能更好。
企业级项目实践(推荐)
对于你目前开发的 React 性能平台、后台管理系统项目,通常采用:
AppProviders
├── AuthProvider
├── ThemeProvider
├── ConfigProvider
└── RouterProvider
业务状态
├── Zustand
├── Redux Toolkit
└── React Query
原则:
-
Context 只存全局配置
- 登录态
- 主题
- 国际化
- 权限
-
业务数据不用 Context
- 表格数据
- 查询条件
- 图表数据
- 性能报告数据
-
Provider 使用统一组合管理
-
Provider value 必须 useMemo
-
高频更新状态放 Zustand/Redux
这样既避免 Provider 地狱,又能获得更好的渲染性能。