ReadonlyArray与readonly arr: T[]的区别及三类接口差异解析
先看你提供的代码示例:
interface Feature { readonly name: string; } // 规则判定OK interface Foo1 { readonly arr: ReadonlyArray<Feature>; } function save1(foo: Foo1) { console.log(foo); } // 规则判定bad interface Foo2 { arr: ReadonlyArray<Feature>; } function save2(foo: Foo2) { console.log(foo); } // 规则判定bad interface Foo3 { readonly arr: Feature[]; } function save3(foo: Foo3) { console.log(foo); }
你原本以为这三个接口等价,但@typescript-eslint/prefer-readonly-parameter-types规则给出了不同判定,下面拆解它们的差异,以及ReadonlyArray<T>和readonly arr: T[]的核心区别。
三个接口的差异
Foo1:双重只读保障
readonly arr: ReadonlyArray<Feature> 做了两层只读限制:
- 第一层:属性
arr本身是只读的——你不能在函数内部给foo.arr重新赋值(比如foo.arr = []会报错)。 - 第二层:
arr指向的数组内容是只读的——因为ReadonlyArray类型禁用了所有修改数组的方法(push/pop/splice等),也不能通过索引修改元素(比如foo.arr[0] = {name: "new"}会报错)。
完全符合eslint规则要求的“参数不可被修改”,所以判定为OK。
Foo2:数组内容只读,但属性可写
arr: ReadonlyArray<Feature> 只限制了数组内容不可变,但属性arr本身是可写的——你可以在函数内部给foo.arr重新赋值一个新的数组(比如foo.arr = [{name: "new"}]是合法的)。
eslint规则希望参数的所有属性都保持只读,避免函数内部意外修改参数结构,所以判定为bad。
Foo3:属性只读,但数组内容可写
readonly arr: Feature[] 只限制了属性arr不能被重新赋值,但arr指向的是普通Feature[]数组——你可以在函数内部修改数组内容,比如foo.arr.push({name: "new"})(不过Feature的name是只读,无法修改元素的name,但数组的增删操作是允许的)。
因为数组内容仍可被修改,违反了规则“参数应只读”的核心要求,所以判定为bad。
ReadonlyArray与readonly arr: T[]的核心区别
要分两种场景来看:
作为独立类型标注时:
readonly T[]是ReadonlyArray<T>的语法糖,两者是等价的类型,都表示内容不可变的数组——不能修改元素、不能调用修改类方法。作为接口属性的一部分时:
arr: ReadonlyArray<T>:重点在数组内容只读,但属性arr本身可以被重新赋值。readonly arr: T[]:重点在属性arr不能被重新赋值,但数组内容是可变的(因为是普通T[]类型)。
简单总结:前者管数组内容不能改,属性能改;后者管属性不能改,数组内容能改。如果要同时锁死两者,就用readonly arr: ReadonlyArray<T>(也就是Foo1的写法)。
内容的提问来源于stack exchange,提问作者azizj

