TypeScript中的深度不可变

79 阅读2分钟

const

  • 当用const定义原始数据类型时,修改变量会产生报错
  • 当const定义对象类型时,const只是定义该对象指向的引用地址不可变,但是对于对象属性的修改,并没有限制。

In JavaScript, mutability is the default, although it allows variable declarations with const to declare that the reference is immutable. The referent is still mutable: (在js里,可变是默认的,const只是声明引用是不可变,但是被引用对象是可变的)

const msg = 'hello'
const man = {
    age: 2
}

// 报错:Cannot assign to 'msg' because it is a constant.
msg = 'world'

// 这里没有报错
man.age = 1

// 报错:Cannot assign to 'man' because it is a constant.
man = {}

readonly 修饰符

  • readonly可以用来修饰类或者interface的属性,描述该属性是不可变的
class Cat {
    readonly name: string
    readonly address: {
        code: string
    }
}

interface Dog {
    readonly name: string
    readonly address: {
        code: string
    }
}

const dog1: Dog = {
    name: 'd1',
    address: {
        code: '1'
    }
}

// 这里没有报错
dog1.address.code = '2'

// 报错:Cannot assign to 'name' because it is a read-only property.
dog1.name = 'd2'

Readonly 工具类型

Readonly是个范型工具类型,用来描述浅层属性的不可变。 使一个本来默认可变的类型变为浅层不可变

/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};
interface Dog {
    name: string
    address: {
        code: string
    }
}

const dog1: Readonly<Dog> = {
    name: 'd1',
    address: {
        code: '1'
    }
}

// 这里依然不会报错
dog1.address.code = '2'
// 报错: Cannot assign to 'name' because it is a read-only property.
dog1.name = 'd2'

字面量常量断言

在typescript 3.4 中引入了常量断言, 常量断言会使对象深度不可变。常量断言的适用范围是字面量值

const dog1 = {
    name: 'd1',
    address: {
        code: '1',
    },
} as const

// Cannot assign to 'code' because it is a read-only property.
dog1.address.code = '2'
// Cannot assign to 'name' because it is a read-only property.
dog1.name = 'd2'

但是常量断言也有限制,就是使用了常量断言,就不能使用类型标注,否者常量断言不生效。

interface Dog {
    name: string
    address: {
        code: string
    }
}

// 这里同时使用了类型标注
const dog1: Dog = {
    name: 'd1',
    address: {
        code: '1',
    },
} as const

// 这里不会报错
dog1.address.code = '2'
// 这里不会报错
dog1.name = 'd2'

深层不可变

无论是const、readonly、还是Readonly都只能描述对象的浅层不可变。

那么有没有办法能够描述变量的深层不可变呢?

本身ts里是没有深层不可变这个范型的,但是我们可以参考Readonly范型,实现一个Immutable类型。

type Immutable<T> = {
  readonly [K in keyof T]: Immutable<T[K]>;
};

interface Dog {
    name: string
    address: {
        code: string
    }
}

const dog1: Immutable<Dog> = {
    name: 'd1',
    address: {
        code: '1',
    },
}

// Cannot assign to 'code' because it is a read-only property.
dog1.address.code = '2'
// Cannot assign to 'name' because it is a read-only property.
dog1.name = 'd2'

也可以使用NPM上的ts-essentials包来实现