If you are a TypeScript user, you may have experienced the following scenario:
interface Base {
type: 't1' | 't2'
}
interface Extend1 extends Base {
ccc: string
bbb: number
}
interface Extend2 extends Base {
aaa: boolean
}
You have an array where only a part of the fields are determined, and you need to operate on each element during traversal, only able to determine the actual type of the element through some custom logic.
And when you want to get the result with just a single call, while also wanting to filter out elements of a certain subtype, you often end up writing code like this:
const arr: Base[] = [/* ... */]
const arrOfExt1 = arr
.filter(item => item.type === 't1') as Extend1[]
The above usage of assertion may seem subjective, and it is fine to use it this way, but there is a better solution for the "assertion after filter" issue.
The core issue lies in the fact that the return value of .filter
seems to still be just a Base[]
. When you look at the built-in type definition of the .filter
method in TypeScript:
Oh, there seems to be another way? It appears that you can pass a type parameter to the .filter
method, or explicitly define a type predicate in the signature part of the function passed to .filter
. TypeScript's "type predicates" can be used to customize type assertions, so we can do it like this:
const arrOfExt1 = arr
.filter((item): item is Extend1 => item.type === 't1')
console.log(arr_of_ext1)
// ?^ Extend1[]
Following the principle of separation of concerns, the side using .filter
should belong to business logic, and the predicate function passed in is a standalone util. This type assertion function may be reused multiple times, so consider extracting the predicate function:
function isExtend1(item: Base): item is Extend1 {
return item.type === 't1'
}
const arrOfExt1 = arr.filter(isExtend1)
In the end, the result we see is very concise, intuitive, and readable.