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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42696098/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:19:46  来源:igfitidea点击:

How do I declare a read-only array tuple in TypeScript?

typescript

提问by Jonas Kello

We can declare a typed tuple in TypeScript, for example, with the type annotation [string, number]. This means an array of 2 elements where the first element needs to be a string and the second a number.

我们可以在 TypeScript 中声明一个类型化的元组,例如,使用类型注释[string, number]。这意味着一个包含 2 个元素的数组,其中第一个元素需要是一个字符串,第二个元素需要是一个数字。

We can also declare read-only arrays with ReadonlyArray<string>which means a read-only array of strings.

我们还可以声明只读数组,ReadonlyArray<string>这意味着字符串的只读数组。

Now I want to have a read-only tuple like in the first example, but I want it to be read-only like in the second example. How would I declare that?

现在我想要像第一个示例中那样的只读元组,但我希望它像第二个示例中那样是只读的。我该如何声明?

采纳答案by Mariusz Pawelski

From Typescript version 3.4 you can just prefix tuple type with readonlykeyword (source).

从 Typescript 3.4 版开始,您可以在元组类型前加上readonly关键字(source)。

TypeScript 3.4 also introduces new support for readonlytuples. We can prefix any tuple type with the readonlykeyword to make it a readonlytuple, much like we now can with array shorthand syntax. As you might expect, unlike ordinary tuples whose slots could be written to, readonlytuples only permit reading from those positions.

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

TypeScript 3.4 还引入了对readonly元组的新支持。我们可以用readonly关键字作为任何元组类型的前缀以使其成为readonly元组,就像我们现在可以使用数组速记语法一样。正如您所料,与可以写入插槽的普通元组不同,readonly元组只允许从这些位置读取。

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

回答by Arg0n

Since the type [string, number]already is an Array, you can simply use:

由于类型[string, number]已经是 an Array,您可以简单地使用:

Readonly<[string, number]>

Readonly<[string, number]>

Example:

例子:

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)

回答by Jake Tunaley

The accepted answer leaves array mutation methods unaffected, which can cause unsoundness in the following way:

接受的答案使数组变异方法不受影响,这可能会通过以下方式导致不健全:

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

The code below fixes this issue, and includes Sergey Shandar's destructuring fix. You'll need to use --noImplicitAnyfor it to work properly.

下面的代码修复了这个问题,包括 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

回答by ggradnig

Solution in Typescript 3.4: Const Contexts

Typescript 3.4 中的解决方案:Const Contexts

It looks like there will be a clean solution for this requirement coming with TypeScript 3.4 version:

看起来 TypeScript 3.4 版本会为这个要求提供一个干净的解决方案:

With so-called const contexts, the compiler can be told to treat an array or an object as immutable, meaning that their properties are read-only. This also allows the creation of literal tuple types with narrower type inference (i.e. your ["a", "b"]can for the first time be of type ["a", "b"], not string[]without specifiying the whole thing as a contextual type)

使用所谓的const 上下文,可以告诉编译器将数组或对象视为不可变的,这意味着它们的属性是只读的。这也允许创建具有更窄类型推断的文字元组类型(即您 ["a", "b"]第一次可以是 type ["a", "b"],而不是string[]没有将整个事物指定为上下文类型)

The syntax will look like this:

语法如下所示:

let foo = ["text", 1] as const

or

或者

let foo = <const> ["text", 1]

Here is the extended informationof the corresponding PR. As of now, the feature should be available in typescript@next.

这里是对应PR的扩展信息。截至目前,该功能应该在typescript@next.

回答by Sergey Shandar

Readonly<[string, T]>doesn't allow destruction. For example

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.

So, it's better to use a custom interface

所以,最好使用自定义界面

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

For example

例如

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

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

回答by aleclarson

As of v3.2.2, there's no perfect way of making a readonly tuple type without converting it to an object that lookslike an array, but is not.

从 v3.2.2 开始,没有完美的方法可以制作只读元组类型而不将其转换为看起来像数组的对象,但实际上不是。

The lead architect of TypeScript has said thison the topic of combining Readonly<T>with tuple types.

TypeScript 的首席架构师在与元组类型结合的主题上说过句话Readonly<T>

Here is the best solution I've come up with:

这是我想出的最佳解决方案:

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