旧巷馆子

旧巷馆子

twitter
jike

Using custom type assertions in Array.filter

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 some fields of the type are determined, and you need to operate on each element during traversal. The actual type of the element can only be determined through some custom logic.

When you want to get the result with just one call and also want to filter out elements of a certain subtype, you often write code like this:

const arr: Base[] = [/* ... */]
const arrOfExt1 = arr
  .filter(item => item.type === 't1')
  .map(item => item as Extend1)

The use of as in the above .map looks ugly and redundant because it seems to be doing nothing meaningful.

The core issue is that the return value of .filter seems to still be just Base[]. But when you open the TS built-in type definition of the .filter method:

Type definition of .filter

Oh, there's another option? It seems that you can pass a type parameter to the .filter method or explicitly define a type predicate in the signature of the argument function passed to .filter. TypeScript's "type predicate" can be used to customize type assertions, so we can do this:

const arrOfExt1 = arr
  .filter((item): item is Extend1 => item.type === 't1')

console.log(arr_of_ext1)
//           ?^ Extend1[]

According to the code, the side using .filter should belong to the business logic, and the predicate function passed in is a separate util. This type assertion function may be reused multiple times, so consider extracting the judgment function:

function isExtend1(item: Base): item is Extend1 {
  return item.type === 't1'
}

const arrOfExt1 = arr.filter(isExtend1)

Finally, the result we see is very concise, intuitive, and readable.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.