miso_soup3 Blog

主に ASP.NET 関連について書いています。

TypeScript の readonly な型と仕様

TypeScript で、readonly な仕組みとしてどのような型や仕様があるのか気になり調べました。見つけた範囲で書きます。

試したコードは gist にあります: TypeScript readonly

目次:

※以降のコードには、readonly なのでこのコードは書けない、ということを示すため、コンパイルエラーとなる記述が含まれます。同じ行のコメントに「// Error:」とかいてあるものがコンパイルエラーとなる行です。

Read-only properties

readonly 修飾子は TypeScript 2.0 でリリースされたようです(参照

interface Foo {
    readonly bar: number;
    readonly bas: number;
}

const foo: Foo = { bar: 1, bas: 2 };
foo.bar = 11; // Error: Cannot assign to 'bar' because it is a read-only property
class Person {
    constructor(readonly name: string) {
    }

    write() {
        this.name = 'new name'; // Error: [ts] Cannot assign to 'name' because it is a read-only property. [2540]
    }
}
const value: { readonly name: string } = { name: 'name' };
value.name = 'new name'; // Error: [ts] Cannot assign to 'name' because it is a read-only property. [2540]

readonly に限った話ではないですが、型を崩せば値を変更できてしまいます。

(foo as { bar: number }).bar = 999;
(foo as any).bar = 999;
Read-only index signatures
const dic: { readonly [key: number]: number } = {
    0: 100,
    1: 101,
    2: 102,
};
dic[103] = 103;// Error Index signature in type '{ readonly [key: number]: number; }' only permits reading.

Readonly<T>

Readonly<T> は、2.1 から使えます。参考: TypeScript 2.1 · TypeScript

interface Category {
    name: string;
    [key: number]: number;
}

let category1: Readonly<Category> = { name: 'category1' };
category1 = { name: 'new cateogry name' }; // ok
category1.name = 'new name'; // Error: [ts] Cannot assign to 'name' because it is a read-only property. [2540]
category1[100] = 100; // Error: [ts] Index signature in type 'Readonly<Category>' only permits reading. [2542]
const state1: Readonly<{ name: string }> = { name: 'name' };
state1.name = 'new name';// Error: Cannot assign to 'name' because it is a read-only property.

sample:

function read<T extends Category>(value: Readonly<T>) {
    value.name = 'new name';// Error: Cannot assign to 'name' because it is a read-only property.
}

以下の記述は 2.8 からです。参考: Improved control over mapped type modifiers

type ReadonlyCategory = { +readonly [P in keyof Category]: Category[P] };
const category2: ReadonlyCategory = category1;
category2.name = 'new name'; // Error: [ts] Cannot assign to 'name' because it is a read-only property. [2540]
category2[100] = 100; // Error: [ts] Index signature in type 'Readonly<Category>' only permits reading. [2542]

type MutableCategory = { -readonly [P in keyof ReadonlyCategory]: ReadonlyCategory[P] };
const category3: MutableCategory = category2;
category3.name = 'new name';// ok
category3[100] = 100;// ok

ReadonlyArray<T>

ReadonlyArray<T>、ReadonlyMap<T>、ReadonlySet<T> は、readonly 修飾子と同じタイミングの 2.0 から使えます。

const numbers = [1, 2, 3];
numbers.push(4);

const numbers2: ReadonlyArray<number> = numbers;
numbers2.push(5);// Error: [ts] Property 'push' does not exist on type 'ReadonlyArray<number>'. [2339]
numbers2[0] = 4;// Error: [ts] Index signature in type 'ReadonlyArray<number>' only permits reading. [2542]

ReadonlyMap<T>

const map = new Map<string, string>();
map.set("key1", "value1");
const readonlyMap: ReadonlyMap<string, string> = map;
readonlyMap.set("key1", "value1");// Error: Property 'set' does not exist on type 'ReadonlyMap<string, string>'.

ReadonlySet<T>

const set = new Set<string>();
set.add('value');
const readonlySet: ReadonlySet<string> = set;
readonlySet.add('value;');// Error: [ts] Property 'add' does not exist on type 'ReadonlySet<string>'.

追記 2019/3/18