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

[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 和自动导入提示的细节问题了,毕竟大部分时候我个人还是觉得,有自动提示/自动导入还是比较方便的。

猜你喜欢

转载自blog.csdn.net/weixin_42938619/article/details/124122194
今日推荐