Everyday TypeScript: Partial in Practice
Welcome to the Partial in Practice lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
We saw JavaScript's spread syntax in an earlier lesson:
{...obj1, ...obj2}lets us build new objects out of multiple old objects. But what if the types areUserandPartial<User>, like in{...user, ...partialUser}?In that case, we're building an object with all of
user's properties, butpartialUsermay override some of them. Here are some concrete examples of that. In each example, we combine two objects.In the first example, we have a "template", like a user object with all of its properties, and we have some "overrides", like a partial user object. Here, the "template" is created as its own variable that lives outside of the function.
>
type User = {name: stringcountry: string};const template: User = {name: 'Amir',country: 'France',};/* This function combines the template object and the overrides to* produce a new object. */function override(overrides: Partial<User>): User {return {...template, ...overrides};}If we provide no overrides, we get the original object back.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
override({});Result:
{name: 'Amir', country: 'France'} We can override individual properties as long as the types match.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
override({name: 'Betty'});Result:
{name: 'Betty', country: 'France'} - Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
override({country: 'unknown'});Result:
{name: 'Amir', country: 'unknown'} If we provide the wrong type for a property, that's a type error.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
override({country: 75010});Result:
type error: Type 'number' is not assignable to type 'string'.
If we pass unexpected properties in an object literal, that's also a type error.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
override({age: 36});Result:
type error: Object literal may only specify known properties, and 'age' does not exist in type 'Partial<User>'.
That's a lot of flexibility in one small function!
Functions like
overrideare useful in many situations, most notably in unit tests. When we're testing our user module, we could create a new user object from scratch in every test. But user objects often have dozens of properties, and most tests only care about one or at most a few of them.Our
overridefunction solves that problem perfectly. We define one template user object, then useoverrideto tweak only the properties that are relevant to each test. In a test of email validation we might calloverride({email: 'invalid!@#$%^&*email@example.com'}), and in another test we might calloverride({admin: true}). That's much nicer than specifying two dozen properties every time.One final note about using
Partialto override properties. So far, ouroverridefunction has known about theUsertype. But it doesn't have to! We can make it generic, taking both the template object and the overrides as function parameters.>
/* This override function is generic and works with any object type! */function override<T>(template: T, overrides: Partial<T>): T {return {...template, ...overrides};}- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
type User = {name: stringcountry: string};const template: User = {name: 'Amir',country: 'France',};const user = override(template, {name: 'Betty'});user;Result:
{name: 'Betty', country: 'France'} - Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
type Album = {name: stringlengthMinutes: number};const template: Album = {name: 'Kind of Blue',lengthMinutes: 45,};const album = override(template, {lengthMinutes: 46});album;Result:
{name: 'Kind of Blue', lengthMinutes: 46} That little three-line
overridefunction does a lot of work:- It overrides properties at runtime.
- It works for any object type.
- It ensures that we only override properties that exist on the type.