在检查值是否是联合的一部分时摆脱类型断言
Get rid of type assertion when checking if value is part of union
我有一个部分基于数组的联合类型——我也想用它来检查 运行 时间的值。但是,TypeScript 强制我在这里使用类型断言。
考虑以下代码:
const Pets = ["dog", "cat"] as const
type Pet = typeof Pets[number]
type Animal = Pet | "tiger"
function checkDanger(animal: Animal) {
if (Pets.includes(animal as Pet)) {
return "not dangerous"
}
return "very dangerous"
}
问题出在 as Pet
部分。
如果我忽略它,我会得到:Argument of type 'Animal' is not assignable to parameter of type '"dog" | "cat"'. Type '"tiger"' is not assignable to type '"dog" | "cat"'.
但是,在更复杂的现实情况下,该断言可能会产生我想要避免的影响。有没有办法在没有断言的情况下做这样的事情?
不幸的是,这是打字稿编译器的限制。但是,不用在 animal as Pet
的同一行中编写千个函数,您可以按如下方式使用类型保护
function isPet(something: Pet | Animal) : something is Pet {
return Pets.includes(something as Pet)
}
function checkDanger(animal: Animal) {
if (isPet(animal)) {
return "not dangerous"
}
return "very dangerous"
}
这种方法更适用,因为如果重命名类型定义名称,重构代码会更容易。
不过,如果你声明一个只读数组,你就失去了灵活性,并且必须执行类型断言。
这是 includes
的一个已知问题,请参阅 issues/26255。
但是,有一个解决方法。您可以创建自定义 curried
typeguard:
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) => (elem: string
): elem is Tuple[number] =>
tuple.includes(elem)
// (elem: string) => elem is "dog" | "cat"
const inPets = inTuple(Pets)
让我们试试看:
const Pets = ["dog", "cat"] as const
type Pet = typeof Pets[number]
type Animal = Pet | "tiger"
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) => (elem: string
): elem is Tuple[number] =>
tuple.includes(elem)
// (elem: string) => elem is "dog" | "cat"
const inPets = inTuple(Pets)
function checkDanger(animal: Animal) {
if (inPets(animal)) {
animal // "dog" | "cat"
return "not dangerous"
}
return "very dangerous"
}
因为你有一个条件语句,我假设这个函数可以重载以缩小 return 类型:
const Pets = ["dog", "cat"] as const
type Pet = typeof Pets[number]
type Animal = Pet | "tiger"
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) => (elem: string
): elem is Tuple[number] =>
tuple.includes(elem)
// (elem: string) => elem is "dog" | "cat"
const inPets = inTuple(Pets)
function checkDanger(animal: Pet): "not dangerous"
function checkDanger(animal: Animal): "very dangerous"
function checkDanger(animal: string) {
if (inPets(animal)) {
animal // "dog" | "cat"
return "not dangerous"
}
return "very dangerous"
}
const result = checkDanger('tiger') // very dangerous
const result2 = checkDanger('cat') // not dangerous
Playground
重载签名的顺序很重要。
您可能已经注意到,我的代码中没有类型断言。
inTuple
typeguard 有效,因为 tuple
被视为函数体内的字符串数组。这意味着该操作是允许的,因为 tuple[number]
和 elem
可以相互赋值。
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) =>
(elem: string): elem is Tuple[number] => {
tuple[2] = elem // ok
elem = tuple[3] // ok
return tuple.includes(elem)
}
我有一个部分基于数组的联合类型——我也想用它来检查 运行 时间的值。但是,TypeScript 强制我在这里使用类型断言。 考虑以下代码:
const Pets = ["dog", "cat"] as const
type Pet = typeof Pets[number]
type Animal = Pet | "tiger"
function checkDanger(animal: Animal) {
if (Pets.includes(animal as Pet)) {
return "not dangerous"
}
return "very dangerous"
}
问题出在 as Pet
部分。
如果我忽略它,我会得到:Argument of type 'Animal' is not assignable to parameter of type '"dog" | "cat"'. Type '"tiger"' is not assignable to type '"dog" | "cat"'.
但是,在更复杂的现实情况下,该断言可能会产生我想要避免的影响。有没有办法在没有断言的情况下做这样的事情?
不幸的是,这是打字稿编译器的限制。但是,不用在 animal as Pet
的同一行中编写千个函数,您可以按如下方式使用类型保护
function isPet(something: Pet | Animal) : something is Pet {
return Pets.includes(something as Pet)
}
function checkDanger(animal: Animal) {
if (isPet(animal)) {
return "not dangerous"
}
return "very dangerous"
}
这种方法更适用,因为如果重命名类型定义名称,重构代码会更容易。
不过,如果你声明一个只读数组,你就失去了灵活性,并且必须执行类型断言。
这是 includes
的一个已知问题,请参阅 issues/26255。
但是,有一个解决方法。您可以创建自定义 curried
typeguard:
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) => (elem: string
): elem is Tuple[number] =>
tuple.includes(elem)
// (elem: string) => elem is "dog" | "cat"
const inPets = inTuple(Pets)
让我们试试看:
const Pets = ["dog", "cat"] as const
type Pet = typeof Pets[number]
type Animal = Pet | "tiger"
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) => (elem: string
): elem is Tuple[number] =>
tuple.includes(elem)
// (elem: string) => elem is "dog" | "cat"
const inPets = inTuple(Pets)
function checkDanger(animal: Animal) {
if (inPets(animal)) {
animal // "dog" | "cat"
return "not dangerous"
}
return "very dangerous"
}
因为你有一个条件语句,我假设这个函数可以重载以缩小 return 类型:
const Pets = ["dog", "cat"] as const
type Pet = typeof Pets[number]
type Animal = Pet | "tiger"
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) => (elem: string
): elem is Tuple[number] =>
tuple.includes(elem)
// (elem: string) => elem is "dog" | "cat"
const inPets = inTuple(Pets)
function checkDanger(animal: Pet): "not dangerous"
function checkDanger(animal: Animal): "very dangerous"
function checkDanger(animal: string) {
if (inPets(animal)) {
animal // "dog" | "cat"
return "not dangerous"
}
return "very dangerous"
}
const result = checkDanger('tiger') // very dangerous
const result2 = checkDanger('cat') // not dangerous
Playground 重载签名的顺序很重要。
您可能已经注意到,我的代码中没有类型断言。
inTuple
typeguard 有效,因为 tuple
被视为函数体内的字符串数组。这意味着该操作是允许的,因为 tuple[number]
和 elem
可以相互赋值。
const inTuple = <Tuple extends string[]>(
tuple: readonly [...Tuple]) =>
(elem: string): elem is Tuple[number] => {
tuple[2] = elem // ok
elem = tuple[3] // ok
return tuple.includes(elem)
}