를 하면서 무엇이 바뀌었나 간단하게 알아보자.
## 현재 사용중인 타입스크립트 버전
* 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
})
```