在 React 项目里,你是不是也遇到过这种情况:为了把一个状态传到很深层的子组件,props 一层一层往下传,传到自己都晕了;要是中间有一层组件根本用不上这份数据,还得硬生生把 props 接过去再传下去,既麻烦又不优雅。
其实,React 早就给我们准备好了更好的解决方案 ------ useContext
。很多人知道它能"跨组件传值",但真正理解它的用法和适用场景的人并不多。而且在管理局部状态时,useState
又和它密不可分。
这篇文章,我就用一个简单的「主题切换」示例,带你彻底搞明白:
useContext
到底怎么用,用在哪儿最合适?useState
背后到底是怎么运作的?为什么它更新有时看起来是"异步"的?- 这俩 Hook 联合起来,怎么让你的组件既高效又优雅?
🌟 1. 单项数据流与 props drilling
在 React 中,状态是单向流动的:父组件通过 props 一层层把数据传给子组件。如果组件层级比较浅,没什么问题。但随着层级变深,props drilling(钻孔传值)就变成了噩梦。
比如我们要把一个「主题」传到页面某个很深的子组件中,至少要经历 4 层组件:
jsx
<App>
<Parent>
<Child>
<GrandChild>
<GreatGrandChild />
</GrandChild>
</Child>
</Parent>
</App>
每层都要写 props
,传到自己都想删项目。有没有办法只传一次?
有的!Context
天生就适合全局状态共享,完美解决跨层级传递。
⚙️ 2. 用 createContext
创建一个上下文对象
先在项目里新建 ThemeContext.js
:
jsx
// ThemeContext.js
import { createContext } from 'react';
// 创建一个上下文对象,默认值是 'light'
export const ThemeContext = createContext('light');
🗂️ 3. 在顶层用 Provider 提供状态
在 App.jsx
里,用 useState
管理主题状态,然后通过 ThemeContext.Provider
提供给后代组件。
jsx
// App.jsx
import { useState } from 'react'
import Page from './components/Page'
import { ThemeContext } from './ThemeContext'
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={theme}>
<Page />
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</ThemeContext.Provider>
);
}
export default App;
现在,只要在 Provider
里包裹的组件,都能拿到 theme
了。
🪝 4. 在子组件中用 useContext
消费状态
这里我们封装一个自定义 Hook,让拿 theme
更方便。
jsx
// hooks/useTheme.js
import { useContext } from 'react';
import { ThemeContext } from '../ThemeContext';
export function useTheme() {
return useContext(ThemeContext);
}
在子组件中调用它:
jsx
// components/Child.jsx
import { useTheme } from '../../hooks/useTheme';
const Child = () => {
const theme = useTheme();
return (
<div className={theme}>
Child 当前主题: {theme}
</div>
);
};
export default Child;
再在中间层组件里用一下:
jsx
// components/Page.jsx
import Child from '../Child'
import { useTheme } from '../../hooks/useTheme'
const Page = () => {
const theme = useTheme();
return (
<>
Page 当前主题: {theme}
<Child />
</>
);
}
export default Page;
就这么简单,跨层级状态共享搞定了,整个过程再也不用 props 一层层传了。
⚡️ 5. useState
的深度理解
再来说说 useState
。
几乎所有函数组件都离不开它,它就是用来在组件里保存和更新状态的。
jsx
const [count, setCount] = useState(0);
- 它接收一个初始值,返回
[状态值, 更新函数]
。 - 每次你调用
setCount
,React 都会把"我要更新状态"的这个动作先记下来,然后等下一次批量执行时再去真正更新状态和重新渲染。
🤔 setState
是同步的还是异步的?
这是很多人刚开始学时都会疑惑的点。
其实更准确的说法是:
- 调用
setState
是同步的,你写到那一行时,React 会立刻把"要更新"的信息放进内部队列里。 - 但状态的更新和组件的重新渲染是异步的,它不会立刻执行,而是等到当前代码都跑完,React 会在合适的时机把所有状态更新"打包处理",然后才一起渲染页面。
这就是为什么很多人会觉得它"看起来是异步的"。
⚡️ 多次更新会被合并
再看个典型例子:
jsx
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
有些人会以为这里 count
会加 3,但实际上只会加 1。
为什么?
因为每次 setCount(count + 1)
用的都是最开始的 count
值,并不会即时拿到上一次更新后的值。而且 React 会把多次状态更新合并在一起,最后只执行一次。
✅ 避坑方法:用函数式更新
那怎么让每次更新都基于最新状态呢?
用「函数式更新」写法就对了:
jsx
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
这里每次 prev
拿到的都是最新的状态值 ,所以最后 count
就会真正加 3。
一句话:
setState
触发是同步的,但执行是异步的。如果要多次更新同一个状态,记得用函数式更新,别让它被批处理"吃掉"了。
理解了这一点,很多状态更新相关的"坑"都能轻松避开了。
✅ 6. 小结
- props drilling 麻烦?跨组件传值就用
useContext
。 - 局部状态管理?放心大胆用
useState
。 - 多个状态更新要注意批处理,函数式更新是你的好朋友。
useContext
+ useState
的组合几乎能应付大部分组件通信和状态管理场景。
它们简单、高效、与 React 思想一脉相承,真正写起来又非常优雅。
📌 最后
最好的学习就是动手!
把文中示例敲一遍,自己切换下主题、调试一下状态更新过程,踩踩坑,记忆最深刻。
如果这篇文章对你有帮助,点个赞、收藏一下!
有其他 React 问题或者更优雅的写法,欢迎评论区一起来分享!🚀