October 25, 2019
My previous post was the final part of the TypeScript in the Back series but I just could not help myself not to write another TypeScript post! So I’m calling this one a bonus round and the topic is Utility Types. These types have been a mystery for me a long time until recently so I thought that might be the case for others as well.
The post is divided into two parts because I realized I was working on this for a long time and it started to get a bit lengthy.
This is the first part and will be about the first six utility types: Partial<T>
, Required<T>
, Readonly<T>
, Record<K,T>
, Pick<T,K>
and Omit<T,K>
.
The second part will be about the rest of the types.
In case you’ve never heard of utility types, you might be confused about the topic or why you should care. First of all, here are the types we are going to take a look at part 1:
The main reason why I think utility types are so awesome is that often when dealing with JavaScript code and libraries there’s a need to do complex typing. You want the power and flexibility of JavaScript and the safety of static types and that requires a advanced type system.
Utility types are for type transformations in the same way that there are many utility functions in JavaScript for data transformations (e.g. map, filter, reduce). Utility types typically take one or more types as arguments and return a new type.
Hold on a moment! Before we jump into Utility Types there are some features of TypeScript you should be familiar with first.
Index Type Query or keyof
returns the permitted property names for a given type as a union type. The returned union type is a subtype of string
because all the property names are strings. Here’s an example:
interface Person {
name: string
age: number
location: string
}
type K1 = keyof Person // "name" | "age" | "location"
When looking at how utility types are implemented, you will bump into keyof T
a lot.
The keyof example resulted in an interesting looking type "name" | "age" | "location"
. This is a union type. Union types are used constantly with utility types so it’s best to get familiar with them. Also, some of the utility types operate exclusively on union types. Union type simply means that the type can be any of the types separated with |
character.
In the previous example type K1
can be either "name"
, "age"
or "location"
but not for example "phoneNumber"
.
Another example of a union type would be for example:
type T = number | string
const t1: T = 1
const t2: T = 'foo'
const t3: T = true // Type 'true' is not assignable to type 'T'.
Number 1
and string "foo"
are valid values for type T
but not true
which is a boolean
.
Mapped Types are a way to create new types based on old types. Here is a fictious example:
type Foo = { a: number; b: string }
type Flags = { [P in keyof T]: boolean } // { a: boolean, b: boolean }
Think of the [P in K]: T
as the same kind of syntax as for .. in
. All the properties K
on the right side of in
will be iterated over and the bound type variable on each turn is P
. The resulting type is T
. Things get a bit more complicated in actual utility types but this is the basic idea of mapped types.
Ok! So let’s get started with our first utility type Partial<T>
. This utility type can be used to create a type with any number of fields from the original type. Here’s the implementation of the type:
type Partial<T> = { [P in keyof T]?: T[P] }
Based on what we learned about keyof
and mapped types we can read that this means that every property type from original type T
will be mapped to a new optional property.
The first time I ran into the Partial<T>
type was when using classes in TypeScript. I wanted to create an instance of a class in the same way it can be done with an object initializer in for example C#.
💡 In case you’re not familiar with C# this is what the object initializer looks like:
var person = new Person() {
Id = 1,
Name = "Matti"
};
In TypeScript, there’s not a similar feature as an object initializer. With a class, you need to define a constructor that initializes all the properties of the object.
class Person {
id: number
name: string
country: string
city: string
age: number
constructor(
id: number,
name: string,
country: string,
city: string,
age: number
) {
this.id = id
this.name = name
this.country = country
this.city = city
this.age = age
}
}
const person = new Person(1, 'Matti Petrelius', 'Finland', 'Helsinki', 37)
That’s a lot of code just to initialize an object. We can keep the code more concise with the Partial<T>
utility type.
class Person {
id: number = 0
name: string = ''
csountry: string = ''
city: string = ''
age: number = 0
constructor(init: Partial<Person>) {
Object.assign(this, init)
}
}
const person = new Person({ id: 1, name: 'Matti Petrelius' })
Now the constructor takes a single parameter and has a single call to the Object.assign
function. Also, the object given as an argument can have any number of properties, just like you can with an object initializer. Still, the type checker will make sure you can only use properties from the Person
type.
💡 When creating an instance this way, we needed to add initializers to the properties. Since the properties may or may not be initialized in the constructor we have to make sure they won’t be
undefined
.
This utility type takes a type as argument T
and transforms all of its properties to required. In essence, it’s the opposite of Partial<T>
. Here is the implementation:
type Required<T> = { [P in keyof T]-?: T[P] }
You might be wondering about the strange-looking -?
syntax but it simply means that the ?
will be removed from the property definition if it exists, effectively making the property non-optional aka required.
interface Foo {
a?: number
b?: string
}
const foo: Foo = { a: 0 } // This is fine
type Bar = Required<Foo>
const bar: Bar = { a: 0 } // Error: property 'b' missing
One can imagine this being useful when a type is inferred from an object literal and some fields have been marked as nullable even though the intention is to have a type with all required properties.
There’s a readonly
keyword in TypeScript that allows you to mark a property on a type to only allow reading the value and not reassigning it.
type Foo = {
readonly bar: string
}
const baz: Foo = {
bar: 'qux',
}
baz.bar = 'quux' // Cannot assign to 'bar' because it is a read-only property.
The utility type Readonly<T>
takes a type as a parameter and creates a new type with all the properties marked as readonly
. Here’s the implementation:
type Readonly<T> = { readonly [P in keyof T]: T[P] }
As you can see this is another mapped type that adds a readonly
modifier to all of the properties of T
.
type ReadAndWriteFoo = {
bar1: string
bar2: string
}
type Foo = Readonly<ReadAndWriteFoo>
const baz: Foo = {
bar1: 'qux1',
bar2: 'qux2',
}
baz.bar1 = 'quux' // Cannot assign to 'bar1' because it is a read-only property.
The utility type Readonly<T>
is useful if you want to make all properties readonly. Preventing reassignment is useful in functional programming paradigms. For example, React uses Readonly<T>
in its type definitions to make props and state types readonly so that the type checker will throw an error if you try to assign a value to them.
import React from 'react'
interface Props {
foo: string
}
export class SomeComponent extends React.Component<Props> {
someMethod() {
this.props.foo = 'bar' // Cannot assign to 'foo' because it is a read-only property.
}
}
This utility type creates a new type with given properties K
all of the same type T
. Here is the implementation:
type Record<K extends keyof any, T> = { [P in K]: T }
One thing to note here is that K
is keyof any
which means that it can be given any property names instead of copying them from an existing type. In other words, while all the other mapped types so far have been homomorphic the Record<K, T>
is not.
Here is a simple example:
type K = 'foo' | 'bar'
type T = Record<K, boolean> // { foo: boolean, bar: boolean }
You can also, of course, use the keyof
operator to get the properties of a type and then create another type with Record<K,T>
having the same properties but with different types.
type PropDefinition = {
typeName: string
description: string
}
type Foo = {
bar: string
baz: number
}
type FooDefinition = Record<keyof Foo, PropDefinition>
const fooDef: FooDefinition = {
bar: { typeName: 'string', description: 'This is bar' },
baz: { typeName: 'number', description: 'This is baz' },
}
This utility type is useful when you want to create a new type that has a subset of another type’s properties. Here’s the implementation:
type Pick<T, K extends keyof T> = { [P in K]: T[P] }
Notice that K
is a subtype of keyof T
since it’s using the extends
keyword. This means that all properties must exist on type T
but all properties are not required in K
.
Often you need some properties from a type but not all of them, for example, from a view model or data transfer object. In those cases, the Pick<T, K>
utility type can be useful.
type Foo = {
bar: string
baz: number
qux: boolean
}
type NewFoo = Pick<Foo, 'bar' | 'baz'>
const newFoo: NewFoo = {
bar: 'bar',
baz: 'baz',
}
This utility type creates a new type without the given properties. It is the opposite of Pick<T, K>
. Here’s the implementation:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
Whoops, the implementation uses a utility type that we have not introduced yet. But don’t worry we will get to Exclude<T, U>
in the next part of this. At this point, it is enough to know that it removes the given K
properties from the properties of type T
and returns the remaining properties. After that Pick<T>
can be used to pick the remaining properties from type T
. Quite a clever way of combining the utility types to make new utility types.
So you can use Omit<T, K>
to create a new type that has a subset of type T
properties, by providing as argument K
the properties you don’t want in the new type. Sometimes it’s easier to define the properties to omit instead of all the properties you want to pick. Here’s an example:
type Foo = {
bar: string
baz: number
qux: boolean
}
type NewFoo = Omit<Foo, 'qux'>
const newFoo: NewFoo = {
bar: 'bar',
baz: 'baz',
}
If you’re not familiar with the advanced typing and utility types of TypeScript then I bet this was a lot to take in. That’s why were are going to leave the rest of the utility types for part 2 of this post.
Types in part 2 are going to be even more complex and some of them are a little less useful in common scenarios but knowing that you can do those things with TypeScript is mind-opening.
I hope you have enjoyed part 1 of Utility Types and are looking forward to the next part with more typing goodness.