I have a union type (Pet
in the example below) that combines multiple object types which each have a type
property indicating their type. Sometimes I have an array of the union type (Pet[]
) and need to .filter()
it based on the type
property. That in itself works perfectly fine, but in order to avoid redundant type declarations I want to make sure that the result of the .filter()
call is automatically typed properly.
User-defined type guards seemed like the perfect solution for this, so I implemented one that just checks the type
property and narrows the type to { type: 'something' }
without explicitly declaring the full type (this one's called isCatLike
below). I tried using it inside an if
and it correctly narrowed my type from Pet
to Cat
.
I then tried to use it as a predicate for .filter()
and this time the type wasn't narrowed at all. The resulting array was still typed as Pet[]
although my if
experiment had shown that the type guard was generally able to narrow from Pet
to Cat
.
As another experiment I tried to change the type guard slightly and make the type predicate more explicit (is Cat
instead of is { type: 'cat' }
and suddenly the .filter()
call correctly narrowed the type from Pet[]
to Cat[]
(this function is called isCat
below).
type Cat = { type: 'cat'; name: string; purrs: boolean }
type Dog = { type: 'dog'; name: string; woofs: boolean }
type Pet = Cat | Dog
declare const pets: Pet[]
const isCatLike = (pet: any): pet is { type: 'cat' } => pet.type === 'cat'
const isCat = (pet: Pet): pet is Cat => pet.type === 'cat'
for (const pet of pets) {
if (isCatLike(pet)) {
pet // Cat -> Correct!
}
if (isCat(pet)) {
pet // Cat
}
}
const catLikes = pets.filter(isCatLike)
catLikes // Pet[] -> Incorrect!
const cats = pets.filter(isCat)
cats // Cat[]
Open the example on the TypeScript Playground to inspect the types yourself.
The problem now is that I can't use the more explicit approach (illustrated by the isCat
function) because my actual code has a lot more types in the union and there the predicate is also created by a function (isType(type: string)
).
So what I'm wondering at the moment is this:
Why does my "structural type guard" work in the context of an if
statement, but not as a predicate for filtering an array? Shouldn't it work exactly the same way in both cases? Am I doing something wrong or have I hit a limitation of the type system?
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…