hen*_*dra 5 generics typescript reactjs react-component
想象一个灵活的组件,它接受 aReact.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);
};
Run Code Online (Sandbox Code Playgroud)
我可以以某种方式直接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);
};
Run Code Online (Sandbox Code Playgroud)
错误:
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 '{}'.
Run Code Online (Sandbox Code Playgroud)
这里我们需要了解一些实用程序类型以及 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
}
Run Code Online (Sandbox Code Playgroud)
在上面的代码中, for 的推断类型rest是{b: number, c: number}因为 objecti1仅包含三个键,并且其中一个键a已耗尽。在 的情况下rest2,TS 仍然可以推断类型,Obj因为接口中的键I1已用完。我所说的“精疲力尽”是指它们没有使用休息运算符捕获。
但对于函数来说,TS 无法进行此类推理。我不知道为什么TS做不到。这可能是由于泛型的限制。
如果是函数,则rest函数内部的类型是Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>。从泛型类型中排除Exclude键a和b。勾选此处的排除。然后,使用 所返回的键创建一个新类型。由于可以是任何类型,因此 TS 无法确定排除后的键,因此无法确定所选取的键以及新创建的类型,即使 T 被限制为。这就是函数中类型变量仍然保留的原因。cTPickI1 & TExcludeTObjrestPick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>
请注意,返回的类型Pick是以下类型的子类型Obj
现在回到问题,同样的情况也发生在componentProps. 推断的类型将是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>
Run Code Online (Sandbox Code Playgroud)
并称其为
React.createElement(component, componentProps)
Run Code Online (Sandbox Code Playgroud)
P签名中的推断类型将C在您的代码中从第一个参数开始,即component因为它具有 type React.ComponentType<C>。第二个参数应该是undefinedor nullor C(Attributes现在忽略)。componentProps但是is的类型Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>,它肯定可以分配给{}但不能分配给C,因为它是{}not of的子类型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 '{}'.
Run Code Online (Sandbox Code Playgroud)
由于我们比 TS 编译器更聪明,我们知道它们是兼容的,但 TS 不兼容。所以让 TS 相信我们做的是正确的,我们可以做这样的类型断言
'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 '{}'.
Run Code Online (Sandbox Code Playgroud)
这绝对是一个正确的类型断言,因为我们知道 of 的类型componentProps将是C
希望这能回答您的问题并解决您的问题。