Effective TypeScript Item 17

TypeScript

发布于 2021

什么时候使用 readonly

如果你想打印一个数列 (1, 1+2, 1+2+3, etc.),你可能会这么写代码:

function printTriangles(n: number) {
  const nums = [];
  for (let i = 0; i < n; i++) {
    nums.push(i);
    console.log(arraySum(nums));
  }
}

结果打印出了:

0
1
2
3
4

问题出在 arraySum 函数里: (谁会写这种必然出 bug 的代码啊?😂 )

function arraySum(arr: number[]) {
  let sum = 0,
    num;
  // 这里修改了 arr 的原始值。
  while ((num = arr.pop()) !== undefined) {
    sum += num;
  }
  return sum;
}

如果想保证入参的值不会被修改,可以给参数加上 readonly 声明:

function arraySum(arr: readonly number[]) {
  let sum = 0,
    num;
  while ((num = arr.pop()) !== undefined) {
    // ~~~ 'pop' does not exist on type 'readonly number[]'
    sum += num;
  }
  return sum;
}

readonly 是为了保证某个变量不会被意外的修改。嗯···纯函数里的使用,在业务中很难不修改某个 VO 的值。

使用 readonly 后的影响

不带 readonly 声明的类型是带 readonly 声明的同种类型的子类型:

const a: number[] = [1, 2, 3];
const b: readonly number[] = a;
const c: number[] = b;
// ~ Type 'readonly number[]' is 'readonly' and cannot be
//   assigned to the mutable type 'number[]'

如果你的函数能保证不修改入参的值,可以使用 readonly,函数的调用者可以放心的使用你的函数,而不用担心入参会被修改。

如果函数入参中使用了 readonly,再将入参传入其他函数的时候,其他函数的参数声明也必须是 readonly,需要注意以下情况:

function foo(a: readonly number[]) {
  bar(a);
  // Argument of type 'readonly number[]' is not assignable to parameter of type 'number'.
}

function bar(b: number[]) {}

foo([1, 2, 3]);
function foo(a: readonly number[]) {
  return a;
}

let b: number[];
b = foo([1, 2, 3]);
// The type 'readonly number[]' is 'readonly' and cannot be assigned to the mutable type 'number[]'.

readonly is shallow

const dates: readonly Date[] = [new Date()];
dates.push(new Date());
// ~~~~ Property 'push' does not exist on type 'readonly Date[]'
dates[0].setFullYear(2037); // OK
interface Outer {
  inner: {
    x: number;
  };
}
const o: Readonly<Outer> = { inner: { x: 0 } };
o.inner = { x: 1 };
// ~~~~ Cannot assign to 'inner' because it is a read-only property
o.inner.x = 1; // OK

type T = Readonly<Outer>;
// Type T = {
//   readonly inner: {
//     x: number;
//   };
// }

readonly VS const

const Arr = [1, 2, 3];
Arr[0] = 10; //OK
Arr.push(12); // OK
Arr.pop(); //Ok
//But
Arr = [4, 5, 6]; // ERROR

let arr1: Readonly<number> = [10, 11, 12];
arr1.pop(); //ERROR
arr1.push(15); //ERROR
arr1[0] = 1; //ERROR
// But
arr1 = [1, 2, 3];
let obj1: { readonly [k: string]: number } = {};
obj1.hi = 45; // ndex signature in type '{ readonly [k: string]: number; }' only permits reading.
obj1 = { ...obj1, hi: 12 }; // OK

const obj2: { [k: string]: number } = {};
obj2.hi = 45; // OK
obj2 = { ...obj2, hi: 12 }; // Cannot assign to 'obj2' because it is a constant.