React Context with TypeScript:第3部分 - 带有类组件的上下文

425 阅读3分钟

Photo by mohammad takhsh on Unsplash

这是关于React contextwith TypeScript的系列文章中的另一篇文章。在上一篇文章中,我们创建了一个复杂的上下文,并将其消耗在一个函数组件中:

在这篇文章中,我们将学习如何使用React上下文与类组件。

尝试在类组件中使用上下文钩子

我们将继续使用我们在上一篇文章中创建的上下文,它允许消费者分享和设置一个主题。让我们把Header 组件更新为一个类组件:

class Header extends React.Component {
  render() {
    const { theme, setTheme } = useTheme()!;

    return (
      <div style={{ backgroundColor: theme }}>
        <select
          value={theme}
          onChange={e => setTheme(e.currentTarget.value)}
        >
          <option value="white">White</option>
          <option value="lightblue">Blue</option>
          <option value="lightgreen">Green</option>
        </select>
        <span>Hello!</span>
      </div>
    );
  }
}

不过这个实现有一个问题:

Context hooks error

钩子只能在函数组件内调用,所以,上面的代码在下面一行会出错:

const { theme, setTheme } = useTheme()!;

使用context 属性

React类组件有一个context 属性,我们可以用它来消耗一个上下文。首先,我们需要用static contextType 属性告诉类应该使用什么上下文,然后我们可以访问context 属性:

class Header extends React.Component {
  static contextType = ThemeContext;
  render() {
    const { theme, setTheme } = this.context!;
    return (
      ...
    );
  }
}

注意,我们在context 属性后面加了一个感叹号(!),以告诉TypeScript编译器,它不是undefined

让我们看看themesetTheme 被推断为什么类型。

Context inferred type in class

themesetTheme 都被推断为具有any 类型。

明确设置context 属性的类型

目前,消耗的上下文并不是强类型的。我们可以用类型注解显式地定义类context 属性,使其成为强类型的:

class Header extends React.Component {
  static contextType = ThemeContext;
  context: React.ContextType<typeof ThemeContext>;
  render() {
    const { theme, setTheme } = this.context!;

    return (
      ...
    );
  }
}

请注意,我们没有使用React.ContextType<ThemeContextType> 作为context 属性的类型注解,因为如果我们这样做,我们会得到一个类型错误。

点击下面的链接,可以得到一个完整的工作实现。试一试,改变主题值,看看背景的颜色变化。

打开完整的实现

使用Consumer 组件

如果我们只需要在JSX中访问上下文,还有另一种方法来消耗类组件中的上下文。这种方法是使用contextsConsumer 组件:

class Header extends React.Component {
  render() {
    return (
      <ThemeContext.Consumer>        {value => (
          <div style={{ backgroundColor: value!.theme }}>
            <select
              value={value!.theme}
              onChange={e => value!.setTheme(e.currentTarget.value)}
            >
              <option value="white">White</option>
              <option value="lightblue">Blue</option>
              <option value="lightgreen">Green</option>
            </select>
            <span>Hello!</span>
          </div>
        )}
      </ThemeContext.Consumer>    );
  }
}

Consumer 组件的孩子是一个函数,它有传入上下文的值,并返回我们想要渲染的JJSX。注意,我们在引用value 后加了一个感叹号(!),以告诉TypeScript编译器这不是undefined

这种方法的好处是,contextType 静态属性不需要被实现。我们不需要用类型注解来声明context 属性。

让我们检查一下Consumer 组件子函数中的value 参数的推断类型。

Consumer value inferred type

value 参数的类型是ThemeContextType | undefined

包裹起来

我们可以在类组件中使用Reacts上下文,但我们不能使用useContext 钩子。

使用Consumer 组件是一种整洁的方式,可以在render 方法中获得上下文,它的类型被正确推断。

context 属性可以在其他生命周期方法中使用,以获得对上下文的访问。我们需要为context 属性明确地定义一个类型注解,并在contextType 静态属性中指定具体的上下文。

在下一篇文章中,我们将了解一种创建上下文的方法,而不必传递默认值,然后在消费它时做任何undefined 检查。