TypeScript's type system allows you to mark individual properties on an interface as readonly. This allows you to work in a functional way (unexpected mutation is bad):
functionfoo(config: {readonly bar:number,readonly bas:number}) {// ..}let config = { bar:123, bas:123 };foo(config);// You can be sure that `config` isn't changed 🌹
Of course you can use readonly in interface and type definitions as well e.g.:
typeFoo= {readonly bar:number;readonly bas:number;}// Initialization is okaylet foo:Foo= { bar:123, bas:456 };// Mutation is notfoo.bar =456; // Error: Left-hand side of assignment expression cannot be a constant or a read-only property
You can even declare a class property as readonly. You can initialize them at the point of declaration or in the constructor as shown below:
classFoo {readonly bar =1; // OKreadonly baz:string;constructor() {this.baz ="hello"; // OK }}
Readonly
There is a type Readonly that takes a type T and marks all of its properties as readonly using mapped types. Here is a demo that uses it in practice:
typeFoo= { bar:number; bas:number;}typeFooReadonly=Readonly<Foo>; let foo:Foo= {bar:123, bas:456};let fooReadonly:FooReadonly= {bar:123, bas:456};foo.bar =456; // OkayfooReadonly.bar =456; // ERROR: bar is readonly
Various Use Cases
ReactJS
One library that loves immutability is ReactJS, you could mark your Props and State to be immutable e.g.:
interfaceProps {readonly foo:number;}interfaceState {readonly bar:number;}exportclassSomethingextendsReact.Component<Props,State> {someMethod() {// You can rest assured no one is going to dothis.props.foo =123; // ERROR: (props are immutable)this.state.baz =456; // ERROR: (one should use this.setState) }}
You do not need to, however, as the type definitions for React mark these as readonly already (by internally wrapping the passed in generic types with the Readonly type mentioned above).
exportclassSomethingextendsReact.Component<{ foo:number }, { baz:number }> {// You can rest assured no one is going to dosomeMethod() {this.props.foo =123; // ERROR: (props are immutable)this.state.baz =456; // ERROR: (one should use this.setState) }}
This is great if you want to use native JavaScript arrays in an immutable fashion. In fact TypeScript ships with a ReadonlyArray<T> interface to allow you to do just that:
let foo:ReadonlyArray<number> = [1,2,3];console.log(foo[0]); // Okayfoo.push(4); // Error: `push` does not exist on ReadonlyArray as it mutates the arrayfoo =foo.concat([4]); // Okay: create a copy
Automatic Inference
In some cases the compiler can automatically infer a particular item to be readonly e.g. within a class if you have a property that only has a getter but no setter, it is assumed readonly e.g.:
classPerson { firstName:string="John"; lastName:string="Doe";getfullName() {returnthis.firstName +this.lastName; }}constperson=newPerson();console.log(person.fullName); // John Doeperson.fullName ="Dear Reader"; // Error! fullName is readonly
Difference from const
const 1. is for a variable reference 1. the variable cannot be reassigned to anything else.
readonly is 1. for a property 1. the property can be modified because of aliasing
Sample explaining 1:
constfoo=123; // variable referencevar bar: {readonly bar:number; // for property}
Sample explaining 2:
let foo: {readonly bar:number;} = { bar:123 };functioniMutateFoo(foo: { bar:number }) {foo.bar =456;}iMutateFoo(foo); // The foo argument is aliased by the foo parameterconsole.log(foo.bar); // 456!
Basically readonly ensures that a property cannot be modified by me, but if you give it to someone that doesn't have that guarantee (allowed for type compatibility reasons) they can modify it. Of course if iMutateFoo said that they do not mutate foo.bar the compiler would correctly flag it as an error as shown:
interfaceFoo {readonly bar:number;}let foo:Foo= { bar:123};functioniTakeFoo(foo:Foo) {foo.bar =456; // Error! bar is readonly}iTakeFoo(foo); // The foo argument is aliased by the foo parameter