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

前端 未结 6 1624
太阳男子
太阳男子 2020-12-17 15:07

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 ne

相关标签:
6条回答
  • 2020-12-17 15:34

    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 --noImplicitAny for it to work properly.

    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
    
    0 讨论(0)
  • 2020-12-17 15:41

    Solution in Typescript 3.4: Const Contexts

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

    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)

    The syntax will look like this:

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

    or

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

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

    0 讨论(0)
  • 2020-12-17 15:46

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

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

    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]>
    
    0 讨论(0)
  • 2020-12-17 15:48

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

    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)
    
    0 讨论(0)
  • 2020-12-17 15:52

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

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

    function foo(pair: readonly [string, string]) {
        console.log(pair[0]);   // okay
        pair[1] = "hello!";     // error
    }
    
    0 讨论(0)
  • 2020-12-17 15:57

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

    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
    
    0 讨论(0)
提交回复
热议问题