【问题标题】:React and Typescript: How to extend generic component props?React 和 Typescript:如何扩展通用组件道具?
【发布时间】:2021-01-26 00:22:00
【问题描述】:

想象一个灵活的组件,它接受 React.ComponentType 和它的 props 并呈现它:

type Props<C> = {
  component: React.ComponentType<C>;
  componentProps: C;
  otherProp: string;
};

const MyComponent = <C extends {}>(props: Props<C>) => {
  return React.createElement(props.component, props.componentProps);
};

我可以让MyComponent 直接接收动态props,例如像那样(不工作):

type Props<C> = {
  component: React.ComponentType<C>;
  otherProp: string;
};

const MyComponent = <C extends {}>(props: Props<C> & C) => {
  const { otherProp, component, ...componentProps } = props;
  return React.createElement(component, componentProps);
};

错误:

Error:(11, 41) TS2769: No overload matches this call.
  The last overload gave the following error.
    Argument of type 'Pick<Props<C> & C, Exclude<keyof C, "component" | "otherProp">>' is not assignable to parameter of type 'Attributes & C'.
      Type 'Pick<Props<C> & C, Exclude<keyof C, "component" | "otherProp">>' is not assignable to type 'C'.
        'Pick<Props<C> & C, Exclude<keyof C, "component" | "otherProp">>' is assignable to the constraint of type 'C', but 'C' could be instantiated with a different subtype of constraint '{}'.

【问题讨论】:

    标签: reactjs typescript generics react-component


    【解决方案1】:

    这里我们需要了解一些实用程序类型以及解构在 TS 中是如何发生的。

    type Obj = {
      [key: string]: any
    }
    
    interface I1 {
      a: number
      b: number
      c: number
    }
    
    const i1: I1 = {
      a: 1,
      b: 1,
      c: 1,
    }
    
    let {a, ...rest} = i1
    
    interface Obj {
        [key: string]: any
    }
    
    const i2: Obj & I1 = {
      a: 1,
      b: 1,
      c: 1,
      d: 1,
      e: 1,
    }
    
    let {a: a1, b, c, ...rest2} = i2
    
    function func<T extends Obj>(param: I1 & T) {
        const {a, b, c, ...rest} = param
    }
    

    在上面的代码中,rest 的推断类型将是 {b: number, c: number},因为对象 i1 仅包含三个键,其中一个又名 a 已用尽。在rest2 的情况下,TS 仍然可以将类型推断为Obj,因为来自接口I1 的键已用尽。用尽我的意思是他们没有使用休息运算符捕获。

    但是在函数的情况下,TS 无法进行这种类型的推理。不知道TS不能做的原因。这可能是由于泛型的限制。

    如果是函数,函数内部的rest 的类型是Pick&lt;I1 &amp; T, Exclude&lt;keyof T, "a" | "b" | "c"&gt;&gt;Exclude 从通用类型 T 中排除键 abc。选中排除 here。然后,PickI1 &amp; T 创建一个新类型,其键由Exclude 返回。由于T 可以是任何类型,TS 无法确定排除后的键,因此即使 T 被限制为Obj,也无法确定选择的键和新创建的类型。这就是为什么函数中的类型变量rest仍然是Pick&lt;I1 &amp; T, Exclude&lt;keyof T, "a" | "b" | "c"&gt;&gt;

    请注意Pick返回的类型是Obj的子类型

    现在问题来了,componentProps 也发生了同样的情况。推断的类型将为Pick&lt;Props&lt;C&gt; &amp; C, Exclude&lt;keyof C, "otherProp" | "component"&gt;&gt;。 TS 将无法缩小范围。看React.createElement的签名

     function createElement<P extends {}>(
            type: ComponentType<P> | string,
            props?: Attributes & P | null,
            ...children: ReactNode[]): ReactElement<P>
    

    然后叫它

    React.createElement(component, componentProps)
    

    签名中P 的推断类型将是您代码中第一个参数的C,即component,因为它的类型为React.ComponentType&lt;C&gt;。第二个参数应该是undefinednullC(现在忽略Attributes)。但是componentProps 的类型是Pick&lt;Props&lt;C&gt; &amp; C, Exclude&lt;keyof C, "otherProp" | "component"&gt;&gt;,它绝对可以分配给{},但不能分配给C,因为它是{} 的子类型而不是CC 也是 {} 的子类型,但选择类型和 C 可能兼容也可能不兼容(这与 - 有一个类 A;B 和 C 派生 A,B 和 C 的对象是可分配的归于 A,但 B 的对象不能归于 C)。这就是错误的原因

            'Pick<Props<C> & C, Exclude<keyof C, "component" | "otherProp">>' is assignable to the constraint of type 'C', but 'C' could be instantiated with a different subtype of constraint '{}'.
    
    

    由于我们比 TS 编译器更智能,我们知道它们是兼容的,但 TS 不兼容。所以让 TS 相信我们做的是正确的,我们可以做这样的类型断言

    type Props<C> = {
      component: React.ComponentType<C>;
      otherProp: string;
    };
    
    const MyComponent = <C extends {}>(props: Props<C> & C) => {
      const { otherProp, component, ...componentProps } = props;
      return React.createElement(component, componentProps as unknown as C);
      // ------------------------------------------------^^^^^^^^
    };
    

    这绝对是一个正确的类型断言,因为我们知道componentProps 的类型将是C

    希望这能回答您的问题并解决您的问题。

    【讨论】:

      猜你喜欢
      • 2018-02-19
      • 2019-12-16
      • 2020-06-03
      • 1970-01-01
      • 2020-03-14
      • 1970-01-01
      • 2019-06-30
      • 1970-01-01
      • 2021-10-22
      相关资源
      最近更新 更多