[React 进阶系列] React Context 案例学习:子组件内更新父组件的状态

本文介绍了如何使用React Context和自定义hook实现子组件更新父组件的状态。首先展示了基础的Context用法,然后通过二次封装Context创建Provider并使用useState进行状态管理。接着引入UpdateContext来传递更新状态的方法,最后展示了一个简洁的实现,讨论了在不同项目复杂度下选择Context还是Redux的考量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[React 进阶系列] React Context 案例学习:子组件内更新父组件的状态

一直知道 React Context 是 React 内部实现状态管理的方法,也简单的跟着官方的案例敲了一下使用 Context 进行的渲染,不过始终因为 子组件中更新父组件父组件 这一方法的实现太过麻烦,最终还是决定使用 Redux——毕竟 Redux 虽然内部实现依靠的是 Context,但是它已经封装好了。不过最近还是想要深入的学习一下 React,也就包括其状态管理机制,正好学到一个非常干净的写法,也就借此写一篇笔记。

基础的 Context 以及 useContext hook 使用

基础实现的页面如下:

context theme

改动的文件有两个:

  • App.js

    主要只是清理了一下页面,让渲染的内容简单一些,并且增加了一个子组件:

    import { useTheme } from "./context";
    import "./styles.css";
    
    const Child = () => {
      const themeContext = useTheme();
    
      return <div style={themeContext}> "Theme" </div>;
    };
    
    export default function App() {
      return (
        <div className="App">
          <Child />
        </div>
      );
    }
    
  • context.js

    这是主要实现的部分,也对 themeContext 进行了简单的封装,使得其他的组件不需要每次都机械性的使用 useContext(ThemeContext) 这样的代码,实现如下:

    import React, { useContext } from "react";
    
    const theme = {
      dark: {
        backgroundColor: "#333",
        color: "#ccc",
        padding: "2em",
        margin: "2em",
      },
      light: {
        backgroundColor: "#ccc",
        color: "#333",
        padding: "2em",
        margin: "2em",
      },
    };
    
    const ThemeContext = React.createContext(theme.dark);
    
    export const useTheme = () => useContext(ThemeContext);
    

当然,这是一个比较偷懒的做法,没有使用 <ThemeContext.Provider value={state}> ... </ThemeContext.Provider> 的方法传值,而是直接在创建的时候写死。

下一步就会对 Context 进行二次封装,实现 Provier 的部分。

二次封装 Context

这里使用一个 custom hook 去对 Context 进行一下封装,将 theme 的值存在 custom hook 的 state 中,这样就可以在之后使用 setState 的方式更新 Context 中的值。

更新内容如下:

  • App.js

    只有在 App 中新增了一个对 ThemeContextProvider 的 wrapper。

    需要注意的是,因为在 App 中使用的 ThemeContextProvider 包裹住子组件,所以 App 中是无法获得 ThemeContext 的值,只有子组件才 ”继承“ 了 Context 中的内容。

    export default function App() {
    return (
    <ThemeContextProvider>
      <div className="App">
        <Child />
      </div>
    </ThemeContextProvider>
    );
    
  • context.js

    import React, { useContext, useState } from "react";
    
    // 因为变量名冲突的关系,这里将其改成了大写……不过常量大写也比较正常
    const THEME = {
      dark: {
        backgroundColor: "#333",
        color: "#ccc",
        padding: "2em",
        margin: "2em",
      },
      light: {
        backgroundColor: "#ccc",
        color: "#333",
        padding: "2em",
        margin: "2em",
      },
    };
    
    const ThemeContext = React.createContext();
    
    export const useTheme = () => useContext(ThemeContext);
    
    export const ThemeContextProvider = ({ children }) => {
      const [theme, setTheme] = useState(THEME.dark);
    
      return (
        <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
      );
    };
    

    之后所要做的事情就是将 setTheme 这个方法传到子组件中。

    鉴于 Context 只能传一个 value,这里所采取的方法是再次新建一个 Context,其 value 是更新 cuttom hook 中的 set 函数。

新增 Update Context

这一步的做法,相当于对 Context 进行一个三次封装,具体变动如下:

// 之前的THEME没有变化

const ThemeContext = React.createContext();
const ThemeUpdateContext = React.createContext();

export const useTheme = () => useContext(ThemeContext);
export const useThemeUpdate = () => useContext(ThemeUpdateContext);

export const ThemeContextProvider = ({ children }) => {
  const [theme, setTheme] = useState(THEME.dark);

  const updateTheme = ({ theme }) => {
    // 这里只是做一个传值的示范
    console.log(theme);

    setTheme((prevTheme) =>
      prevTheme === THEME.dark ? THEME.light : THEME.dark
    );
  };

  return (
    <ThemeContext.Provider value={theme}>
      <ThemeUpdateContext.Provider value={updateTheme}>
        {children}
      </ThemeUpdateContext.Provider>
    </ThemeContext.Provider>
  );
};

使用 useThemeUpdate 去进行 Context 的状态变化

App.js 中的更新如下:

const Child = () => {
  const themeContext = useTheme();
  const updateTheme = useThemeUpdate();

  return (
    <div
      style={themeContext}
      onClick={() => updateTheme({ theme: "hello world" })}
    >
      "Theme"
    </div>
  );
};

可以看到,更新的逻辑相对而言还是比较清晰的,最终实现效果如下:

context complete

这样看来,如果项目结构相对而言比较简单,对 Redux-Saga 的需求不是很大,直接使用 Context hook 也或许是一个不错的选择,毕竟 hooks 省去了 Context.Consumer 使用回调函数取值这样一个繁复的过程。对于 Context 进行三次封装后,可以直接使用 const someVar = useXXX(); 的方法取值,以及 const updateSomeVar = useXXXUpdate(); 的方法进行更新也不会显得太过繁复。

最后,如果想要省去一层 wrapper,使用这样的方式也可以:

<ThemeContext.Provider value={{ theme, updateTheme }}>
  {children}
</ThemeContext.Provider value={{ theme, updateTheme }}>

// when you need to use it
const {theme, updateTheme } = useTheme();

不过这也是个人倾向,以及在不在意 typo 和自动导入提示的细节问题了,毕竟大部分时候我个人还是觉得,有自动提示/自动导入还是比较方便的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值