在 React 中,Context 本身不是性能问题,但Context 的更新机制容易导致不必要的组件重新渲染,这也是大家常说的"Context 导致性能问题"的根本原因。
Context 的工作原理
假设有这样一个 Context:
const UserContext = React.createContext(null);
function App() {
const [user, setUser] = useState({
name: 'Tom',
age: 18
});
return (
<UserContext.Provider value={user}>
<Header />
<Content />
<Footer />
</UserContext.Provider>
);
}
Header 和 Content 中使用了 Context:
function Header() {
const user = useContext(UserContext);
return <div>{user.name}</div>;
}
function Content() {
const user = useContext(UserContext);
return <div>{user.age}</div>;
}
问题 1:Context 更新会导致所有消费者重新渲染
当执行:
setUser({
name: 'Tom',
age: 19
});
React 会发现:
value={user}
发生了变化。
于是:
Header
Content
所有调用了:
useContext(UserContext)
的组件都会重新执行。
即使:
Header
只依赖 name,
而这次更新的只是:
age
Header 仍然会重新渲染。
示例
function Header() {
console.log('Header render');
const user = useContext(UserContext);
return <div>{user.name}</div>;
}
function Content() {
console.log('Content render');
const user = useContext(UserContext);
return <div>{user.age}</div>;
}
点击:
setUser({
...user,
age: user.age + 1
});
控制台:
Header render
Content render
两者都会执行。
问题 2:React.memo 对 Context 无效
很多人以为:
export default React.memo(Header);
就能解决。
实际上:
const user = useContext(UserContext);
Context 更新时:
React.memo
不会阻止重新渲染。
因为 Context 更新属于:
Provider -> Consumer
的直接通知机制。
例如:
const Header = React.memo(() => {
const user = useContext(UserContext);
return <div>{user.name}</div>;
});
仍然会重新执行。
问题 3:Provider 每次创建新对象
很多项目会这样写:
<UserContext.Provider
value={{
user,
updateUser,
}}
>
这里:
{
user,
updateUser,
}
每次渲染都会创建新对象。
即使:
user
没有变化。
React 仍认为:
value !== oldValue
于是所有 Consumer 更新。
错误示例
function App() {
const [count, setCount] = useState(0);
return (
<UserContext.Provider
value={{
name: 'Tom',
}}
>
<Child />
</UserContext.Provider>
);
}
每次:
setCount(...)
都会产生:
{
name: 'Tom'
}
新引用。
导致:
Child
重新渲染。
解决方案 1:useMemo 缓存 value
const contextValue = useMemo(
() => ({
user,
updateUser,
}),
[user]
);
<UserContext.Provider value={contextValue}>
这样只有:
user
变化时才通知 Consumer。
解决方案 2:拆分 Context
不要把所有状态放一个 Context。
不推荐
{
user,
theme,
language,
permission,
menu,
}
只要一个字段变化:
所有 Consumer 更新
推荐
<UserContext.Provider>
<ThemeContext.Provider>
<LanguageContext.Provider>
例如:
const UserContext = createContext();
const ThemeContext = createContext();
修改主题:
setTheme(...)
不会影响用户信息组件。
解决方案 3:Context + Selector
Redux 的核心优化思想。
例如:
const name = useContextSelector(
UserContext,
state => state.name
);
修改:
age
时:
name
不会更新。
常见库:
-
use-context-selector -
zustand -
redux
例如:
import { useContextSelector } from 'use-context-selector';
const name = useContextSelector(
UserContext,
v => v.name
);
只有 name 变化时才渲染。
解决方案 4:使用 Zustand / Redux
大型项目里:
Context
更适合:
-
主题 Theme
-
国际化 i18n
-
登录用户信息
-
配置项
不适合:
-
高频更新状态
-
大量组件共享状态
例如:
鼠标位置
实时表单
聊天消息
表格状态
这些场景使用:
-
Zustand
-
Redux Toolkit
通常性能更好。
React Context 性能问题总结
| 原因 | 说明 |
|---|---|
| Consumer 全量通知 | Context 更新时所有 Consumer 都会重新渲染 |
| 无细粒度订阅 | 无法只监听某个字段 |
| React.memo 无效 | Context 更新会绕过 memo |
| value 对象变化 | 新引用会触发更新 |
| 单一大 Context | 一个字段变化影响全部组件 |
一句话概括:
Context 的性能问题不在于读取(
useContext),而在于 Provider 的value一旦引用变化,所有消费该 Context 的组件都会重新渲染,无法像 Redux/Zustand 那样做到字段级订阅。