偏好唯讀參數類型
需要函式參數指定為
readonly
類型,以防止輸入的意外變動。
此規則需要 類型資訊 才能執行。
變動函式參數可能會導致令人困惑的難以除錯的行為。雖然很容易隱含地記住不要變更函式參數,但明確命名參數為唯讀會為使用者提供明確的合約。此合約讓使用者更容易推論函式是否有副作用。
此規則允許你強制函式參數解析為唯讀類型。唯讀類型
- 是指
- 基元類型 (
字串
、數字
、布林
、符號
或列舉)、 - 函式簽章類型、
- 其元素類型視為唯讀的唯讀陣列類型。
- 其元素都被視為唯讀的唯讀組類型。
module.exports = {
"rules": {
"@typescript-eslint/prefer-readonly-parameter-types": "error"
}
};
在試驗場試用此規則 ↗
範例
- ❌ 錯誤
- ✅ 正確
function array1(arg: string[]) {} // array is not readonly
function array2(arg: readonly string[][]) {} // array element is not readonly
function array3(arg: [string, number]) {} // tuple is not readonly
function array4(arg: readonly [string[], number]) {} // tuple element is not readonly
// the above examples work the same if you use ReadonlyArray<T> instead
function object1(arg: { prop: string }) {} // property is not readonly
function object2(arg: { readonly prop: string; prop2: string }) {} // not all properties are readonly
function object3(arg: { readonly prop: { prop2: string } }) {} // nested property is not readonly
// the above examples work the same if you use Readonly<T> instead
interface CustomArrayType extends ReadonlyArray<string> {
prop: string; // note: this property is mutable
}
function custom1(arg: CustomArrayType) {}
interface CustomFunction {
(): void;
prop: string; // note: this property is mutable
}
function custom2(arg: CustomFunction) {}
function union(arg: string[] | ReadonlyArray<number[]>) {} // not all types are readonly
// rule also checks function types
interface Foo {
(arg: string[]): void;
}
interface Foo {
new (arg: string[]): void;
}
const x = { foo(arg: string[]): void {} };
function foo(arg: string[]);
type Foo = (arg: string[]) => void;
interface Foo {
foo(arg: string[]): void;
}
在試驗場開啟function array1(arg: readonly string[]) {}
function array2(arg: readonly (readonly string[])[]) {}
function array3(arg: readonly [string, number]) {}
function array4(arg: readonly [readonly string[], number]) {}
// the above examples work the same if you use ReadonlyArray<T> instead
function object1(arg: { readonly prop: string }) {}
function object2(arg: { readonly prop: string; readonly prop2: string }) {}
function object3(arg: { readonly prop: { readonly prop2: string } }) {}
// the above examples work the same if you use Readonly<T> instead
interface CustomArrayType extends ReadonlyArray<string> {
readonly prop: string;
}
function custom1(arg: Readonly<CustomArrayType>) {}
// interfaces that extend the array types are not considered arrays, and thus must be made readonly.
interface CustomFunction {
(): void;
readonly prop: string;
}
function custom2(arg: CustomFunction) {}
function union(arg: readonly string[] | ReadonlyArray<number>) {}
function primitive1(arg: string) {}
function primitive2(arg: number) {}
function primitive3(arg: boolean) {}
function primitive4(arg: unknown) {}
function primitive5(arg: null) {}
function primitive6(arg: undefined) {}
function primitive7(arg: any) {}
function primitive8(arg: never) {}
function primitive9(arg: string | number | undefined) {}
function fnSig(arg: () => void) {}
enum Foo {
a,
b,
}
function enumArg(arg: Foo) {}
function symb1(arg: symbol) {}
const customSymbol = Symbol('a');
function symb2(arg: typeof customSymbol) {}
// function types
interface Foo {
(arg: readonly string[]): void;
}
interface Foo {
new (arg: readonly string[]): void;
}
const x = { foo(arg: readonly string[]): void {} };
function foo(arg: readonly string[]);
type Foo = (arg: readonly string[]) => void;
interface Foo {
foo(arg: readonly string[]): void;
}
在試驗場開啟選項
本規則支援下列選項
type Options = [
{
allow?: (
| {
from: 'file';
name: [string, ...string[]] | string;
path?: string;
}
| {
from: 'lib';
name: [string, ...string[]] | string;
}
| {
from: 'package';
name: [string, ...string[]] | string;
package: string;
}
| string
)[];
checkParameterProperties?: boolean;
ignoreInferredTypes?: boolean;
treatMethodsAsReadonly?: boolean;
},
];
const defaultOptions: Options = [
{
allow: [],
checkParameterProperties: true,
ignoreInferredTypes: false,
treatMethodsAsReadonly: false,
},
];
allow
有些複雜類型無法輕易設定為唯讀,例如來自 @types/jquery
的 HTMLElement
類型或 JQueryStatic
類型。透過此選項,可以在全域停用針對這類類型的報告。
此選項採用陣列形式,其中包含要忽略的類型規格。陣列中的每個項目都必須符合下列之一的格式
- 定義在檔案中的類型(
{ from: "file", name: "Foo", path: "src/foo-file.ts" }
,path
為相對於專案根目錄的選用路徑) - 預設程式庫中的類型(
{ from: "lib", name: "Foo" }
) - 封裝中的類型(
{ from: "package", name: "Foo", package: "foo-lib" }
,這也適用於定義在類型封裝中的類型)。
此外,類型也可以定義為一個簡單字串,這會讓類型與其來源無關地進行比對。
本規則的程式碼範例,搭配
{
"allow": [
"$",
{ "from": "file", "name": "Foo" },
{ "from": "lib", "name": "HTMLElement" },
{ "from": "package", "name": "Bar", "package": "bar-lib" }
]
}
- ❌ 錯誤
- ✅ 正確
interface ThisIsMutable {
prop: string;
}
interface Wrapper {
sub: ThisIsMutable;
}
interface WrapperWithOther {
readonly sub: Foo;
otherProp: string;
}
// Incorrect because ThisIsMutable is not readonly
function fn1(arg: ThisIsMutable) {}
// Incorrect because Wrapper.sub is not readonly
function fn2(arg: Wrapper) {}
// Incorrect because WrapperWithOther.otherProp is not readonly and not in the allowlist
function fn3(arg: WrapperWithOther) {}
在試驗場開啟import { Foo } from 'some-lib';
import { Bar } from 'incorrect-lib';
interface HTMLElement {
prop: string;
}
// Incorrect because Foo is not a local type
function fn1(arg: Foo) {}
// Incorrect because HTMLElement is not from the default library
function fn2(arg: HTMLElement) {}
// Incorrect because Bar is not from "bar-lib"
function fn3(arg: Bar) {}
在試驗場開啟interface Foo {
prop: string;
}
interface Wrapper {
readonly sub: Foo;
readonly otherProp: string;
}
// Works because Foo is allowed
function fn1(arg: Foo) {}
// Works even when Foo is nested somewhere in the type, with other properties still being checked
function fn2(arg: Wrapper) {}
在試驗場開啟import { Bar } from 'bar-lib';
interface Foo {
prop: string;
}
// Works because Foo is a local type
function fn1(arg: Foo) {}
// Works because HTMLElement is from the default library
function fn2(arg: HTMLElement) {}
// Works because Bar is from "bar-lib"
function fn3(arg: Bar) {}
在試驗場開啟import { Foo } from './foo';
// Works because Foo is still a local type - it has to be in the same package
function fn(arg: Foo) {}
在試驗場開啟checkParameterProperties
此選項可啟用或停用參數屬性的檢查。因為參數屬性會在類別上建立屬性,所以強制它們為唯讀可能不甚理想。
此規則的程式碼範例,搭配 {checkParameterProperties: true}
- ❌ 錯誤
- ✅ 正確
此規則的**正確**程式碼範例,搭配 {checkParameterProperties: false}
class Foo {
constructor(
private paramProp1: string[],
private paramProp2: readonly string[],
) {}
}
在試驗場開啟ignoreInferredTypes
透過此選項,您可以忽略未明確指定類型的參數。在外部依存項指定包含動態參數的回呼,而且手動註解回呼的參數並不理想時,這項功能可能會派上用場。
此規則的程式碼範例,搭配 {ignoreInferredTypes: true}
- ❌ 錯誤
- ✅ 正確
import { acceptsCallback, CallbackOptions } from 'external-dependency';
acceptsCallback((options: CallbackOptions) => {});
在試驗場開啟external-dependency.d.ts
export interface CallbackOptions {
prop: string;
}
type Callback = (options: CallbackOptions) => void;
type AcceptsCallback = (callback: Callback) => void;
export const acceptsCallback: AcceptsCallback;
import { acceptsCallback } from 'external-dependency';
acceptsCallback(options => {});
在試驗場開啟external-dependency.d.ts
export interface CallbackOptions {
prop: string;
}
type Callback = (options: CallbackOptions) => void;
type AcceptsCallback = (callback: Callback) => void;
export const acceptsCallback: AcceptsCallback;
treatMethodsAsReadonly
此選項可將所有動態函式視為唯讀。當您從未使用重新指定函式時,這項功能可能會派上用場。
此規則的程式碼範例,搭配 {treatMethodsAsReadonly: false}
- ❌ 錯誤
- ✅ 正確
type MyType = {
readonly prop: string;
method(): string; // note: this method is mutable
};
function foo(arg: MyType) {}
在試驗場開啟type MyType = Readonly<{
prop: string;
method(): string;
}>;
function foo(arg: MyType) {}
type MyOtherType = {
readonly prop: string;
readonly method: () => string;
};
function bar(arg: MyOtherType) {}
在試驗場開啟此規則的**正確**程式碼範例,搭配 {treatMethodsAsReadonly: true}
type MyType = {
readonly prop: string;
method(): string; // note: this method is mutable
};
function foo(arg: MyType) {}
在試驗場開啟不適合使用的情況
如果您的專案不嘗試執行強制性的強不可變性保證,則可以忽略這條規則。
此規則對於它視為可變的內容非常嚴格。許多宣告為 readonly 的類型皆視為可變,因為它們具有可變的屬性,例如陣列或組。若要解決這些限制,您可能需要使用該規則的選項。特別是,allow
選項 可以明確地將類型標記為 readonly。
類型檢查程式比傳統程式碼品質檢查更為強大,但類型檢查程式碼品質也需要設定 類型檢查程式碼品質。如果在啟用類型檢查規則後遇到效能下降問題,請參閱 效能問題解決。