想象一下一个灵活的组件,它需要一个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 '{}'.
答案 0 :(得分: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
,由于接口Obj
的键已用尽,TS仍可以推断类型为I1
。精疲力竭是指没有使用rest运算符捕获它们。
但是在功能上,TS无法执行这种类型的推断。我不知道TS无法执行的原因。这可能是由于通用类的限制。
在使用函数的情况下,函数内部rest
的类型为Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>
。 Exclude
从通用类型a
中排除键b
,c
和T
。选中排除here。然后,Pick
使用I1 & T
返回的键从Exclude
创建新类型。由于T
可以是任何类型,因此即使T约束为Obj
,TS也无法确定排除后的密钥,因此也无法确定所选择的密钥以及新创建的类型。这就是为什么函数中的类型变量rest
保持Pick<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>
并称呼它
React.createElement(component, componentProps)
在签名中,P
的推断类型将在代码中从第一个参数开始C
,即component
,因为它的类型为React.ComponentType<C>
。第二个参数应该是undefined
或null
或C
(到目前为止,忽略Attributes
)。但是componentProps
的类型为Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>
,它绝对可以分配给{}
而不是C
,因为它是{}
的子类型,而不是{{1} }。 C
也是C
的子类型,但选择类型和{}
可能兼容,也可能不兼容(这与-存在类A; B和C派生A,对象B和C的B可以分配给A,但是B的对象不能归于C)。这就是为什么错误
C
由于我们比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 '{}'.
这绝对是正确的类型断言,因为我们知道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
希望这能回答您的问题并解决您的问题。