复杂情况下的组件通信Hooks:useContext

您好,我是沧沧凉凉,是一名前端开发者,目前在掘金知乎以及个人博客上同步发表一些学习前端时遇到的趣事和知识,欢迎关注。


对于一个中后台产品来说,其实Redux显得不是那么有必要,因为它操作复杂和繁琐,而且由于Hooks的发布,让Redux有了更多的替代方案。

在中后台产品中很多数据都仅仅只在当前页面进行展示,几乎不会出现这里获取数据,放在一个完全不同的地方进行展示。如果使用Redux的话还有一定的副作用,就是在Redux中的数据,如果你不手动重置或者刷新页面,它会一直存在。而这个时候使用Context就显得更加方便。

关于Redux可以看TypeScript下使用Hooks的方式重新学习Redux和React-Redux

Context包裹中的组件都可以访问到Context中的状态,无论是子组件,孙组件,兄弟组件,无论层级有多深,只要被Context包裹,都能够获取到Context中的状态,所以在写复杂页面的时候Context几乎是你的不二选择。

1. 使用useContext

官方对于useContext的介绍非常简单,可能看了后有一些疑惑,今天我就主要以官方的代码来说一下useContext到底应该怎么用。

// 这里是需要使用到的数据
const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

// 1、这里是生成一个Context
const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    // 2、传递一个Context而value中就是传入的值
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // 3、消费Context
  const theme = useContext(ThemeContext);  
  return (    
    <button style={{ background: theme.background, color: theme.foreground }}>      
          I am styled by theme context!    
    </button>
  );
}

其实useContext的使用步骤可以分为3步,分别为:

  1. 生成一个Context。
  2. 使用生成的Context对象将所有可能需要用到Context的组件进行包裹(只要被包裹,无论层级,都可以消费Context)。
  3. 使用useContext进行消费。

清楚了这3个步骤后我们再回过头看一下上面的代码,第一个步骤,生成Context。

const ThemeContext = React.createContext(themes.light);

React.createContext的括号中的东西我个人理解为数据模型,其实你传什么都可以,你传入一个null也无所谓,都可以正确的消费Context,有些人就喜欢在项目中全局定义一个Context对象,就像下面这么写:

export const ThemeContext = React.createContext(null);

然后所有地方都是用这个Context对象进行包裹,当然我个人是不建议这么做的,因为一旦你这么做,你消费Context的时候就无法得到正确的代码提示。

可以看到上面的数据传递进去不是响应式的,就算更改了数据页面也不会刷新,其实Context对象中value的值也可以为state,例如下面这个计数器。

2. TS中使用useContext

TS和React简直是绝配,在React中能够完全发挥出TS的最大功效,而Context也是这样。

import React, { useContext, useState } from "react";

// 声明Context的数据模型
interface CountProps {
  count: number;
  setCount: React.Dispatch<React.SetStateAction<number>>;
}

// 1、创建一个Context对象
const ThemeContext = React.createContext({} as CountProps);

function ThemedButton() {
  // 3、也可以直接使用解构来获取数据
  const { count, setCount } = useContext(ThemeContext);
  return (
    <>
      {count}
      <button
        onClick={() => {
          setCount((value) => value + 1);
        }}
      >
        点我
      </button>
    </>
  );
}

function Toolbar() {
  // 3、消费Context
  const theme = useContext(ThemeContext);
  return (
    <div>
      <button
        onClick={() => {
          theme.setCount((value) => value + 1);
        }}
      >
        点我
      </button>
      <ThemedButton />
    </div>
  );
}

export default function App() {
  const [count, setCount] = useState(0);

  return (
    // 2、可以将count和setCount传入子组件中
    <ThemeContext.Provider value={{ count, setCount }}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

使用TS的类型声明可以直接对数据模型进行定义,而在消费Context的时候就会获得正确的类型提示。

3. 最后

以上就是useContext的基础用法了,也许在中后台项目中Context不会经常使用,大部分情况还是通过Props进行参数传递,但是一旦出现层级比较深的情况,或者兄弟之间需要使用同一个状态,那么使用Context会大大降低这些页面搭建的复杂度。