如何在TypeScript中声明只读数组元组?

ss2ws0br  于 2023-03-04  发布在  TypeScript
关注(0)|答案(7)|浏览(203)

我们可以在TypeScript中声明一个类型化元组,例如,使用类型注解[string, number],这意味着一个包含2个元素的数组,其中第一个元素需要是字符串,第二个元素需要是数字。
我们也可以用ReadonlyArray<string>声明只读数组,这意味着字符串的只读数组。
现在我想有一个只读元组,就像第一个例子一样,但是我想让它像第二个例子一样是只读的,我该怎么声明呢?

hsgswve4

hsgswve41#

由于类型[string, number]已经是Array,因此可以简单地使用:
Readonly<[string, number]>
示例:

let tuple: Readonly<[string, number]> = ['text', 3, 4, 'another text'];

tuple[0] = 'new text'; //Error (Readonly)

let string1: string = tuple[0]; //OK!
let string2: string = tuple[1]; //Error (Type number)
let number1: number = tuple[0]; //Error (Type string)
let number2: number = tuple[1]; //OK!
let number3: number = tuple[2]; //Error (Type any)
apeeds0o

apeeds0o2#

TypeScript 3.4+的解决方案:常量Assert

使用constAssert,编译器可以被告知将数组或对象视为不可变的,这意味着它们的属性是只读的。这也允许创建具有更窄类型推断的文本元组类型(即,您的["a", "b"]可以是["a", "b"]类型,而不是string[]类型,而无需将整个对象指定为上下文类型)
语法:

const foo = ["text", 1] as const // or
const foo = <const> ["text", 1]
// typeof foo:
readonly ["text", 1]

它也可以用于对象文本:

const myObj = {
  foo: 1,
  bar: ["a", "b"],
  baz: true,
} as const
// typeof myObj:
{ readonly foo: 1, readonly bar: readonly ["a", "b"], readonly baz: true }

对应PR的Here is the extended information

tkclm6bt

tkclm6bt3#

从Typescript 3.4版开始,您可以只在元组类型前面加上readonly关键字(源代码)。
TypeScript 3.4还引入了对readonly元组的新支持。我们可以在任何元组类型的前面加上readonly关键字,使其成为readonly元组,就像我们现在使用数组速记语法所做的那样。正如您所期望的,与插槽可以写入的普通元组不同,readonly元组只允许从这些位置阅读。

function foo(pair: readonly [string, string]) {
    console.log(pair[0]);   // okay
    pair[1] = "hello!";     // error
}
jckbn6z7

jckbn6z74#

接受的答案不影响数组变异方法,这可能会以下列方式导致不合理:

const tuple: Readonly<[number, string]> = [0, ''];
tuple.shift();
let a = tuple[0]; // a: number, but at runtime it will be a string

下面的代码修复了这个问题,并且包含了Sergey Shandar的重构修复。你需要使用--noImplicitAny来使它正常工作。

type ArrayItems<T extends ReadonlyArray<any>> = T extends ReadonlyArray<infer TItems> ? TItems : never;

type ExcludeProperties<TObj, TKeys extends string | number | Symbol> = Pick<TObj, Exclude<keyof TObj, TKeys>>;

type ArrayMutationKeys = Exclude<keyof any[], keyof ReadonlyArray<any>> | number;

type ReadonlyTuple<T extends any[]> = Readonly<ExcludeProperties<T, ArrayMutationKeys>> & {
    readonly [Symbol.iterator]: () => IterableIterator<ArrayItems<T>>;
};

const tuple: ReadonlyTuple<[number, string]> = [0, ''];
let a = tuple[0]; // a: number
let b = tuple[1]; // b: string
let c = tuple[2]; // Error when using --noImplicitAny
tuple[0] = 1; // Error
let [d, e] = tuple; // d: number, e: string
let [f, g, h] = tuple; // Error
cpjpxq1n

cpjpxq1n5#

Readonly<[string, T]>不允许销毁。例如

const tuple: Readonly<[string, number]> = ["text", 4]

const [n, v] = tuple // error TS2488: Type 'Readonly<[string, number]>' must have a '[Symbol.iterator]()' method that returns an iterator.

因此,最好使用自定义接口

export interface Entry<T> {
    readonly [0]: string
    readonly [1]: T
    readonly [Symbol.iterator]: () => IterableIterator<string|T>
}

例如

const tuple: Entry<number> = ["text", 4]

const [name, value] = tuple // ok
const nameCheck: string = name
const valueCheck: number = value
hyrbngr7

hyrbngr76#

从v3.2.2开始,没有一种完美的方法可以创建一个只读元组类型,而不将其转换为一个 * 看起来 * 像数组的对象,但实际上不是。
TypeScript的首席架构师在讨论将Readonly<T>与元组类型结合时说过这一点。
下面是我想出的最佳解决方案:

type ReadonlyTuple<T extends any[]> = {
    readonly [P in Exclude<keyof T, keyof []>]: T[P]
} & Iterable<T[number]>
8wtpewkr

8wtpewkr7#

您可以在创建数组时调用Object.freeze,使其成为只读。

const arr = Object.freeze(["text", 2, 3]);

这会自动将类型转换为TypeScript中的readonly (string | number)[],并且在运行时也是只读的。

相关问题