什么是use-context-selector
?
use-context-selector
是一个 React 库,用于优化 React Context 的性能。它基于标准的 React.createContext
和 useContext
,但提供了更细粒度的控制,让组件只在 Context 中特定部分发生变化时重新渲染,而不是整个 Context 值变化时都触发渲染。这种优化对于大型应用或频繁更新的 Context 非常有用。
背景
在 React 中,使用 useContext
时,只要 Context 的值发生任何变化,所有依赖该 Context 的组件都会重新渲染,即使它们只关心 Context 中的一部分数据。use-context-selector
通过引入“选择器”(selector)的概念,允许你指定组件只订阅 Context 的某些特定部分,从而减少不必要的渲染。
核心功能
- 选择器模式:你可以通过一个选择器函数指定组件需要的数据,只有当选择器返回的值发生变化时,组件才会重新渲染。
- 性能优化:避免因无关数据变化导致的全局重新渲染。
- 简单集成:与现有的 Context API 兼容,只需替换
React.createContext
和useContext
。
依赖
use-context-selector
依赖于 use-sync-external-store
(React 18+ 内置的工具),以确保状态同步和性能优化。
如何工作的
- 创建 Context:使用
createContext
从use-context-selector
创建一个 Context。 - 提供值:通过
Provider
提供 Context 值,和普通 Context 一样。 - 消费值:使用
useContextSelector
钩子,传入选择器函数,指定你关心的数据部分。
例子
下面是一个简单的例子,展示如何使用 use-context-selector
来优化 Context 的使用。
代码
import { createContext, useContextSelector } from 'use-context-selector';
import React, { useState } from 'react';
// 1. 创建 Context
interface AppState {
count: number;
name: string;
}
const AppContext = createContext<AppState>({ count: 0, name: 'Default' });
// 2. Provider 组件
const AppProvider = ({ children }: { children: React.ReactNode }) => {
const [state, setState] = useState({ count: 0, name: 'Alice' });
return (
<AppContext.Provider value={state}>
{children}
<button onClick={() => setState((prev) => ({ ...prev, count: prev.count + 1 }))}>
Increment Count
</button>
<button onClick={() => setState((prev) => ({ ...prev, name: 'Bob' }))}>
Change Name
</button>
</AppContext.Provider>
);
};
// 3. 只关心 count 的组件
const CountDisplay = () => {
const count = useContextSelector(AppContext, (state) => state.count);
console.log('CountDisplay rendered');
return <div>Count: {count}</div>;
};
// 4. 只关心 name 的组件
const NameDisplay = () => {
const name = useContextSelector(AppContext, (state) => state.name);
console.log('NameDisplay rendered');
return <div>Name: {name}</div>;
};
// 5. 主应用
const App = () => (
<AppProvider>
<CountDisplay />
<NameDisplay />
</AppProvider>
);
export default App;
安装
先安装 use-context-selector
:
npm install use-context-selector
运行结果
- 点击“Increment Count”按钮:
CountDisplay
会重新渲染(因为它订阅了count
)。NameDisplay
不会重新渲染(因为name
没变)。
- 点击“Change Name”按钮:
NameDisplay
会重新渲染(因为它订阅了name
)。CountDisplay
不会重新渲染(因为count
没变)。
与普通 useContext
的对比
如果使用标准的 React.createContext
和 useContext
,每次 state
更新(无论是 count
还是 name
),CountDisplay
和 NameDisplay
都会重新渲染,因为它们无法区分 Context 中哪些部分变了。
// 使用普通 useContext 的版本(对比用)
import React, { useContext, useState } from 'react';
const AppContext = React.createContext({ count: 0, name: 'Default' });
const CountDisplay = () => {
const { count } = useContext(AppContext);
console.log('CountDisplay rendered');
return <div>Count: {count}</div>;
};
const NameDisplay = () => {
const { name } = useContext(AppContext);
console.log('NameDisplay rendered');
return <div>Name: {name}</div>;
};
在这个版本中,任何状态变化都会导致两个组件都重新渲染。
use-context-selector
的优点
- 性能提升:只订阅需要的数据,减少不必要的渲染。
- 可维护性:选择器让代码更清晰,组件只关心自己需要的数据。
- 兼容性:可以逐步迁移现有 Context 代码。
警告
- 选择器稳定性:选择器函数应该保持稳定,避免每次渲染都创建新函数(可以用
useCallback
包裹,如果需要动态选择器)。 - 额外依赖:需要引入
use-context-selector
包,增加了项目依赖。
什么时候使用它
- 当你的 Context 值包含多个字段,且组件只关心其中的一部分。
- 当 Context 频繁更新,且你希望最小化渲染开销。
- 在大型应用中管理复杂状态时。