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

相关推荐
中微子4 分钟前
React Router 面试指南:从基础到实战
前端·react.js·前端框架
3Katrina7 分钟前
深入理解 useLayoutEffect:解决 UI "闪烁"问题的利器
前端·javascript·面试
前端_学习之路1 小时前
React--Fiber 架构
前端·react.js·架构
coderlin_1 小时前
BI布局拖拽 (1) 深入react-gird-layout源码
android·javascript·react.js
甜瓜看代码1 小时前
1.
react.js·node.js·angular.js
伍哥的传说1 小时前
React 实现五子棋人机对战小游戏
前端·javascript·react.js·前端框架·node.js·ecmascript·js
qq_424409191 小时前
uniapp的app项目,某个页面长时间无操作,返回首页
前端·vue.js·uni-app
我在北京coding1 小时前
element el-table渲染二维对象数组
前端·javascript·vue.js
布兰妮甜1 小时前
Vue+ElementUI聊天室开发指南
前端·javascript·vue.js·elementui
SevgiliD1 小时前
el-button传入icon用法可能会出现的问题
前端·javascript·vue.js