![](/img/trans.png)
[英]How to generically type React component props and extend the generic props type
[英]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
排除鍵a
、 b
和c
。 在此處檢查排除。 然后, Pick
從I1 & 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>
。 第二個參數應該是undefined
或null
或C
(現在忽略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.