原生context、useContext详解
React 的 Context API 是一种组件间共享数据的机制,它允许你在组件树中传递数据而不必手动逐层传递 props,特别适合"全局"数据的共享(如主题、用户认证信息等)。
基本使用:
创建context:
import { createContext, useContext } from 'react';
export type ThemeType = 'light' | 'dark';
export interface ThemeContextType {
theme: ThemeType;
toggleTheme: () => void;
}
// 1. 创建 Context
export const ThemeContext = createContext<ThemeContextType>({
theme: 'light',
toggleTheme: () => {},
});
type ThemeProviderProps = {
children: React.ReactNode;
} & ThemeContextType;
// 2. 创建 Provider 组件
export const ThemeProvider = ({
children,
theme,
toggleTheme,
}: ThemeProviderProps) => {
return (
<ThemeContext.Provider value={{
theme,
toggleTheme,
}}>
{children}
</ThemeContext.Provider>
);
};
// 3. 自定义 Hook(可选,提升可读性)
export const useTheme = () => useContext(ThemeContext);
顶层组件 top.tsx
"use client";
import React, { useState } from 'react';
import { ThemeContext, ThemeContextType, ThemeType } from './context';
import Button from '../../components/button';
function App() {
const [theme, setTheme] = useState<ThemeType>('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
const value: ThemeContextType = { theme, toggleTheme };
return (
<ThemeContext.Provider value={value}>
<div
style={{
padding: '20px',
background: theme === 'dark' ? '#000' : '#fff',
color: theme === 'dark' ? '#fff' : '#000'
}}
>
<h1>Current theme: {theme}</h1>
<Button />
</div>
</ThemeContext.Provider>
);
}
export default App;
Button组件
import React from 'react';
import { useTheme } from '../hook-api/use-context/context';
export default function Button() {
const { theme, toggleTheme } = useTheme();
return (
<button onClick={toggleTheme}>Toggle Theme {theme}</button>
);
}
使用场景
- 全局主题(亮色/暗色模式)
- 用户认证状态(登录用户信息)
- 多语言国际化(i18n)
- 全局配置或状态(如购物车、通知设置)
注意事项:
性能问题:当 Provider 的 value 发生变化时,所有使用该 Context 的子组件都会重新渲染(即使只用到部分字段)。为避免不必要的重渲染:
- 将
value拆分为多个 Context; - 使用
useMemo稳定value引用; - 将不依赖 Context 的子组件提取到 Provider 外部。
不要滥用:Context 不是万能的状态管理工具。对于复杂状态逻辑,建议结合 useReducer 或使用 Redux、Zustand 等状态库。
use-context-selector
use-context-selector 是一个 React 上下文(Context)优化库,它解决了 React 原生 useContext 在性能上的一个关键问题:当上下文值变化时,所有使用该上下文的组件都会重新渲染,即使它们只依赖上下文中的一小部分数据。
核心特性
- 选择性订阅:允许组件只订阅上下文中的特定部分数据
- 精确更新:只有当下文中的选定部分变化时才会触发组件更新
- 与原生Context API兼容:使用方式与React原生Context相似
- 轻量级:体积小,对应用包大小影响小
基本使用:
App.tsx
'use client'
import React, { StrictMode } from 'react';
import { MyProvider } from './context';
import CounterA from './components/CounterA';
import CounterB from './components/CounterB';
function App() {
return (
<StrictMode>
<MyProvider>
<CounterA />
<CounterB />
</MyProvider>
</StrictMode>
);
}
export default App;
context.tsx
'use client'
import { useState } from 'react';
import{ createContext } from 'use-context-selector';
const MyContext = createContext({} as any);
export function MyProvider({ children }: any) {
const [countA, setCountA] = useState(0);
const [countB, setCountB] = useState(0);
const state: any = {
countA,
setCountA,
countB,
setCountB,
};
return (
<MyContext.Provider value={state}>
{children}
</MyContext.Provider>
);
}
export default MyContext;
CounterA.tsx
'use client'
import React from 'react';
import { useContextSelector, useContext } from 'use-context-selector';
import MyContext from '../context';
function CounterA() {
const countA = useContextSelector(MyContext, (v) => v.countA);
const setCountA = useContextSelector(MyContext, (v) => v.setCountA);
const increment = () =>
setCountA((s) => s -1);
console.log('CounterA rendered');
return (
<div>
<p>{new Date().getTime()}</p>
<p>Counter A: {countA}</p>
<button onClick={increment}>
Increment A
</button>
</div>
);
}
export default CounterA;
CounterB.tsx
'use client'
import React from 'react';
import { useContextSelector, useContext } from 'use-context-selector';
import MyContext from '../context';
function CounterB() {
const countB = useContextSelector(MyContext, (v) => v.countB);
const setCountB = useContextSelector(MyContext, (v) => v.setCountB);
const increment = () =>
setCountB((s) => s -1);
console.log('CounterB rendered');
return (
<div>
<button onClick={increment}>
Increment B
</button>
<p>{new Date().getTime()}</p>
<p>Counter B: {countB}</p>
</div>
);
}
export default CounterB;