别再用 props 一层层传了!React useContext 真正用法与 useState 深度解析

在 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 问题或者更优雅的写法,欢迎评论区一起来分享!🚀

相关推荐
zengyuhan50327 分钟前
Windows BLE 开发指南(Rust windows-rs)
前端·rust
醉方休30 分钟前
Webpack loader 的执行机制
前端·webpack·rust
前端老宋Running39 分钟前
一次从“卡顿地狱”到“丝般顺滑”的 React 搜索优化实战
前端·react.js·掘金日报
隔壁的大叔39 分钟前
如何自己构建一个Markdown增量渲染器
前端·javascript
用户44455436542641 分钟前
Android的自定义View
前端
WILLF42 分钟前
HTML iframe 标签
前端·javascript
枫,为落叶1 小时前
Axios使用教程(一)
前端
小章鱼学前端1 小时前
2025 年最新 Fabric.js 实战:一个完整可上线的图片选区标注组件(含全部源码).
前端·vue.js
ohyeah1 小时前
JavaScript 词法作用域、作用域链与闭包:从代码看机制
前端·javascript
流星稍逝1 小时前
手搓一个简简单单进度条
前端