簡體   English   中英

React 和 Typescript:如何擴展通用組件道具?

[英]React and Typescript: How to extend generic component props?

想象一個靈活的組件,它接受一個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 '{}'.

這里我們需要了解一些實用程序類型以及解構在 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只包含三個鍵,其中一個 aka a已經用完了。 rest2的情況下,TS 仍然可以推斷Obj類型,因為來自接口I1鍵已用完。 用盡我的意思是它們不是使用 rest 運算符捕獲的。

但是在函數的情況下,TS 無法進行這種類型的推理。 不知道為什么TS做不到。 這可能是由於限制泛型。

在函數的情況下,函數內部的rest類型是Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">> Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">> Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">> Exclude從泛型類型T排除鍵abc 在此處檢查排除。 然后, PickI1 & T使用Exclude返回的鍵創建一個新類型。 由於T可以是任何類型,因此即使 T 被限制為Obj ,TS 也無法確定排除后的鍵,因此無法確定選擇的鍵以及新創建的類型。 這就是為什么函數中的類型變量rest仍然是Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">> Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">> Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>

請注意Pick返回的類型是Obj的子類型

現在來回答這個問題,同樣的情況發生在componentProps 推斷的類型將是Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">> Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">> . TS 將無法縮小范圍。 查看React.createElement的簽名

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

並調用它

React.createElement(component, componentProps)

簽名中P的推斷類型將是您代碼中第一個參數(即component C ,因為它的類型為React.ComponentType<C> 第二個參數應該是undefinednullC (現在忽略Attributes )。 但是componentProps的類型是Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">> Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">> ,它絕對可以分配給{}但不能分配給C因為它是{}子類型而不是C C也是{}的子類型,但選擇類型和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

希望這能回答您的問題並解決您的問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM