从打字稿中的部分受歧视的联合中检测类型
Detecting a type from a partial of a discriminated unions in typescript
我在制作类型检测函数时遇到问题,该函数从已区分的联合中获取部分字段,并从联合中获取匹配类型 return。我有一个 create()
函数,它接受不包括时间戳和 ID 的字段,我 return 那些带有 ID 和时间戳的字段。我们在参数和 return 对象中有 type
字段,但它没有用于确定 return 类型。
interface Node {
type: string
id: string
createdAt: number
updatedAt: number
}
interface Pokemon extends Node {
type: 'Pokemon'
name: string;
}
interface Trainer extends Node {
type: 'Trainer'
name: string;
pokemon: string[]
}
type CreateNode<T extends Node> = T extends unknown
? Omit<T, 'id' | 'createdAt' | 'updatedAt'>
: never
function create<R extends Node>(obj: CreateNode<R>): R {
return {
...obj,
id: 'ds',
createdAt: 4,
updatedAt: 5,
} // give as error "... as is assignable to the constraint of type 'R', but 'R' could be instantiated with a different subtype of constraint 'Node'."
}
const pokemon = create<Pokemon|Trainer>({
type: 'Pokemon',
name: 'Garalaxapon'
})
pokemon //should be Pokemon but is Pokemon | Trainer
我不明白 return 中的错误,但我确定这就是问题的症结所在。谢谢!
pokemon //should be Pokemon but is Pokemon | Trainer
这是正确的。原因:
您有 const pokemon = create<Pokemon|Trainer>
,其中 create<R extends Node>(obj: CreateNode<R>): R
。因为create
returns传入的是R
,pokemon就是你传入的R
。你传入的是Pokemon|Trainer
所以pokemon: Pokemon|Trainer
解决方案
如果这是您想要的,请使用 Pokemon
,即 create<Pokemon>
我将忽略实施中的问题,这是与您询问的问题不同的问题。现在我将假设实现有效,并使用 declare
statement 来关注调用签名问题。
您面临的问题是您对单个 R
通用类型的期望过高。您手动指定它是为了让编译器知道您想要区分哪个特定的 Node
子类型联合。但是您还希望编译器以某种方式将 R
缩小到该联合的成员之一。这是行不通的。一旦您手动将 R
指定为一个联合体,它就是永远的联合体。
您可以使用两个通用参数来解决这个问题;一个 (R
) 您为可区分的联合指定,一个 (T
) 编译器从 passed-in 参数推断。不幸的是,您不能在单个函数签名中执行此操作;您需要手动指定 R
和 T
,否则编译器将尝试推断 R
和 T
。 TypeScript 中没有 partial type parameter inference (microsoft/TypeScript#26242)。
有时在这种情况下,我会使用类型参数 currying 将两个类型参数的单个函数拆分为多个 one-type-parameter 函数。在您的情况下,它看起来像这样:
declare function create<R extends Node>(): <T extends CreateNode<R>>(obj: T) => Extract<R, T>;
注意 R
如何对应于完整的可区分联合,而 T
指的是 passed-in obj
参数的类型。您通过使用 Extract
实用程序类型将 R
与 T
区分开来:return 只有 R
中可分配给 T
的元素:
const createPokemonOrTrainer = create<Pokemon | Trainer>();
const pokemon = createPokemonOrTrainer({
type: 'Pokemon',
name: 'Garalaxapon'
}); // Pokemon
在这里,create()
return 是另一个函数,create<Pokemon | Trainer>()
return 是您之前尝试创建的函数:接受 partial-ish Pokemon | Trainer
并将其区分为 Pokemon
或 Trainer
.
但也许您实际上根本不需要 R
;你打算每次都用不同的受歧视工会打电话给 create()
吗?如果你只打算使用像 Pokemon | Trainer
这样的单一联合类型,那么你可以只对它进行硬编码。本质上,不要为 over-generic create()
而烦恼,只需手动编写 createPokemonOrTrainer()
:
type DiscrimUnion = Pokemon | Trainer;
declare function createDiscrimUnion<T extends CreateNode<DiscrimUnion>>(obj: T): Extract<DiscrimUnion, T>;
const pokemonAlso = createDiscrimUnion({
type: 'Pokemon',
name: 'Garalaxapon'
}); // Pokemon
好的,希望对您有所帮助;祝你好运!
我在制作类型检测函数时遇到问题,该函数从已区分的联合中获取部分字段,并从联合中获取匹配类型 return。我有一个 create()
函数,它接受不包括时间戳和 ID 的字段,我 return 那些带有 ID 和时间戳的字段。我们在参数和 return 对象中有 type
字段,但它没有用于确定 return 类型。
interface Node {
type: string
id: string
createdAt: number
updatedAt: number
}
interface Pokemon extends Node {
type: 'Pokemon'
name: string;
}
interface Trainer extends Node {
type: 'Trainer'
name: string;
pokemon: string[]
}
type CreateNode<T extends Node> = T extends unknown
? Omit<T, 'id' | 'createdAt' | 'updatedAt'>
: never
function create<R extends Node>(obj: CreateNode<R>): R {
return {
...obj,
id: 'ds',
createdAt: 4,
updatedAt: 5,
} // give as error "... as is assignable to the constraint of type 'R', but 'R' could be instantiated with a different subtype of constraint 'Node'."
}
const pokemon = create<Pokemon|Trainer>({
type: 'Pokemon',
name: 'Garalaxapon'
})
pokemon //should be Pokemon but is Pokemon | Trainer
我不明白 return 中的错误,但我确定这就是问题的症结所在。谢谢!
pokemon //should be Pokemon but is Pokemon | Trainer
这是正确的。原因:
您有 const pokemon = create<Pokemon|Trainer>
,其中 create<R extends Node>(obj: CreateNode<R>): R
。因为create
returns传入的是R
,pokemon就是你传入的R
。你传入的是Pokemon|Trainer
所以pokemon: Pokemon|Trainer
解决方案
如果这是您想要的,请使用 Pokemon
,即 create<Pokemon>
我将忽略实施中的问题,这是与您询问的问题不同的问题。现在我将假设实现有效,并使用 declare
statement 来关注调用签名问题。
您面临的问题是您对单个 R
通用类型的期望过高。您手动指定它是为了让编译器知道您想要区分哪个特定的 Node
子类型联合。但是您还希望编译器以某种方式将 R
缩小到该联合的成员之一。这是行不通的。一旦您手动将 R
指定为一个联合体,它就是永远的联合体。
您可以使用两个通用参数来解决这个问题;一个 (R
) 您为可区分的联合指定,一个 (T
) 编译器从 passed-in 参数推断。不幸的是,您不能在单个函数签名中执行此操作;您需要手动指定 R
和 T
,否则编译器将尝试推断 R
和 T
。 TypeScript 中没有 partial type parameter inference (microsoft/TypeScript#26242)。
有时在这种情况下,我会使用类型参数 currying 将两个类型参数的单个函数拆分为多个 one-type-parameter 函数。在您的情况下,它看起来像这样:
declare function create<R extends Node>(): <T extends CreateNode<R>>(obj: T) => Extract<R, T>;
注意 R
如何对应于完整的可区分联合,而 T
指的是 passed-in obj
参数的类型。您通过使用 Extract
实用程序类型将 R
与 T
区分开来:return 只有 R
中可分配给 T
的元素:
const createPokemonOrTrainer = create<Pokemon | Trainer>();
const pokemon = createPokemonOrTrainer({
type: 'Pokemon',
name: 'Garalaxapon'
}); // Pokemon
在这里,create()
return 是另一个函数,create<Pokemon | Trainer>()
return 是您之前尝试创建的函数:接受 partial-ish Pokemon | Trainer
并将其区分为 Pokemon
或 Trainer
.
但也许您实际上根本不需要 R
;你打算每次都用不同的受歧视工会打电话给 create()
吗?如果你只打算使用像 Pokemon | Trainer
这样的单一联合类型,那么你可以只对它进行硬编码。本质上,不要为 over-generic create()
而烦恼,只需手动编写 createPokemonOrTrainer()
:
type DiscrimUnion = Pokemon | Trainer;
declare function createDiscrimUnion<T extends CreateNode<DiscrimUnion>>(obj: T): Extract<DiscrimUnion, T>;
const pokemonAlso = createDiscrimUnion({
type: 'Pokemon',
name: 'Garalaxapon'
}); // Pokemon
好的,希望对您有所帮助;祝你好运!