739 字
4 分钟
useContext

props 搬运从哪开始难受#

主题要从最外层传到第四层的导航:

function App() {
return <Layout theme="dark" />
}
function Layout({ theme }) {
return <Sidebar theme={theme} />
}
function Sidebar({ theme }) {
return <Nav theme={theme} />
}

Layout 与 Sidebar 都不用 theme,它们只是搬运工。再加两三个这样的参数,每加一个都要改中间所有组件的签名。

Context 的三步#

第一步,建一个 Context:

import { createContext } from 'react'
const ThemeContext = createContext('light')

第二步,在上层提供值。React 19 起 Context 自己就能当 Provider:

function App() {
const [theme, setTheme] = useState('dark')
return (
<ThemeContext value={theme}>
<Toolbar />
</ThemeContext>
)
}

第三步,需要的地方直接读,中间层不用出现:

function Nav() {
const theme = useContext(ThemeContext)
return <nav className={theme}>导航</nav>
}

关于写法要说明一句:<ThemeContext.Provider value={...}> 这种老写法现在仍然能跑,读旧代码会看到。官方说将来会弃用 .Provider(React 19 发布说明),新代码用第一种。

默认值什么时候生效#

createContext('light') 里的 'light' 只在上方一个 Provider 都没有时生效,它不是兜底值。上方有 Provider 但 value 传了 undefined,读到的就是 undefined。

value 变了,所有消费者都会重渲染#

Provider 的 value 是对象时,只要每次渲染都新建一个对象,引用就变了,所有消费者都会重渲染,哪怕内容一模一样。用 useMemo 把引用固定住:

// 每次渲染都是新对象,消费者全部重渲染
<AuthContext value={{ user, setUser }}>
// 引用只在 user 变化时改变
const value = useMemo(() => ({ user, setUser }), [user])
<AuthContext value={value}>

别急着上 Context#

官方给的顺序是先试两件更轻的事:继续传 props;中间层只是搬运时,抽成组件并用 children 传 JSX。

第二件值得记住,它常常能直接消掉对 Context 的需求:

function Layout({ children }) {
return <div className="layout">{children}</div>
}
function App() {
return (
<Layout>
<Nav theme="dark" />
</Layout>
)
}

Nav 由 App 直接渲染,Layout 不必认识 theme。真正的「全局」数据(主题、语言、登录态)再交给 Context。判断方法见官方 Passing Data Deeply with Context。

与 useReducer 搭配#

跨多层共享的状态,常见形态是把 state 与 dispatch 一起放进 Context,改法仍然集中在 reducer 里:

const AuthContext = createContext(null)
function AuthProvider({ children }) {
const [state, dispatch] = useReducer(authReducer, { isLogin: false })
const value = useMemo(() => ({ state, dispatch }), [state])
return <AuthContext value={value}>{children}</AuthContext>
}

这样消费组件拿到的是两样东西:现在的状态,和声明「发生了什么」的 dispatch。官方在 Scaling Up with Reducer and Context 里给了完整形态。

几条边界#

就近匹配。组件树上有多个同类型 Provider 时,读到的是最近的那一个。

默认值写成 null、消费处判空并抛错,是常见做法。这样漏包 Provider 会立刻报出来,而不是悄悄用默认值跑下去。

useContext 是 Hook,只能在组件或自定义 Hook 的顶层调用,条件和循环里不行。如果确实需要在条件里读 Context,用 use(),它不受这条限制(use)。

第 13 篇,共 39 篇本文属于系列

useContext
https://www.choria.top/posts/hook-use-context/
作者
乔瑞雅·克林威尔
发布于
2026-05-02
许可协议
CC BY-NC-SA 4.0

分享文章

生成精美分享图或复制链接,与更多人分享本文。

继续阅读

沿着主题读

基于共同的标签与分类

换条路线

从其他文章中稳定抽取

最后更新于 ,距今已过 190 天

部分内容可能已过时