Appearance
readonly
TypeScript 类型系统允许你在一个接口里使用 readonly
来标记属性。它能让你以一种更安全的方式工作(不可预期的改变是很糟糕的):
ts
function foo(config: { readonly bar: number, readonly bas: number }) {
// ..
}
const config = { bar: 123, bas: 123 };
foo(config);
// 现在你能够确保 'config' 不能够被改变了
当然,你也可以在 interface
和 type
里使用 readonly
:
ts
type Foo = {
readonly bar: number;
readonly bas: number;
};
// 初始化
const foo: Foo = { bar: 123, bas: 456 };
// 不能被改变
foo.bar = 456; // Error: foo.bar 为仅读属性
你也能指定一个类的属性为只读,然后在声明时或者构造函数中初始化它们,如下所示:
ts
class Foo {
readonly bar = 1; // OK
readonly baz: string;
constructor() {
this.baz = 'hello'; // OK
}
}
Readonly
这有一个 Readonly
的映射类型,它接收一个泛型 T
,用来把它的所有属性标记为只读类型:
ts
type Foo = {
bar: number;
bas: number;
};
type FooReadonly = Readonly<Foo>;
const foo: Foo = { bar: 123, bas: 456 };
const fooReadonly: FooReadonly = { bar: 123, bas: 456 };
foo.bar = 456; // ok
fooReadonly.bar = 456; // Error: bar 属性只读
其他的使用用例
ReactJS
ReactJS
是一个喜欢用不变数据的库,你可以标记你的 Props
和 State
为不可变数据:
ts
interface Props {
readonly foo: number;
}
interface State {
readonly bar: number;
}
export class Something extends React.Component<Props, State> {
someMethod() {
// 你可以放心,没有人会像下面这么做
this.props.foo = 123; // Error: props 是不可变的
this.state.baz = 456; // Error: 你应该使用 this.setState()
}
}
然而,你并没有必要这么做,React
的声明文件已经标记这些为 readonly
(通过传入泛型参数至一个内部包装,来把每个属性标记为 readonly
,如上例子所示),
ts
export class Something extends React.Component<{ foo: number }, { baz: number }> {
someMethod() {
this.props.foo = 123; // Error: props 是不可变的
this.state.baz = 456; // Error: 你应该使用 this.setState()
}
}
绝对的不可变
你甚至可以把索引签名标记为只读:
ts
interface Foo {
readonly [x: number]: number;
}
// 使用
const foo: Foo = { 0: 123, 2: 345 };
console.log(foo[0]); // ok(读取)
foo[0] = 456; // Error: 属性只读
如果你想以不变的方式使用原生 JavaScript 数组,可以使用 TypeScript 提供的 ReadonlyArray<T>
接口:
ts
let foo: ReadonlyArray<number> = [1, 2, 3];
console.log(foo[0]); // ok
foo.push(4); // Error: ReadonlyArray 上不存在 `push`,因为他会改变数组
foo = foo.concat(4); // ok, 创建了一个复制
自动推断
在一些情况下,编译器能把一些特定的属性推断为 readonly
,例如在一个 class
中,如果你有一个只含有 getter
但是没有 setter
的属性,他能被推断为只读:
ts
class Person {
firstName: string = 'John';
lastName: string = 'Doe';
get fullName() {
return this.firstName + this.lastName;
}
}
const person = new Person();
console.log(person.fullName); // John Doe
person.fullName = 'Dear Reader'; // Error, fullName 只读
与 const
的不同
const
- 用于变量;
- 变量不能重新赋值给其他任何事物。
readonly
- 用于属性;
- 用于别名,可以修改属性;
简单的例子 1:
ts
const foo = 123; // 变量
let bar: {
readonly bar: number; // 属性
};
简单的例子 2:
ts
const foo: {
readonly bar: number;
} = {
bar: 123
};
function iMutateFoo(foo: { bar: number }) {
foo.bar = 456;
}
iMutateFoo(foo);
console.log(foo.bar); // 456
readonly
能确保“我”不能修改属性,但是当你把这个属性交给其他并没有这种保证的使用者(允许出于类型兼容性的原因),他们能改变它。当然,如果 iMutateFoo
明确的表示,他们的参数不可修改,那么编译器会发出错误警告:
ts
interface Foo {
readonly bar: number;
}
let foo: Foo = {
bar: 123
};
function iTakeFoo(foo: Foo) {
foo.bar = 456; // Error: bar 属性只读
}
iTakeFoo(foo);