TypeScript에서 ANY를 사용할 때의 비밀 함정

15222 단어 typescriptweb

무엇입니까?



TypeScript로 작업하는 경우 the any type을 사용하게 될 가능성이 있습니다. any 기본적으로 유형 검사를 해제하고 해당 변수를 모든 용도로 사용할 수 있습니다. any 변수에서 모든 메서드를 호출할 수 있으며 모두 any도 반환합니다. 코드베이스의 모든 항목에 대한 유형을 작성할 수 없을 때 유용합니다.

let obj: any = { x: 0 };
// None of these lines of code are errors
const foo: any = obj.foo();
obj();
obj.bar = 100;


함수 오버로드란 무엇입니까?



TypeScript에는 또 다른neat feature called function overloads . 일부 JavaScript 함수는 제공한 인수에 따라 다른 결과를 반환하며, 이는 여러 함수 유형을 서로 위에 작성하여 TypeScript에서 나타낼 수 있습니다. 한 번에 하나의 함수 시그니처만 일치시킬 수 있습니다. 일치하는 오버로드는 함수에 제공하는 인수에 따라 결정됩니다. The first applicable overload will always be chosen .

function convertType(value: string): number;
function convertType(value: number): string;
function convertType(value: string | number): number | string {
  if (typeof value === 'number') {
    return value.toString();
  } else {
    return parseFloat(value);
  }
}

const num: number = convertType('number');
const str: string = convertType(num);


배열에 오버로딩 사용



일부 함수는 어떤 방식으로 배열을 변환하고 동일한 길이와 약간 수정된 유형의 배열을 반환하려고 합니다. A good example of this is Promise.all , 약속 배열을 값 배열로 해결되는 단일 약속으로 변환합니다.

generic function definitions 을 사용하여 Promise.all 에 전달된 배열의 유형을 유추할 수 있습니다. 그러나 결과 유형은 위치 데이터가 없는 배열이 됩니다.

class Promise {
  static all<T>(array: (T | Promise<T>)[]): T[];
}

Promise.all([Promise.resolve(10), Promise.resolve('hello world')]).then(
  (result) => {
    // result's type is (number | string)[]
    // @ts-expect-error: Type 'string' is not assignable to type 'number'.
    const num: number = result[0];
  }
);


TypeScript는 특정 배열 항목의 유형을 유추할 수 있지만 길이를 하드코딩해야 합니다. 오버로딩을 사용하면 다양한 배열 길이에 대해 몇 가지 변형을 가질 수 있습니다.

class Promise {
  static all<A, B, C>(
    array: [A | Promise<A>, B | Promise<B>, C | Promise<C>]
  ): [A, B, C];
  static all<A, B>(array: [A | Promise<A>, B | Promise<B>]): [A, B];
  static all<A>(array: [A | Promise<A>]): [A];
  // fallback to unknown length
  static all<T>(array: (T | Promise<T>)[]): T[];
}

Promise.all([Promise.resolve(10), Promise.resolve('hello world')]).then(
  (result) => {
    // result's type is [number, string]
    // This line is no longer an error
    const num: number = result[0];
  }
);


그러나 너무 많은 오버로드만 작성할 수 있습니다. 결국 위와 같이 길이를 알 수 없는 배열로 폴백해야 합니다. TypeScriptofficial type definitions for Promise.all 는 배열을 최대 길이 10까지 하드코딩하고 그 이후에는 폴백합니다.

과부하로 인해 문제가 발생하는 방법


any는 모든 유형과 일치하며 함수 오버로드는 적용 가능한 첫 번째 오버로드를 사용합니다. 이 두 가지 사실은 유형이 any인 변수를 오버로드가 있는 함수에 전달할 때 문제를 일으킵니다.

function convertType(value: string): number;
function convertType(value: number): string;
function convertType(value: string | number): number | string {
  if (typeof value === 'number') {
    return value.toString();
  } else {
    return parseFloat(value);
  }
}

const num: any = 10;
// @ts-expect-error: Type 'number' is not assignable to type 'string'.
const str: string = convertType(num);


첫 번째 오버로드는 any 형식의 변수를 전달할 때 항상 사용됩니다. 해당 서명에 적용할 수 있기 때문입니다. 나중에 더 일반적인 서명이 있더라도 첫 번째 오버로드가 선택됩니다. 더 일반적인 서명이 전달하는 모든 변수와 일치하기 때문에 오버로드 순서를 되돌릴 수 없습니다. 내가 아는 한, 변수가 유형any인 경우에만 일치하는 서명을 작성할 수 없습니다. 양방향으로.
Promise.all의 경우 첫 번째 함수 오버로드 서명은 하드코딩된 길이가 10인 배열입니다. 그러면 any가 전달되고 결과 유형이 정확히 10unknown의 배열이 되는 이와 같은 혼란스러운 버그가 발생할 수 있습니다. ) 에스.









에반™










알겠습니다. 왜 *정확히* 10개의 `unknown` 배열인 `any` 맵을 기다리고 있습니까?


오후 22:31 - 2020년 10월 12일





0

1



미래 솔루션



배열 길이를 하드코딩하는 것은 좋지 않습니다. 너무 많은 변형만 지원할 수 있기 때문입니다. TypeScript 4.0에는 “variadic tuple types” 이라는 새로운 기능이 도입되어 정확한 배열 인수를 캡처하고 변환할 수 있습니다. Future type definitions for Promise.all 은 모든 함수 오버로드를 단일 함수 서명으로 대체하여 any 버그를 완전히 제거할 수 있습니다.

TypeScript는 향후 함수 오버로드로 전달any하기 위한 특수 처리를 추가할 수도 있습니다. 기존 문제를 알고 있거나 내가 놓친 것을 본 경우 알려주세요.

좋은 웹페이지 즐겨찾기