readonly

TypeScript 类型系统允许你在一个接口里使用 readonly 来标记属性。它能让你以一种更安全的方式工作(不可预期的改变是很糟糕的):

  1. function foo(config: { readonly bar: number, readonly bas: number }) {
  2. // ..
  3. }
  4. const config = { bar: 123, bas: 123 };
  5. foo(config);
  6. // 现在你能够确保 'config' 不能够被改变了

当然,你也可以在 interfacetype 里使用 readonly

  1. type Foo = {
  2. readonly bar: number;
  3. readonly bas: number;
  4. };
  5. // 初始化
  6. const foo: Foo = { bar: 123, bas: 456 };
  7. // 不能被改变
  8. foo.bar = 456; // Error: foo.bar 为仅读属性

你也能指定一个类的属性为只读,然后在声明时或者构造函数中初始化它们,如下所示:

  1. class Foo {
  2. readonly bar = 1; // OK
  3. readonly baz: string;
  4. constructor() {
  5. this.baz = 'hello'; // OK
  6. }
  7. }

Readonly

这有一个 Readonly 的映射类型,它接收一个泛型 T,用来把它的所有属性标记为只读类型:

  1. type Foo = {
  2. bar: number;
  3. bas: number;
  4. };
  5. type FooReadonly = Readonly<Foo>;
  6. const foo: Foo = { bar: 123, bas: 456 };
  7. const fooReadonly: FooReadonly = { bar: 123, bas: 456 };
  8. foo.bar = 456; // ok
  9. fooReadonly.bar = 456; // Error: bar 属性只读

其他的使用用例

ReactJS

ReactJS 是一个喜欢用不变数据的库,你可以标记你的 PropsState 为不可变数据:

  1. interface Props {
  2. readonly foo: number;
  3. }
  4. interface State {
  5. readonly bar: number;
  6. }
  7. export class Something extends React.Component<Props, State> {
  8. someMethod() {
  9. // 你可以放心,没有人会像下面这么做
  10. this.props.foo = 123; // Error: props 是不可变的
  11. this.state.baz = 456; // Error: 你应该使用 this.setState()
  12. }
  13. }

然而,你并没有必要这么做,React 的声明文件已经标记这些为 readonly(通过传入泛型参数至一个内部包装,来把每个属性标记为 readonly,如上例子所示),

  1. export class Something extends React.Component<{ foo: number }, { baz: number }> {
  2. someMethod() {
  3. this.props.foo = 123; // Error: props 是不可变的
  4. this.state.baz = 456; // Error: 你应该使用 this.setState()
  5. }
  6. }

绝对的不可变

你甚至可以把索引签名标记为只读:

  1. interface Foo {
  2. readonly [x: number]: number;
  3. }
  4. // 使用
  5. const foo: Foo = { 0: 123, 2: 345 };
  6. console.log(foo[0]); // ok(读取)
  7. foo[0] = 456; // Error: 属性只读

如果你想以不变的方式使用原生 JavaScript 数组,可以使用 TypeScript 提供的 ReadonlyArray<T> 接口:

  1. let foo: ReadonlyArray<number> = [1, 2, 3];
  2. console.log(foo[0]); // ok
  3. foo.push(4); // Error: ReadonlyArray 上不存在 `push`,因为他会改变数组
  4. foo = foo.concat(4); // ok, 创建了一个复制

自动推断

在一些情况下,编译器能把一些特定的属性推断为 readonly,例如在一个 class 中,如果你有一个只含有 getter 但是没有 setter 的属性,他能被推断为只读:

  1. class Person {
  2. firstName: string = 'John';
  3. lastName: string = 'Doe';
  4. get fullName() {
  5. return this.firstName + this.lastName;
  6. }
  7. }
  8. const person = new Person();
  9. console.log(person.fullName); // John Doe
  10. person.fullName = 'Dear Reader'; // Error, fullName 只读

与 const 的不同

const

  • 用于变量;
  • 变量不能重新赋值给其他任何事物。
    readonly

  • 用于属性;

  • 用于别名,可以修改属性;
    简单的例子 1:
  1. const foo = 123; // 变量
  2. let bar: {
  3. readonly bar: number; // 属性
  4. };

简单的例子 2:

  1. const foo: {
  2. readonly bar: number;
  3. } = {
  4. bar: 123
  5. };
  6. function iMutateFoo(foo: { bar: number }) {
  7. foo.bar = 456;
  8. }
  9. iMutateFoo(foo);
  10. console.log(foo.bar); // 456

readonly 能确保“我”不能修改属性,但是当你把这个属性交给其他并没有这种保证的使用者(允许出于类型兼容性的原因),他们能改变它。当然,如果 iMutateFoo 明确的表示,他们的参数不可修改,那么编译器会发出错误警告:

  1. interface Foo {
  2. readonly bar: number;
  3. }
  4. let foo: Foo = {
  5. bar: 123
  6. };
  7. function iTakeFoo(foo: Foo) {
  8. foo.bar = 456; // Error: bar 属性只读
  9. }
  10. iTakeFoo(foo);

原文: https://jkchao.github.io/typescript-book-chinese/typings/readonly.html