Everyday TypeScript: Readonly Properties vs. Values
Welcome to the Readonly Properties vs. Values lesson!
This lesson is shown as static text below. However, it's designed to be used interactively. Click the button below to start!
In an earlier lesson, we saw
readonlyproperties.>
type User = {name: stringreadonly catNames: string[]};const amir: User = {name: 'Amir', catNames: ['Ms. Fluff']};amir.catNames = ['Keanu'];amir;Result:
type error: Cannot assign to 'catNames' because it is a read-only property.
We declared
catNamesasreadonly catNames: string[]. That syntax may seem strange: why doesreadonlycome before the property name? Shouldn'treadonlybe part of the type, on the right side of the colon likename: readonly string?There's subtle but important reason for this: the
readonlyis part of theUserobject's type, not part of thecatNamesarray's type. The array is just a regular array! To understand how this works, let's look at three examples where a property is "read-only", but in a different way each time.First, we can make the object property read-only with the
readonly catNames: string[]syntax. That stops us from replacing thecatNamesarray with a different array. But the array itself still has the typestring[], so it's still mutable! We can't replace the array, but we can callpushon it.>
type User = {name: stringreadonly catNames: string[]};const amir: User = {name: 'Amir', catNames: ['Ms. Fluff']};- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amir.catNames = ['Keanu'];amir.catNames;Result:
type error: Cannot assign to 'catNames' because it is a read-only property.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amir.catNames.push('Keanu');amir.catNames;Result:
['Ms. Fluff', 'Keanu']
Second, we can make the array itself read-only by declaring it as a
ReadonlyArray. That stops us from calling methods that would mutate the array, likepush. However, the property no longer gets areadonlymodifier, so we can replace the array with a different array.>
type User = {name: stringcatNames: ReadonlyArray<string>};const amir: User = {name: 'Amir', catNames: ['Ms. Fluff']};- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amir.catNames = ['Keanu'];amir.catNames;Result:
['Keanu']
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amir.catNames.push('Keanu');amir.catNames;Result:
type error: Property 'push' does not exist on type 'readonly string[]'.
Third, we can do both of these at once: we can make the property read-only, and also make the array inside it read-only. We can't call
pushor other mutating methods, and we can't replace the array with a different array. This is the strongest sense of "read-only".>
type User = {name: stringreadonly catNames: ReadonlyArray<string>};const amir: User = {name: 'Amir', catNames: ['Ms. Fluff']};- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amir.catNames = ['Keanu'];amir.catNames;Result:
type error: Cannot assign to 'catNames' because it is a read-only property.
- Note: this code example reuses elements (variables, etc.) defined in earlier examples.
>
amir.catNames.push('Keanu');amir.catNames;Result:
type error: Property 'push' does not exist on type 'readonly string[]'.
This distinction between read-only properties and read-only values may seem pedantic at first. However, understanding it is important in order to build a sensible model of read-only data.
Our
Usertype is a distinct object with its own rules about whether it can be mutated. Each of its properties can either bereadonlyor not. The array is also a distinct object that also has its own rules about whether it can be mutated. It can either be anArrayor aReadonlyArray.