고급 TypeScript 연습 - 답변 3

21031 단어 typescriptjavascript
내가 질문한 질문은 종속성을 갖기 위해 함수 인수를 입력하는 방법이었습니다. 따라서 첫 번째가 string이면 두 번째는 string 이어야 하며 절대 혼합되지 않아야 하며 원래 코드 조각은 다음과 같습니다.

function f(a: string | number, b: string | number) {
    if (typeof a === 'string') {
        return a + ':' + b; // no error but b can be number!
    } else {
        return a + b; // error as b can be number | string
    }
}
f(2, 3); // correct usage
f(1, 'a'); // should be error
f('a', 2); // should be error
f('a', 'b') // correct usage


퍼즐을 풀 수 있는 가능성은 없습니다. 몇 가지 가능한 옵션 아래에 있습니다.

솔루션 1 - 두 인수 모두에 대한 간단한 제네릭 유형




function f<T extends string | number>(a: T, b: T) {
    if (typeof a === 'string') {
      return a + ':' + b;
    } else {
      return (a as number) + (b as number); // assertion
    }
  }
// but usage is disappointing:
const a = f('a', 'b'); // och no the return is number | string :(


멋지고 간단합니다. 두 인수 모두에 대해 하나의 유형이 있다고 합니다. 따라서 first가 string인 경우 second도 string가 되어야 합니다. 모든 잘못된 사용 사례가 이제 제거되므로 함수 api 수준에서 이것은 좋은 솔루션입니다. 여기서 작은 문제는 else 의 숫자에 대한 주장이 필요하다는 것입니다. 하지만 가장 큰 문제는 반환 유형이 올바르지 않다는 것입니다.

반환 유형 수정




function f<T extends string | number, R extends (T extends string ? string : number)>(a: T, b: T): R {
  if (typeof a === 'string') {
    return a + ':' + b as R;
  } else {
    return ((a as number) + (b as number)) as R;
  }
}
const a = f('a', 'b'); // a is string :)
const b = f(1, 2); // b is number :)


보시다시피 솔루션은 그리 간단하지 않으며 우리에게 많은 타이핑과 주장을 요구합니다. 우리는 여기에 우리 함수의 반환 유형인 조건부 유형R을 소개합니다. 불행히도 우리는 이 유형에 대한 모든 반환을 주장해야 합니다. 그러나 함수의 인터페이스는 이제 완벽하고 인수는 유형 안전하며 반환은 적절하게 좁혀집니다.

솔루션 2 - 인수를 하나의 유형으로 작성




// type guard
const isStrArr = (a: string[] | number[]): a is string[] => typeof a[0] === 'string'

function f(...args: string[] | number[]) {
   if (isStrArr(args)) {
     return args[0] + ':' + args[1];
   } else {
     return args[0] + args[1]; // no assertion
   }
 }


이 솔루션에는 제네릭 유형도 필요하지 않습니다. 우리는 인수를 하나의 유형string[] | number[]으로 구성합니다. 그리고 모든 인수가 문자열이거나 모두 숫자가 됨을 의미합니다. 제네릭이 사용되지 않기 때문에 코드에 어설션이 필요하지 않습니다. 문제는 순수 조건이 else 에서 유형을 좁히지 않기 때문에 추가 유형 가드를 제공해야 한다는 사실뿐입니다. 이 문제는 a, b 대신 인덱스를 직접 사용하는 것으로 간주할 수 있으며 이를 전달할 수 없으며 ifelse 에서 구조를 해제할 수 있지만 이 방법은 더 이상 좋지 않습니다. 우리가 할 수 없는 이유 - 별도로 a 확인하면 b 유형에 영향을 미치지 않기 때문입니다. 고려하다:

function f(...[a,b]: string[] | number[]) {
  if (typeof a === 'string') {
    return a + ':' + b; // b is number | string
  } else {
    return a + b; // error both are number | string
  }
}


There is issue with type string[] | number[], it doesn't fully fit this function. Can you spot why, and what type would be better?



또한 이 솔루션에서는 일반 유형 🙄이 없기 때문에 반환 유형을 수정할 수 없으므로 반환은 항상 string | number

해결 방법 3 - 인수에 대한 일반 작성 유형




// type guard
const isNumArr = (a: string[] | number[]): a is number[] => typeof a[0] === 'number'

function f<T extends string[] | number[], R extends (T extends string[] ? string : number)>(...args: T): R {
  if (isNumArr(args)) {
    return args[0] + args[1] as R;
  } else {
    return args[0] + ':' + args[1] as R
  }
}
const a = f('a', 'b'); // a is string :)
const b = f(1, 2); // b is number :)


솔루션 3은 리턴 유형R을 도입하여 솔루션 2를 고정한 방법과 유사합니다. 마찬가지로 여기에서 R 에 대한 주장도 수행해야 하지만 else 에서 number 까지 주장할 필요는 없습니다. 여기서 내가 한 것은 좋은 트릭이라는 것을 알 수 있듯이 나는 조건을 반대로하고 먼저 숫자에 대해 묻습니다 😉.

해결 방법 4 - 함수 오버로드




function f(a: string, b: string): string
function f(a: number, b: number): number
function f(a: string | number, b: string | number ): string | number {
  if (typeof a === 'string') {
    return a + ':' + b;
  } else {
    return ((a as number) + (b as number));
  }
}
const a = f('a', 'b'); // a is string :)
const b = f(1, 2); // b is number :)


함수 오버로드를 사용하여 원하는 인수 상관 관계 및 적절한 반환 유형을 만들 수 있습니다. 오버로드에는 제네릭 유형과 조건부 유형이 필요하지 않습니다. 이 경우 IMHO 과부하는 가장 간단하고 최상의 솔루션입니다.

요약 - 모든 솔루션이 이상적이지는 않습니다.



요약하자면, 그렇게 하지 마세요. 가능하면 그런 기능을 만들지 마십시오. 하나는 string 와 함께 작업하고 다른 하나는 number 로 작업하는 두 가지 다른 기능을 만드는 것이 훨씬 더 좋습니다. 우리가 여기서 만든 이런 종류의 임시변통주의는 누군가를 행복하게 만들 수도 있지만 복잡할 뿐입니다. 자세한 내용은 .

이 답변에 대한 코드는 Playground 에서 찾을 수 있습니다.

이 시리즈는 이제 막 시작되었습니다. 고급 TypeScript의 새롭고 흥미로운 질문에 대해 알고 싶다면 및 에서 저를 팔로우하십시오.

좋은 웹페이지 즐겨찾기