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

Posted GoldenaArcher

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[React 进阶系列] React Context 案例学习:子组件内更新父组件的状态相关的知识,希望对你有一定的参考价值。

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

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

基础的 Context 以及 useContext hook 使用

基础实现的页面如下:

改动的文件有两个:

  • 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>
  );
;

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

这样看来,如果项目结构相对而言比较简单,对 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 和自动导入提示的细节问题了,毕竟大部分时候我个人还是觉得,有自动提示/自动导入还是比较方便的。

以上是关于[React 进阶系列] React Context 案例学习:子组件内更新父组件的状态的主要内容,如果未能解决你的问题,请参考以下文章

[React 进阶系列] Functional Component 与 Class Component 中使用 Context

[React 进阶系列] Functional Component 与 Class Component 中使用 Context

[React 进阶系列] Functional Component 与 Class Component 中使用 Context

ReactNative进阶(十九):React Native按钮Touchable系列组件使用详解

React 深入系列5:事件处理

React 系列导航