【问题标题】:How do I declare a read-only array tuple in TypeScript?如何在 TypeScript 中声明只读数组元组?
【发布时间】:2017-03-09 13:01:22
【问题描述】:

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

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

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

【问题讨论】:

    标签: typescript


    【解决方案1】:

    由于[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)
    

    【讨论】:

    • 我认为这会给你一个只读的可写元组数组。我想要的是一个只读元组。尝试编译这个,你会看到const x: ReadonlyArray&lt;[string, number]&gt; = ["a", 1];
    • 转译成 JavaScript 后它是可写的,是的。但是当你尝试做tuple[0] = 'new text';时你会得到TypeScript错误。
    • 我将您的答案中的Readonly 误读为ReadonlyArray。实际上Readonly&lt;T&gt; 工作:-)。
    • 对我不起作用,当我尝试修改 Readonly 数组时没有出现类型错误。这是我正在尝试的代码,TypeScript 不会抱怨:const a: Readonly&lt;number[]&gt; = [1,2,3]; a[1] = 4。我的应用程序没有错误!我在 TS 3.3.0-dev.20181208。也不能在操场上工作(在撰写本文时)!见:typescriptlang.org/play/…
    • 确认,Readonly&lt;Array&lt;T&gt;&gt; 不起作用,但 ReadonlyArray&lt;Array&lt;T&gt;&gt; 起作用:github.com/Microsoft/TypeScript/issues/28968
    【解决方案2】:

    Typescript 3.4 中的解决方案:常量上下文

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

    语法如下:

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

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

    Here is the extended information 对应的 PR。

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

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

        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
        

        【讨论】:

        • 在最新版本的 typescript 中,Readonly&lt;[number, string]&gt;readonly [number, string] 相同。而这种情况下,方法shift是不存在的。
        【解决方案5】:

        Readonly&lt;[string, T]&gt; 不允许破坏。例如

        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
        

        【讨论】:

          【解决方案6】:

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

          TypeScript 的首席架构师已就 Readonly&lt;T&gt; 与元组类型结合的话题发表了 this

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

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

          【讨论】:

            猜你喜欢
            • 2017-09-24
            • 2021-12-15
            • 2016-12-16
            • 2019-12-02
            • 1970-01-01
            • 2021-07-27
            • 2022-10-06
            • 2018-01-15
            • 1970-01-01
            相关资源
            最近更新 更多