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