tenbits
tenbits

Reputation: 8008

`as const` alternative to narrow an array literal tuple, to avoid `readonly` modifiers

We use as const to narrow the types:

[ {foo: 'foo' }, { bar: 'bar' } ] as const

but this also adds readonly modifiers for all props and values. As of const this makes sens, but I would like to have the narrowing, without the readonly modifiers, as offen you want to modify the objects in tuple, or just pass it further to some method, which is of the same interface, but without deep readonly

let foo = [ {foo: 'foo' }, { bar: 'bar' } ]

here the foo is of type:

({
    foo: string;
    bar?: undefined;
} | {
    bar: string;
    foo?: undefined;
})[]

but I would expect to get

[{
    foo: string;
}, {
    bar: string;
}]

There for we use as const, but in most cases it is not possible, as foo/bar get deeply nested readonly modifiers, we could use something like

type DeepWritable<T> = { -readonly [P in keyof T]: DeepWritable<T[P]> };

But it would be wired to use as const which adds readonly, and then DeepWritable to remove it.

Do you know any alternative to as const to narrow the tuple type without having manually to define types.

Upvotes: 14

Views: 2925

Answers (3)

Makien Osman
Makien Osman

Reputation: 1

Found a better way:

type Tuple = [unknown, ...unknown[]]

let foo = [{ foo: "foo" }, { bar: "bar" }] satisfies Tuple

Important to note is that you have to use satisfies which exists since TypeScript 4.9 and not as.

Upvotes: 0

Geoff Davids
Geoff Davids

Reputation: 1017

Here is a better generic Tuple function, since it preserves tuple member literal values too:

export type Writable<T> = {
  -readonly [K in keyof T]: T[K];
};
export const Tuple = <T extends readonly [unknown, ...unknown[]]>(v: T) =>
  v as Writable<T>;

Use it like so:

const foo = Tuple([{foo: 'foo' }, { bar: 'bar' }] as const)
// foo: [{ readonly foo: 'foo' }, { readonly bar: 'bar' }]
// Note the array itself is not readonly, only the object properties are

See the relevant TypeScript 3.4 release note for reference.

Also worth checking out this related, more thorough answer

Upvotes: 0

ccarton
ccarton

Reputation: 3666

You can use the following generic function to constrain the type as a tuple:

const Tuple = <T extends [any, ...any]>(v:T) => v

And then wrap the literal:

let foo = Tuple([ {foo: 'foo' }, { bar: 'bar' } ]) // foo: [{ foo: string }, { bar: string }]

Upvotes: 7

Related Questions