---
# System prepended metadata

title: 타입스크립트 버전 업그레이드

---

를 하면서 무엇이 바뀌었나 간단하게 알아보자.


## 현재 사용중인 타입스크립트 버전
* v4.5.4



---

### v4.6
[4.6 버전](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-6.html)

1. **구조분해할당 사용시 타입 추적**
```typescript
4.6 버전 이하에서 아래와 같이 사용하면 터진다
type Action =
  | { kind: "NumberContents"; payload: number }
  | { kind: "StringContents"; payload: string };
  
function processAction(action: Action) {
  const { kind, payload } = action; <--- 여기서 타입 추론이 안됨
  if (kind === "NumberContents") {
    let num = payload * 2;
    // ...
  } else if (kind === "StringContents") {
    const str = payload.trim();
    // ...
  }
}

```
> 각 조건문 안에서 **num**과 **str**의 타입이 여전히 `string | number`여서 타입 에러를 발생시킴
> **4.6**부터는 구조분해할당을 해도 타입추론이 되어 안터진다!

2. **종속 매개변수에 대한 제어 흐름**
```typescript
type Func = (...args: ["a", number] | ["b", string]) => void;

const f1: Func = (kind, payload) => {
  if (kind === "a") {
    payload.toFixed(); // 'payload' narrowed to 'number'
  }
  if (kind === "b") {
    payload.toUpperCase(); // 'payload' narrowed to 'string'
  }
};

f1("a", 42);
f1("b", "hello");

```
> 여기서도 **4.6**버전 이하에서는 **payload**의 값이 `string | number`로 나오지만
> **4.6**부터는 첫번째 인자가 `'str'`이면 `string` `'num'`이면 `number`로 타입을 좁힐 수 있다.

3. **인덱싱된 액세스 추론 개선**
```typescript
interface TypeMap {
  number: number;
  string: string;
  boolean: boolean;
}
type UnionRecord<P extends keyof TypeMap> = {
  [K in P]: {
    kind: K;
    v: TypeMap[K];
    f: (p: TypeMap[K]) => void;
  };
}[P];
function processRecord<K extends keyof TypeMap>(record: UnionRecord<K>) {
  record.f(record.v);
}
// This call used to have issues - now works!
processRecord({
  kind: "string",
  v: "hello!",
  // 'val' used to implicitly have the type 'string | number | boolean',
  // but now is correctly inferred to just 'string'.
  f: (val) => {
    console.log(val.toUpperCase());
  },
});

record.f(record.v);는 동작하지만 processRecord에 대한 추론이 정확하지 않았던점이 개선되었다고한다.
```

---


### v4.7
[4.7 버전](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html)

1. **Node.js의 ECMAScript 모듈 지원**
    * 백엔드 관련 내용이지만 같이 봐둬도 좋을듯
    * 기존 **node**는 **CJS**로 통일되다가 **node.js 12버전** 이상부터 **ESM**가 지원되기 시작하면서 2개를 같이 사용하는 중 인듯하다. 그렇기에 기존 **CJS**만 지원하던 타입스크립트가 **4.7**부터 **ESM**도 같이 지원을 시작함
    * 관련해서 같이보면 [좋은 자료](https://toss.tech/article/commonjs-esm-exports-field)

2. **대괄호로 묶인 요소 액세스에 대한 타입 좁히기**
```typescript
const key = Symbol();
const numberOrString = Math.random() < 0.5 ? 42 : "hello";
const obj = {
    [key]: numberOrString,
};
if (typeof obj[key] === "string") {
    let str = obj[key].toUpperCase();
}
```
> 여기서도 `obj[key]`는 `string | number`로 타입이 추론되다가 **4.7**버전부터 `string`으로 인식된다.

3. **개체 및 매서드의 향상된 함수 추론**
```typescript
declare function f<T>(arg: {
    produce: (n: string) => T,
    consume: (x: T) => void }
): void;
// Works
f({
    produce: () => "hello",
    consume: x => x.toLowerCase()
});
// Works
f({
    produce: (n: string) => n,
    consume: x => x.toLowerCase(),
});
// Was an error, now works.
f({
    produce: n => n,
    consume: x => x.toLowerCase(),
});
// Was an error, now works.
f({
    produce: function () { return "hello"; },
    consume: x => x.toLowerCase(),
});
// Was an error, now works.
f({
    produce() { return "hello" },
    consume: x => x.toLowerCase(),
});
```
> ==Was an error, now works==인 케이스들을 보면 **화살표함수**, **익명함수등** **일반함수**가 아닐경우 전부 error로 나왔으나 개선되었다.

4. **인스턴스화 표현식**
```typescript
interface Box<T> {
    value: T;
}
function makeBox<T>(value: T) {
    return { value };
}

function makeHammerBox(hammer: Hammer) {
    return makeBox(hammer);
}
// or...
const makeWrenchBox: (wrench: Wrench) => Box<Wrench> = makeBox;
```
> **4.7버전** 이하에서는 위와같이 작성을 해야했다면 **4.7**부터는 아래와 같이 사용할 수 있다.
```typescript
const makeHammerBox = makeBox<Hammer>;
const makeWrenchBox = makeBox<Wrench>;

const makeStringBox = makeBox<string>;
// TypeScript correctly rejects this.
makeStringBox(42);
```
5. **유형 매개변수에 대한 선택적 분산 주석**
[원문](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-7.html#extends-constraints-on-infer-type-variables)
* 제너릭 안에 **out** 또는 **in** 또는 **in out**과 같이 사용할수있다는거같은데 내용이 어려워서 제대로 이해하지 못함.

6. **개체 매서드 스니펫 완성**
```typescript
interface Options {
    processChange?(key: string, oldState: string, newState:string): void;
}

function getWatchedMap(m: Map<string, string>, options: Options) {
    // ...
}

getWatchedMap(myMap, {
    // pro까지 입력하면
    // processChage(key, oldState, newState) 와 같이 자동완성 스니펫이 나온다.
})
```


---

### v4.8
[4.8 버전](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-8.html)

1. 향상된 **Intersection Reduction, Union Compatibility, and Narrowing**
```typescript
4.8이전에는 y = x가 에러였으나 4.8부터는 ok
function f(x: unknown, y: {} | null | undefined) {
    x = y; // always worked
    y = x; // used to error, now works
}


4.8 이전에는 {} | null | undefined와 같이 사용했으나
function narrowUnknownishUnion(x: {} | null | undefined) {
    if (x) {
        x;  // {}
    }
    else {
        x;  // {} | null | undefined
    }
}
4.8 부터는 unknown으로 사용해도 ok
function narrowUnknown(x: unknown) {
    if (x) {
        x;  // used to be 'unknown', now '{}'
    }
    else {
        x;  // unknown
    }
}
```

2. 향상된 **infer 타입(템플릿 문자열)** 추론
> 백그라운드 지식
> infer U type일때는 상관없지만 infer U extends type과 같이 extends를 infer와 같이 사용하려면 템플릿 문자열을 사용해야한다
> ex) `${infer U extends type}`
```typescript
// SomeNum used to be 'number'; now it's '100'.
type SomeNum = "100" extends `${infer U extends number}` ? U : never;

/** 기존에는 "100"이라는 문자열 리터럴 타입이 extends를 통해 
infer U extends number 즉 number로 상속 또는 확장 가능한지를 판단하고 
맞으면 타입 U 즉 상속 또는 확장이 되는 number로 타입을 추론했으나 
4.8부터는 문자열 리터럴 타입인 "100"으로 추론한다.
*/

// SomeBigInt used to be 'bigint'; now it's '100n'.
type SomeBigInt = "100" extends `${infer U extends bigint}` ? U : never;
// 공식문서에 나온 문자열 리터럴 타입이 오타난걸로 추정 "100" => "100n"

// SomeBool used to be 'boolean'; now it's 'true'.
type SomeBool = "true" extends `${infer U extends boolean}` ? U : never;
```

3. 바인딩 패턴에서 향상된 추론
```typescript
declare function chooseRandomly<T>(x: T, y: T): T;
let [a, b, c] = chooseRandomly([42, true, "hi!"], [0, false, "bye!"]);
//   ^  ^  ^
//   |  |  |
//   |  |  string
//   |  |
//   |  boolean
//   |
//   number
```

---

### v4.9
[4.9 버전](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html)

1. **satisfies** 연산자
```typescript
다음과 같은 palette객체가 있다고 가정하고 blue가 아닌 bleu로 오타가 났을 때
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ^^^^ sacrebleu - 오타를 냈습니다!
};

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
과 같이 타입을 설정하고

const palette: Record<Colors, string | RGB>로 객체의 오타를 잡을 수 있지만

const redComponent = palette.red.at(0);에서
palette.red의 타입이 string | RGB이기에 .at 매서드 사용이 불가능하다고 에러가 발생한다.

satisfies연산자를 사용한다면

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
} satisfies Record<Colors, string | RGB>;

오타도 잡히면서 아래와 같이 .at .toUpperCase 매서드 사용이 가능하다.
const redComponent = palette.red.at(0);
const greenNormalized = palette.green.toUpperCase();

이렇게 되는 이유는 satisfies 연산자가 특정 타입과 일치하는지 검증하기때문이다.
```


2. **in** 연산자를 사용하여 정의되지 않은 프로퍼티 타입 좁히기
```typescript
interface Context {
    packageJSON: unknown;
}
function tryGetPackageName(context: Context) {
    const packageJSON = context.packageJSON;
    // 객체 여부를 확인합니다.
    if (packageJSON && typeof packageJSON === "object") {
        // 문자열 타입의 name 프로퍼티를 가지고 있는지 확인합니다.
        if ("name" in packageJSON && typeof packageJSON.name === "string") {
        //                                              ~~~~
        // error! Property 'name' does not exist on type 'object.
            return packageJSON.name;
        //                     ~~~~
        // error! Property 'name' does not exist on type 'object.
        }
    }
    return undefined;
}

/* 4.9버전 이하에서는 in 연산자를 사용하더라고 Typescript는 해당 프로퍼티(packageJSON)가 
 * 존재하는지에 대한 정확한 타입 정보를 얻지 못할 때가 여럿 있었고 
 * 특히 unknown 타입과 조합 될 경우 빈번하게 발생했다.
 * 4.9버전부터는 이러한 in 연산자가 강화되어 타입이 unknown등으로 제한되어있더라도 
 * 프로퍼티 존재 여부와 타입 정보를 정확하게 검사할 수 있게 되었다.
*/

interface Context {
    packageJSON: unknown;
}
function tryGetPackageName(context: Context): string | undefined {
    const packageJSON = context.packageJSON;
    // 객체 여부를 확인합니다.
    if (packageJSON && typeof packageJSON === "object") {
        // 문자열 타입의 name 프로퍼티를 가지고 있는지 확인합니다.
        if ("name" in packageJSON && typeof packageJSON.name === "string") {
            // 정상 동작합니다!
            return packageJSON.name;
        }
    }
    return undefined;
}
```

3. **NaN** 동등성 검사

```typescript
//JS에서 NaN은 동일한 값이 없다, 같은 NaN일지어라도
console.log(NaN == 0)  // false
console.log(NaN === 0) // false
console.log(NaN == NaN)  // false
console.log(NaN === NaN) // false
//하지만 적어도 대칭적으로 모든 것은 항상 NaN과 동일하지 않다
console.log(NaN != 0)  // true
console.log(NaN !== 0) // true
console.log(NaN != NaN)  // true
console.log(NaN !== NaN) // true

//Typescript에서는 이제 NaN값을 직접 비교하면 오류를 보여주고 Number.isNan사용을 제안한다.

function validate(someValue: number) {
    return someValue !== NaN;
    //     ~~~~~~~~~~~~~~~~~
    // error: This condition will always return 'true'.
    //        Did you mean '!Number.isNaN(someValue)'?
}
```

---

### v5.0
[5.0 버전](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html)

1. **const 타입 파라미터**
```typescript
객체의 타입을 추론할 때 Typescript는 일반적인 타입을 선택한다.

type HasNames = { readonly names: string[] };
function getNamesExactly<T extends HasNames>(arg: T): T["names"] {
    return arg.names;
}
//names의 추론된 타입은 string[]이다.
// Inferred type: string[]
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

// as const를 사용하여 구체적인 타입을 지정해줄 수 있었다.

// 우리가 원하는 타입:
//    readonly ["Alice", "Bob", "Eve"]
// 실제로 우리가 얻는 타입:
//    string[]
const names1 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]});

// Correctly gets what we wanted:
//    readonly ["Alice", "Bob", "Eve"]
const names2 = getNamesExactly({ names: ["Alice", "Bob", "Eve"]} as const);

하지만 이제 5.0버전부터는 유형 매개변수 선언에 const를 추가하여 const와 유사한 추론이 기본값이 되도록 할 수 있다.

type HasNames = { names: readonly string[] };
function getNamesExactly<const T extends HasNames>(arg: T): T["names"] {
//                       ^^^^^
    return arg.names;
}

// Inferred type: readonly ["Alice", "Bob", "Eve"]
// Note: Didn't need to write 'as const' here
const names = getNamesExactly({ names: ["Alice", "Bob", "Eve"] });
```

---

### v5.1
[5.1 버전](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-1.html)

1. 더 쉬운 undefined 반환 함수 리턴
> 배경지식
> JS에서 함수가 반환을 하지 않고 실행을 완료하면 undefined 값을 반환한다.
> 기존의 Typescript에서는 반환문이 전혀 없는 함수는 void함수와 any함수 뿐, 명시적으로 undefined를 반환한다는 하나의 반환문이 있어야 undefined를 반환했음
```typescript
// ✅ fine - we inferred that 'f1' returns 'void'
function f1() {
  // no returns
}
// ✅ fine - 'void' doesn't need a return statement
function f2(): void {
  // no returns
}
// ✅ fine - 'any' doesn't need a return statement
function f3(): any {
  // no returns
}
// ❌ error!
// A function whose declared type is neither 'void' nor 'any' must return a value.
function f4(): undefined {
  // no returns
}

5.1 버전부터는 undefined 반환 함수에 반환문을 포함하지 않아도 된다.
// ✅ Works in TypeScript 5.1!
function f4(): undefined {
  // no returns
}
// ✅ Works in TypeScript 5.1!
takesFunction((): undefined => {
  // no returns
})

또한 함수에 반환 표현식이 없고 undefined로 함수를 반환할 것이 예상되는 함수에는 
Typescript가 undefined로 추론하여 반환한다.
// ✅ Works in TypeScript 5.1!
takesFunction(function f() {
  //                 ^ return type is undefined
  // no returns
})
// ✅ Works in TypeScript 5.1!
takesFunction(function f() {
  //                 ^ return type is undefined
  return
})
```