【问题标题】:Default prop types through a higher order component通过高阶组件的默认道具类型
【发布时间】:2019-06-26 11:20:21
【问题描述】:

通过 HOC 传递组件会导致 defaultProps 信息丢失给 typescript 编译器。比如

themed.tsx

export interface ThemedProps {
    theme: {};
}

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type Subtract<T extends K, K> = Omit<T, keyof K>;

const themed = <P extends ThemedProps = ThemedProps>(
    ComponentToWrap: React.ComponentType<P>
) => {
    return class ThemeWrappedComponent extends React.Component<
         Subtract<P, ThemedProps>
    > {
        static displayName = `themed(${ComponentToWrap.displayName})`;

        theme = () => {
            return {}
        };

        render() {
            return (
                <ComponentToWrap
                    {...this.props as P}
                    theme={this.theme()}
                />
            );
        }
    }
};

Foo.tsx

interface FooProps {
    theme: object,
    name: string,
}

class Foo extends React.Component<FooProps> {
    static defaultProps = {
        name: 'world'
    }
    render() {
        return <span>hello ${this.props.name}</span>
    }
}

export default themed(Foo);

当我实例化 &lt;Foo /&gt; 时,我收到一个编译器错误,提示 Property 'name' is missing in type '{}' but required in type 'Readonly&lt;Pick&lt;FooProps, "name"&gt;&gt;'.

我知道有一种方法可以使用 JSX.LibraryManagedAttributes 来解决这种问题,但我不知道如何,也找不到任何关于该功能的文档。

【问题讨论】:

    标签: javascript reactjs typescript higher-order-components


    【解决方案1】:

    您必须利用 JSX.LibraryManagedAttributes 才能从 HOC 中的包装组件中提取必需和可选(默认)道具。这看起来有点棘手:

    import React from 'react';
    
    interface FooProps {
      theme: string;
      name: string;
    }
    
    class Foo extends React.Component<FooProps> {
      static defaultProps = {
        name: 'world',
      };
      render() {
        return <span>hello ${this.props.name}</span>;
      }
    }
    
    interface ThemedProps {
      theme: string;
    }
    
    type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
    type Subtract<T extends K, K> = Omit<T, keyof K>;
    
    
    const themed = <
      C extends React.ComponentType<React.ComponentProps<C> & ThemedProps>,
      // that's where all the magic happens
      ResolvedProps = JSX.LibraryManagedAttributes<C, Subtract<React.ComponentProps<C>, ThemedProps>>
    >(
      Component: C
    ) => {
      return class ThemeWrappedComponent extends React.Component<ResolvedProps> {
        static displayName = `themed(${ComponentToWrap.displayName})`;
    
        render() {
          return (
            <Component
              // proper typecast since ts has fixed type infering on object rest
              {...this.props as JSX.LibraryManagedAttributes<C, React.ComponentProps<C>>} 
              theme="theme" 
            />
          );
        }
      };
    };
    
    const Wrapped = themed(Foo);
    
    const el = <Wrapped />; // works
    

    【讨论】:

    • 我认为重要的是要注意,这里的JSX.LibraryManagedAttributes&lt;C, ...&gt; C 应该是一个类型,而不仅仅是一个组件类,所以typeof ComponentName 将是另一种情况的正确选择。我花了半个小时试图弄清楚为什么 @types/react 中的条件 C extends { defaultProps: infer D; } 对我不起作用。
    猜你喜欢
    • 2018-04-03
    • 1970-01-01
    • 2019-02-20
    • 2021-11-23
    • 1970-01-01
    • 2021-02-27
    • 1970-01-01
    • 1970-01-01
    • 2020-11-09
    相关资源
    最近更新 更多