简体   繁体   中英

noUncheckedIndexedAccess type assertion in array destructuring assignment

With noUncheckedIndexedAccess , array accesses now return T | undefined T | undefined to reflect the fact that you might be accessing past .length . Sometimes you know you aren't and can use a ! type assertion to tell TypeScript to forget about it.

But I can't figure out how to do that in this case:

function double(x: number[]): number[] {
  return x.map(a => a * 2)
}

function foo() {
  let a: number = 5;
  let b: number = 6;
  [a, b] = double([b, a]);
}

My first guess is

  [a!, b!] = double([b, a]);

But it doesn't work. Is there a non-tedious way to do this?

Playground Link

This is a shortcoming of how the map method is defined on the ReadonlyArray interface in the standard library (and the ReadonlyArray interface in general): it returns a mutable array regardless of whether the input type was a tuple or not:

interface ReadonlyArray<T> {
  map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): U[]
}

Since the type of values in the ReadonlyArray is determined by a generic type parameter T , there is no way to determine an exact relationship between indices and values which is essential to being able to return a tuple.

An inherently unsafe, but easy way out using type assertions with as was already provided (nothing wrong with it per se since your situation is exactly the case where you know more than the compiler does).

Another, safer, but tedious solution you are likely to be aware of is defining your variables as number | undefined number | undefined (a little less tedious if you define a helper MaybeNum or similar) and then use type guards to reassure the compiler.

Finally, you can take advantage of the declaration merging technique and provide your own overload for the map method that will use this inference to get the tuple type. With it, you can return a mapped type in which values are replaced by the inferred U :

interface ReadonlyArray<T> {
  map<U>(callbackfn: (value: T, index: number, array: readonly T[]) => U, thisArg?: any): { [ P in keyof this ] : U };
}

Your double function now only needs a tweak to tell the compiler that x is going to be a readonly array:

const dbl = <T extends readonly number[]>(x: T) => x.map(a => a * 2);

The result is exactly what you want (obviously, an as const required when you pass an array literal so as it is treated as a tuple, but this is a small price to pay), what's more, the ReadonlyArray methods can still be used properly:

function foo2() {
  let a: number = 5;
  let b: number = 6;
  [a, b] = dbl([b, a] as const); //OK
}

dbl([5,6] as const).forEach((a) => console.log(a)); //OK, a is number here

Playground

Maybe just cast it to a tuple?

function double(x: number[]): number[] {
  return x.map(a => a * 2)
}

function foo() {
  let a: number = 5;
  let b: number = 6;
  [a, b] = double([b, a]) as [number, number];
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM