TS 5.5 변경점 및 여러 꿀팁모음

이수빈·2024년 8월 30일
1

Typescript

목록 보기
11/20
post-thumbnail

TS 5.5 버전의 변경점?

공식문서 ref) https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html

Inferred Type Predicates(타입가드추론)

  • 타입스크립트에서는 타입가드를 통해 타입을 좁힌다. 예를들어, 하나의 코드 컨텍스트에서 아래와 같은 코드가 있을 때 타입가드를 하는방법은 다음과 같다.
const a:string|number; 

if(typeof a === 'string'){
    ....
} // 이때는 a가 string type

if(typeof a === 'number'){
    ....
} // 이때는 a가 number type..
  • 하지만 문제가 발생한다. 이를 다른 함수로 만들어서 타입가드를 하게 된다면? TS는 이를 인식하지 못한다.
function isString(value: unknown) {
  return typeof value === 'string'
}

if (isString(a)) {
  console.log(a) // string | number
}
  • 이를 위해 Inferred type predicate 기능이 추가되었다. (타입가드 추론)

Type assertion vs Type Predicates?

Type Guards

  • 타입가드는 js 문법을 통해 type을 좁히는 방법이다.

  • js의 if문, typeof, in, instanceof.. 등 Javascript 문법을 이용해 타입을 좁히는 것이 핵심

  • typeof 예시

function printAll(strs: string | string[] | null) {
  if (strs && typeof strs === "object") {
    for (const s of strs) {
      console.log(s);
    }
  } else if (typeof strs === "string") {
    console.log(strs);
  }
}
  • in 연산자 예시 => 객체의 특정 프로퍼티가 있는지 확인하는 것을 통해 type Guard
type Fish = { swim: () => void };
type Bird = { fly: () => void };
 
function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    return animal.swim();
  }
 
  return animal.fly();
}
  • instanceOf 예시
function logValue(x: Date | string) {
  if (x instanceof Date) {
    console.log(x.toUTCString());
               
(parameter) x: Date
  } else {
    console.log(x.toUpperCase());
               
(parameter) x: string
  }
}

Type predicate

  • JS 문법으로 Type을 Narrowing 하는 것이 아니라, TS 문법으로 direct로 control 하는 방식이다.

  • [parameterName is Type] 형식으로 사용하고, 여기서 오는 parameterName은 함수가 인자로 받는 parameter 중에 하나의 name과 동일해야 한다.

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter(isFish) as Fish[];
 
// The predicate may need repeating for more complex examples
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
  if (pet.name === "sharkey") return false;
  return isFish(pet);
});

Type assertion

  • Type 단언은 type에 대한 정보를 알고는 있지만, TS가 알 수 없을때 사용한다. (타입을 좁히는 것은 아니다)

  • 아래와 같은 코드에서, document.getElementById("main_canvas")의 결과로 HTMLCanvasElement를 return할 것이라는 결과를 개발자는 알고 있지만, TS는 이를 추론할 수 없다.

  • 이럴때 assertion을 통해 => HTMLCanvasElement를 명시적으로 return한다고 TS에게 알려주는 것.

  • Type Narrowing이 필요할 때 Type assertion을 사용하지 않도록 사용을 주의 할 필요가 있다.

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

배열에서 null, undefined type의 제거?

  • 또 하나의 예시를 보자.

ref) https://www.typescriptlang.org/play/?ts=5.5.4#code/MYewdgzgLgBANgS2jAvDA2gcgIyYDQyYBMmMBYArnHARWACYCmAZgmI-QLoDcAUL6EixWcKIwBOAOSpwAggwCqDFmw4AZJLDSJoAOhFjxACgBuAQzioAfDHOWAhCjR0mrdvRgAyT7YsxHaJTUAJR8AuAQIHCMunAgAOZGBhLS1PL0Sq6q9BrQoUA

  • 아래와 같은 코드가 있다 => 여기서 먼저 list는 (string | null | undefind)[] 타입으로 추론된다.

  • filterNullAndUndefinedList는 filter 함수를 통해 null과 undefined를 제거한 배열이다.

  • 하지만, 5.5 이전의 버전에서는 type predicate 기능이 없었기 때문에, 5.4.5 버전으로 컴파일 한 결과는 여전히 동일한 타입을 추론된다.

const list = ['1', '2' , null, undefined];

const filterNullAndUndefinedList = list.filter(val => val !== undefined && val !== null);

console.log(filterNullAndUndefinedList); // ['1', '2'] but (string | null | undefind)[] 로 추론
  • 이를 해결하려면, filter함수에 인자로 넘겨주는 fn에 type을 predicate해서 넘겨주는 작업을 해야한다.
function filterNullFn<T>(value : T | null | undefined):value is T{
    return value !== undefined && value !== null;
}

const filterNullAndUndefinedListByFn = list.filter(filterNullFn);

console.log(filterNullAndUndefinedListByFn)

  • 이런 과정이 5.5 이상 버전부터는 Inferred Type Predicates으로 자동으로 추론된다.

Control Flow Narrowing for Constant Indexed Accesses

  • 5.5이상 버전부터는 obj[key]의 type을 narrowing 하는것이 가능해졌다.

  • obj[key]의 결과값을 곧바로 narrowing하면 TS는 인식하지 못하는 문제가 있었기 때문.(타입가드가 안되어서 unknown으로 추론됨)

//5.5이전버전
function f1(obj: Record<string, unknown>, key: string) {
    const value = obj[key];
    if (typeof value === "string") {
        value.toUpperCase();
    }
}

//5.5 버전
function f1(obj: Record<string, unknown>, key: string) {
    if (typeof obj[key] === "string") {
        // Now okay, previously was error
        obj[key].toUpperCase();
    }
}

그 외 TS 끄적임

IndexSignature의 활용

TS PlayGround) https://www.typescriptlang.org/play/?ts=5.4.5#code/KYDwDg9gTgLgBDAnmYcDqBLGALAClYAMwxAB58iS5QZgA7AEwGc4mYoM6BzAGjgBU+AZWBgAhlDExo1ELUYs2HbnAC8cAOQB6DQD41cAN4AoOHADaAaTic4Aa2CIIhAXDEsABgBJDFYiABfHxFxSWkoIMMlTi44ADI4SwCPAF0ALgErFIBuYwDc4yQUOABVJmAoAEEwDAMTMy5gGAzo7lyzcubWdhj2twAbfpaetryCgGMIOjY3GozMHD8SUm0xGo0+MorqjH11es0tNYwtRpgNDI0N00PjrU6LzWuzVZqjwcernjHjQuRUMQMBhLMj8SyOPj8EGyeTMbrKLh7ATgxAw+hw1pcG4Afjg3kMUII-kiYMcyRuGTowAAbhUCkVUKTUepMb9JtN4GI4BlAcCicsmXwNFhgABbAByYlFwD0BmFtAlUuAACYAMwARg0vwZcAIoogtJBpEJlBAwAYKMh0Jo6MUI0RBhN-nNKLRCjcQKNnEIFWREIEIN0OL9iApT3p-zgYH5ZoYADUxP0AK7AY0AeQARgArYDjeA290QbO5mBWmMuxxujH2sumqt2hFI-iZnN5+tGKw2Oi6sUG4BGp0kCuIWv+XTpLs+qACBPJ4ABYP8WcpsNU2lQCPFXkg-gQFsl9PFtsFuFF1ulgMx9uYpEHTu2bcx0gOJwuZtHi+DkDjjLR03m5dU3fc8+EsUcSCDAI4CAA

  • 보통 IndexSignature은 객체에 key를 통해 동적으로 아이템을 추가할 때 사용한다.

  • 하지만 동적으로 추가하는 것이기 때문에 key값을 제한하고 싶은 경우가 있다.

type TIndexSignature = {
  [key: string]: string;
};
  • 아래와 같이 string이 아니라, literal type을 사용해서 key값을 제한 할 수 있다.
type TItemNameKey = `itemName${number}`;

type TIndexSignature = {
  [key: TItemNameKey]: string;
};
  • Prefix를 key가 string일때 제한한 후 리터럴 타입을 만드는 타입을 추가해서 사용 할 수 있다.
type addPrefix<TKey, TPrefix extends string> = TKey extends string
  ? `${TPrefix}${TKey}`
  : never;

type TKey = string

const a : addPrefix<TKey, 'itemName'> = 'itemName231'
  • 아래와 같이 Prefix를 추가한 key를 만드는 Utility type도 만들 수 있다.

ref) Utility Type :https://dev.to/teamradhq/utility-type-withprefix-eje

export type WithPrefix<Prefix extends string, T, Separator extends string = '/'> = {
  [K in keyof T as `${Prefix}${Separator}${string & K}`]: T[K];
};
// T를 받아서 => K 는 keyof T => 이를 Prefix, Sepeartor가 붙은 값으로 제한

type UserApi = {
  get: string;
  set: string;
  all: string;
};

const api: WithPrefix<'/api', UserApi> = {
  '/api/get': '',
  '/api/set': '',
  '/api/all': '',
};

Prettify Helper

type Prettify<T> = {
  [K in keyof T]: T[K];
} & unknown;
type Intersected = {
  a: string;
} & {
  b: number;
} & {
  c: boolean;
};

/**
 * { a: string; } & { b: number; } & { c: boolean; }
 */ //이렇게 보임

type Intersected = Prettify<
  {
    a: string;
  } & {
    b: number;
  } & {
    c: boolean;
  }
>;

/**
 * {
 *   a: string;
 *   b: number;
 *   c: boolean;
 * }
 */ //묶여서 보임

Reduce 함수를 사용할 때 3가지 type 정의방법

  1. initial Value를 as로 선언
const grouped = array.reduce((obj, item) => {
  obj[item.key] = item.value;
  return obj;
}, {} as Record<string, string>);
  1. 파라미터 자체의 TYPE ANNOTATION 선언
const grouped = array.reduce(
  (obj: Record<string, string>, item) => {
    obj[item.key] = item.value;
    return obj;
  },
  {}
);
  1. 제너릭 TYPE 넘겨주기
const grouped = array.reduce<Record<string, string>>(
  (obj, item) => {
    obj[item.key] = item.value;
    return obj;
  },
  {}
);
profile
응애 나 애기 개발자

0개의 댓글