Let's say I have three types (and a union one)
type A = {
type: 'a'
title: string
description: string
}
type B = {
type: 'b'
title: string
}
type C = {
type: 'c'
description: string
}
type D = A | B | C
I know I can get a correct type inference with the === operator
function logger(t: D) {
if (t.type === 'a' || t.type === 'b') console.log(t.title) // no problems here
if (t.type === 'a' || t.type === 'c') console.log(t.description) // no problems here
}
However, would it be possible to write an utility function:
function matches<T extends { type: string }>(t: T, types: T['type'][]) : boolean {
return types.includes(t.type)
}
So I can do this without errors?
function logger(t: D) {
if (matches(t, ['a', 'b'])) console.log(t.title) // Property 'title' does not exist on type D
if (matches(t, ['a', 'c'])) console.log(t.description) // Property 'description' does not exist on type D
}
You could write
matches()
as a user-defined type guard function so that the compiler understands your intent for thetrue
/false
output of the function to narrow the type of thet
parameter.Here's one way to do it:
This is generic both in
T
, the type oft
, but alsoK
, the union of string literal types of the elements oftypes
. The return type isExtract<T, {type: K}>
, which uses theExtract<T, U>
utility type to filter the union typeT
to just those constituents assignable to{type: K}
.Note that the compiler will complain about
types.includes(t.type)
because the elements oftypes
are narrower thant.type
. The way I deal with that is to first (safely) widentypes
fromK[]
toreadonly string[]
and then callincludes()
on that. See this question and its answers for more information.Let's see if it works:
Looks good!
Playground link to code