主题
useContext
基本使用
介绍
useContext 是一个 React Hook,用于在组件中访问上下文。上下文是一个组件树中的变量,它可以在组件树中传递给任何组件。
示例
- 使用
createContext方法创建一个上下文对象MyContext。 - 在顶层组件中通过
MyContext.Provider组件提供数据。 - 在底层组件中通过
useContext钩子函数获取消费数据。
jsx
import { createContext, useContext, useState } from 'react'
const MyContext = createContext({
count: 0,
setCount: (value) => {},
})
const Child = () => {
const { count, setCount } = useContext(MyContext)
return (
<>
<h3>Child {count}</h3>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
)
}
const Parent = () => {
const { count } = useContext(MyContext)
return (
<>
<h2>Parent {count}</h2>
<Child />
</>
)
}
const App = () => {
const [count, setCount] = useState(0)
return (
<>
<MyContext.Provider value={{ count, setCount }}>
<h1>App {count}</h1>
<Parent />
</MyContext.Provider>
</>
)
}
export default AppuseContext 的性能陷阱
useContext 的核心机制是“广播”:当 Provider 的 value 改变时,所有消费该 Context 的组件都会被重新渲染。
问题的根源:即使组件只依赖于 value 对象中的一小部分数据,只要 value 对象的引用发生了变化,该组件就会被强制更新。这就是不必要渲染的主要来源。
1. 拆分 Context
将一个大的 Context 拆分成多个独立的、更小的 Context。
jsx
const GlobalContext = createContext(null)
function App() {
const [theme, setTheme] = useState('light')
const [user, setUser] = useState(null)
const value = useMemo(() => ({ theme, setTheme, user, setUser }), [theme, user])
return (
<GlobalContext.Provider value={value}>
<ThemeToggleButton />
<UserProfile />
</GlobalContext.Provider>
)
}jsx
// 1. 创建独立的 Context
const ThemeContext = createContext(null)
const UserContext = createContext(null)
// 2. 在顶层组件中分别提供不同的 Context
function App() {
const [theme, setTheme] = useState('light')
const [user, setUser] = useState(null)
// 保证 value 引用的稳定性
const themeValue = useMemo(() => ({ theme, setTheme }), [theme])
const userValue = useMemo(() => ({ user, setUser }), [user])
return (
<>
<ThemeContext.Provider value={themeValue}>
<ThemeToggleButton />
</ThemeContext.Provider>
<UserContext.Provider value={userValue}>
<UserProfile />
</UserContext.Provider>
</>
)
}
// 3. 组件按需消费
function ThemeToggleButton() {
const { theme, setTheme } = useContext(ThemeContext) // 只订阅主题
// ...
}
function UserProfile() {
const { user, setUser } = useContext(UserContext) // 只订阅用户
// ...
}2. 利用组合与 children Prop
将不依赖 Context 变化的组件(尤其是渲染昂贵的组件)作为 children 传入 Provider,从而将它们“隔离”在渲染循环之外。
利用了 React 的渲染机制:如果一个组件的 props(包括 children)引用没有变化,React 会更倾向于跳过对它的更新。
jsx
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light')
const themeValue = useMemo(() => ({ theme, setTheme }), [theme])
return (
<ThemeContext.Provider value={themeValue}>
<ThemeToggleButton /> {/* 这个组件依赖于 theme */}
{children} {/* 引用稳定 */}
</ThemeContext.Provider>
)
}
function App() {
return (
<ThemeProvider>
{/* 作为 children 传入,不会因为 theme 的变化而重新渲染 */}
<ExpansiveStaticComponent />
</ThemeProvider>
)
}3. 分离数据与 API
将易变的状态(state)和稳定的更新函数(dispatch)分离到不同的 Context 中。
jsx
// 1. 创建两个 Context
const CountStateContext = createContext(null)
const CountDispatchContext = createContext(null)
// 2. 在 Provider 中分别提供
function CountProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<CountStateContext.Provider value={state}>
<CountDispatchContext.Provider value={dispatch}>{children}</CountDispatchContext.Provider>
</CountStateContext.Provider>
)
}
// 3. 只读取状态
const state = useContext(CountStateContext)
// 4. 只读取更新函数(不会因 state 变化而重新渲染)
const dispatch = useContext(CountDispatchContext)useContext 配合 useReducer 使用
TODO
