Search code examples
typescripttsc

Defining constant/ro properties on objects with TypeScript


Using TypeScript, is there any way to enforce non-reassignment?

Say I have this:

fn.foo = 5;
fn.foo = 6;

is there a way to get a compile error on the second line? As soon as I assign the foo property, it should be ReadOnly I suppose.

My case foo could adhere to this interface:

export interface RO {
  [readonly key: string]: any
}

but I am not sure if that syntax works out.


Solution

  • If you use a class you can mark the field itself as readonly and you can only assign it in the constructor:

    class Test {
        readonly foo : string;
        constructor () {
            this.foo = "";
        }
    }
    
    let fn = new Test();
    fn.foo = "" // Error 
    

    If you are using object literals you can use an explicit type to not allow mutation of a field just it's initial assignment:

    let fn : {
        readonly foo: string
    } = {
        foo: "1"
    }
    

    You could also use Object.freeze which has runtime behavior as well:

    let fn  = Object.freeze({
        foo: "1"
    })
    fn.foo = "" // error
    

    In either case you can create a readonly field that is only assignable on object creation. There is no way to create a field that is only assignable once after initialization such as in your example.

    Edit

    Note that readonly is not very strong,it does nto cause type incompatibility between two types with the same fields:

    let fn  = Object.freeze({
        foo: "1"
    }) // foo is readonly 
    let nonReadonlyFn : { foo: string } =fn; // foo is not readonly but assignment is allowed
    nonReadonlyFn.foo = "" // not a compiler error.
    

    Update

    If you don't know the keys you can create a readonly index signature:

    let fn: { 
        readonly [n: string] : string
    } = { foo: "" };
    fn["foo"] ="" // error
    console.log(fn["foo"])
    

    A common way to create readonly objects without initializing them all at once is to have a mutable version, mutate it and when you are done assigning it to a readonly version:

    let fnNotReadOnly: { 
        [n: string] : string
    } ;
    fnNotReadOnly["foo"] = "";
    //When done mutating:
    let fn : { 
        readonly [n: string] : string
    } =  fnNotReadOnly;
    
    fn["foo"] = ""
    console.log(fn["foo"])