React / TypeScript:通过HOC消耗上下文

时间:2018-05-30 19:41:16

标签: reactjs typescript

我试图在TypeScript(2.8)中的React文档(React 16.3)中实现示例Consuming Context with a HOC并且失败了。作为参考,来自React手册的代码:

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

// This function takes a component...
export function withTheme(Component) {
  // ...and returns another component...
  return function ThemedComponent(props) {
    // ... and renders the wrapped component with the context theme!
    // Notice that we pass through any additional props as well
    return (
      <ThemeContext.Consumer>
        {theme => <Component {...props} theme={theme} />}
      </ThemeContext.Consumer>
    );
  };
}

我能想出的最好的结果:

export interface ThemeAwareProps {
  theme: string;
}

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

export function withTheme<P extends ThemeAwareProps, S>(Component: new() => React.Component<P, S>) {
  return function ThemedComponent(props: P) {
    return (
      <ThemeContext.Consumer>
        {theme => <Component {...props} theme={theme} />}
      </ThemeContext.Consumer>
    );
  };
}

class App extends React.Component {

  public render() {
    return (
      <ThemeContext.Provider value={'dark'}>
        <ThemedButton/>
      </ThemeContext.Provider>
    );
  }
}

ThemedButton.tsx:

interface ThemedButtonProps extends ThemeAwareProps {
}

interface ThemedButtonState{
}

class ThemedButton extends React.Component<ThemedButtonProps, ThemedButtonState> {

  constructor(props: ThemedButtonProps) {
    super(props);
  }


  public render() {
    return (
      <button className={this.props.theme}/>
    )
  }
}

export default withTheme(ThemedButton);

问题是最后一行(export default withTheme(ThemedButton))。 TypeScript编译器抱怨

  

类型typeof ThemedButton的参数不能分配给new () => Component<ThemedButtonProps, ThemedButtonState, any>类型的参数。

我错过了什么?

1 个答案:

答案 0 :(得分:12)

你在大多数情况下做得对,只是缺少一些东西:

  1. 对于Component,请使用React.ComponentType<Props>,它正确接受类组件和功能组件。我认为单独使用new () => ...无法在这里工作,因为签名并不完全匹配。

  2. 要在使用道具时从ThemedButton中排除道具,您必须使用一些神奇的语法:

  3. function ThemedComponent(props: Pick<P, Exclude<keyof P, keyof ThemeAwareProps>>)
    

    以下是这样做的:

    • Exclude<keyof P, keyof ThemeAwareProps>表示&#34;获取P的密钥,然后带走ThemeAwareProps&#34;
    • 中的密钥
    • Pick<P, ...>然后说,&#34;来自P,返回仅包含这些属性的对象类型&#34;

    组合这些组合为我们提供了一个组件,可以接受ThemedButton所做的所有道具,减去theme道具,这样我们就可以<ThemedButton />毫无错误地执行。{/ p>

    这是完整的HOC:

    function withTheme<P extends ThemeAwareProps>(Component: React.ComponentType<P>) {
      return function ThemedComponent(props: Pick<P, Exclude<keyof P, keyof ThemeAwareProps>>) {
        return (
          <ThemeContext.Consumer>
            {(theme) => <Component {...props} theme={theme} />}
          </ThemeContext.Consumer>
        )
      }
    }
    

    And finally, a good blog post on the subject, from which I gleamed most of this information from.如果您愿意,还可以使用Pick<...>类型缩短Omit内容。

    编辑:The behavior of rest/spread has changed in 3.2this bug came up as an unfortunate side effect,导致props的类型在与其他道具合并时被删除。 A currently working workaround is to cast props as P

        return (
          <ThemeContext.Consumer>
            {(theme) => <Component {...props as P} theme={theme} />}
          </ThemeContext.Consumer>
        )