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
只包含三个键,其中一个又名 a
已用尽。在 rest2
的情况下,TS 仍然可以将类型推断为 Obj
,因为来自接口 I1
的键已用完。精疲力尽的意思是它们不是使用 rest 运算符捕获的。
但在函数的情况下,TS 无法进行此类推理。我不知道为什么TS做不到。这可能是由于泛型的限制。
在函数的情况下发生的情况是函数内部 rest
的类型是 Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>
。 Exclude
从通用类型 T
中排除键 a
、b
和 c
。选中排除 here。然后,Pick
使用 Exclude
返回的键从 I1 & T
创建一个新类型。由于 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
的推断类型在您的代码中将从第一个参数即 component
推断为 C
,因为它的类型为 React.ComponentType<C>
。第二个参数应该是 undefined
或 null
或 C
(现在忽略 Attributes
)。但是 componentProps
的类型是 Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>
,它肯定可以分配给 {}
但不能分配给 C
因为它是 {}
的子类型而不是 C
. C
也是 {}
的子类型,但选择类型和 C
可能兼容也可能不兼容(这与 - 有一个 class 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
希望这能回答您的问题并解决您的问题。
想象一个灵活的组件,它接受 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
只包含三个键,其中一个又名 a
已用尽。在 rest2
的情况下,TS 仍然可以将类型推断为 Obj
,因为来自接口 I1
的键已用完。精疲力尽的意思是它们不是使用 rest 运算符捕获的。
但在函数的情况下,TS 无法进行此类推理。我不知道为什么TS做不到。这可能是由于泛型的限制。
在函数的情况下发生的情况是函数内部 rest
的类型是 Pick<I1 & T, Exclude<keyof T, "a" | "b" | "c">>
。 Exclude
从通用类型 T
中排除键 a
、b
和 c
。选中排除 here。然后,Pick
使用 Exclude
返回的键从 I1 & T
创建一个新类型。由于 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
的推断类型在您的代码中将从第一个参数即 component
推断为 C
,因为它的类型为 React.ComponentType<C>
。第二个参数应该是 undefined
或 null
或 C
(现在忽略 Attributes
)。但是 componentProps
的类型是 Pick<Props<C> & C, Exclude<keyof C, "otherProp" | "component">>
,它肯定可以分配给 {}
但不能分配给 C
因为它是 {}
的子类型而不是 C
. C
也是 {}
的子类型,但选择类型和 C
可能兼容也可能不兼容(这与 - 有一个 class 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
希望这能回答您的问题并解决您的问题。