Original Source: https://github.com/kolodny/ts-cookbook/tree/master
Table of Contents
- How do I...
- Make all properties on an interface optional
- Make all properties on an interface required
- Make all properties on an interface nullable
- Make all properties on an interface readonly
- Create a tuple from a known set of elements
- Create a new type with some keys of another type
- Remove properties from an interface
- Get the Array type, Promise type, Observable type, ...etc from something
- Make a readonly object mutable
- Make some readonly keys on an object be mutable
- Create a sub interface explicitly using only some keys of the interface:
- Create a sub interface infering which keys of the interface to use:
- Create a deep readonly object
- Remove some types from an interface
- Create a new interface using some types of another interface
- Require one and only one property of an object exist
- Advanced Mapped Types Crash Course
Use the Partial
type.
interface Person {
name: string;
age: number;
}
type PersonWithAllPropertiesOptional = Partial<Person>;
const person1: PersonWithAllPropertiesOptional = {}; // OK
// OK
const person2: PersonWithAllPropertiesOptional = {
name: 'Me',
};
// OK
const person3: PersonWithAllPropertiesOptional = {
name: 'Me',
age: 123,
};
const person4: PersonWithAllPropertiesOptional = {
name: 'Me',
age: 123,
// Error: Object literal may only specify known properties, and 'foo' does not exist in type 'Partial<Person>'.
foo: 'bar',
};
Use the Required
type.
interface Person {
name: string;
age?: number;
}
const person: Person = { name: 'Alex' };
// Error: Property 'age' is optional in type 'Person' but required in type 'Required<Person>'.
const requiredPerson: Required<Person> = person;
Use the Nullable
type.
type Nullable<T> = { [P in keyof T]: T[P] | null };
interface Person {
name: string;
age: number;
}
// OK
const person: Nullable<Person> = {
name: null,
age: null,
};
// OK
person.name = 'Adam';
Use the Readonly
type.
interface Person {
name: string;
age?: number;
}
const person: Readonly<Person> = { name: 'Alex' };
// Error: Cannot assign to 'name' because it is a constant or a read-only property.
person.name = 'Bob';
Use the tuple
function of toopl
.
import { tuple } from 'toopl';
// Type: [number, string, boolean]
const myTuple = tuple(1, '2', true);
Use the Pick
type.
interface Person {
name: string;
age: number;
id: number;
}
const personForTest: Pick<Person, 'name'|'age'> = {
name: 'Charlie',
age: 123,
};
Use the OmitStrict
type from type-zoo
.
import { OmitStrict } from 'type-zoo';
interface Person {
name: string;
age: number;
id: number;
}
const person: OmitStrict<Person, 'age'|'id'> = { name: 'Danny' };
Note: Omit
from type-zoo
would also work but wouldn't check if the property actually exists on the interface.
Use the infer
keyword.
type ArrayUnpacker<T> = T extends Array<infer U> ? U : never;
const stringArray = ['this', 'is', 'cool'];
// Type: string
let unpackedStringArray: ArrayUnpacker<typeof stringArray>;
type PromiseUnpacker<T> = T extends Promise<infer U> ? U : never;
const stringPromise = Promise.resolve('test');
// Type: string
let unpackedStringPromise: PromiseUnpacker<typeof stringPromise>;
class Box<T> {
constructor(private readonly value: T) {}
}
type BoxUnpacker<T> = T extends Box<infer U> ? U : never;
const myBox = new Box('a string box!');
// Type: string
let myUnpackedBox: BoxUnpacker<typeof myBox>;
Use the Mutable
type from [ts-cookbook
].
import { Mutable } from 'ts-cookbook';
interface ImmutablePerson {
readonly name: string;
readonly age: number;
}
const immutablePerson: ImmutablePerson = {
name: 'Danny',
age: 50,
};
// Error: Cannot assign to 'age' because it is a read-only property.
immutablePerson.age = 51;
const person: Mutable<ImmutablePerson> = {
name: 'Eric',
age: 34,
};
// OK
person.age = 35;
Use the MutableKeys
type from ts-cookbook
.
import { MutableKeys } from 'ts-cookbook';
interface ImmutablePerson {
readonly name: string;
readonly age: number;
readonly isPremium: boolean;
}
const immutablePerson: ImmutablePerson = {
name: 'Danny',
age: 50,
isPremium: false,
};
// Error: Cannot assign to 'age' because it is a read-only property.
immutablePerson.age = 51;
const person: MutableKeys<ImmutablePerson, 'age'|'isPremium'> = {
name: 'Eric',
age: 34,
isPremium: false,
};
// OK
person.age = 35;
person.isPremium = true;
// Error: Cannot assign to 'name' because it is a read-only property.
immutablePerson.name = 'Erik';
Use the Use the Pick
type.
interface Person {
name: string;
age: number;
id: string;
}
type PersonWithNameAndAge = Pick<Person, 'name'|'age'>;
const person: PersonWithNameAndAge = { name: 'Greg', age: 23 };
Use the inferPick
function of ts-cookbook
.
import { inferPick } from 'ts-cookbook';
interface Person {
name: string;
age: number;
id: string;
}
const person = inferPick<Person>()({ name: 'Greg', age: 23 });
Use the Readonly
type or readonly
function from ts-cookbook
. Note: for shallow objects you can use the built in Typescript Readonly
type. If you want to ensure that it will work if the object is a Map
, Set
, or Array
, use the ShallowReadonly
type (or shallowReadonly
function) from ts-cookbook
.
import { Readonly, readonly, shallowReadonly } from 'ts-cookbook';
const array = readonly([1, 2, 3]);
// Error: Property 'push' does not exist on type 'ReadonlyArray<number>'.
array.push(4);
class Person {
constructor(public name: string, public age: number) {}
}
// `person` is Readonly<Person>
const person = readonly(new Person('Harry', 42));
// Error: Cannot assign to 'name' because it is a read-only property
person.name = 'Harr';
const person2: Readonly<Person> = new Person('Kevin', 43);
// Error: Cannot assign to 'name' because it is a read-only property
person.name += '!';
// `map` is a ReadonlyMap<string, string>
const map = readonly(new Map([['foo', 'bar']]));
// Error: Property 'set' does not exist on type 'ReadonlyMap<string, string>'.
map.set('baz', 'bork');
// `myObj` is Readonly<{cool: string}>
const myObj = readonly({ cool: 'thing' });
// Note: `readonly` creates a deep readonly object, as opposed to the native
// Typescript `Readonly` type which only creates a shallow readonly
// object. You can still get the inferred readonly behavior in a shallow
// fashion by using the `shallowReadonly` function from `ts-cookbook`.
const myObj2 = readonly({
o: {
prop: 1,
},
map: new Map([['foo', 'bar']]),
a: [1, 2, 3],
});
// Error: Cannot assign to 'prop' because it is a read-only property.
myObj2.o.prop = 2;
// Error: Property 'set' does not exist on type 'DeepReadonlyMap<string, string>'.
myObj2.map.set('boo', 'zaf');
// Error: Property 'push' does not exist on type 'DeepReadonlyArray<number>'.
myObj2.a.push(4);
Use the RemoveType
function from ts-cookbook
.
import { RemoveType } from 'ts-cookbook';
interface Person {
name: string;
age: number;
isSaved: boolean;
save: () => void;
}
const personForTest: RemoveType<Person, boolean|Function> = {
name: 'Joe',
age: 44,
};
Use the KeepType
function from ts-cookbook
.
import { KeepType } from 'ts-cookbook';
interface Person {
name: string;
age: number;
isSaved: boolean;
save: () => void;
}
const personForTest: KeepType<Person, string|number> = {
name: 'Joe',
age: 44,
};
Use the OneOf
function from ts-cookbook
.
import { OneOf } from 'ts-cookbook';
interface UnsavedRecord {
name: string;
age: number;
}
type DbRecord = UnsavedRecord &
OneOf<{
draftId: string;
dbId: string;
}>;
const record: DbRecord = {} as any;
if (record.dbId) {
record.draftId; // draftId is typed as `draftId?: undefined`.
}
The mapped syntax type is somewhat cryptic, here's the general idea of how it breaks down.
First we have a simple key type:
// This is a simple object type
type MyObject = { ['myCoolKey']: string };
let obj: MyObject = { myCoolKey: 'test' };
obj.myCoolKey; // OK
Typescript allows use of a union type (eg 'foo'|'bar'
) with the in
keyword:
type MyObject = { [K in ('foo'|'bar')]: string };
let obj: MyObject = { foo: 'foo', bar: 'BAR' };
obj.foo; // OK
obj.bar; // OK
Another way of getting a union type is by using the keyof
keyword:
interface Point { x: number; y: number; }
type PointKeys = keyof Point; // same as `type PointKeys = 'x'|'y'`
Using that knowledge, we can create a mapped type as follows:
interface Person {
name: string;
age: number;
}
// Create a readonly person by adding the readonly modifier to the key.
type ReadonlyPerson = { readonly [K in keyof Person]: Person[K] };
The type doesn't need to be tied to an existing type (like Person
in the example above) but can be generic as well:
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
See the official Typescript handbook for more details.