React组件泛型为指定类型时如何让Prop可选?类型推断异常排查
我希望当React组件的泛型类型参数为Option时,transform prop变为可选。为此定义了如下类型:
type Option = { value: string; label: string } type TransformFn<T> = (item: T) => Option type MaybeOptionalTransform<T> = T extends Option ? { transform?: TransformFn<T> } : { transform: TransformFn<T> } type SuspendedPromise<T> = { read: () => T} type SuspendedSelectProps<T> = { resource: SuspendedPromise<T[]> onChange?: (value: string) => void value?: string } & MaybeOptionalTransform<T> function SuspendedSelect<T>(props: SuspendedSelectProps<T>) { return null }
以函数形式调用组件时一切正常:
SuspendedSelect({ resource: { read: () => [{ foo: 'aaa', bar: 'bbb' }] }, onChange: value => undefined, transform: item => ({ label: item.foo, value: item.bar }) }) SuspendedSelect({ resource: { read: () => [{ label: 'aaa', value: 'bbb' }] }, onChange: value => undefined })
两种调用均符合预期,TypeScript无报错。但以React组件形式调用时:
<SuspendedSelect resource={{ read: () => [{ foo: 'aaa', bar: 'bbb' }] }} onChange={value => undefined} transform={item => ({ label: item.foo, value: item.bar })} />
出现TypeScript错误:
TS2322: Type '(item: Option & { foo: string; bar: string; }) => { label: string; value: string; }'
is not assignable to type 'TransformFn<{ foo: string; bar: string; }>'.
Types of parameters 'item' and 'item' are incompatible.
Type '{ foo: string; bar: string; }' is not assignable to type
'Option & { foo: string; bar: string; }'.
Type '{ foo: string; bar: string; }' is missing the following properties from type
'Option': value, labeldeviation-modal.tsx(23, 7): The expected type comes from property 'transform' which is
declared here on type
'IntrinsicAttributes
& {
resource: { read(): { foo: string; bar: string; }[]; };
onChange?: ((value: string) => void) | undefined; value?: string | undefined;
}
& { transform: TransformFn<...>; }'
手动指定组件渲染时的类型可消除错误,但我希望TypeScript能自动推断正确类型。请问为何TypeScript会为transform函数推断“错误”类型?有什么解决办法?
这是React组件JSX语法的类型推断逻辑与函数式调用不同导致的:
- JSX调用时,TypeScript会同时从所有props推断泛型
T,包括transform函数的参数和返回值。 - 由于
MaybeOptionalTransform<T>是分布式条件类型,TypeScript会错误地将transform的返回值类型(Option)反向关联到T的约束,导致它推断T为Option & { foo: string; bar: string; },而非正确的{ foo: string; bar: string; }。 - 函数式调用时,TypeScript的推断顺序更直接:先从
resource的返回值确定T,再验证transform是否符合TransformFn<T>,因此不会出现推断冲突。
方案1:使用非分布式条件类型
通过在条件判断中用元组包裹T,将分布式条件类型改为非分布式,阻止TypeScript提前拆分类型进行反向推断:
type Option = { value: string; label: string } type TransformFn<T> = (item: T) => Option // 用[T] extends [Option]避免分布式条件类型的反向推断问题 type MaybeOptionalTransform<T> = [T] extends [Option] ? { transform?: TransformFn<T> } : { transform: TransformFn<T> } type SuspendedPromise<T> = { read: () => T} type SuspendedSelectProps<T> = { resource: SuspendedPromise<T[]> onChange?: (value: string) => void value?: string } & MaybeOptionalTransform<T> function SuspendedSelect<T>(props: SuspendedSelectProps<T>) { return null }
方案2:调整泛型推断优先级
拆分泛型参数,让TypeScript优先从resource推断T,再处理transform的类型约束:
type Option = { value: string; label: string } type TransformFn<T> = (item: T) => Option // 用默认泛型参数U=T,优先从resource推断T type SuspendedSelectProps<T, U = T> = { resource: SuspendedPromise<T[]> onChange?: (value: string) => void value?: string } & (U extends Option ? { transform?: TransformFn<U> } : { transform: TransformFn<U> }) type SuspendedPromise<T> = { read: () => T} function SuspendedSelect<T>(props: SuspendedSelectProps<T>) { return null }
内容的提问来源于stack exchange,提问作者Simon

