<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>hustle-dev.log</title>
        <link>https://velog.io/</link>
        <description>It is possible for ordinary people to choose to be extraordinary.</description>
        <lastBuildDate>Fri, 21 Jun 2024 08:55:25 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>hustle-dev.log</title>
            <url>https://images.velog.io/images/hustle-dev/profile/c55342f6-0fc7-4d5e-93f7-12f938b35318/social.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. hustle-dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hustle-dev" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[TypeScript 5.5 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-5.5-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-5.5-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Fri, 21 Jun 2024 08:55:25 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>TypeScript 5.5에서 중요하다고 생각되는 부분을 번역했습니다. 더 자세한 글은 아래 원글 링크를 참고해주세요.
<a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/">https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/</a></p>
</blockquote>
<h2 id="추론된-타입-서술">추론된 타입 서술</h2>
<p>TypeScript의 제어 흐름 분석은 변수 타입이 코드에서 어떻게 변화하는지 추적한다. </p>
<pre><code class="language-ts">interface Bird {
    commonName: string;
    scientificName: string;
    sing(): void;
}

// Maps country names -&gt; national bird.
// Not all nations have official birds (looking at you, Canada!)
declare const nationalBirds: Map&lt;string, Bird&gt;;

function makeNationalBirdCall(country: string) {
  const bird = nationalBirds.get(country);  // bird has a declared type of Bird | undefined
  if (bird) {
    bird.sing();  // bird has type Bird inside the if statement
  } else {
    // bird has type undefined here.
  }
}</code></pre>
<p><code>undefined</code> 경우를 처리하도록 하여 TypeScript는 더 견고한 코드를 작성하도록 유도한다.</p>
<p>과거에는 이러한 타입 정제를 배열에 적용하기 어려웠다. 이 코드는 이전 모든 버전의 TypeScript에서 오류가 발생했을 것이다.</p>
<pre><code class="language-ts">function makeBirdCalls(countries: string[]) {
  // birds: (Bird | undefined)[]
  const birds = countries
    .map(country =&gt; nationalBirds.get(country))
    .filter(bird =&gt; bird !== undefined);

  for (const bird of birds) {
    bird.sing();  // error: &#39;bird&#39; is possibly &#39;undefined&#39;.
  }
}</code></pre>
<p>모든 <code>undefined</code> 값을 목록에서 필터링 했기 때문에 이 코드는 완벽하게 정상이지만 TypeScript가 따라가지 못했다.</p>
<p>TypeScript 5.5에서는 타입 체커가 이 코드를 올바르게 처리한다.</p>
<pre><code class="language-ts">function makeBirdCalls(countries: string[]) {
  // birds: Bird[]
  const birds = countries
    .map(country =&gt; nationalBirds.get(country))
    .filter(bird =&gt; bird !== undefined);

  for (const bird of birds) {
    bird.sing();  // ok!
  }
}</code></pre>
<p><code>birds</code>의 더 정밀한 타입을 주목하자.</p>
<p>이는 TypeScript가 이제 <code>filter</code> 함수에 대해 타입 서술을 추론하기 때문이다. 이를 독립된 함수로 분리하여 더 명확하게 볼 수 있다.</p>
<pre><code class="language-ts">// function isBirdReal(bird: Bird | undefined): bird is Bird
function isBirdReal(bird: Bird | undefined) {
  return bird !== undefined;
}</code></pre>
<p><code>bird is Bird</code>는 type predicate(타입 서술)이다. 즉 함수가 참을 반환하면 <code>Bird</code>가 되고 <code>false</code>를 반환하면 <code>undefined</code>라는 것을 의미한다. <code>Array.prototype.filter</code>는 타입 서술을 알고 있으므로 더 정밀한 타입을 얻을 수 있다.</p>
<p>TypeScript는 다음 조건이 충족되면 함수가 타입 서술을 반환한다고 추론한다</p>
<ol>
<li>명시적 반환 타입 또는 타입 서술 주석이 없음</li>
<li>단일 반환문과 암시적 반환이 없음</li>
<li>매개변수를 변형하지 않음</li>
<li>매개변수의 정제와 관련된 boolean 표현식을 반환함</li>
</ol>
<p>추론된 타입 서술의 몇 가지 예는 다음과 같다.</p>
<pre><code class="language-ts">// const isNumber: (x: unknown) =&gt; x is number
const isNumber = (x: unknown) =&gt; typeof x === &#39;number&#39;;

// const isNonNullish: &lt;T&gt;(x: T) =&gt; x is NonNullable&lt;T&gt;
const isNonNullish = &lt;T,&gt;(x: T) =&gt; x != null;</code></pre>
<p>이전에는 TypeScript가 이러한 함수가 <code>boolean</code>을 반환한다고만 추론했지만, 이제는 <code>x is number</code> 또는 <code>x is NonNullable&lt;T&gt;</code>와 같은 타입 서술을 포함한 시그니처를 추론한다.</p>
<p>타입 서술은 &quot;오직 ~한 경우에만&quot;이라는 의미를 가진다. 함수가 <code>x is T</code>를 반환하면</p>
<ol>
<li>함수가 <code>true</code>를 반환하면 <code>x</code>는 타입 <code>T</code>를 가진다.</li>
<li>함수가 <code>false</code>를 반환하면 <code>x</code>는 타입 <code>T</code>를 가지지 않는다.</li>
</ol>
<p>타입 서술이 추론되지 않는 경우, 두 번째 규칙을 어겼을 가능성이 크다. 이는 종종 &quot;truthiness&quot; 검사에서 발생한다.</p>
<pre><code class="language-ts">function getClassroomAverage(students: string[], allScores: Map&lt;string, number&gt;) {
  const studentScores = students
    .map(student =&gt; allScores.get(student))
    .filter(score =&gt; !!score);

  return studentScores.reduce((a, b) =&gt; a + b) / studentScores.length;
  //     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // error: Object is possibly &#39;undefined&#39;.
}</code></pre>
<p>TypeScript는 <code>score =&gt; !!score</code>에 대해 타입 서술을 추론하지 않는다. 이는 올바른 판단이다. 이 함수가 <code>true</code>를 반환하면 <code>score</code>는 <code>number</code>이지만, <code>false</code>를 반환하면 <code>score</code>는 <code>undefined</code> 또는 숫자(구체적으로 <code>0</code>)일 수 있다. 이는 실제 버그로, 시험에서 0점을 받은 학생의 점수를 필터링하면 평균이 왜곡된다. </p>
<p>따라서, <code>undefined</code> 값을 명시적으로 필터링하는 것이 좋다.</p>
<pre><code class="language-ts">function getClassroomAverage(students: string[], allScores: Map&lt;string, number&gt;) {
  const studentScores = students
    .map(student =&gt; allScores.get(student))
    .filter(score =&gt; score !== undefined);

  return studentScores.reduce((a, b) =&gt; a + b) / studentScores.length;  // ok!
}</code></pre>
<p>참거짓(truthiness) 검사는 모호성이 없는 객체 타입에 대해서는 타입 서술을 추론한다. 함수가 추론된 타입 서술의 후보가 되려면 boolean을 반환해야 한다: <code>x =&gt; !!x</code>는 타입 서술을 추론할 수 있지만 <code>x =&gt; x</code>는 그렇지 못한다.</p>
<p>명시적 타입 서술은 기존과 동일하게 작동하며, TypeScript는 동일한 타입 서술을 추론할지 여부를 확인하지 않는다. 명시적 타입 서술(&quot;is&quot;)은 타입 단언(&quot;as&quot;)과 마찬가지로 안전하지 않다.</p>
<p>이 기능이 더 정밀한 타입을 추론하여 기존 코드를 깨뜨릴 가능성이 있다. 예를 들어</p>
<pre><code class="language-ts">// Previously, nums: (number | null)[]
// Now, nums: number[]
const nums = [1, 2, 3, null, 5].filter(x =&gt; x !== null);

nums.push(null);  // ok in TS 5.4, error in TS 5.5</code></pre>
<p>해결책은 명시적으로 배열에 타입을 사용하는 것이다.</p>
<pre><code class="language-ts">const nums: (number | null)[] = [1, 2, 3, null, 5].filter(x =&gt; x !== null);
nums.push(null);  // ok in all versions</code></pre>
<h2 id="상수-인덱스-접근에-대한-제어-흐름-좁히기">상수 인덱스 접근에 대한 제어 흐름 좁히기</h2>
<p>TypeScript는 이제 <code>obj</code>와 <code>key</code>가 모두 사실상 상수일 때 <code>obj[key]</code> 형태의 표현식을 좁힐 수 있다.</p>
<pre><code class="language-ts">function f1(obj: Record&lt;string, unknown&gt;, key: string) {
    if (typeof obj[key] === &quot;string&quot;) {
        // Now okay, previously was error
        obj[key].toUpperCase();
    }
}</code></pre>
<p>위 예제에서, <code>obj</code>나 <code>key</code>는 변경되지 않으므로 TypeScript는 <code>typeof</code> 검사를 통해 <code>obj[key]</code>의 타입을 <code>string</code>으로 좁힐 수 있다.</p>
<h2 id="jsdoc-import-태그">JSDoc <code>@import</code> 태그</h2>
<p>오늘날 JavaScript 파일에서 타입 검사만을 위해 무언가를 가져오려면 번거롭다. JavaScript 개발자는 런타임에 존재하지 않는 <code>SomeType</code>이라는 이름의 타입을 단순히 import할 수 없다.</p>
<pre><code class="language-ts">// ./some-module.d.ts
export interface SomeType {
    // ...
}

// ./index.js
import { SomeType } from &quot;./some-module&quot;; // ❌ runtime error!

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}</code></pre>
<p><code>SomeType</code>는 런타임에 존재하지 않으므로 import가 실패한다. 대신, 개발자는 네임스페이스 import를 사용할 수 있다.</p>
<pre><code class="language-ts">import * as someModule from &quot;./some-module&quot;;

/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}</code></pre>
<p>그러나 <code>./some-module</code>은 여전히 런타임에 import된다.</p>
<p>이를 피하기 위해, 개발자들은 일반적으로 JSDoc 주석에서 <code>import(...)</code> 타입을 사용해야 했다.</p>
<pre><code class="language-ts">/**
 * @param {import(&quot;./some-module&quot;).SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}</code></pre>
<p>여러 곳에서 동일한 타입을 재사용하려면, <code>typedef</code>를 사용하여 import를 반복하는 것을 피할 수 있다.</p>
<pre><code class="language-ts">/**
 * @typedef {import(&quot;./some-module&quot;).SomeType} SomeType
 */

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}</code></pre>
<p>이 방식은 <code>SomeType</code>의 로컬 사용에는 도움이 되지만, 많은 import에 반복적으로 사용하면 번거로울 수 있다.</p>
<p>그래서 TypeScript는 이제 ECMAScript import와 동일한 문법을 사용하는 새로운 <code>@import</code> 주석 태그를 지원한다.</p>
<pre><code class="language-ts">/** @import { SomeType } from &quot;some-module&quot; */

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}</code></pre>
<p>여기서 명시적 import를 사용했다. 네임스페이스 import로도 작성할 수 있다.</p>
<pre><code class="language-ts">/** @import * as someModule from &quot;some-module&quot; */

/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}</code></pre>
<p>이는 JSDoc 주석일 뿐이므로 런타임 동작에 전혀 영향을 미치지 않는다.</p>
<h2 id="정규-표현식-구문-검사">정규 표현식 구문 검사</h2>
<p>지금까지 TypeScript는 코드에서 대부분의 정규 표현식을 건너뛰었다. 이는 정규 표현식이 확장 가능한 문법을 가지고 있으며, TypeScript가 이전 버전의 JavaScript로 컴파일하는 노력을 기울이지 않았기 때문이다. 그 결과, 정규 표현식에서 많은 일반적인 문제들이 발견되지 못하고 런타임 오류로 이어지거나 조용히 실패하곤 했다.</p>
<p>그러나 이제 TypeScript는 정규 표현식에 대한 기본 구문 검사를 수행한다! 이를 통해 많은 일반적인 오류를 사전에 발견할 수 있게 되었다.</p>
<pre><code class="language-ts">let myRegex = /@robot(\s+(please|immediately)))? do some task/;
//                                            ~
// error!
// Unexpected &#39;)&#39;. Did you mean to escape it with backslash?</code></pre>
<p>이것은 단순한 예제이지만, 이러한 검사는 많은 일반적인 실수를 잡아낼 수 있다. 사실, TypeScript의 검사는 단순한 구문 검사를 넘어선다. 예를 들어, 존재하지 않는 역참조(backreference) 문제를 잡아낼 수 있다.</p>
<pre><code class="language-ts">let myRegex = /@typedef \{import\((.+)\)\.([a-zA-Z_]+)\} \3/u;
//                                                        ~
// error!
// This backreference refers to a group that does not exist.
// There are only 2 capturing groups in this regular expression.</code></pre>
<p>같은 원칙이 명명된 캡처 그룹(named capturing groups)에도 적용된다.</p>
<pre><code class="language-ts">let myRegex = /@typedef \{import\((?&lt;importPath&gt;.+)\)\.(?&lt;importedEntity&gt;[a-zA-Z_]+)\} \k&lt;namedImport&gt;/;
//                                                                                        ~~~~~~~~~~~
// error!
// There is no capturing group named &#39;namedImport&#39; in this regular expression.</code></pre>
<p>TypeScript의 검사는 이제 특정 RegExp 기능이 ECMAScript의 대상 버전보다 최신일 때도 인식한다. 예를 들어, ES5 타겟에서 위와 같이 명명된 캡처 그룹을 사용하면 오류가 발생한다.</p>
<pre><code class="language-ts">let myRegex = /@typedef \{import\((?&lt;importPath&gt;.+)\)\.(?&lt;importedEntity&gt;[a-zA-Z_]+)\} \k&lt;importedEntity&gt;/;
//                                  ~~~~~~~~~~~~         ~~~~~~~~~~~~~~~~
// error!
// Named capturing groups are only available when targeting &#39;ES2018&#39; or later.</code></pre>
<p>특정 정규 표현식 플래그도 마찬가지이다.</p>
<p>TypeScript의 정규 표현식 지원은 정규 표현식 리터럴로 제한된다. 문자열 리터럴을 사용하여 new RegExp를 호출하면 TypeScript는 제공된 문자열을 검사하지 않는다.</p>
<h2 id="새로운-ecmascript-set-메서드-지원">새로운 ECMAScript Set 메서드 지원</h2>
<p>TypeScript 5.5는 ECMAScript <code>Set</code> 타입에 대한 새로운 제안된 메서드를 선언한다.</p>
<p>이러한 메서드 중 일부는 <code>union</code>, <code>intersection</code>, <code>difference</code>, <code>symmetricDifference</code>로, 다른 <code>Set</code>을 받아 새로운 <code>Set</code>을 반환한다. 다른 메서드인 <code>isSubsetOf</code>, <code>isSupersetOf</code>, <code>isDisjointFrom</code>은 다른 <code>Set</code>을 받아 <code>boolean</code>을 반환한다. 이들 메서드는 원래의 <code>Set</code>을 변경하지 않는다.</p>
<p>다음은 이러한 메서드를 사용하는 예제이다.</p>
<pre><code class="language-ts">let fruits = new Set([&quot;apples&quot;, &quot;bananas&quot;, &quot;pears&quot;, &quot;oranges&quot;]);
let applesAndBananas = new Set([&quot;apples&quot;, &quot;bananas&quot;]);
let applesAndOranges = new Set([&quot;apples&quot;, &quot;oranges&quot;]);
let oranges = new Set([&quot;oranges&quot;]);
let emptySet = new Set();

////
// union
////

// Set(4) {&#39;apples&#39;, &#39;bananas&#39;, &#39;pears&#39;, &#39;oranges&#39;}
console.log(fruits.union(oranges));

// Set(3) {&#39;apples&#39;, &#39;bananas&#39;, &#39;oranges&#39;}
console.log(applesAndBananas.union(oranges));

////
// intersection
////

// Set(2) {&#39;apples&#39;, &#39;bananas&#39;}
console.log(fruits.intersection(applesAndBananas));

// Set(0) {}
console.log(applesAndBananas.intersection(oranges));

// Set(1) {&#39;apples&#39;}
console.log(applesAndBananas.intersection(applesAndOranges));

////
// difference
////

// Set(3) {&#39;apples&#39;, &#39;bananas&#39;, &#39;pears&#39;}
console.log(fruits.difference(oranges));

// Set(2) {&#39;pears&#39;, &#39;oranges&#39;}
console.log(fruits.difference(applesAndBananas));

// Set(1) {&#39;bananas&#39;}
console.log(applesAndBananas.difference(applesAndOranges));

////
// symmetricDifference
////

// Set(2) {&#39;bananas&#39;, &#39;oranges&#39;}
console.log(applesAndBananas.symmetricDifference(applesAndOranges)); // no apples

////
// isDisjointFrom
////

// true
console.log(applesAndBananas.isDisjointFrom(oranges));

// false
console.log(applesAndBananas.isDisjointFrom(applesAndOranges));

// true
console.log(fruits.isDisjointFrom(emptySet));

// true
console.log(emptySet.isDisjointFrom(emptySet));

////
// isSubsetOf
////

// true
console.log(applesAndBananas.isSubsetOf(fruits));

// false
console.log(fruits.isSubsetOf(applesAndBananas));

// false
console.log(applesAndBananas.isSubsetOf(oranges));

// true
console.log(fruits.isSubsetOf(fruits));

// true
console.log(emptySet.isSubsetOf(fruits));

////
// isSupersetOf
////

// true
console.log(fruits.isSupersetOf(applesAndBananas));

// false
console.log(applesAndBananas.isSupersetOf(fruits));

// false
console.log(applesAndBananas.isSupersetOf(oranges));

// true
console.log(fruits.isSupersetOf(fruits));

// false
console.log(emptySet.isSupersetOf(fruits));</code></pre>
<h2 id="격리된-선언">격리된 선언</h2>
<p>선언 파일(<code>.d.ts</code> 파일)은 기존 라이브러리와 모듈의 구조를 TypeScript에 설명한다. 이 파일들은 라이브러리의 타입 시그니처를 포함하지만 함수 본문과 같은 구현 세부 사항은 제외된다. 선언 파일을 직접 작성할 수 있지만, TypeScript가 <code>--declaration</code> 옵션을 사용하여 소스 파일에서 자동으로 생성하게 하는 것이 더 안전하고 간단하다.</p>
<p>TypeScript 컴파일러와 API는 항상 선언 파일을 생성하는 역할을 해왔지만, 다른 도구를 사용하거나 기존 빌드 프로세스가 확장되지 않는 경우도 있다.</p>
<h3 id="유즈-케이스-더-빠른-선언-파일-생성-도구">유즈 케이스: 더 빠른 선언 파일 생성 도구</h3>
<p>더 빠른 선언 파일 생성 도구를 만들고 싶다면, 특히 출판 서비스나 새로운 번들러의 일부로서 고려할 수 있다. TypeScript를 JavaScript로 변환하는 빠른 도구들은 많지만, TypeScript를 선언 파일로 변환하는 도구는 그렇지 않다. 그 이유는 TypeScript의 추론 기능을 사용하면 명시적으로 타입을 선언하지 않고도 코드를 작성할 수 있게 하기 때문이다.</p>
<p>예를 들어, 두 개의 가져온 변수를 더하는 간단한 함수를 고려해 보자.</p>
<pre><code class="language-ts">// util.ts
export let one = &quot;1&quot;;
export let two = &quot;2&quot;;

// add.ts
import { one, two } from &quot;./util&quot;;
export function add() { return one + two; }</code></pre>
<p>우리가 단지 <code>add.d.ts</code> 파일을 생성하려고 하더라도, TypeScript는 다른 가져온 파일(<code>util.ts</code>)로 들어가서 <code>one</code>과 <code>two</code>의 타입이 문자열임을 추론하고, 두 문자열에 대한 <code>+</code> 연산자가 <code>string</code> 반환 타입을 가진다는 것을 계산해야 한다.</p>
<pre><code class="language-ts">// add.d.ts
export declare function add(): string;</code></pre>
<p>이러한 추론은 개발자 경험에 중요하지만, 선언 파일을 생성하려는 도구는 타입 체커의 일부를 복제하고, 추론 및 모듈 지정자를 해결하여 import를 따라가야 한다.</p>
<h3 id="유즈-케이스-병렬-선언-파일-생성-및-병렬-검사">유즈 케이스: 병렬 선언 파일 생성 및 병렬 검사</h3>
<p>여러 프로젝트가 있는 모노레포와 다중 코어 CPU가 있다면, 각 프로젝트를 다른 코어에서 동시에 검사할 수 있다면 좋을 것이다. 하지만 의존성 순서대로 프로젝트를 빌드해야 한다. 예를 들어, <code>backend와</code> <code>frontend</code>가 <code>core</code>에 의존할 경우, <code>core</code>가 빌드되어 선언 파일이 생성될 때까지 <code>frontend</code>나 <code>backend</code>를 검사할 수 없다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/038c0704-8872-4d04-bd76-e5f6269de007/image.png" alt=""></p>
<p>위 그래프에서 병목 현상이 발생하는 것을 볼 수 있다. <code>frontend</code>와 <code>backend</code>를 병렬로 빌드할 수 있지만, 먼저 <code>core</code>가 빌드 완료될 때까지 기다려야 한다.</p>
<p>이 문제를 개선하려면 어떻게 해야 할까? 빠른 도구가 <code>core</code>의 선언 파일을 병렬로 생성할 수 있다면, TypeScript는 이를 통해 <code>core</code>, <code>frontend</code>, <code>backend</code>를 병렬로 타입 검사할 수 있다.</p>
<h3 id="해결책-명시적-타입">해결책: 명시적 타입!</h3>
<p>두 가지 사용 사례의 공통 요구 사항은 선언 파일을 생성하기 위한 파일 간 타입 검사기가 필요하다는 것이다. 도구 커뮤니티에게 많은 것을 요구하는 셈이다.</p>
<p>복잡한 예로, 다음 코드를 위한 선언 파일이 필요하다면...</p>
<pre><code class="language-ts">import { add } from &quot;./add&quot;;

const x = add();

export function foo() {
    return x;
}
</code></pre>
<p><code>foo</code>의 시그니처를 생성해야 한다. 이를 위해 <code>foo</code>의 구현을 봐야 하고, <code>foo</code>는 <code>x</code>를 반환하므로 <code>x</code>의 타입을 얻기 위해 <code>add</code>의 구현을 살펴봐야 한다. 이는 <code>add</code>의 의존성까지 살펴봐야 할 수 있다. 이는 선언 파일을 생성하려면 다양한 위치의 타입을 알아내기 위한 많은 논리가 필요하다는 것을 의미한다.</p>
<p>그러나 빠른 반복 시간과 완전한 병렬 빌드를 원하는 개발자들에게는 다른 접근 방식이 있다. 선언 파일은 모듈의 공개 API 타입만 필요하다. 개발자가 내보내는 항목의 타입을 명시적으로 작성하면, 도구는 모듈의 구현을 보지 않고 선언 파일을 생성할 수 있다. 이는 전체 타입 검사기를 재구현하지 않아도 된다.</p>
<p>이때 새로운 <code>--isolatedDeclarations</code> 옵션이 필요하다. 이 옵션은 타입 검사기 없이 모듈을 신뢰할 수 있게 변환할 수 없을 때 오류를 보고한다. 즉, 내보내는 항목이 충분히 주석 처리되지 않은 파일이 있는 경우 TypeScript가 오류를 보고한다.</p>
<p>위 예제에서는 다음과 같은 오류가 발생할 수 있다.</p>
<pre><code class="language-ts">export function foo() {
//              ~~~
// error! Function must have an explicit
// return type annotation with --isolatedDeclarations.
    return x;
}
</code></pre>
<h3 id="왜-오류가-바람직한가요">왜 오류가 바람직한가요?</h3>
<p>오류는 TypeScript가 다음을 가능하게 하기 때문이다</p>
<ol>
<li>다른 도구가 선언 파일을 생성하는 데 문제가 있는지 미리 알려준다.</li>
<li>누락된 주석을 추가하는 데 도움을 주는 빠른 수정을 제공한다.</li>
</ol>
<p>이 모드는 모든 곳에 주석을 요구하지 않는다. 공개 API에 영향을 미치지 않는 로컬 변수에 대해서는 주석이 없어도 된다. 예를 들어, 다음 코드는 오류를 발생시키지 않는다.</p>
<pre><code class="language-ts">import { add } from &quot;./add&quot;;

const x = add(&quot;1&quot;, &quot;2&quot;); // no error on &#39;x&#39;, it&#39;s not exported.

export function foo(): string {
    return x;
}</code></pre>
<p>계산하기에 &#39;사소한&#39; 유형인 특정 표현식도 있다.</p>
<pre><code class="language-ts">// No error on &#39;x&#39;.
// It&#39;s trivial to calculate the type is &#39;number&#39;
export let x = 10;

// No error on &#39;y&#39;.
// We can get the type from the return expression.
export function y() {
    return 20;
}

// No error on &#39;z&#39;.
// The type assertion makes it clear what the type is.
export function z() {
    return Math.max(x, y()) as number;
}</code></pre>
<h3 id="격리된-선언-사용">격리된 선언 사용</h3>
<p><code>isolatedDeclarations</code>는 <code>declaration</code> 또는 <code>composite</code> 플래그가 설정되어 있어야 한다.</p>
<p><code>isolatedDeclarations</code>는 TypeScript의 emit 방식을 변경하지 않고, 오류 보고 방식만 변경한다. 이 기능은 아직 초기 단계에 있으며, 클래스와 객체 리터럴의 계산된 속성 선언과 같은 일부 시나리오는 지원되지 않는다.</p>
<p>이 기능을 도입할 때는 각 경우를 신중하게 고려해야 한다. 일부 개발자 경험이 손실될 수 있지만, 병렬 빌드 전략의 최적화 기회를 제공한다.</p>
<h2 id="configdir-템플릿-변수-for-설정-파일"><code>${configDir}</code> 템플릿 변수 for 설정 파일</h2>
<p>많은 코드베이스에서 다른 구성 파일의 &quot;베이스&quot; 역할을 하는 공유 <code>tsconfig.json</code> 파일을 재사용하는 것이 일반적이다. 이 작업은 <code>tsconfig.json</code> 파일의 <code>extends</code> 필드를 사용하여 수행한다.</p>
<pre><code class="language-json">{
    &quot;extends&quot;: &quot;../../tsconfig.base.json&quot;,
    &quot;compilerOptions&quot;: {
        &quot;outDir&quot;: &quot;./dist&quot;
    }
}</code></pre>
<p>이 문제 중 하나는 <code>tsconfig.json</code> 파일의 모든 경로가 파일 자체의 위치에 상대적이라는 것이다. 즉, 여러 프로젝트에서 사용하는 공유 <code>tsconfig.base.json</code> 파일이 있는 경우 파생된 프로젝트에서 상대 경로가 유용하지 않은 경우가 많다. 예를 들어 다음 <code>tsconfig.base.json</code>을 상상해 보자.</p>
<pre><code class="language-json">{
    &quot;compilerOptions&quot;: {
        &quot;typeRoots&quot;: [
            &quot;./node_modules/@types&quot;
            &quot;./custom-types&quot;
        ],
        &quot;outDir&quot;: &quot;dist&quot;
    }
}</code></pre>
<p>만약 작성자가 모든 <code>tsconfig.json</code> 파일이 이 파일을 확장하여 다음과 같은 설정을 의도했다면</p>
<ol>
<li>파생된 <code>tsconfig.json</code>에 상대적인 <code>dist</code> 디렉토리에 출력.</li>
<li>파생된 <code>tsconfig.json</code>에 상대적인 <code>custom-types</code> 디렉토리 사용.</li>
</ol>
<p>위의 방법은 작동하지 않을 것이다. <code>typeRoots</code> 경로는 공유된 <code>tsconfig.base.json</code> 파일의 위치를 기준으로 하기 때문에, 각 프로젝트는 동일한 <code>outDir</code>과 <code>typeRoots</code>를 선언해야 한다. </p>
<p>이를 해결하기 위해 TypeScript 5.5는 새로운 템플릿 변수 <code>${configDir}</code>을 도입했다. <code>tsconfig.json</code> 또는 <code>jsconfig.json</code> 파일의 특정 경로 필드에 <code>${configDir}</code>을 작성하면 이 변수는 지정된 컴파일에서 구성 파일의 포함 디렉터리로 대체된다. 즉, 위의 <code>tsconfig.base.json</code>을 다음과 같이 다시 작성할 수 있다.</p>
<pre><code class="language-json">{
    &quot;compilerOptions&quot;: {
        &quot;typeRoots&quot;: [
            &quot;${configDir}/node_modules/@types&quot;
            &quot;${configDir}/custom-types&quot;
        ],
        &quot;outDir&quot;: &quot;${configDir}/dist&quot;
    }
}</code></pre>
<p>이제 프로젝트에서 이 파일을 확장할 때 경로는 공유된 <code>tsconfig.base.json</code> 파일이 아니라 파생된 <code>tsconfig.json</code>에 상대적인 경로가 된다. 이렇게 하면 프로젝트 간에 구성 파일을 공유하기가 더 쉬워지고 구성 파일의 이식성이 향상된다.</p>
<p><code>tsconfig.json</code> 파일을 확장할 수 있게 만들려면 <code>./</code> 대신 <code>${configDir}</code>로 작성해야 하는지 고려하자.</p>
<h2 id="마무리">마무리</h2>
<p>이번에 변경된 버전을 보면 위 변경 사항 말고도 성능적으로 변경된 사항이 많다. 추가로 도움 될만한 정보들이 있어서 더 자세하게 알고 싶으신 분들은 원글 링크로 들어가 확인하면 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[velog to Gatsby 블로그 이전기]]></title>
            <link>https://velog.io/@hustle-dev/velog-to-Gatsby-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9D%B4%EC%A0%84%EA%B8%B0</link>
            <guid>https://velog.io/@hustle-dev/velog-to-Gatsby-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%9D%B4%EC%A0%84%EA%B8%B0</guid>
            <pubDate>Sun, 09 Apr 2023 02:21:55 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>새로운 블로그 주소: <a href="https://hustle-dev.github.io/">https://hustle-dev.github.io/</a></p>
</blockquote>
<h2 id="velog에서의-추억">velog에서의 추억</h2>
<p>velog를 처음에 어떻게 접하게된 것인지는 기억이 잘 나진 않지만, 첫 글이 <a href="https://velog.io/@hustle-dev/C-Chapter-01-C%EC%96%B8%EC%96%B4-%EA%B8%B0%EB%B0%98%EC%9D%98-C">2020년 11월에 적은 C++ 관련 글</a>인 것으로 보아 막 컴퓨터공학을 복수전공하면서 공부한 내용을 이곳에 적은것 같다.</p>
<p>약 3년이 안된 시간동안 학교도 졸업하고, 부트캠프, 취업까지.. 얼마 안되는 기간동안 이렇게나 많은 일들이 있었다니 다시 되돌아보니 너무 신기했다. 또한 이 기간동안 <code>velog</code>에서 약 250개 정도의 글을 작성하면서 트렌딩에도 몇번 올라가보기도 하고, 많은 분들이 작성한 글을 좋아해주셔서 정말 좋은 경험을 한 것 같다.</p>
<blockquote>
<p>이렇게 적으니 다시는 <code>velog</code>에 안올 것처럼 보이는데 앞으로도 다른 기술글들을 보기 위해 <code>velog</code>를 자주 방문하지 않을까 싶다.</p>
</blockquote>
<h2 id="블로그를-만들게된-계기">블로그를 만들게된 계기</h2>
<p>블로그를 만들게 된 이유는 딱히 <code>velog</code>가 맘에 안들어서는 아니다. 트렌딩이라는 공간을 통해 다른사람들이 작성한 좋은 글을 볼 수 있고, 댓글과 좋아요를 통해 상호작용 할 수 있어서 좋았다.</p>
<p>전에 취준하면서 react나 next와 같은 best-practice 코드를 보려면 어떻게 해야하는지 궁금했다. 그러던 중 Numble의 다른 색깔 찾기 게임 제작 챌린지에서 코드리뷰를 받을 때 리뷰어분이 이 <a href="https://github.com/leerob/leerob.io">leerob.io</a> 사이트를 추천해주셨다.</p>
<p>이 분도 자신의 블로그를 만들면서 최신 기술을 블로그에 사용하는 것을 볼 수 있는데 나도 나중에 나만의 블로그를 만들어서 각종 기술들을 적용시켜보고 싶다는 생각이 들었다.</p>
<blockquote>
<p>이 이유말고도 아래와 같은 이유도 있었다.</p>
</blockquote>
<ul>
<li>블로그를 만드는데 고려해야하는 것들이 어떤 것들인지 알고 싶어서</li>
<li>Gatsby와 GraphQL을 사용해보고 싶어서</li>
<li>최근 사내에서 <a href="https://css-for-js.dev/">css-for-js</a> 스터디를 하는데 공부한 것들을 적용해보고 싶어서</li>
<li>google analytics를 붙여서 블로그에 얼마나 들어오는지 확인하고 싶어서</li>
<li>광고 같은 것을 붙여서 수익화 같이 나중에 커스터마이징을 하고 싶어서</li>
</ul>
<h2 id="블로그-만들기">블로그 만들기</h2>
<p>처음에 블로그를 만들 때, <code>Astro</code>로 만들지 <code>Gatsby</code>로 만들지 고민했다. Gatsby는 이미 plugin 생태계가 풍부했고 블로그를 만들기에 최적화 되어있다. Astro는 나온지 얼마 되지 않았지만 island architecture와 같이 필요한 부분에만 hydration을 하고, 빌드 속도가 빠르다는 장점이 있어서 이것도 한번 써보면 재미있을 것 같다고 생각했다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/6380dc27-dbe2-420d-a6cf-4557c1d311eb/image.png" alt=""></p>
<blockquote>
<p>npm trend로 보았을 땐, 아직 <code>Gatsby</code>가 앞서있지만 점점 <code>Astro</code>가 올라올 것 같다는 생각이 든다.</p>
</blockquote>
<p>고민끝에 사용자 경험 측면에는 아직까진 <code>Gatsby</code>가 더 나은것 같아서 <code>Gatsby</code> 프레임워크를 선택했다.</p>
<h3 id="고려해야-할-것">고려해야 할 것</h3>
<p>가장 간단하게 블로그를 만든다고 하더라도 아래와 같은 고민이 필요했다.</p>
<ul>
<li>markdown을 어떻게 파싱해서 화면에 보여줄 것인지?</li>
<li>댓글 시스템을 어떻게 만들지?</li>
<li>블로그에 작성한 글을 어떻게 많은 사람들에게 보여줄 수 있을 지?</li>
</ul>
<blockquote>
<p>이 모든 고민들은 gatsby의 plugin과 라이브러리를 통해 해결할 수 있었다.</p>
</blockquote>
<h3 id="gatsby를-사용하면서-좋았던-점">Gatsby를 사용하면서 좋았던 점</h3>
<p>위에선 가장 중요한 기능들을 언급했지만 실제로 블로그를 만들기 위해 필요한 기능들이 많다.</p>
<ul>
<li>gtag</li>
<li>sitemap</li>
<li>robots.txt</li>
<li>markdown parsing</li>
<li>toc</li>
<li>rss feed</li>
</ul>
<p>등등..</p>
<p>하지만 대부분의 것들이 gatsby plugin으로 존재하고 있고, 필요한 데이터들은 GraphQL이라는 데이터 계층을 통해 데이터를 가공해서 만들어 가져올 수 있어서 편했다.</p>
<p>또한 Gatsby에서 자체적으로 제공하는 Built-in API들을 사용함으로써 이미지처리를 쉽게 할 수 있었고, 사용자 경험을 좋게 만들 수 있었다.</p>
<h3 id="gatsby를-사용하면서-불편했던-점">Gatsby를 사용하면서 불편했던 점</h3>
<p>내가 Gatsby를 잘못다룬 것일 수도 있지만, TypeScript를 함께 사용하였는데 <code>GraphQL</code>로 값을 가져올때, 특정 필드마다 <code>null</code>일 수 있어서 <code>createSchemaCustomization</code>를 사용하여 non-null 값을 지정해주는 이런 과정들이 필요했고, 몇몇 필드를 사용할 땐 optional 혹은 null 단언을 해주어야해서 불편했다.</p>
<h2 id="앞으로-할일">앞으로 할일</h2>
<p>배포를 했지만 앞으로 해야할 작업들이 많이 남아있다.</p>
<h3 id="issue-to-markdown-parsing-system">Issue to Markdown Parsing system</h3>
<p>velog에 있던 글을 그대로 옮기다보니까 현재 마크다운의 글을 보면 아래와 같이 이미지를 가져올 때, velog에서 사용하는 CDN을 이용해 가져온다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/4792f16b-c880-412c-a0d7-2b22d261656c/image.png" alt=""></p>
<p>또한 VSCode에서도 마크다운에 이미지를 바로 등록하려고하면 되지 않아서 손수 이미지파일을 폴더로 복사하고 경로를 맞춰주어야 하는데 너무 번거롭기 때문에 github의 Issue를 CMS로 하여 이슈로 글을 작성하면 Workflow를 통해 자동으로 <code>content/${글제목}</code>에 image와 md파일을 만들어 배포되도록 할 예정이다.</p>
<h3 id="giscus-테마작업">giscus 테마작업</h3>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/5608f1ee-005f-4778-8c34-0740ab343938/image.png" alt=""></p>
<p>현재 댓글 시스템으로 giscus를 사용하고 있는데 테마중 그나마 다크모드와 라이트모드에 가장 어울리는 transparent를 사용하고 있는데 가독성이 좋지 않아서 커스텀 테마를 만들 예정이다.</p>
<h3 id="그-외">그 외</h3>
<p>그외로 post 페이지에 prev, next가 들어가는 기능들을 넣고 스타일링을 해보고 싶고, tailwind CSS를 활용하여 마이그레이션을 하면서 이 기술을 배워보고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 18에서 고쳐진 버그 (feat. Dan의 답변)]]></title>
            <link>https://velog.io/@hustle-dev/React-18%EC%97%90%EC%84%9C-%EA%B3%A0%EC%B3%90%EC%A7%84-%EB%B2%84%EA%B7%B8-feat.-Dan%EC%9D%98-%EB%8B%B5%EB%B3%80</link>
            <guid>https://velog.io/@hustle-dev/React-18%EC%97%90%EC%84%9C-%EA%B3%A0%EC%B3%90%EC%A7%84-%EB%B2%84%EA%B7%B8-feat.-Dan%EC%9D%98-%EB%8B%B5%EB%B3%80</guid>
            <pubDate>Wed, 22 Mar 2023 13:13:58 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>아래와 같은 react 18 버전 코드가 있다.</p>
<pre><code class="language-tsx">import { MouseEvent, useEffect, useState } from &quot;react&quot;;
import type { ReactNode } from &quot;react&quot;;

type DropdownProps = {
  target: HTMLElement | null;
  children: ReactNode;
};

function Dropdown({ target, children }: DropdownProps) {
  useEffect(() =&gt; {
    if (!target) return;

    const listener = () =&gt; {
      console.log(&quot;listener executed&quot;);
    };

    document.addEventListener(&quot;click&quot;, listener);
    return () =&gt; document.removeEventListener(&quot;click&quot;, listener);
  }, [target]);

  if (!target) {
    return null;
  }

  return &lt;div&gt;{children}&lt;/div&gt;;
}

function App() {
  const [target, setTarget] = useState&lt;HTMLButtonElement | null&gt;(null);

  return (
    &lt;div&gt;
      &lt;button
        onClick={(e: MouseEvent&lt;HTMLButtonElement&gt;) =&gt;
          setTarget(e.currentTarget)
        }
      &gt;
        Button
      &lt;/button&gt;
      &lt;Dropdown target={target}&gt;dropdown test&lt;/Dropdown&gt;
    &lt;/div&gt;
  );
}

export default App;
</code></pre>
<p>코드 샌드박스 링크: <a href="https://codesandbox.io/s/sad-firefly-sphup6?file=/src/App.tsx">https://codesandbox.io/s/sad-firefly-sphup6?file=/src/App.tsx</a></p>
<blockquote>
<p>이 코드에 대해서 간략하게 설명하면, <code>target</code>이 <code>falsy</code> 값일 경우에는 <code>Dropdown</code> 컴포넌트의 listener가 실행되고 있지 않다가 <code>target</code>이 <code>falsy</code> 값이 아니게 되면, 이벤트 리스너가 부착되는 코드이다. </p>
</blockquote>
<p>이 코드의 동작방식을 예측하면, <strong>당연히 처음 버튼을 눌렀을 때, <code>target</code>의 값이 초기화되면서, <code>document</code>에 listener가 부착되고, 그 이후에 버튼을 다시 눌렀을 때 listener가 실행되는 것을 예상</strong>했다. 그러나 실제 동작방식은 아래의 화면과 같았다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/e26326ce-97c4-469f-8212-b7459e60f081/image.gif" alt=""></p>
<blockquote>
<p><strong>버튼을 누르자마자 listener가 실행</strong>된것이다. </p>
</blockquote>
<p>react 17에선 같은 코드가 이렇게 동작하지 않고, react 18의 <code>createRoot</code>를 사용할 때, 이런 일이 발생하였는데 왜 이런일이 발생한 것일까? </p>
<h2 id="dan-abramov의-답변">Dan abramov의 답변</h2>
<p>계속 고민하다가 왜 이렇게 동작하는 지 이해가 안가서 다음과 같이 <a href="https://github.com/facebook/react">facebook/react</a> 레포에 <a href="https://github.com/facebook/react/issues/26090">Issue</a>를 올렸다.</p>
<p>우선 결론부터 말하면 저 동작은 <strong>의도된 동작</strong>이라는 것이다.</p>
<blockquote>
<p>처음에 이 답변을 듣고, 내가 React를 지금까지 잘못 알고 있었나 생각이 들었다.</p>
</blockquote>
<p>위 이슈에 달린 Dan의 답변을 토대로 정리해보면 원래 React17에서는 이러한 동작이 <strong>일관성 없게 동작</strong>을 하였다고 한다. 즉, 저렇게 작성을 하여도 어떤 경우에는 <strong>이벤트가 부착만 되고 이벤트 리스너가 실행되지 않는 경우가 있었고</strong>, <strong>이벤트가 동기식으로 처리되어 이벤트 리스너가 실행되는 경우도 있었다</strong>고 한다.</p>
<p>아래 예시 코드샌드박스를 보면 확인할 수 있다.</p>
<ul>
<li>React 17에서 이벤트가 동기식으로 처리되는 경우(portal과 함께 사용)
<a href="https://codesandbox.io/s/runtime-glitter-256mit?file=/src/App.js">https://codesandbox.io/s/runtime-glitter-256mit?file=/src/App.js</a></li>
</ul>
<ul>
<li>React 17에서 이벤트가 동기식으로 처리되지 않는 경우
<a href="https://codesandbox.io/s/little-cherry-hbvwik?file=/src/App.js">https://codesandbox.io/s/little-cherry-hbvwik?file=/src/App.js</a></li>
</ul>
<blockquote>
<p>즉, 정리해보면 처음 문제의 코드에서 원래 이벤트가 부착이 되고 <code>listener</code>가 실행되는 동작이 정상적인 동작이며 17에선 버그였다고 한다. 그림으로 그려보면 아래와 같다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/c0f02420-0f8c-4ae0-9f01-fe9e0c7f7801/image.png" alt=""></p>
<blockquote>
<p>18에서는 이러한 동작이 일관되어 항상 이벤트를 포착하고, 클릭/입력과 같은 의도적인 사용자 이벤트로 인한 렌더링 효과는 항상 동기적으로 처리 된다고 한다.</p>
</blockquote>
<h2 id="처음-의도한-대로-코드를-실행시키려면">처음 의도한 대로 코드를 실행시키려면?</h2>
<p>우선 Dan이 댓글에 언급한 대로, <code>setTimeout</code> 방법이 있다.</p>
<h3 id="settimeout">setTimeout</h3>
<p>이벤트의 부착하는 시점을 <code>setTimeout</code>으로 감싼다.</p>
<pre><code class="language-tsx">    setTimeout(() =&gt; {
      document.addEventListener(&quot;click&quot;, listener);
    }, 0);</code></pre>
<p>추후 더 고민해보니 아래와 같은 방법으로도 해결할 수 있었다.</p>
<h3 id="usedeferredvalue">useDeferredValue</h3>
<p>기존 코드의 <code>target</code>을 사용하는 부분을 <code>useDeferredValue</code>로 감싼 값으로 사용한다.</p>
<pre><code class="language-tsx">const deferredTarget = useDeferredValue(target);</code></pre>
<h3 id="starttransition">startTransition</h3>
<p><code>setState</code>하는 부분의 코드를 <code>startTransition</code>으로 감싼다.</p>
<pre><code class="language-tsx">      &lt;button
        onClick={(e: MouseEvent&lt;HTMLButtonElement&gt;) =&gt; {
          startTransition(() =&gt; {
            setTarget(e.currentTarget);
          });
        }}
      &gt;</code></pre>
<h2 id="참고자료">참고자료</h2>
<ul>
<li><a href="https://github.com/facebook/react/issues/26090">https://github.com/facebook/react/issues/26090</a></li>
<li><a href="https://github.com/facebook/react/issues/24657">https://github.com/facebook/react/issues/24657</a></li>
<li><a href="https://github.com/facebook/react/issues/20074">https://github.com/facebook/react/issues/20074</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 5.0 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-5.0-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-5.0-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Sun, 19 Mar 2023 05:50:52 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/">https://devblogs.microsoft.com/typescript/announcing-typescript-5-0/</a></p>
</blockquote>
<h2 id="데코레이터">데코레이터</h2>
<p>데코레이터는 <strong>재사용 가능한 방식으로 클래스와 그 멤버를 사용자 정의</strong>하는 ECMAScript의 기능이다.</p>
<p>다음 코드를 살펴보자.</p>
<pre><code class="language-tsx">class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person(&quot;Ron&quot;);
p.greet();</code></pre>
<p><code>greet</code> 함수가 여기서는 간단하지만, 비동기 로직을 수행하거나 재귀적으로 호출되거나 사이드 이펙트가 있는 등 훨씬 더 복잡하다고 상상해보자. 어떤 복잡한 로직이라도, <code>greet</code> 함수를 디버깅하기 위해 <code>console.log</code> 호출을 추가한다고 가정해 보자.</p>
<pre><code class="language-tsx">class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    greet() {
        console.log(&quot;LOG: Entering method.&quot;);

        console.log(`Hello, my name is ${this.name}.`);

        console.log(&quot;LOG: Exiting method.&quot;)
    }
}</code></pre>
<p>이 패턴은 매우 일반적이다. 모든 메서드에 대해 이 작업을 수행할 수 있는 방법이 있다면 정말 좋을 것 같다.</p>
<p>이때 <strong>데코레이터</strong>를 활용할 수 있다. 다음과 같은 <code>loggedMethod</code>라는 함수를 작성할 수 있다.</p>
<pre><code class="language-tsx">function loggedMethod(originalMethod: any, _context: any) {

    function replacementMethod(this: any, ...args: any[]) {
        console.log(&quot;LOG: Entering method.&quot;)
        const result = originalMethod.call(this, ...args);
        console.log(&quot;LOG: Exiting method.&quot;)
        return result;
    }

    return replacementMethod;
}</code></pre>
<p>이 코드를 보고 아래처럼 생각할 수 있다.</p>
<blockquote>
<p>&quot;any의 사용량이 왜 이리 많아요? TypeScript가 아니라 anyScript인가요?&quot;</p>
</blockquote>
<p><code>loggedMethod</code>는 원래 메서드(originalMethod)를 가져와서 다음과 같은 함수를 반환한다.</p>
<ul>
<li>&quot;Entering...&quot; 메시지를 기록한다.</li>
<li>this 및 모든 인수를 원래 메서드에 전달한다.</li>
<li>&quot;Exiting...&quot; 메시지를 기록하고,</li>
<li>원래 메서드가 반환한 값을 반환한다.</li>
</ul>
<p>이제 <code>loggedMethod</code>를 사용하여 <code>greet</code> 메소드를 장식 할 수 있다.</p>
<pre><code class="language-tsx">class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person(&quot;Ron&quot;);
p.greet();

// Output:
//
//   LOG: Entering method.
//   Hello, my name is Ron.
//   LOG: Exiting method.</code></pre>
<p>우리는 <code>loggedMethod</code>를 <code>greet</code> 메서드에 대한 데코레이터로 사용하였다. <code>@loggedMethod</code>로 작성되었는데 이것은 <code>loggedMethod</code>가 대상 메서드에 대해 호출되고 컨텍스트 객체가 전달되었기 때문이다. <code>loggedMethod</code>가 새로운 함수를 반환했기 때문에 해당 함수가 원래 정의되어 있던 <code>greet</code>를 대체하게 된다.</p>
<p>아직 언급하지 않았지만, <code>loggedMethod</code>는 두 번째 매개변수를 갖고 있다. 이것은 &quot;컨텍스트 객체&quot;라고 불리며 데코레이트된 메서드가 선언된 방식에 대한 몇 가지 유용한 정보를 가지고 있다. 예를 들어, 해당 메서드가 <code>#private</code> 멤버인지, <code>static</code>인지, 또는 메서드의 이름이 무엇인지를 알 수 있다. 이를 활용해 <code>loggedMethod</code>를 다시 작성하고 데코레이트된 메서드의 이름을 출력해보자.</p>
<pre><code class="language-tsx">function loggedMethod(originalMethod: any, context: ClassMethodDecoratorContext) {
    const methodName = String(context.name);

    function replacementMethod(this: any, ...args: any[]) {
        console.log(`LOG: Entering method &#39;${methodName}&#39;.`)
        const result = originalMethod.call(this, ...args);
        console.log(`LOG: Exiting method &#39;${methodName}&#39;.`)
        return result;
    }

    return replacementMethod;
}</code></pre>
<p>이제 우리는 컨텍스트 매개변수를 사용하고 있다. 이것은 <code>loggedMethod</code>에서 <code>any</code>와 <code>any[]</code>보다 엄격한 타입을 가진다. TypeScript는 메서드 데코레이터가 사용하는 컨텍스트 개체를 모델링하는 <code>ClassMethodDecoratorContext</code>라는 유형을 제공한다.</p>
<p>메타데이터 이외에도 메서드의 컨텍스트 개체에는 <code>addInitializer</code>라는 유용한 함수가 있다. 이것은 생성자의 시작 부분(또는 정적인 경우 클래스 자체의 초기화)에 연결할 수 있는 방법이다.</p>
<p>예를 들어, 자바스크립트에서는 다음과 같은 패턴을 자주 사용한다.</p>
<pre><code class="language-tsx">class Person {
    name: string;
    constructor(name: string) {
        this.name = name;

        this.greet = this.greet.bind(this);
    }

    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}</code></pre>
<p>대안으로 <code>greet</code>는 화살표 함수로 초기화된 속성으로 선언될 수도 있다.</p>
<pre><code class="language-tsx">class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    greet = () =&gt; {
        console.log(`Hello, my name is ${this.name}.`);
    };
}</code></pre>
<p>이 코드는 <code>greet</code>가 독립적인 함수로 호출되거나 콜백으로 전달될 경우 this가 다시 바인딩되지 않도록하는 것을 보장하기 위해 작성되었다.</p>
<pre><code class="language-tsx">const greet = new Person(&quot;Ron&quot;).greet; // 원래 실행이 안되지만, this 바인딩이 화살표 함수로 인해 상위스코프로 변경되어 실행가능

// We don&#39;t want this to fail!
greet();</code></pre>
<p>우리는 <code>addInitializer</code>를 사용하여 생성자에서 <code>bind</code>를 호출하는 데코레이터를 작성할 수 있다.</p>
<pre><code class="language-tsx">function bound(originalMethod: any, context: ClassMethodDecoratorContext) {
    const methodName = context.name;
    if (context.private) {
        throw new Error(`&#39;bound&#39; cannot decorate private properties like ${methodName as string}.`);
    }
    context.addInitializer(function () {
        this[methodName] = this[methodName].bind(this);
    });
}</code></pre>
<p><code>bound</code>는 아무것도 반환하지 않으므로, 메서드를 장식할 때 원래 메서드를 그대로 두고 있다. 대신, 다른 필드가 초기화되기 전에 로직을 추가한다.</p>
<pre><code class="language-tsx">class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @bound
    @loggedMethod
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person(&quot;Ron&quot;);
const greet = p.greet;

// Works!
greet();</code></pre>
<p>주목해야 할 점은 두 개의 데코레이터(<code>@bound</code>와 <code>@loggedMethod</code>)를 중첩해서 사용했다는 것이다. 이러한 데코레이터는 &quot;역순&quot;으로 실행된다. 즉, <code>@loggedMethod</code>가 원래의 메소드 <code>greet</code>를 꾸미고, <code>@bound</code>가 <code>@loggedMethod</code>의 결과를 꾸미게 된다. 이 예에서는 상관없지만, 데코레이터가 부수 효과를 가지거나 특정한 순서를 기대하는 경우에는 중요할 수 있다.</p>
<p>또한 스타일적으로 원하는 경우, 이러한 데코레이터를 같은 줄에 작성할 수도 있다.</p>
<pre><code class="language-tsx">@bound @loggedMethod greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }</code></pre>
<p>알아차리기 어려울 수 있는 점은 함수를 반환하는 데코레이터 함수를 만들 수 있다는 것이다. 이렇게하면 최종 데코레이터를 약간 수정할 수 있다. 만약 원한다면, <code>loggedMethod</code>를 데코레이터를 반환하도록 만들어서 메시지를 로그하는 방법을 사용자화할 수 있다.</p>
<pre><code class="language-tsx">function loggedMethod(headMessage = &quot;LOG:&quot;) {
    return function actualDecorator(originalMethod: any, context: ClassMethodDecoratorContext) {
        const methodName = String(context.name);

        function replacementMethod(this: any, ...args: any[]) {
            console.log(`${headMessage} Entering method &#39;${methodName}&#39;.`)
            const result = originalMethod.call(this, ...args);
            console.log(`${headMessage} Exiting method &#39;${methodName}&#39;.`)
            return result;
        }

        return replacementMethod;
    }
}</code></pre>
<p>만약 그렇게 했다면, <code>loggedMethod</code>를 decorator로 사용하기 전에 호출해야한다. 그러면 로그 메시지에 사용되는 접두사로 모든 문자열을 전달할 수 있다.</p>
<pre><code class="language-tsx">class Person {
    name: string;
    constructor(name: string) {
        this.name = name;
    }

    @loggedMethod(&quot;&quot;)
    greet() {
        console.log(`Hello, my name is ${this.name}.`);
    }
}

const p = new Person(&quot;Ron&quot;);
p.greet();

// Output:
//
//    Entering method &#39;greet&#39;.
//   Hello, my name is Ron.
//    Exiting method &#39;greet&#39;.</code></pre>
<p>데코레이터는 메서드에만 적용할 수 있는 것이 아니라, 속성/필드, 게터, 세터, 그리고 자동 접근자에도 적용할 수 있다. 심지어는 클래스 자체도 서브클래싱과 등록과 같은 목적으로 데코레이팅할 수 있다.</p>
<h2 id="실험적-레거시-데코레이터와-차이점">실험적 레거시 데코레이터와 차이점</h2>
<p>지금까지 TypeScript를 사용해본 사람이라면, &quot;실험적&quot; 데코레이터를 지원했다는 것을 알고 있을 것이다. 이러한 실험적 데코레이터는 매우 오래된 데코레이터 제안을 모델로 하고 있었으며, <code>--experimentalDecorators</code>이라는 옵션을 지정해주지 않으면 사용할 수 없었다. TypeScript에서 이 옵션 없이 데코레이터를 사용하려고 하면 오류 메시지가 표시된다.</p>
<p><code>--experimentalDecorators</code> 옵션은 앞으로도 계속 존재할 것이다. 하지만 이제부터 데코레이터는 모든 새 코드에서 유효한 구문이다. <code>--experimentalDecorators</code>를 사용하지 않은 경우, 이들은 다른 방식으로 타입 체크 및 출력된다. 이들은 충분히 다른 타입 체크 규칙 및 출력 방식을 가지고 있기 때문에, 기존의 데코레이터 함수들이 새로운 동작 방식을 지원할 가능성은 낮다.</p>
<p>이러한 새로운 데코레이터 제안은 <code>--emitDecoratorMetadata</code>와 호환되지 않으며, 매개변수에 데코레이터를 지정하는 것은 허용되지 않는다. 앞으로의 ECMAScript 제안들이 이러한 간극을 줄일 수 있을 것이다.</p>
<p>마지막으로, 데코레이터를 <code>export</code> 키워드 앞에 놓을 수 있게 됨에 따라, 데코레이터를 <code>export</code>나 <code>export default</code> 키워드 뒤에 놓을 수도 있게 되었다. 두 가지 스타일을 혼합하는 것은 허용되지 않는다.</p>
<pre><code class="language-tsx">//  allowed
@register export default class Foo {
    // ...
}

//  also allowed
export default @register class Bar {
    // ...
}

//  error - before *and* after is not allowed
@before export @after class Bar {
    // ...
}</code></pre>
<h2 id="잘-타입화된-데코레이터-작성">잘 타입화된 데코레이터 작성</h2>
<p>위의 <code>loggedMethod</code>와 <code>bound</code> 데코레이터 예제는 의도적으로 간단하게 작성되었으며 타입에 관한 많은 세부 정보를 생략하고 있다.</p>
<p>데코레이터에 타입을 지정하는 것은 상당히 복잡할 수 있다. 예를 들어, 위의 <code>loggedMethod</code>의 타입이 잘 지정된 버전은 다음과 같을 수 있다.</p>
<pre><code class="language-tsx">function loggedMethod&lt;This, Args extends any[], Return&gt;(
    target: (this: This, ...args: Args) =&gt; Return,
    context: ClassMethodDecoratorContext&lt;This, (this: This, ...args: Args) =&gt; Return&gt;
) {
    const methodName = String(context.name);

    function replacementMethod(this: This, ...args: Args): Return {
        console.log(`LOG: Entering method &#39;${methodName}&#39;.`)
        const result = target.call(this, ...args);
        console.log(`LOG: Exiting method &#39;${methodName}&#39;.`)
        return result;
    }

    return replacementMethod;
}</code></pre>
<p>위 예제에서는 <code>This</code>, <code>Args</code>, 그리고 <code>Return</code>의 유형 매개 변수를 사용하여 <code>this</code>, 매개 변수 및 원래 메서드의 반환 타입을 별도로 모델링해야 했다.</p>
<p>데코레이터 함수를 정확히 얼마나 복잡하게 정의할지는 보장하려는 내용에 따라 달라진다. 데코레이터는 작성된 것보다 더 많이 사용되므로 일반적으로 잘 타입화된 버전이 바람직하지만 가독성과는 분명한 상충 관계가 있으므로 단순하게 유지하도록 하자.</p>
<p>데코레이터 작성에 대한 더 많은 문서가 앞으로 제공될 예정이지만 <a href="https://2ality.com/2022/10/javascript-decorators.html">이 글</a>에서 데코레이터의 메커니즘에 대해 충분히 알 수 있을 것이다.</p>
<h2 id="const-타입-파라미터"><code>const</code> 타입 파라미터</h2>
<p>객체의 타입을 추론할 때 TypeScript는 일반적인 타입을 선택한다. 예를 들어, 이 경우에 <code>names</code>의 추론된 타입은 <code>string[]</code>이다.</p>
<pre><code class="language-tsx">type HasNames = { readonly names: string[] };
function getNamesExactly&lt;T extends HasNames&gt;(arg: T): T[&quot;names&quot;] {
    return arg.names;
}

// Inferred type: string[]
const names = getNamesExactly({ names: [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Eve&quot;]});</code></pre>
<p>일반적으로 이러한 경우에는 나중에 변이를 활성화하도록 의도되어 있다.</p>
<p>그러나 <code>getNamesExactly</code> 함수가 정확히 무엇을 수행하고 어떻게 사용되는지에 따라 더 구체적인 타입이 필요할 수 있다.</p>
<p>지금까지, API 작성자는 일부 위치에 <code>as const</code> 추가를 권장하여 원하는 유추를 얻을 수 있었다.</p>
<pre><code class="language-tsx">// The type we wanted:
//    readonly [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Eve&quot;]
// The type we got:
//    string[]
const names1 = getNamesExactly({ names: [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Eve&quot;]});

// Correctly gets what we wanted:
//    readonly [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Eve&quot;]
const names2 = getNamesExactly({ names: [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Eve&quot;]} as const);</code></pre>
<p>이는 번거롭고 잊어버리기 쉬울 수 있다. TypeScript 5.0에서는 이제 유형 매개변수 선언에 <code>const</code> 수정자를 추가하여 <code>const</code>와 유사한 추론이 기본값이 되도록 할 수 있다</p>
<pre><code class="language-tsx">type HasNames = { names: readonly string[] };
function getNamesExactly&lt;const T extends HasNames&gt;(arg: T): T[&quot;names&quot;] {
//                       ^^^^^
    return arg.names;
}

// Inferred type: readonly [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Eve&quot;]
// Note: Didn&#39;t need to write &#39;as const&#39; here
const names = getNamesExactly({ names: [&quot;Alice&quot;, &quot;Bob&quot;, &quot;Eve&quot;] });</code></pre>
<p><code>const</code> 수정자는 변경 가능한 값을 거부하지 않으며, 불변 제약 조건이 필요하지 않다. 변경 가능한 타입 제약 조건을 사용하면 의외의 결과가 나올 수 있다. 예를 들어</p>
<pre><code class="language-tsx">declare function fnBad&lt;const T extends string[]&gt;(args: T): void;

// &#39;T&#39; is still &#39;string[]&#39; since &#39;readonly [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]&#39; is not assignable to &#39;string[]&#39;
fnBad([&quot;a&quot;, &quot;b&quot; ,&quot;c&quot;]);</code></pre>
<p>여기에서 <code>T</code>의 추론된 후보는 <code>readonly [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]</code> 이며, 읽기 전용 배열은 변경 가능한 곳에서 사용할 수 없다. 이 경우, 추론이 제약 조건으로 빠지고, 배열은 <code>string[]</code>으로 처리되며 호출이 성공적으로 수행된다.</p>
<p>이 함수의 더 나은 정의는 <code>readonly string[]</code>을 사용해야 한다.</p>
<pre><code class="language-tsx">declare function fnGood&lt;const T extends readonly string[]&gt;(args: T): void;

// T is readonly [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;]
fnGood([&quot;a&quot;, &quot;b&quot; ,&quot;c&quot;]);</code></pre>
<p>마찬가지로, <code>const</code> 수정자는 호출 내에서 작성된 객체, 배열 및 프리미티브 표현식의 추론에만 영향을 미치므로, <code>as const</code>로 수정할 수 없는 인수는 동작이 변경되지 않는다는 점을 명심하세요:</p>
<pre><code class="language-tsx">declare function fnGood&lt;const T extends readonly string[]&gt;(args: T): void;
const arr = [&quot;a&quot;, &quot;b&quot; ,&quot;c&quot;];

// &#39;T&#39; is still &#39;string[]&#39;-- the &#39;const&#39; modifier has no effect here
fnGood(arr);</code></pre>
<h2 id="extends에서-여러-구성-파일-지원"><code>extends</code>에서 여러 구성 파일 지원</h2>
<p>여러 프로젝트를 관리할 때 다른 <code>tsconfig.json</code> 파일에서 확장할 수 있는 &quot;기본&quot; 구성 파일을 갖는 것이 유용할 수 있다. 이것이 바로 TypeScript가 <code>compilerOptions</code>에서 필드를 복사할 수 있는 <code>extends</code> 필드를 지원하는 이유이다.</p>
<pre><code class="language-tsx">// packages/front-end/src/tsconfig.json
{
    &quot;extends&quot;: &quot;../../../tsconfig.base.json&quot;,
    &quot;compilerOptions&quot;: {
        &quot;outDir&quot;: &quot;../lib&quot;,
        // ...
    }
}</code></pre>
<p>그러나 여러 구성 파일에서 확장하려는 시나리오가 있을 수 있다. 예를 들어 <a href="https://github.com/tsconfig/bases">npm에 제공된 TypeScript 기본 구성 파일</a>을 사용한다고 가정해보자. 모든 프로젝트에서 npm의 <code>@tsconfig/strictest</code> 패키지의 옵션도 사용하도록 하려면 <code>tsconfig.base.json</code>을 <code>@tsconfig/strictest</code>에서 확장하는 간단한 해결책이 있다.</p>
<pre><code class="language-tsx">// tsconfig.base.json
{
    &quot;extends&quot;: &quot;@tsconfig/strictest/tsconfig.json&quot;,
    &quot;compilerOptions&quot;: {
        // ...
    }
}</code></pre>
<p>이 방법은 어느 정도 효과가 있다. <code>@tsconfig/strictest</code>를 사용하지 않으려는 프로젝트가 있는 경우 해당 옵션을 수동으로 비활성화하거나 <code>@tsconfig/strictest</code>에서 확장되지 않는 별도의 <code>tsconfig.base.json</code> 버전을 만들어야 한다.</p>
<p>여기에 더 많은 유연성을 제공하기 위해 이제 Typescript 5.0에서는 <code>extends</code> 필드에 여러 항목을 사용할 수 있다. 예를 들어, 이 구성 파일에서</p>
<pre><code class="language-tsx">{
    &quot;extends&quot;: [&quot;a&quot;, &quot;b&quot;, &quot;c&quot;],
    &quot;compilerOptions&quot;: {
        // ...
    }
}</code></pre>
<p>이렇게 작성하는 것은 <code>c</code>를 직접 확장하는 것과 비슷하며, 여기서 <code>c</code>는 <code>b</code>를 확장하고 <code>b</code>는 <code>a</code>를 확장한다. 필드가 &#39;충돌&#39;하는 경우 후자의 항목이 우선한다.</p>
<p>따라서 다음 예제에서는 최종 <code>tsconfig.json</code>에서 <code>strictNullChecks</code>와 <code>noImplicitAny</code>가 모두 활성화되어 있다.</p>
<pre><code class="language-tsx">// tsconfig1.json
{
    &quot;compilerOptions&quot;: {
        &quot;strictNullChecks&quot;: true
    }
}

// tsconfig2.json
{
    &quot;compilerOptions&quot;: {
        &quot;noImplicitAny&quot;: true
    }
}

// tsconfig.json
{
    &quot;extends&quot;: [&quot;./tsconfig1.json&quot;, &quot;./tsconfig2.json&quot;],
    &quot;files&quot;: [&quot;./index.ts&quot;]
}</code></pre>
<p>다른 예로, 원래 예제를 다음과 같은 방식으로 다시 작성할 수 있다.</p>
<pre><code class="language-tsx">// packages/front-end/src/tsconfig.json
{
    &quot;extends&quot;: [&quot;@tsconfig/strictest/tsconfig.json&quot;, &quot;../../../tsconfig.base.json&quot;],
    &quot;compilerOptions&quot;: {
        &quot;outDir&quot;: &quot;../lib&quot;,
        // ...
    }
}</code></pre>
<h2 id="모든-enum은-유니온-enum이다">모든 <code>enum</code>은 유니온 <code>enum</code>이다</h2>
<p>TypeScript가 처음 열거형을 도입했을 때만 해도 열거형은 동일한 타입을 가진 숫자 상수 집합에 불과했다.</p>
<pre><code class="language-tsx">enum E {
    Foo = 10,
    Bar = 20,
}</code></pre>
<p><code>E.Foo</code>와 <code>E.Bar</code>의 유일한 특별한 점은 <code>E</code>타입을 예상하는 모든 것에 할당할 수 있다는 것이었다. 그 외에는 그냥 <code>number</code>에 불과했다.</p>
<pre><code class="language-tsx">function takeValue(e: E) {}

takeValue(E.Foo); // works
takeValue(123); // error!</code></pre>
<p>타입스크립트 2.0에서 열거형 리터럴 타입이 도입되면서 열거형은 좀 더 특별해졌다. 열거형 리터럴 타입은 각 열거형 멤버에 고유한 타입을 부여하고 열거형 자체를 각 멤버 타입의 합집합으로 만들었다. 또한 열거형 유형의 하위 집합만 참조하고 해당 유형의 범위를 좁힐 수 있게 되었다.</p>
<pre><code class="language-tsx">// Color is like a union of Red | Orange | Yellow | Green | Blue | Violet
enum Color {
    Red, Orange, Yellow, Green, Blue, /* Indigo */, Violet
}

// Each enum member has its own type that we can refer to!
type PrimaryColor = Color.Red | Color.Green | Color.Blue;

function isPrimaryColor(c: Color): c is PrimaryColor {
    // Narrowing literal types can catch bugs.
    // TypeScript will error here because
    // we&#39;ll end up comparing &#39;Color.Red&#39; to &#39;Color.Green&#39;.
    // We meant to use ||, but accidentally wrote &amp;&amp;.
    return c === Color.Red &amp;&amp; c === Color.Green &amp;&amp; c === Color.Blue;
}</code></pre>
<p>각 열거형 멤버에 고유한 유형을 부여할 때 발생하는 한 가지 문제는 해당 유형이 멤버의 실제 값과 일부 연관되어 있다는 점이다. 예를 들어 열거형 멤버가 함수 호출로 초기화될 수 있는 경우와 같이 해당 값을 계산할 수 없는 경우도 있다.</p>
<pre><code class="language-tsx">enum E {
    Blah = Math.random()
}</code></pre>
<p>TypeScript는 이러한 문제가 발생할 때마다 조용히 물러나 기존 열거형 전략을 사용했다. 이는 유니온과 리터럴 타입의 모든 장점을 포기하는 것을 의미했다.</p>
<p>TypeScript 5.0은 계산된 각 멤버에 대해 고유한 타입을 생성하여 모든 열거형을 공용체 열거형으로 만들 수 있다. 즉, 이제 모든 열거형을 좁혀서 그 멤버를 타입으로 참조할 수 있다.</p>
<p>버전에 따른 차이를 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/cef1346d-e39c-4176-9d7c-5e1216c63bdd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/0640bb6e-ead3-4047-b8cf-3115d8ababf4/image.png" alt=""></p>
<h2 id="--moduleresolution-bundler"><strong><code>--moduleResolution bundler</code></strong></h2>
<p>TypeScript 4.7에는 <code>--module</code> 및 <code>--moduleResolution</code>설정에 대한 <code>node16</code> 및 <code>nodenext</code> 옵션이 도입되었다. 이 옵션의 의도는 Node.js에서 ECMAScript 모듈에 대한 정확한 조회 규칙을 더 잘 모델링하기 위한 것이었지만, 이 모드에는 다른 도구에서는 실제로 적용되지 않는 많은 제한이 있다.</p>
<p>예를 들어, Node.js의 ECMAScript 모듈에서 모든 상대 가져오기에는 파일 확장명이 포함되어야 한다.</p>
<pre><code class="language-tsx">// entry.mjs
import * as utils from &quot;./utils&quot;;     //  wrong - we need to include the file extension.

import * as utils from &quot;./utils.mjs&quot;; //  works</code></pre>
<p>파일 조회 속도가 빨라지고 순수한 파일 서버에서 더 잘 작동하는 등 Node.js와 브라우저에는 특정한 이유가 있다. 하지만 번들러와 같은 도구를 사용하는 많은 개발자에게는 번들러에는 이러한 제한이 대부분 없기 때문에 <code>node16/nodenext</code> 설정이 번거로웠다. 어떤 면에서는 <code>node</code> resolution 모드가 번들러를 사용하는 모든 사용자에게 더 좋았다.</p>
<p>하지만 어떤 면에서는 원래의 <code>node</code> resolution 모드는 이미 시대에 뒤떨어진 모드였다. 대부분의 최신 번들러는 Node.js에서 ECMAScript 모듈과 CommonJS 조회 규칙의 융합을 사용한다. 예를 들어, 확장자 없는 가져오기는 CommonJS에서처럼 잘 작동하지만 패키지의 <a href="https://nodejs.org/api/packages.html#nested-conditions">export condition</a>을 살펴볼 때는 ECMAScript 파일에서와 같은 <code>import</code> 조건을 선호한다.</p>
<p>번들러의 작동 방식을 모델링하기 위해 TypeScript는 이제 새로운 전략인 <code>--moduleResolution</code> 번들러를 도입한다.</p>
<pre><code class="language-tsx">{
    &quot;compilerOptions&quot;: {
        &quot;target&quot;: &quot;esnext&quot;,
        &quot;moduleResolution&quot;: &quot;bundler&quot;
    }
}</code></pre>
<p>하이브리드 조회 전략을 구현하는 Vite, esbuild, swc, Webpack, Parcel 등의 최신 번들러를 사용 중이라면 새로운 <code>bundler</code>옵션이 적합할 것이다.</p>
<p>반면에 npm에 게시할 라이브러리를 작성하는 경우 번들러 옵션을 사용하면 번들러를 사용하지 않는 사용자에게 발생할 수 있는 호환성 문제를 숨길 수 있다. 따라서 이러한 경우에는 <code>node16</code> 또는 <code>nodenext</code> 해결 옵션을 사용하는 것이 더 나은 방법일 수 있다.</p>
<h2 id="resolution-커스터마이징-플래그">Resolution 커스터마이징 플래그</h2>
<p>자바스크립트 도구는 이제 위에서 설명한 번들러 모드에서와 같이 &quot;하이브리드&quot; resolution 규칙을 모델링할 수 있다. 도구마다 지원되는 기능이 조금씩 다를 수 있으므로 TypeScript 5.0에서는 사용자 구성에 따라 작동하거나 작동하지 않을 수 있는 몇 가지 기능을 활성화 또는 비활성화할 수 있는 방법을 제공한다.</p>
<h3 id="allowimportingtsextensions"><code>allowImportingTsExtensions</code></h3>
<p><code>--allowImportingTsExtensions</code>를 사용하면 TypeScript 파일이 <code>.ts</code>, <code>.mts</code> 또는 <code>.tsx</code>와 같은 TypeScript 전용 확장자를 사용하여 서로를 임포트할 수 있다.</p>
<p>이 플래그는 <code>--noEmit</code> 또는 <code>--emitDeclarationOnly</code>가 활성화된 경우에만 허용되는데, 이는 이러한 가져오기 경로가 JavaScript 출력 파일에서 런타임에 확인되지 않기 때문이다. 여기서 기대하는 것은 리졸버(예: 번들러, 런타임 또는 기타 도구)가 <code>.ts</code> 파일 간의 이러한 가져오기를 작동시킬 것이라는 점이다.</p>
<h3 id="resolvepackagejsonexports"><code>resolvePackageJsonExports</code></h3>
<p><code>--resolvePackageJsonExports</code>는 TypeScript가 <code>node_modules</code>의 패키지에서 읽을 경우 <a href="https://nodejs.org/api/packages.html#exports">package.json 파일의 내보내기 필드</a>를 참조하도록 한다.</p>
<p>이 옵션은 <code>--moduleResolution</code>에 대한 <code>node16</code>, <code>nodenext</code> 및 번들러 옵션에서 기본값이 <code>true</code>로 설정된다.</p>
<h3 id="resolvepackagejsonimports"><code>resolvePackageJsonImports</code></h3>
<p><code>--resolvePackageJsonImports</code>는 조상 디렉터리에 <code>package.json</code>이 포함된 파일에서 <code>#</code>으로 시작하는 조회를 수행할 때 TypeScript가 <a href="https://nodejs.org/api/packages.html#imports">package.json 파일의 import 필드</a>를 참조하도록 강제한다.</p>
<p>이 옵션은 <code>--moduleResolution</code>에 대한 <code>node16</code>, <code>nodenext</code> 및 번들러 옵션에서 기본값이 <code>true</code>로 설정된다.</p>
<h3 id="allowarbitraryextensions"><strong><code>allowArbitraryExtensions</code></strong></h3>
<p>TypeScript 5.0에서 가져오기 경로가 알려진 JavaScript 또는 TypeScript 파일 확장자가 아닌 확장자로 끝나는 경우 컴파일러는 <code>{파일 기본 이름}.d.{확장자}.ts</code> 형식의 해당 경로에 대한 선언 파일을 찾는다. 예를 들어 번들러 프로젝트에서 CSS 로더를 사용하는 경우 해당 스타일시트에 대한 선언 파일을 작성(또는 생성)해야 할 수 있다</p>
<pre><code class="language-tsx">/* app.css */
.cookie-banner {
  display: none;
}</code></pre>
<pre><code class="language-tsx">// app.d.css.ts
declare const css: {
  cookieBanner: string;
};
export default css;</code></pre>
<pre><code class="language-tsx">// App.tsx
import styles from &quot;./app.css&quot;;

styles.cookieBanner; // string</code></pre>
<p>기본적으로 이 가져오기는 TypeScript가 이 파일 형식을 이해하지 못하며 런타임에서 가져오기를 지원하지 않을 수 있음을 알리는 오류를 발생시킨다. 하지만 런타임이나 번들러가 이 오류를 처리하도록 구성한 경우 새로운 <code>--allowArbitraryExtensions</code> 컴파일러 옵션을 사용하여 오류를 억제할 수 있다.</p>
<p>이전에는 <code>app.d.css.ts</code> 대신 <code>app.css.d.ts</code>라는 선언 파일을 추가하여 비슷한 효과를 얻을 수 있었지만, 이는 CommonJS에 대한 Node의 요구 해결 규칙을 통해 작동했을 뿐이다. 엄밀히 말하면, 전자는 <code>app.css.js</code>라는 JavaScript 파일에 대한 선언 파일로 해석된다. 상대 파일 가져오기는 Node의 ESM 지원에서 확장자를 포함해야 하므로, 예제에서 <code>--moduleResolution</code> <code>node16</code> 또는 <code>nodenext</code> 아래의 ESM 파일에서 TypeScript가 오류를 일으킨다.</p>
<h2 id="customconditions"><strong><code>customConditions</code></strong></h2>
<p><code>--customConditions</code>는 <code>package.json</code>의 [<code>exports</code>] 또는 (<a href="https://nodejs.org/api/packages.html#exports">https://nodejs.org/api/packages.html#exports</a>) 또는 import 필드에서 TypeScript가 확인할 때 성공해야 하는 추가 조건의 목록을 받는다. 이러한 조건은 리졸버가 기본적으로 사용하는 기존 조건에 추가된다.</p>
<p>예를 들어 이 필드가 <code>tsconfig.json</code>에 다음과 같이 설정되어 있는 경우이다</p>
<pre><code class="language-tsx">{
    &quot;compilerOptions&quot;: {
        &quot;target&quot;: &quot;es2022&quot;,
        &quot;moduleResolution&quot;: &quot;bundler&quot;,
        &quot;customConditions&quot;: [&quot;my-condition&quot;]
    }
}</code></pre>
<p><code>package.json</code>에서 내보내기 또는 가져오기 필드가 참조될 때마다 TypeScript는 <code>my-condition</code>이라는 조건을 고려한다.</p>
<p>따라서 다음과 같은 <code>package.json</code>이 있는 패키지에서 가져올 때</p>
<pre><code class="language-tsx">{
    // ...
    &quot;exports&quot;: {
        &quot;.&quot;: {
            &quot;my-condition&quot;: &quot;./foo.mjs&quot;,
            &quot;node&quot;: &quot;./bar.mjs&quot;,
            &quot;import&quot;: &quot;./baz.mjs&quot;,
            &quot;require&quot;: &quot;./biz.mjs&quot;
        }
    }
}</code></pre>
<p>TypeScript는 <code>foo.mjs</code>에 해당하는 파일을 찾으려고 시도한다.</p>
<p>이 필드는 <code>--moduleResolution</code>에 대한 <code>node16</code>, <code>nodenext</code> 및 <code>bundler</code> 옵션에서만 유효하다.</p>
<h2 id="--verbatimmodulesyntax"><strong><code>--verbatimModuleSyntax</code></strong></h2>
<p>기본적으로 TypeScript는 가져오기 엘리전스라는 것을 수행한다. 기본적으로 다음과 같이 작성하면</p>
<pre><code class="language-tsx">import { Car } from &quot;./car&quot;;

export function drive(car: Car) {
    // ...
}</code></pre>
<p>TypeScript는 타입에 대해서만 가져오기를 사용하고 있음을 감지하고 가져오기를 완전히 삭제한다. 출력 자바스크립트는 다음과 같이 보일 수 있다</p>
<pre><code class="language-tsx">export function drive(car) {
    // ...
}</code></pre>
<p><code>Car</code>가 <code>./car</code>에서 내보낸 값이 아닌 경우 런타임 오류가 발생하기 때문에 대부분의 경우 이 방법이 좋다.</p>
<p>하지만 특정 에지 케이스의 경우 복잡성이 추가된다. 예를 들어, <code>import &quot;./car&quot;;</code> 와 같은 문이 없으므로 가져오기가 완전히 삭제되었다. 이는 실제로 부작용이 있는 모듈과 없는 모듈에 차이를 만든다.</p>
<p>자바스크립트에 대한 타입스크립트의 임포트 전략에는 또 다른 몇 가지 복잡한 계층이 있다. 임포트 생략은 항상 임포트가 어떻게 사용되는지에 따라 결정되는 것이 아니라 값이 선언되는 방식도 참조하는 경우가 많다. 따라서 다음과 같은 코드가 보존하거나 삭제 되어야 하는지는 항상 명확하지는 않다.</p>
<pre><code class="language-tsx">export { Car } from &quot;./car&quot;;</code></pre>
<p><code>Car</code>가 클래스와 같이 선언된 경우 결과 JavaScript 파일에 보존될 수 있다. 그러나 <code>Car</code>가 <code>type</code> alias이나 <code>interface</code>로만 선언된 경우 JavaScript 파일은 <code>Car</code>를 전혀 내보내지 않아야 한다.</p>
<p>TypeScript는 파일 전반의 정보를 기반으로 이러한 내보내기 결정을 내릴 수 있지만 모든 컴파일러에서 가능한 것은 아니다.</p>
<p>가져오기 및 내보내기의 <code>type</code> 수정자는 이러한 상황에 약간 도움이 된다. <code>type</code> 수정자를 사용하면 가져오기 또는 내보내기가 타입 분석에만 사용되는지 여부를 명시할 수 있으며, JavaScript 파일에서 완전히 삭제할 수 있다.</p>
<pre><code class="language-tsx">// This statement can be dropped entirely in JS output
import type * as car from &quot;./car&quot;;

// The named import/export &#39;Car&#39; can be dropped in JS output
import { type Car } from &quot;./car&quot;;
export { type Car } from &quot;./car&quot;;</code></pre>
<p><code>type</code> 수정자는 그 자체로는 그다지 유용하지 않다. 기본적으로 모듈 elision은 여전히 가져오기를 삭제하며, 타입과 일반 가져오기 및 내보내기를 구분하도록 강제하는 것은 없다. 따라서 TypeScript에는 <code>type</code> 수정자를 사용하도록 하는 <code>--importsNotUsedAsValues</code> 플래그, 일부 모듈 엘리션 동작을 방지하는 <code>--preserveValueImports</code> 플래그, TypeScript 코드가 여러 컴파일러에서 작동하는지 확인하는 <code>--isolatedModules</code> 플래그가 있다. 안타깝게도 이 세 가지 플래그의 세부 사항을 이해하는 것은 어렵고 예기치 않은 동작이 발생하는 에지 케이스가 여전히 존재한다.</p>
<p>TypeScript 5.0에서는 상황을 단순화하기 위해 <code>--verbatimModuleSyntax</code>라는 새로운 옵션이 도입되었다. <code>type</code> 수정자가 없는 모든 가져오기 또는 내보내기는 그대로 유지되므로 규칙이 훨씬 더 간단해졌다. <code>type</code> 수정자를 사용하는 모든 항목은 완전히 삭제된다.</p>
<pre><code class="language-tsx">// Erased away entirely.
import type { A } from &quot;a&quot;;

// Rewritten to &#39;import { b } from &quot;bcd&quot;;&#39;
import { b, type c, type d } from &quot;bcd&quot;;

// Rewritten to &#39;import {} from &quot;xyz&quot;;&#39;
import { type xyz } from &quot;xyz&quot;;</code></pre>
<p>이 새로운 옵션을 사용하면 보이는 그대로를 얻을 수 있다.</p>
<p>하지만 모듈 상호 운용과 관련하여 몇 가지 시사점이 있다. 이 플래그를 사용하면 설정 또는 파일 확장자가 다른 모듈 시스템을 암시하는 경우 ECMAScript 가져오기 및 내보내기가 호출을 요구하도록 다시 작성되지 않는다. 대신 오류가 발생한다. <code>require</code> 및 <code>module.exports</code>를 사용하는 코드를 내보내야 하는 경우 ES2015 이전의 TypeScript의 모듈 구문을 사용해야 한다:</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/742d9b38-9d30-4cd3-95b6-750dfa9a7559/image.png" alt=""></p>
<p>이는 제한 사항이지만 일부 문제를 더 명확하게 파악하는 데 도움이 된다. 예를 들어, <code>package.json</code>의 <code>--module node16</code>에서 타입 필드를 설정하는 것을 잊어버리는 경우가 매우 흔하다. 그 결과, 개발자는 이를 깨닫지 못한 채 ES 모듈 대신 CommonJS 모듈을 작성하기 시작하여 예상치 못한 조회 규칙과 JavaScript 출력을 제공하게 된다. 이 새로운 플래그는 구문이 의도적으로 다르기 때문에 사용 중인 파일 형식에 대해 의도적으로 확인할 수 있다.</p>
<p><code>-verbatimModuleSyntax</code>은 <code>--importsNotUsedAsValues</code> 및 <code>--preserveValueImports</code>보다 더 일관된 스토리를 제공하므로, 기존의 두 플래그는 이 구문으로 대체된다.</p>
<h2 id="support-for-export-type-"><strong>Support for <code>export type *</code></strong></h2>
<p>TypeScript 3.8에서 타입 전용 import가 도입되면서, 이 새로운 문법은 <code>export * from &quot;module&quot;</code> 또는 <code>export * as ns from &quot;module&quot;</code> 재내보내기에서 사용이 불가능했다. TypeScript 5.0에서는 이 두 형태를 지원한다.</p>
<pre><code class="language-tsx">// models/vehicles.ts
export class Spaceship {
  // ...
}

// models/index.ts
export type * as vehicles from &quot;./vehicles&quot;;

// main.ts
import { vehicles } from &quot;./models&quot;;

function takeASpaceship(s: vehicles.Spaceship) {
  //  ok - `vehicles` only used in a type position
}

function makeASpaceship() {
  return new vehicles.Spaceship();
  //         ^^^^^^^^
  // &#39;vehicles&#39; cannot be used as a value because it was exported using &#39;export type&#39;.
}</code></pre>
<h2 id="satisfies-support-in-jsdoc"><strong><code>@satisfies</code> Support in JSDoc</strong></h2>
<p>타입스크립트 4.9에는 <code>satisfies</code> 연산자가 도입되었다. 이 연산자는 타입 자체에 영향을 주지 않고 표현식의 타입이 호환되는지 확인한다. 예를 들어 다음 코드를 살펴보자.</p>
<pre><code class="language-tsx">interface CompilerOptions {
    strict?: boolean;
    outDir?: string;
    // ...
}

interface ConfigSettings {
    compilerOptions?: CompilerOptions;
    extends?: string | string[];
    // ...
}

let myConfigSettings = {
    compilerOptions: {
        strict: true,
        outDir: &quot;../lib&quot;,
        // ...
    },

    extends: [
        &quot;@tsconfig/strictest/tsconfig.json&quot;,
        &quot;../../../tsconfig.base.json&quot;
    ],

} satisfies ConfigSettings;</code></pre>
<p>여기서 TypeScript는 <code>myConfigSettings.extends</code>가 배열로 선언되었음을 알고 있다. 이는 <code>satisfies</code>가 객체의 타입을 확인했지만 <code>ConfigSettings</code>로 강제 형변환하여 정보를 잃지 않았기 때문이다. 그래서 <code>extends</code>에 map함수를 사용하여도 괜찮다.</p>
<pre><code class="language-tsx">declare function resolveConfig(configPath: string): CompilerOptions;

let inheritedConfigs = myConfigSettings.extends.map(resolveConfig);</code></pre>
<p>이 기능은 TypeScript 사용자에게 유용했지만, 많은 사람들이 JSDoc 주석을 사용하여 JavaScript 코드를 타입 검사하는 데 TypeScript를 사용한다. 그렇기 때문에 TypeScript 5.0에서는 똑같은 기능을 하는 <code>@satisfies</code>라는 새로운 JSDoc 태그가 지원된다.</p>
<p><code>/** @satisfies */</code>는 타입 불일치를 포착할 수 있다</p>
<pre><code class="language-tsx">// @ts-check

/**
 * @typedef CompilerOptions
 * @prop {boolean} [strict]
 * @prop {string} [outDir]
 */

/**
 * @satisfies {CompilerOptions}
 */
let myCompilerOptions = {
    outdir: &quot;../lib&quot;,
//  ~~~~~~ oops! we meant outDir
};</code></pre>
<p>하지만 표현식의 원래 타입이 유지되므로 나중에 코드에서 값을 더 정확하게 사용할 수 있다.</p>
<pre><code class="language-tsx">// @ts-check

/**
 * @typedef CompilerOptions
 * @prop {boolean} [strict]
 * @prop {string} [outDir]
 */

/**
 * @typedef ConfigSettings
 * @prop {CompilerOptions} [compilerOptions]
 * @prop {string | string[]} [extends]
 */

/**
 * @satisfies {ConfigSettings}
 */
let myConfigSettings = {
    compilerOptions: {
        strict: true,
        outDir: &quot;../lib&quot;,
    },
    extends: [
        &quot;@tsconfig/strictest/tsconfig.json&quot;,
        &quot;../../../tsconfig.base.json&quot;
    ],
};

let inheritedConfigs = myConfigSettings.extends.map(resolveConfig);</code></pre>
<p>괄호로 묶인 표현식에서 <code>/** @satisfies */</code>를 인라인으로 사용할 수도 있다. <code>myConfigSettings</code>를 다음과 같이 작성할 수 있다</p>
<pre><code class="language-tsx">let myConfigSettings = /** @satisfies {ConfigSettings} */ ({
    compilerOptions: {
        strict: true,
        outDir: &quot;../lib&quot;,
    },
    extends: [
        &quot;@tsconfig/strictest/tsconfig.json&quot;,
        &quot;../../../tsconfig.base.json&quot;
    ],
});</code></pre>
<p>일반적으로 함수 호출과 같은 다른 코드에서 더 깊숙이 들어가면 더 의미가 있다.</p>
<pre><code class="language-tsx">compileCode(/** @satisfies {ConfigSettings} */ ({
    // ...
}));</code></pre>
<h2 id="overload-support-in-jsdoc"><strong><code>@overload</code> Support in JSDoc</strong></h2>
<p>타입스크립트에서는 함수에 오버로드를 지정할 수 있다. 오버로드를 사용하면 함수를 다른 인수로 호출할 수 있고 다른 결과를 반환할 수도 있다. 오버로드를 사용하면 호출자가 실제로 함수를 사용하는 방법을 제한하고 어떤 결과를 반환할지 구체화할 수 있다.</p>
<pre><code class="language-tsx">// Our overloads:
function printValue(str: string): void;
function printValue(num: number, maxFractionDigits?: number): void;

// Our implementation:
function printValue(value: string | number, maximumFractionDigits?: number) {
    if (typeof value === &quot;number&quot;) {
        const formatter = Intl.NumberFormat(&quot;en-US&quot;, {
            maximumFractionDigits,
        });
        value = formatter.format(value);
    }

    console.log(value);
}</code></pre>
<p>여기서는 <code>printValue</code>가 문자열 또는 숫자를 첫 번째 인자로 받는다고 했다. 숫자를 인자로 받으면 두 번째 인자로 인쇄할 수 있는 소수점 자릿수를 결정할 수 있다.</p>
<p>TypeScript 5.0에서는 이제 JSDoc이 새로운 <code>@overload</code> 태그를 사용하여 오버로드를 선언할 수 있다. 오버로드 태그가 있는 각 JSDoc 주석은 다음 함수 선언에 대해 별개의 오버로드로 취급된다.</p>
<pre><code class="language-tsx">// @ts-check

/**
 * @overload
 * @param {string} value
 * @return {void}
 */

/**
 * @overload
 * @param {number} value
 * @param {number} [maximumFractionDigits]
 * @return {void}
 */

/**
 * @param {string | number} value
 * @param {number} [maximumFractionDigits]
 */
function printValue(value, maximumFractionDigits) {
    if (typeof value === &quot;number&quot;) {
        const formatter = Intl.NumberFormat(&quot;en-US&quot;, {
            maximumFractionDigits,
        });
        value = formatter.format(value);
    }

    console.log(value);
}</code></pre>
<p>이제 TypeScript 파일로 작성하든 JavaScript 파일로 작성하든 상관없이 TypeScript는 함수를 잘못 호출했는지 알려줄 수 있다.</p>
<pre><code class="language-tsx">// all allowed
printValue(&quot;hello!&quot;);
printValue(123.45);
printValue(123.45, 2);

printValue(&quot;hello!&quot;, 123); // error!</code></pre>
<h2 id="passing-emit-specific-flags-under---build"><strong>Passing Emit-Specific Flags Under <code>--build</code></strong></h2>
<p>이제 타입스크립트에서 <code>--build</code> 모드에서 다음 플래그를 전달할 수 있다.</p>
<ul>
<li><code>--declaration</code></li>
<li><code>—-emitDeclarationOnly</code></li>
<li><code>—-declarationMap</code></li>
<li><code>—-sourceMap</code></li>
<li><code>—-inlineSourceMap</code></li>
</ul>
<p>이렇게 하면 개발 빌드와 프로덕션 빌드가 다를 수 있는 빌드의 특정 부분을 훨씬 쉽게 커스터마이징할 수 있다.</p>
<p>예를 들어 라이브러리의 개발 빌드에서는 선언 파일을 생성할 필요가 없지만 프로덕션 빌드에서는 생성해야 할 수 있다. 프로젝트에서 선언 파일 생성을 기본적으로 해제하도록 구성하고</p>
<pre><code class="language-bash">tsc --build -p ./my-project-dir</code></pre>
<p>내부 루프에서 반복을 완료하면 &quot;프로덕션&quot; 빌드에서 <code>--declaration</code> 플래그를 전달하기만 하면 된다.</p>
<pre><code class="language-bash">tsc --build -p ./my-project-dir --declaration</code></pre>
<h2 id="case-insensitive-import-sorting-in-editors"><strong>Case-Insensitive Import Sorting in Editors</strong></h2>
<p>Visual Studio 및 VS Code와 같은 편집기에서 TypeScript는 가져오기 및 내보내기를 구성하고 정렬하는 환경을 지원한다. 하지만 종종 목록이 &#39;정렬&#39;되는 시점에 대해 서로 다른 해석이 있을 수 있다.</p>
<p>예를 들어 다음 가져오기 목록은 정렬되어 있을까?</p>
<pre><code class="language-tsx">import {
    Toggle,
    freeze,
    toBoolean,
} from &quot;./utils&quot;;</code></pre>
<p>의외로 대답은 &quot;상황에 따라 다르다&quot;일 수 있다. 대소문자 구분을 신경 쓰지 않는다면 이 목록은 정렬되지 않은 것이 분명하다. 문자 <code>f</code>는 <code>t</code>와 <code>T</code> 앞에온다.</p>
<p>하지만 대부분의 프로그래밍 언어에서 정렬은 기본적으로 문자열의 바이트 값을 비교한다. 자바스크립트가 문자열을 비교하는 방식은 ASCII 문자 인코딩에 따라 대문자가 소문자보다 먼저 오기 때문에 항상 &quot;<code>Toggle</code>&quot;이 &quot;<code>Freeze</code>&quot;보다 먼저 온다는 것을 의미한다. 따라서 이러한 관점에서 가져오기 목록이 정렬된다.</p>
<p>이전에 TypeScript는 대소문자를 구분하는 기본 정렬을 수행했기 때문에 가져오기 목록이 정렬된 것으로 간주했다. 대소문자를 구분하지 않는 정렬을 선호하거나 기본적으로 대소문자를 구분하지 않는 정렬이 필요한 ESLint와 같은 도구를 사용하는 개발자에게는 불만스러운 점이 될 수 있다.</p>
<p>이제 TypeScript는 기본적으로 대소문자 구분을 감지한다. 즉, TypeScript와 ESLint와 같은 도구가 임포트를 가장 잘 정렬하는 방법을 놓고 서로 &#39;싸우지&#39; 않는다는 뜻이다.</p>
<p>저희 팀에서는 추가적인 정렬 전략도 실험하고 있으며, 이에 대한 내용은 여기에서 확인할 수 있다. 이러한 옵션은 결국 편집자가 구성할 수 있다. 현재로서는 아직 불안정하고 실험적인 상태이며, 지금 바로 VS Code에서 JSON 옵션의 <code>typescript.unstable</code>항목을 사용하여 해당 옵션을 선택할 수 있다. 다음은 사용해 볼 수 있는 모든 옵션이다(기본값으로 설정됨)</p>
<pre><code class="language-tsx">{
    &quot;typescript.unstable&quot;: {
        // Should sorting be case-sensitive? Can be:
        // - true
        // - false
        // - &quot;auto&quot; (auto-detect)
        &quot;organizeImportsIgnoreCase&quot;: &quot;auto&quot;,

        // Should sorting be &quot;ordinal&quot; and use code points or consider Unicode rules? Can be:
        // - &quot;ordinal&quot;
        // - &quot;unicode&quot;
        &quot;organizeImportsCollation&quot;: &quot;ordinal&quot;,

        // Under `&quot;organizeImportsCollation&quot;: &quot;unicode&quot;`,
        // what is the current locale? Can be:
        // - [any other locale code]
        // - &quot;auto&quot; (use the editor&#39;s locale)
        &quot;organizeImportsLocale&quot;: &quot;en&quot;,

        // Under `&quot;organizeImportsCollation&quot;: &quot;unicode&quot;`,
        // should upper-case letters or lower-case letters come first? Can be:
        // - false (locale-specific)
        // - &quot;upper&quot;
        // - &quot;lower&quot;
        &quot;organizeImportsCaseFirst&quot;: false,

        // Under `&quot;organizeImportsCollation&quot;: &quot;unicode&quot;`,
        // do runs of numbers get compared numerically (i.e. &quot;a1&quot; &lt; &quot;a2&quot; &lt; &quot;a100&quot;)? Can be:
        // - true
        // - false
        &quot;organizeImportsNumericCollation&quot;: true,

        // Under `&quot;organizeImportsCollation&quot;: &quot;unicode&quot;`,
        // do letters with accent marks/diacritics get sorted distinctly
        // from their &quot;base&quot; letter (i.e. is é different from e)? Can be
        // - true
        // - false
        &quot;organizeImportsAccentCollation&quot;: true
    },
    &quot;javascript.unstable&quot;: {
        // same options valid here...
    },
}</code></pre>
<h2 id="exhaustive-switchcase-completions"><strong>Exhaustive <code>switch</code>/<code>case</code> Completions</strong></h2>
<p>이제 스위치 문을 작성할 때 TypeScript는 검사 대상 값에 리터럴 타입이 있는지 감지한다. 그렇다면 발견되지 않은 각 대소문자를 스캐폴딩하는 완결성을 제공한다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/64923339-79c3-4012-9aec-3c566381086d/image.png" alt=""></p>
<h2 id="speed-memory-and-package-size-optimizations"><strong>Speed, Memory, and Package Size Optimizations</strong></h2>
<p>TypeScript 5.0에는 코드 구조, 데이터 구조 및 알고리즘 구현 전반에 걸쳐 많은 강력한 변경 사항이 포함되어 있다. 이 모든 것이 의미하는 바는 TypeScript를 실행하는 것뿐만 아니라 설치하는 것까지 전체 경험이 더 빨라진다는 것이다.</p>
<p>다음은 TypeScript 4.9와 비교하여 속도와 크기 면에서 달성한 몇 가지 흥미로운 성과이다.</p>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Time or Size Relative to TS 4.9</th>
</tr>
</thead>
<tbody><tr>
<td>material-ui build time</td>
<td>90%</td>
</tr>
<tr>
<td>TypeScript Compiler startup time</td>
<td>89%</td>
</tr>
<tr>
<td>Playwright build time</td>
<td>88%</td>
</tr>
<tr>
<td>TypeScript Compiler self-build time</td>
<td>87%</td>
</tr>
<tr>
<td>Outlook Web build time</td>
<td>82%</td>
</tr>
<tr>
<td>VS Code build time</td>
<td>80%</td>
</tr>
<tr>
<td>typescript npm Package Size</td>
<td>59%</td>
</tr>
</tbody></table>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/153b57fc-16cf-4b85-a7b3-931f22cba405/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/b96f4e2a-9ae7-43c7-87ea-e2039589552c/image.png" alt=""></p>
<p>앞으로 몇 가지 주목할 만한 개선 사항을 더 자세히 알려드리고자 한다. 하지만 블로그 포스팅을 기다리게 하지는 않겠다.</p>
<p>우선, 최근 네임스페이스에서 모듈로 TypeScript를 마이그레이션하여 스코프 호이스트와 같은 최적화를 수행할 수 있는 최신 빌드 툴을 활용할 수 있게 되었다. 이 툴을 사용하여 패키징 전략을 재검토하고 더 이상 사용되지 않는 코드를 제거함으로써 TypeScript 4.9의 63.8MB 패키지 크기에서 약 26.4MB를 줄일 수 있었다. 또한 직접 함수 호출을 통해 눈에 띄는 속도 향상을 가져왔다. </p>
<p>또한 TypeScript는 컴파일러 내의 내부 객체 유형에 더 많은 균일성을 추가했으며, 일부 객체 유형에 저장되는 데이터도 슬림화했다. 이를 통해 다형성 연산을 줄이면서 객체 모양을 더 균일하게 만드는 데 따른 메모리 사용량 증가의 균형을 맞출 수 있었다.</p>
<p>또한 정보를 문자열로 직렬화할 때 일부 캐싱을 수행했다. 오류 보고, 선언 방출, 코드 완성 등의 일부로 발생할 수 있는 유형 표시는 결국 상당히 많은 비용을 초래할 수 있다. 이제 TypeScript는 이러한 작업에서 재사용할 수 있도록 일반적으로 사용되는 몇 가지 메커니즘을 캐싱한다.</p>
<p>구문 분석기를 개선한 또 다른 주목할 만한 변경 사항은 var를 활용하여 클로저에서 let과 const를 사용하는 데 드는 비용을 가끔씩 우회하는 것이다. 이를 통해 구문 분석 성능이 일부 개선되었다.</p>
<p>전반적으로 대부분의 코드베이스에서 TypeScript 5.0에서 속도 향상을 기대할 수 있으며, 지속적으로 10%에서 20% 사이의 속도 향상을 재현할 수 있었다. 물론 하드웨어와 코드베이스 특성에 따라 다르겠지만, 지금 바로 코드베이스에서 사용해 보시기 바란다!</p>
<h2 id="breaking-changes-and-deprecations"><strong>Breaking Changes and Deprecations</strong></h2>
<h3 id="runtime-requirements"><strong>Runtime Requirements</strong></h3>
<p>TypeScript는 이제 ECMAScript 2018을 대상으로 한다. TypeScript 패키지는 또한 최소 예상 엔진을 12.20으로 설정했다. Node 사용자의 경우 TypeScript 5.0의 최소 버전 요구 사항이 Node.js 12.20 이상이라는 뜻이다.</p>
<h3 id="libdts-changes"><code>lib.d.ts</code> Changes</h3>
<p>DOM의 유형이 생성되는 방식이 변경되어 기존 코드에 영향을 미칠 수 있다. 특히 특정 프로퍼티가 숫자에서 숫자 리터럴 타입으로 변환되었으며, 잘라내기, 복사, 붙여넣기 이벤트 처리를 위한 프로퍼티와 메서드가 인터페이스 전반으로 이동되었다.</p>
<h3 id="api-breaking-changes">API Breaking Changes</h3>
<p>TypeScript 5.0에서는 모듈로 전환하고, 불필요한 인터페이스를 제거했으며, 일부 정확성을 개선했다. </p>
<h3 id="forbidden-implicit-coercions-in-relational-operators">Forbidden Implicit Coercions in Relational Operators</h3>
<p>TypeScript의 특정 연산은 암시적으로 문자열을 숫자로 강제 변환할 수 있는 코드를 작성할 경우 이미 경고한다.</p>
<pre><code class="language-tsx">function func(ns: number | string) {
  return ns * 4; // Error, possible implicit coercion
}</code></pre>
<p>5.0에서는 관계 연산자 &gt;, &lt;, &lt;=, &gt;=에도 이 기능이 적용될 예정이다</p>
<pre><code class="language-tsx">function func(ns: number | string) {
  return ns &gt; 4; // Now also an error
}</code></pre>
<p>원하는 경우 이를 허용하려면 +를 사용하여 피연산자를 숫자로 명시적으로 강제할 수 있다.</p>
<pre><code class="language-tsx">function func(ns: number | string) {
  return +ns &gt; 4; // OK
}</code></pre>
<h3 id="enum-overhaul">Enum Overhaul</h3>
<p>TypeScript는 처음 출시된 이래로 열거형과 관련하여 오랜 기간 동안 몇 가지 이상한 점이 있었다. 5.0에서는 이러한 문제 중 일부를 해결하고 선언할 수 있는 다양한 열거형 유형을 이해하는 데 필요한 개념 수를 줄였다.</p>
<p>그 일환으로 두 가지 주요 오류가 새로 추가되었다. 첫 번째는 열거형 유형에 도메인 외부 리터럴을 할당하면 이제 예상대로 오류가 발생한다는 것이다.</p>
<pre><code class="language-tsx">enum SomeEvenDigit {
    Zero = 0,
    Two = 2,
    Four = 4
}

// Now correctly an error
let m: SomeEvenDigit = 1;</code></pre>
<p>다른 하나는 숫자와 간접 문자열 열거형 참조가 혼합되어 선언된 값이 있는 열거형은 모든 숫자 열거형을 잘못 생성한다는 것이다.</p>
<pre><code class="language-tsx">enum Letters {
    A = &quot;a&quot;
}
enum Numbers {
    one = 1,
    two = Letters.A
}

// Now correctly an error
const t: number = Numbers.two;</code></pre>
<h3 id="more-accurate-type-checking-for-parameter-decorators-in-constructors-under---experimentaldecorators"><strong>More Accurate Type-Checking for Parameter Decorators in Constructors Under <code>--experimentalDecorators</code></strong></h3>
<p>TypeScript 5.0에서는 <code>--experimentalDecorators</code> 아래의 데코레이터에 대한 유형 검사가 더 정확해졌다. 이것이 분명하게 드러나는 한 곳은 생성자 매개변수에 데코레이터를 사용할 때이다.</p>
<pre><code class="language-tsx">export declare const inject:
  (entity: any) =&gt;
    (target: object, key: string | symbol, index?: number) =&gt; void;

export class Foo {}

export class C {
    constructor(@inject(Foo) private x: any) {
    }
}</code></pre>
<p>이 호출은 <code>key</code>가 <code>string | symbol</code>를 기대하지만 생성자 매개 변수가 <code>undefined</code> 키를 수신하기 때문에 실패한다. 올바른 수정 방법은 <code>inject</code> 내에서 키 유형을 변경하는 것이다. 업그레이드할 수 없는 라이브러리를 사용하는 경우 합리적인 해결 방법은 <code>inject</code>를 보다 유형에 안전한 데코레이터 함수로 감싸고 키에 타입 단언을 사용하는 것이다.</p>
<h3 id="deprecations-and-default-changes"><strong>Deprecations and Default Changes</strong></h3>
<p>TypeScript 5.0에서는 다음 설정 및 설정 값이 더 이상 사용되지 않는다</p>
<ul>
<li><code>--target: ES3</code></li>
<li><code>--out</code></li>
<li><code>--noImplicitUseStrict</code></li>
<li><code>--keyofStringsOnly</code></li>
<li><code>--suppressExcessPropertyErrors</code></li>
<li><code>--suppressImplicitAnyIndexErrors</code></li>
<li><code>--noStrictGenericChecks</code></li>
<li><code>--charset</code></li>
<li><code>--importsNotUsedAsValues</code></li>
<li><code>--preserveValueImports</code></li>
<li><code>prepend</code> in project references</li>
</ul>
<p>이러한 설정은 TypeScript 5.5까지 계속 허용될 것이며, 그때까지 이러한 설정을 사용하면 경고가 발생하지만, <code>&quot;ignoreDeprecations&quot;: &quot;5.0&quot;</code>을 지정하여 이러한 경고를 무시할 수 있다. TypeScript 5.0 및 이후 릴리스 5.1, 5.2, 5.3 및 5.4에서 교차 플랫폼 동작을 더 개선하기 위해 일부 설정을 변경했다.</p>
<p>자바스크립트 파일에 출력되는 줄 끝을 제어하는 <code>--newLine</code>은 이제 명시하지 않으면 현재 운영 체제에 따라 추론되지 않으며, 가능한 한 결정론적인 빌드가 되어야 한다고 생각한다. 또한, Windows Notepad가 이제 줄 끝 행 종결자를 지원하므로, 새로운 기본 설정은 <code>LF</code>이다. 예전의 OS별 추론 동작은 더 이상 사용할 수 없다.</p>
<p>프로젝트 내에서 동일한 파일 이름을 참조하는 모든 참조가 케이스에 대해 동의하도록 보장하는 <code>--forceConsistentCasingInFileNames</code>은 이제 <code>true</code>로 기본 설정된다. 이것은 케이스가 대소문자를 구분하지 않는 파일 시스템에서 작성된 코드에 대한 차이점 문제를 해결하는 데 도움이 될 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 4.3 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-4.3-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-4.3-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Sat, 18 Mar 2023 14:05:28 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-3.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-3.html</a></p>
</blockquote>
<h2 id="프로퍼티의-쓰기-타입-분리">프로퍼티의 쓰기 타입 분리</h2>
<p>JS에서 API는 값을 저장하기 전에 값을 변환하는 것이 일반적이다. 이는 getter와 setter에서도 자주 발생한다. 예를 들어, 항상 값을 숫자로 변환한 후에 비공개 필드에 저장하는 setter를 갖는 클래스가 있다고 상상해보자.</p>
<pre><code class="language-ts">class Thing {
  #size = 0;

  get size() {
    return this.#size;
  }
  set size(value) {
    let num = Number(value);

    // Don&#39;t allow NaN and stuff.
    if (!Number.isFinite(num)) {
      this.#size = 0;
      return;
    }

    this.#size = num;
  }
}</code></pre>
<p>이 JS 코드를 TS로 어떻게 입력할까? 기술적으로는 여기서 특별한 조치를 취할 필요가 없다. TS는 명시적인 타입이 없어도 이를 살펴보고 <code>size</code>가 숫자임을 알아낼 수 있다.</p>
<p>문제는 <code>size</code>가 숫자뿐만 아니라 다른 타입의 값을 할당할 수 있다는 점이다. 따라서 우리는 이 스니펫에서와 같이 <code>size</code>가 <code>unknown</code> 또는 <code>any</code>와 같은 타입을 갖는다고 명시하여 이 문제를 해결할 수 있다.</p>
<pre><code class="language-ts">class Thing {
  // ...
  get size(): unknown {
    return this.#size;
  }
}</code></pre>
<p>하지만 이 방법은 좋은 방법은 아니다. <code>unknown</code>은 <code>size</code>를 읽는 사람들이 타입 단언을 해야 한다는 불편함을 가져오며, <code>any</code>는 어떤 오류도 잡아내지 못한다. 값을 변환하는 API를 모델링하려면 이전 버전의 TypeScript에서는 정확성(값 읽기는 더 쉽고 쓰기는 더 어렵게)과 관대함(값 쓰기는 더 쉽고 읽기는 더 어렵게) 중 하나를 선택해야 했다.</p>
<p>그래서 TypeScript 4.3에서는 속성에 대한 읽기 및 쓰기 타입을 지정할 수 있도록 한다.</p>
<pre><code class="language-ts">class Thing {
  #size = 0;

  get size(): number {
    return this.#size;
  }

  set size(value: string | number | boolean) {
    let num = Number(value);

    // Don&#39;t allow NaN and stuff.
    if (!Number.isFinite(num)) {
      this.#size = 0;
      return;
    }

    this.#size = num;
  }
}</code></pre>
<p>위의 예제에서, <code>set</code> 접근자는 더 넓은 타입 집합 (문자열, 불리언 및 숫자)을 사용하지만 <code>get</code> 접근자는 항상 숫자임을 보장한다. 이제 우리는 다른 타입의 값을 오류없이 이러한 속성에 할당할 수 있다.</p>
<pre><code class="language-ts">let thing = new Thing();

// Assigning other types to `thing.size` works!
thing.size = &quot;hello&quot;;
thing.size = true;
thing.size = 42;

// Reading `thing.size` always produces a number!
let mySize: number = thing.size;</code></pre>
<p>동일한 이름을 갖는 두 속성이 어떻게 관련되는지 고려할 때, TypeScript는 &quot;읽기&quot; 타입 (위의 get 접근자의 타입)만 사용한다. &quot;쓰기&quot; 타입은 직접 속성에 쓸 때만 고려된다.</p>
<p>이는 클래스에만 제한된 패턴이 아니다. 객체 리터럴에서도 다른 타입의 getter와 setter를 작성할 수 있다.</p>
<pre><code class="language-ts">function makeThing(): Thing {
  let size = 0;
  return {
    get size(): number {
      return size;
    },
    set size(value: string | number | boolean) {
      let num = Number(value);
      // Don&#39;t allow NaN and stuff.
      if (!Number.isFinite(num)) {
        size = 0;
        return;
      }
      size = num;
    },
  };
}</code></pre>
<p>사실, 인터페이스/객체 타입에는 속성에 대한 다양한 읽기/쓰기 타입을 지원하기 위한 문법이 추가되었다.</p>
<pre><code class="language-ts">// Now valid!
interface Thing {
    get size(): number
    set size(value: number | string | boolean);
}</code></pre>
<p>속성의 읽기와 쓰기에 대해 사로 다른 타입을 사용하는 것의 제한 사항 중 하나는 속성을 읽는 데 사용되는 타입이 쓰는 타입에 할당 가능해야 한다는 것이다. 다시 말해, getter 타입은 setter에 할당 가능해야 한다. 이렇게 함으로써 일관성 수준이 보장되어 속성이 항상 자신에게 할당 가능하도록 유지된다.</p>
<h2 id="override-and-the---noimplicitoverride-flag"><code>override</code> and the <code>--noImplicitOverride</code> Flag</h2>
<p>JavaScript에서 클래스를 확장할 때, 언어 자체에서 메서드를 오버라이드하기가 매우 쉽지만, 불행하게도 발생할 수 있는 몇 가지 오류가 있다.</p>
<p>가장 큰 문제 중 하나는 이름 변경을 빼먹는 것이다. 예를 들어, 다음 클래스를 살펴보자.</p>
<pre><code class="language-ts">class SomeComponent {
  show() {
    // ...
  }
  hide() {
    // ...
  }
}
class SpecializedComponent extends SomeComponent {
  show() {
    // ...
  }
  hide() {
    // ...
  }
}</code></pre>
<p><code>SpecializedComponent</code>는 <code>SomeComponent</code>를 상속하며 <code>show</code> 및 <code>hide</code> 메서드를 오버라이드한다. 그러나 누군가 <code>show</code>와 <code>hide</code>를 제거하고 단일 메서드로 대체하면 어떻게 될까?</p>
<pre><code class="language-ts"> class SomeComponent {
-    show() {
-        // ...
-    }
-    hide() {
-        // ...
-    }
+    setVisible(value: boolean) {
+        // ...
+    }
 }
 class SpecializedComponent extends SomeComponent {
     show() {
         // ...
     }
     hide() {
         // ...
     }
 }</code></pre>
<p>이런! <code>SpecializedComponent</code>가 업데이트되지 않았다. 이제 이것은 호출되지 않을 불필요한 <code>show</code> 및 <code>hide</code> 메서드를 추가하고 있다.</p>
<p>이 문제의 일부는 사용자가 새로운 메서드를 추가할 것인지 기존 메서드를 오버라이드할 것인지 명확하게 할 수 없다는 것이다. 이것이 TypeScript 4.3에서 <code>override</code> 키워드가 추가된 이유이다.</p>
<pre><code class="language-ts">class SpecializedComponent extends SomeComponent {
    override show() {
        // ...
    }
    override hide() {
        // ...
    }
}</code></pre>
<p><code>override</code>가 지정된 메서드는 TypeScript가 항상 기본 클래스에 동일한 이름의 메서드가 존재하는지 확인한다.</p>
<pre><code class="language-ts">class SomeComponent {
    setVisible(value: boolean) {
        // ...
    }
}
class SpecializedComponent extends SomeComponent {
    override show() {
This member cannot have an &#39;override&#39; modifier because it is not declared in the base class &#39;SomeComponent&#39;.

    }
}</code></pre>
<p>이것은 큰 개선이지만, 메서드에 <code>override</code>를 작성하는 것을 잊어버리면 도움이 되지 않는다. 이것도 사용자가 발생할 수 있는 큰 실수이다.</p>
<p>예를 들어, 기본 클래스에 있는 메서드가 존재하는데도 불구하고 그것을 모르고 덮어쓸 수도 있다.</p>
<pre><code class="language-ts">class Base {
  someHelperMethod() {
    // ...
  }
}
class Derived extends Base {
  // Oops! We weren&#39;t trying to override here,
  // we just needed to write a local helper method.
  someHelperMethod() {
    // ...
  }
}</code></pre>
<p>그래서 TypeScript 4.3에서는 <code>noImplicitOverride</code> 플래그도 제공한다. 이 옵션을 켜면 <code>override</code> 키워드를 명시적으로 사용하지 않는 한 수퍼클래스의 메서드를 오버라이드하는 것이 오류가 된다. 마지막 예제에서 TypeScript는 <code>noImplicitOverride</code> 하에서 오류가 되며, <code>Derived</code> 내부의 메서드 이름을 변경해야 할 필요가 있다는 단서를 제공한다.</p>
<h2 id="템플릿-문자열-타입-개선사항">템플릿 문자열 타입 개선사항</h2>
<p>TypeScript의 최근 버전에서 새로운 타입 구조인 &quot;템플릿 문자열 타입(template string types)&quot;이 추가되었다. 이 타입은 문자열과 유사한 타입을 연결(concatenate)해서 새로운 타입을 만들 수 있다.</p>
<pre><code class="language-ts">type Color = &quot;red&quot; | &quot;blue&quot;;
type Quantity = &quot;one&quot; | &quot;two&quot;;
type SeussFish = `${Quantity | Color} fish`;
// same as
//   type SeussFish = &quot;one fish&quot; | &quot;two fish&quot;
//                  | &quot;red fish&quot; | &quot;blue fish&quot;;</code></pre>
<p>그리고 이 타입은 다른 문자열 유사 타입의 패턴(match patterns)을 표현할 수도 있다.</p>
<pre><code class="language-ts">declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
// Works!
s1 = s2;</code></pre>
<p>첫 번째 변경 사항은 TypeScript가 템플릿 문자열 타입을 추론하는 시기이다. TypeScript가 우리가 리터럴 타입을 사용해야 하는 것을 인식할 때 (즉, 우리가 리터럴 타입을 사용해야 하는 것을 받아들일 때) 템플릿 문자열이 문맥적으로 문자열 리터럴 타입으로 타입화될 때, TypeScript는 그 식에 대해 템플릿 타입을 할당하려고 한다.</p>
<pre><code class="language-ts">function bar(s: string): `hello ${string}` {
    // Previously an error, now works!
    return `hello ${s}`;
}</code></pre>
<p>이는 또한 타입 추론시에도 적용된다. 그리고 타입 파라미터가 string을 확장(extends)하는 경우에도 적용된다.</p>
<pre><code class="language-ts">declare let s: string;
declare function f&lt;T extends string&gt;(x: T): T;
// Previously: string
// Now       : `hello ${string}`
let x2 = f(`hello ${s}`);</code></pre>
<p>두 번째 주요 변경 사항은 TypeScript가 이제 서로 다른 템플릿 문자열 타입 간의 관계를 더 잘 파악하고 추론할 수 있다는 것이다.</p>
<p>이를 확인하기 위해 다음 예제 코드를 살펴보자.</p>
<pre><code class="language-ts">declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
declare let s3: `${number}-2-3`;
s1 = s2;
s1 = s3;</code></pre>
<p><code>s2</code>와 같은 문자열 리터럴 타입을 검사할 때, TypeScript는 문자열 내용을 매치하여 <code>s2</code>가 첫 번째 할당에서 <code>s1</code>과 호환됨을 알아낼 수 있었지만, 다른 템플릿 문자열을 보자마자 포기해 버렸다. 결과적으로 <code>s3</code>를 <code>s1</code>에 할당하는 것과 같은 할당은 작동하지 않았다.</p>
<p>이제 TypeScript는 각 템플릿 문자열의 각 부분이 성공적으로 일치하는지 여부를 증명하기 위해 작업을 수행한다. 이제 서로 다른 치환과 함께 템플릿 문자열을 혼합하여 사용할 수 있으며, TypeScript가 실제로 호환되는지 여부를 잘 알아낼 것이다.</p>
<pre><code class="language-ts">declare let s1: `${number}-${number}-${number}`;
declare let s2: `1-2-3`;
declare let s3: `${number}-2-3`;
declare let s4: `1-${number}-3`;
declare let s5: `1-2-${number}`;
declare let s6: `${number}-2-${number}`;
// Now *all of these* work!
s1 = s2;
s1 = s3;
s1 = s4;
s1 = s5;
s1 = s6;</code></pre>
<p>이 작업을 수행함에 따라, 우리는 더 나은 추론 능력도 추가했다. 이러한 기능이 어떻게 작동하는지 예제를 보자.</p>
<pre><code class="language-ts">declare function foo&lt;V extends string&gt;(arg: `*${V}*`): V;
function test&lt;T extends string&gt;(s: string, n: number, b: boolean, t: T) {
    let x1 = foo(&quot;*hello*&quot;);            // &quot;hello&quot;
    let x2 = foo(&quot;**hello**&quot;);          // &quot;*hello*&quot;
    let x3 = foo(`*${s}*` as const);    // string
    let x4 = foo(`*${n}*` as const);    // `${number}`
    let x5 = foo(`*${b}*` as const);    // &quot;true&quot; | &quot;false&quot;
    let x6 = foo(`*${t}*` as const);    // `${T}`
    let x7 = foo(`**${s}**` as const);  // `*${string}*`
}</code></pre>
<h2 id="ecmascript-private-클래스-멤버">ECMAScript <code>#private</code> 클래스 멤버</h2>
<p>TypeScript 4.3에서는 클래스의 어떤 멤버가 <code>#private</code> <code>#names</code>으로 지정될 수 있는지가 확장되어 런타임에서 진정한 비공개로 만들 수 있다. 이제 속성(property)뿐만 아니라 메서드(method)와 접근자(accessor)도 비공개 이름을 가질 수 있다.</p>
<pre><code class="language-ts">class Foo {
  #someMethod() {
    //...
  }
  get #someValue() {
    return 100;
  }
  publicMethod() {
    // These work.
    // We can access private-named members inside this class.
    this.#someMethod();
    return this.#someValue;
  }
}
new Foo().#someMethod();
//        ~~~~~~~~~~~
// error!
// Property &#39;#someMethod&#39; is not accessible
// outside class &#39;Foo&#39; because it has a private identifier.
new Foo().#someValue;
//        ~~~~~~~~~~
// error!
// Property &#39;#someValue&#39; is not accessible
// outside class &#39;Foo&#39; because it has a private identifier.</code></pre>
<p>더 넓게는, 이제 정적 멤버(static members)도 비공개 이름을 가질 수 있다.</p>
<pre><code class="language-ts">class Foo {
  static #someMethod() {
    // ...
  }
}
Foo.#someMethod();
//  ~~~~~~~~~~~
// error!
// Property &#39;#someMethod&#39; is not accessible
// outside class &#39;Foo&#39; because it has a private identifier.</code></pre>
<h2 id="constructorparameters가-추상-클래스에서도-동작"><code>ConstructorParameters</code>가 추상 클래스에서도 동작</h2>
<p>TypeScript 4.3에서 <code>ConstructorParameters</code> 타입 헬퍼는 이제 추상 클래스에서도 작동한다.</p>
<pre><code class="language-ts">abstract class C {
  constructor(a: string, b: number) {
    // ...
  }
}
// Has the type &#39;[a: string, b: number]&#39;.
type CParams = ConstructorParameters&lt;typeof C&gt;;</code></pre>
<p>이것은 TypeScript 4.2에서 수행된 작업 덕분이다. 해당 작업에서 구성 시그니처(construct signatures)를 추상으로 표시할 수 있게 되었다.</p>
<pre><code class="language-ts">type MyConstructorOf&lt;T&gt; = {
    abstract new(...args: any[]): T;
}
// or using the shorthand syntax:
type MyConstructorOf&lt;T&gt; = abstract new (...args: any[]) =&gt; T;</code></pre>
<h2 id="제네릭에-대한-문맥적-좁힘contextual-narrowing">제네릭에 대한 문맥적 좁힘(Contextual Narrowing)</h2>
<p>TypeScript 4.3에는 이제 제네릭 값에 대한 약간 더 똑똑한 타입 좁힘 로직이 포함된다. 이를 통해 TypeScript는 더 많은 패턴을 허용하고 때로는 실수를 잡을 수도 있다.</p>
<p>이해를 돕기 위해 <code>Set</code> 또는 <code>Array</code> 의 원소를 받아서 중복을 제거하는 비교 함수에 따라 해당 <code>Array</code>를 정렬하는 함수인 <code>makeUnique</code>를 작성하려고 한다고 가정해 보자. 그 후에는 원래의 컬렉션을 반환한다.</p>
<pre><code class="language-ts">function makeUnique&lt;T&gt;(
  collection: Set&lt;T&gt; | T[],
  comparer: (x: T, y: T) =&gt; number
): Set&lt;T&gt; | T[] {
  // Early bail-out if we have a Set.
  // We assume the elements are already unique.
  if (collection instanceof Set) {
    return collection;
  }
  // Sort the array, then remove consecutive duplicates.
  collection.sort(comparer);
  for (let i = 0; i &lt; collection.length; i++) {
    let j = i;
    while (
      j &lt; collection.length &amp;&amp;
      comparer(collection[i], collection[j + 1]) === 0
    ) {
      j++;
    }
    collection.splice(i + 1, j - i);
  }
  return collection;
}</code></pre>
<p>이 함수의 구현과 관련된 질문들은 일단 뒤로 두고, 이 함수가 더 넓은 응용프로그램의 요구사항에서 유래되었다고 가정해보자. 여기서 주목할 점 중 하나는 해당 시그니처가 원래 컬렉션의 타입을 포착하지 않는다는 것이다. <code>Set&lt;T&gt; | T[]</code>가 쓰인 자리에 <code>C</code>라는 타입 파라미터를 추가하여 이를 해결할 수 있다.</p>
<pre><code class="language-ts">- function makeUnique&lt;T&gt;(collection: Set&lt;T&gt; | T[], comparer: (x: T, y: T) =&gt; number): Set&lt;T&gt; | T[]
+ function makeUnique&lt;T, C extends Set&lt;T&gt; | T[]&gt;(collection: C, comparer: (x: T, y: T) =&gt; number): C</code></pre>
<p>TypeScript 4.2 이전 버전에서는 이렇게 하면 곧바로 오류가 발생한다.</p>
<pre><code class="language-ts">function makeUnique&lt;T, C extends Set&lt;T&gt; | T[]&gt;(
  collection: C,
  comparer: (x: T, y: T) =&gt; number
): C {
  // Early bail-out if we have a Set.
  // We assume the elements are already unique.
  if (collection instanceof Set) {
    return collection;
  }
  // Sort the array, then remove consecutive duplicates.
  collection.sort(comparer);
  //         ~~~~
  // error: Property &#39;sort&#39; does not exist on type &#39;C&#39;.
  for (let i = 0; i &lt; collection.length; i++) {
    //                             ~~~~~~
    // error: Property &#39;length&#39; does not exist on type &#39;C&#39;.
    let j = i;
    while (
      j &lt; collection.length &amp;&amp;
      comparer(collection[i], collection[j + 1]) === 0
    ) {
      //                    ~~~~~~
      // error: Property &#39;length&#39; does not exist on type &#39;C&#39;.
      //                                       ~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~
      // error: Element implicitly has an &#39;any&#39; type because expression of type &#39;number&#39;
      //        can&#39;t be used to index type &#39;Set&lt;T&gt; | T[]&#39;.
      j++;
    }
    collection.splice(i + 1, j - i);
    //         ~~~~~~
    // error: Property &#39;splice&#39; does not exist on type &#39;C&#39;.
  }
  return collection;</code></pre>
<p>오류가 발생하는 이유는, 우리가 <code>컬렉션의 instanceof Set</code>을 체크할 때, 우리는 이를 타입 가드(Type Guard)로 작동하여 <code>Set&lt;T&gt; | T[]</code> 타입을 해당 브랜치에 따라 <code>Set&lt;T&gt;</code> 또는 <code>T[]</code>로 좁히기를 기대하기 때문이다. 그러나 <code>Set&lt;T&gt; | T[]</code>가 아니라 <code>C</code>라는 제네릭 값을 좁히려고 하는 것이다.</p>
<p>이것은 매우 미묘한 차이이지만, 차이가 있다. TypeScript는 <code>C</code>의 제약 조건(<code>Set&lt;T&gt; | T[]</code>)을 가져와서 좁히는 것이 아니다. TypeScript가 <code>Set&lt;T&gt; | T[]</code>에서 좁히려고 한다면, TypeScript는 해당 정보를 보존할 수 있는 쉬운 방법이 없기 때문에 각 브랜치에서 컬렉션도 C라는 사실을 잊어버릴 것이다. 가상으로 TypeScript가 이러한 접근 방식을 시도한다면 위의 예제는 다른 방식으로 중단될 것이다. 함수가 <code>C</code> 타입의 값을 반환하는 위치에서, TypeScript는 각 브랜치에서 <code>Set&lt;T&gt;</code>와 <code>T[]</code>를 얻을 것이며, TypeScript는 이를 거부할 것이다.</p>
<pre><code class="language-ts">function makeUnique&lt;T&gt;(
  collection: Set&lt;T&gt; | T[],
  comparer: (x: T, y: T) =&gt; number
): Set&lt;T&gt; | T[] {
  // Early bail-out if we have a Set.
  // We assume the elements are already unique.
  if (collection instanceof Set) {
    return collection;
    //     ~~~~~~~~~~
    // error: Type &#39;Set&lt;T&gt;&#39; is not assignable to type &#39;C&#39;.
    //          &#39;Set&lt;T&gt;&#39; is assignable to the constraint of type &#39;C&#39;, but
    //          &#39;C&#39; could be instantiated with a different subtype of constraint &#39;Set&lt;T&gt; | T[]&#39;.
  }
  // ...
  return collection;
  //     ~~~~~~~~~~
  // error: Type &#39;T[]&#39; is not assignable to type &#39;C&#39;.
  //          &#39;T[]&#39; is assignable to the constraint of type &#39;C&#39;, but
  //          &#39;C&#39; could be instantiated with a different subtype of constraint &#39;Set&lt;T&gt; | T[]&#39;.
}</code></pre>
<p>그렇다면 TypeScript 4.3에서는 어떤 변화가 있을까? 크게 몇 가지 핵심적인 부분에서, 코드 작성 시 타입 시스템이 실제로 관심을 가지는 것은 타입의 제약 조건뿐이다. 예를 들어, <code>collection.length</code>를 작성할 때 TypeScript는 <code>collection</code>이 <code>C</code>라는 사실에 관심이 없으며, 제약 조건 <code>T[] | Set&lt;T&gt;</code>에 의해 결정된 속성에만 관심이 있다.</p>
<p>이러한 경우, TypeScript는 제약 조건의 좁힌 타입을 가져온다. 왜냐하면 이것이 실제로 필요한 데이터이기 때문이다. 그러나 다른 경우에는 원래 제네릭 타입을 좁히려고 시도할 것이다(그리고 종종 원래 제네릭 타입으로 끝날 것이다).</p>
<p>즉, 제네릭 값을 사용하는 방식에 따라 TypeScript가 약간 다르게 좁힐 수 있다. 결과적으로 위의 예제 전체가 타입 체크 오류 없이 컴파일된다.</p>
<h2 id="always-truthy-promise-확인">Always-Truthy Promise 확인</h2>
<p><code>strictNullChecks</code> 모드에서, 조건부에서 Promise가 &quot;truthy&quot;한지 확인하면 오류가 발생한다.</p>
<pre><code class="language-ts">async function foo(): Promise&lt;boolean&gt; {
  return false;
}
async function bar(): Promise&lt;string&gt; {
  if (foo()) {
    //  ~~~~~
    // Error!
    // This condition will always return true since
    // this &#39;Promise&lt;boolean&gt;&#39; appears to always be defined.
    // Did you forget to use &#39;await&#39;?
    return &quot;true&quot;;
  }
  return &quot;false&quot;;
}</code></pre>
<h2 id="static-인덱스-시그니처"><code>static</code> 인덱스 시그니처</h2>
<p>인덱스 시그니처는 타입에서 명시적으로 선언한 속성보다 더 많은 속성을 값에 설정할 수 있게 한다.</p>
<pre><code class="language-ts">class Foo {
  hello = &quot;hello&quot;;
  world = 1234;
  // This is an index signature:
  [propName: string]: string | number | undefined;
}
let instance = new Foo();
// Valid assigment
instance[&quot;whatever&quot;] = 42;
// Has type &#39;string | number | undefined&#39;.
let x = instance[&quot;something&quot;];</code></pre>
<p>지금까지 인덱스 시그니처는 클래스의 인스턴스 측면에서만 선언할 수 있었다. 그러나 Wenlu Wang 님의 기여로 인해 인덱스 시그니처를 <code>static</code>으로 선언할 수 있게 되었다.</p>
<pre><code class="language-ts">class Foo {
  static hello = &quot;hello&quot;;
  static world = 1234;
  static [propName: string]: string | number | undefined;
}
// Valid.
Foo[&quot;whatever&quot;] = 42;
// Has type &#39;string | number | undefined&#39;
let x = Foo[&quot;something&quot;];</code></pre>
<p>인스턴스 측에서와 마찬가지로 클래스의 정적 측에 있는 인덱스 시그니처에도 동일한 종류의 규칙이 적용된다. 즉, 다른 모든 정적 속성이 인덱스 서명과 호환되어야 한다는 것이다.</p>
<pre><code class="language-ts">class Foo {
  static prop = true;
  //     ~~~~
  // Error! Property &#39;prop&#39; of type &#39;boolean&#39;
  // is not assignable to string index type
  // &#39;string | number | undefined&#39;.
  static [propName: string]: string | number | undefined;
}</code></pre>
<h2 id="tsbuildinfo-사이즈-개선"><code>.tsbuildinfo</code> 사이즈 개선</h2>
<p>TypeScript 4.3에서, 증분 빌드의 일부로 생성되는 <code>.tsbuildinfo</code> 파일의 크기가 크게 줄어든다. 내부 형식에서 몇 가지 최적화를 수행하여 전체 경로와 유사한 정보를 반복하지 않고 파일 전체에서 사용할 수 있는 숫자 식별자로 테이블을 만들었다. </p>
<p>우리는 다음과 같은 <code>.tsbuildinfo</code> 파일 크기의 큰 감소를 보았다.</p>
<ul>
<li>1MB에서 411 KB로</li>
<li>14.9MB에서 1MB로</li>
<li>1345MB에서 467MB로</li>
</ul>
<p>말할 필요 없이, 이렇게 크기를 줄이면 빌드 시간도 약간 더 빨라진다.</p>
<h2 id="--incremental-와---watch-편집에서-지연-계산"><code>--incremental</code> 와 <code>--watch</code> 편집에서 지연 계산</h2>
<p>증분 및 --watch 모드의 문제 중 하나는 나중에 컴파일하는 속도가 빨라지지만 초기 컴파일 속도가 약간 느려질 수 있으며 경우에 따라서는 상당히 느려질 수 있다는 것이다. 이 모드는 현재 프로젝트에 대한 정보를 계산하고, 때로는 나중에 빌드할 수 있도록 해당 데이터를 <code>.tsbuildinfo</code> 파일에 저장하는 등 많은 장부 작업을 수행해야 하기 때문이다.</p>
<p>그렇기 때문에 TypeScript 4.3에서는 <code>.tsbuildinfo</code> 크기 개선 외에도 이러한 플래그가 있는 프로젝트의 첫 번째 빌드를 일반 빌드만큼 빠르게 만드는 증분 및 <code>--watch</code> 모드에 대한 몇 가지 변경 사항이 제공된다! 이를 위해 일반적으로 미리 계산해야 하는 많은 정보를 나중에 빌드할 때 온디맨드 방식으로 계산한다. 이렇게 하면 후속 빌드에 약간의 오버헤드가 추가될 수 있지만 TypeScript의 증분 및 --watch 기능은 일반적으로 훨씬 작은 파일 집합에서 작동하며 필요한 정보는 나중에 저장된다. 어떤 의미에서 증분 및 --watch 빌드는 파일을 몇 번 업데이트하면 &quot;워밍업&quot;을 거쳐 컴파일 속도가 빨라진다.</p>
<p>3000개의 파일이 있는 리포지토리에서 초기 빌드 시간이 거의 1/3로 단축되었다!</p>
<h2 id="import-문-완료">Import 문 완료</h2>
<p>자바스크립트에서 import 및 export 문에서 사용자가 가장 많이 겪는 문제 중 하나는 순서이다. 특히 import는 다른 구문보다 먼저 작성되어야 한다는 것이다.</p>
<pre><code class="language-ts">from &quot;./module.js&quot; import { func };

/* 대신에 아래 처럼 작성해야함 */

import { func } from &quot;./module.js&quot;;</code></pre>
<p>이로 인해 자동 완성 기능이 제대로 작동하지 않아 전체 가져오기 문을 처음부터 작성할 때 약간의 어려움이 있다. 예를 들어 <code>import {</code> 와 같은 문장을 작성하기 시작하면 TypeScript는 어떤 모듈에서 가져올 계획인지 알지 못하기 때문에 범위가 지정된 완성을 제공할 수 없다.</p>
<p>이 문제를 완화하기 위해 자동 가져오기 기능을 활용했다! 자동 가져오기는 특정 모듈에서 완성된 내용을 좁힐 수 없는 문제를 이미 해결한 기능으로, 가능한 모든 내보내기를 제공하고 파일 상단에 가져오기 문을 자동으로 삽입하는 것이 핵심이다.</p>
<p>따라서 이제 경로가 없는 가져오기 문을 작성하기 시작하면 가능한 가져오기 목록이 제공된다. 완료를 커밋하면 작성하려고 했던 경로를 포함하여 전체 가져오기 명령문이 완성된다.</p>
<h2 id="link-태그를-에디터에서-지원"><code>@link</code> 태그를 에디터에서 지원</h2>
<p>TypeScript가 이제 <code>@link</code> 태그를 이해하고, 해당 태그에서 참조한 선언을 해결할 수 있게 되었다. 즉, <code>@link</code> 태그 내에서 이름 위에 마우스를 가져가면 빠른 정보를 얻거나, go-to-definition이나 find-all-references와 같은 명령을 사용할 수 있다.</p>
<p>예를 들어, 아래 예제의 <code>@link bar</code>에서 <code>bar</code>에 대한 go-to-definition을 할 수 있고, TypeScript가 지원하는 편집기에서는 <code>bar</code>의 함수 선언으로 이동한다.</p>
<pre><code class="language-js">/**
 * To be called 70 to 80 days after {@link plantCarrot}.
 */
function harvestCarrot(carrot: Carrot) {}
/**
 * Call early in spring for best results. Added in v2.1.0.
 * @param seed Make sure it&#39;s a carrot seed!
 */
function plantCarrot(seed: Seed) {
  // TODO: some gardening
}</code></pre>
<h2 id="자바스크립트가-아닌-파일-경로에서-정의-바로가기">자바스크립트가 아닌 파일 경로에서 정의 바로가기</h2>
<p>이제 많은 로더들은 자바스크립트를 사용하여 애플리케이션에 에셋을 포함시킬 수 있도록 해준다. 일반적으로 <code>import &quot;./styles.css&quot;</code>와 같이 작성된다.</p>
<p>지금까지 TypeScript의 편집기 기능은 이 파일을 읽어들이지 못하기 때문에 go-to-definition이 실패했다. 최선의 경우에도 go-to-definition은 <code>declare module &quot;*.css&quot;</code>와 같은 선언으로 점프할 수 있었다.</p>
<p>이제 TypeScript의 언어 서비스는 상대 파일 경로에 대한 go-to-definition을 수행할 때 올바른 파일로 점프하려고 시도한다. 자바스크립트 또는 TypeScript 파일이 아닌 경우에도 CSS, SVG, PNG, 폰트 파일, Vue 파일 등의 가져온 파일에 대해서도 시도할 수 있다.</p>
<h2 id="주요-변경사항">주요 변경사항</h2>
<h3 id="libdts-변화"><code>lib.d.ts</code> 변화</h3>
<p>모든 TypeScript 버전과 마찬가지로, <code>lib.d.ts</code>에 대한 선언(특히 웹 컨텍스트에 대한 생성된 선언)이 변경되었다. 이번 릴리스에서는 Mozilla의 browser-compat-data를 활용하여 브라우저에서 구현되지 않은 API를 제거했다. 대부분의 경우에는 사용하지 않겠지만, <code>Account, AssertionOptions, RTCStatsEventInit, MSGestureEvent, DeviceLightEvent, MSPointerEvent, ServiceWorkerMessageEvent 및 WebAuthentication</code>과 같은 API가 <code>lib.d.ts</code>에서 제거되었다.</p>
<h3 id="esnext와-es2022-에서-usedefineforclassfields-기본-값의-참"><code>esnext</code>와 <code>es2022</code> 에서 <code>useDefineForClassFields</code> 기본 값의 참</h3>
<p>2021년에는 클래스 필드 기능이 JavaScript 사양에 추가되었으며 TypeScript에서 구현 방법과 다른 동작을 하였다. 이에 대비하여 TypeScript 3.7에서는 useDefineForClassFields 플래그가 추가되어 JavaScript 표준 동작과 일치하도록 마이그레이션되는 것이 가능해졌다.</p>
<p>이제 이 기능이 JavaScript에 포함되었으므로 ES2022 및 ESNext를 포함한 기본값을 true로 변경한다.</p>
<h3 id="always-truthy-promise-확인에서-에러">Always-Truthy Promise 확인에서 에러</h3>
<p><code>strictNullChecks</code> 옵션을 사용할 때, 조건 체크 내에서 항상 정의된 것처럼 보이는 <code>Promise</code>를 사용하는 것은 이제 오류로 간주된다.</p>
<pre><code class="language-ts">declare var p: Promise&lt;number&gt;;
if (p) {
  //  ~
  // Error!
  // This condition will always return true since
  // this &#39;Promise&lt;number&gt;&#39; appears to always be defined.
  //
  // Did you forget to use &#39;await&#39;?
}</code></pre>
<h3 id="union-enums은-임의의-숫자와-비교할-수-없음">Union Enums은 임의의 숫자와 비교할 수 없음</h3>
<p>TypeScript 4.3에서는 멤버가 자동으로 채워지거나 간단하게 작성될 때 일부 열거형은 유니온 열거형으로 간주된다. 이 경우, 열거형은 나타낼 수 있는 각 값에 대해 기억할 수 있다.</p>
<p>유니온 열거형 타입을 가진 값이 그 값과 동일할 수 없는 숫자 리터럴과 비교되면 타입 체커가 오류를 발생시킨다.</p>
<pre><code class="language-ts">enum E {
  A = 0,
  B = 1,
}
function doSomething(x: E) {
  // Error! This condition will always return &#39;false&#39; since the types &#39;E&#39; and &#39;-1&#39; have no overlap.
  if (x === -1) {
    // ...
  }
}</code></pre>
<p>해결 방법으로, 적절한 리터럴 타입을 포함하는 주석을 다시 작성할 수 있다</p>
<pre><code class="language-ts">enum E {
  A = 0,
  B = 1,
}
// Include -1 in the type, if we&#39;re really certain that -1 can come through.
function doSomething(x: E | -1) {
  if (x === -1) {
    // ...
  }
}</code></pre>
<p>값에 타입 단언을 사용할 수도 있다.</p>
<pre><code class="language-ts">enum E {
  A = 0,
  B = 1,
}
function doSomething(x: E) {
  // Use a type asertion on &#39;x&#39; because we know we&#39;re not actually just dealing with values from &#39;E&#39;.
  if ((x as number) === -1) {
    // ...
  }
}</code></pre>
<p>또는 열거형에 일반적이지 않은 초기화 값을 사용하도록 열거형을 다시 선언하여 모든 숫자를 할당할 수 있고 해당 열거형과 비교할 수 있도록 할 수 있다. 이 방법은 열거형에 잘 알려진 몇 가지 값을 지정하려는 의도가 있는 경우 유용할 수 있다.</p>
<pre><code class="language-ts">enum E {
  // the leading + on 0 opts TypeScript out of inferring a union enum.
  A = +0,
  B = 1,
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 4.2 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-4.2-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-4.2-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Sat, 04 Mar 2023 08:17:59 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-2.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-2.html</a></p>
</blockquote>
<h2 id="더-스마트한-타입-별칭-보존">더 스마트한 타입 별칭 보존</h2>
<p>타입스크립트에는 타입 별칭이라고 하는 타입의 새 이름을 선언하는 방법이 있다. <code>string | number | boolean</code>에서 모두 작동하는 함수 집합을 작성하는 경우 타입 별칭을 작성하여 반복되는 작업을 피할 수 있다.</p>
<pre><code class="language-ts">type BasicPrimitive = number | string | boolean;</code></pre>
<p>TS는 타입을 출력할 때, 타입 별칭을 재사용할 시점에 대해 항상 일련의 규칙과 추측을 사용했다. 예를 들어 다음 코드 스니펫을 보자.</p>
<pre><code class="language-ts">export type BasicPrimitive = number | string | boolean;
export function doStuff(value: BasicPrimitive) {
  let x = value;
  return x;
}</code></pre>
<p>Visual Studio, Visual Studio Code 또는 TypeScript Playground와 같은 편집기에서 마우스 커서를 <code>x</code>로 가져가면 <code>BasicPrimitive</code> 타입이 표시된 빠른 정보 패널이 나타난다. 마찬가지로 이 파일에 대한 선언 파일 출력(<code>.d.ts</code> 출력)을 가져오는 경우 TypeScript는 <code>doStuff</code>가 <code>BasicPrimitive</code>를 반환한다고 말한다.</p>
<p>하지만 <code>BasicPrimitive</code> 혹은 <code>undefined</code>를 반환하는 경우 어떻게 될까?</p>
<pre><code class="language-ts">export type BasicPrimitive = number | string | boolean;
export function doStuff(value: BasicPrimitive) {
  if (Math.random() &lt; 0.5) {
    return undefined;
  }
  return value;
}</code></pre>
<p>TypeScript 4.1 플레이그라운드에서 어떤 일이 일어나는지 확인할 수 있다.
TypeScript가 <code>doStuff</code>의 반환 타입을 <code>BasicPrimitive | undefined</code>으로 표시하기를 원하지만, 대신 <code>string | number | boolean | undefined</code>으로 표시된다! 왜 그럴까?</p>
<p>이는 타입스크립트가 내부적으로 타입을 표현하는 방식과 관련이 있다. 하나 이상의 유니온 타입에서 유니온 타입을 생성할 때 항상 해당 타입을 새로운 flattened된 유니온 유형으로 정규화하지만 그렇게 하면 정보가 손실된다. 타입체커는 <code>tring | number | boolean | undefined</code>에서 모든 타입 조합을 찾아서 어떤 타입 별칭이 사용되었는지 확인해야 하며, 그 경우에도 <code>tring | number | boolean</code>에 대한 여러 타입 별칭이 있을 수 있다.</p>
<p>TypeScript 4.2에서는 내부가 좀 더 스마트해졌다. 시간이 지남에 따라 타입이 원래 어떻게 작성되고 구성되었는지에 대한 부분을 유지함으로써 타입이 어떻게 구성되었는지 추적할 수 있다. 또한 타입 별칭을 추적하고 다른 별칭의 인스턴스와 구별한다!</p>
<p>코드에서 사용한 방식에 따라 유형을 다시 출력할 수 있다는 것은 TypeScript 사용자로서 안타깝게도 많은 타입이 표시되는 것을 피할 수 있다는 의미이며, 이는 종종 빠른 정보 및 서명 도움말에서 <code>.d.ts</code> 파일 출력, 오류 메시지 및 편집기 내 타입 표시가 개선되는 것으로 해석된다. 이를 통해 TypeScript를 처음 접하는 분들에게 조금 더 친근하게 다가갈 수 있다.</p>
<h2 id="튜플-타입의-첫중간-나머지-요소">튜플 타입의 첫/중간 나머지 요소</h2>
<p>TS에서 튜플 타입은 특정 길이와 요소 타입을 가진 배열을 모델링하기 위한 것이다.</p>
<pre><code class="language-ts">// A tuple that stores a pair of numbers
let a: [number, number] = [1, 2];
// A tuple that stores a string, a number, and a boolean
let b: [string, number, boolean] = [&quot;hello&quot;, 42, true];</code></pre>
<p>시간이 지남에 따라 TypeScript의 튜플 타입은 JavaScript의 매개변수 목록과 같은 것을 모델링하는 데에도 사용되기 때문에 점점 더 정교해졌다. 그 결과 선택적 요소와 나머지 요소를 가질 수 있고, 도구와 가독성을 위한 레이블을 가질 수도 있다.</p>
<pre><code class="language-ts">// A tuple that has either one or two strings.
let c: [string, string?] = [&quot;hello&quot;];
c = [&quot;hello&quot;, &quot;world&quot;];

// A labeled tuple that has either one or two strings.
let d: [first: string, second?: string] = [&quot;hello&quot;];
d = [&quot;hello&quot;, &quot;world&quot;];

// A tuple with a *rest element* - holds at least 2 strings at the front,
// and any number of booleans at the back.
let e: [string, string, ...boolean[]];

e = [&quot;hello&quot;, &quot;world&quot;];
e = [&quot;hello&quot;, &quot;world&quot;, false];
e = [&quot;hello&quot;, &quot;world&quot;, true, false, true];</code></pre>
<p>TypeScript 4.2에서는 rest 요소의 사용 방법이 특히 확장되었다. 이전 버전에서 TypeScript는 튜플 타입의 맨 마지막 위치에만 <code>...rest</code> 요소를 허용했다.</p>
<p>그러나 이제 몇 가지 제한 사항만 적용하면 튜플 내 어느 위치에서나 rest 요소를 사용할 수 있다.</p>
<pre><code class="language-ts">let foo: [...string[], number];

foo = [123];
foo = [&quot;hello&quot;, 123];
foo = [&quot;hello!&quot;, &quot;hello!&quot;, &quot;hello!&quot;, 123];

let bar: [boolean, ...string[], boolean];

bar = [true, false];
bar = [true, &quot;some text&quot;, false];
bar = [true, &quot;some&quot;, &quot;separated&quot;, &quot;text&quot;, false];</code></pre>
<p>유일한 제한 사항은 다른 선택적 요소나 나머지 요소가 뒤에 오지 않는 한 나머지 요소는 튜플의 어느 곳에나 배치할 수 있다는 것이다. 즉, 튜플당 rest 요소는 하나만 있고 rest 요소 뒤에는 선택 요소가 없어야 한다.</p>
<pre><code class="language-ts">interface Clown {
  /*...*/
}
interface Joker {
  /*...*/
}

let StealersWheel: [...Clown[], &quot;me&quot;, ...Joker[]];
// A rest element cannot follow another rest element.

let StringsAndMaybeBoolean: [...string[], boolean?];
// An optional element cannot follow a rest element.</code></pre>
<p>이러한 후행이 아닌 rest 요소는 몇 개의 선행 인수와 몇 개의 고정 인수가 뒤따르는 함수를 모델링하는 데 사용할 수 있다.</p>
<pre><code class="language-ts">declare function doStuff(...args: [...names: string[], shouldCapitalize: boolean]): void;

doStuff(/*shouldCapitalize:*/ false)
doStuff(&quot;fee&quot;, &quot;fi&quot;, &quot;fo&quot;, &quot;fum&quot;, /*shouldCapitalize:*/ true);</code></pre>
<p>JavaScript에는 선행 rest 매개변수를 모델링하는 구문이 없지만, 선행 rest 요소를 사용하는 튜플 타입으로 <code>...args</code> rest 매개변수를 선언하여 선행 인수를 취하는 함수로 <code>doStuff</code>를 선언할 수 있었다. 이렇게 하면 기존의 많은 자바스크립트를 모델링하는 데 도움이 될 수 있다!</p>
<h2 id="in-연산자에-대한-더-엄격한-검사"><code>in</code> 연산자에 대한 더 엄격한 검사</h2>
<p>JS에서 <code>in</code> 연산자의 오른쪽에 객체가 아닌 타입을 사용하는 것은 런타임 에러이다. TypeScript 4.2에서는 이를 디자인 타임(코드를 작성하는 순간)에 포착할 수 있다.</p>
<pre><code class="language-ts">&quot;foo&quot; in 42;
// The right-hand side of an &#39;in&#39; expression must not be a primitive.</code></pre>
<p>이 검사는 대부분 상당히 보수적이므로 이와 관련된 오류가 발생하면 코드에 문제가 있는 것일 수 있다.</p>
<h2 id="--nopropertyaccessfromindexsignature"><code>--noPropertyAccessFromIndexSignature</code></h2>
<p>TypeScript가 처음 인덱스 시그니처를 도입했을 때만 해도 <code>person[&quot;name&quot;]</code>과 같이 &#39;괄호로 묶인&#39; 엘리먼트 액세스 구문을 사용하여 선언된 프로퍼티만 가져올 수 있었다.</p>
<pre><code class="language-ts">interface SomeType {
  /** This is an index signature. */
  [propName: string]: any;
}

function doStuff(value: SomeType) {
  let x = value[&quot;someProperty&quot;];
}</code></pre>
<p>이는 임의의 속성을 가진 객체로 작업해야 하는 상황에서 번거로웠다. 예를 들어, 프로퍼티 이름 끝에 <code>s</code>를 추가하여 철자를 잘못 입력하는 것이 일반적인 API를 상상해 보자.</p>
<pre><code class="language-ts">interface Options {
  /** File patterns to be excluded. */
  exclude?: string[];

  /**
   * It handles any extra properties that we haven&#39;t declared as type &#39;any&#39;.
   */
  [x: string]: any;
}

function processOptions(opts: Options) {
  // Notice we&#39;re *intentionally* accessing `excludes`, not `exclude`
  if (opts.excludes) {
    console.error(
      &quot;The option `excludes` is not valid. Did you mean `exclude`?&quot;
    );
  }
}</code></pre>
<p>이러한 상황을 더 쉽게 처리하기 위해 얼마 전 TypeScript는 타입에 문자열 인덱스 시그니처가 있는 경우 <code>person.name</code>과 같은 &quot;점선&quot; 속성 액세스 구문을 사용할 수 있도록 했다. 이를 통해 기존 JavaScript 코드를 TypeScript로 쉽게 전환할 수 있게 되었다.</p>
<p>그러나 제한이 완화되면서 명시적으로 선언된 프로퍼티의 철자를 잘못 입력하는 것이 훨씬 쉬워졌다.</p>
<pre><code class="language-ts">function processOptions(opts: Options) {
  // ...

  // Notice we&#39;re *accidentally* accessing `excludes` this time.
  // Oops! Totally valid.
  for (const excludePattern of opts.excludes) {
    // ...
  }
}</code></pre>
<p>어떤 경우에는 사용자가 인덱스 시그니처를 명시적으로 선택하기를 원할 수도 있다. 점으로 표시된 속성 액세스가 특정 속성 선언과 일치하지 않을 때 오류 메시지가 표시되기를 원하기 때문이다.</p>
<p>이것이 바로 TypeScript가 <code>noPropertyAccessFromIndexSignature</code>라는 새로운 플래그를 도입한 이유이다. 이 모드에서는 오류를 발생시키는 TypeScript의 이전 동작을 선택하게 됩니다. 이 새로운 설정은 사용자가 특정 코드베이스에서 다른 코드베이스보다 더 유용하다고 생각하기 때문에 엄격한 플래그 계열에 속하지 않았다.</p>
<h2 id="abstract-생성자-시그니처"><code>abstract</code> 생성자 시그니처</h2>
<p>타입스크립트에서는 클래스를 <code>abstract</code>으로 표시할 수 있다. 이는 TypeScript가 클래스를 확장하기 위한 용도로만 사용되며, 실제로 인스턴스를 생성하려면 특정 멤버를 서브클래스에서 채워야 한다는 것을 알려준다.</p>
<pre><code class="language-ts">abstract class Shape {
  abstract getArea(): number;
}

new Shape();
// Cannot create an instance of an abstract class.

class Square extends Shape {
  #sideLength: number;

  constructor(sideLength: number) {
    super();
    this.#sideLength = sideLength;
  }

  getArea() {
    return this.#sideLength ** 2;
  }
}

// Works fine.
new Square(42);</code></pre>
<p><code>abstract</code> 클래스를 새로 생성할 때 이러한 제한이 일관되게 적용되도록 하려면 생성자 시그니처가 필요한 모든 항목에 추상 클래스를 할당할 수 없다.</p>
<pre><code class="language-ts">interface HasArea {
  getArea(): number;
}

let Ctor: new () =&gt; HasArea = Shape;
// Type &#39;typeof Shape&#39; is not assignable to type &#39;new () =&gt; HasArea&#39;.
//  Cannot assign an abstract constructor type to a non-abstract constructor type.</code></pre>
<p>이는 <code>new Ctor</code>와 같은 코드를 실행하려는 경우에는 올바르게 작동하지만, <code>Ctor</code>의 서브클래스를 작성하려는 경우에는 지나치게 제한적이다.</p>
<pre><code class="language-ts">abstract class Shape {
  abstract getArea(): number;
}

interface HasArea {
  getArea(): number;
}

function makeSubclassWithArea(Ctor: new () =&gt; HasArea) {
  return class extends Ctor {
    getArea() {
      return 42
    }
  };
}

let MyShape = makeSubclassWithArea(Shape);
// Argument of type &#39;typeof Shape&#39; is not assignable to parameter of type &#39;new () =&gt; HasArea&#39;.
//  Cannot assign an abstract constructor type to a non-abstract constructor type.</code></pre>
<p>또한 <code>InstanceType</code>과 같은 기본 제공 헬퍼 유형에서는 잘 작동하지 않는다.</p>
<pre><code class="language-ts">type MyInstance = InstanceType&lt;typeof Shape&gt;;</code></pre>
<p>이것이 바로 타입스크립트 4.2에서 생성자 시그니처에 <code>abstract</code> 수정자를 지정할 수 있는 이유이다.</p>
<pre><code class="language-ts">interface HasArea {
    getArea(): number;
}

// Works!
let Ctor: abstract new () =&gt; HasArea = Shape;</code></pre>
<p>생성자 시그니처에 <code>abstract</code> 수정자를 추가하면 <code>abstract</code> 생성자에서 전달할 수 있다는 신호가 된다. &quot;구체적인&quot; 다른 클래스/생성자 함수를 전달하는 것을 막는 것이 아니라, 생성자를 직접 실행할 의도가 없다는 신호일 뿐이므로 어느 클래스 유형으로 전달해도 안전하다.</p>
<p>이 기능을 사용하면 추상 클래스를 지원하는 방식으로 믹스인 팩토리를 작성할 수 있다. 예를 들어, 다음 코드 스니펫에서는 믹스인 함수 <code>withStyles</code>를 <code>abstract</code> 클래스 <code>SuperClass</code>와 함께 사용할 수 있다.</p>
<pre><code class="language-ts">abstract class SuperClass {
    abstract someMethod(): void;
    badda() {}
}

type AbstractConstructor&lt;T&gt; = abstract new (...args: any[]) =&gt; T

function withStyles&lt;T extends AbstractConstructor&lt;object&gt;&gt;(Ctor: T) {
    abstract class StyledClass extends Ctor {
        getStyles() {
            // ...
        }
    }
    return StyledClass;
}

class SubClass extends withStyles(SuperClass) {
    someMethod() {
        this.someMethod()
    }
}</code></pre>
<p><code>withStyles</code>은 일반적이고 추상 생성자(예: <code>Ctor</code>)에 의해 경계가 지정된 값을 확장하는 클래스(예: <code>StyledClass</code>)도 추상적으로 선언해야 한다는 특정 규칙을 보여드리고 있다는 점에 유의하자. 추상 멤버가 더 많은 클래스가 전달되었는지 알 수 있는 방법이 없기 때문에 하위 클래스가 모든 추상 멤버를 구현하는지 여부를 알 수 없기 때문이다.</p>
<h2 id="--explainfiles를-사용하여-프로젝ㅌ-구조-이해하기"><code>--explainFiles</code>를 사용하여 프로젝ㅌ 구조 이해하기</h2>
<p>타입스크립트 사용자들이 의외로 흔히 하는 질문은 &quot;왜 타입스크립트에 이 파일이 포함되나요?&quot;이다. 프로그램의 파일을 추론하는 것은 복잡한 과정이기 때문에 특정 조합의 <code>lib.d.ts</code>가 사용된 이유, <code>node_modules</code>의 특정 파일이 포함된 이유, <code>exclude</code>를 지정하면 제외될 줄 알았는데 특정 파일이 포함된 이유 등 여러 가지 이유가 있을 수 있다.</p>
<p>이것이 바로 TypeScript가 이제 <code>explainFiles</code> 플래그를 제공하는 이유이다.</p>
<pre><code class="language-bash">tsc --explainFiles</code></pre>
<p>이 옵션을 사용하면 TypeScript 컴파일러에서 파일이 프로그램에 포함된 이유에 대한 매우 자세한 출력을 제공한다. 이 출력을 더 쉽게 읽으려면 출력을 파일로 전달하거나 쉽게 볼 수 있는 프로그램으로 파이프할 수 있다.</p>
<pre><code class="language-bash"># Forward output to a text file
tsc --explainFiles &gt; explanation.txt

# Pipe output to a utility program like `less`, or an editor like VS Code
tsc --explainFiles | less

tsc --explainFiles | code -</code></pre>
<p>일반적으로 출력은 <code>lib.d.ts</code> 파일을 포함해야 하는 이유를 나열하는 것으로 시작하여 로컬 파일, <code>node_modules</code> 파일 순으로 나열한다.</p>
<pre><code>TS_Compiler_Directory/4.2.2/lib/lib.es5.d.ts
  Library referenced via &#39;es5&#39; from file &#39;TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts&#39;
TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts
  Library referenced via &#39;es2015&#39; from file &#39;TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts&#39;
TS_Compiler_Directory/4.2.2/lib/lib.es2016.d.ts
  Library referenced via &#39;es2016&#39; from file &#39;TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts&#39;
TS_Compiler_Directory/4.2.2/lib/lib.es2017.d.ts
  Library referenced via &#39;es2017&#39; from file &#39;TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts&#39;
TS_Compiler_Directory/4.2.2/lib/lib.es2018.d.ts
  Library referenced via &#39;es2018&#39; from file &#39;TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts&#39;
TS_Compiler_Directory/4.2.2/lib/lib.es2019.d.ts
  Library referenced via &#39;es2019&#39; from file &#39;TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts&#39;
TS_Compiler_Directory/4.2.2/lib/lib.es2020.d.ts
  Library referenced via &#39;es2020&#39; from file &#39;TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts&#39;
TS_Compiler_Directory/4.2.2/lib/lib.esnext.d.ts
  Library &#39;lib.esnext.d.ts&#39; specified in compilerOptions

... More Library References...

foo.ts
  Matched by include pattern &#39;**/*&#39; in &#39;tsconfig.json&#39;</code></pre><p>현재로서는 출력 형식에 대해 보장할 수 없으며 시간이 지남에 따라 변경될 수 있다. </p>
<h2 id="논리-표현식에서-호출되지-않은-함수-검사-기능-개선">논리 표현식에서 호출되지 않은 함수 검사 기능 개선</h2>
<p>TS의 호출되지 않은 함수 검사가 <code>&amp;&amp;</code> 및 <code>||</code> 표현식 내에 적용된다.</p>
<p><code>strictNullChecks</code>에서는 이제 다음 코드에서 오류가 발생한다.</p>
<pre><code class="language-ts">function shouldDisplayElement(element: Element) {
  // ...
  return true;
}
function getVisibleItems(elements: Element[]) {
  return elements.filter((e) =&gt; shouldDisplayElement &amp;&amp; e.children.length);
  //                          ~~~~~~~~~~~~~~~~~~~~
  // This condition will always return true since the function is always defined.
  // Did you mean to call it instead.
}</code></pre>
<h2 id="디스트럭처링된-변수를-명시적으로-미사용으로-표시할-수-있다">디스트럭처링된 변수를 명시적으로 미사용으로 표시할 수 있다</h2>
<p>이제 디스트럭처링된 변수 앞에 밑줄(<code>_</code> 문자)을 붙여 사용하지 않는 것으로 표시할 수 있다.</p>
<pre><code class="language-ts">let [_first, second] = getValues();</code></pre>
<p>이전에는 <code>_first</code>가 나중에 한 번도 사용되지 않으면 TypeScript가 <code>noUnusedLocals</code>에서 오류를 발생시켰다. 이제 TypeScript는 <code>_first</code>를 사용할 의도가 없었기 때문에 의도적으로 밑줄을 넣어 명명했음을 인식한다.</p>
<h2 id="선택적-속성과-문자열-인덱스-시그니처-간의-완화된-규칙">선택적 속성과 문자열 인덱스 시그니처 간의 완화된 규칙</h2>
<p>문자열 인덱스 시그니처는 임의의 키로 액세스를 허용하려는 사전과 같은 객체를 입력하는 방식이다</p>
<pre><code class="language-ts">const movieWatchCount: { [key: string]: number } = {};

function watchMovie(title: string) {
  movieWatchCount[title] = (movieWatchCount[title] ?? 0) + 1;
}</code></pre>
<p>물론 아직 사전에 없는 영화 제목의 경우 <code>movieWatchCount[title]</code>은 <code>undefined</code>이다(TypeScript 4.1에서는 이와 같은 인덱스 시그니처에서 읽을 때 <code>undefined</code>을 포함하도록 <code>noUncheckedIndexedAccess</code> 옵션이 추가되었다). <code>movieWatchCount</code>에 존재하지 않는 문자열이 있어야 한다는 것이 분명하지만, 이전 버전의 TypeScript에서는 <code>undefined</code>이기 때문에 선택적 객체 속성을 다른 호환 가능한 인덱스 시그니처에 할당할 수 없는 것으로 처리했다.</p>
<pre><code class="language-ts">type WesAndersonWatchCount = {
  &quot;Fantastic Mr. Fox&quot;?: number;
  &quot;The Royal Tenenbaums&quot;?: number;
  &quot;Moonrise Kingdom&quot;?: number;
  &quot;The Grand Budapest Hotel&quot;?: number;
};

declare const wesAndersonWatchCount: WesAndersonWatchCount;
const movieWatchCount: { [key: string]: number } = wesAndersonWatchCount;
//    ~~~~~~~~~~~~~~~ error!
// Type &#39;WesAndersonWatchCount&#39; is not assignable to type &#39;{ [key: string]: number; }&#39;.
//    Property &#39;&quot;Fantastic Mr. Fox&quot;&#39; is incompatible with index signature.
//      Type &#39;number | undefined&#39; is not assignable to type &#39;number&#39;.
//        Type &#39;undefined&#39; is not assignable to type &#39;number&#39;. (2322)</code></pre>
<p>타입스크립트 4.2에서는 이 할당이 허용된다. 그러나 타입이 <code>undefined</code>와 함께 비선택적 속성의 할당은 허용되지 않으며, 특정 키에 <code>undefined</code> 쓰기도 허용되지 않는다.</p>
<pre><code class="language-ts">type BatmanWatchCount = {
  &quot;Batman Begins&quot;: number | undefined;
  &quot;The Dark Knight&quot;: number | undefined;
  &quot;The Dark Knight Rises&quot;: number | undefined;
};

declare const batmanWatchCount: BatmanWatchCount;

// Still an error in TypeScript 4.2.
const movieWatchCount: { [key: string]: number } = batmanWatchCount;
Type &#39;BatmanWatchCount&#39; is not assignable to type &#39;{ [key: string]: number; }&#39;.
  Property &#39;&quot;Batman Begins&quot;&#39; is incompatible with index signature.
    Type &#39;number | undefined&#39; is not assignable to type &#39;number&#39;.
      Type &#39;undefined&#39; is not assignable to type &#39;number&#39;.

// Still an error in TypeScript 4.2.
// Index signatures don&#39;t implicitly allow explicit `undefined`.
movieWatchCount[&quot;It&#39;s the Great Pumpkin, Charlie Brown&quot;] = undefined;
Type &#39;undefined&#39; is not assignable to type &#39;number&#39;.</code></pre>
<p>숫자 인덱스 시그니처는 배열과 유사하고 밀도가 높다고 가정하므로 새 규칙은 숫자 인덱스 시그니처에는 적용되지 않는다</p>
<pre><code class="language-ts">declare let sortOfArrayish: { [key: number]: string };
declare let numberKeys: { 42?: string };

sortOfArrayish = numberKeys;
//  Type &#39;{ 42?: string | undefined; }&#39; is not assignable to type &#39;{ [key: number]: string; }&#39;.
//  Property &#39;42&#39; is incompatible with index signature.
//    Type &#39;string | undefined&#39; is not assignable to type &#39;string&#39;.
//      Type &#39;undefined&#39; is not assignable to type &#39;string&#39;.</code></pre>
<h2 id="누락된-헬퍼-함수-선언">누락된 헬퍼 함수 선언</h2>
<p>이제 호출한 코드를 기반으로 새로운 함수와 메서드를 선언할 수 있는 빠른 수정 기능이 추가되었다!</p>
<h2 id="주요-변경-사항">주요 변경 사항</h2>
<p>TS 4.2에는 몇 가지 중요한 변경 사항이 포함되어 있지만 업그레이드에서 관리할 수 있는 수준이라고 생각한다.</p>
<h3 id="libdts-updates"><code>lib.d.ts</code> Updates</h3>
<p>모든 타입스크립트 버전과 마찬가지로 <code>lib.d.ts</code>의 선언(특히 웹 컨텍스트에 대해 생성된 선언)이 변경되었다. 여러 가지 변경 사항이 있지만 <code>Intl</code>과 <code>ResizeObserver</code>의 변경 사항이 가장 큰 영향을 미칠 수 있다.</p>
<h3 id="noimplicitany-에러가-느슨한-yield-표현식에-적용"><code>noImplicitAny</code> 에러가 느슨한 <code>yield</code> 표현식에 적용</h3>
<p><code>yield</code> 표현식의 값이 캡처되었지만 TypeScript가 수신하려는 타입을 즉시 파악할 수 없는 경우(즉, <code>yield</code> 표현식이 컨텍스트에 맞게 타입화되지 않은 경우) 이제 TypeScript에서 암시적 오류를 발생시킨다.</p>
<pre><code class="language-ts">function* g1() {
  const value = yield 1;
&#39;yield&#39; expression implicitly results in an &#39;any&#39; type because its containing generator lacks a return-type annotation.
}

function* g2() {
  // No error.
  // The result of `yield 1` is unused.
  yield 1;
}

function* g3() {
  // No error.
  // `yield 1` is contextually typed by &#39;string&#39;.
  const value: string = yield 1;
}

function* g4(): Generator&lt;number, void, string&gt; {
  // No error.
  // TypeScript can figure out the type of `yield 1`
  // from the explicit return type of `g4`.
  const value = yield 1;</code></pre>
<h3 id="확장된-호출되지-않은-함수-검사">확장된 호출되지 않은 함수 검사</h3>
<p>위에서 설명한 대로, 이제 <code>strictNullChecks</code>를 사용할 때 호출되지 않은 함수 검사가 <code>&amp;&amp;</code> 및 <code>||</code> 표현식 내에서 일관되게 작동한다. 이는 새로운 중단의 원인이 될 수 있지만 일반적으로 기존 코드의 논리 오류를 나타냅니다.</p>
<h3 id="javascript의-타입-인수가-타입-인수로-파싱되지-않음">JavaScript의 타입 인수가 타입 인수로 파싱되지 않음</h3>
<p>자바스크립트에서는 이미 타입 인수가 허용되지 않았지만, 타입스크립트 4.2에서는 파서가 보다 사양을 준수하는 방식으로 타입 인수를 구문 분석한다. 따라서 JavaScript 파일에 다음 코드를 작성할 때</p>
<pre><code class="language-js">f&lt;T&gt;(100);</code></pre>
<p>TypeScript는 이를 다음과 같은 자바스크립트로 구문 분석한다.</p>
<pre><code class="language-ts">f &lt; T &gt; 100;</code></pre>
<p>이 문제는 자바스크립트 파일의 타입 구문을 구문 분석하기 위해 타입스크립트 API를 사용하는 경우 영향을 미칠 수 있으며, 이는 Flow 파일을 구문 분석하려고 할 때 발생할 수 있다.</p>
<h3 id="스프레드에-대한-튜플-크기-제한">스프레드에 대한 튜플 크기 제한</h3>
<p>타입스크립트에서 스프레드 구문 (<code>...</code>)을 사용하여 튜플 유형을 만들 수 있다.</p>
<pre><code class="language-ts">// Tuple types with spread elements
type NumStr = [number, string];
type NumStrNumStr = [...NumStr, ...NumStr];

// Array spread expressions
const numStr = [123, &quot;hello&quot;] as const;
const numStrNumStr = [...numStr, ...numStr] as const;</code></pre>
<p>때때로 이러한 튜플 타입은 실수로 커질 수 있으며, 이로 인해 타입 검사에 오랜 시간이 걸릴 수 있다. 타입 검사 프로세스가 중단되는 대신(편집기 시나리오에서 특히 나쁘다), 타입스크립트에는 이 모든 작업을 수행하지 않도록 하는 리미터가 있다.</p>
<h3 id="가져오기-경로에-dts-확장자를-사용할-수-없음">가져오기 경로에 <code>.d.ts</code> 확장자를 사용할 수 없음</h3>
<p>TypeScript 4.2에서는 이제 가져오기 경로의 확장자에 .d.ts가 포함되는 것이 오류이다.</p>
<pre><code class="language-ts">// must be changed something like
//   - &quot;./foo&quot;
//   - &quot;./foo.js&quot;
import { Foo } from &quot;./foo.d.ts&quot;;</code></pre>
<p>대신, 임포트 경로는 런타임에 로더가 수행하는 모든 작업을 반영해야 한다. 다음 임포트를 대신 사용할 수 있다.</p>
<pre><code class="language-ts">import { Foo } from &quot;./foo&quot;;
import { Foo } from &quot;./foo.js&quot;;
import { Foo } from &quot;./foo/index.js&quot;;</code></pre>
<h3 id="템플릿-리터럴-추론-되돌리기">템플릿 리터럴 추론 되돌리기</h3>
<p>이 변경으로 인해 TypeScript 4.2 베타 버전에서 기능이 제거되었다. 마지막 안정 릴리스 이후 아직 업그레이드하지 않았다면 영향을 받지 않지만 변경 사항에 관심이 있을 수 있다.</p>
<p>TypeScript 4.2 베타 버전에는 템플릿 문자열에 대한 추론 기능이 변경되었다. 이 변경 사항에서는 템플릿 문자열 리터럴에 템플릿 문자열 유형이 주어지거나 여러 문자열 리터럴 유형으로 단순화된다. 그런 다음 이러한 타입은 가변 변수에 할당할 때 <code>string</code>으로 확장된다.</p>
<pre><code class="language-ts">declare const yourName: string;
// &#39;bar&#39; is constant.
// It has type &#39;`hello ${string}`&#39;.
const bar = `hello ${yourName}`;
// &#39;baz&#39; is mutable.
// It has type &#39;string&#39;.
let baz = `hello ${yourName}`;</code></pre>
<p>이는 문자열 리터럴 추론이 작동하는 방식과 유사하다.</p>
<pre><code class="language-ts">// &#39;bar&#39; has type &#39;&quot;hello&quot;&#39;.
const bar = &quot;hello&quot;;
// &#39;baz&#39; has type &#39;string&#39;.
let baz = &quot;hello&quot;;</code></pre>
<p>이러한 이유로 템플릿 문자열 표현식에 템플릿 문자열 타입이 있으면 &#39;일관성&#39;이 유지될 것이라고 생각했지만, 보고 들은 바에 따르면 이것이 항상 바람직한 것은 아니다.</p>
<p>이에 따라 이 기능(그리고 잠재적인 변경 사항)을 되돌렸다. 템플릿 문자열 표현식에 리터럴과 유사한 타입을 부여하고 싶다면 언제든지 표현식 끝에 const를 추가하면 된다.</p>
<pre><code class="language-ts">declare const yourName: string;
// &#39;bar&#39; has type &#39;`hello ${string}`&#39;.
const bar = `hello ${yourName}` as const;
//                              ^^^^^^^^
// &#39;baz&#39; has type &#39;string&#39;.
const baz = `hello ${yourName}`;</code></pre>
<h3 id="visitnode안의-타입스크립트의-lift-콜백은-다른-타입을-사용"><code>visitNode</code>안의 타입스크립트의 <code>lift</code> 콜백은 다른 타입을 사용</h3>
<p>타입스크립트에는 <code>lift</code> 함수를 받는 <code>visitNode</code> 함수가 있다. 이제 <code>lift</code>는 <code>NodeArray&lt;Node&gt;</code> 대신 <code>readonly Node[]</code>를 기대한다. 이는 기술적으로 API를 깨는 변경 사항으로, 자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/42000">여기</a>에서 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 4.1 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-4.1-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-4.1-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Tue, 14 Feb 2023 08:56:39 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-1.html</a></p>
</blockquote>
<h2 id="템플릿-리터럴-타입">템플릿 리터럴 타입</h2>
<p>TS의 문자열 리터럴 타입은 특정한 문자열 집합을 요구하는 함수와 API를 모델링하는데 사용된다.</p>
<pre><code class="language-ts">function setVerticalAlignment(location: &quot;top&quot; | &quot;middle&quot; | &quot;bottom&quot;) {
  // ...
}

setVerticalAlignment(&quot;middel&quot;);
// Argument of type &#39;&quot;middel&quot;&#39; is not assignable to parameter of type &#39;&quot;top&quot; | &quot;middle&quot; | &quot;bottom&quot;&#39;.</code></pre>
<p>이는 문자열 리터럴 타입이 문자열 값의 철자 오류를 검사할 수 있기 때문에 꽤 유용하다.</p>
<p>또한 문자열 리터럴은 매핑된 타입에서 속성 이름으로 사용될 수 있다는 것도 좋은 점이다. 이런 면에서 문자열 리터럴은 구성 요소로서 사용될 수도 있다.</p>
<pre><code class="language-ts">type Options = {
  [K in &quot;noImplicitAny&quot; | &quot;strictNullChecks&quot; | &quot;strictFunctionTypes&quot;]?: boolean;
};
// same as
//   type Options = {
//       noImplicitAny?: boolean,
//       strictNullChecks?: boolean,
//       strictFunctionTypes?: boolean
//   };</code></pre>
<p>더하여 문자열 리터럴 타입은 다른 문자열 리터럴 타입을 구성하는 데에 사용될 수 있다.</p>
<pre><code class="language-ts">type World = &quot;world&quot;;

type Greeting = `hello ${World}`;

// type Greeting = &quot;hello world&quot;</code></pre>
<p>치환 위치에서 유니온 타입이 있는 경우 유니온 멤버가 나타낼 수 있는 모든 가능한 문자열 리터럴 집합을 생성한다.</p>
<pre><code class="language-ts">type Color = &quot;red&quot; | &quot;blue&quot;;
type Quantity = &quot;one&quot; | &quot;two&quot;;

type SeussFish = `${Quantity | Color} fish`;
// type SeussFish = &quot;one fish&quot; | &quot;two fish&quot; | &quot;red fish&quot; | &quot;blue fish&quot;</code></pre>
<p>이러한 기능은 다음과 같은 상황에서 사용될 수 있다.
UI 컴포넌트를 위한 여러 라이브러리는 수직 및 수평 정렬을 모두 지정하는 방법을 제공한다. 종종 단일 문자열인 <code>&quot;bottom-right&quot;</code>와 같이 둘 다를 한 번에 사용하는데, <code>&quot;top&quot;, &quot;middle&quot;, &quot;bottom&quot;</code>으로 수직 정렬하고 <code>&quot;left&quot;, &quot;center&quot;, &quot;right&quot;</code>으로 수평 정렬할 때, 총 9가지의 경우의 수가 나온다.</p>
<pre><code class="language-ts">type VerticalAlignment = &quot;top&quot; | &quot;middle&quot; | &quot;bottom&quot;;
type HorizontalAlignment = &quot;left&quot; | &quot;center&quot; | &quot;right&quot;;

// Takes
//   | &quot;top-left&quot;    | &quot;top-center&quot;    | &quot;top-right&quot;
//   | &quot;middle-left&quot; | &quot;middle-center&quot; | &quot;middle-right&quot;
//   | &quot;bottom-left&quot; | &quot;bottom-center&quot; | &quot;bottom-right&quot;

declare function setAlignment(value: `${VerticalAlignment}-${HorizontalAlignment}`): void;

setAlignment(&quot;top-left&quot;);   // works!
setAlignment(&quot;top-middel&quot;); // error!
// Argument of type &#39;&quot;top-middel&quot;&#39; is not assignable to parameter of type &#39;&quot;top-left&quot; | &quot;top-center&quot; | &quot;top-right&quot; | &quot;middle-left&quot; | &quot;middle-center&quot; | &quot;middle-right&quot; | &quot;bottom-left&quot; | &quot;bottom-center&quot; | &quot;bottom-right&quot;&#39;.
setAlignment(&quot;top-pot&quot;);    // error! but good doughnuts if you&#39;re ever in Seattle
// Argument of type &#39;&quot;top-pot&quot;&#39; is not assignable to parameter of type &#39;&quot;top-left&quot; | &quot;top-center&quot; | &quot;top-right&quot; | &quot;middle-left&quot; | &quot;middle-center&quot; | &quot;middle-right&quot; | &quot;bottom-left&quot; | &quot;bottom-center&quot; | &quot;bottom-right&quot;&#39;.</code></pre>
<p>사실, 9개의 문자열에 대해서는 수동으로 작성하는 것이 더 나을 것이다. 그러나 많은 양의 문자열이 필요한 경우, 모든 타입 검사에 작업을 절약하기 위해 미리 자동으로 생성하는 것이 좋다.</p>
<p>실제 가치 중 일부는 동적으로 새로운 문자열 리터럴을 만드는 데서 온다. 예를 들어, 객체를 가져와 대부분 동일한 객체를 생성하지만 속성 변경을 감지하기 위한 새로운 on 메서드를 생성하는 makeWatchedObject API를 상상해보자.</p>
<pre><code class="language-ts">let person = makeWatchedObject({
  firstName: &quot;Homer&quot;,
  age: 42, // give-or-take
  location: &quot;Springfield&quot;,
});
person.on(&quot;firstNameChanged&quot;, () =&gt; {
  console.log(`firstName was changed!`);
});</code></pre>
<p>&quot;firstNameChanged&quot;와 같은 이벤트를 수신하는 on 메서드의 타입을 어떻게 지정해야 할까?</p>
<pre><code class="language-ts">type PropEventSource&lt;T&gt; = {
    on(eventName: `${string &amp; keyof T}Changed`, callback: () =&gt; void): void;
};
/// Create a &quot;watched object&quot; with an &#39;on&#39; method
/// so that you can watch for changes to properties.
declare function makeWatchedObject&lt;T&gt;(obj: T): T &amp; PropEventSource&lt;T&gt;;</code></pre>
<p>이를 통해 잘못된 속성을 제공할 때 오류가 발생하는 것을 만들 수 있다.</p>
<pre><code class="language-ts">// error!
person.on(&quot;firstName&quot;, () =&gt; {});
Argument of type &#39;&quot;firstName&quot;&#39; is not assignable to parameter of type &#39;&quot;firstNameChanged&quot; | &quot;ageChanged&quot; | &quot;locationChanged&quot;&#39;.

// error!
person.on(&quot;frstNameChanged&quot;, () =&gt; {});
Argument of type &#39;&quot;frstNameChanged&quot;&#39; is not assignable to parameter of type &#39;&quot;firstNameChanged&quot; | &quot;ageChanged&quot; | &quot;locationChanged&quot;&#39;.</code></pre>
<p>템플릿 리터럴 타입에서도 치환 위치에서 추론하는 등의 특별한 것을 할 수 있다. 마지막 예제를 일반화하여 <code>eventName</code> 문자열의 일부분에서 연관된 속성을 찾아내도록 유추할 수 있다.</p>
<pre><code class="language-ts">type PropEventSource&lt;T&gt; = {
    on&lt;K extends string &amp; keyof T&gt;
        (eventName: `${K}Changed`, callback: (newValue: T[K]) =&gt; void ): void;
};

declare function makeWatchedObject&lt;T&gt;(obj: T): T &amp; PropEventSource&lt;T&gt;;

let person = makeWatchedObject({
    firstName: &quot;Homer&quot;,
    age: 42,
    location: &quot;Springfield&quot;,
});

// works! &#39;newName&#39; is typed as &#39;string&#39;
person.on(&quot;firstNameChanged&quot;, newName =&gt; {
    // &#39;newName&#39; has the type of &#39;firstName&#39;
    console.log(`new name is ${newName.toUpperCase()}`);
});

// works! &#39;newAge&#39; is typed as &#39;number&#39;
person.on(&quot;ageChanged&quot;, newAge =&gt; {
    if (newAge &lt; 0) {
        console.log(&quot;warning! negative age&quot;);
    }
})</code></pre>
<p>여기서 on을 일반 메서드로 만들었다. 사용자가 <code>&quot;firstNameChanged&quot;</code> 문자열로 호출하면 TypeScript는 K에 대한 적절한 타입을 추론하려고 한다. 이를 위해 &quot;Changed&quot; 이전의 내용과 K를 대조하여 <code>&quot;firstName&quot;</code> 문자열을 추론한다. TypeScript가 이를 파악하면 on 메서드는 원래 객체에서 <code>firstName</code>의 타입을 가져올 수 있고, 이 경우에는 string이다. 비슷하게 <code>&quot;ageChanged&quot;</code>로 호출할 때는 숫자인 <code>age</code> 속성의 타입을 찾는다).</p>
<p>추론은 종종 문자열을 분해하고 다른 방식으로 재구성하는 데 다양한 방식으로 결합될 수 있다. 사실, 이러한 문자열 리터럴 타입 수정을 돕기 위해 문자 케이싱을 수정하기 위한 새로운 유틸리티 타입 별칭 몇 개가 추가되었다(예 : 소문자 및 대문자 문자로 변환).</p>
<pre><code class="language-ts">type EnthusiasticGreeting&lt;T extends string&gt; = `${Uppercase&lt;T&gt;}`

type HELLO = EnthusiasticGreeting&lt;&quot;hello&quot;&gt;;

// type HELLO = &quot;HELLO&quot;</code></pre>
<p>새로운 타입 별칭은 <code>Uppercase</code>, <code>Lowercase</code>, <code>Capitalize</code> 및 <code>Uncapitalize</code>이다. 처음 두 개는 문자열의 모든 문자를 변환하고, 나머지 두 개는 문자열의 첫 번째 문자만 변환한다.</p>
<h2 id="매핑된-타입의-키-재매핑">매핑된 타입의 키 재매핑</h2>
<p>매핑된 타입은 임의의 키 기반으로 새로운 객체 타입을 생성할 수 있다.</p>
<pre><code class="language-ts">type Options = {
  [K in &quot;noImplicitAny&quot; | &quot;strictNullChecks&quot; | &quot;strictFunctionTypes&quot;]?: boolean;
};
// same as
//   type Options = {
//       noImplicitAny?: boolean,
//       strictNullChecks?: boolean,
//       strictFunctionTypes?: boolean
//   };</code></pre>
<p>또는 다른 객체 타입을 기반으로 새로운 객체 타입을 만들 수도 있다.</p>
<pre><code class="language-ts">/// &#39;Partial&lt;T&gt;&#39; is the same as &#39;T&#39;, but with each property marked optional.
type Partial&lt;T&gt; = {
  [K in keyof T]?: T[K];
};</code></pre>
<p>지금까지 매핑된 타입은 제공된 키를 기반으로만 새로운 객체 타입을 생성할 수 있었다. 그러나 대부분의 경우 입력에 기반하여 새로운 키를 만들거나 기존 키를 필터링하려는 경우가 많다.</p>
<p>이것이 TypeScript 4.1에서 새로운 <code>as</code> 절을 사용하여 매핑된 타입에서 키를 다시 매핑할 수 있도록 허용하는 이유이다.</p>
<pre><code class="language-ts">type MappedTypeWithNewKeys&lt;T&gt; = {
    [K in keyof T as NewKeyType]: T[K]
    //            ^^^^^^^^^^^^^
    //            This is the new syntax!
}</code></pre>
<p>이 새로운 <code>as</code> 절을 사용하면 템플릿 리터럴 타입과 같은 기능을 활용하여 기존 속성을 기반으로 속성 이름을 쉽게 만들 수 있다.</p>
<pre><code class="language-ts">type Getters&lt;T&gt; = {
    [K in keyof T as `get${Capitalize&lt;string &amp; K&gt;}`]: () =&gt; T[K]
};

interface Person {
    name: string;
    age: number;
    location: string;
}

type LazyPerson = Getters&lt;Person&gt;;

type LazyPerson = {
    getName: () =&gt; string;
    getAge: () =&gt; number;
    getLocation: () =&gt; string;
}</code></pre>
<p>또한, <code>never</code>를 생성함으로써 키를 필터링할 수도 있다. 이는 경우에 따라 추가적인 <code>Omit</code> 헬퍼 타입을 사용하지 않아도 된다는 것을 의미한다.</p>
<pre><code class="language-ts">// Remove the &#39;kind&#39; property
type RemoveKindField&lt;T&gt; = {
    [K in keyof T as Exclude&lt;K, &quot;kind&quot;&gt;]: T[K]
};

interface Circle {
    kind: &quot;circle&quot;;
    radius: number;
}

type KindlessCircle = RemoveKindField&lt;Circle&gt;;

// type KindlessCircle = {
//     radius: number;
// }</code></pre>
<h2 id="재귀-조건부-타입">재귀 조건부 타입</h2>
<p>JS에선 임의의 레벨에서 컨테이너 타입을 펼치고 빌드하는 함수를 자주 볼 수 있다. 예를 들어 <code>Promise</code> 인스턴스의 <code>.then()</code> 메서드를 생각해보자. <code>.then(...)</code>은 &#39;promise-like&#39;이 아닌 값이 나올 때까지 각 프로미스를 풀고, 그 값을 콜백으로 전달한다. 또한 상대적으로 새로운 <code>flat</code> 메서드가 있고, 이는 배열을 얼마나 깊이 펼칠지를 나타내는 depth를 사용할 수 있다.</p>
<p>TS의 타입 시스템에서 이를 표현하는 것은 사실상 불가능하다. 이를 구현하기 위한 해키한 기법이 있지만, 타입은 매우 불합리해 보인다.</p>
<p>그래서 TS 4.1은 조건부 타입에 대한 제한을 완하하여 이러한 패턴을 모델링할 수 있도록 했다. TS 4.1에서 조건부 타입은 이제 분기 안에서 즉시 자신을 참조할 수 있으므로, 재귀적인 타입 별칭을 작성하기가 더 쉬워졌다.</p>
<p>예를들어, 중첩된 배열의 요소 타입을 가져오는 타입을 작성하려면 다음 <code>deepFlatten</code> 타입을 작성할 수 있다.</p>
<pre><code class="language-ts">type ElementType&lt;T&gt; = T extends ReadonlyArray&lt;infer U&gt; ? ElementType&lt;U&gt; : T;
function deepFlatten&lt;T extends readonly unknown[]&gt;(x: T): ElementType&lt;T&gt;[] {
  throw &quot;not implemented&quot;;
}
// All of these return the type &#39;number[]&#39;:
deepFlatten([1, 2, 3]);
deepFlatten([[1], [2, 3]]);
deepFlatten([[1], [[2]], [[[3]]]]);</code></pre>
<p>마찬가지로, TypeScript 4.1에서는 <code>Promise</code>를 깊게 풀어내는 <code>Awaited</code> 타입을 작성할 수 있다.</p>
<pre><code class="language-ts">type Awaited&lt;T&gt; = T extends PromiseLike&lt;infer U&gt; ? Awaited&lt;U&gt; : T;
/// Like `promise.then(...)`, but more accurate in types.
declare function customThen&lt;T, U&gt;(
  p: Promise&lt;T&gt;,
  onFulfilled: (value: Awaited&lt;T&gt;) =&gt; U
): Promise&lt;Awaited&lt;U&gt;&gt;;</code></pre>
<p>이러한 재귀적인 타입은 강력하지만, 책임 있고 절제되게 사용해야 한다는 것을 염두에 두어야 한다.</p>
<p>첫째로, 이러한 타입은 많은 작업을 수행할 수 있으며, 이는 타입 체크 시간을 증가시킬 수 있다는 것을 의미한다. 콜라츠 추측이나 피보나치 수열의 숫자를 모델링하는 것은 재미있을 수 있지만, 이를 <code>.d.ts</code> 파일에서 npm에 배포하는 것은 지양해야 한다.</p>
<p>또한 계산이 복잡할 뿐만 아니라, 이러한 타입은 충분히 복잡한 입력에 대해 내부 재귀 깊이 제한에 도달할 수 있다. 이 재귀 깊이 제한에 도달하면 컴파일 타임 오류가 발생한다. 일반적으로 실제적인 예제에서 실패하는 것보다는 이러한 타입을 사용하지 않는 것이 좋다.</p>
<h2 id="확인된-인덱스-액세스--nouncheckedindexedaccess">확인된 인덱스 액세스(<code>--noUncheckedIndexedAccess</code>)</h2>
<p>TypeScript에는 인덱스 시그니처라는 기능이 있다. 이러한 시그니처는 사용자가 임의로 이름을 지정한 속성에 액세스할 수 있다는 것을 타입 시스템에 알리는 방법이다.</p>
<pre><code class="language-ts">interface Options {
  path: string;
  permissions: number;

  // Extra properties are caught by this index signature.
  [propName: string]: string | number;
}

function checkOptions(opts: Options) {
  opts.path; // string
  opts.permissions; // number

  // These are all allowed too!
  // They have the type &#39;string | number&#39;.
  opts.yadda.toString();
  opts[&quot;foo bar baz&quot;].toString();
  opts[Math.random()].toString();
}</code></pre>
<p>위 예제에서 <code>Options</code>은 인덱스 시그니처를 갖고 있으며, 이미 나열된 속성이 아닌 모든 액세스 속성은 <code>string | number</code> 타입을 가져야한다고 나타낸다. 이것은 자신이 무엇을하고 있는지 알고 있다고 가정하는 낙관적인 코드에 대해 편리하다. 그러나 대부분의 JavaScript 값은 모든 잠재적 속성 이름을 지원하지 않는다. 예를 들어, 이전 예제와 같이 <code>Math.random()</code>에 의해 생성된 속성 키를 갖는 값은 대부분의 타입에서 지원되지 않는다. 많은 사용자들에게 이러한 동작은 원하지 않는 것이었으며, strictNullChecks의 전체 엄격한 검사를 활용하지 않는 것처럼 느껴졌다.</p>
<p>이것이 TypeScript 4.1에서 noUncheckedIndexedAccess라는 새로운 플래그가 제공되는 이유이다. 이 새로운 모드에서 모든 속성 액세스 (<code>foo.bar</code>와 같은) 또는 인덱스 액세스 (<code>foo[&quot;bar&quot;]</code>와 같은)는 잠재적으로 정의되지 않은 것으로 간주된다. 즉, 마지막 예제에서 <code>opts.yadda</code>는 <code>string | number</code> 대신 <code>string | number | undefined</code> 타입을 가지게된다. 그 속성에 액세스해야하는 경우, 먼저 존재 여부를 확인하거나 (후위 <code>!</code> 문자) non-null 단언 연산자를 사용해야한다.</p>
<pre><code class="language-ts">function checkOptions(opts: Options) {
  opts.path; // string
  opts.permissions; // number

  // These are not allowed with noUncheckedIndexedAccess
  opts.yadda.toString();
Object is possibly &#39;undefined&#39;.
  opts[&quot;foo bar baz&quot;].toString();
Object is possibly &#39;undefined&#39;.
  opts[Math.random()].toString();
Object is possibly &#39;undefined&#39;.

  // Checking if it&#39;s really there first.
  if (opts.yadda) {
    console.log(opts.yadda.toString());
  }

  // Basically saying &quot;trust me I know what I&#39;m doing&quot;
  // with the &#39;!&#39; non-null assertion operator.
  opts.yadda!.toString();
}</code></pre>
<p>noUncheckedIndexedAccess를 사용하면 경계 검사 반복문에서도 배열에 대한 인덱싱이 더 엄격하게 검사된다는 것이다.</p>
<pre><code class="language-ts">function screamLines(strs: string[]) {
  // This will have issues
  for (let i = 0; i &lt; strs.length; i++) {
    console.log(strs[i].toUpperCase());
// Object is possibly &#39;undefined&#39;.</code></pre>
<p>인덱스가 필요하지 않은 경우 <code>for-of</code> 루프 또는 <code>forEach</code> 호출을 사용하여 개별 요소를 반복할 수 있다.</p>
<pre><code class="language-ts">function screamLines(strs: string[]) {
  // This works fine
  for (const str of strs) {
    console.log(str.toUpperCase());
  }

  // This works fine
  strs.forEach((str) =&gt; {
    console.log(str.toUpperCase());
  });
}</code></pre>
<p>이 플래그는 범위를 벗어난 오류를 포착하는 데 유용할 수 있지만 많은 코드에서 노이즈가 많을 수 있으므로 strict 플래그에 의해 자동으로 활성화 되지 않는다. 그러나 이 기능이 흥미롭다면 자유롭게 사용해보고 팀의 코드베이스에 적합한지 판단해야 한다.</p>
<h2 id="baseurl이-없는-paths"><code>baseUrl</code>이 없는 <code>paths</code></h2>
<p>경로 매핑을 사용하는 것은 매우 일반적이다. 종종 <code>imports</code>를 더 좋게 하기 위해, 모노레포 연결 동작을 시뮬레이션 하기 위해 사용한다.</p>
<p>불행하게도 경로 매핑을 사용하도록 경로를 지정하려면 <code>baseUrl</code>이라는 옵션도 지정해야 했다. 이 옵션을 사용하면 기본 지정자 경로도 <code>baseUrl</code>에 상대적으로 도달할 수 있다. 이로 인해 종종 자동-imports에서 잘못된 경로가 사용되었다.</p>
<p>TS 4.1에선 <code>paths</code> 옵션을 <code>baseUrl</code> 없이 사용할 수 있다. 이렇게 하면 이러한 문제 중 일부를 방지할 수 있다.</p>
<h2 id="checkjs는-allowjs를-함축한다"><code>checkJs</code>는 <code>allowJs</code>를 함축한다</h2>
<p>이전에는 체크된 JS 프로젝트를 시작하는 경우 <code>allowJs</code>와 <code>checkJs</code>를 모두 설정해야 했다. 이것은 약간 성가신 일이었기 때문에 <code>checkJs</code>는 이제 기본적으로 <code>allowJs</code>를 의미한다.</p>
<h2 id="react-17-jsx-factories">React 17 JSX Factories</h2>
<p>TypeScript 4.1에서는 jsx 컴파일러 옵션을 위해 두 개의 새로운 옵션을 지원한다. 이를 통해 React 17에서 새롭게 도입된 <code>jsx</code>와 <code>jsxs</code> 팩토리 함수를 지원한다.</p>
<ul>
<li><code>react-jsx</code></li>
<li><code>react-jsxdev</code></li>
</ul>
<p>이러한 옵션들은 각각 production 및 development 컴파일용으로 만들어졌다. 종종 하나의 옵션에서 다른 옵션을 확장할 수 있다. 예를 들어, <code>tsconfig.json</code>은 다음과 같이 구성될 수 있다.</p>
<pre><code class="language-json">// ./src/tsconfig.json
{
  &quot;compilerOptions&quot;: {
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;target&quot;: &quot;es2015&quot;,
    &quot;jsx&quot;: &quot;react-jsx&quot;,
    &quot;strict&quot;: true
  },
  &quot;include&quot;: [&quot;./**/*&quot;]
}</code></pre>
<p>개발용 빌드는 다음과 같다.</p>
<pre><code class="language-json">// ./src/tsconfig.dev.json
{
  &quot;extends&quot;: &quot;./tsconfig.json&quot;,
  &quot;compilerOptions&quot;: {
    &quot;jsx&quot;: &quot;react-jsxdev&quot;
  }
}</code></pre>
<h2 id="에디터에서-jsdoc의-see-태그-지원">에디터에서 JSDoc의 <code>@see</code> 태그 지원</h2>
<p>JSDoc 태그 <code>@see</code>는 이제 TypeScript와 JavaScript 에디터에서 더 나은 지원을 받는다. 이를 통해 태그 뒤에 점(.)이 있는 이름에서 go-to-definition과 같은 기능을 사용할 수 있다. 예를 들어, 다음 예제의 JSDoc 주석에서 <code>first</code> 또는 <code>C</code>에서 정의로 이동하는 것이 가능하다.</p>
<pre><code class="language-ts">// @filename: first.ts
export class C {}
// @filename: main.ts
import * as first from &quot;./first&quot;;
/**
 * @see first.C
 */
function related() {}</code></pre>
<h2 id="breaking-changes">Breaking Changes</h2>
<h3 id="libdts-변경-사항"><code>lib.d.ts</code> 변경 사항</h3>
<p><code>lib.d.ts</code>의 변경된 API 세트에는 DOM 타입이 자동으로 생성되는 방식에 따라 일부 변화가 있을 수 있다. 한 가지 구체적인 변경 사항은 ES2016에서 제거되었기 때문에 <code>Reflect.enumerate</code>가 제거되었다.</p>
<h3 id="abstract-멤버는-async로-마킹될-수-없다"><code>abstract</code> 멤버는 <code>async</code>로 마킹될 수 없다</h3>
<p><code>abstract</code> 멤버로 표시된 멤버에는 더 이상 <code>async</code>가 표시될 수 없다. 여기서 수정해야 할 부분은 <code>async</code> 키워드를 제거하는 것이다. 호출자는 반환되는 타입에만 관심이 있기 때문이다.</p>
<h3 id="any--unknown-이-falsy-값-위치에서-전파됨"><code>any</code> / <code>unknown</code> 이 falsy 값 위치에서 전파됨</h3>
<p>이전에는 <code>foo &amp;&amp; somethingElse</code>와 같은 식에서 <code>foo</code>의 타입이 <code>any</code> 또는 <code>unknown</code>인 경우 전체 식의 타입은 <code>somethingElse</code>의 타입이 되었다.</p>
<p>예를 들어, 이전에는 <code>x</code>의 타입이 <code>{ someProp: string }</code>이었다.</p>
<pre><code class="language-ts">declare let foo: unknown;
declare let somethingElse: { someProp: string };
let x = foo &amp;&amp; somethingElse;</code></pre>
<p>그러나 TS 4.1에선 이 타입을 어떻게 결정할지 더 주의하기로 했다. <code>&amp;&amp;</code>의 왼쪽에 있는 타입에 대해 알려진 것이 없으므로 오른쪽 타입 대신 <code>any</code> 및 <code>unknown</code>을 외부로 전파한다. </p>
<p>가장 일반적인 패턴은 특히 조건 함수에서 <code>boolean</code>과의 호환성을 확인할 때이다.</p>
<pre><code class="language-ts">function isThing(x: any): boolean {
  return x &amp;&amp; typeof x === &quot;object&quot; &amp;&amp; x.blah === &quot;foo&quot;;
}</code></pre>
<p>종종 적절한 수정은 <code>foo &amp;&amp; someExpression</code>에서 <code>!!foo &amp;&amp; someExpression</code>으로 전환하는 것이다.</p>
<h3 id="promise에서-resolve-의-매개변수는-더-이상-선택사항이-아니다"><code>Promise</code>에서 <code>resolve</code> 의 매개변수는 더 이상 선택사항이 아니다.</h3>
<p>다음과 같은 코드를 작성할 때</p>
<pre><code class="language-ts">new Promise((resolve) =&gt; {
  doSomethingAsync(() =&gt; {
    doSomething();
    resolve();
  });
});</code></pre>
<p>다음과 같은 오류가 발생할 수 있다.</p>
<pre><code class="language-ts">  resolve()
  ~~~~~~~~~
// error TS2554: Expected 1 arguments, but got 0.
//  An argument for &#39;value&#39; was not provided.</code></pre>
<p>이는 <code>resolve</code>에 더 이상 선택적 매개변수가 없기 때문에 기본적으로 값을 전달해야 한다. 이것은 <code>Promise</code>를 사용하여 적합한 버그를 포착하는 경우가 많다. 일반적인 수정은 올바른 인수를 전달하고 때로는 명시적인 타입 인수를 추가하는 것이다.</p>
<pre><code class="language-ts">new Promise&lt;number&gt;((resolve) =&gt; {
  //     ^^^^^^^^
  doSomethingAsync((value) =&gt; {
    doSomething();
    resolve(value);
    //      ^^^^^
  });
});</code></pre>
<p>그러나 때때로 <code>resolve()</code>는 실제로 인수 없이 호출되어야 한다. 이러한 경우 <code>Promise</code>에 명시적 <code>void</code> 제네릭 형식 인수를 지정할 수 있다.(즉, <code>Promise&lt;void&gt;</code>로 작성). 이는 잠재적으로 <code>void</code>인 후행 매개변수가 선택 사항이 될 수 있는 TS 4.1의 새로운 기능을 활용한다.</p>
<pre><code class="language-ts">new Promise&lt;void&gt;((resolve) =&gt; {
  //     ^^^^^^
  doSomethingAsync(() =&gt; {
    doSomething();
    resolve();
  });
});</code></pre>
<p>TS 4.1은 이 break를 수정하는 데 도움이 되는 빠른 수정과 함께 제공된다.</p>
<h3 id="조건부-스프레드는-선택적-속성을-생성한다">조건부 스프레드는 선택적 속성을 생성한다</h3>
<p>JS에서 <code>{...foo}</code>와 같은 객체 스프레드는 잘못된 값에 대해 작동하지 않는다. 따라서 <code>{...foo}</code>와 같은 코드에서 <code>foo</code>는 <code>null</code>이거나 <code>undefined</code> 인 경우 건너뛴다.</p>
<p>많은 사용자가 이를 이용하여 &#39;조건부&#39; 속성을 spread 한다.</p>
<pre><code class="language-ts">interface Person {
  name: string;
  age: number;
  location: string;
}
interface Animal {
  name: string;
  owner: Person;
}
function copyOwner(pet?: Animal) {
  return {
    ...(pet &amp;&amp; pet.owner),
    otherStuff: 123,
  };
}
// We could also use optional chaining here:
function copyOwner(pet?: Animal) {
  return {
    ...pet?.owner,
    otherStuff: 123,
  };
}</code></pre>
<p>여기에서 만약 <code>pet</code>이 정의되면 <code>pet.owner</code>의 속성이 spread된다. 그렇지 않으면 속성이 반환된 객체로 spread되지 않는다.</p>
<p><code>copyOwner</code>의 반환 타입은 이전에 각 스프레드를 기반으로 하는 통합 타입이었다.</p>
<pre><code>{ x: number } | { x: number, name: string, age: number, location: string }</code></pre><p>이는 연산이 발생하는 방식을 정확하게 모델링 했다. <code>pet</code>이 정의된 경우 <code>Person</code>의 모든 속성이 표시된다. 그렇지 않으면 결과에 정의되지 않는다. all or nothing한 방법이었다.</p>
<p>그러나 단일 객체에 수백 개의 스프레드가 있고 각각의 스프레드가 잠재적으로 수백 또는 수천 개의 속성을 추가하는 극단적인 패턴을 보았다. 여러가지 이유로 이것은 매우 비싸고 일반적으로 많은 이점이 없는 것으로 밝혀졌다. </p>
<p>TS 4.1에서 반환된 타입은 때때로 모든 선택적 속성을 사용한다.</p>
<pre><code class="language-ts">{
    x: number;
    name?: string;
    age?: number;
    location?: string;
}</code></pre>
<p>결과적으로 성능이 더 좋아지고 일반적으로 더 잘 표시된다.</p>
<p>현재 이 동작이 완전히 일관되지는 않지만 향후 릴리스에서는 보다 깨끗하고 예측 가능한 결과를 생성할 것으로 기대한다.</p>
<h3 id="일치하지-않는-매개변수는-더-이상-관련이-없다">일치하지 않는 매개변수는 더 이상 관련이 없다</h3>
<p>TS는 이전에 서로 일치하지 않는 매개변수를 <code>any</code> 타입에 대응하였다. TS 4.1의 변경 사항으로 언어는 이제 이 프로세스를 건너뛴다. 이는 일부 할당 가능성 사례가 이제 실패하지만 일부 과부하 해결 사례도 실패할 수 있음을 의미한다. 예를 들어, Node.js의 <code>util.promisify</code>에 대한 오버로드 해결은 TS 4.1에서 다른 오버로드를 선택할 수 있으며, 때때로 새롭거나 다른 오류 다운스트림을 유발할 수 있다.</p>
<p>이에 대한 해결방안으로 타입 단언을 사용하여 오류를 억제하는 것이 가장 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 4.0 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-4.0-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-4.0-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Tue, 24 Jan 2023 06:58:03 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html</a></p>
</blockquote>
<h2 id="가변-인자-튜플-타입">가변 인자 튜플 타입</h2>
<p>두 개의 배열 또는 튜플 타입을 결합하여 새로운 배열을 만드는 JS의 <code>concat</code> 함수에 대해서 생각해보자.</p>
<pre><code class="language-ts">function concat(arr1, arr2) {
  return [...arr1, ...arr2];
}</code></pre>
<p>또한 배열 또는 튜플을 인수로 가져와 첫 번째 원소를 제외한 나머지를 반환하는 <code>tail</code> 함수에 대해서도 생각해보자.</p>
<pre><code class="language-ts">function tail(arg) {
  const [_, ...result] = arg;
  return result;
}</code></pre>
<p>TS에서 이 두함수의 타입을 어떻게 정의할 수 있을까?</p>
<p><code>concat</code>의 경우, 이전 버전에서는 여러 개의 오버로드를 작성하는 방법이 유일했다.</p>
<pre><code class="language-ts">function concat(arr1: [], arr2: []): [];
function concat&lt;A&gt;(arr1: [A], arr2: []): [A];
function concat&lt;A, B&gt;(arr1: [A, B], arr2: []): [A, B];
function concat&lt;A, B, C&gt;(arr1: [A, B, C], arr2: []): [A, B, C];
function concat&lt;A, B, C, D&gt;(arr1: [A, B, C, D], arr2: []): [A, B, C, D];
function concat&lt;A, B, C, D, E&gt;(arr1: [A, B, C, D, E], arr2: []): [A, B, C, D, E];
function concat&lt;A, B, C, D, E, F&gt;(arr1: [A, B, C, D, E, F], arr2: []): [A, B, C, D, E, F];</code></pre>
<p>이 오버로드들의 두 번째 배열은 전부 비어있다. 이때, <code>arr2</code>가 하나의 인자를 가지고 있는 경우를 추가해보자.</p>
<pre><code class="language-ts">function concat&lt;A2&gt;(arr1: [], arr2: [A2]): [A2];
function concat&lt;A1, A2&gt;(arr1: [A1], arr2: [A2]): [A1, A2];
function concat&lt;A1, B1, A2&gt;(arr1: [A1, B1], arr2: [A2]): [A1, B1, A2];
function concat&lt;A1, B1, C1, A2&gt;(arr1: [A1, B1, C1], arr2: [A2]): [A1, B1, C1, A2];
function concat&lt;A1, B1, C1, D1, A2&gt;(arr1: [A1, B1, C1, D1], arr2: [A2]): [A1, B1, C1, D1, A2];
function concat&lt;A1, B1, C1, D1, E1, A2&gt;(arr1: [A1, B1, C1, D1, E1], arr2: [A2]): [A1, B1, C1, D1, E1, A2];
function concat&lt;A1, B1, C1, D1, E1, F1, A2&gt;(arr1: [A1, B1, C1, D1, E1, F1], arr2: [A2]): [A1, B1, C1, D1, E1, F1, A2];</code></pre>
<p>이런 오버로딩 함수들은 분명 비합리적이다. 불행히도, <code>tail</code> 함수를 타이핑할 때도 이와 비슷한 문제에 직면하게 된다.</p>
<p>이것은 &#39;천 개의 오버로드로 인한 죽음&#39;이라고 불리는 하나의 경우이며, 심지어 대부분의 문제를 해결하지 못한다. 우리가 작성하고자 하는 만큼의 오버로드에 한해서만 올바른 타입을 제공한다. 포괄적인 케이스를 만들고 싶다면, 다음과 같은 오버로드가 필요하다.</p>
<pre><code class="language-ts">function concat&lt;T, U&gt;(arr1: T[], arr2: U[]): Array&lt;T | U&gt;;</code></pre>
<p>그러나 위 시그니처는 튜플을 사용할 때 입력 길이나 요소 순서에 대한 어떤 것도 처리하지 않는다.</p>
<p>TS 4.0은 타입 추론 개선을 포함한 두 가지 핵심적인 변화를 도입해 이러한 타이핑을 가능하도록 만들었다.</p>
<p>첫 번째 변화는 튜플 타입 구문의 스프레드 연산자에 제네릭 타입을 사용할 수 있다는 점이다. 우리가 작동하는 실제 타입을 모르더라도 튜플과 배열에 대한 고차함수를 표현할 수 있다는 뜻이다. 이러한 튜플 타입에서 제네릭 스프레드 연산자가 인스턴스화(혹은, 실제 타입으로 대체)되면 또 다른 배열이나 튜플 타입 세트를 생산할 수 있다.</p>
<p>예를 들어, <code>tail</code> 같은 함수를 &#39;천 개의 오버로드로 인한 죽음&#39;이슈 없이 타이핑 할 수 있게 된다.</p>
<pre><code class="language-ts">function tail&lt;T extends any[]&gt;(arr: readonly [any, ...T]) {
  const [_ignored, ...rest] = arr;
  return rest;
}

const myTuple = [1, 2, 3, 4] as const;
const myArray = [&quot;hello&quot;, &quot;world&quot;];

const r1 = tail(myTuple);

const r1: [2, 3, 4]

const r2 = tail([...myTuple, ...myArray] as const);

const r2: [2, 3, 4, ...string[]]</code></pre>
<p>두 번째 변화는 요소의 끝뿐만 아니라, rest element는 튜플의 어느 곳에서나 발생할 수 있다는 것이다.</p>
<pre><code class="language-ts">type Strings = [string, string];
type Numbers = [number, number];
type StrStrNumNumBool = [...Strings, ...Numbers, boolean];</code></pre>
<p>이전의 TS는 다음과 같은 오류를 생성했다.</p>
<pre><code class="language-ts">A rest element must be last in a tuple type.</code></pre>
<p>그러나 TS 4.0에선 이런 제한이 완화되었다.</p>
<p>길이가 정해지지 않은 타입을 확장하려고 할 때, 결과의 타입은 제한되지 않으며, 다음 모든 요소가 결과의 나머지 요소에 포함되는 점에 유의하라.</p>
<pre><code class="language-ts">type Strings = [string, string];
type Numbers = number[];
type Unbounded = [...Strings, ...Numbers, boolean];
//   ^ = type Unbounded = [string, string, ...(number | boolean)[]]</code></pre>
<p>이 두 가지 동작을 함께 결합하여, <code>concat</code>에 대해 타입이 제대로 정의된 시그니처를 작성할 수 있다.</p>
<pre><code class="language-ts">type Arr = readonly any[];

function concat&lt;T extends Arr, U extends Arr&gt;(arr1: T, arr2: U): [...T, ...U] {
  return [...arr1, ...arr2];
}</code></pre>
<p>하나의 시그니처가 조금 길더라도, 반복할 필요가 없는 하나의 시그니처일 뿐이며, 모든 배열과 튜플에서 예측 가능한 행동을 제공한다.</p>
<p>이 기능은 그 자체만으로 매우 훌륭하지만, 조금 더 정교한 시나리오에서 빛을 발한다. 예를들어, <a href="https://en.wikipedia.org/wiki/Partial_application">함수의 매개변수를 부분적으로 적용하여 새로운 함수를 반환하는</a> <code>partialCall</code> 함수가 있다고 생각해보자. <code>partialCall</code>은 다음과 같은 함수이다. - <code>f</code>가 예상하는 몇 가지 인수와 함께 <code>f</code>라고 부르자. 그 후, <code>f</code>가 필요로하는 다른 인수를 가지고, 그것을 받을 때 <code>f</code>를 호출하는 새로운 함수를 반환한다.</p>
<pre><code class="language-ts">function partialCall(f, ...headArgs) {
  return (...tailArgs) =&gt; f(...headArgs, ...tailArgs);
}</code></pre>
<p>TS 4.0은 나머지 파라미터들과 튜플 원소들에 대한 추론 프로세스를 개선하여 타입을 지정할 수 있고 &#39;그냥 동작&#39;하도록 할 수 있다.</p>
<pre><code class="language-ts">type Arr = readonly unknown[];

function partialCall&lt;T extends Arr, U extends Arr, R&gt;(
  f: (...args: [...T, ...U]) =&gt; R,
  ...headArgs: T
) {
  return (...tailArgs: U) =&gt; f(...headArgs, ...tailArgs);
}</code></pre>
<p>이 경우, <code>partialCall</code>은 처음에 취할 수 있는 파라미터와 할 수 없는 파라미터를 파악하고, 남은 것들을 적절히 수용하고 거부하는 함수들을 반환한다.</p>
<pre><code class="language-ts">type Arr = readonly unknown[];

function partialCall&lt;T extends Arr, U extends Arr, R&gt;(
  f: (...args: [...T, ...U]) =&gt; R,
  ...headArgs: T
) {
  return (...tailArgs: U) =&gt; f(...headArgs, ...tailArgs);
}
// ---cut---
const foo = (x: string, y: number, z: boolean) =&gt; {};

const f1 = partialCall(foo, 100);

const f2 = partialCall(foo, &quot;hello&quot;, 100, true, &quot;oops&quot;);

// 작동합니다!
const f3 = partialCall(foo, &quot;hello&quot;);
//    ^ = const f3: (y: number, z: boolean) =&gt; void

// f3으로 뭘 할 수 있을까요?

// 작동합니다!
f3(123, true);
f3();
f3(123, &quot;hello&quot;);</code></pre>
<p>가변 인자 튜플 타입은 특히 기능 구성과 관련하여 많은 새로운 흥미로운 패턴을 가능하게 한다. 우리는 JS에 내장된 <code>bind</code> 메서드의 타입 체킹을 더 잘하기 위해 이를 활용할 수 있을 것이라고 기대한다. 몇 가지 다른 추론 개선 및 패턴들도 여기에 포함되어 있고, 가변 인자 튜플에 대해 더 알아보고 싶다면, <a href="https://github.com/microsoft/TypeScript/pull/39094">PR</a>을 참고하라.</p>
<h2 id="라벨링된-튜플-요소">라벨링된 튜플 요소</h2>
<p>튜플 타입과 매개 변수 목록에 대해 개선하는 것은 일반적인 JS 관용구에 대한 타입 유효성 검사를 강화시켜주기 때문에 중요하다. - 실제로 인수 목록을 자르고 다른 함수로 전달만 해주면 된다. 나머지 매개 변수에 튜플 타입을 사용할 수 있다는 생각은 아주 중요하다.</p>
<p>예를 들어, 튜플 타입을 나머지 매개 변수로 사용하는 다음 함수는..</p>
<pre><code class="language-ts">function foo(...args: [string, number]): void {
  // ...
}</code></pre>
<p>다음 함수와 다르지 않아야 한다.</p>
<pre><code class="language-ts">function foo(arg0: string, arg1: number): void {
  // ...
}</code></pre>
<p><code>foo</code>의 모든 호출자에 대해서도</p>
<pre><code class="language-ts">foo(&quot;hello&quot;, 42);

foo(&quot;hello&quot;, 42, true);
Expected 2 arguments, but got 3.
foo(&quot;hello&quot;);
Expected 2 arguments, but got 1.</code></pre>
<p>그러나 차이점이 보이기 시작한 부분은 가독성이다. 첫 번째 예시에선, 첫 번째와 두 번째 요소에 대한 매개 변수 이름이 없다. 타입검사에는 전혀 영향이 없지만, 튜플 위치에 라벨이 없는 것은 의도를 전달하기 어려워 사용하기 어렵다. </p>
<p>TS 4.0에서는 튜플 타입에 라벨을 제공한다.</p>
<pre><code class="language-ts">type Range = [start: number, end: number];</code></pre>
<p>매개 변수 목록과 튜플 타입 사이의 연결을 강화하기 위해, 나머지 요소와 선택적 요소에 대한 구문이 매개 변수 목록의 구문을 반영한다.</p>
<pre><code class="language-ts">type Foo = [first: number, second?: string, ...rest: any[]];</code></pre>
<p>라벨링 된 튜플을 사용할 때는 몇 가지 규칙이 있다. 하나는 튜플 요소를 라벨링 할 때, 튜플에 있는 다른 모든 요소들 역시 라벨링 되어야 한다.</p>
<pre><code class="language-ts">type Bar = [first: string, number];
Tuple members must all have names or all not have names.</code></pre>
<p>당연하게, 라벨은 구조 분해할 때 변수 이름을 다르게 지정할 필요가 없다. 이것은 순전히 문서화와 도구를 위해 존재한다.</p>
<pre><code class="language-ts">function foo(x: [first: string, second: number]) {
    // ...

    // note: we didn&#39;t need to name these &#39;first&#39; and &#39;second&#39;
    const [a, b] = x;
    a

const a: string
    b

const b: number
}</code></pre>
<p>전반적으로 라벨링된 튜플은 안전한 타입 방식으로 오버로드를 구현하는 것과 튜플과 인수 목록의 패턴을 활용할 때 편리하다. 사실, TS 에디터 지원은 가능한 경우 오버로드로 표시하려 한다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/0893e7d1-5145-485e-b043-7257d3eed017/image.png" alt=""></p>
<p>더 알고 싶다면, 라벨링된 튜플 요소에 대한 <a href="https://github.com/microsoft/TypeScript/pull/38234">PR</a>을 확인하라.</p>
<h2 id="생성자로부터-클래스-프로퍼티-타입-추론하기">생성자로부터 클래스 프로퍼티 타입 추론하기</h2>
<p>TS 4.0에서는 <code>noImplicitAny</code>가 활성화되었을 때 클래스 내의 프로퍼티 타입을 결정하기 위해 제어 흐름 분석을 사용할 수 있다.</p>
<pre><code class="language-ts">class Square {
  // Previously both of these were any
  area;

// (property) Square.area: number
  sideLength;

// (property) Square.sideLength: number
  constructor(sideLength: number) {
    this.sideLength = sideLength;
    this.area = sideLength ** 2;
  }
}</code></pre>
<p>생성자의 모든 경로가 인스턴스 멤버에 할당한 것이 아닐경우, 프로퍼티는 잠재적으로 <code>undefined</code>가 된다.</p>
<pre><code class="language-ts">class Square {
  sideLength;

// (property) Square.sideLength: number | undefined

  constructor(sideLength: number) {
    if (Math.random()) {
      this.sideLength = sideLength;
    }
  }

  get area() {
    return this.sideLength ** 2;
// Object is possibly &#39;undefined&#39;.
  }
}</code></pre>
<blockquote>
<p>위 경우는 random 함수에 의해 <code>sideLength</code>에 값이 할당되지 않을 수 있음.</p>
</blockquote>
<p>더 많은 내용이 있는 경우(e.g <code>initialize</code> 메서드 등이 있는 경우), <code>strictPropertyInitialization</code> 모드에서는 확정적 할당 단언(<code>!</code>)에 따라 명시적으로 타입을 선언해야 한다.</p>
<pre><code class="language-ts">class Square {
  // definite assignment assertion
  //        v
  sideLength!: number;
  // type annotation

  constructor(sideLength: number) {
    this.initialize(sideLength);
  }

  initialize(sideLength: number) {
    this.sideLength = sideLength;
  }

  get area() {
    return this.sideLength ** 2;
  }
}</code></pre>
<p>더 자세한 것은 <a href="https://github.com/microsoft/TypeScript/pull/37920">PR</a>을 확인하라.</p>
<h2 id="단축-평가-할당-연산자">단축 평가 할당 연산자</h2>
<p>JS와 많은 언어는 복합 할당(compound assignment) 연산자라고 불리는 연산자 집합을 지원한다. 복합 할당 연산자는 두 개의 인수에 연산자를 적용한 다음 결과를 왼쪽에 할당한다. 이전에 아래와 같은 것을 본 적이 있을 것이다.</p>
<pre><code class="language-ts">// Addition
// a = a + b
a += b;
// Subtraction
// a = a - b
a -= b;
// Multiplication
// a = a * b
a *= b;
// Division
// a = a / b
a /= b;
// Exponentiation
// a = a ** b
a **= b;
// Left Bit Shift
// a = a &lt;&lt; b
a &lt;&lt;= b;</code></pre>
<p>JS의 많은 연산자에 위와 같은 할당 연산자가 있다! 그러나 최근까지도 논리 and 연산자 (<code>&amp;&amp;</code>), 논리 or 연산자 (<code>||</code>) 및 null과 같은 것을 병합하는 연산자 (nullish coalescing) (<code>??</code>)의 세 가지 주목할만한 예외가 있었다.</p>
<p>이것이 TS 4.0이 새로운 할당 연산자 <code>&amp;&amp;=</code>, <code>||=</code>, <code>??=</code>를 추가하는 새로운 ECMAScript 기능을 지원하는 이유이다.</p>
<p>이러한 연산자는 사용자가 다음과 같은 코드를 작성할 수 있는 모든 예를 대체하는 데 유용하다.</p>
<pre><code class="language-ts">a = a &amp;&amp; b;
a = a || b;
a = a ?? b;</code></pre>
<p>혹은 아래와 비슷한 <code>if</code> 블록</p>
<pre><code class="language-ts">// could be &#39;a ||= b&#39;
if (!a) {
  a = b;
}</code></pre>
<p>우리가 본(혹은 직접 작성한) 코드 패턴 중 필요한 경우에만 값을 지연 초기화 시키기 위한 패턴도 있다.</p>
<pre><code class="language-ts">let values: string[];
(values ?? (values = [])).push(&quot;hello&quot;);
// After
(values ??= []).push(&quot;hello&quot;);</code></pre>
<p>드물지만 부수 효과가 있는 getter 또는 setter를 사용하는 경우 이러한 연산자가 필요한 경우에만 할당을 수행한다는 점에 유의할 필요가 있다. 그런 의미에서 연산자의 오른쪽이 &#39;단축&#39;될 뿐만 아니라 할당 자체도 마찬가지이다.</p>
<pre><code class="language-ts">obj.prop ||= foo();
// roughly equivalent to either of the following
obj.prop || (obj.prop = foo());
if (!obj.prop) {
    obj.prop = foo();
}</code></pre>
<p><a href="https://www.typescriptlang.org/play?ts=Nightly#code/MYewdgzgLgBCBGArGBeGBvAsAKBnmA5gKawAOATiKQBQCUGO+TMokIANkQHTsgHUAiYlChFyMABYBDCDHIBXMANoBuHI2Z4A9FpgAlIqXZTgRGAFsiAQg2byJeeTAwAslKgSu5KWAAmIczoYAB4YAAYuAFY1XHwAXwAaWxgIEhgKKmoAfQA3KXYALhh4EA4iH3osWM1WCDKePkFUkTFJGTlFZRimOJw4mJwAM0VgKABLcBhB0qCqplr63n4BcjGCCVgIMd8zIjz2eXciXy7k+yhHZygFIhje7BwFzgblgBUJMdlwM3yAdykAJ6yBSQGAeMzNUTkU7YBCILgZUioOBIBGUJEAHwxUxmqnU2Ce3CWgnenzgYDMACo6pZxpYIJSOqDwSkSFCYXC0VQYFi0NMQHQVEA">다음 예시를 실행해보아라.</a> 예시를 통해 항상 할당을 수행하는 것과 어떻게 다른지 확인해보아라.</p>
<pre><code class="language-ts">const obj = {
    get prop() {
        console.log(&quot;getter has run&quot;);

        // Replace me!
        return Math.random() &lt; 0.5;
    },
    set prop(_val: boolean) {
        console.log(&quot;setter has run&quot;);
    }
};

function foo() {
    console.log(&quot;right side evaluated&quot;);
    return true;
}

console.log(&quot;This one always runs the setter&quot;);
obj.prop = obj.prop || foo();

console.log(&quot;This one *sometimes* runs the setter&quot;);
obj.prop ||= foo();</code></pre>
<blockquote>
<p>위는 항상 할당을 하여 setter가 계속 실행되는데, 아래의 것은 <code>obj.prop</code>의 값이 <code>false</code>인 경우에만 <code>foo</code> 함수가 실행되고, 세터가 실행된다.</p>
</blockquote>
<p>기여해주신 커뮤니티 멤버 <a href="https://github.com/Kingwl">Wenlu Wang</a>님에게 큰 감사를 표한다.</p>
<p>더 자세한 내용을 보고 싶다면 <a href="https://github.com/microsoft/TypeScript/pull/37727">이 PR을 확인하라.</a> <a href="https://github.com/tc39/proposal-logical-assignment/">TC39 제안 레포지토리에서도 이 기능을 확인할 수 있다.</a></p>
<h2 id="catch-구문에서-unknown-clause-바인딩">catch 구문에서 unknown Clause 바인딩</h2>
<p><code>catch</code> 구문 바인딩에 대해서는 이미 TS의 시작부터 언제나 <code>any</code> 타입 이었다. 이는 TS가 그 안에서 모든 것을 허용한다는 것을 의미한다.</p>
<pre><code class="language-ts">try {
  // Do some work
} catch (x) {
  // x has type &#39;any&#39; - have fun!
  console.log(x.message);
  console.log(x.toUpperCase());
  x++;
  x.yadda.yadda.yadda();
}</code></pre>
<p>catch 구문의 변수 타입을 <code>any</code>로 지정하는 것은 우리의 오류 처리 코드에서 더 많은 오류를 핸들링하는 경우 원하지 않는 행동을 보일 수 있다. 이러한 변수는 기본적으로 <code>any</code> 타입을 가지기 때문에 유효하지 않은 연산에서 오류를 출력하지 않는다.</p>
<p>그래서 TS 4.0에서는 catch 구문 변수의 타입의 <code>unknown</code>으로 지정할 수 있다. <code>unknown</code>은 <code>any</code>보다 안전하며, 값을 조작하기 전에 타입 검사를 수행해야 한다는 것을 알려준다.</p>
<pre><code class="language-ts">try {
  // ...
} catch (e: unknown) {
  // Can&#39;t access values on unknowns
  console.log(e.toUpperCase());
Object is of type &#39;unknown&#39;.

  if (typeof e === &quot;string&quot;) {
    // We&#39;ve narrowed &#39;e&#39; down to the type &#39;string&#39;.
    console.log(e.toUpperCase());
  }
}</code></pre>
<p><code>catch</code> 구문 안의 변수 타입은 기본적으로 변경되지 않지만, 사용자가 이 동작을 선택할 수 있도록 새로운 <code>strict</code>모드 플래그를 생각해볼 수 있다. 그러나, <code>catch</code> 구문 안 변수에 명시적으로 <code>:any</code> 또는 <code>:unknown</code> 주석을 부여하도록 <code>lint</code> 규칙을 작성할 수 있다.</p>
<p>더 자세한 것은 <a href="https://github.com/microsoft/TypeScript/pull/39015">이 기능의 변경사항을 참조하라.</a></p>
<h2 id="커스텀-jsx-factories">커스텀 JSX Factories</h2>
<p>JSX를 사용할 때, Fragment는 JSX 요소의 한 유형으로, 여러 개의 자식 요소를 반환할 수 있는 유형의 JSX 요소이다. TS에서 Fragment를 처음 구현 했을 때, 다른 라이브러리가 어떻게 사용할지에 대해서는 잘 몰랐다. 지금은 JSX를 사용하는 것을 권장하며 Fragment를 지원하는 대부분의 다른 라이브러리들도 비슷한 API 형태를 가지고 있다.</p>
<p>TS 4.0에서 사용자는 새로운 jsxFragmentFactory 옵션을 통해 Fragment 팩토리를 사용자 정의 할 수 있다.</p>
<p>예를들어, 다음 <code>tsconfig.json</code> 파일은 TS를 React와 호환되도록 JSX를 변환하도록 알려준다. 그리고 각 팩토리 호출을 <code>React.createElement</code> 대신 <code>h</code>로, <code>React.Fragment</code> 대신 <code>Fragment</code>로 전환한다.</p>
<blockquote>
<p>이건 잘 사용하면 좋을 것 같다.</p>
</blockquote>
<pre><code class="language-json">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;esnext&quot;,
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;jsx&quot;: &quot;react&quot;,
    &quot;jsxFactory&quot;: &quot;h&quot;,
    &quot;jsxFragmentFactory&quot;: &quot;Fragment&quot;
  }
}</code></pre>
<p>파일 단위로 다른 JSX 팩토리가 필요한 경우, <code>/** @jsxFrag */</code> pragma comment를 사용할 수 있다. 예를 들어, 다음 예제는 파일 단위로 JSX 팩토리를 지정하는 방법이다.</p>
<pre><code class="language-ts">// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.

/** @jsx h */
/** @jsxFrag Fragment */

import { h, Fragment } from &quot;preact&quot;;

export const Header = (
  &lt;&gt;
    &lt;h1&gt;Welcome&lt;/h1&gt;
  &lt;/&gt;
);</code></pre>
<p>위 예제는 아래와 같은 JS 결과물로 변환된다.</p>
<pre><code class="language-ts">import React from &#39;react&#39;;
export const Header = (React.createElement(React.Fragment, null,
    React.createElement(&quot;h1&quot;, null, &quot;Welcome&quot;)));</code></pre>
<p>우리는 이 PR을 보내고 우리 팀과 인내심을 가지고 함께 일해준 커뮤니티 멤버 <a href="https://github.com/nojvek">Noj Vek</a>에게 큰 감사를 드린다.</p>
<p>자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/38720">PR</a>을 참조하라.</p>
<h2 id="빌드-모드에서---noemitonerror와-함께-속도-개선">빌드 모드에서 --noEmitOnError와 함께 속도 개선</h2>
<p>이전에는 <code>noEmitOnError</code> 플래그를 사용할 때 <code>incremental</code> 오류가 있는 이전 컴파일 후 프로그램을 컴파일 하면 매우 느리게 되낟. 이는 이전 컴파일 정보가 <code>noEmitOnError</code> 플래그 기반으로, <code>.tsbuildinfo</code> 파일에 캐시되지 않기 때문이다.</p>
<p>TS 4.0은 이를 변경하여 이러한 상황에서 성능 향상을 기대할 수 있고, <code>--build</code> 모드 사례(<code>incremental</code>과 <code>noEmitOnError</code>를 포함)를 개선한다.</p>
<p>더 자세한 것은 <a href="https://github.com/microsoft/TypeScript/pull/38853">이 PR을 참조하라.</a></p>
<h2 id="--noemit-과-함게---incremental-사용">--noEmit 과 함게 --incremental 사용</h2>
<p>TS 4.0은 증분 컴파일을 사용하면서도 noEmit 플래그를 사용할 수 있도록 허용한다. 이를 통해 <code>.tsbuildinfo</code> 파일을 생성하지 않아도 증분 컴파일을 사용할 수 있어 개발 프로세스를 더욱 편리하게 할 수 있다.</p>
<h2 id="에디터-개선">에디터 개선</h2>
<p>TS 컴파일러는 대부분의 주요 편집기에서 TS 자체 편집 경험을 제공하는 것뿐만 아니라 VS 편집기와 기타 편집기에서 JS 경험을 제공한다. 그래서 대부분의 우리의 작업은 개발자가 가장 많은 시간을 보내는 편집기 시나리오를 개선하는 것에 초점을 맞춘다.</p>
<p>편집기에서 새로운 TS/JS 기능을 사용하는 방법은 편집기마다 다르다.</p>
<ul>
<li>VSCode는 <a href="https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript">TS의 다른 버전을 선택</a>할 수 있다. 또는 <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next">JS/TS Nightly Extension</a>을 사용하면 최신기능을 사용할 수 있다.(일반적으로 매우 안정적이다.)</li>
<li>VS 2017/2019는 SDK 설치 프로그램과 <a href="https://www.nuget.org/packages/Microsoft.TypeScript.MSBuild">MSBuild 설치</a>를 지원한다.</li>
<li>Sublime Text 3은 <a href="https://github.com/microsoft/TypeScript-Sublime-Plugin#note-using-different-versions-of-typescript">TS의 다른 버전을 선택할 수 있다.</a></li>
</ul>
<p>TS를 지원하는 <a href="https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support">편집기의 부분적인 목록을 확인</a>하여 가장 좋아하는 편집기가 새 버전을 사용할 수 있는지 여부를 자세히 확인할 수 있다.</p>
<h3 id="옵셔널-체이닝-변환">옵셔널 체이닝 변환</h3>
<p>옵셔널 체이닝은 최근에 출시된 기능중 하나로 사랑을 많이 받았다. 그래서 TS 4.0에서는 <code>optional chaning</code>과 <code>nullish coalescing</code>을 이용하는 흔한 패턴을 변환하는 새로운 refactoring을 제공한다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/b3c3ad9c-feff-46d8-9bc6-fe74b2cd4f71/image.png" alt=""></p>
<p>코드의 의도를 완벽히 잡지 못할 수 있다는 점을 염두해 두길 바란다. 이는 JS에서 참/거짓에 대한 세밀한 차이 때문이다. 그러나 에디터가 TS가 사용되는 유형에 대한 정확한 지식을 가지고 있을 때, 대부분의 사용 사례에서 원래 코드의 의도를 잡는다고 생각한다.</p>
<p>더 자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/39135">이 기능에 대한 PR을 확인하라.</a></p>
<blockquote>
<p>위 형태의 코드를 옵셔널 체이닝, 널 병합 연산을 사용하는 코드로 리팩터링 하는 기능을 제공함.</p>
</blockquote>
<h3 id="-deprecated--support"><code>/** @deprecated */</code> Support</h3>
<p>TS의 편집 지원은 선언이 <code>/** @deprecated */</code> JSDoc 주석으로 표시되었을 때 인식한다. 그 정보는 완료 목록에서 표시되며 편집기가 특별히 처리할 수 있는 제안 진단으로 표시된다. VS Code와 같은 편집기에서는 사용되지 않는 값은 일반적으로 이렇게 취소선 스타일로 표시된다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/da6e7a91-10fa-45d7-a26e-28cde8a1cda7/image.png" alt=""></p>
<h2 id="시작-시-부분-semantic-모드">시작 시 부분 Semantic 모드</h2>
<p>대규모 프로젝트에서 특히 긴 시작 시간을 겪는 사용자들의 의견을 많이 들었다. 이는 <em>program construction</em> 이라는 프로세스를 원인으로 한다. 이는 초기 root 파일 세트로 시작하여 이를 분석하고 의존성을 찾는 과정이다. 그리고 의존성을 분석하여 의존성의 의존성을 찾는 과정이다. 프로젝트가 커질수록 go-to-definition 또는 quick info와 같은 기본 편집 작업을 시작하기 전에 기다려야 할 시간이 길어진다.</p>
<p>이러한 문제를 해결하기 위해 편집기에서 전체 언어 서비스가 로드될 때까지 일부 경험을 제공하는 새로운 모드를 개발하고 있다. 핵심 아이디어는 편집기에서 현재 편집기에 열려있는 파일만 보는 가벼운 일부의 서버를 실행하는 것이다.</p>
<p>정확히 어떤 성능 향상을 볼 수 있을지는 말하기 어렵지만, 사실상으로는 VSCode 베이스에서 TS가 완전히 반응하기전에 20초에서 1분 사이의 시간이 걸렸다. 반면, 새로운 일부 SemanticMode는 그 지연시간을 몇초로 줄일 수 있는 것으로 보인다. 예를들어 아래의 영상에서 왼쪽의 TS 3.9가 실행되고 오른쪽에 TS 4.0이 실행되는 것을 확인할 수 있다.</p>
<p><a href="https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2020/08/partialModeFast.mp4">https://devblogs.microsoft.com/typescript/wp-content/uploads/sites/11/2020/08/partialModeFast.mp4</a></p>
<p>특히 큰 코드베이스에서 두 편집기를 다시 시작할 때, TS 3.9의 편집기는 완료 목록 또는 빠른 정보를 제공하지 않는다. 반면, TS 4.0의 편집기는 현재 편집 중인 파일에서 다양한 경험을 즉시 제공할 수 있으며, 백그라운드에서 전체 프로젝트를 로드하는 동안에도 이를 제공한다.</p>
<p>현재 이 모드를 지원하는 편집기는 VSCode만 있으며 VSCode Insider에는 UX 개선 사항이 추가될 예정이다. 이 경험에는 UX와 기능적인 면에서 아직 개선이 필요할 수 있다는 것을 인식하고 있으며, 개선 사항을 생각해보고 있다.</p>
<h3 id="더-똑똑한-자동-import">더 똑똑한 자동 import</h3>
<p>자동 가져오기는 코딩을 매우 쉽게 만드는 훌륭한 기능이다. 그러나 자동 가져오기가 작동하지 않을 때마다 사용자들이 당황할 수 있다. 우리가 사용자들로부터 들은 특정 문제 중 하나는 자동 가져오기가 TS로 작성된 의존성에서 작동하지 않는 것이다. 이는 다른곳에서 명시적으로 가져오기를 작성하기 전까지는 그렇다.</p>
<p>자동 가져오기가 <code>@types</code> 패키지에서는 작동하는 것은 왜일까? 자체타입을 제공하는 패키지에선 작동하지 않는 것은 왜일까? 결국 자동 가져오기는 프로젝트에 이미 포함된 패키지에만 작동한다. TS에는 자동으로 <code>node_moduels/@types</code> 폴더의 패키지를 프로젝트에 추가하는 특이한 기본설정을 가지고 있기 때문에 그러한 패키지는 자동으로 가져올 수 있다. 반면에 모든 <code>node_moduels</code> 패키지를 탐색하는 것은 매우 비용이 크기 때문에 다른 패키지는 제외되었다.</p>
<p>이렇게 하면 새로 설치한 것을 아직 사용하지 않은 경우 자동 가져오기를 시도할 때 처음부터 불쾌한 경험을 겪게 된다.</p>
<p>TS 4.0은 편집기 시나리오에서 <code>package.json</code>의 <code>dependencies</code>(과 <code>peerDependencies</code>) 필드에 목록화 된 패키지를 포함하는 약간의 추가 작업을 수행한다. 이러한 패키지로부터의 정보는 자동 가져오기를 개선하는 데만 사용되며, 타입 검사와 같은 다른 것들은 변경되지 않는다. 이렇게 하면 <code>node_modules</code> 탐색의 비용을 지불하지 않고도 모든 의존성에 대한 자동 가져오기를 제공 할 수 있다. 극히 드문 경우, <code>package.json</code>에 아직 가져오지 않은 타입이 있는 의존성이 10개 이상 있는 경우, 이 기능은 자동으로 프로젝트 로딩을 늦추지 않도록 자체를 비활성화한다. 이 기능을 강제로 작동시키거나 온전히 비활성화하려면 편집기에서 설정을 할 수 있어야 한다.  Visual Studio Code에서는 &quot;Include Package JSON Auto Imports&quot; (또는 <code>typescript.preferences.includePackageJsonAutoImports</code>) 설정을 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/45656332-89cc-401e-a2e8-a1ebcd315382/image.png" alt=""></p>
<h2 id="새로운-웹사이트">새로운 웹사이트!</h2>
<p>TypeScript 웹사이트는 최근에 처음부터 다시 작성되어 출시되었다!</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/d7ddf10b-538d-4512-9a37-6b8d68b321c8/image.png" alt=""></p>
<p>우리는 이미 우리의 새로운 사이트에 대해 조금 썼기 때문에, 그곳에서 더 많은 정보를 얻을 수 있다. 하지만 우리는 여전히 의견을 듣고 있고, 질문, 의견 또는 제안이 있는 경우 <a href="https://github.com/microsoft/TypeScript-Website">웹사이트의 이슈트래커</a>에 그것들을 제보할 수 있다.</p>
<h2 id="변경">변경</h2>
<h3 id="libdts-변경"><code>lib.d.ts</code> 변경</h3>
<p><code>lib.d.ts</code> 선언이 변경되었다. 특히 DOM에 대한 유형이 변경되었다. 가장 큰 변화는 document.origin 제거일 수 있다. 이것은 오래된 버전의 IE와 Safari에서만 작동했고, MDN은 <a href="https://developer.mozilla.org/en-US/docs/Web/API/origin">self.origin</a>을 사용하는 것을 권장한다.</p>
<h3 id="접근자를-재정의하는-속성및-vice-versa은-에러">접근자를 재정의하는 속성(및 vice versa)은 에러</h3>
<p>이전에는 <code>useDefineForClassFields</code>를 사용할 때 속성이 접근자를 재정의하거나 접근자가 속성을 재정의하는 것이 오류에 불과했지만, 이제 TS는 기본 클래스의 getter 또는 setter를 재정의 하는 파생 클래스의 속성을 선언할 때 항상 오류를 발생시킨다.</p>
<pre><code class="language-ts">class Base {
  get foo() {
    return 100;
  }
  set foo(value) {
    // ...
  }
}

class Derived extends Base {
  foo = 10;
// &#39;foo&#39; is defined as an accessor in class &#39;Base&#39;, but is overridden here in &#39;Derived&#39; as an instance property.
}</code></pre>
<pre><code class="language-ts">class Base {
  prop = 10;
}

class Derived extends Base {
  get prop() {
// &#39;prop&#39; is defined as a property in class &#39;Base&#39;, but is overridden here in &#39;Derived&#39; as an accessor.
    return 100;
  }
}</code></pre>
<h3 id="delete-피연산자는-선택-사항이어야-한다"><code>delete</code> 피연산자는 선택 사항이어야 한다.</h3>
<p><code>strictNullChecks</code>에서 삭제 연산자를 사용할 때는 피연산자는 이제 <code>any, unknown, never</code> 이거나 선택적이어야 한다.(타입에 <code>undefined</code>가 포함되어 있음) 그렇지 않고, 삭제 연산자를 사용하면 오류가 발생한다.</p>
<pre><code class="language-ts">interface Thing {
  prop: string;
}

function f(x: Thing) {
  delete x.prop;
// The operand of a &#39;delete&#39; operator must be optional.
}</code></pre>
<h2 id="ts의-node-factory-사용은-권장하지-않는다">TS의 Node Factory 사용은 권장하지 않는다.</h2>
<p>TypeScript는 AST Node를 생성하는데 사용되는 &quot;factory&quot; 함수들을 제공하지만, TypeScript 4.0은 새로운 노드 팩토리 API를 제공한다. 그래서 TypeScript 4.0에서는 이러한 이전 함수들을 새로운 함수들로 대체하는 것을 결정했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 3.9 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-3.9-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-3.9-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Sat, 07 Jan 2023 11:45:45 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html</a></p>
</blockquote>
<h2 id="promiseall과-추론-개선">Promise.all과 추론 개선</h2>
<p>최신 버전의 TS(약 3.7)은 <code>Promise.all</code> 및 <code>Promise.race</code>와 같은 함수 선언이 업데이트 되었다. 특히 <code>null</code> 또는 <code>undefined</code>와 값을 혼합할 때, 약간의 회귀가 발생했다.</p>
<pre><code class="language-ts">interface Lion {
  roar(): void;
}
interface Seal {
  singKissFromARose(): void;
}
async function visitZoo(
  lionExhibit: Promise&lt;Lion&gt;,
  sealExhibit: Promise&lt;Seal | undefined&gt;
) {
  let [lion, seal] = await Promise.all([lionExhibit, sealExhibit]);
  lion.roar(); // uh oh
  //  ~~~~
  // Object is possibly &#39;undefined&#39;.
}</code></pre>
<p>이 동작은 이상하다. <code>sealExhibit</code>가 <code>undefined</code>를 포함하는 것은 <code>lion</code>의 타입에 <code>undefined</code>를 포함하게 주입하는 것이다.</p>
<p><a href="https://github.com/jablko">Jack Bates</a>의 <a href="https://github.com/microsoft/TypeScript/pull/34501">PR</a> 덕분에, TS 3.9의 추론 프로세스가 개선되었다. 위 오류는 더 이상 발생하지 않는다. <code>Promise</code>와 관련된 문제로 인해 이전 버전의 TS에서 고생했다면, 3.9를 사용하는 것이 좋을 것이다.</p>
<h2 id="awaited-타입은-무엇인가">awaited 타입은 무엇인가?</h2>
<p>이슈 트래커와 설계 노트를 봐왔다면, <a href="https://github.com/microsoft/TypeScript/pull/35998">awaited라는 새로운 연산자</a>에 대한 일부 작업을 알고 있을 것이다. 이 타입 연산자의 목표는 JS에서 <code>Promise</code>를 푸는 방식을 정확하게 모델링 하는 것이다.</p>
<p>처음에는 TS 3.9에서 <code>awaited</code>을 제공할 것으로 예상했지만, 기존 코드 베이스와 함께 초기 TS 빌드를 실행함으로써 모든 사용자에게 원활하게 배포하기 전에 이 기능에 더 많은 설계 작업이 필요하다는 사실을 알았다. 결과적으로, 더 확실해질 때까지 메인 브랜치에서 이 기능을 빼기로 결정했다. 이 기능에 대해 더 많은 실험을 할 예정이지만, 이번 릴리스에서는 제공하지 않는다.</p>
<h2 id="속도-향상">속도 향상</h2>
<p>TS 3.9는 많은 새로운 속도 향상 기능이 포함되어 있다. material-ui 및 styled-components와 같은 패키지를 사용할 때 편집/컴파일 속도가 매우 열악한 것을 확인한 후 성능에 중점을 두었다. 거대한 unions, intersections, 조건별 타입, 그리고 매핑된 타입과 관련된 특정 병리학적 사례를 최적화 하는 다양한 PR로 심층 분석했다.</p>
<ul>
<li><a href="https://github.com/microsoft/TypeScript/pull/36576">https://github.com/microsoft/TypeScript/pull/36576</a></li>
<li><a href="https://github.com/microsoft/TypeScript/pull/36590">https://github.com/microsoft/TypeScript/pull/36590</a></li>
<li><a href="https://github.com/microsoft/TypeScript/pull/36607">https://github.com/microsoft/TypeScript/pull/36607</a></li>
<li><a href="https://github.com/microsoft/TypeScript/pull/36622">https://github.com/microsoft/TypeScript/pull/36622</a></li>
<li><a href="https://github.com/microsoft/TypeScript/pull/36754">https://github.com/microsoft/TypeScript/pull/36754</a></li>
<li><a href="https://github.com/microsoft/TypeScript/pull/36696">https://github.com/microsoft/TypeScript/pull/36696</a></li>
</ul>
<p>이러한 각 PR은 특정 코드 베이스에서 컴파일 시간이 약 5-10% 단축된다. 전체적으로 material-ui의 컴파일 시간이 약 40% 단축되었다.</p>
<p>또한 에디터 시나리오에서 파일 이름 변경 기능이 일부 변경되었다. VSCode 팀으로부터 파일 이름을 바꿀 때 어떤 import문을 업데이트해야 하는지 파악하는데 5초에서 10초가 소요될 수 있다고 들었다. TS 3.9는 <a href="https://github.com/microsoft/TypeScript/pull/37055">컴파일러 및 언어 서비스가 파일 조회를 캐싱하는 방식의 내부 변경</a>을 통해 이 문제를 해결한다.</p>
<p>여전히 개선의 여지가 있지만, 이 작업이 모든 사람들에게 보다 빠른 경험으로 이어지기를 바란다.</p>
<h2 id="-ts-expect-error-주석">// @ts-expect-error 주석</h2>
<p>TS로 라이브러리를 작성하고 퍼블릭 API의 일부분으로 <code>doStuff</code>라는 함수를 export 한다고 상상해보자. TS 사용자가 타입-체크 오류를 받을 수 있도록 <code>doStuff</code> 함수의 타입은 두 개의 <code>string</code>을 갖는다고 선언하지만, 또한 JS 사용자에게 유용한 오류를 제공하기 위해 런타임 오류를 체크한다. (개발 빌드 시에만 가능).</p>
<pre><code class="language-js">function doStuff(abc: string, xyz: string) {
  assert(typeof abc === &quot;string&quot;);
  assert(typeof xyz === &quot;string&quot;);
  // do some stuff
}</code></pre>
<p>그래서 TS 사용자는 함수를 잘못 사용할 경우 유용한 빨간 오류 밑줄과 오류 메시지를 받게 되며, JS 사용자는 단언 오류를 얻게 된다. 이러한 작동을 테스트하기 위해, 유닛 테스트를 작성하겠다.</p>
<pre><code class="language-ts">expect(() =&gt; {
    doStuff(123, 456);
}).toThrow();</code></pre>
<p>불행히도 위의 테스트가 TS에서 작동된다면, TS는 오류를 발생시킬 것이다.</p>
<pre><code class="language-ts">doStuff(123, 456);
//          ~~~
// error: Type &#39;number&#39; is not assignable to type &#39;string&#39;.</code></pre>
<p>그래서 TS 3.9는 <code>// @ts-expect-error</code> 주석이라는 새로운 기능을 도입했다. 라인 앞에 <code>// @ts-expect-error</code> 주석이 붙어있을 경우, TS는 해당 오류를 보고하는 것을 멈춘다. 그러나 오류가 존재하지 않으면, TS는 <code>// @ts-expect-error</code>가 필요하지 않다고 보고할 것이다.</p>
<p>간단한 예로, 다음 코드는 괜찮다.</p>
<pre><code class="language-ts">// @ts-expect-error
console.log(47 * &quot;octopus&quot;);</code></pre>
<p>그러나 다음 코드는</p>
<pre><code class="language-ts">// @ts-expect-error
console.log(1 + 1);</code></pre>
<p>오류로 이어질 것이다.</p>
<pre><code class="language-ts">Unused &#39;@ts-expect-error&#39; directive.</code></pre>
<p>이 기능을 구현한 컨트리뷰터, <a href="https://github.com/JoshuaKGoldberg">Josh Goldberg</a>에게 큰 감사를 드린다. 자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/36014">the ts-expect-error pull request</a>를 참고하라.</p>
<h2 id="ts-ignore-또는-ts-expect-error-">ts-ignore 또는 ts-expect-error ?</h2>
<p>어떤 점에서는 <code>// @ts-expect-error</code> 가 <code>// @ts-ignore</code>와 유사하게 억제 주석(supressiong comment)로 작용할 수 있다. 차이점은 <code>// @ts-ignore</code>는 다음 행에 오류가 없을 경우 아무것도 하지 않는다는 것이다.</p>
<p>기존 <code>// @ts-ignore</code> 주석을 <code>// @ts-expect-error</code>로 바꾸고 싶은 마음이 들 수 있으며, 향후 코드에 무엇이 적합한지 궁금할 수 있다. 이는 전적으로 당신과 당신 팀의 선택이지만, 어떤 상황에서 어떤 것을 선택할 것인지에 대한 몇 가지 아이디어를 아래에서 확인할 수 있다.</p>
<p>다음 경우라면 <code>ts-expect-error</code>를 선택하라.</p>
<ul>
<li>타입 시스템이 기능에 대한 오류를 발생시키는 테스트 코드 작성을 원하는 경우</li>
<li>수정이 빨리 이루어지길 원하며 빠른 해결책이 필요한 경우</li>
<li>오류가 발생한 코드가 다시 유효해지면 바로 억제 주석을 삭제하길 원하는 혁신적인 팀이 이끄는 적당한-크기의 프로젝트에서 작업하는 경우</li>
</ul>
<p>다음 경우라면 <code>ts-ignore</code>를 선택하라.</p>
<ul>
<li>더 큰 프로젝트를 갖고 있고 코드에서 발생한 새로운 오류의 명확한 책임자를 찾기 힘든 경우</li>
<li>TS의 두 가지 버전 사이에서 업그레이드하는 중이고, 한 버전에서는 코드 오류가 발생하지만 나머지 버전에서는 그렇지 않은 경우</li>
<li>솔직히 어떤 옵션이 더 나은지 결정할 시간이 없는 경우</li>
</ul>
<h2 id="조건문에서-호출되지-않은-함수-체크">조건문에서 호출되지 않은 함수 체크</h2>
<p>TS 3.7에서 함수 호출을 잊어버렸을 경우 오류를 보고하기 위해 호출되지 않은 함수 체크를 도입했다.</p>
<pre><code class="language-ts">function hasImportantPermissions(): boolean {
  // ...
}
// Oops!
if (hasImportantPermissions) {
  //  ~~~~~~~~~~~~~~~~~~~~~~~
  // This condition will always return true since the function is always defined.
  // Did you mean to call it instead?
  deleteAllTheImportantFiles();
}</code></pre>
<p>그러나 이 오류는 <code>if</code>문의 조건에만 적용된다. <a href="https://github.com/a-tarasyuk">Alexander Tarasyuk</a>의 <a href="https://github.com/microsoft/TypeScript/pull/36402">PR</a> 덕분에 이 기능은 삼항 조건 연산자에도 지원하게 되었다.</p>
<pre><code class="language-ts">declare function listFilesOfDirectory(dirPath: string): string[];
declare function isDirectory(): boolean;
function getAllFiles(startFileName: string) {
  const result: string[] = [];
  traverse(startFileName);
  return result;
  function traverse(currentPath: string) {
    return isDirectory
      ? //     ~~~~~~~~~~~
        // This condition will always return true
        // since the function is always defined.
        // Did you mean to call it instead?
        listFilesOfDirectory(currentPath).forEach(traverse)
      : result.push(currentPath);
  }
}</code></pre>
<p><a href="https://github.com/microsoft/TypeScript/issues/36048">https://github.com/microsoft/TypeScript/issues/36048</a></p>
<h2 id="에디터-개선">에디터 개선</h2>
<p>TS 컴파일러는 주요 에디터의 TS 작성 경험뿐만 아니라, Visual Studio 계열 에디터 또는 그 이상의 에디터에 JS 작성 경험에도 영향을 준다. 에디터에서 새로운 TS/JS 기능을 사용하는 것은 에디터에 따라 다르겠지만 </p>
<ul>
<li>VSCode는 <a href="https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-the-workspace-version-of-typescript">다른 버전의 TS 선택</a>을 지원한다. 또는 최신으로 유지하기 위한 <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next">JS/TS Nightly Extension</a>도 있다. </li>
<li>Visual Studio 2017/2019 에는 SDK 설치 프로그램과 <a href="https://www.nuget.org/packages/Microsoft.TypeScript.MSBuild">MSBuild 설치</a>가 있다.</li>
<li>Sublime Text 3은 다른 버전의 TS 선택을 지원한다.</li>
</ul>
<h2 id="js에서-commonjs-자동-import">JS에서 CommonJS 자동-import</h2>
<p>CommonJS 모듈을 사용하는 JS 파일에서 자동-import 기능이 크게 개선되었다.</p>
<p>이전 버전에서는, TS는 항상 파일에 관계없이 ECMAScript 스타일의 import를 원한다고 가정했다.</p>
<pre><code class="language-ts">import * as fs from &quot;fs&quot;;</code></pre>
<p>그러나, 모든 사람들이 JS 파일을 작성할 때, ECMAScript 스타일의 모듈을 원하는 것은 아니다. 많은 사용자가 여전히 CommonJS 스타일의 <code>require(...)</code> import를 사용하고 있다.</p>
<pre><code class="language-ts">const fs = require(&quot;fs&quot;);</code></pre>
<p>이제 TS는 파일 스타일을 깔끔하고 일관되게 유지하기 위해서 사용중인 import 유형을 자동으로 검색한다.</p>
<p>이 변화에 대한 자세한 정보는 <a href="https://github.com/microsoft/TypeScript/pull/37027">해당 PR</a>을 참고하라.</p>
<h2 id="코드-작업-개행-유지">코드 작업 개행 유지</h2>
<p>TS의 리팩터링과 빠른 수정은 종종 개행을 유지하는데 큰 역할을 하지는 않았다. 기본적인 예로 다음 코드를 보자.</p>
<pre><code class="language-ts">const maxValue = 100;
/*start*/
for (let i = 0; i &lt;= maxValue; i++) {
  // First get the squared value.
  let square = i ** 2;

  // Now print the squared value.
  console.log(square);
}
/*end*/</code></pre>
<p>에디터에서 <code>/*시작*/</code>에서 <code>/*끝*/</code>까지 범위를 강조하여 새로운 함수로 추출한다면, 다음과 같은 코드가 된다.</p>
<blockquote>
<p>이 부분은 TS 공식문서의 <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#code-actions-preserve-newlines">첨부된 영상</a> 참고</p>
</blockquote>
<pre><code class="language-ts">const maxValue = 100;

printSquares();

function printSquares() {
  for (let i = 0; i &lt;= maxValue; i++) {
    // First get the squared value.
    let square = i ** 2;
    // Now print the squared value.
    console.log(square);
  }
}</code></pre>
<p>이건 이상적이지 않다. - <code>for</code> 루프에서 각각의 문 사이에 빈 줄이 있었지만 리팩터링이 없애버렸다! TS 3.9은 우리가 작성한 것을 보존하기 위해 조금 더 신경을 쓴다.</p>
<pre><code class="language-ts">const maxValue = 100;

printSquares();

function printSquares() {
  for (let i = 0; i &lt;= maxValue; i++) {
    // First get the squared value.
    let square = i ** 2;

    // Now print the squared value.
    console.log(square);
  }
}</code></pre>
<p><a href="https://github.com/microsoft/TypeScript/pull/36688">이 PR</a>에서 구현에 대해 더 자세히 볼 수 있다.</p>
<h2 id="누락된-반환문-빠른-수정quick-fixes">누락된 반환문 빠른 수정(quick fixes)</h2>
<p>특히 화살표 함수에 중괄호를 추가할 때, 함수의 마지막 문의 값을 반환하는 것을 잊는 경우가 있다.</p>
<pre><code class="language-ts">// before
let f1 = () =&gt; 42;

// oops - not the same!
let f2 = () =&gt; {
  42;
};</code></pre>
<p>커뮤니티 멤버인 <a href="https://github.com/Kingwl">Wenlu Wang</a>의 <a href="https://github.com/microsoft/TypeScript/pull/26434">PR</a> 덕분에, TS는 누락된 <code>return</code>문을 추가하거나, 중괄호를 제거하거나, 객체 리터럴처럼 보이는 화살표 함수 몸체에 괄호를 추가하는 빠른 수정을 제공할 수 있다.</p>
<h2 id="tsconfigjson-파일-솔루션-스타일-지원">tsconfig.json 파일 &#39;솔루션 스타일&#39; 지원</h2>
<p>에디터는 파일이 어떤 설정 파일에 속하는지 파악하여 적절한 옵션을 적용할 수 있도록 하고 현재 &#39;프로젝트&#39;에 어떤 다른 파일이 포함되어 있는지 파악해야 한다. 기본적으로, TS의 언어 서버가 영향을 주는 에디터는 각 상위 디렉터리를 따라 올라가 <code>tsconfig.json</code>을 찾음으로써 이 작업을 수행한다.</p>
<p>이 문제가 약간 실패하는 경우 중 하나는 <code>tsconfig.json</code>이 단순히 다른 <code>tsconfig.json</code> 파일을 참조하기 위해 존재할 때였다.</p>
<pre><code class="language-json">// tsconfig.json
{
    &quot;files&quot;: [],
    &quot;references&quot;: [
        { &quot;path&quot;: &quot;./tsconfig.shared.json&quot; },
        { &quot;path&quot;: &quot;./tsconfig.frontend.json&quot; },
        { &quot;path&quot;: &quot;./tsconfig.backend.json&quot; },
    ]
}</code></pre>
<p>다른 프로젝트 파일을 관리만 하는 이 파일은 어떤 환경에서는 종종 &#39;솔루션&#39;이라고 불린다. 여기서 <code>tsconfig.*.json</code>파일 중 어떤 파일도 서버에 의해 검색되지 않지만, 현재 <code>.ts</code>파일이 루트의 <code>tsconfig.json</code>에 언급된 프로젝트 중 하나에 속한다는 것을 언어 서버가 이해하기를 원한다.</p>
<p>TS 3.9는 이 설정에 대한 시나리오 수정을 지원한다. 더 자세한 사항은 <a href="https://github.com/microsoft/TypeScript/pull/37239">이 기능을 추가한 PR</a>을 확인하라.</p>
<h2 id="주요-변경-사항">주요 변경 사항</h2>
<h3 id="옵셔널-체이닝과-널이-아닌-단언에서-파싱-차이점">옵셔널 체이닝과 널이 아닌 단언에서 파싱 차이점</h3>
<p>최근에 TS는 옵셔널 체이닝 연산자를 도입했지만, 널이 아닌 단언 연산자(<code>!</code>)와 함께 사용하는 선택적 체이닝(<code>?.</code>)의 동작이 매우 직관적이지 않다는 사용자 피드백을 받았다.</p>
<p>구체적으로, 이전 버전에서는 코드가</p>
<pre><code class="language-ts">foo?.bar!.baz;</code></pre>
<p>다음 JS와 동일하게 해석되었다.</p>
<pre><code class="language-ts">(foo?.bar).baz</code></pre>
<p>위의 코드에서 괄호는 옵셔널 체이닝의 &#39;단축평가&#39; 동작을 중단한다. 그래서 만약 <code>foo</code>가 <code>undefined</code>이면, <code>baz</code>에 접근하는 것은 런타임 오류를 발생시킨다.</p>
<p>이 동작을 지적한 바벨 팀과 피드백을 준 대부분의 사용자들은 이 동작이 잘못되었다고 생각한다. <code>bar</code>의 타입에서 <code>null</code>과 <code>undefined</code>를 제거하는 것이 의도이기 때문에 가장 많이 들은 말은 <code>!</code> 연산자는 그냥 &#39;사라져야 한다&#39; 이다.</p>
<p>즉, 대부분의 사람들은 원본 문장을 다음과 같이</p>
<pre><code class="language-ts">foo?.bar.baz;</code></pre>
<p><code>foo</code>가 <code>undefined</code>일 때, 그냥 <code>undefined</code>로 평가하는 것으로 해석되어야 한다고 생각한다.</p>
<p>이것이 주요 변경 사항이지만, 대부분의 코드가 새로운 해석을 염두에 두고 작성되었다고 생각한다. 이전 동작으로 되돌리고 싶은 사용자는 <code>!</code> 연산자 왼쪽에 명시적인 괄호를 추가할 수 있다.</p>
<pre><code class="language-ts">(foo?.bar)!.baz</code></pre>
<h3 id="와-는-이제-유효하지-않은-jsx-텍스트-문자이다">}와 &gt;는 이제 유효하지 않은 JSX 텍스트 문자이다.</h3>
<p>JSX 명세서에는 텍스트 위치에 <code>}</code>와 <code>&gt;</code> 문자의 사용을 금지한다. TS와 바벨은 이 규칙을 더 적합하게 적용하기로 결정했다. 이 문자를 넣기 위한 새로운 방법은 HTML 이스케이프 코드를 사용하거나 (예를 들어, <code>&lt;span&gt; 2 &amp;gt 1 &lt;/span&gt;</code>) 문자열 리터럴로 표현식을 넣는 것이다.(예를 들어, <code>&lt;span&gt; 2 {&quot;&gt;&quot;} 1 &lt;/span&gt;</code>).</p>
<p>다행히, <a href="https://github.com/bradzacher">Brad Zacher</a>의 <a href="https://github.com/microsoft/TypeScript/pull/36636">PR</a> 덕분에, 다음 문장과 함께 오류 메시지를 받을 수 있다.</p>
<p>예를 들어</p>
<pre><code class="language-ts">let directions = &lt;span&gt;Navigate to: Menu Bar &gt; Tools &gt; Options&lt;/div&gt;
//                                           ~       ~
// Unexpected token. Did you mean `{&#39;&gt;&#39;}` or `&gt;`?</code></pre>
<p>이 오류 메시지는 편리한 빠른 수정과 함께 제공되고, <a href="https://github.com/a-tarasyuk">Alexander Tarasyuk</a> 덕분에, 많은 오류가 있으면 <a href="https://github.com/microsoft/TypeScript/pull/37436">이 변경사항을 일괄 적용</a>할 수 있다.</p>
<h3 id="교집합과-선택적-프로퍼티에-대한-더-엄격해진-검사">교집합과 선택적 프로퍼티에 대한 더 엄격해진 검사</h3>
<p>일반적으로 <code>A &amp; B</code>와 같은 교차 타입은 <code>A</code> 또는 <code>B</code>가 <code>C</code>에 할당할 수 있으면, <code>A &amp; B</code>는 <code>C</code>에 할당할 수 있다. 하지만, 가끔 선택적 프로퍼티에서 문제가 생긴다. 예를 들어, 다음을 보자.</p>
<pre><code class="language-ts">interface A {
    a: number; // &#39;number&#39; 인 것에 주목
}
interface B {
    b: string;
}
interface C {
    a?: boolean; // &#39;boolean&#39; 인것에 주목
    b: string;
}
declare let x: A &amp; B;
declare let y: C;
y = x;</code></pre>
<p>이전 버전의 TS에서는 <code>A</code>가 <code>C</code>와 완전히 호환되지 않지만, <code>B</code>가 <code>C</code>와 호환되었기 때문에 허용되었다.</p>
<p>TS 3.9에서는, 교집합 안에 모든 타입이 구체적인 객체 타입이면, 타입 시스템은 모든 프로퍼티를 한 번에 고려한다. 결과적으로, TS는 <code>A &amp; B</code>의 <code>a</code> 프로퍼티는 <code>C</code>의 <code>a</code> 프로퍼티와 호환되지 않는다고 본다.</p>
<pre><code class="language-ts">Type &#39;A &amp; B&#39; is not assignable to type &#39;C&#39;.
  Types of property &#39;a&#39; are incompatible.
    Type &#39;number&#39; is not assignable to type &#39;boolean | undefined&#39;.</code></pre>
<p>이 변경사항에 대한 자세한 정보는, <a href="https://github.com/microsoft/TypeScript/pull/37195">해당 PR</a>을 참조하라.</p>
<h3 id="판별-프로퍼티로-줄어든-교집합">판별 프로퍼티로 줄어든 교집합</h3>
<p>존재하지 않는 값을 기술하는 타입으로 끝날 수 있는 몇 가지 경우가 있다. 예를 들어</p>
<pre><code class="language-ts">declare function smushObjects&lt;T, U&gt;(x: T, y: U): T &amp; U;
interface Circle {
  kind: &quot;circle&quot;;
  radius: number;
}
interface Square {
  kind: &quot;square&quot;;
  sideLength: number;
}
declare let x: Circle;
declare let y: Square;
let z = smushObjects(x, y);
console.log(z.kind);</code></pre>
<p>이 코드는 <code>Circle</code>과 <code>Square</code>의 교집합을 생성할 방법이 전혀 없기 때문에 약간 이상하다. - 호환되지 않는 두 <code>kind</code> 필드가 있다. 이전 버전의 TS에서는, 이 코드는 허용되었고 <code>&quot;circle&quot; &amp; &quot;square&quot;</code>가 <code>절대(never)</code> 존재할 수 없는 값의 집합을 기술했기 때문에 <code>kind</code> 자체의 타입은 <code>never</code>였다.</p>
<p>TS 3.9에서는, 타입 시스템이 더 공격적이다. <code>kind</code> 프로퍼티 때문에 <code>Circle</code>과 <code>Square</code>를 교차하는 것이 불가능하다는 것을 알고 있다. 그래서 <code>z.kind</code>를 <code>never</code>로 축소하는 대신, <code>z</code> 자체(<code>Circle &amp; Square)</code> 타입을 <code>never</code>로 축소한다. 즉 위의 코드는 다음과 같은 오류가 발생한다.</p>
<pre><code class="language-ts">Property &#39;kind&#39; does not exist on type &#39;never&#39;.</code></pre>
<p>위에서 본 대부분의 오류는 약간의 잘못된 타입 선언에 대응하는 것으로 보인다. 자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/36696">원문 PR</a>을 보아라.</p>
<h3 id="getterssetters는-더-이상-enumerable이-아니다">Getters/Setters는 더 이상 Enumerable이 아니다.</h3>
<p>이전 버전의 TS에서 클래스의 <code>get</code>과 <code>set</code> 접근자는 enumerable로 방출되었다. 그러나 이는, <code>get</code>과 <code>set</code>은 열거할 수 없다는 ECMAScript 사양을 따른 것이 아니었다. 결과적으로 ES5와 ES2015를 타겟팅 하는 TS 코드는 동작이 다를 수 있다.</p>
<p>깃허브 사용자 <a href="https://github.com/pathurs">pathurs</a>의 <a href="https://github.com/microsoft/TypeScript/pull/37195">PR</a> 덕분에 TS 3.9는 이와 관련하여 ECMAScript와 더 밀접하게 호환된다.</p>
<h3 id="any로-확장된-타입-매개변수는-더-이상-any처럼-행동하지-않음">any로 확장된 타입 매개변수는 더 이상 any처럼 행동하지 않음</h3>
<p>이전 버전의 TS에서 <code>any</code>로 제한된 타입 매개변수는 <code>any</code>로 다룰 수 있었다.</p>
<pre><code class="language-ts">function foo&lt;T extends any&gt;(arg: T) {
    arg.spfjgerijghoied; // 오류가 아님!
}</code></pre>
<p>이는 실수였다. 그래서 TS 3.9에서는 더 보수적인 접근을 취하고 이런 의심스러운 작업에 대해 오류를 발생시킨다.</p>
<pre><code class="language-ts">function foo&lt;T extends any&gt;(arg: T) {
    arg.spfjgerijghoied;
    //  ~~~~~~~~~~~~~~~
    // &#39;spfjgerijghoied&#39; 프로퍼티는 &#39;T&#39; 타입에 존재하지 않습니다.
}</code></pre>
<h3 id="export--은-항상-유지된다">export * 은 항상 유지된다.</h3>
<p>이전 TS 버전에서 <code>export * from &#39;foo&#39;</code> 같은 선언은 <code>foo</code>가 어떠한 값도 export 하지 않으면 JS 출력에서 제외되었다.이런 내보내기는 타입 지향적이고 바벨에서 에뮬레이트 될 수 없기 때문에 문제가 된다. TS 3.9는 이런 <code>export *</code> 선언을 항상 내보낸다. 이 변화가 기존 코드에 영향을 줄 것이라고 생각하진 않는다.</p>
<h3 id="더-많은-libdomdts-개선">더 많은 libdom.d.ts 개선</h3>
<p>Web IDL 파일에서 바로 TS의 내장 .d.ts 라이브러리가 생성될 수 있도록 DOM 규격의 TS의 내장 .d.ts 라이브러리를 옮기는 작업을 계속 진행하고 있다. 그 결과 미디어 액세스와 관련된 일부 벤더별 타입이 제거되었다.</p>
<p>프로젝트의 ambient <code>*.d.ts</code> 파일에 이 파일을 추가하면 다시 복구할 수 있다.</p>
<pre><code class="language-ts">interface HTMLVideoElement {
  msFrameStep(forward: boolean): void;
  msInsertVideoEffect(activatableClassId: string, effectRequired: boolean, config?: any): void;
  msSetVideoRectangle(left: number, top: number, right: number, bottom: number): void;
  webkitEnterFullScreen(): void;
  webkitEnterFullscreen(): void;
  webkitExitFullScreen(): void;
  webkitExitFullscreen(): void;
  msHorizontalMirror: boolean;
  readonly msIsLayoutOptimalForPlayback: boolean;
  readonly msIsStereo3D: boolean;
  msStereo3DPackingMode: string;
  msStereo3DRenderMode: string;
  msZoom: boolean;
  onMSVideoFormatChanged: ((this: HTMLVideoElement, ev: Event) =&gt; any) | null;
  onMSVideoFrameStepCompleted: ((this: HTMLVideoElement, ev: Event) =&gt; any) | null;
  onMSVideoOptimalLayoutChanged: ((this: HTMLVideoElement, ev: Event) =&gt; any) | null;
  webkitDisplayingFullscreen: boolean;
  webkitSupportsFullscreen: boolean;
}
interface MediaError {
  readonly msExtendedCode: number;
  readonly MS_MEDIA_ERR_ENCRYPTED: number;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RN] First-class Support for TypeScript 번역]]></title>
            <link>https://velog.io/@hustle-dev/RN-First-class-Support-for-TypeScript-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/RN-First-class-Support-for-TypeScript-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Wed, 04 Jan 2023 05:02:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원문: <a href="https://reactnative.dev/blog/2023/01/03/typescript-first">https://reactnative.dev/blog/2023/01/03/typescript-first</a></p>
</blockquote>
<blockquote>
<p>이 포스트는 현재 릴리스 후보인 RN 0.71의 TS 기능들에 대해서 다룬다. <a href="https://github.com/reactwg/react-native-releases/discussions/41?sort=new#discussion-4499027">이곳에서 현재 릴리즈 상태</a>를 확인할 수 있다.</p>
</blockquote>
<p>리액트 네이티브는 0.71의 출시와 함께 아래와 같은 변경 사항을 포함하여 TS 경험에 애쓰고 있다.</p>
<h2 id="타입스크립트를-기본으로-한-새로운-앱-템플릿">타입스크립트를 기본으로 한 새로운 앱 템플릿</h2>
<p>0.71부터 React Native CLI를 통해 새 React Native를 만들면 기본적으로 TS 어플리케이션이 제공된다.</p>
<pre><code class="language-bash">npx react-native init My71App --version 0.71.0</code></pre>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/c46c0b2b-4dd7-4cdb-bc96-84b5da8f9305/image.png" alt=""></p>
<p>새로 생성된 앱의 진입점은 <code>App.js</code> 가 아닌 <code>App.tsx</code> 로, 완전한 TS 타입 파일이다. 새 프로젝트는 이미 <code>tsconfig.json</code> 으로 설정되어 있으므로 즉시 IDE를 사용하여 타입스크립트 코드를 작성할 수 있다!</p>
<h2 id="ts-선언이-리액트-네이티브와-함께-제공">TS 선언이 리액트 네이티브와 함께 제공</h2>
<p>0.71은 TS 선언이 내장된 첫 번째 릴리즈 버전이다.</p>
<p>이전에 리액트 네이티브에 대한 타입스크립트 선언은 <a href="https://www.npmjs.com/package/@types/react-native">@types/react-native</a>에서 호스팅 되는 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped">DefinitelyTyped</a> 레포에서 제공되었다. 리액트 네이티브 소스와 함께 타입스크립트 타입을 공존하게 한 결정은 정확성과 유지보수를 개선하기 위한 것이었다.</p>
<p><code>@types/react-native</code>은 stable 릴리즈 버전에 대한 타입을 제공한다. 즉, TS에서 React Native의 pre-release 버전으로 개발하려면 부정확할 수 있는 이전 릴리즈된 버전의 타입을 사용해야 한다. <code>@types/react-native</code>를 해제하는 것도 오류가 발생하기 쉽다. 이 릴리즈는 리액트 네이티브 릴리즈를 지연시키며, 이 프로세스는 리액트 네이티브의 공용 API에 대한 타입 변경을 수동으로 검사하고 TS 선언을 일치하도록 업데이트 하는 것을 포함한다.</p>
<p>TS 타입은 리액트 네이티브 소스와 함께 공존하므로 TS 타입의 가시성과 소유권이 더 높아진다. 우리 팀은 Flow와 TS간의 정렬을 유지하기 위한 툴을 적극적으로 개발하고 잇다.</p>
<p>이렇게 변경하면 리액트 네이티브 사용자가 관리할 종속성도 제거된다. 0.71 이상으로 업그레이드할 대 종속성으로 <code>@types/react-native</code>를 제거할 수 있다. <a href="https://github.com/facebook/react-native/blob/main/template/tsconfig.json">TS 지원을 설정하는 방법은 새 앱 템플릿을 참조하라.</a></p>
<p>0.73 이상 버전에서는 <code>@types/react-native</code>를 더 이상 사용하지 않을 계획이다. 구체적으로 이것은 다음을 의미한다.</p>
<ul>
<li><code>@types/react-native</code>를 지속적으로 유지하는 React Native 버전 0.71 및 0.72가 출시된다. 이것들은 리액트 네이티브에 관련된 릴리즈 브랜치에 있는 타입들과 동일하다.</li>
<li>리액트 네이티브 0.73 이상의 경우 TS 타입은 React Native에서만 사용할 수 있다.</li>
</ul>
<h3 id="어떻게-마이그레이션을-할지">어떻게 마이그레이션을 할지</h3>
<p>가능한 한 빨리 공존하는 타입을 먼저 마이그레이션 하라. 필요에 따라 마이그레이션 하는 방법에 대한 자세한 내용은 다음과 같다.</p>
<p><strong>앱 메인테이너</strong></p>
<p>0.71 이상의 리액트네이티브로 업그레이드 한 후에는 <code>devDependency</code>에서 <code>@types/react-native</code>를 제거할 수 있다.</p>
<p><code>@types/react-native</code>를 <code>peerDependency</code>로써 참조하는 라이브러리를 사용해서 경고가 발생한 경우 해당 라이브러리에 대해 이슈를 제기하거나 PR을 올려서 선택적 peerDependency를 사용하고 현재는 경고를 무시하라.</p>
<p><strong>라이브러리 메인테이너</strong></p>
<p>0.71 미만의 리액트 네이티브 버전을 대상으로 하는 라이브러리는 <code>@types/react-native</code>의 <code>peerDependency</code>를 사용하여 앱 버전의 타이핑을 확인할 수 있다. 이 디펜던시는 <a href="https://docs.npmjs.com/cli/v7/configuring-npm/package-json#peerdependenciesmeta">peerDependenciesMeta</a>에서 선택사항으로 표기되어야 한다. 그래서 TS가 없는 사용자가 타입이 기본적으로 제공되는 0.71 사용자의 경우에는 입력이 필요하지 않도록 해야한다.</p>
<p><strong><code>@types/react-native</code>에 의존하는 TS 선언 메인테이너</strong></p>
<p>마이그레이션 할 준비가 되어있는지 확인하려면 함께 <a href="https://github.com/facebook/react-native/blob/main/CHANGELOG.md">도입된 변경사항</a>을 확인하라.</p>
<p>0.71이 릴리즈 후보에 있는 동안에는 <a href="https://github.com/facebook/react-native/pull/35200/files">변경 로그 PR</a>을 확인할 수 있다.</p>
<h3 id="만약-flow를-쓴다면">만약 Flow를 쓴다면?</h3>
<p>Flow 사용자는 0.71 이상을 대상으로 하는 타입체킹 어플리케이션을 계속 사용할 수 있지만 이에 대한 구성 로직은 더 이상 템플릿에 기본으로 제공되지 않는다.</p>
<p>Flow 사용자는 <code>flow-bin</code>을 수동으로 업데이트하여 새 앱 템플릿으로부터 <code>.flowconfig</code>를 병합하여  리액트 네이티브의 Flow 타입을 업그레이드했다. 새 앱 템플릿에는 더 이상 <code>.flowconfig</code>가 없지만 <a href="https://github.com/facebook/react-native/blob/main/.flowconfig">앱의 기본으로 사용할 수 있는 리액트 네이티브 레포지토리에는 여전히 <code>.flowconfig</code>가 있다.</a></p>
<p>Flow에서 새 React Native 앱을 시작해야하는 경우 <a href="https://github.com/facebook/react-native/tree/0.70-stable/template">0.70에서 새 엡 템플릿을 참조</a>할 수 있다.</p>
<h3 id="만약-내가-ts-선언에서-버그를-발견한다면">만약 내가 TS 선언에서 버그를 발견한다면?</h3>
<p>내장 TS 유형을 사용하든 <code>@types/react-native</code>를 사용하든 상관없이 버그를 발견하면 <a href="https://github.com/facebook/react-native">리액트 네이티브</a>와 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped">DefinitelyTyped</a>에게 PR을 제출하라. 이 문제를 해결하는 방법을 모를 경우, RN 저장소에 깃헙 이슈를 제출하고 해당 문제에 대해 <a href="https://github.com/lunaleaps">@lunaleaps</a>를 멘션하라.</p>
<h2 id="문서는-ts-first-이다">문서는 TS First 이다.</h2>
<p>일관된 TS 경험을 보장하고, TS를 새로운 기본 언어로 반영하기 위해 RN 문서를 여러 번 업데이트해야했다.</p>
<p>이러한 문서 업데이트는 0.71이 사전 릴리즈 중인 동안 <a href="https://reactnative.dev/docs/next/getting-started">next</a>와 같이 레이블이 지정된 0.71문서를 참조한다.</p>
<p>현재 코드 예제는 인라인 타입스크립트를 허용하고 있고, 170개 이상의 interactive 코드 예제가 업데이트되어 새로운 템플릿에서 린팅, 포맷 및 타입 검사를 통과한다. 대부분의 예제는 TS와 JS 모두에서 유효하다. 호환되지 않는 경우 두 언어 중 하나로 예제를 볼 수 있다.</p>
<p>만약 당신이 실수를 발견했거나 개선점이 있다면, 그 웹사이트는 오픈 소스라는 것을 기억하고 당신의 PR을 받고 싶다!</p>
<h2 id="rn-typescript-커뮤니티에-감사드린다">RN Typescript 커뮤니티에 감사드린다.</h2>
<p>마지막으로, 리액트 네이티브 개발자가 TS를 사용할 수 있도록 커뮤니티가 수년간 수행한 모든 작업을 인정하고 싶다.</p>
<p>2015년부터 <code>@types/react-native</code>를 유지해 온 모든 기여자에게 감사드린다. RN 사용자가 최상의 환경을 경험할 수 있도록 여러분 모두의 노력과 정성을 확인할 수 있었다.</p>
<p>TypeScript 타입을 React Native로 이동하기 위해 도움을 준 @acoates, @eps1lon, @kelset, @tido64, @Titoz 및 @ZihanChen-MSFT에 감사드린다.</p>
<p>마찬가지로, 우리는 첫날부터 리액트 네이티브에서 새로운 앱 개발을 위한 TypeScript 경험을 지원해준 리액트 네이티브 템플릿 유형 스크립트의 유지보수자들에게 감사하고 싶다.</p>
<p>React Native 저장소에서 보다 직접적으로 협업하고 React Native 개발자 환경을 지속적으로 개선할 수 있기를 기대한다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 3.8 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-3.8-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-3.8-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Fri, 30 Dec 2022 14:50:35 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html</a></p>
</blockquote>
<h2 id="type-only-imports와-export">Type-Only Imports와 Export</h2>
<p>이 기능은 대부분의 사용자에겐 생각할 필요가 없는 기능이지만, <a href="https://www.typescriptlang.org/tsconfig#isolatedModules">isolatedModules</a>, TS의 <code>transpileModule</code> API 또는 Babel에서 문제가 발생하면 이 기능과 관련이 있을 수 있다.</p>
<p>TS 3.8은 type-only imports, exports를 위한 새로운 구문이 추가되었다.</p>
<pre><code class="language-ts">import type { SomeThing } from &quot;./some-module.js&quot;;
export type { SomeThing };</code></pre>
<p><code>impmort type</code>은 타입 표기와 선언에 사용될 선언만 import 한다. 이는 항상 완전히 제거되므로, 런타임에 남아있는 것은 없다. 마찬가지로 <code>export type</code>은 타입 문맥에 사용할 export만 제공하며, 이 또한 TS의 output에서 제거된다.</p>
<p>클래스는 런타임에 값을 가지고 있고, design-time(프로그래머가 코딩하는중)에 타입이 있으며 상황에 따라 사용이 다르다는 것에 유의해야 한다. 클래스를 import 하기 위해 <code>import type</code>을 사용하면, 확장 같은 것은 할 수 없다.</p>
<pre><code class="language-ts">import type { Component } from &quot;react&quot;;
interface ButtonProps {
  // ...
}
class Button extends Component&lt;ButtonProps&gt; {
  //               ~~~~~~~~~
  // error! &#39;Component&#39; only refers to a type, but is being used as a value here.
  // ...
}</code></pre>
<p>이전에 Flow를 사용해본 적이 있다면, 이 구문은 상당하게 유사하다. 한 가지 차이점은 코드가 모호해 보이지 않도록 몇 가지 제한을 두었다는 것이다.</p>
<pre><code class="language-ts">// Is only &#39;Foo&#39; a type? Or every declaration in the import?
// We just give an error because it&#39;s not clear.
import type Foo, { Bar, Baz } from &quot;some-module&quot;;
//     ~~~~~~~~~~~~~~~~~~~~~~
// error! A type-only import can specify a default import or</code></pre>
<p><code>import type</code>과 함께, TS 3.8은 런타임 시 사용되지 않는 import에서 발생하는 작업을 제어하기 위해 새로운 컴파일 플래그를 추가한다: <a href="https://www.typescriptlang.org/tsconfig#importsNotUsedAsValues">importsNotUsedAsValues</a> 이 플래그는 3 가지 다른 값을 가진다.</p>
<ul>
<li><code>remove</code>: 이는 기본값으로, imports를 제거하는 동작이며, 기존 동작을 바꾸는 플래그는 아니다.</li>
<li><code>preserve</code>: 이는 사용되지 않는 값들을 모두 보존한다. 이로 인해 imports/사이드 이펙트가 보존될 수 있다.</li>
<li><code>error</code>: 이는 모든(<code>preserve</code> 옵션처럼) 모든 imports를 보존하지만, import 값이 타입으로만 사용될 경우 오류를 발생시킨다. 이는 실수로 값을 import하지 않지만 사이드 이펙트 import를 명시적으로 만들고 싶을 때 유용하다.</li>
</ul>
<p>이 기능에 대한 자세한 정보는 <code>import type</code>이 선언될 수 있는 범위를 확대하는 <a href="https://github.com/microsoft/TypeScript/pull/35200">PR</a>과 <a href="https://github.com/microsoft/TypeScript/pull/36092/">관련된 변경 사항</a>에서 확인할 수 있다.</p>
<h2 id="ecmascript-비공개-필드">ECMAScript 비공개 필드</h2>
<p>TS 3.8은 ECMAScript의 <a href="https://github.com/tc39/proposal-class-fields/">stage-3 클래스 필드 제안</a>의 비공개 필드를 지원한다.</p>
<pre><code class="language-ts">class Person {
  #name: string;
  constructor(name: string) {
    this.#name = name;
  }
  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}
let jeremy = new Person(&quot;Jeremy Bearimy&quot;);
jeremy.#name;
//     ~~~~~
// Property &#39;#name&#39; is not accessible outside class &#39;Person&#39;
// because it has a private identifier.</code></pre>
<p>일반적인 프로퍼티들과 달리, 비공개 필드는 몇 가지 주의해야할 규칙이 있다.</p>
<ul>
<li>비공개 필드는 <code>#</code>문자로 시작한다. 때때로 이를 비공개 이름이라고 부른다.</li>
<li>모든 비공개 필드 이름은 이를 포함한 클래스 범위에서 유일하다.</li>
<li><code>public</code> 또는 <code>private</code> 같은 TS 접근 지정자는 비공개 필드로 사용될 수 없다.</li>
<li>JS 사용자로부터도 비공개 필드는 이를 포함한 클래스 밖에서 접근하거나 탐지할 수 없다. 때때로 이를 강한 비공개(hard privacy)라고 부른다.</li>
</ul>
<p>hard privacy와 별개로, 비공개 필드의 또 다른 장점은 유일하다는 것이다. 예를 들어, 일반적인 프로퍼티 선언은 하위클래스에서 덮어쓰기 쉽다.</p>
<pre><code class="language-ts">class C {
  foo = 10;
  cHelper() {
    return this.foo;
  }
}
class D extends C {
  foo = 20;
  dHelper() {
    return this.foo;
  }
}
let instance = new D();
// &#39;this.foo&#39; refers to the same property on each instance.
console.log(instance.cHelper()); // prints &#39;20&#39;
console.log(instance.dHelper()); // prints &#39;20&#39;</code></pre>
<p>비공개 필드에서는, 포함하고 있는 클래스에서 각각의 필드 이름이 유일하기 때문에 이에 대해 걱정하지 않아도 된다.</p>
<pre><code class="language-ts">class C {
  #foo = 10;
  cHelper() {
    return this.#foo;
  }
}
class D extends C {
  #foo = 20;
  dHelper() {
    return this.#foo;
  }
}
let instance = new D();
// &#39;this.#foo&#39; refers to a different field within each class.
console.log(instance.cHelper()); // prints &#39;10&#39;
console.log(instance.dHelper()); // prints &#39;20&#39;</code></pre>
<p>알아두면 좋은 또 다른 점은 다른 타입으로 비공개 필드에 접근하면 <code>TypeError</code>를 발생한다는 것이다.</p>
<pre><code class="language-ts">class Square {
  #sideLength: number;
  constructor(sideLength: number) {
    this.#sideLength = sideLength;
  }
  equals(other: any) {
    return this.#sideLength === other.#sideLength;
  }
}
const a = new Square(100);
const b = { sideLength: 100 };
// Boom!
// TypeError: attempted to get private field on non-instance
// This fails because &#39;b&#39; is not an instance of &#39;Square&#39;.
console.log(a.equals(b));</code></pre>
<p>마지막으로, 모든 일반 <code>.js</code> 파일 사용자들의 경우, 비공개 필드는 항상 할당되기 전에 선언되어야 한다.</p>
<pre><code class="language-ts">class C {
  // No declaration for &#39;#foo&#39;
  // :(
  constructor(foo: number) {
    // SyntaxError!
    // &#39;#foo&#39; needs to be declared before writing to it.
    this.#foo = foo;
  }
}</code></pre>
<p>JS는 항상 사용자들에게 선언되지 않은 프로퍼티에 접근을 허용했지만, TS는 항상 클래스 프로퍼티 선언을 요구했다. 비공개 필드는 <code>.js</code> 또는 <code>.ts</code>파일에서 동작하는지 상관없이 항상 선언이 필요하다.</p>
<pre><code class="language-ts">class C {
  /** @type {number} */
  #foo;
  constructor(foo: number) {
    // This works.
    this.#foo = foo;
  }
}</code></pre>
<p>구현과 관련한 더 많은 정보는 <a href="https://github.com/Microsoft/TypeScript/pull/30829">원본 PR</a>을 참고</p>
<h2 id="which-should-i-use">Which should I use?</h2>
<p>이미 TS 유저로서 어떤 종류의 private를 사용해야 하는지에 대한 많은 질문을 받았다. 주로 <code>private</code> 키워드를 사용해야 하나요? 아니면 ECMAScript의 <code>#</code> private fields를 사용해야 하나요? 이는 상황마다 다르다.</p>
<p>프로퍼티에서 TS의 <code>private</code> 지정자는 완전히 지워진다. 이는 런타임에서는 완전히 일반 프로퍼티처럼 동작하며 이것이 <code>private</code> 지정자로 선언되었다고 알릴 방법이 없다. <code>prviate</code> 키워드를 사용할 때, 비공개는 오직 컴파일-타임/디자인-타임에만 시행되며, JS 사용자에게는 전적으로 의도기반이다.</p>
<pre><code class="language-ts">class C {
  private foo = 10;
}
// This is an error at compile time,
// but when TypeScript outputs .js files,
// it&#39;ll run fine and print &#39;10&#39;.
console.log(new C().foo); // prints &#39;10&#39;
//                  ~~~
// error! Property &#39;foo&#39; is private and only accessible within class &#39;C&#39;.
// TypeScript allows this at compile-time
// as a &quot;work-around&quot; to avoid the error.
console.log(new C()[&quot;foo&quot;]); // prints &#39;10&#39;</code></pre>
<p>이 같은 종류의 약한 비공개(soft privacy)는 사용자가 API에 접근할 수 없는 상태에서 일시적으로 작업을 하는 데 도움이 되며, 어떤 런타임에서도 동작한다.</p>
<p>반면에, ECMAScript의 <code>#</code> 비공개는 완벽하게 클래스 밖에서 접근 불가능하다.</p>
<pre><code class="language-ts">class C {
  #foo = 10;
}
console.log(new C().#foo); // SyntaxError
//                  ~~~~
// TypeScript reports an error *and*
// this won&#39;t work at runtime!
console.log(new C()[&quot;#foo&quot;]); // prints undefined
//          ~~~~~~~~~~~~~~~
// TypeScript reports an error under &#39;noImplicitAny&#39;,
// and this prints &#39;undefined&#39;.</code></pre>
<p>이런 강한 비공개(hard privacy)는 아무도 내부를 사용할 수 없도록 엄격하게 보장하는 데 유용하다. 만약 라이브러리 작성자일 경우, 비공개 필드를 제거하거나 이름을 바꾸는 것이 급격한 변화를 초래해서는 안된다.</p>
<blockquote>
<p>비공개 필드는 실제로 사용하는 측에서 안보이기 때문에?</p>
</blockquote>
<p>언급했듯이, 다른 장점은 ECMAScript의 <code>#</code> 비공개가 말그대로의 비공개이기 때문에 서브 클래싱을 쉽게할 수 있다는 것이다. ECMAScript의 <code>#</code> 비공개 필드를 사용하면, 어떤 서브 클래스도 필드 네이밍 충돌에 대해 걱정할 필요가 없다. TS의 <code>private</code> 프로퍼티 선언에서는, 사용자는 여전히 상위 클래스에 선언된 프로퍼티를 짓밟지 않도록 주의해야 한다.</p>
<p>한 가지 더 생각해봐야할 것은 코드가 실행되기를 의도하는 곳이다. 현재 TS는 이 기능을 ECMAScript 2015(ES6) 이상 버전을 대상으로 하지 않으면 지원할 수 없다. 이는 하위 레벨 구현이 비공개를 강제하기 위해 <code>WeakMap</code>을 사용하는데, <code>WeakMap</code>은 메모리 누수를 일으키지 않도록 폴리필될 수 없기 때문이다. 반면, TS의 <code>private</code>선언 프로퍼티는 모든 대상에서 동작한다. (심지어 ECMASCript 3에서도)</p>
<p>마지막 고려 사항은 속도 일수 있다.: <code>private</code> 프로퍼티는 다른 어떤 프로퍼티와 다르지 않기 때문에, 어떤 런타임의 대상으로 하든 다른 프로퍼티와 마찬가지로 접근 속도가 빠를 수 있다. 반면에 <code>#</code> 비공개 필드는 <code>WeakMap</code>을 이용해 다운 레벨되기 때문에 사용 속도가 느려질 수 잇다. 어떤 런타임은 <code>#</code> 비공개 필드 구현을 최적화 하고, 더 빠른 <code>WeakMap</code>을 구현을 가지고 있을 수 있지만 모든 런타임에서 그렇지 않을 수 있다.</p>
<h2 id="export--as-ns-문법">export * as ns 문법</h2>
<p>다른 모듈의 모든 멤버를 단일 멤버로 내보내는 단일 진입점을 갖는 것은 종종 일반적이다.</p>
<pre><code class="language-ts">import * as utilities from &quot;./utilities.js&quot;;
export { utilities };</code></pre>
<p>이는 매우 일반적이어서 ECMAScript 2020은 최근 이 패턴을 지원하기 위해 새로운 문법을 추가했다.</p>
<pre><code class="language-ts">export * as utilities from &quot;./utilities.js&quot;;</code></pre>
<p>이것은 JS를 한층 더 향상시키고 TS 3.8은 이 문법을 지원한다. 모듈 대상이 <code>es2020</code>보다 이전인 경우, TS는 첫번째 코드 스니펫을 따라서 출력할 것이다.</p>
<h2 id="top-level-await">Top-Level await</h2>
<p>TS 3.8은 &#39;top-level <code>await</code>&#39;라고 불리는 편리한 ECMAScript기능을 지원한다.</p>
<p>JS 유저는 <code>await</code>를 사용하기 위해 <code>async</code> 함수를 호출하는데, 이를 정의한 후 즉시 함수를 호출한다.</p>
<pre><code class="language-ts">async function main() {
  const response = await fetch(&quot;...&quot;);
  const greeting = await response.text();
  console.log(greeting);
}
main().catch((e) =&gt; console.error(e));</code></pre>
<p>이는 이전의 JS가 <code>await</code> 키워드는 오직 <code>async</code> 함수 몸체 안에서 허용되었기 때문에 그랬다. 하지만 top-level <code>await</code>과 함께, 모듈의 최상위 레벨에서 <code>await</code> 키워드를 사용할 수 있다.</p>
<pre><code class="language-ts">const response = await fetch(&quot;...&quot;);
const greeting = await response.text();
console.log(greeting);
// Make sure we&#39;re a module
export {};</code></pre>
<p>최상위 레벨 <code>await</code>은 오직 모듈의 최상단에서 동작하며 타입스크립트가 <code>import</code> 혹은 <code>export</code>를 찾을 때에만 모듈로 간주된다. 일부 기본적인 경우에, <code>export {}</code>와 같은 보일러 플레이트를 작성하여 이를 확인할 필요가 있다.</p>
<p>이러한 경우가 예상되는 모든 환경에서 Top Level <code>await</code>은 동작하지 않을 수 있다. 현재, <code>target</code> 컴파일러 옵션이 <code>es2017</code> 이상이고 <code>module</code>이 <code>esnext</code>또는 <code>system</code>인 경우에만 최상위 레벨 <code>await</code>를 사용할 수 잇다. 몇몇 환경과 번들러내에서의 지원은 제한적으로 작동하거나 실험적 지원을 활성화해야할 수 있다.</p>
<p>구현에 관한 더 자세한 정보는 <a href="https://github.com/microsoft/TypeScript/pull/35813">원본 PR</a>을 참고</p>
<h2 id="es2020용-target과-module">es2020용 target과 module</h2>
<p>TS 3.8은 <code>es2020</code>을 <code>module</code>과 <a href="https://www.typescriptlang.org/tsconfig/#target">target</a> 옵션으로 지원한다. 이를 통해 옵셔널 체이닝이나 널병합 연산, <code>export * as ns</code>, 동적 <code>import(...)</code> 문법과 같은 ECMAScript 2020 기능을 사용할 수 있다. 또한 <code>bigint</code> 리터럴이 <code>esnext</code> 아래에 안정적인 target을 갖는 것을 의미한다.</p>
<h2 id="jsdoc-프로퍼티-지정자">JSDoc 프로퍼티 지정자</h2>
<p>TS 3.8은 <code>allowJs</code> 플래그를 사용하여 JS 파일을 지원하고, <code>checkJs</code> 옵션이나 <code>// @ts-check</code> 주석을 <code>.js</code> 파일의 최상단에 추가함으로써 JS 파일의 타입 검사를 지원한다.</p>
<p>JS 파일에는 타입 체킹을 위한 전용 문법이 없기 때문에, TS는 JSDoc을 활용한다. TS 3.8은 프로퍼티에 대한 몇 가지 새로운 JSDoc 태그를 인식한다.</p>
<p>먼저 접근 지정자이다. <code>@public</code>, <code>@private</code>, 그리고 <code>@protected</code>이다. 이 태그들은 <code>public</code> <code>private</code>, <code>protected</code>와 동일하게 동작한다.</p>
<pre><code class="language-ts">// @ts-check
class Foo {
  constructor() {
    /** @private */
    this.stuff = 100;
  }
  printStuff() {
    console.log(this.stuff);
  }
}
new Foo().stuff;
//        ~~~~~
// error! Property &#39;stuff&#39; is private and only accessible within class &#39;Foo&#39;.</code></pre>
<ul>
<li><code>@public</code>은 항상 암시적이며 생략될 수 있지만, 어디서든 프로퍼티에 접근 가능함을 의미한다.</li>
<li><code>@private</code>은 오직 프로퍼티를 포함하는 클래스 안에서만 해당 프로퍼티는 사용할 수 있음을 의미한다.</li>
<li><code>@protected</code>는 프로퍼티를 포함하는 클래스와 파생된 모든 하위 클래스내에서 해당 프로퍼티를 사용할 수 있지만, 포함하는 클래스의 인스턴스는 해당 프로퍼티를 사용할 수 없다.</li>
</ul>
<p>다음으로, <code>@readonly</code> 지정자를 추가하여 프로퍼티가 초기화 과정 내에서만 값이 쓰이는 것을 보장한다.</p>
<pre><code class="language-ts">// @ts-check
class Foo {
  constructor() {
    /** @readonly */
    this.stuff = 100;
  }
  writeToStuff() {
    this.stuff = 200;
    //   ~~~~~
    // Cannot assign to &#39;stuff&#39; because it is a read-only property.
  }
}
new Foo().stuff++;
//        ~~~~~
// Cannot assign to &#39;stuff&#39; because it is a read-only property.</code></pre>
<h2 id="리눅스에서-더-나은-디렉터리-감시와-watchoptions">리눅스에서 더 나은 디렉터리 감시와 watchOptions</h2>
<p>TS 3.8에서는 <code>node_modules</code>의 변경사항을 효율적으로 수집하는데 중요한 새로운 디렉터리 감시전략을 제공한다.</p>
<p>리눅스와 같은 운영체제에서 TS는 <code>node_modules</code>에 디렉터리 왓쳐를 설치하고 의존성 변화를 감지하기 위해 많은 하위 디렉터리를 설치한다. 왜냐하면 사용 가능한 파일 왓쳐의 수는 <code>node_modules</code>의 파일 수에 의해 가려지기 때문이고, 추적할 디렉터리 수가 적기 때문이다.</p>
<p>TS의 이전 버전은 즉각적으로 폴더에 디렉터리 왓쳐를 즉시 설치하며, 초기에는 괜찮을 것이다. 그러나 npm install을 할 때, <code>node_modules</code>안에서 많은 일들이 발생할 것이고, TS를 압도하여 종종 에디터 세션을 아주 느리게 만든다. 이를 방지하기 위해, TS 3.8은 디렉터리 왓쳐를 설치하기 전에 조금 기다려서 변동성이 높은 디렉터리에게 안정화될 시간을 준다.</p>
<p>모든 프로젝트는 다른 전략에서 더 잘 작동할 수 있고, 이 새로운 방법은 당신의 작업 흐름에는 잘 맞지 않을 수 있기 때문에, TS 3.8은 파일과 디렉터리를 감시하는데 어떤 감시 전략을 사용할지 컴파일러/언어 서비스에 알려줄 수 있도록 <code>tsconfig.json</code>과 <code>jsconfig.json</code>에 <code>watchOptions</code>란 새로운 필드를 제공한다.</p>
<pre><code class="language-json">{
  // Some typical compiler options
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es2020&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;
    // ...
  },
  // NEW: Options for file/directory watching
  &quot;watchOptions&quot;: {
    // Use native file system events for files and directories
    &quot;watchFile&quot;: &quot;useFsEvents&quot;,
    &quot;watchDirectory&quot;: &quot;useFsEvents&quot;,
    // Poll files for updates more frequently
    // when they&#39;re updated a lot.
    &quot;fallbackPolling&quot;: &quot;dynamicPriority&quot;
  }
}</code></pre>
<p><code>watchOptions</code>는 구성할 수 있는 4가지의 새로운 옵션이 포함되어 있다.</p>
<ul>
<li><a href="https://www.typescriptlang.org/tsconfig#watchFile">watchFile</a>: 각 파일의 감시 방법 전략. 다음과 같이 설정할 수 있다.<ul>
<li><code>fixedPollingInterval</code>: 고정된 간격으로 모든 파일의 변경을 1초에 여러 번 검사한다.</li>
<li><code>priorityPollingInterval</code>: 모든 파일의 변경을 1초에 여러 번 검사한다. 하지만 휴리스틱을 사용하여 특정 타입의 파일은 다른 타입의 파일보다 덜 자주 검사한다.</li>
<li><code>dynamicPriorityPolling</code>: 동적 큐를 사용하여 자주 수정되지 않은 파일은 적게 검사한다.</li>
<li><code>useFsEvents</code>(디폴트): 파일 변화에 운영체제/파일 시스템의 네이티브 이벤트를 사용한다.</li>
<li><code>useFsEventsOnParentDirectory</code>: 파일을 포함하고 있는 디렉터리를 감지할 때, 운영체제/파일 시스템의 네이티브 이벤트를 사용한다. 파일 왓쳐를 적게 사용할 수 있지만, 덜 정확할 수 있다.</li>
</ul>
</li>
<li><a href="https://www.typescriptlang.org/tsconfig#watchDirectory">watchDirectory</a>: 재귀적인 파일 감시기능이 없는 시스템 안에서 전체 디렉터리 트리가 감시되는 전략. 다음과 같이 설정할 수 있다.<ul>
<li><code>fixedPollingInterval</code>: 고정된 간격으로 모든 디렉터리의 변경을 1초에 여러 번 검사한다.</li>
<li><code>dynamicPriorityPolling</code>: 동적 큐를 사용하여 자주 수정되지 않은 디렉터리는 적게 검사한다.</li>
<li><code>useFsEvents</code>(디폴트): 디렉터리 변경에 운영체제/파일 시스템의 네이티브 이벤트를 사용한다.</li>
</ul>
</li>
<li><a href="https://www.typescriptlang.org/tsconfig#fallbackPolling">fallbackPolling</a><ul>
<li><code>fixedPollingInterval</code>: 위 내용 참조</li>
<li><code>priorityPollingInterval</code>: 위 내용 참조</li>
<li><code>dynamicPriorityPolling</code>: 위 내용 참조</li>
<li><code>synchronousWatchDirectory</code>: 디렉터리의 연기된 감시를 비활성화 한다. 연기된 감시는 많은 파일이 한 번에 변경될 때 유용하다. (예를 들어, <code>npm install</code>을 실행하여 <code>node_modules</code>의 변경), 하지만 보편적이지 않은 설정이기 때문에 비활성화할 수 있다.</li>
</ul>
</li>
</ul>
<p>  이 변경에 대한 더 자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/35615">Github으로 이동하여 PR 참고</a></p>
<h2 id="빠르고-느슨한-증분-검사">&quot;빠르고 느슨한&quot; 증분 검사</h2>
<p>TS 3.8은 새로운 컴파일러 옵션인 <a href="https://www.typescriptlang.org/tsconfig#assumeChangesOnlyAffectDirectDependencies">assumeChangesOnlyAffectDirectDepencies</a>을 제공한다. 이 옵션이 활성화 되면, TS는 영향을 받은 파일들은 재검사/재빌드 하지않고, 변경된 파일과 직접 import한 파일만 재검사/재빌드 한다.</p>
<p>예를들어, 다음과 같이 <code>fileA.ts</code>를 import한 <code>fileB.ts</code>를 import한 <code>fileC.ts</code>를 import한 <code>fileD.ts</code>를 살펴보자.</p>
<pre><code class="language-ts">fileA.ts &lt;- fileB.ts &lt;- fileC.ts &lt;- fileD.ts</code></pre>
<p><code>--watch</code> 모드에서는, <code>fileA.ts</code>의 변경이 <code>fileB.ts</code>, <code>fileC.ts</code> 그리고 <code>fileD.ts</code>를 TS가 재검사 해야한다는 의미이다. <code>assumeChangesOnlyAffectDirectDependencies</code>에서는 <code>fileA.ts</code>의 변경은 <code>fileA.ts</code>와 <code>fileB.ts</code>만 재검사하면 된다.</p>
<p>VSCode와 같은 코드 베이스에서는, 특정 파일의 변경에 대해 약 14초에서 약 1초로 재 빌드 시간을 줄여주었다. 이 옵션을 모든 코드 베이스에서 추천하는 것은 아니지만, 큰 코드베이스를 가지고 있고, 나중까지 전체 프로젝트 오류를 기꺼이 연기하겠다면 이 옵션이 흥미로울 것이다.</p>
<p>더 자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/35711">원본 PR 참고</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 3.7 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-3.7-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-3.7-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Sun, 25 Dec 2022 09:56:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html</a></p>
</blockquote>
<h2 id="옵셔널-체이닝">옵셔널 체이닝</h2>
<p>옵셔널 체이닝은 이슈 트래커에 <a href="https://github.com/microsoft/TypeScript/issues/16">issue #16</a>으로 있다.</p>
<p>핵심적으로 옵셔널 체이닝은 <code>null</code>이나 <code>undefined</code>인 경우 TS가 일부 표현식의 실행을 즉시 중지할 수 있는 코드를 작성할 수 있게 해준다. 옵셔널 체이닝은 옵션 속성 접근에 대한 새로운 연산자인 <code>?.</code>를 이용한다. 다음과 같이 코드를 사용한다.</p>
<pre><code class="language-ts">let x = foo?.bar.baz();</code></pre>
<p>이것은 <code>foo</code>가 정의될 때 <code>foo.bar.baz()</code>가 실행될 것이라고 이야기하는 방법이다. 그러나 <code>foo</code>가 <code>null</code> 이거나 <code>undefined</code>인 경우, 하고 있는 것을 중지하고 <code>undefined</code>를 반환한다.</p>
<p>좀 더 명확하게, 옵셔널 체이닝은 아래와 같은 의미의 코드로 볼 수 있다.</p>
<pre><code class="language-ts">let x = foo === null || foo === undefined ? undefined : foo.bar.baz();</code></pre>
<p>만약 <code>bar</code>가 <code>null</code>이나 <code>undefined</code>인 경우 코드는 <code>baz</code>로 접근할 때 에러를 발생시킨다. 마찬가지로 <code>baz</code>가 <code>null</code>이나 <code>undefined</code>인 경우 호출하는 쪽에서 에러를 발생시킨다. <code>?.</code>는 왼쪽의 값이 <code>null</code>인지 <code>undefined</code>인지만 확인한다. 뒤에 오는 속성은 확인하지 않는다.</p>
<p><code>?.</code>(옵셔널 체이닝 연산자)는 <code>&amp;&amp;</code> 연산자를 사용하여 반복적인 널리쉬 검사를 수행하는 코드를 대체한다.</p>
<pre><code class="language-ts">// Before
if (foo &amp;&amp; foo.bar &amp;&amp; foo.bar.baz) {
  // ...
}
// After-ish
if (foo?.bar?.baz) {
  // ...
}</code></pre>
<p><code>?.</code>는 실제로 <code>&amp;&amp;</code> 연산과 다르게 동작하는 것을 명심해야 하는데, <code>&amp;&amp;</code>는 falsy 값에 대해 동작하기 동작하기 때문이다. 그러나 이것은 의도적인 구조적 특징으로, 옵셔널 체이닝의 경우 0이나 빈 문자열과 같은 유효한 데이터에서는 단축평가 되지 않는다.</p>
<p>옵셔널 체이닝에선 두 가지 다른 작업도 포함된다. 먼저 선택적 프로퍼티 액세스와 유사하게 동작하지만 식별자가 아닌 속성(임의 문자열, 숫자 및 기호)에 액세스할 수 있는 선택적 요소 액세스가 있다. 코드로 보는게 더 이해가 쉬울 것이다.</p>
<pre><code class="language-ts">/**
 * Get the first element of the array if we have an array.
 * Otherwise return undefined.
 */
function tryGetFirstElement&lt;T&gt;(arr?: T[]) {
  return arr?.[0];
  // equivalent to
  //   return (arr === null || arr === undefined) ?
  //       undefined :
  //       arr[0];
}</code></pre>
<p>또한 선택적 호출이 있으며, 이를 통해 표현식이 <code>null</code>이거나 <code>undefined</code>인 경우 조건부 호출이 가능하다.</p>
<pre><code class="language-ts">async function makeRequest(url: string, log?: (msg: string) =&gt; void) {
  log?.(`Request started at ${new Date().toISOString()}`);
  // roughly equivalent to
  //   if (log != null) {
  //       log(`Request started at ${new Date().toISOString()}`);
  //   }
  const result = (await fetch(url)).json();
  log?.(`Request finished at ${new Date().toISOString()}`);
  return result;
}</code></pre>
<p>옵셔널 체이닝이 갖는 단축 평가 동작은 제한된 프로퍼티 접근, 호출, 요소 접근이다. 이는 이러한 표현에서 더 이상 확장되지 않는다.</p>
<p>아래의 코드를 살펴보자.</p>
<pre><code class="language-ts">// 나누기 또는 someComputation() 호출이 발생하는 것을 중지하지 않는다.
let result = foo?.bar / someComputation();

// 위는 다음의 코드와 동일하다.
let temp = foo === null || foo === undefined ? undefined : foo.bar;
let result = temp / someComputation();</code></pre>
<p>이 경우 <code>undefined</code>를 나누는 결과가 될 수 있으므로 <a href="https://www.typescriptlang.org/tsconfig/#strictNullChecks">stictNullChecks</a>에서 아래와 같이 오류가 발생한다.</p>
<pre><code class="language-ts">function barPercentage(foo?: { bar: number }) {
  return foo?.bar / 100;
  //     ~~~~~~~~
  // Error: Object is possibly undefined.
}</code></pre>
<p>더 자세한 내용은 <a href="https://github.com/tc39/proposal-optional-chaining/">proposal</a>과 <a href="https://github.com/microsoft/TypeScript/pull/33294">원래의 PR</a>을 확인하자.</p>
<h2 id="널병합-연산자">널병합 연산자</h2>
<p>널 병합 연산자는 옵셔널 체이닝과 함께 ECMAScript에 제안에 있는 기능이다.</p>
<p><code>??</code> 연산자는 <code>null</code>또는 <code>undefined</code>를 처리할 때 기본값으로 폴백하는 방법이다.</p>
<p>아래의 예시를 보자.</p>
<pre><code class="language-ts">let x = foo ?? bar();</code></pre>
<p>이것은 값 <code>foo</code>가 존재할 때, 사용될 것이라고 말하는 새로운 방법이다. 그러나 <code>null</code> 또는 <code>undefined</code>인 경우 대신 <code>bar()</code>를 계산한다. 위 코드는 아래와 같다.</p>
<pre><code class="language-ts">let x = foo !== null &amp;&amp; foo !== undefined ? foo : bar();</code></pre>
<p><code>??</code> 연산자는 기본값을 사용하려고 할 때, <code>||</code> 연산자의 사용을 대체할 수 있다. 예를들어, 다음 코드는 <code>localStorage</code>에 마지막으로 저장된 볼륨을 가져오려고 시도하지만 <code>||</code>를 사용하기 때문에 버그가 있다.</p>
<pre><code class="language-ts">function initializeAudio() {
  let volume = localStorage.volume || 0.5;
  // ...
}</code></pre>
<p><code>localStorage.volume</code>이 0으로 설정되었을 때, 페이지에서 볼륨을 0.5로 설정한. 이는 의도하지 않은 동작이며 <code>??</code> 연산자는 값이 <code>0, NaN, &#39;&#39;</code>일 때, 의도하지 않은 동작으로 잘못된 값으로 처리되는 것을 방지한다.</p>
<h2 id="assertion-함수">Assertion 함수</h2>
<p>예기치 않은 일이 발생하면 오류를 발생시키는 특정 함수 세트가 있다. 그것들은 assertion 함수라고 부른다. 예를 들어, Node.js는 이를 위한 전용 함수인 <code>assert</code>를 가지고 있다.</p>
<pre><code class="language-ts">assert(someValue === 42);</code></pre>
<p>이 예에서 <code>someValue</code>가 <code>42</code>와 같지 않으면 <code>assert</code>는 <code>AssertionError</code>를 발생시킨다.</p>
<p>JS에서 assertion은 아래 예시처럼 종종 부적절한 유형이 전달되지 않도록 보호하기 위해 사용된다.</p>
<pre><code class="language-ts">function multiply(x, y) {
  assert(typeof x === &quot;number&quot;);
  assert(typeof y === &quot;number&quot;);
  return x * y;
}</code></pre>
<p>불행이도, TS에서는 이러한 검사를 제대로 인코딩할 수 없다. 느슨하게 타이핑된 코드는 TS가 덜 검사한다는 것을 의미하고, 약간 보수적인 코드의 경우 사용자들이 종종 타입 assertion을 사용하도록 강요했다.</p>
<pre><code class="language-ts">function yell(str) {
  assert(typeof str === &quot;string&quot;);
  return str.toUppercase();
  // Oops! We misspelled &#39;toUpperCase&#39;.
  // Would be great if TypeScript still caught this!
}</code></pre>
<p>대안은 언어가 코드를 분석할 수 있도록 다시 쓰는 것이었지만, 이것은 편리하지 않다.</p>
<pre><code class="language-ts">function yell(str) {
  if (typeof str !== &quot;string&quot;) {
    throw new TypeError(&quot;str should have been a string.&quot;);
  }
  // Error caught!
  return str.toUppercase();
}</code></pre>
<p>궁극적인 TS의 목표는 기존 JS 구조를 가장 덜 파괴적인 방식으로 타이핑 하는 것이다. 이러한 이유로 TS 3.7은 이러한 assertion 함수를 모델링 하는 &#39;assertion 시그니처&#39;라는 새로운 개념을 도입한다.</p>
<p>첫 번째 유형의 어설션 시그니처는 노드의 <code>assert</code> 함수가 작동하는 방식을 모델링한다. 검사 중인 조건이 포함된 스코프의 나머지 부분에 대해 참이어야 한다.</p>
<pre><code class="language-ts">function assert(condition: any, msg?: string): asserts condition {
  if (!condition) {
    throw new AssertionError(msg);
  }
}</code></pre>
<p><code>asserts condition</code>은 <code>assert</code>가 반환되면 <code>condition</code> 매개변수로 전달되는 몯느 것이 참이어야 한다고 말한다.(그렇지 않으면 오류가 발생한다.) 그것은 나머지 범위에 대해서는 그 조건이 참이어야 한다는 것을 의미한다. 예를 들어, 이 assertion 함수를 사용하는 것은 우리가 원래의 <code>yell</code> 예시를 잡는다는 것을 의미한다.</p>
<pre><code class="language-ts">function yell(str) {
  assert(typeof str === &quot;string&quot;);
  return str.toUppercase();
  //         ~~~~~~~~~~~
  // error: Property &#39;toUppercase&#39; does not exist on type &#39;string&#39;.
  //        Did you mean &#39;toUpperCase&#39;?
}
function assert(condition: any, msg?: string): asserts condition {
  if (!condition) {
    throw new AssertionError(msg);
  }
}</code></pre>
<p>다른 유형의 어설션 시그니처는 조건을 확인하지 않고, 대신 특정 변수나 속성이 다른 유형을 가지고 있음을 TS에 알려준다.</p>
<pre><code class="language-ts">function assertIsString(val: any): asserts val is string {
  if (typeof val !== &quot;string&quot;) {
    throw new AssertionError(&quot;Not a string!&quot;);
  }
}</code></pre>
<p>여기서 <code>asserts val is string</code>은 <code>assertIsString</code> 함수를 호출한 후 전달된 변수가 문자열임을 보장한다.</p>
<p>이러한 어서셜 시그니처는 쓰기 유형 시그니처와 매우 유사하다.</p>
<pre><code class="language-ts">function isString(val: any): val is string {
  return typeof val === &quot;string&quot;;
}
function yell(str: any) {
  if (isString(str)) {
    return str.toUppercase();
  }
  throw &quot;Oops!&quot;;
}</code></pre>
<p>그리고 타입 예측 서명과 마찬가지로, 이 어설션 서명은 믿을 수 없을 정도로 표현력이 뛰어나다. </p>
<p>다음과 같이 정교하게 함수를 작성할 수 있다.</p>
<pre><code class="language-ts">function assertIsDefined&lt;T&gt;(val: T): asserts val is NonNullable&lt;T&gt; {
  if (val === undefined || val === null) {
    throw new AssertionError(
      `Expected &#39;val&#39; to be defined, but received ${val}`
    );
  }
}</code></pre>
<p>어설션 서명에 대한 자세한 내용을 보려면 <a href="https://github.com/microsoft/TypeScript/pull/32695">이 PR</a>을 확인하자.</p>
<h2 id="never를-반환하는-함수에-대한-더-나은-지원">never를 반환하는 함수에 대한 더 나은 지원</h2>
<p>어설션 시그니처를 위한 작업의 일부로서, TS는 어디서 어떤 함수가 호출되는 지에 대한 더 많은 인코딩이 필요했다. 이것은 우리에게 <code>never</code>를 리턴하는 함수같은 또 다른 종류의 함수에 대한 지원을 확대할 수 있는 기회를 주었다.</p>
<p><code>never</code>를 반환하는 함수의 의도는 <code>never</code>를 반환하는 것이다. 예외가 발생했거나, 중지 오류 조건이 발생했거나, 프로그램이 종료되었음을 나타낸다. 예를들어 <a href="https://github.com/DefinitelyTyped/DefinitelyTyped/blob/5299d372a220584e75a031c13b3d555607af13f8/types/node/globals.d.ts#l874">process.exit(...) in @types/node</a>은 <code>never</code>를 반환하도록 인터페이스가 설계되어 있다.</p>
<p>함수가 잠재적으로 <code>undefined</code>이거나 모든 코드 경로에서 효과적으로 반환되지 않도록 하기 위해 TS는 함수의 끝에 <code>return</code> 또는 <code>throw</code>와 같은 구문신호가 필요했다. 그래서 사용자들은 아래 코드처럼 그들의 실패 함수를 <code>return</code>하는 스스로를 발견했다.</p>
<pre><code class="language-ts">function dispatch(x: string | number): SomeType {
  if (typeof x === &quot;string&quot;) {
    return doThingWithString(x);
  } else if (typeof x === &quot;number&quot;) {
    return doThingWithNumber(x);
  }
  return process.exit(1);
}</code></pre>
<p>이제 이러한 <code>never</code>를 반환하는 함수가 호출되면 TS는 제어 흐름 그래프에 영향을 미친다는 것을 인식한다.</p>
<pre><code class="language-ts">function dispatch(x: string | number): SomeType {
  if (typeof x === &quot;string&quot;) {
    return doThingWithString(x);
  } else if (typeof x === &quot;number&quot;) {
    return doThingWithNumber(x);
  }
  process.exit(1);
}</code></pre>
<p>어설션 함수와 마찬가지로 <a href="https://github.com/microsoft/TypeScript/pull/32695">동일한 PR에서 더 많은 것을 읽을 수 있다.</a></p>
<h2 id="재귀-타입-별칭">재귀 타입 별칭</h2>
<p>타입 별칭은 항상 재귀적으로 참조되는 방법에 제한이 있었다. 그 이유는 타입 별칭을 사용할 때는 별칭이 무엇이든 스스로 대체할 수 있어야 하기 때문이다. 어떤 경우에는 그럴 수 없기 때문에 컴파일러는 다음과 같은 특정 재귀 별칭을 거부한다.</p>
<pre><code class="language-ts">type Foo = Foo;</code></pre>
<p>이것은 합리적인 제한인데, 왜냐하면 <code>Foo</code>의 모든 사용은 <code>Foo</code>로 대체되어야 하고, <code>Foo</code>는 <code>Foo</code>로 대체되어야 하기 때문이다. 결국에, <code>Foo</code>를 대신해서 말이되는 타입은 없다.</p>
<p>이는 다른 언어가 <a href="https://wikipedia.org/w/index.php?title=Recursive_data_type&amp;oldid=913091335#in_type_synonyms">타입 별칭을 다루는 방식과 상당히 일치</a>하지만 사용자가 이 기능을 활용하는 방법에 약간 놀라운 시나리오가 발생한다. 예를 들어, TS 3.6 이전 버전에서는 다음과 같은 오류가 발생한다.</p>
<pre><code class="language-ts">type ValueOrArray&lt;T&gt; = T | Array&lt;ValueOrArray&lt;T&gt;&gt;;
//   ~~~~~~~~~~~~
// error: Type alias &#39;ValueOrArray&#39; circularly references itself.</code></pre>
<p>이것은 기술적으로 사용자가 인터페이스를 도입함으로써 항상 효과적으로 동일한 코드를 작성할 수 있는 어떤 사용에도 아무런 문제가 없기 때문에 이상하다.</p>
<pre><code class="language-ts">type ValueOrArray&lt;T&gt; = T | ArrayOfValueOrArray&lt;T&gt;;
interface ArrayOfValueOrArray&lt;T&gt; extends Array&lt;ValueOrArray&lt;T&gt;&gt; {}</code></pre>
<p>인터페이스는 간접적인 수준을 도입하고, 전체 구조를 열심히 구축할 필요가 없기 때문에 TS는 이 구조로 작동하는 데 문제가 없다.</p>
<p>그러나 인터페이스를 도입하는 해결책은 사용자에게 직관적이지 않았다. 기본적으로 <code>Array</code>를 직접 사용한 원래 버전의 <code>ValueOrArray</code>에는 아무런 문제가 없었다. 만약 컴파일러가 조금 더 게으르고 필요할 때만 <code>Array</code>에 대한 타입 인자를 계산한다면, TS는 이것들을 정확하게 표현할 수 있다.</p>
<p>그것이 바로 TS 3.7이 도입한 것이다. 타입 별칭의 최상위 수준에서 TS는 이러한 패턴을 허용하기 위해 타입 인수의 확인을 연기한다.(늦게 확인한다라는 뜻)</p>
<p>이것은 JSON을 나타내려고 했던 다음과 같은 코드를 의미한다.</p>
<pre><code class="language-ts">type Json = string | number | boolean | null | JsonObject | JsonArray;
interface JsonObject {
  [property: string]: Json;
}
interface JsonArray extends Array&lt;Json&gt; {}</code></pre>
<p>마지막으로 helper 인터페이스 없이 다시 작성할 수 있다.</p>
<pre><code class="language-ts">type Json =
  | string
  | number
  | boolean
  | null
  | { [property: string]: Json } // 기존에는 JSONObject라는 것을 사용
  | Json[];</code></pre>
<p>이 새로운 완화를 통해 튜플에서도 유형 별칭을 재귀적으로 참조할 수 있다. 오류가 발생하던 다음 코드는 이제 유효한 TS 코드이다.</p>
<pre><code class="language-ts">type VirtualNode = string | [string, { [key: string]: any }, ...VirtualNode[]];
const myNode: VirtualNode = [
  &quot;div&quot;,
  { id: &quot;parent&quot; },
  [&quot;div&quot;, { id: &quot;first-child&quot; }, &quot;I&#39;m the first child&quot;],
  [&quot;div&quot;, { id: &quot;second-child&quot; }, &quot;I&#39;m the second child&quot;],
];</code></pre>
<p>더 자세한 정보는 <a href="https://github.com/microsoft/TypeScript/pull/33050">이 PR</a>을 참조</p>
<h2 id="--declaration과---allowjs">--declaration과 --allowJs</h2>
<p>TS의 <a href="https://www.typescriptlang.org/tsconfig#declaration">선언</a> 플래그를 사용하면 TS 소스 파일에서 <code>.d.ts</code> 파일을 생성할 수 있다. 이러한 <code>.d.ts</code>파일은 몇 가지 중요한 이유로 중요하다.</p>
<p>무엇보다도, 그것들은 TS가 원본 소스 코드를 다시 확인하지 않고 다른 프로젝트의 타입을 확인할 수 있게 해주기 때문에 중요하다. 그것들을 또한 TS를 염두에 두고 구축되지 않은 기존의 JS 라이브러리와 상호 운용할 수 있도록 하기 때문에 중요하다. 마지막으로, TS와 JS 사용자 모두가 더 나은 자동 완성과 같은 것들을 얻기 위해 TS에 의해 구동되는 편집기를 사용할 때 이러한 파일들로부터 이익을 얻을 수 있다.</p>
<p>안타깝게도 선언은 TS와 JS 입력 파일을 혼합할 수 있는 <a href="https://www.typescriptlang.org/tsconfig#allowJs">allowJs</a>와 함께 작동하지 않았다. 이것은 사용자가 JSDoc 주석을 달았더라도 코드베이스를 마이그레이션 할 때 선언 플래그를 사용할 수 없다는 것을 의미해서 답답한 한계였다. TS 3.7은 이를 변경하고 두 옵션을 함께 사용할 수 있도록 한다.</p>
<p>TS 3.7을 사용하면 JSDoc 주석이 달린 JS 라이브러리를 작성하고 TS 사용자를 지원할 수 있다.</p>
<p>이것이 작동하는 방식은 allowJs를 사용할 때 TS는 일반적으로 JS 패턴을 이해하기 위한 최선의 분기를 가지고 있다. 선언 방출이 켜져 있을 때, TS는 출력 <code>.d.ts</code> 파일에서 JSDoc 주석 및 CommonJs 내보내기를 유효한 형식 선언 등으로 변환하는 가장 좋은 방법을 찾는다.</p>
<p>예를 들어, 아래의 코드 스니펫은</p>
<pre><code class="language-ts">const assert = require(&quot;assert&quot;);
module.exports.blurImage = blurImage;
/**
 * Produces a blurred image from an input buffer.
 *
 * @param input {Uint8Array}
 * @param width {number}
 * @param height {number}
 */
function blurImage(input, width, height) {
  const numPixels = width * height * 4;
  assert(input.length === numPixels);
  const result = new Uint8Array(numPixels);
  // TODO
  return result;
}</code></pre>
<p>다음과 같은 <code>.d.ts</code> 파일을 생성한다.</p>
<pre><code class="language-ts">/**
 * Produces a blurred image from an input buffer.
 *
 * @param input {Uint8Array}
 * @param width {number}
 * @param height {number}
 */
export function blurImage(
  input: Uint8Array,
  width: number,
  height: number
): Uint8Array;</code></pre>
<p>이는 <code>@param</code> 태그를 사용하는 기본 기능을 뛰어넘을 수 있다. 여기서 다음과 같은 예를 들수 있다.</p>
<pre><code class="language-ts">/**
 * @callback Job
 * @returns {void}
 */
/** Queues work */
export class Worker {
  constructor(maxDepth = 10) {
    this.started = false;
    this.depthLimit = maxDepth;
    /**
     * NOTE: queued jobs may add more items to queue
     * @type {Job[]}
     */
    this.queue = [];
  }
  /**
   * Adds a work item to the queue
   * @param {Job} work
   */
  push(work) {
    if (this.queue.length + 1 &gt; this.depthLimit) throw new Error(&quot;Queue full!&quot;);
    this.queue.push(work);
  }
  /**
   * Starts the queue if it has not yet started
   */
  start() {
    if (this.started) return false;
    this.started = true;
    while (this.queue.length) {
      /** @type {Job} */ (this.queue.shift())();
    }
    return true;
  }
}</code></pre>
<p>이는 다음과 같은 <code>d.ts</code> 파일로 변환된다.</p>
<pre><code class="language-ts">/**
 * @callback Job
 * @returns {void}
 */
/** Queues work */
export class Worker {
  constructor(maxDepth?: number);
  started: boolean;
  depthLimit: number;
  /**
   * NOTE: queued jobs may add more items to queue
   * @type {Job[]}
   */
  queue: Job[];
  /**
   * Adds a work item to the queue
   * @param {Job} work
   */
  push(work: Job): void;
  /**
   * Starts the queue if it has not yet started
   */
  start(): boolean;
}
export type Job = () =&gt; void;</code></pre>
<p>이러한 플래그를 함께 사용할 때 TS는 .<code>js</code> 파일을 반드시 다운레벨 할 필요는 없다. TS에서 <code>.d.ts</code> 파일을 생성하려는 경우 <a href="https://www.typescriptlang.org/tsconfig#emitDeclarationOnly">emitDeclarationOnly</a> 컴파일러 옵션을 사용할 수 있다.</p>
<p>자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/32372">원본 PR</a>에서 확인</p>
<h2 id="usedefineforclassfields-플래그와-declare-속성-수정자">useDefineForClassFields 플래그와 declare 속성 수정자</h2>
<p>TS가 공개 필드 클래스를 구현했을 때, 능력을 최대한 발휘하여 아래 코드를 가정했다.</p>
<pre><code class="language-ts">class C {
  foo = 100;
  bar: string;
}</code></pre>
<p>이는 생성자 본체 내에서 유사한 할당과 같다.</p>
<pre><code class="language-ts">class C {
  constructor() {
    this.foo = 100;
  }
}</code></pre>
<p>불행히도, 이것이 제안 초기에 나아간 방향처럼 보이지만, public 클래스 필드가 다르게 표준화될 가능성이 매우 크다. 대신 원래 코드 샘플은 다음과 같은 것에 더 가까운 &#39;설탕 제거&#39;가 필요할 수 있다.(더 문법적으로 어려워진다는 의미인것 같음)</p>
<pre><code class="language-ts">class C {
  constructor() {
    Object.defineProperty(this, &quot;foo&quot;, {
      enumerable: true,
      configurable: true,
      writable: true,
      value: 100,
    });
    Object.defineProperty(this, &quot;bar&quot;, {
      enumerable: true,
      configurable: true,
      writable: true,
      value: void 0,
    });
  }
}</code></pre>
<p>TS 3.7은 기본적으로 기존 방출을 변경하지 않지만, 사용자가 향후 발생할 수 있는 손상을 완화할 수 있도록 점진적으로 변경 사항을 롤 아웃하고 있다. <a href="https://www.typescriptlang.org/tsconfig#useDefineForClassFields">useDefineForClassFields</a>라는 새 플래그를 제공하여 이 방출 모드를 몇 가지 새로운 확인 로직으로 활성화했다.</p>
<p>가장 큰 두가지 변경 사항은 다음과 같다.</p>
<ul>
<li>선언은 <code>Object.defineProperty</code>로 초기화된다.</li>
<li>선언은 이니셜라이저가 없는 경우에도 항상 <code>undefined</code>로 초기화된다.</li>
</ul>
<p>이는 상속을 사용하는 기존 코드에 상당한 영향을 미칠 수 있다. 우선 기본 클래스의 설정된 <code>set</code> 접근자는 트리거되지 않고 완전히 덮어쓰게 된다.</p>
<pre><code class="language-ts">class Base {
  set data(value: string) {
    console.log(&quot;data changed to &quot; + value);
  }
}
class Derived extends Base {
  // No longer triggers a &#39;console.log&#39;
  // when using &#39;useDefineForClassFields&#39;.
  data = 10;
}</code></pre>
<p>둘째로, 기본 클래스의 속성을 특성화하기 위해 클래스 필드를 사용하는 것도 효과가 없다.</p>
<pre><code class="language-ts">interface Animal {
  animalStuff: any;
}
interface Dog extends Animal {
  dogStuff: any;
}
class AnimalHouse {
  resident: Animal;
  constructor(animal: Animal) {
    this.resident = animal;
  }
}
class DogHouse extends AnimalHouse {
  // Initializes &#39;resident&#39; to &#39;undefined&#39;
  // after the call to &#39;super()&#39; when
  // using &#39;useDefineForClassFields&#39;!
  resident: Dog;
  constructor(dog: Dog) {
    super(dog);
  }
}</code></pre>
<p>이 두 가지의 결론은 속성을 접근자와 혼합하면 문제가 발생하고 이니셜라이저 없이 속성을 다시 선언한다는 것이다.</p>
<p>접근자 주변의 문제를 감지하기 위해 TS 3.7은 이제 <code>.d.ts</code> 파일의 <code>get/set</code> 접근자를 방출하여 TS에서 오버라이드된 접근자를 확인할 수 있다.</p>
<p>클래스 필드 변경의 영향을 받는 코드는 필드 이니셜라이저를 생성자 본문의 할당으로 변환하여 이 문제를 해결할 수 있다.</p>
<pre><code class="language-ts">class Base {
  set data(value: string) {
    console.log(&quot;data changed to &quot; + value);
  }
}
class Derived extends Base {
  constructor() {
    this.data = 10;
  }
}</code></pre>
<p>두 번째 문제를 완화하기 위해 명시적 이니셜라이저를 추가하거나 속성에 방출이 없어야 함을 나타내는 선언 수정자를 추가할 수 있다.</p>
<pre><code class="language-ts">interface Animal {
  animalStuff: any;
}
interface Dog extends Animal {
  dogStuff: any;
}
class AnimalHouse {
  resident: Animal;
  constructor(animal: Animal) {
    this.resident = animal;
  }
}
class DogHouse extends AnimalHouse {
  declare resident: Dog;
  //  ^^^^^^^
  // &#39;resident&#39; now has a &#39;declare&#39; modifier,
  // and won&#39;t produce any output code.
  constructor(dog: Dog) {
    super(dog);
  }
}</code></pre>
<p><code>Object.defineProperty</code>가 ES3에 없기 때문에 현재 ES5 이상을 대상으로 한 경우에만 <code>useDefineForClassFields</code>를 사용할 수 있다. 유사한 문제 검사를 수행하려면, ES5를 대상으로 하고 전체 빌드를 피하기 위해 <a href="https://www.typescriptlang.org/tsconfig#noEmit">noEmit</a>을 사용하는 별도의 프로젝트를 생성할 수 있다.</p>
<p>자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/33509">이러한 변경에 대한 원본 PR</a>을 참조</p>
<h2 id="프로젝트-참조를-사용한-빌드-프리-편집">프로젝트 참조를 사용한 빌드 프리 편집</h2>
<p>TS의 프로젝트 참조는 우리에게 더 빠른 컴파일을 제공하기 위해 코드베이스를 분해하는 쉬운 방법을 제공한다. 안타깝게도 종속성이 구축되지 않은 프로젝트를 편집하면 편집 환경이 제대로 작동하지 않는다.</p>
<p>TS 3.7에서 종속성이 있는 프로젝트를 열 때 TS는 자동으로 <code>.ts/.tsx</code> 파일을 대신 사용한다. 이는 프로젝트 참조를 사용하는 프로젝트가 의미론적 작업이 최신 상태로 &#39;그냥 작동&#39;하는 편집 환경을 개선할 수 있음을 의미한다. 컴파일러 옵션 <a href="https://www.typescriptlang.org/tsconfig#disableSourceOfProjectReferenceRedirect">disableSourceOfProjectReferenceRedirect</a>를 사용하여 이 동작을 비활성화 할 수 있다. 이는 편집 성능에 영향을 줄 수 있는 매우 큰 프로젝트에서 작업할 때 적합하다.</p>
<p>자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/32028">이 PR 참조</a></p>
<h2 id="호출되지-않은-함수-검사">호출되지 않은 함수 검사</h2>
<p>일반적이고 위험한 오류는 함수를 호출하는 것을 잊는 것인데, 특히 함수가 인수가 0개이거나 함수가 아닌 속성일 수 있음을 암시하는 이름이 지정된 경우이다.</p>
<pre><code class="language-ts">interface User {
  isAdministrator(): boolean;
  notify(): void;
  doNotDisturb?(): boolean;
}
// later...
// Broken code, do not use!
function doAdminThing(user: User) {
  // oops!
  if (user.isAdministrator) {
    sudo();
    editTheConfiguration();
  } else {
    throw new AccessDeniedError(&quot;User is not an admin&quot;);
  }
}</code></pre>
<p>여기서 <code>isAdministrator</code> 함수를 호출하는 것을 잊었다. 코드는 관리자가 아닌 사용자가 구성을 편집할 수 있도록 잘못 허용한다.</p>
<p>TS 3.7에서는 다음과 같은 오류가 발생할 수 있다.</p>
<pre><code class="language-ts">function doAdminThing(user: User) {
    if (user.isAdministrator) {
    //  ~~~~~~~~~~~~~~~~~~~~
    // error! This condition will always return true since the function is always defined.
    //        Did you mean to call it instead?</code></pre>
<p>이 검사는 획기적인 변화이지만, 그러한 이유로 검사는 매우 보수적이다. 이 오류는 조건이 있는 경우에만 발생하며, 선택적 속성, strictNullChecks가 해제된 경우 또는 함수가 <code>if</code>문의 몸체 안에서 나중에 호출되는 경우 발생하지 않는다.</p>
<pre><code class="language-ts">interface User {
  isAdministrator(): boolean;
  notify(): void;
  doNotDisturb?(): boolean;
}
function issueNotification(user: User) {
  if (user.doNotDisturb) {
    // OK, property is optional
  }
  if (user.notify) {
    // OK, called the function
    user.notify();
  }
}</code></pre>
<p>함수를 호출하지 않고 테스트하려는 경우 <code>undefined/null</code>을 포함하도록 함수의 정의를 수정하거나, <code>!!</code>를 사용해 <code>if (!!user.isAdministrator)</code>와 같은 항목을 작성하여 강제성이 의도적임을 나타낼 수 있다.</p>
<h2 id="타입스크립트-파일에서--ts-nocheck">타입스크립트 파일에서 // @ts-nocheck</h2>
<p>TS 3.7을 사용하면 TS 파일의 맨 위에 <code>// @ts-nocheck</code> 주석을 추가하여 의미론적 검사를 비활성화 할 수 있다. 역사적으로 이 주석은 <a href="https://www.typescriptlang.org/tsconfig#checkJs">checkJs</a>가 존재하는 JS 소스 파일에서만 사용되었지만 우리는 모든 사용자가 더 쉽게 마이그레이션할 수 있도록 TS 파일로 지원을 확장했다.</p>
<h2 id="세미콜론-형식-지정-옵션">세미콜론 형식 지정 옵션</h2>
<p>JS의 ASI 규칙 때문에 후속 세미콜론이 선택적인 위치에서 세미콜론 삽입 및 제거를 지원한다. 이 설정은 VSCode Insider에서 사용할 수 있고, Tools Option 메뉴의 Visual Studio 16.4 Preview 2에서 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/2b08805b-f59e-44b9-8018-e57935d49b8a/image.png" alt=""></p>
<p>&#39;삽입&#39; 또는 &#39;제거&#39; 값을 선택하는 것은 자동 가져오기, 추출된 유형 및 TS 서비스에서 제공하는 기타 생성된 코드는 TS 서비스에 의해 제공된다. 설정을 기본값인 &#39;무시&#39;로 유지하면 생성된 코드가 현재 파일에서 탐지된 세미콜론 환경설정과 일치한다.</p>
<h2 id="37-변경-사항">3.7 변경 사항</h2>
<h3 id="dom-변경-사항">DOM 변경 사항</h3>
<p><a href="https://github.com/microsoft/TypeScript/pull/33627">lib.dom.dts의 유형이 업데이트 되었다.</a> 이러한 변경사항은 대부분 무효성(nullability)과 관련된 정확성 변경사항이지만, 영향은 궁극적으로 코드베이스에 따라 달라진다.</p>
<h3 id="클래스-필드-완화">클래스 필드 완화</h3>
<p>위에서 언급한 바와 같이 TS 3.7은 <code>.d.ts</code> 파일의 <code>get/set</code> 접근자를 방출하여 3.5 이전 버전과 같은 이전 버전의 TS에서 소비자에게 심각한 변화를 일으킬 수 있다. TS 3.6 사용자는 이 기능에 대해 미래에 대비한 버전이므로 영향을 받지 않는다.</p>
<p><code>useDefineForClassFields</code> 플래그를 선택하면 다음과 같은 파손이 발생할 수 있다.</p>
<ul>
<li>속성 선언을 사용하여 파생 클래스의 접근자 재지정</li>
<li>이니셜라이저를 사용하지 않고 속성 선언 다시 실행</li>
</ul>
<h3 id="함수-참값-검사">함수 참값 검사</h3>
<p>위에서 언급했듯이, TS 는 if문 조건 내에서 함수가 호출되지 않은 것처럼 보일 때 오류가 발생한다. 다음 조건이 적용되지 않는 한 함수 타입이 검사될 때, 오류가 발생한다.</p>
<ul>
<li>선택된 값은 선택적 속성에 가져온다.</li>
<li>strictNullChecks가 비활성화</li>
<li>함수는 나중에 <code>if</code>문 안에서 호출된다.</li>
</ul>
<h3 id="로컬과-임포트된-타입-선언이-충돌-한다">로컬과 임포트된 타입 선언이 충돌 한다.</h3>
<p>버그로 인해 이전에 TS에서 허용된 구문은 다음과 같다.</p>
<pre><code class="language-ts">// ./someOtherModule.ts
interface SomeType {
  y: string;
}
// ./myModule.ts
import { SomeType } from &quot;./someOtherModule&quot;;
export interface SomeType {
  x: number;
}
function fn(arg: SomeType) {
  console.log(arg.x); // Error! &#39;x&#39; doesn&#39;t exist on &#39;SomeType&#39;
}</code></pre>
<p>여기서 <code>SomeType</code>은 <code>import</code> 선언과 로컬 <code>interface</code> 선언에서 모두 발생하는 것으로 보인다. 놀랍게도 모듈 내부에서 <code>SomeType</code>은 가져온 정의만을 가리키며, 로컬 선언 <code>SomeType</code>은 다른 파일에서 가져올 때만 사용할 수 있다.</p>
<p>TS 3.7에서는 중복 식별자 오류가 수정되어 올바르게 식별된다. 올바른 해결책은 저자의 원래 의도에 따라 다르며, 사례별로 다루어야 한다. 일반적으로 이름 지정 충돌은 의도적이지 않고, 가져온 유형의 이름을 변경하는 것이 가장 좋다. 가져온 유형을 보강(augment)하는 것이 목적이었다면, 대신 적절한 모듈 확대(augmentation)을 작성해야 한다.</p>
<h2 id="37-api-변경">3.7 API 변경</h2>
<p>위에서 설명한 재귀 타입 별칭 패턴을 사용하도록 설정하기 위해 <code>typeArguments</code> 속성이 <code>TypeReference</code> 인터페이스에서 제거되었다. 사용자는 대신 <code>TypeChecker</code> 인스턴스에서 <code>getTypeArguments</code> 함수를 사용해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 3.6 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-3.6-%EB%B2%88%EC%97%AD</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-3.6-%EB%B2%88%EC%97%AD</guid>
            <pubDate>Mon, 05 Dec 2022 16:48:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html</a></p>
</blockquote>
<h2 id="엄격한-제너레이터">엄격한 제너레이터</h2>
<p>TS 3.6은 이터레이터와 제너레이터 함수에 대해 더 엄격하게 검사를 한다. 이전 버전에서 제너레이터의 사용자들은 값이 제너레이터의 <code>yield</code>로 부터 나왔는지, <code>return</code>에 의해 반환되었는지를 구별할 방법이 없었다.</p>
<pre><code class="language-ts">function* foo() {
  if (Math.random() &lt; 0.5) yield 100;
  return &quot;Finished!&quot;;
}
let iter = foo();
let curr = iter.next();
if (curr.done) {
  // TypeScript 3.5 and prior thought this was a &#39;string | number&#39;.
  // It should know it&#39;s &#39;string&#39; since &#39;done&#39; was &#39;true&#39;!
  curr.value;
}</code></pre>
<p>게다가 제너레이터의 <code>yield</code>의 타입이 항상 <code>any</code>라고 가정했다.</p>
<pre><code class="language-ts">function* bar() {
  let x: { hello(): void } = yield;
  x.hello();
}
let iter = bar();
iter.next();
iter.next(123); // oops! runtime error!</code></pre>
<p>TS 3.6에서 Checker는 이제 첫 번째 예에서 <code>curr.value</code>에 대한 올바른 타입이 문자열이어야 한다는 것을 알고 있고, 마지막 예에서 <code>next()</code>로 호출할 때 올바르게 오류를 발생시킨다. 이것은 몇 가지 새로운 타입 매개변수를 포함하도록 <code>Iterator</code> 및 <code>IteratorResult</code> 타입 선언의 일부 변경과 <code>Generator</code> 타입이라고 불리는 제너레이터를 나타내기 위해 타입스크립트가 사용하는 새로운 형식 덕분에 가능해졌다.</p>
<pre><code class="language-ts">interface Iterator&lt;T, TReturn = any, TNext = undefined&gt; {
  // Takes either 0 or 1 arguments - doesn&#39;t accept &#39;undefined&#39;
  next(...args: [] | [TNext]): IteratorResult&lt;T, TReturn&gt;;
  return?(value?: TReturn): IteratorResult&lt;T, TReturn&gt;;
  throw?(e?: any): IteratorResult&lt;T, TReturn&gt;;
}</code></pre>
<p>이 작업을 기반으로 한 새로운 제너레이터 타입은 <code>return</code> 및 <code>throw</code> 메서드가 항상 존재하는 <code>Iterator</code>이며, 또한 반복이 가능하다.</p>
<pre><code class="language-ts">interface Generator&lt;T = unknown, TReturn = any, TNext = unknown&gt;
  extends Iterator&lt;T, TReturn, TNext&gt; {
  next(...args: [] | [TNext]): IteratorResult&lt;T, TReturn&gt;;
  return(value: TReturn): IteratorResult&lt;T, TReturn&gt;;
  throw(e: any): IteratorResult&lt;T, TReturn&gt;;
  [Symbol.iterator](): Generator&lt;T, TReturn, TNext&gt;;
}</code></pre>
<p><code>return</code>된 값과 <code>yield</code>된 값을 구별할 수 있도록 TS 3.6은 <code>IteratorResult</code> 유형을 식별된 유니언 타입으로 변환한다.</p>
<pre><code class="language-ts">type IteratorResult&lt;T, TReturn = any&gt; =
  | IteratorYieldResult&lt;T&gt;
  | IteratorReturnResult&lt;TReturn&gt;;
interface IteratorYieldResult&lt;TYield&gt; {
  done?: false;
  value: TYield;
}
interface IteratorReturnResult&lt;TReturn&gt; {
  done: true;
  value: TReturn;
}</code></pre>
<p>간단히 말해, 이것이 의미하는 바는 이터레이터를 직접 다룰 때 값을 적절하게 좁힐 수 있다는 것이다.</p>
<p>제너레이터의 <code>next()</code> 호출로 전달되는 타입을 올바르게 표현하기 위해 TS 3.6은 또한 제너레이터 함수의 본문 내에서 특정한 <code>yield</code> 사용을 추론한다.</p>
<pre><code class="language-ts">function* foo() {
  let x: string = yield;
  console.log(x.toUpperCase());
}
let x = foo();
x.next(); // first call to &#39;next&#39; is always ignored
x.next(42); // error! &#39;number&#39; is not assignable to &#39;string&#39;</code></pre>
<p>명시적으로 사용하기를 원하는 경우 명시적으로 반환 타입을 사용하여 <code>yield</code> 표현식에서 return, yield 및 평가할 수 있는 값 유형을 적용할 수 있다. 아래에서 <code>next()</code>는 <code>boolean</code>만 사용하여 호출할 수 있고, <code>done</code> 값에 따라 값은 <code>string</code> 또는 <code>number</code>이다.</p>
<pre><code class="language-ts">/**
 * - yields numbers
 * - returns strings
 * - can be passed in booleans
 */
function* counter(): Generator&lt;number, string, boolean&gt; {
  let i = 0;
  while (true) {
    if (yield i++) {
      break;
    }
  }
  return &quot;done!&quot;;
}
var iter = counter();
var curr = iter.next();
while (!curr.done) {
  console.log(curr.value);
  curr = iter.next(curr.value === 5);
}
console.log(curr.value.toUpperCase());
// prints:
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!</code></pre>
<p>자세한 사항을 확인하고 싶다면 <a href="https://github.com/Microsoft/TypeScript/issues/2983">이 PR 참조</a></p>
<h2 id="더-정확한-배열-스프레드">더 정확한 배열 스프레드</h2>
<p>ES2015 이전 타겟에서 <code>for/of</code> 반복문 및 배열 스프레드와 같은 문법에 대한 가장 신뢰있는 방출은 다소 무거울 수 있다. 이러한 이유로 TS는 기본적으로 배열 타입만 지원하는 더 단순한 방출을 사용하며 <code>downlevelIteration</code> 플래그를 사용하는 다른 유형에 대한 반복을 지원한다. </p>
<blockquote>
<p>다운 레벨링이란?
자바스크립트의 이전 버전으로 전환하는 것을 가리키는 TS 용어이다. 이 플래그는 이전 JS 런타임에서 새로운 개념을 통해 현재 JS가 반복되는 방식을 보다 정확하게 구현할 수 있도록 지원하기 위한 것이다.
ECMAScript 6는 <code>for/of</code>나 <code>for (el of arr)</code>, 배열 스프레드, 인수 스프레드, <code>Symbol.iterator</code>와 같은 새로운 반복 기본 요소를 추가했다. <code>downlevelIteration</code>을 사용하면 <code>Symbol.iterator</code> 구현이 존재하는 경우 ES5 환경에서 이러한 반복 기본 요소를 보다 정확하게 사용할 수 있다.
자세한 사항은 아래 링크참조: <a href="https://www.typescriptlang.org/tsconfig#downlevelIteration">https://www.typescriptlang.org/tsconfig#downlevelIteration</a></p>
</blockquote>
<p><code>downlevelIteration</code>이 없는 더 느슨한 기본값 반복은 상당히 잘 동작하지만 배열 스프레드의 변환이 관찰 가능한 차이를 갖는 몇 가지 일반적인 경우가 있었다. 예를들어 아래와 같이 스프레드 배열을 포함하는 배열이다.</p>
<pre><code class="language-ts">[...Array(5)];</code></pre>
<p>이는 다음 배열 리터럴로 다시 작성할 수 있다.</p>
<pre><code class="language-ts">[undefined, undefined, undefined, undefined, undefined];</code></pre>
<p>그러나 TS는 대신 원본 코드를 다음과 같이 변환한다.</p>
<pre><code class="language-ts">Array(5).slice();</code></pre>
<p>약간 다른데, <code>Array(5)</code>는 길이가 5이지만, 위는 정의된 프로퍼티가 없는 배열을 생성한다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/15ed2d54-2dbc-45bf-8885-3a15f473ef79/image.png" alt=""></p>
<p>TS 3.6은 새로운 <code>__spreadArrays</code> 헬퍼를 도입하여 <code>downlevelIteration</code>이 아닌 이전 대상에서 ECMAScript 2015에서 발생하는 일을 정확하게 모델링한다. <code>__spreadArrays</code>는 <a href="https://github.com/Microsoft/tslib/">tslib</a>에서도 사용할 수 있다.</p>
<p>자세한 사항을 <a href="https://github.com/microsoft/TypeScript/pull/31166">이 PR을 참조</a></p>
<h2 id="프로미스를-둘러싼-ux-개선">프로미스를 둘러싼 UX 개선</h2>
<p>TS 3.6은 <code>Promise</code>가 잘못 처리될 때 몇 가지 개선 사항을 소개한다.</p>
<p>예를들어<code>Promise</code>의 내용을 다른 함수로 전달하기 전에 <code>.then()</code> 하거나 <code>await</code>하는 것을 까먹는 경우가 매우 일반적이다. TS의 오류 메시지는 이제 전문화되어 사용자에게 <code>await</code> 키워드를 고려해야 할 것임을 알려준다.</p>
<pre><code class="language-ts">interface User {
  name: string;
  age: number;
  location: string;
}
declare function getUserData(): Promise&lt;User&gt;;
declare function displayUser(user: User): void;
async function f() {
  displayUser(getUserData());
  //              ~~~~~~~~~~~~~
  // Argument of type &#39;Promise&lt;User&gt;&#39; is not assignable to parameter of type &#39;User&#39;.
  //   ...
  // Did you forget to use &#39;await&#39;?
}</code></pre>
<p>또한 <code>Promise</code>를 <code>await</code> 하거나 <code>.then()</code> 전에 메서드에 액세스 하려고 시도하는 것이 일반적이다. 아래가 그러한 예이다.</p>
<pre><code class="language-ts">async function getCuteAnimals() {
  fetch(&quot;https://reddit.com/r/aww.json&quot;).json();
  //   ~~~~
  // Property &#39;json&#39; does not exist on type &#39;Promise&lt;Response&gt;&#39;.
  //
  // Did you forget to use &#39;await&#39;?
}</code></pre>
<p>자세한 사항은 <a href="https://github.com/microsoft/TypeScript/issues/30646">이 이슈 참고</a></p>
<h2 id="식별자에-대한-향상된-유니코드-지원">식별자에 대한 향상된 유니코드 지원</h2>
<p>TS 3.6은 ES2015 이상의 대상에 방출할 때, 식별자의 유니코드 문자를 더 잘 지원한다.</p>
<pre><code class="language-ts">const 𝓱𝓮𝓵𝓵𝓸 = &quot;world&quot;; // previously disallowed, now allowed in &#39;--target es2015&#39;</code></pre>
<h2 id="systemjs에서-importmeta-지원">SystemJS에서 import.meta 지원</h2>
<p>TS는 <code>module</code>의 타겟이 <code>system</code>으로 설정된 경우 <code>import.meta</code>를 <code>context.meta</code>로 변환하는 기능을 지원한다.</p>
<pre><code class="language-ts">// This module:
console.log(import.meta.url);
// gets turned into the following:
System.register([], function (exports, context) {
  return {
    setters: [],
    execute: function () {
      console.log(context.meta.url);
    },
  };
});</code></pre>
<h2 id="주변-컨텍스트에서-get-set-접근자-허용">주변 컨텍스트에서 get, set 접근자 허용</h2>
<p>이전 버전의 TS에서 언어는 주변 컨텍스트(<code>declare</code> -d 클래스 또는 일반적으로 <code>.d.ts</code> 파일)에서 <code>get</code> 및 <code>set</code>의 접근자를 하용하지 않았다. 그러나 <a href="https://github.com/tc39/proposal-class-fields/issues/248">ECMAScript의 클래스 필드 제안이 기존 버전의 TS와 다른 동작을 할 수 있기</a> 때문에 하위 클래스에서 적절할 오류를 제공하기 위해 서로 다른 동작을 전달하는 방법이 필요하다는 것을 알았다.</p>
<p>결과적으로 사용자는 TS 3.6에서 주변 컨텍스트에 게터와 세터를 작성할 수 있다.</p>
<pre><code class="language-ts">declare class Foo {
  // Allowed in 3.6+.
  get x(): number;
  set x(val: number);
}</code></pre>
<p>TS 3.7에서 컴파일러 자체는 이 기능을 이용하여 생성된 <code>.d.ts</code> 파일도 <code>get/set</code> 접근자를 내보낸다.</p>
<h2 id="주변-클래스와-함수가-병합될-수-있음">주변 클래스와 함수가 병합될 수 있음</h2>
<p>이전 버전의 TS에서는 어떤 상황에서도 클래스와 함수를 병합하는 것이 오류였다. 이제 주변 클래스와 함수(<code>declare</code> 수식자가 있는 클래스/함수 또는 <code>.d.ts</code> 파일)가 병합될 수 있다. 이것은 이제 아래와 같이 쓸 수 있다는 것을 의미한다.</p>
<pre><code class="language-ts">// 이렇게 사용하는 대신에
export interface Point2D {
  x: number;
  y: number;
}
export declare var Point2D: {
  (x: number, y: number): Point2D;
  new (x: number, y: number): Point2D;
};

// 이렇게 사용할 수 있다
export declare function Point2D(x: number, y: number): Point2D;
export declare class Point2D {
  x: number;
  y: number;
  constructor(x: number, y: number);
}</code></pre>
<p>이것의 한 가지 장점은 호출 가능한 생성자 패턴을 쉽게 표현할 수 있는 동시에 네임스페이스가 네임스페이스와 병합될 수 있다는 것이다.(<code>var</code> 선언은 네임스페이스와 병합 X)</p>
<p>TS 3.7에서 컴파일러는 <code>.js</code> 파일에서 생성된 <code>.d.ts</code> 파일이 클래스 유사 함수의 호출 가능성과 생성 가능성을 모두 적절하게 캡처할 수 있도록 이 기능을 활용한다.</p>
<p>자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/32584">이 PR 참고</a></p>
<h2 id="--build와---incremental를-지원하는-api들">--build와 --incremental를 지원하는 API들</h2>
<p>TS 3.0은 <code>--build</code> 플래그를 사용하여 다른 것을 참조하고 점진적으로 빌드하는 지원을 도입했다. 또한 TS 3.4는 특정 파일을 재구축하기 위해 이전 컴파일에 대한 정보를 저장하기 위한 증분 플래그를 도입했다. 이러한 플래그는 프로젝트를 보다 유연하게 구성하고 구축 속도를 높이는 데 매우 유용했다. 불행히도 이러한 도구를 사용하는 것은 Gulp 및 Webpack과 같은 제 3자 빌드 도구에서는 작동하지 않았다. TS 3.6은 이제 프로젝트 참조 및 증분 프로그램 빌드에서 작동하는 두 개의 API 집합을 제공한다.</p>
<p>증분 빌드를 만들기 위해 사용자는 <code>createIncrementalProgram</code>와 <code>createIncrementalCompilerHost</code> API를 활용할 수 있다. 사용자는 또한 노출된 <code>readBuilderProgram</code> 함수를 사용하여 이 API에 의해 생성된 <code>.tsbuildinfo</code> 파일에서 오래된 프로그램 인스턴스를 re-hydrate를 할 수 있고, 이는 새 프로그램을 만드는 데만 사용한다.(즉 반환된 인스턴스를 수정할 수 없다. 다른 <code>create*Program</code> 함수에서 <code>oldProgram</code> 매개 변수에만 사용된다.)</p>
<p>프로젝트 참조를 활용하기 위해 새 타입 <code>SolutionBuilder</code>의 인스턴스를 반환하는 새 <code>createSolutionBuilder</code> 함수가 노출되었다.</p>
<p>이러한 API에 대한 자세한 설명은 <a href="https://github.com/microsoft/TypeScript/pull/31432">이 PR 참조</a></p>
<h2 id="세미콜론-인식-코드-편집">세미콜론 인식 코드 편집</h2>
<p>Visual Studio 및 VSCode와 같은 편집기는 빠른 수정, 리팩터링 및 다른 모듈에서 값을 자동으로 가져오는 등의 변환을 자동으로 적용할 수 있다. 이러한 변환은 TS에 의해 구동되며, 이전 버전의 TS는 모든 문 끝에 무조건 세미콜론을 추가했다.</p>
<p>이제 TS는 이러한 종류의 편집을 적용할 때 파일이 세미콜론을 사용하는지 여부를 감지할 수 있을 정도로 충분히 똑똑하다. 파일에 일반적으로 세미콜론이 없는 경우 TS는 세미콜론을 추가하지 않는다.</p>
<p>자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/31801">이 PR 참조</a></p>
<h2 id="더-스마트한-auto-import-문법">더 스마트한 Auto-Import 문법</h2>
<p>JS는 ECMAScript 표준의 것, 이미 지원하는 하나의 노드 (CommonJS), AMD, System.js 등 다양한 모듈 구문 또는 규약을 가지고 있다. 대부분의 경우 TS는 ECMAScript 모듈 구문을 사용하여 Auto-Import를 수행하는데, 이는 컴파일러 설정이 서로 다른 특정 TS 프로젝트나 일반 JS가 있는 노드 프로젝트에서 부적절한 경우가 많았다.</p>
<p>TS 3.6은 이제 다른 모듈을 Auto-Import 방법을 결정하기 전에 기존 import 문을 검토하는 것이 조금 더 현명하다.</p>
<p>자세한 내용은 <a href="https://github.com/microsoft/TypeScript/pull/32684">이 PR 참조</a></p>
<h2 id="새-ts-playground">새 TS Playground</h2>
<p>TS Playground는 편리한 새로운 기능으로 많은 환호를 받았다. 새로운 playground는 커뮤니티 구성원들이 점점 더 많이 사용하고 있는 <a href="https://github.com/agentcooper">Artem Tyurin</a>의 <a href="https://github.com/agentcooper/typescript-play">TS Playground</a>이다. 우리는 Artem에게 큰 감사를 표한다.</p>
<p>새로운 playground는 아래와 같은 많은 옵션을 지원한다.</p>
<ul>
<li><a href="https://www.typescriptlang.org/tsconfig#target">타겟</a> 옵션(사용자가 es5에서 es3, es2015, esnext 등으로 전환 가능)</li>
<li>모든 엄격성 플래그(<a href="https://www.typescriptlang.org/tsconfig#strict">strict</a>만 포함)</li>
<li>일반 JS 파일 지원(<code>allowJS</code>를 사용하고 선택적으로 <a href="https://www.typescriptlang.org/tsconfig#checkJs">checkJs</a>)</li>
</ul>
<p>이러한 옵션은 playground 샘플에 대한 링크를 공유할 때에도 유지되므로 사용자가 수신자에게 따로 옵셜 설정을 말해줄 필요없이 신뢰도높은 공유를 가능하게 한다.</p>
<p>가까운 미래에, playground 샘플을 고치고, JSX 지원을 추가하고, 자동 타입 획득을 연마할 것이다. 이것은 여러분이 개인 편집기에서 얻을 수 있는 것과 동일한 경험을 playground에서 볼 수 있다는 것을 의미한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 3.5 번역]]></title>
            <link>https://velog.io/@hustle-dev/TypeScript-3.5</link>
            <guid>https://velog.io/@hustle-dev/TypeScript-3.5</guid>
            <pubDate>Sun, 04 Dec 2022 15:03:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>가장 많이 사용하는 TypeScript의 버전이 영어 아티클로 나오는 데 맨날 읽지 않아서 3.5부터 번역을 하면서 학습하려고 합니다.
원글 링크: <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html">https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html</a></p>
</blockquote>
<h2 id="속도-향상">속도 향상</h2>
<p>TypeScript 3.5는 <code>타입 체킹</code>과 <code>증분 빌드</code>와 관련하여 몇 가지 최적화를 도입한다.</p>
<h3 id="타입-체킹-속도-향상">타입 체킹 속도 향상</h3>
<p>TypeScript 3.5는 TypeScript 3.4보다 더 효율적인 타입 체크를 위한 특정 최적화를 포함한다. 이러한 개선은 타입 체크가 코드 완료 목록과 같은 작업을 수행하는 에디터 시나리오에서 훨씬 더 뚜렷하게 볼 수 있다.</p>
<h3 id="--incremental-개선">&#39;--incremental&#39; 개선</h3>
<p>TypeScript 3.5는 컴파일러 설정, 파일 검색 이유, 파일 검색 위치 등 파일 정보가 계산된 상태정보를 저장함으로써 3.4의 증분 모드를 개선한다. <code>--build</code> 모드에서 <a href="https://github.com/Microsoft/TypeScript/pull/31101">TypeScript의 프로젝트 참조를 사용하는 수백 개의 프로젝트가 포함된 시나리오에서 TypeScript 3.4에 비해 재구성 시간을 최대 68%까지 줄일 수 있음을 발견했다.</a></p>
<p>자세한 정보는 아래 PR 확인</p>
<ul>
<li><a href="https://github.com/Microsoft/TypeScript/pull/31100">https://github.com/Microsoft/TypeScript/pull/31100</a></li>
<li><a href="https://github.com/Microsoft/TypeScript/pull/31101">https://github.com/Microsoft/TypeScript/pull/31101</a></li>
</ul>
<blockquote>
<p>→ 증분 모드란?
TypeScript에서 프로젝트 그래프에 대한 정보를 마지막 컴파일부터 디스크에 저장된 파일까지 저장하도록 한다. 그러면 컴파일 출력과 동일한 폴더에 일련에 <code>.tsbuildinfo</code> 파일이 생성된다. 실행 시 JS에서 사용하지 않으며 안전하게 삭제할 수 있다.</p>
</blockquote>
<h2 id="omit-헬퍼-타입">Omit 헬퍼 타입</h2>
<p>TypeScript 3.5는 새로운 <code>Omit</code> 헬퍼 타입을 도입하여 원본에서 일부 속성이 삭제된 새로운 유형을 생성한다.</p>
<pre><code class="language-ts">type Person = {
  name: string;
  age: number;
  location: string;
}

type QuantumPerson = Omit&lt;Person, &#39;location&#39;&gt;;

// equicalent to
type QuantumPerson = {
  name: string;
  age: number;
};</code></pre>
<p><code>Omit</code> 헬퍼를 사용하여 <code>location</code>을 제외한 <code>Person</code>의 모든 속성을 복사할 수 있다.</p>
<p>원글의 자세한 사항을 확인하기위해 PR을 확인해보면 <code>Omit</code>이란 타입은 아래와 같이 선언되어 있음을 확인할 수 있다.</p>
<pre><code class="language-ts">type Omit&lt;ObjectType, KeysType extends keyof ObjectType&gt; = 
Pick&lt;ObjectType, Exclude&lt;keyof ObjectType, KeysType&gt;&gt;;</code></pre>
<h3 id="유니온-타입에서-초과-속성-검사-개선">유니온 타입에서 초과 속성 검사 개선</h3>
<p>타입스크립트 3.4 및 이전 버전에서는 특정 초과 속성이 있어서는 안되는 상황에서 허용되었다. 예를 들어, TS 3.4는 객체 리터럴에 잘못된 이름 속성을 허용하였다.</p>
<pre><code class="language-ts">type Point = {
  x: number;
  y: number;
}

type Label = {
  name: string;
}

const thing : Point | Label = {
  x: 0,
  y: 0,
  name: true // 3.5 버전 이후엔 잘못되었음을 체킹
}</code></pre>
<h2 id="--allowumdglobalaccess-플래그">&#39;--allowUmdGlobalAccess&#39; 플래그</h2>
<p>타입스크립트 3.5에서 다음과 같은 UMD 글로벌 선언을 참조할 수 있다.</p>
<p>→ UMD는 AMD와 CommonJS 등과 같은 어떠한 모듈 시스템을 사용하더라도 모든 경우를 커버할 수 있도록 동작되게 만드는 것</p>
<pre><code class="language-ts">export as namespace foo;</code></pre>
<p>새 <code>allowUmdGlobalAccess</code> 플래그를 사용하여 어디에서나 액세스를 할 수 있다. 이 모드는 서드파트 라이브러리를 혼합하고 매칭할 수 있는 유연성을 추가하며, 여기서 라이브러리가 선언하는 글로벌을 모듈 내에서도 항상 소비할 수 있다.</p>
<h2 id="보다-더-스마트한-유니온-타입-체킹">보다 더 스마트한 유니온 타입 체킹</h2>
<p>타입스크립트 3.4에서는 아래와 같은 예제는 실패했다.</p>
<pre><code class="language-ts">type S = { done: boolean; value: number };
type T = { done: false; value: number } | { done: true; value: number };
declare let source: S;
declare let target: T;
target = source;</code></pre>
<p>S라는 타입을 <code>{ done: false; value: number }</code> 또는 <code>{ done: true; value: number }</code>에 할당할 수 없기 때문인데, S의 완료 속성이 충분히 구체적이지 않기 때문이다. 이는 <code>boolean</code>인 반면 T의 각 구상요소는 구체적으로 <code>true</code>이거나 <code>false</code>인 완료 속성을 가지고 있다. 그것이 의미하는 바는 각 구성 요소 유형이 고립되어 확인된다는 것이다. TS는 단순히 각 속성을 결합하여 S가 할당 가능한지 확인하지 않는다. </p>
<p>TS 3.5에서는 T와 같은 판별 속성을 가진 유형에 할당할 때, 언어는 실제로 S와 같은 유형을 가능한 유형의 조합으로 분해한다. 이 경우 <code>boolean</code>은 <code>true</code>와 <code>false</code>의 조합이므로 S는 <code>{ done: false; value: number }</code> 및 <code>{ done: true; value: number }</code>의 조합으로 간주된다.</p>
<h2 id="제너릭-생성자의-고차-유형-추론">제너릭 생성자의 고차 유형 추론</h2>
<p>타입스크립트 3.4에서 다음과 같은 함수를 반환하는 일반 함수에 대한 추론을 개선했다.</p>
<pre><code class="language-ts">function compose&lt;T, U, V&gt;(f: (x: T) =&gt; U, g: (y: U) =&gt; V): (x: T) =&gt; V {
  return x =&gt; g(f(x));
}</code></pre>
<p>이를 사용한 예제를 보자.</p>
<pre><code class="language-ts">function arrayify&lt;T&gt;(x: T): T[] {
  return [x];
}
type Box&lt;U&gt; = { value: U };
function boxify&lt;U&gt;(y: U): Box&lt;U&gt; {
  return { value: y };
}
let newFn = compose(arrayify, boxify);</code></pre>
<p>TS 3.4의 추론은 <code>(x: {}) =&gt; Box&lt;{}[]&gt;</code>와 같은 비교적 쓸모없는 유형 대신에 새로운 Fn이 일반적일 수 있게 한다. 즉 <code>newFn</code>의 타입은 <code>&lt;T&gt;(x: T) =&gt; Box&lt;T[]&gt;</code> 이다.</p>
<p>타입스크립트 3.5는 이 동작을 생성자 함수에서도 작동하도록 일반화한다.</p>
<pre><code class="language-ts">class Box&lt;T&gt; {
  kind: &quot;box&quot;;
  value: T;
  constructor(value: T) {
    this.value = value;
  }
}
class Bag&lt;U&gt; {
  kind: &quot;bag&quot;;
  value: U;
  constructor(value: U) {
    this.value = value;
  }
}
function composeCtor&lt;T, U, V&gt;(
  F: new (x: T) =&gt; U,
  G: new (y: U) =&gt; V
): (x: T) =&gt; V {
  return x =&gt; new G(new F(x));
}
let f = composeCtor(Box, Bag); // has type &#39;&lt;T&gt;(x: T) =&gt; Bag&lt;Box&lt;T&gt;&gt;&#39;
let a = f(1024); // has type &#39;Bag&lt;Box&lt;number&gt;&gt;&#39;</code></pre>
<p>위와 같은 구성 패턴 외에도 제너릭 생성자에 대한 이러한 새로운 추론은 리액트와 같은 특정 UI 라이브러리의 클래스 구성 요소에서 작동하는 함수가 제너릭 클래스 구성 요소에서 더 정확하게 작동할 수 있음을 의미한다.</p>
<pre><code class="language-tsx">type ComponentClass&lt;P&gt; = new (props: P) =&gt; Component&lt;P&gt;;
declare class Component&lt;P&gt; {
  props: P;
  constructor(props: P);
}
declare function myHoc&lt;P&gt;(C: ComponentClass&lt;P&gt;): ComponentClass&lt;P&gt;;
type NestedProps&lt;T&gt; = { foo: number; stuff: T };
declare class GenericComponent&lt;T&gt; extends Component&lt;NestedProps&lt;T&gt;&gt; {}
// type is &#39;new &lt;T&gt;(props: NestedProps&lt;T&gt;) =&gt; Component&lt;NestedProps&lt;T&gt;&gt;&#39;
const GenericComponent2 = myHoc(GenericComponent);</code></pre>
<p>타입스크립트 관련 PR링크: <a href="https://github.com/microsoft/TypeScript/pull/31116">https://github.com/microsoft/TypeScript/pull/31116</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩터링 2판] - 상속 다루기]]></title>
            <link>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EC%83%81%EC%86%8D-%EB%8B%A4%EB%A3%A8%EA%B8%B0</link>
            <guid>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EC%83%81%EC%86%8D-%EB%8B%A4%EB%A3%A8%EA%B8%B0</guid>
            <pubDate>Mon, 26 Sep 2022 11:10:17 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="http://www.yes24.com/Product/Goods/89649360">리팩터링 2판</a>의 Chatper 12를 보고 정리한 글입니다.</p>
</blockquote>
<p>이 장에서는 객체 지향 프로그래밍에서 가장 유명한 특성인 상속을 다룬다. 이 <strong>상속은 유용한 동시에 오용하기 쉬운데</strong>, 이와 관련한 리팩터링 기법들을 알아보자.</p>
<h2 id="메서드-올리기">메서드 올리기</h2>
<blockquote>
<p>반대 리팩터링: 메서드 내리기</p>
</blockquote>
<h3 id="배경">배경</h3>
<p>메서드들의 본문 코드가 똑같을 때, 중복이 발생하게 된다. 이러한 중복은 한<strong>쪽의 변경이 다른쪽에는 반영되지 않을 수 있다는 위험을 항상 수반</strong>한다. 이를 위해 메서드 올리기를 적용하여 리팩터링을 진행해보자.</p>
<h3 id="절차">절차</h3>
<ol>
<li>똑같이 동작하는 메서드인지 면밀히 살펴본다.</li>
<li>메서드 안에서 호출하는 다른 메서드와 참조하는 필드들을 슈퍼클래스에서도 호출하고 참조할 수 있는지 확인한다.</li>
<li>메서드 시그니처가 다르다면 함수 선언 바꾸기로 슈퍼클래스에서 사용하고 싶은 형태로 통일한다.</li>
<li>슈퍼클래스에 새로운 메서드를 생성하고, 대상 메서드의 코드를 복사해넣는다.</li>
<li>정적 검사를 수행한다.</li>
<li>서브클래스 중 하나의 메서드를 제거한다.</li>
<li>테스트한다.</li>
<li>모든 서브클래스의 메서드가 없어질 때까지 다른 서브클래스의 메서드를 하나씩 제거한다.</li>
</ol>
<h3 id="예시">예시</h3>
<p><code>리팩터링 전</code></p>
<p>두 서브클래스에서 같은 일을 수행하는 메서드 발견</p>
<pre><code class="language-js">class Employee extends Party {
  get annualCost() {
    return this.monthlyCost * 12;
  }
}

class Department extends Party {
  get totalAnnualCost() {
    return this.monthlyCost * 12;
  }
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">// 슈퍼클래스에 메서드를 붙여 넣자.
class Party {
  get annualCost() {
    return this.monthlyCost * 12;
  }
}</code></pre>
<blockquote>
<p>monthlyCost()를 Party에선 구현해 놓지 않았는데, JS가 동적 언어여서 잘 동작한다. 이럴 때는 함정 메서드(monthlyCost 메서드 정의로 에러를 던지는 코드)를 Party 클래스에 구현해 놓고 사용하면 서브클래스가 monthlyCost()를 구현해야 한다는 사실을 알려줄 수 있다.</p>
</blockquote>
<h2 id="필드-올리기">필드 올리기</h2>
<blockquote>
<p>반대 리팩터링: 필드 내리기</p>
</blockquote>
<h3 id="배경-1">배경</h3>
<p>서브클래스들이 독립적으로 개발되었거나 뒤늦게 하나의 계층구조로 리팩터링된 경우라면 일부 기능이 중복되어 있을 때가 있다. 이러한 경우 분석 결과 필드들이 비슷한 방식으로 쓰인다고 판단되면 슈퍼클래스로 끌어올리자.</p>
<blockquote>
<p>이렇게 하면 데이터 중복 선언을 없앨 수 있고, 해당 필드를 사용하는 동작을 서브 클래스에서 슈퍼클래스로 옮길 수 있다.</p>
</blockquote>
<h3 id="절차-1">절차</h3>
<ol>
<li>후보 필드들을 사용하는 곳 모두가 그 필드들을 똑같은 방식으로 사용하는지 면밀히 살핀다.</li>
<li>필드들의 이름이 각기 다르다면 똑같은 이름으로 바꾼다.</li>
<li>슈퍼클래스에 새로운 필드를 생성한다.</li>
<li>서브클래스의 필드들을 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-1">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-java">class Employee {...} // 자바코드

class SalesPerson extends Employee {
  private String name;
}

class Engineer extends Employee {
  private String name;
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-java">class Employee {
  protected String name;  
}

class SalesPerson extends Employee {...}
class Engineer extends Employee {...}</code></pre>
<h2 id="생성자-본문-올리기">생성자 본문 올리기</h2>
<h3 id="배경-2">배경</h3>
<p>생성자는 다루기 까다롭지만, 서브클래스들에게서 기능이 같은 것들을 발견하고, 생성자로 옮길 수 있는 경우 생성자로 옮겨보자.</p>
<h3 id="절차-2">절차</h3>
<ol>
<li>슈퍼클래스에 생성자가 없다면 하나 정의한다. 서브클래스의 생성자들에게서 이 생성자가 호출되는지 확인한다.</li>
<li>문장 슬라이드하기로 공통 문장 모두 super() 호출 직후로 옮긴다.</li>
<li>공통 코드를 슈퍼클래스에 추가하고 서브클래스들에게서는 제거한다. 생성자 매개변수 중 공통 코드에서 참조하는 값들을 모두 super()로 건넨다.</li>
<li>테스트한다.</li>
<li>생성자 시작 부분으로 옮길 수 없는 공통 코드에는 함수 추출하기와 메서드 올리기를 차례로 적용한다.</li>
</ol>
<h3 id="예시-2">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Party {}

class Employee extends Party {
  constructor(name, id, monthlyCost) {
    super();
    this._id = id;
    this._name = name;
    this._monthlyCost = monthlyCost;
  }

  // 생략
}

class Department extends Party {
  constructor(name, staff) {
    super();
    this._name = name;
    this._staff = staff;
  }

  // 생략
}</code></pre>
<blockquote>
<p>여기서 name을 초기화하는 공통 코드를 발견할 수 있다. 이를 생성자로 옮겨보자.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">class Party {
  constructor(name) {
    this.name = name;
  }
}

class Employee extends Party {
  constructor(name, id, monthlyCost) {
    super(name);
    this._id = id;
    this._monthlyCost = monthlyCost;
  }

  // 생략
}

class Department extends Party {
  constructor(name, staff) {
    super(name);
    this._staff = staff;
  }

  // 생략
}</code></pre>
<blockquote>
<p>위 예제와 달리 공통 코드가 나중에 오는 경우, 함수로 추출한뒤 슈퍼클래스로 옮겨서 사용하는 식으로 리팩터링을 진행할 수 있다.</p>
</blockquote>
<h2 id="메서드-내리기">메서드 내리기</h2>
<blockquote>
<p>반대 리팩터링: 메서드 올리기</p>
</blockquote>
<h3 id="배경-3">배경</h3>
<p>특정 서브클래스 하나와만 관련된 메서드는 슈퍼클래스에서 제거하고 해당 서브클래스들에 추가하는 편이 깔끔하다. </p>
<blockquote>
<p>해당 기능을 제공하는 서브클래스가 정확히 무엇인지를 호출자가 알고 있을 때만 적용할 수 있다.</p>
</blockquote>
<h3 id="절차-3">절차</h3>
<ol>
<li>대상 메서드를 모든 서브클래스에 복사한다.</li>
<li>슈퍼클래스에서 그 메서드를 제거한다.</li>
<li>테스트한다.</li>
<li>이 메서드를 사용하지 않는 모든 서브클래스에서 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-3">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Employee {
  get quota() {...}
}

class Engineer extends Employee {...}
class Salesperson extends Employee {...}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">class Employee {...}

class Engineer extends Employee {...}
class Salesperson extends Employee {
  get quota() {...}
}
</code></pre>
<h2 id="필드-내리기">필드 내리기</h2>
<blockquote>
<p>반대 리팩터링: 필드 올리기</p>
</blockquote>
<h3 id="배경-4">배경</h3>
<p>서브클래스 하나에서만 사용하는 필드는 해당 서브클래스로 옮긴다.</p>
<h3 id="절차-4">절차</h3>
<ol>
<li>대상 필드를 모든 서브클래스에 정의한다.</li>
<li>슈퍼클래스에서 그 필드를 제거한다.</li>
<li>테스트한다.</li>
<li>이 필드를 사용하지 않는 모든 서브클래스에서 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-4">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-java">class Employee { // 자바코드
  private String quota;
}

class Engineer extends Employee {...}
class Salesperson extends Employee {...}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-java">class Employee {...}

class Engineer extends Employee {...}
class Salesperson extends Employee {
  protected String quota;
}</code></pre>
<h2 id="타입-코드를-서브클래스로-바꾸기">타입 코드를 서브클래스로 바꾸기</h2>
<blockquote>
<p>반대 리팩터링: 서브 클래스 제거하기</p>
</blockquote>
<h3 id="배경-5">배경</h3>
<p>소프트웨어 시스템에서는 비슷한 대상들을 특정 특성에 따라 구분해야 할 때가 자주 있는데 이를 다루는 수단으로 타입 코드 필드가 있다.</p>
<p>이러한 타입코드를 사용하는 곳에서 서브클래스는 아래와 같은 2가지 면에서 매력적이다.</p>
<ol>
<li>조건에 따라 다르게 동작하도록 해주는 다형성을 제공한다.</li>
<li>특정 타입에서만 의미가 있는 값을 사용하는 필드나 메서드가 있을 때 좋다.</li>
</ol>
<blockquote>
<p>이 리팩터링은 대상 클래스에 직접 적용하는 경우와, 타입 코드 자체에 적용하는 경우를 고민해야 한다. 예시를 참고하자.</p>
</blockquote>
<h3 id="절차-5">절차</h3>
<ol>
<li>타입 코드 필드를 자가 캡슐화한다.</li>
<li>타입 코드 값 하나를 선택하여 그 값에 해당하는 서브클래스를 만든다. 타입 코드 게터 메서드를 오버라이드하여 해당 타입 코드의 리터럴 값을 반환하게 한다.</li>
<li>매개변수로 받은 타입 코드와 방금 만든 서브클래스를 매핑하는 선택 로직을 만든다.</li>
<li>테스트한다.</li>
<li>타입 코드 값 각각에 대해 서브클래스 생성과 선택 로직 추가를 반복한다. 클래스 하나가 완성될 때마다 테스트한다.</li>
<li>타입 코드 필드를 제거한다.</li>
<li>테스트한다.</li>
<li>타입 코드 접근자를 이용하는 메서드 모두에 메서드 내리기와 조건부 로직을 다형성으로 바꾸기를 적용한다.</li>
</ol>
<h3 id="예시-5">예시</h3>
<p>직접 상속하는 경우</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Employee {
  constructor(name, type) {
    this.validateType(type);
    this._name = name;
    this._type = type;
  }

  validateType(arg) {
    if (![&#39;engineer&#39;, &#39;manager&#39;, &#39;salesman&#39;].includes(arg)) {
      throw new Error(`Employee cannot be of type ${arg}`);
    }
  }

  toString() {
    return `${this._name} (${this._type})`;
  }
}</code></pre>
<p><code>리팩터링 후</code></p>
<p>클래스에 있는 type을 제거하고, 생성자를 팩터리 함수로 바꿔서 선택 로직을 함수에서 처리하는 모습을 확인할 수 있다.</p>
<pre><code class="language-js">class Employee {
  constructor(name) {
    this._name = name;
  }

  toString() {
    return `${this._name} (${this.type})`;
  }
}

class Engineer extends Employee {
  get type() {
    return &#39;engineer&#39;;
  }
}
class Salesperson extends Employee {
  get type() {
    return &#39;salesperson&#39;;
  }
}
class Manager extends Employee {
  get type() {
    return &#39;manager&#39;;
  }
}

function createEmployee(name, type) {
  switch (type) {
    case &#39;engineer&#39;:
      return new Engineer(name);
    case &#39;salesperson&#39;:
      return new Salesperson(name);
    case &#39;manager&#39;:
      return new Manager(name);
      default: throw new Error(`Employee cannot be of type ${arg}`);
  }
}</code></pre>
<blockquote>
<p>서브클래스들에는 타입 코드 게터가 여전히 남아 있고 이를 제거하고 싶겠지만 이 메서드를 이용하는 코드가 어딘가에 남아 있을 수 있으므로 조건부 로직을 다형성으로 바꾸기와 메서드 내리기로 문제를 해결하자.</p>
</blockquote>
<p>간접 상속하는 경우</p>
<p><code>리팩터링 전</code></p>
<p>이번에는 직원의 서브클래스로 &#39;아르바이트&#39;와 &#39;정직원&#39;이라는 클래스가 이미 있어서 Employee를 직접 상속하는 방식으로 타입 코드 문제를 대처할 수 없다. 이러한 경우 간접 상속을 이용한다.</p>
<pre><code class="language-js">class Employee {
  constructor(name, type) {
    this.validateType(type);
    this._name = name;
    this._type = type;
  }

  validateType(arg) {
    if (![&#39;engineer&#39;, &#39;manager&#39;, &#39;salesperson&#39;].includes(arg)) {
      throw new Error(`${arg}라는 직원 유형은 없습니다.`);
    }
  }

  get type() {
    return this._type;
  }

  set type(arg) {
    this._type = arg;
  }

  get capitalizedType() {
    return this._type.charAt(0).toUpperCase() + this._type.substr(1);
  }

  toString() {
    return `${this._name} (${this.capitalizedType})`;
  }
}</code></pre>
<p><code>리팩터링 후</code></p>
<p>타입 코드를 객체로 바꾸고, 앞 예시와 같은 방식으로 직원 유형을 리팩터링해보자.</p>
<pre><code class="language-js">class EmployeeType {
  constructor(aString) {
    this._value = aString;
  }

  toString() {
    return this._value;
  }

  get capitalizedType() {
    return this.toString().charAt(0).toUpperCase() + this.toString().substr(1);
  }
}

class Engineer extends EmployeeType {
  toString() {
    return &#39;engineer&#39;;
  }
}
class Manager extends EmployeeType {
  toString() {
    return &#39;manager&#39;;
  }
}
class Salesperson extends EmployeeType {
  toString() {
    return &#39;salesperson&#39;;
  }
}

class Employee {
  constructor(name, type) {
    this.validateType(type);
    this._name = name;
    this._type = type;
  }

  validateType(arg) {
    if (![&#39;engineer&#39;, &#39;manager&#39;, &#39;salesperson&#39;].includes(arg)) {
      throw new Error(`${arg}라는 직원 유형은 없습니다.`);
    }
  }

  get typeString() {
    return this._type.toString();
  }

  get type() {
    return this._type;
  }

  set type(arg) {
    this._type = new EmployeeType(arg);
  }

  static createEmployeeType(aString) {
    switch(aString) {
      case &#39;engineer&#39;: return new Engineer();
      case &#39;manager&#39;: return new Manager();
      case &#39;salesperson&#39;: return new Salesperson();
      default: throw new Error(`${aString}라는 직원 유형은 없습니다.`);
    }
  }

  toString() {
    return `${this._name} (${this.type.capitalizedType})`;
  }
}</code></pre>
<h2 id="서브클래스-제거하기">서브클래스 제거하기</h2>
<blockquote>
<p>반대 리팩터링: 타입 코드를 서브클래스로 바꾸기</p>
</blockquote>
<h3 id="배경-6">배경</h3>
<p>서브클래스는 소프트웨어 시스템이 성장함에 따라 그 가치가 바래지기도 한다. <strong>더 이상 쓰이지 않는 서브클래스와 마주하는 프로그래머는 가치 없는 것을 이해하느라 에너지를 낭비</strong>할 것이고, 이러한 경우 서브클래스를 슈퍼클래스의 필드로 대체해 제거하는게 최선이다.</p>
<h3 id="절차-6">절차</h3>
<ol>
<li>서브클래스의 생성자를 팩터리 함수로 바꾼다.</li>
<li>서브클래스의 타입을 검사하는 코드가 있다면 그 검사 코드에 함수 추출하기와 함수 옮기기를 차례로 적용하여 슈퍼클래스로 옮긴다. 하나 변경할 때마다 테스트한다.</li>
<li>서브클래스의 타입을 나타내는 필드를 슈퍼클래스에 만든다.</li>
<li>서브클래스를 참조하는 메서드가 방금 만든 타입 필드를 이용하도록 수정한다.</li>
<li>서브클래스를 지운다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-6">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Person {
  constructor(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }

  get genderCoder() {
    return &#39;X&#39;;
  }
  // 생략
}

class Male extends Person {
  get genderCode() {
    return &#39;M&#39;;
  }
}

class Female extends Person {
  get genderCode() {
    return &#39;F&#39;;
  }
}

// 클라이언트
const numberOfMales = people.filter(p =&gt; p instanceof Male).length;</code></pre>
<p>다음과 같은 코드에서 서브 클래스가 하는 일이 이게 다라면 굳이 존재할 이유가 없다. 이를 리팩터링 해보자.</p>
<p><code>리팩터링 후</code></p>
<p>서브클래스 만들기를 캡슐화하는 방법은 생성자를 팩터리 함수로 바꾸기다. 먼저 이를 진행해보자.</p>
<pre><code class="language-js">function createPerson(aRecord) {
  let p;
  switch (aRecord.gender) {
    case &#39;M&#39;:
      p = new Male(aRecord.name);
      break;
    case &#39;F&#39;:
      p = new Female(aRecord.name);
      break;
    default:
      p = new Person(aRecord.name);
  }
  return p;
}

function loadFromInput(data) {
  const result = [];
  data.forEach((aRecord) =&gt; {
    result.push(createPerson(aRecord));
  });
  return result;
}</code></pre>
<blockquote>
<p>현재 이 코드를 깔끔히 청소해보자. 변수 p를 인라인 하고 loadFromInput()의 반복문을 파이프라인으로 바꾼다. 또한 클라이언트 코드에서 instanceOf를 사용하는 타입 검사 코드를 함수로 추출한다.</p>
</blockquote>
<pre><code class="language-js">function createPerson(aRecord) {
  switch (aRecord.gender) {
    case &#39;M&#39;:
      return new Male(aRecord.name);

    case &#39;F&#39;:
      return new Female(aRecord.name);
    default:
      return new Person(aRecord.name);
  }
}

function loadFromInput(data) {
  data.map((aRecord) =&gt; createPerson(aRecord));
}

function isMale(aPerson) {return aPerson instanceof Male;}

class Person {
  get isMale() {return this instanceOf Male;}
}

// 클라이언트
const numberOfMales = people.filter(p =&gt; p.isMale).length;</code></pre>
<p>마지막으로 매개변수를 이용하여 서브클래스들의 차이를 나타낼 필드를 추가하자.</p>
<pre><code class="language-js">class Person {
  constructor(name, genderCode) {
    this._name = name;
    this._genderCode = genderCode;
  }

  get name() {
    return this._name;
  }

  get genderCoder() {
    return &#39;X&#39;;
  }

  get isMale() {
    return this._genderCode === &#39;M&#39;;
  }
  // 생략
}

function createPerson(aRecord) {
  switch (aRecord.gender) {
    case &#39;M&#39;:
      return new Person(aRecord.name, &#39;M&#39;);
    case &#39;F&#39;:
      return new Person(aRecord.name, &#39;F&#39;);
    default:
      return new Person(aRecord.name, &#39;X&#39;);
  }
}</code></pre>
<h2 id="슈퍼클래스-추출하기">슈퍼클래스 추출하기</h2>
<h3 id="배경-7">배경</h3>
<p>비슷한 일을 수행하는 두 클래스가 보이면 상속 메커니즘을 이용해서 비슷한 부분을 공통의 슈퍼클래스로 옮겨 담을 수 있다.</p>
<h3 id="절차-7">절차</h3>
<ol>
<li>빈 슈퍼클래스를 만든다. 원래의 클래스들이 새 클래스를 상속하도록 한다.</li>
<li>테스트한다.</li>
<li>생성자 본문 올리기, 메서드 올리기, 필드 올리기를 차례로 적용하여 공통 원소를 슈퍼클래스로 옮긴다.</li>
<li>서브클래스에 남은 메서드들을 검토한다. 공통되는 부분이 있다면 함수로 추출한 다음 메서드 올리기를 적용한다.</li>
<li>원래 클래스들을 사용하는 코드를 검토하여 슈퍼클래스의 인터페이스를 사용하게 할지 고민해본다.</li>
</ol>
<h3 id="예시-7">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Employee {
  constructor(name, id, monthlyCost) {
    this.name = name;
    this.id = id;
    this.monthlyCost = monthlyCost;
  }

  get monthlyCost() {
    return this._monthlyCost;
  }

  get name() {
    return this._name;
  }

  get id() {
    return this._id;
  }

  get annualCost() {
    return this.monthlyCost * 12;
  }
}

class Department {
  constructor(name, staff) {
    this.name = name;
    this.staff = staff;
  }

  get staff() {
    return this._staff.slice();
  }

  get name() {
    return this._name;
  }

  get totalMonthlyCost() {
    return this.staff.map((e) =&gt; e.monthlyCost).reduce((sum, cost) =&gt; sum + cost);
  }

  get headCount() {
    return this.staff.length;
  }

  get totalAnnualCost() {
    return this.totalMonthlyCost * 12;
  }
}</code></pre>
<blockquote>
<p>연간 비용과 월간 비용부분과 관련한 부분에서 공통된 기능이 눈에 띔을 확인할 수 있다.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<p>Party라는 빈 클래스를 만들고 두 클래스가 이를 확장하도록 만들자. 공통된 필드와 메서드를 슈퍼클래스로 옮긴다.</p>
<pre><code class="language-js">class Party {
  constructor(name) {
    this._name = name;
  }

  get name() {
    return this._name;
  }

  get annualCost() {
    return this.monthlyCost * 12;
  }
}

class Employee extends Party {
  constructor(name, id, monthlyCost) {
    super(name);
    this.name = name;
    this.id = id;
    this.monthlyCost = monthlyCost;
  }

  get monthlyCost() {
    return this._monthlyCost;
  }

  get id() {
    return this._id;
  }
}

class Department extends Party {
  constructor(name, staff) {
    super(name);
    this.name = name;
    this.staff = staff;
  }

  get staff() {
    return this._staff.slice();
  }

  get totalMonthlyCost() {
    return this.staff.map((e) =&gt; e.monthlyCost).reduce((sum, cost) =&gt; sum + cost);
  }

  get headCount() {
    return this.staff.length;
  }
}</code></pre>
<h2 id="계층-합치기">계층 합치기</h2>
<h3 id="배경-8">배경</h3>
<p>계층구조도 진화하면서 어떤 클래스와 그 부모가 너무 비슷해져서 더는 독립적으로 존재해야 할 이유가 사라지는 경우가 생기기도 한다. 
→ 둘을 하나로 합쳐야 할 시점이다.</p>
<h3 id="절차-8">절차</h3>
<ol>
<li>두 클래스 중 제거할 것을 고른다.</li>
<li>필드 올리기와 메서드 올리기 혹은 필드 내리기와 메서드 내리기를 적용하여 모든 요소를 하나의 클래스로 옮긴다.</li>
<li>제거할 클래스를 참조하던 모든 코드가 남겨질 클래스를 참조하도록 고친다.</li>
<li>빈 클래스를 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-8">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Employee {...}
class SalesPerson extends Employee {...}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">class Employee {...}</code></pre>
<h2 id="서브클래스를-위임으로-바꾸기">서브클래스를 위임으로 바꾸기</h2>
<h3 id="배경-9">배경</h3>
<p>상속은 무언가가 달라져야 하는 이유가 여러 개여도 상속에서는 그중 단 하나의 이유만 선택해야 하며, 클래스들의 관계를 아주 긴밀하게 결합한다.</p>
<p>따라서 이 대신 위임을 사용하자. 위임은 객체 사이의 일반적인 관계이므로 상호작용에 필요한 인터페이스를 명확히 정의할 수 있다.</p>
<blockquote>
<p>즉, 상속보다 결합도가 훨씬 약하다.</p>
</blockquote>
<h3 id="절차-9">절차</h3>
<ol>
<li>생성자를 호출하는 곳이 많다면 생성자를 팩터리 함수로 바꾼다.</li>
<li>위임으로 활용할 빈 클래스를 만든다. 이 클래스의 생성자는 서브클래스에 특화된 데이터를 전부 받아야 하며, 보통은 슈퍼클래스를 가리키는 역참조도 필요하다.</li>
<li>위임을 저장할 필드를 슈퍼클래스에 추가한다.</li>
<li>서브클래스 생성 코드를 수정하여 위임 인스턴스를 생성하고 위임 필드에 대입해 초기화한다.</li>
<li>서브클래스의 메서드 중 위임 클래스로 이동할 것을 고른다.</li>
<li>함수 옮기기를 적용해 위임 클래스로 옮긴다. 원래 메서드에서 위임하는 코드는 지우지 않는다.</li>
<li>서브클래스 외부에도 원래 메서드를 호출하는 코드가 있다면 서브클래스에 위임 코드를 슈퍼클래스로 옮긴다. 이때 위임이 존재하는지를 검사하는 보호 코드로 감싸야 한다. 호출하는 외부 코드가 없다면 원래 메서드는 죽은 코드가 되므로 제거한다.</li>
<li>테스트한다.</li>
<li>서브클래스의 모든 메서드가 옮겨질 때까지 5~8 과정을 반복한다.</li>
<li>서브클래스들의 생성자를 호출하는 코드를 찾아서 슈퍼클래스의 생성자를 사용하도록 수정한다.</li>
<li>테스트한다.</li>
<li>서브클래스를 삭제한다.</li>
</ol>
<h3 id="예시-9">예시</h3>
<p>서브 클래스가 하나인 경우(공연 예약 클래스)</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Booking {
  constructor(show, date) {
    this._show = show;
    this._date = date;
  }

  get hasTalkback() {
    return this._show.hasOwnProperty(&#39;talkback&#39;) &amp;&amp; !this.isPeakDay;
  }

  get basePrice() {
    let result = this._show.price;
    if (this.isPeakDay) result += Math.round(result * 0.15);
    return result;
  }
}

// 추가 비용을 다양하게 설정할 수 있느 프리미엄 예약용 서브 클래스
class PremiumBooking extends Booking {
  constructor(show, date, extras) {
    super(show, date);
    this._extras = extras;
  }

  get hasTalkback() {
    return this._show.hasOwnProperty(&#39;talkback&#39;);
  }

  get basePrice() {
    return Math.round(super.basePrice + this._extras.premiumFee);
  }

  // 슈퍼클래스에는 없는 기능을 프리미엄 예약에서 제공하는 예
  get hasDinner() {
    return this._extras.hasOwnProperty(&#39;dinner&#39;) &amp;&amp; !this.isPeakDay;
  }
}

// 클라이언트 쪽 코드
// 일반예약
aBooking = new Booking(show, date);

// 프리미엄 예약
aBooking = new PremiumBooking(show, date, extras);</code></pre>
<p><code>리팩터링 후</code></p>
<p>우선 생성자를 팩터리 함수로 바꿔서 생성자 호출 부분을 캡슐화한다.</p>
<pre><code class="language-js">function createBooking(show, date) {
  return new Booking(show, date);
}

function createPremiumBooking(show, date, extras) {
  return new PremiumBooking(show, date, extras);
}

// 클라이언트 쪽 코드
// 일반예약
aBooking = createBooking(show, date);

// 프리미엄 예약
aBooking = createPremiumBooking(show, date, extras);</code></pre>
<p>이후 위임클래스를 만들고 위임을 예약 객체와 연결</p>
<pre><code class="language-js">// 역참조를 매개변수로 받는 위임 클래스
class PremiumBookingDelegate {
  constructor(hostBooking, extras) {
    this._host = hostBooking;
    this._extras = extras;
  }
}

function createPremiumBooking(show, date, extras) {
  const result = new PremiumBooking(show, date, extras);
  result._bePremium(extras);
  return result;
}

class Booking {
  ...

  _bePremium(extras) {
    this._premiumDelegate = new PremiumBookingDelegate(this, extras);
  }
}</code></pre>
<p>이후 기능을 위임으로 옮겨준다.</p>
<pre><code class="language-js">class Booking {
  constructor(show, date) {
    this._show = show;
    this._date = date;
  }

  _bePremium(extras) {
    this._premiumDelegate = new PremiumBookingDelegate(this, extras);
  }

  get hasTalkback() {
    return this._premiumDelegate
      ? this._premiumDelegate.hasTalkback
      : this._show.hasOwnProperty(&#39;talkback&#39;) &amp;&amp; !this.isPeakDay;
  }

  // 위임의 메서드를 기반 메서드의 확장 형태로 재호출하는 경우
  get basePrice() {
    let result = this._show.price;
    if (this.isPeakDay) result += Math.round(result * 0.15);

    return this._premiumDelegate ? this._premiumDelegate.extendBasePrice(result) : result;
  }

  get hasDinner() {
    return this._premiumDelegate ? this._premiumDelegate.hasDinner : undefined;
  }
}

// 역참조를 매개변수로 받는 위임 클래스
class PremiumBookingDelegate {
  constructor(hostBooking, extras) {
    this._host = hostBooking;
    this._extras = extras;
  }

  get hasTalkback() {
    return this._host._show.hasOwnProperty(&#39;talkback&#39;);
  }

  extendBasePrice(base) {
    return Math.round(base + this._extras.premiumFee);
  }

  get hasDinner() {
    return this._extras.hasOwnProperty(&#39;dinner&#39;) &amp;&amp; !this._host.isPeakDay;
  }
}

// 클라이언트 쪽 코드
// 일반예약
aBooking = createBooking(show, date);

// 프리미엄 예약
aBooking = createPremiumBooking(show, date, extras);

function createBooking(show, date) {
  return new Booking(show, date);
}

function createPremiumBooking(show, date, extras) {
  const result = new Booking(show, date, extras);
  result._bePremium(extras);
  return result;
}</code></pre>
<h2 id="슈퍼클래스를-위임으로-바꾸기">슈퍼클래스를 위임으로 바꾸기</h2>
<h3 id="배경-10">배경</h3>
<p>상속은 기존 기능을 재활용하는 강력하고 손쉬운 수단이지만, 혼란과 복잡도를 키우는 방식으로 이뤄지기도 한다. 따라서 이러한 상속보다 위임을 사용하여 객체를 분리하고 문제를 피해보자.</p>
<blockquote>
<p>그렇다면 &#39;상속은 절대 하지 말아야 할까?&#39; 라는 질문에 저자는 상속을 먼저 적용하고 나중에 문제가 생기면 슈퍼클래스를 위임으로 바꾸는 방식을 조언한다.</p>
</blockquote>
<h3 id="절차-10">절차</h3>
<ol>
<li>슈퍼클래스 객체를 참조하는 필드를 서브클래스에 만든다. 위임 참조를 새로운 슈퍼클래스 인스턴스로 초기화한다.</li>
<li>슈퍼클래스의 동작 각각에 대응하는 전달 함수를 서브클래스에 만든다. 서로 관련된 함수끼리 그룹으로 묶어 진행하며, 그룹을 하나씩 만들 때마다 테스트한다.</li>
<li>슈퍼클래스의 동작 모두가 전달 함수로 오버라이드되었다면 상속 관계를 끊는다.</li>
</ol>
<h3 id="예시-10">예시</h3>
<p>고대 스크롤 관리 코드 예제</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class CatalogItem {
  constructor(id, title, tags) {
    this.id = id;
    this.title = title;
    this.tags = tags;
  }

  get id() {
    return this._id;
  }

  get title() {
    return this._title;
  }

  hasTag(arg) {
    return this._tags.includes(arg);
  }
}

// 정기 세척 이력이 필요하여 카탈로그 아이템을 확장하여
// 세척 관련 데이터를 추가
class Scroll extends CatalogItem {
  constructor(id, title, tags, dateLastCleaned) {
    super(id, title, tags);
    this._lastCleaned = dateLastCleaned;
  }

  needsCleaning(targetDate) {
    const threshold = this.hasTag(&#39;reversed&#39;) ? 700 : 1500;
    return this.daysSinceLastCleaning(targetDate) &gt; threshold;
  }

  datsSinceLastCleaning(targetDate) {
    return this._lastCleaned.until(targetDate, ChronoUnit.DAYS);
  }
}</code></pre>
<blockquote>
<p>석화병 치료법을 적어 놓은 스크롤은 사본이 여러 개임에도 카탈로그 아이템은 하나뿐이라는 차이가 존재하여 슈퍼-서브 관계라는 모델링이 맞지 않음</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<p>Scroll에 카탈로그 아이템을 참조하는 속성을 만들고 슈퍼클래스의 인스턴스를 새로 하나 만들어 대입하고, 대응하는 메서드를 만든 후 상속관계를 끊는다.</p>
<pre><code class="language-js">class CatalogItem {
  constructor(id, title, tags) {
    this.id = id;
    this.title = title;
    this.tags = tags;
  }
}

class Scroll {
  constructor(id, title, tags, dateLastCleaned) {
    this._catalogItem = new CatalogItem(id, title, tags);
    this._lastCleaned = dateLastCleaned;
  }

  needsCleaning(targetDate) {
    const threshold = this.hasTag(&#39;reversed&#39;) ? 700 : 1500;
    return this.daysSinceLastCleaning(targetDate) &gt; threshold;
  }

  datsSinceLastCleaning(targetDate) {
    return this._lastCleaned.until(targetDate, ChronoUnit.DAYS);
  }

  get id() {
    return this._catalogItem.id;
  }

  get title() {
    return this._catalogItem.title;
  }

  hasTag(aString) {
    return this._catalogItem.hasTag(aString);
  }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩터링 2판] - API 리팩터링]]></title>
            <link>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-API-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81</link>
            <guid>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-API-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81</guid>
            <pubDate>Mon, 19 Sep 2022 16:25:07 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="http://www.yes24.com/Product/Goods/89649360">리팩터링 2판</a>의 Chatper 11를 보고 정리한 글입니다.</p>
</blockquote>
<p>모듈과 함수는 소프트웨어를 구성하는 빌딩 블록이며, API는 이 블록들을 끼워 맞추는 연결부이다. 따라서 API를 이해하기 쉽고 사용하기 쉽게 만드는 일은 중요한 일이다. 이러한 API관련 리팩터링 기법들을 알아보자.</p>
<h2 id="질의-함수와-변경-함수-분리하기">질의 함수와 변경 함수 분리하기</h2>
<h3 id="배경">배경</h3>
<p>값을 반환하면서도 부수효과가 있는 함수를 발견하면 <strong>상태를 변경하는 부분과 질의하는 부분을 분리</strong>해보자.</p>
<h3 id="절차">절차</h3>
<ol>
<li>대상 함수를 복제하고 질의 목적에 충실한 이름을 짓는다.</li>
<li>새 질의 함수에서 부수효과를 모두 제거한다.</li>
<li>정적 검사를 수행한다.</li>
<li>원래 함수를 호출하는 곳을 모두 찾아낸다. 호출하는 곳에서 반환 값을 사용한다면 질의 함수를 호출하도록 바꾸고, 원래 함수를 호출하는 코드를 바로 아래 줄에 새로 추가한다. 하나 수정할때마다 테스트한다.</li>
<li>원래 함수에서 질의 관련 코드를 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시">예시</h3>
<p><code>리팩터링 전</code></p>
<p>이름 목록을 훑어 악당을 찾아 악당을 찾으면 그 사람의 이름을 반환하고 경고를 울리는 함수</p>
<pre><code class="language-js">function alertForMiscreant(people) {
  for (const p of people) {
    if (p === &#39;조커&#39;) {
      setOffAlarms();
      return &#39;조커&#39;;
    }
    if (p === &#39;사루만&#39;) {
      setOffAlarms();
      return &#39;사루만&#39;;
    }
  }
  return &#39;&#39;;
}

// 호출하는 쪽 코드
const found = alertForMiscreant(people);</code></pre>
<p><code>리팩터링 후</code></p>
<p>함수를 복제하고 질의 목적에 맞는 이름을 지은후, 부수효과를 낳는 부분을 제거한다. 원래 함수를 호출하는 곳을 모두 찾아서 새로운 질의 함수로 바꾸고 변경 함수 호출 코드를 바로 아래에 삽입하자.</p>
<pre><code class="language-js">function findMiscreant(people) {
  for (const p of people) {
    if (p === &#39;조커&#39;) {
      return &#39;조커&#39;;
    }
    if (p === &#39;사루만&#39;) {
      return &#39;사루만&#39;;
    }
  }
  return &#39;&#39;;
}

function alertForMiscreant(people) {
  for (const p of people) {
    if (p === &#39;조커&#39;) {
      setOffAlarms();
      return;
    }
    if (p === &#39;사루만&#39;) {
      setOffAlarms();
      return;
    }
  }
}

// 호출하는 쪽 코드
const found = findMiscreant(people);
alertForMiscreant(people);


// 여기서 alertForMiscreant 함수를 더 가다듬으면 다음과 같이 만들 수 있다.
function alertForMiscreant(people) {
  if (findMiscreant(people) !== &#39;&#39;) setOffAlarams();
}</code></pre>
<h2 id="함수-매개변수화하기">함수 매개변수화하기</h2>
<h3 id="배경-1">배경</h3>
<p>두 함수의 로직이 비슷하고 단지 <strong>리터럴 값만 다르다면 그 다른 값만 매개변수로 받아 처리하는 함수 하나로 합쳐서 중복을 없앨</strong> 수 있다.</p>
<h3 id="절차-1">절차</h3>
<ol>
<li>비슷한 함수 중 하나를 선택한다.</li>
<li>함수 선언 바꾸기로 리터럴들을 매개변수로 추가한다.</li>
<li>이 함수를 호출하는 곳 모두에 적절한 리터럴 값을 추가한다.</li>
<li>테스트한다.</li>
<li>매개변수로 받은 값을 사용하도록 함수 본문을 수정한다. 하나 수정할 때마다 테스트한다.</li>
<li>비슷한 다른 함수를 호출하는 코드를 찾아 매개변수화된 함수를 호출하도록 하나씩 수정한다. 하나 수정할 때마다 테스트한다.</li>
</ol>
<h3 id="예시-1">예시</h3>
<p>직관적인 경우</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function tenPercentRaise(aPerson) {
  aPerson.salary = aPerson.salary.multiply(1.1);
}

function fivePercentRaise(aPerson) {
  aPerson.salary = aPerson.salary.multiply(1.05);
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function raise(aPerson, factor) {
    aPerson.salary = aPerson.salary.multiply(1 + factor);
}</code></pre>
<p>덜 직관적인 경우</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function baseCharge(usage) {
  if (usage &lt; 0) return usd(0);
  const amount = bottomBand(usage) * 0.03 + middleBand(usage) * 0.05 + topBand(usage) * 0.07;
  return usd(amount);
}

function bottomBand(usage) {
  return Math.min(usage, 100);
}

function middleBand(usage) {
  return usage &gt; 100 ? Math.min(usage, 200) - 100 : 0;
}

function topBand(usage) {
  return usage &gt; 200 ? usage - 200 : 0;
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">// withinBand라는 3가지의 매개변수를 받는 함수를 만들어 사용!
function withinBand(usage, bottom, top) {
  return usage &gt; bottom ? Math.min(usage, top) - bottom : 0;
}

// 하나의 함수로 리팩터링 완료
function baseCharge(usage) {
    if (usage &lt; 0) return usd(0);
    const amount = withinBand(usage, 0, 100) * 0.03
        + withinBand(usage, 100, 200) * 0.05
        + withinBand(usage, 200, Infinity) * 0.07;
    return usd(amount);
}</code></pre>
<h2 id="플래그-인수-제거하기">플래그 인수 제거하기</h2>
<h3 id="배경-2">배경</h3>
<p>플래그 인수란 호출되는 함수가 실행한 로직을 호출하는 쪽에서 선택하기 위해 전달하는 인수다. 그러나 이러한 인수가 있으면 함수들의 기능 차이가 함수 목록에서 드러나지 않는다.</p>
<blockquote>
<p><strong>특정한 기능 하나만 수행하는 명시적인 함수를 제공하는 편이 훨씬 깔끔</strong>하다.</p>
</blockquote>
<h3 id="절차-2">절차</h3>
<ol>
<li>매개변수로 주어질 수 있는 값 각각에 대응하는 명시적 함수들을 생성한다.</li>
<li>원래 함수를 호출하는 코드들을 모두 찾아서 각 리터럴 값에 대응되는 명시적 함수를 호출하도록 수정한다.</li>
</ol>
<h3 id="예시-2">예시</h3>
<p><code>리팩터링 전</code></p>
<p>배송일자를 계산하는 코드</p>
<pre><code class="language-js">// 호출하는 쪽
// boolean 값이 뭘 의미하는지라는 의문이 떠오름
aShipment.deliveryDate = deliveryDate(anOrder, true);
aShipment.deliveryDate = deliveryDate(anOrder, false);



function deliveryDate(anOrder, isRush) {
  if (isRush) {
    let deliveryTime;
    if ([&#39;MA&#39;, &#39;CT&#39;].includes(anOrder.deliveryState)) deliveryTime = 1;
    else if ([&#39;NY&#39;, &#39;NH&#39;].includes(anOrder.deliveryState)) deliveryTime = 2;
    else deliveryTime = 3;
    return anOrder.placedOn.plusDays(1 + deliveryTime);
  }
  let deliveryTime;
  if ([&#39;MA&#39;, &#39;CT&#39;, &#39;NY&#39;].includes(anOrder.deliveryState)) deliveryTime = 2;
  else if ([&#39;ME&#39;, &#39;NH&#39;].includes(anOrder.deliveryState)) deliveryTime = 3;
  else deliveryTime = 4;
  return anOrder.placeOn.plusDays(2 + deliveryTime);
}</code></pre>
<blockquote>
<p>전형적인 플래그 인수를 사용하고 있는 코드</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">// 명시적인 함수가 호출자의 의도를 더 잘 드러낸다.
aShipment.deliveryDate = rushDeliveryDate(anOrder);
aShipment.deliveryDate = regularDeliveryDate(anOrder);

function rushDeliveryDate(anOrder) {
  let deliveryTime;
  if ([&#39;MA&#39;, &#39;CT&#39;].includes(anOrder.deliveryState)) deliveryTime = 1;
  else if ([&#39;NY&#39;, &#39;NH&#39;].includes(anOrder.deliveryState)) deliveryTime = 2;
  else deliveryTime = 3;
  return anOrder.placedOn.plusDays(1 + deliveryTime);
}

function regularDeliveryDate(anOrder) {
  let deliveryTime;
  if ([&#39;MA&#39;, &#39;CT&#39;, &#39;NY&#39;].includes(anOrder.deliveryState)) deliveryTime = 2;
  else if ([&#39;ME&#39;, &#39;NH&#39;].includes(anOrder.deliveryState)) deliveryTime = 3;
  else deliveryTime = 4;
  return anOrder.placeOn.plusDays(2 + deliveryTime);
}</code></pre>
<p>앞 예시와 달리 매개변수가 훨씬 까다로운 방식(if문안의 중첩 조건으로 사용)일 땐, <strong>그 함수를 래핑하는 식으로 하여 앞에서 호출하는 코드들을 작성하고 뒤에 이어서 매개변수를 사용하는 부분만 함수를 만들어 리팩터링을 진행</strong>할 수 있다.</p>
<h2 id="객체-통째로-넘기기">객체 통째로 넘기기</h2>
<h3 id="배경-3">배경</h3>
<p>하나의 레코드에서 값 두어개를 인수로 넘기는 경우, <strong>그 값들 대신 레코드를 통째로 넘기고 함수 본문에서 필요한 값들을 꺼내 쓰도록 수정</strong>할 수 있다.</p>
<h3 id="절차-3">절차</h3>
<ol>
<li>매개변수들을 원하는 형태로 받는 빈 함수를 만든다.</li>
<li>새 함수의 본문에서는 원래 함수를 호출하도록 하며, 새 매개변수와 원래 함수의 매개변수를 매핑한다.</li>
<li>정적 검사를 수행한다.</li>
<li>모든 호출자가 새 함수를 사용하게 수정한다. 하나씩 수정하며 테스트하자.</li>
<li>호출자를 모두 수정했다면 원래 함수를 인라인한다.</li>
<li>새 함수의 이름을 적절히 수정하고 모든 호출자에 반영한다.</li>
</ol>
<h3 id="예시-3">예시</h3>
<p>실내온도 모니터링 시스템(일일 최저,최고 기온이 난방 계획에서 정한 범위를 벗어나는지 확인)</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">const { low } = aRoom.daysTempRange;
const { high } = aRoom.daysTempRange;
if (!aPlan.withinRange(low, high)) alerts.push(&#39;room temperature went outside range&#39;);

class HeatingPlan {
  withinRange(bottom, top) {
    return bottom &gt;= this._temperatureRange.low &amp;&amp; top &lt;= this._temperatureRange.high;
  }
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">class HeatingPlan {
  withinRange(aNumberRange) {
    return aNumberRange.low &gt;= this._temperatureRange.low &amp;&amp; aNumberRange.high &lt;= this._temperatureRange.high;
  }
}

if (!aPlan.withinRange(aRoom.daysTempRange)) alerts.push(&#39;room temperature went outside range&#39;);</code></pre>
<h2 id="매개변수를-질의-함수로-바꾸기">매개변수를 질의 함수로 바꾸기</h2>
<blockquote>
<p>반대 리팩터링: 질의 함수를 매개변수로 바꾸기</p>
</blockquote>
<h3 id="배경-4">배경</h3>
<p>매개변수 목록은 함수의 변동 요인을 모아놓은 곳이기 때문에 중복은 피하는 것이 좋으며 짧을수록 이해하기 쉽다. 따라서 매개변수를 줄이고, 책임 소재를 피호출 함수로 옮겨 호출하는 쪽에서의 책임 주체를 간소하게 만들자.</p>
<p>→ 새로운 의존성이 생기거나 제거하고 싶은 기존 의존성을 강화하는 경우 매개변수를 질의 함수로 바꾸지 말아야 함.</p>
<h3 id="절차-4">절차</h3>
<ol>
<li>필요하다면 대상 매개변수의 값을 계산하는 코드를 별도 함수로 추출해놓는다.</li>
<li>함수 본문에서 대상 매개변수로의 참조를 모두 찾아서 그 매개변수의 값을 만들어주는 표현식을 참조하도록 바꾼다. 하나 수정할 때마다 테스트한다.</li>
<li>함수 선언 바꾸기로 대상 매개변수를 없앤다.</li>
</ol>
<h3 id="예시-4">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Order {
  get finalPrice() {
    const basePrice = this.quantity * this.itemPrice;
    let discountLevel;
    if (this.quantity &gt; 100) discountLevel = 2;
    else discountLevel = 1;
    return this.discountedPrice(basePrice, discountLevel);
  }

  discountedPrice(basePrice, discountLevel) {
    switch (discountLevel) {
      case 1:
        return basePrice * 0.95;
      case 2:
        return basePrice * 0.9;
    }
  }
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">class Order {
  // 기존 로직 변경
  get finalPrice() {
    const basePrice = this.quantity * this.itemPrice;
    return this.discountedPrice(basePrice);
  }

  // 기존 매개변수 삭제
  discountedPrice(basePrice) {
    switch (this.discountLevel) {
      case 1:
        return basePrice * 0.95;
      case 2:
        return basePrice * 0.9;
    }
  }

  // 질의 함수 생성
  get discountLevel() {
    return this.quantity &gt; 100 ? 2 : 1;
  }
}</code></pre>
<blockquote>
<p>필요할 때 직접 호출함으로써, 매개변수를 줄일 수 있다.</p>
</blockquote>
<h2 id="질의-함수를-매개변수로-바꾸기">질의 함수를 매개변수로 바꾸기</h2>
<blockquote>
<p>반대 리팩터링: 매개변수를 질의 함수로 바꾸기</p>
</blockquote>
<h3 id="배경-5">배경</h3>
<p>코드를 보면 전역 변수를 참조하거나 제거하길 원하는 원소를 참조하는 경우와 같이 함수 안에 두기엔 거북한 참조를 발견할 때가 있다. 이 경우 <strong>매개변수로 바꾸어 참조를 풀어내는 책임을 호출자로 옮겨</strong>보자.</p>
<p>→ 질의 함수를 매개변수로 바꾸면 어떤 값을 제공할지를 호출자가 알아내야하므로 호출자가 복잡해지는데 결국 이 문제는 <strong>책임 소재를 프로그램의 어디에 배정하느냐의 문제로 귀결</strong>된다.(답이 없는 문제)</p>
<h3 id="절차-5">절차</h3>
<ol>
<li>변수 추출하기로 질의 코드를 함수 본문의 나머지 코드와 분리한다.</li>
<li>함수 본문 중 해당 질의를 호출하지 않는 코드들을 별도 함수로 추출한다.</li>
<li>방금 만든 변수를 인라인하여 제거한다.</li>
<li>원래 함수도 인라인한다.</li>
<li>새 함수의 이름을 원래 함수의 이름으로 고쳐준다.</li>
</ol>
<h3 id="예시-5">예시</h3>
<p>실내 온도 제어시스템으로 사용자는 온도조절기(thermostat)로 온도를 설정하지만 목표 온도는 난방 계획에서 정한 범위에서만 선택!</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class HeatingPlan {
  get targetTemperature() {
    if (thermostat.selectedTemperature &gt; this._max) return this._max;
    if (thermostat.selectedTemperature &lt; this._min) return this._min;
    return thermostat.selectedTemperature;
  }
}

if (thePlan.targetTemperature &gt; thermostat.currentTemperature) {
  setToHeat();
} else if (thePlan.targetTemperature &lt; thermostat.currentTemperature) {
  setToCool();
} else {
  setOff();
}</code></pre>
<blockquote>
<p>현재 thermostat이라는 전역 객체에 의존하고 있음. 이를 리팩터링 해보자.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">class HeatingPlan {
  targetTemperature(selectedTemperature) {
    if (selectedTemperature &gt; this._max) return this._max;
    if (selectedTemperature &lt; this._min) return this._min;
    return selectedTemperature;
  }
}

if (thePlan.targetTemperature(thermostat.selectedTemperature) &gt; thermostat.currentTemperature) {
  setToHeat();
} else if (thePlan.targetTemperature(thermostat.selectedTemperature) &lt; thermostat.currentTemperature) {
  setToCool();
} else {
  setOff();
}</code></pre>
<blockquote>
<p>매개 변수를 받아 다음과 같이 리팩터링을 할 수 있게 되었다.
또한 클래스는 불변이 되어 모든 필드가 생성자에서 설정되며 필드를 변경할 수 있는 메서드는 존재하지 않게되었다.
→ 테스트하고 다루기 쉬워진다는 장점을 가짐</p>
</blockquote>
<h2 id="세터-제거하기">세터 제거하기</h2>
<h3 id="배경-6">배경</h3>
<p>세터 제거하기 리팩터링이 필요한 상황 2가지</p>
<ol>
<li>사람들이 무조건 접근자 메서드를 통해서만 필드를 다루려 할 때</li>
<li>클라이언트에서 생성 스크립트를 사용해 객체를 생성할 때(생성 스크립트란 생성자를 호출한 후 일련의 세터를 호출하여 객체를 완성하는 형태의 코드)</li>
</ol>
<h3 id="절차-6">절차</h3>
<ol>
<li>설정해야 할 값을 생성자에서 받지 않는다면 그 값을 받을 매개변수를 생성자에 추가한다. 그런 다음 생성자 안에서 적절한 세터를 호출한다.</li>
<li>생성자 밖에서 세터를 호출하는 곳을 찾아 제거하고, 대신 새로운 생성자를 사용하도록 한다. 하나 수정할 때마다 테스트한다.</li>
<li>세터 메서드를 인라인한다. 가능하다면 해당 필드를 불변으로 한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-6">예시</h3>
<p>간단한 사람 클래스</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Person {
  get name() {
    return this._name;
  }

  set name(value) {
    this._name = value;
  }

  get id() {
    return this._id;
  }

  set id(value) {
    this._id = value;
  }
}

const martin = new Person();
martin.name = &#39;Martin&#39;;
martin.id = 1;</code></pre>
<blockquote>
<p>id는 변경되며 안된다는 의도를 명확히 알리기 위해 ID 세터를 없애보자.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<p>최초 한번은 ID를 설정할 수 있도록 생성자에서 ID를 받도록 해보자.</p>
<p>이후 생성 스크립트가 이 생성자를 통해 ID를 설정하게끔 수정하고, 모두 수정했다면 세터 메서드를 인라인 한다.</p>
<pre><code class="language-js">class Person {
  constructor(id) {
    this._id = id;
  }

  get name() {
    return this._name;
  }

  set name(value) {
    this._name = value;
  }

  get id() {
    return this._id;
  }
}

const martin = new Person(&#39;1&#39;);
martin.name = &#39;Martin&#39;;</code></pre>
<h2 id="생성자를-팩터리-함수로-바꾸기">생성자를 팩터리 함수로 바꾸기</h2>
<h3 id="배경-7">배경</h3>
<p>생성자는 객체를 초기화하는 특별한 용도의 함수이다. 그러나 생성자에는 <strong>일반 함수에는 없는 이상한 제약이 따라붙기도하여</strong> 이를 사용하기보다 팩터리 함수를 사용하여 제약을 없애보자.</p>
<h3 id="절차-7">절차</h3>
<ol>
<li>팩터리 함수를 만든다. 팩터리 함수의 본문에서는 원래의 생성자를 호출한다.</li>
<li>생성자를 호출하던 코드를 팩터리 함수 호출로 바꾼다.</li>
<li>하나씩 수정할 때마다 테스트한다.</li>
<li>생성자의 가시 범위가 최소가 되도록 제한한다.</li>
</ol>
<h3 id="예시-7">예시</h3>
<p>직원 유형을 다루는, 간단하지만 이상한 예</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Employee {
  constructor(name, typeCode) {
    this._name = name;
    this._typeCode = typeCode;
  }

  get name() {
    return this._name;
  }

  get type() {
    return Employee.legalTypeCodes[this._typeCode];
  }

  static get legalTypeCodes() {
    return { E: &#39;Engineer&#39;, M: &#39;Manager&#39;, S: &#39;Salesperson&#39; };
  }
}

// 호출자
candidate = new Employee(document.name, document.empType);

const leadEngineer = new Employee(document.leadEngineer, &#39;E&#39;);</code></pre>
<p>팩터리 함수를 만들고, 생성자를 호출하는 곳을 찾아 수정하자.</p>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function createEmployee(name, typeCode) {
    return new Employee(name, typeCode);
}

candidate = createEmployee(document.name, document.empType);

// 호출하는 부분의 리드 엔지니어 같은 경우는 문자열 리터럴을 건네는 방식이므로 좋지않고
// 차라리 createEnginner라는 형식으로 만든다.
function createEngineer(name) {
    return new Employee(name, &#39;E&#39;);
}

const leadEngineer = createEngineer(document.leadEngineer);</code></pre>
<h2 id="함수를-명령으로-바꾸기">함수를 명령으로 바꾸기</h2>
<blockquote>
<p>반대 리팩터링: 명령을 함수로 바꾸기</p>
</blockquote>
<h3 id="배경-8">배경</h3>
<p>함수는 프로그래밍의 기본적인 빌딩 블록 중 하나다. 그런데 <strong>함수를 그 함수만을 위한 객체 안으로 캡슐화하면 더 유용해지는 상황</strong>이 있다. 이런 객체를 가리켜 <strong>&#39;명령 객체&#39; 혹은 단순히 명령</strong>이라 한다.</p>
<h3 id="절차-8">절차</h3>
<ol>
<li>대상 함수의 기능을 옮길 빈 클래스를 만든다. 클래스 이름은 함수 이름에 기초해 짓는다.</li>
<li>방금 생성한 빈 클래스로 함수를 옮긴다.</li>
<li>함수의 인수들 각각은 명령 필드로 만들어 생성자를 통해 설정할지 고민해본다.</li>
</ol>
<h3 id="예시-8">예시</h3>
<p>건강보험 애플리케이션에서 사용하는 점수 계산 함수다.</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function score(candidate, medicalExam, scoringGuide) {
  let result = 0;
  let healthLevel = 0;
  let highMedicalRiskFlag = false;

  if (medicalExam.isSmoker) {
    healthLevel += 10;
    highMedicalRiskFlag = true;
  }

  let certificationGrade = &#39;regular&#39;;
  if (scoringGuide.stateWithLowCertification(candidate.originState)) {
    certificationGrade = &#39;low&#39;;
    result -= 5;
  }

  // lots more code like this
  result -= Math.max(healthLevel - 5, 0);
  return result;
}</code></pre>
<p><code>리팩터링 후</code></p>
<p>빈 클래스를 만들고, 함수를 클래스로 옮겨보자.</p>
<pre><code class="language-js">function score(candidate, medicalExam, scoringGuide) {
  return new Scorer().execute(candidate, medicalExam, scoringGuide);
}

class Scorer {
  execute(candidate, medicalExam, scoringGuide) {
    let result = 0;
    let healthLevel = 0;
    let highMedicalRiskFlag = false;

    if (medicalExam.isSmoker) {
      healthLevel += 10;
      highMedicalRiskFlag = true;
    }
    let certificationGrade = &#39;regular&#39;;
    if (scoringGuide.stateWithLowCertification(candidate.originState)) {
      certificationGrade = &#39;low&#39;;
      result -= 5;
    }

    // lots more code like this
    result -= Math.max(healthLevel - 5, 0);
    return result;
  }
}</code></pre>
<p>이후 execute() 메서드가 매개변수를 받지 않게 생성자쪽으로 옮겨보자.</p>
<pre><code class="language-js">function score(candidate, medicalExam, scoringGuide) {
  return new Scorer(candidate, medicalExam, scoringGuide).execute();
}

class Scorer {
  constructor(candidate, medicalExam, scoringGuide) {
    this._candidate = candidate;
    this._medicalExam = medicalExam;
    this._scoringGuide = scoringGuide;
  }
  execute() {
    let result = 0;
    let healthLevel = 0;
    let highMedicalRiskFlag = false;

    if (this._medicalExam.isSmoker) {
      healthLevel += 10;
      highMedicalRiskFlag = true;
    }

    let certificationGrade = &#39;regular&#39;;
    if (this._scoringGuide.stateWithLowCertification(this._candidate.originState)) {
      certificationGrade = &#39;low&#39;;
      result -= 5;
    }

    // lots more code like this
    result -= Math.max(healthLevel - 5, 0);
    return result;
  }
}</code></pre>
<blockquote>
<p>이후 더 가다듬기에서 지역 변수를 필드로 바꾸고, 중첩 함수로 분리하여 리팩터링이 더 진행된다.</p>
</blockquote>
<h2 id="명령을-함수로-바꾸기">명령을 함수로 바꾸기</h2>
<blockquote>
<p>반대 리팩터링: 함수를 명령으로 바꾸기</p>
</blockquote>
<h3 id="배경-9">배경</h3>
<p>로직이 크게 복잡하지 않다면 <strong>명령 객체는 장점보다 단점이 크니 평범한 함수로 바꿔주는게 낫다.</strong></p>
<h3 id="절차-9">절차</h3>
<ol>
<li>명령을 생성하는 코드와 명령의 실행 메서드를 호출하는 코드를 함께 함수로 추출한다.</li>
<li>명령의 실행 함수가 호출하는 보조 메서드들 각각을 인라인한다.</li>
<li>함수 선언 바꾸기를 적용하여 생성자의 매개변수 모두를 명령의 실행 메서드로 옮긴다.</li>
<li>명령의 실행 메서드에서 참조하는 필드들 대신 대응하는 매개변수를 사용하게끔 바꾼다. 하나씩 수정할 때마다 테스트한다.</li>
<li>생성자 호출과 명령의 실행 메서드 호출을 호출자 안으로 인라인한다.</li>
<li>테스트한다.</li>
<li>죽은 코드 제거하기로 명령 클래스를 없앤다.</li>
</ol>
<h3 id="예시-9">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class ChargeCalculator {
  constructor(customer, usage, provider) {
    this._customer = customer;
    this._usage = usage;
    this._provider = provider;
  }

  get baseCharge() {
    return this._customer.baseRate * this._usage;
  }

  get charge() {
    return this.baseCharge + this._provider.connectionCharge;
  }
}

// 호출자
monthCharge = new ChargeCalculator(customer, usage, provider).charge;</code></pre>
<blockquote>
<p>이 명령 클래스는 간단하므로 함수로 대체해보자.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<p>이 클래스를 생성하고 호출하는 코드를 함께 함수로 추출한다. 또한 값을 반환하는 메서드라면 먼저 반환할 값을 변수로 추출한다.</p>
<pre><code class="language-js">class ChargeCalculator {
  constructor(customer, usage, provider) {
    this._customer = customer;
    this._usage = usage;
    this._provider = provider;
  }

  get charge() {
    const baseCharge = this._customer.baseRate * this._usage;
    return baseCharge + this._provider.connectionCharge;
  }
}


function charge(customer, usage, provider) {
  return new ChargeCalculator(customer, usage, provider).charge;
}

monthCharge = charge(customer, usage, provider);</code></pre>
<p>최종 코드는 다음과 같다.</p>
<pre><code class="language-js">function charge(customer, usage, provider) {
  const baseCharge = customer.baseRate * usage;
  return baseCharge + provider.connectionCharge;
}

// 호출자
monthCharge = charge(customer, usage, provider);
</code></pre>
<h2 id="수정된-값-반환하기">수정된 값 반환하기</h2>
<h3 id="배경-10">배경</h3>
<p>데이터가 어떻게 수정되는지를 추적하는 일은 코드에서 이해하기 가장 어려운 부분 중 하나다. 따라서 데이터가 수정된다면 그 사실을 명확히 알려주어서, 어느 함수가 무슨 일을 하는지 쉽게 알 수 있게 하는 일이 중요하다.</p>
<p>→ 좋은 방법으로, 변수를 갱신하는 함수라면 수정된 값을 반환하여 호출자가 그 값을 변수에 담도록 하는 것이다.</p>
<h3 id="절차-10">절차</h3>
<ol>
<li>함수가 수정된 값을 반환하게 하여 호출자가 그 값을 자신의 변수에 저장하게 한다.</li>
<li>테스트한다.</li>
<li>피호출 함수 안에 반환할 값을 가리키는 새로운 변수를 선언한다.</li>
<li>테스트한다.</li>
<li>계산이 선언과 동시에 이뤄지도록 통합한다.</li>
<li>테스트한다.</li>
<li>피호출 함수의 변수 이름을 새 역할에 어울리도록 바꿔준다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-10">예시</h3>
<p>GPS 위치 목록으로 다양한 계산을 수행하는 코드</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">let totalAscent = 0;
let totalTime = 0;
let totalDistance = 0;
calculateAcent();
calculateTime();
calculateDistance();
const pace = totalTime / 60 / totalDistance;

function calculateAscent() {
  for (let i = 1; i &lt; points.length; i++) {
    const verticalChange = points[i].elevation - points[i - 1].elevation;
    totalAscent += verticalChange &gt; 0 ? verticalChange : 0;
  }
}</code></pre>
<blockquote>
<p>calculateAscent() 안에서 totalAscent가 갱신된다는 사실이 드러나지 않음
→ 밖으로 알려보자.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<p>totalAscent 값을 반환하고, 호출한 곳에서 변수에 대입하게 고친다.</p>
<pre><code class="language-js">const totalAscent = calculateAscent();
const totalTime = calculateTime();
const totalDistance = calculateDistance();
const pace = totalTime / 60 / totalDistance;

function calculateAscent() {
  let result = 0;
  for (let i = 1; i &lt; points.length; i++) {
    const verticalChange = points[i].elevation - points[i - 1].elevation;
    result += verticalChange &gt; 0 ? verticalChange : 0;
  }
  return result;
}
function calculateTime() {...}
function calculateDistance() {...}</code></pre>
<blockquote>
<p>위 처럼 코드가 바뀌게 된다.</p>
</blockquote>
<h2 id="오류-코드를-예외로-바꾸기">오류 코드를 예외로 바꾸기</h2>
<h3 id="배경-11">배경</h3>
<p>예외는 프로그래밍 언어에서 제공하는 독립적인 오류 처리 메커니즘이다. 오류가 발견되면 예외를 던진다. 이러한 예외를 사용하면 오류 코드를 일일이 검사하거나 오류를 식별해 콜스택 위로 던지는 일을 신경쓰지 않아도 된다.</p>
<blockquote>
<p><strong>프로그램의 정상 동작 범주에 들지 않는 오류를 나타낼 때만 이러한 예외를 사용</strong>하자.</p>
</blockquote>
<h3 id="절차-11">절차</h3>
<ol>
<li>콜스택 상위에 해당 예외를 처리할 예외 핸들러를 작성한다.</li>
<li>테스트한다.</li>
<li>해당 오류 코드를 대체할 예외와 그 밖의 예외를 구분할 식별 방법을 찾는다.</li>
<li>정적 검사를 수행한다.</li>
<li>catch절을 수정하여 직접 처리할 수 있는 예외는 적절히 대처하고 그렇지 않은 예외는 다시 던진다.</li>
<li>테스트한다.</li>
<li>오류 코드를 반환하는 곳 모두에서 예외를 던지도록 수정한다. 하나씩 수정할 때마다 테스트한다.</li>
<li>모두 수정했다면 그 오류 코드를 콜스택 위로 전달하는 코드를 모두 제거한다. 하나씩 수정할 때마다 테스트한다.</li>
</ol>
<h3 id="예시-11">예시</h3>
<p>전역 테이블에서 배송지의 배송 규칙을 알아내는 코드</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function localShippingRules(country) {
  const data = countryData.shippingRules[country];
  if (data) return new ShippingRules(data);
  else return -23;
}

function calculateShippingCosts(anOrder) {
    const shippingRules = localShippingRules(anOrder.country);
    if (shippingRules &lt; 0) return shippingRules; // 오류 전파
}

// 최상위
const status = calculateShippingCosts(orderData);
if (status &lt; 0) errorList.push({order: orderData, errorCode: status});</code></pre>
<blockquote>
<p>이 경우 앞서 country(국가 정보)가 유효한지를 함수 호출전에 다 검증했다고 가정하였을 때, 위 코드들이 예상할 수 있는 정상 동작 범주안에 든다면 오류 코드를 예외로 바꾸는 리팩터링을 적용할 준비가 된것이다.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function localShippingRules(country) {
  const data = countryData.shippingRules[country];
  if (data) return new ShippingRules(data);
  throw new OrderProcessingError(-23);
}

function calculateShippingCosts(anOrder) {
  const shippingRules = localShippingRules(anOrder.country);
  if (shippingRules &lt; 0) throw new Error(&#39;오류 코드가 다 사라지지 않음&#39;);
}

try {
  calculateShippingCosts(orderData);
} catch (e) {
  if (e instanceof OrderProcessingError) {
    errorList.push({ order: orderData, errorCode: e.code });
  } else {
    throw e;
  }
}</code></pre>
<blockquote>
<p>저자는 다른 예외와 구별을 위해 별도의 클래스(OrderProcessingError)를 만들어 처리하는것을 좋아하여 위와같이 코드를 작성한다.</p>
</blockquote>
<h2 id="예외를-사전확인으로-바꾸기">예외를 사전확인으로 바꾸기</h2>
<h3 id="배경-12">배경</h3>
<p>예외는 &#39;뜻밖의 오류&#39;라는, 말 그대로 예외적으로 동작할 때만 쓰여야한다. <strong>함수 수행 시 문제가 될 수 있는 조건을 함수 호출 전에 검사할 수 있다면, 예외를 던지는 대신 호출하는 곳에서 조건을 검사</strong>하도록 해야 한다.</p>
<h3 id="절차-12">절차</h3>
<ol>
<li>예외를 유발하는 상황을 검사할 수 있는 조건문을 추가한다. catch 블록의 코드를 조건문의 조건절 중 하나로 옮기고, 남은 try 블록의 코드를 다른 조건절로 옮긴다.</li>
<li>catch 블록에 어서션을 추가하고 테스트한다.</li>
<li>try문과 catch 블록을 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-12">예시</h3>
<p>책의 예시론 자바코드로 자원들을 관리하는 과정에서 자원이 바닥나는 경우, 예외 처리로 대응하기 보다 그 상태를 확인하여 조건처리하는 식으로 문제를 해결한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩터링 2판] - 조건부 로직 간소화]]></title>
            <link>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A1%9C%EC%A7%81-%EA%B0%84%EC%86%8C%ED%99%94</link>
            <guid>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EC%A1%B0%EA%B1%B4%EB%B6%80-%EB%A1%9C%EC%A7%81-%EA%B0%84%EC%86%8C%ED%99%94</guid>
            <pubDate>Sat, 10 Sep 2022 08:50:13 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="http://www.yes24.com/Product/Goods/89649360">리팩터링 2판</a>의 Chatper 10를 보고 정리한 글입니다.</p>
</blockquote>
<p>조건부 로직은 프로그램의 힘을 강화하는 데 기여하지만, 프로그램을 복잡하게 만드는 주요 원흉중 하나이다. 
→ 이러한 조건부 로직과 관련한 리팩터링 기법들을 알아보자.</p>
<h2 id="조건문-분해하기">조건문 분해하기</h2>
<h3 id="배경">배경</h3>
<p>조건문 코드는 무슨 일이 일어나는지 이야기해주지만 <strong>&#39;왜&#39; 일어나는지는 제대로 말해주지 않을 때가 많은 것이 문제</strong>다. </p>
<blockquote>
<p>거대한 코드 블록이 주어지면 코드를 나누고 해체된 덩어리의 의도를 살린 이름의 함수 호출로 바꾸어 조건문안의 의도를 더 확실히 드러내자.</p>
</blockquote>
<h3 id="절차">절차</h3>
<ol>
<li>조건식과 그 조건식에 딸리 조건절 각각을 함수로 추출한다.</li>
</ol>
<h3 id="예시">예시</h3>
<p>여름철이면 할인율이 달라지는 어떤 서비스의 요금을 계산하는 코드</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">if (!aDate.isBefore(plan.summaerStart) &amp;&amp; !aDate.isAfter(plan.summerEnd))
  charge = quantity * plan.summerRate;
else
  charge = quantity * plan.regularRate + plan.regularServiceCharge;</code></pre>
<p><code>리팩터링 후</code></p>
<p>조건식을 별도 함수로 추출하고, 조건이 만족하거나 만족하지 않았을 때의 로직을 모두 함수로 추출한다.</p>
<pre><code class="language-js">if (summer()) charge = summerCharge();
else charge = regularCharge();

function summer() {
  return !aDate.isBefore(plan.summaerStart) &amp;&amp; !aDate.isAfter(plan.summerEnd);
}

function summerCharge() {
  return quantity * plan.summerRate;
}

function regularCharge() {
  return quantity * plan.regularRate + plan.regularServiceCharge;
}


// 취향에 따라 3항 연산자로 바꿔줄 수 있다.
charge = summer() ? summerCharge() : regularCharge();</code></pre>
<h2 id="조건식-통합하기">조건식 통합하기</h2>
<h3 id="배경-1">배경</h3>
<p>비교하는 조건은 다르지만 그 결과로 수행하는 동작은 똑같은 코드들이 있다. 이러한 경우라면 하나로 통합하는게 낫다.</p>
<p>통합하는 이유</p>
<ol>
<li>여러 조각으로 나뉜 조건들을 하<strong>나로 통합함으로써 내가 하려는 일이 더 명</strong>확해진다.</li>
<li>이 작업이 <strong>함수 추출하기까지 이어질 가능성</strong>이 높기 때문이다.</li>
</ol>
<blockquote>
<p>함수로 추출하게 되면 코드의 의도가 훨씬 분명하게 드러나는 경우가 많다.</p>
</blockquote>
<h3 id="절차-1">절차</h3>
<ol>
<li>해당 조건식들 모두에 부수효과가 없는지 확인한다.</li>
<li>조건문 두 개를 선택하여 두 조건문의 조건식들을 논리 연산자로 결합한다.</li>
<li>테스트한다.</li>
<li>조건이 하나만 남을 때까지 2~3 과정을 반복한다.</li>
<li>하나로 합쳐진 조건식을 함수로 추출할지 고려해본다.</li>
</ol>
<h3 id="예시-1">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function disabilityAmount(anEmployee) {
  if (anEmployee.seniority &lt; 2) return 0;
  if (anEmployee.monthsDisabled &gt; 12) return 0;
  if (anEmployee.isPartTime) return 0;
  // 장애 수당 계산
}</code></pre>
<p><code>리팩터링 후</code></p>
<p>조건들을 하나의 식으로 통합하고, 함수로 추출하자.</p>
<pre><code class="language-js">function disabilityAmount(anEmployee) {
  if (isNotEligibleForDisability()) return 0;

  function isNotEligibleForDisability() {
    // 장애 수당 적용 여부 확인
    return anEmployee.seniority &lt; 2 || anEmployee.monthsDisabled &gt; 12 || anEmployee.isPartTime;
  }
}</code></pre>
<h2 id="중첩-조건문을-보호-구문으로-바꾸기">중첩 조건문을 보호 구문으로 바꾸기</h2>
<h3 id="배경-2">배경</h3>
<p>조건문의 두 경로 모두 정상 동작이라면 if else를 사용하는 것이 좋지만, 한쪽만 정상인 경우 그 조건만 if문으로 검사를 하고 그 조건문에서 빠져나와 작성하자. 후자의 경우를 보통 <strong>보호 구문</strong>이라고 한다.</p>
<p>이 리팩터링의 핵심은 <strong>의도를 부각</strong>하는데에 있다. 즉, 동일한 if절과 else는 양 갈래가 똑같은 무게를 가지고 중요하다는 의미를 전달하는데, <strong>보호 구문은 이건 이 함수의 핵심이 아니라는 것을 이야기</strong>할 수 있다.</p>
<h3 id="절차-2">절차</h3>
<ol>
<li>교체해야 할 조건 중 가장 바깥 것을 선택하여 보호 구문으로 바꾼다.</li>
<li>테스트한다.</li>
<li>1~2 과정을 필요한 만큼 반복한다.</li>
<li>모든 보호 구문이 같은 결과를 반환한다면 보호 구문들의 조건식을 통합한다.</li>
</ol>
<h3 id="예시-2">예시</h3>
<p>직원 급여를 계산하는 코드 예시</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function payAmount(employee) {
  let result;
  if (employee.isSeparated) {
    // 퇴사한 직원인가?
    result = { amount: 0, reasonCode: &#39;SEP&#39; };
  } else if (employee.isRetired) {
    // 은퇴한 직원인가?
    result = { amount: 0, reasonCode: &#39;RET&#39; };
  } else {
    // 급여 계산 로직
    lorem.ipsum(dolor.sitAmet);
    consectetur(adipiscing).elit();
    sed.do.eiusmod = tempor.incididunt.ut(labore) &amp;&amp; dolore(magna.aliqua);
    ut.enim.ad(minim.veniam);
    result = someFinalComputation();
  }
  return result;
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function payAmount(employee) {
  if (employee.isSeparated) {
    return { amount: 0, reasonCode: &#39;SEP&#39; };
  }
  if (employee.isRetired) {
    return { amount: 0, reasonCode: &#39;RET&#39; };
  }

  // 급여 계산 로직
  lorem.ipsum(dolor.sitAmet);
  consectetur(adipiscing).elit();
  sed.do.eiusmod = tempor.incididunt.ut(labore) &amp;&amp; dolore(magna.aliqua);
  ut.enim.ad(minim.veniam);
  return someFinalComputation();
}</code></pre>
<p>뒤의 예제에서 기존 조건식을 반대로 만들어서 리팩터링하는 예시도 나오는데 이러한 상황이 필요한 경우 적용해보면 좋을것 같다.</p>
<h2 id="조건부-로직을-다형성으로-바꾸기">조건부 로직을 다형성으로 바꾸기</h2>
<h3 id="배경-3">배경</h3>
<p>조건문 구조를 <strong>클래스와 다형성을 이용하면 더 확실하게 분리</strong>할 수 있다. 이를 활용해보자.</p>
<h3 id="절차-3">절차</h3>
<ol>
<li>다형적 동작을 표현하는 클래스들이 아직 없다면 만들어준다. 이왕이면 적합한 인스턴스를 알아서 만들어 반환하는 팩터리 함수도 함께 만든다.</li>
<li>호출하는 코드에서 팩터리 함수를 사용하게 한다.</li>
<li>조건부 로직 함수를 슈퍼클래스로 옮긴다.</li>
<li>서브클래스 중 하나를 선택한다. 서브클래스에서 슈퍼클래스의 조건부 로직 메서드를 오버라이드한다. 조건부 문장 중 선택된 서브클래스에 해당하는 조건절을 서브클래스 메서드로 복사한 다음 적절히 수정한다.</li>
<li>같은 방식으로 각 조건절을 해당 서브클래스에서 메서드로 구현한다.</li>
<li>슈퍼클래스 메서드에는 기본 동작 부분만 남긴다. 혹은 슈퍼클래스가 추상 클래스여야 한다면, 이 메서드를 추상으로 선언하거나 서브클래스에서 처리해야 함을 알리는 에러를 던진다.</li>
</ol>
<h3 id="예시-3">예시</h3>
<p>새의 종에 따른 비행 속도와 깃털 상태 관련 예시</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function plumages(birds) {
  return new Map(birds.map((b) =&gt; [b.name, plumage(b)]));
}

function speeds(birds) {
  return new Map(birds.map((b) =&gt; [b.name, airSpeedVelocity(b)]));
}

function plumage(bird) {
  switch (bird.type) {
    case &#39;유럽 제비&#39;:
      return &#39;보통이다&#39;;
    case &#39;아프리카 제비&#39;:
      return bird.numberOfCoconuts &gt; 2 ? &#39;지쳤다&#39; : &#39;보통이다&#39;;
    case &#39;노르웨이 파랑 앵무&#39;:
      return bird.voltage &gt; 100 ? &#39;그을렸다&#39; : &#39;예쁘다&#39;;
    default:
      return &#39;알 수 없다&#39;;
  }
}

function airSpeedVelocity(bird) {
  switch (bird.type) {
    case &#39;유럽 제비&#39;:
      return 35;
    case &#39;아프리카 제비&#39;:
      return 40 - 2 * bird.numberOfCoconuts;
    case &#39;노르웨이 파랑 앵무&#39;:
      return bird.isNailed ? 0 : 10 + bird.voltage / 10;
    default:
      return null;
  }
}
</code></pre>
<p><code>리팩터링 후</code></p>
<p>가장 먼저 Bird라는 클래스로 묶어보자.</p>
<pre><code class="language-js">class Bird {
  constructor(birdObject) {
    Object.assign(this, birdObject)l
  }

  get plumage() {
    switch (this.type) {
      case &#39;유럽 제비&#39;:
        return &#39;보통이다&#39;;
      case &#39;아프리카 제비&#39;:
        return this.numberOfCoconuts &gt; 2 ? &#39;지쳤다&#39; : &#39;보통이다&#39;;
      case &#39;노르웨이 파랑 앵무&#39;:
        return this.voltage &gt; 100 ? &#39;그을렸다&#39; : &#39;예쁘다&#39;;
      default:
        return &#39;알 수 없다&#39;;
    }
  }

  get airSpeedVelocity() {
    switch (this.type) {
      case &#39;유럽 제비&#39;:
        return 35;
      case &#39;아프리카 제비&#39;:
        return 40 - 2 * this.numberOfCoconuts;
      case &#39;노르웨이 파랑 앵무&#39;:
        return this.isNailed ? 0 : 10 + this.voltage / 10;
      default:
        return null;
    }
  }
}</code></pre>
<p>이후 종별 서브클래스를 만든다. 적합한 서브클래스의 인스턴스를 만들어줄 팩터리 함수를 만들고, 객체를 얻을 때 팩터리 함수를 사용하도록 수정한다.</p>
<pre><code class="language-js">function plumage(bird) {
  return createBird(bird).plumage;
}

function airSpeedVelocity(bird) {
  return createBird(bird).airSpeedVelocity;
}

function createBird(bird) {
  switch (bird.type) {
    case &#39;유럽 제비&#39;:
      return new EuropeanSwallow(bird);
    case &#39;아프리카 제비&#39;:
      return new AfricanSwallow(bird);
    case &#39;노르웨이 파랑 앵무&#39;:
      return new NorwegianBlueParrot(bird);
    default:
      return new Bird(bird);
  }
}

class EuropeanSwallow extends Bird {}

class AfricanSwallow extends Bird {}

class NorwegianBlueParrot extends Bird {}</code></pre>
<p>이후 서브클래스들에 조건부 메서드를 처리하자.</p>
<pre><code class="language-js">function plumages(birds) {
  return new Map(birds.map((b) =&gt; [b.name, plumage(b)]));
}

function speeds(birds) {
  return new Map(birds.map((b) =&gt; [b.name, airSpeedVelocity(b)]));
}

class Bird {
  constructor(birdObject) {
    Object.assign(this, birdObject);
  }

  get plumage() {
    return &#39;알 수 없다&#39;;
  }

  get airSpeedVelocity() {
    return null;
  }
}

function plumage(bird) {
  return createBird(bird).plumage;
}

function airSpeedVelocity(bird) {
  return createBird(bird).airSpeedVelocity;
}

function createBird(bird) {
  switch (bird.type) {
    case &#39;유럽 제비&#39;:
      return new EuropeanSwallow(bird);
    case &#39;아프리카 제비&#39;:
      return new AfricanSwallow(bird);
    case &#39;노르웨이 파랑 앵무&#39;:
      return new NorwegianBlueParrot(bird);
    default:
      return new Bird(bird);
  }
}

class EuropeanSwallow extends Bird {
  get plumage() {
    return &#39;보통이다&#39;;
  }

  get airSpeedVelocity() {
    return 35;
  }
}

class AfricanSwallow extends Bird {
  get plumage() {
    return this.numberOfCoconuts &gt; 2 ? &#39;지쳤다&#39; : &#39;보통이다&#39;;
  }

  get airSpeedVelocity() {
    return 40 - 2 * this.numberOfCoconuts;
  }
}

class NorwegianBlueParrot extends Bird {
  get plumage() {
    return this.voltage &gt; 100 ? &#39;그을렸다&#39; : &#39;예쁘다&#39;;
  }

  get airSpeedVelocity() {
    return this.isNailed ? 0 : 10 + this.voltage / 10;
  }
}</code></pre>
<blockquote>
<p>위와 같은 계층 구조뿐만아니라 변형동작과 관련하여도 상속과 다형성을 이용하여 조건부 로직을 분리할 수 있다.(예제 책 참고)</p>
</blockquote>
<h2 id="특이-케이스-추가하기">특이 케이스 추가하기</h2>
<h3 id="배경-4">배경</h3>
<p>코드베이스에서 특정 값에 대해 똑같이 반응하는 코드가 여러 곳이라면 그 반응들을 한 데 모으는게 효율적이다. <strong>특수한 경우의 공통 동작을 요소 하나에 모아서 사용하는 특이 케이스 패턴</strong>이라는 것이 있고, 널은 특이 케이스로 처리해야 할 때가 많다. 그래서 이 패턴을 널 객체 패턴이라고도 한다.</p>
<h3 id="절차-4">절차</h3>
<ol>
<li>컨테이너에 특이 케이스인지를 검사하는 속성을 추가하고, false를 반환하게 한다.</li>
<li>특이 케이스 객체를 만든다. 이 객체는 특이 케이스인지를 검사하는 속성만 포함하며, 이 속성은 true를 반환하게 한다.</li>
<li>클라이언트에서 특이 케이스인지를 검사하는 코드를 함수로 추출한다. 모든 클라이언트가 값을 직접 비교하는 대신 방금 추출한 함수를 사용하도록 고친다.</li>
<li>코드에 새로운 특이 케이스 대상을 추가한다. 함수의 반환 값으로 받거나 변환 함수를 적용하면 된다.</li>
<li>특이 케이스를 검사하는 함수 본문을 수정하여 특이 케이스 객체의 속성을 사용하도록 한다.</li>
<li>테스트한다.</li>
<li>여러 함수를 클래스로 묶기나 여러 함수를 변환 함수로 묶기를 적용하여 특이 케이스를 처리하는 공통 동작을 새로운 요소로 옮긴다.</li>
<li>아직도 특이 케이스 검사 함수를 이용하는 곳이 남아 있다면 검사 함수를 인라인한다.</li>
</ol>
<h3 id="예시-4">예시</h3>
<p>전력 회사의 예시로 전력이 필요한 현장에 인프라를 설치해 서비스를 제공하는 코드를 보자.</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Site {
  get customer() { return this._customer; }
}

class Customer {
  get name() {...} // 고객이름
  get billingPlan() {...} // 요금제
  set billingPlan(arg) {...}
  get paymentHistory() {...} // 납부 이력
}</code></pre>
<blockquote>
<p>일반적으로 현장에 고객이 거주하는 게 보통이지만 그렇지 않은 경우가 있다. 즉 &#39;미확인 고객&#39;이란 문자열로 대체되는 상황이 존재하고, 이런 상황을 감안하여 Site 클래스를 사용하는 코드들은 이 경우도 처리할 수 있어야 한다.</p>
</blockquote>
<p>클라이언트 측 코드</p>
<pre><code class="language-js">// 클라이언트 1
const aCustomer = site.customer;
// ... 수많은 코드 ...
let customerName;
if (aCustomer === &#39;미확인 고객&#39;) customerName = &quot;거주자&quot;;
else customerName = aCustomer.name;

// 클라이언트 2
const plan = aCustomer === &quot;미확인 고객&quot; ? registry.billingPlans.basic : aCustomer.billingPlan;

// 클라이언트 3
if (aCustomer !== &#39;미확인 고객&#39;) aCustomer.billingPlan = newPlan;

// 클라이언트 4
const weeksDelinquent = aCustomer === &#39;미확인 고객&#39; ? 0 : aCustomer.paymentHistory.weeksDelinquentInLastYear;</code></pre>
<blockquote>
<p>코드베이스에서 미확인 고객을 처리해야 하는 클라이언트 코드가 여러 개 발견되었다. 이러한 경우가 우리에게 특이 케이스 객체를 도입할 때임을 말해준다.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<p>isUnknown이라는 함수를 만들어 클라이언트측에서 미확인 고객을 확인하는 부분을 바꿔보자.</p>
<pre><code class="language-js">class Site {
  get customer() { this._customer === &#39;미확인 고객&#39; ? new UnknownCustomer() : this._customer }
}

function isUnknown(arg) {
  if(!((arg instanceof Customer) || (arg instanceof UnknownCustomer))) throw new Error(`잘못된 값과 비교: &lt;${arg}&gt;`);
  return arg.isUnknown
}

class Customer {
  get name() {...} // 고객이름
  get billingPlan() {...} // 요금제
  set billingPlan(arg) {...}
  get paymentHistory() {...} // 납부 이력

  get isUnknown() {return true;}
}

class UnknownCustomer {...}

// 클라이언트 1
const aCustomer = site.customer;
// ... 수많은 코드 ...
let customerName;
if (isUnknown(aCustomer)) customerName = &quot;거주자&quot;;
else customerName = aCustomer.name;

// 클라이언트 2
const plan = isUnknown(aCustomer) ? registry.billingPlans.basic : aCustomer.billingPlan;

// 클라이언트 3
if (!isUnknown(aCustomer)) aCustomer.billingPlan = newPlan;

// 클라이언트 4
const weeksDelinquent = isUnknown(aCustomer) ? 0 : aCustomer.paymentHistory.weeksDelinquentInLastYear;</code></pre>
<p>여기서부터 특이 케이스 검사를 일반적인 기본값으로 대체하여 검사 코드에 여러 함수를 클래스로 묶기를 적용할 수 있다.</p>
<pre><code class="language-js">class UnknownCustomer {
  get name() {return &#39;거주자&#39;;}
  get billingPlan() {return registry.billingPlans.basic;}
  set billingPlan(arg) {/* */}
  get paymentHistory() {return new NullPaymentHistory();}
}

// 특이 케이스 객체가 다른 객체를 반환해야 하는경우, 그 객체 역시 특이 케이스여야하므로 새로운 클래스를 만듦
class NullPaymentHistory {
  get weeksDelinquentInLastYear() {return 0;}
}</code></pre>
<blockquote>
<p>책에선 위 예제말고 다른 두가지 예제도 소개한다.</p>
</blockquote>
<ol>
<li>데이터 구조를 읽기만 한다면 간단하게 객체 리터럴 생성함수를 이용하여 간단하게 해결하는 방법</li>
<li>변환 함수를 이용하여 해결하는 법</li>
</ol>
<h2 id="어서션-추가하기">어서션 추가하기</h2>
<h3 id="배경-5">배경</h3>
<p>어서션을 추가하여 프로그램이 어떤 상태임을 가정한 채 실행되는지를 다른 개발자에게 알려주는 소통 도구로 사용할 수 있다.</p>
<blockquote>
<p>어서션: 항상 참이라고 가정하는 조건부 문장
→ 이것이 실패하면 프로그래머가 잘못했다는 뜻</p>
</blockquote>
<h3 id="절차-5">절차</h3>
<ol>
<li>참이라고 가정하는 조건이 보이면 그 조건을 명시하는 어서션을 추가한다.</li>
</ol>
<h3 id="예시-5">예시</h3>
<p>할인과 관련한 간단한 예</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Customer {
  applyDiscount(aNumber) {
    return this.discountRate ? aNumber - this.discountRate * aNumber : aNumber;
  }
}</code></pre>
<blockquote>
<p>이 코드에는 할인율이 항상 양수라는 가정이 전제</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">// 다음과 같이 간단하게 어서션을 추가할 수 있지만
class Customer {
  applyDiscount(aNumber) {
    if (!this.discountRate) return aNumber;
    else {
      assert(this.discountRate &gt;= 0);
      return aNumber - this.discountRate * aNumber;
    }
  }
}

// 이번 예에서는 세터 메서드에 추가함으로써, 이 문제가 언제 처음 발생했는지를 알 수 있다.
// Customer 클래스
set discountRate(aNumber) {
  assert(aNumber === null || aNumber &gt;= 0);
  this._discountRate = aNumber;
}</code></pre>
<h2 id="제어-플래그를-탈출문으로-바꾸기">제어 플래그를 탈출문으로 바꾸기</h2>
<h3 id="배경-6">배경</h3>
<p>제어 플래그란 <strong>코드의 동작을 변경하는 데 사용되는 변수를 말하며, 어딘가에서 값을 계산해 제어 플래그를 설정한 후 다른 어딘가의 조건문에서 검사하는 형태</strong>로 쓰인다.</p>
<blockquote>
<p>이러한 코드는 충분히 리팩터링을 진행할 수 있다.</p>
</blockquote>
<h3 id="절차-6">절차</h3>
<ol>
<li>제어 플래그를 사용하는 코드를 함수로 추출할지 고려한다.</li>
<li>제어 플래그를 갱신하는 코드 각각을 적절한 제어문으로 바꾼다. 하나 바꿀 때마다 테스트한다.</li>
<li>모두 수정했다면 제어 플래그를 제거한다.</li>
</ol>
<h3 id="예시-6">예시</h3>
<p>사람 목록을 훑으면서 악당을 찾는 코드</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">let found = false;
for (const p of people) {
  if (!found) {
    if (p === &#39;조커&#39;) {
      sendAlert();
      found = true;
    }
    if (p === &#39;사루만&#39;) {
      sendAlert();
      found = true;
    }
  }
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function checkForMiscreants(people) {
  for (const p of people) {
    if (p === &#39;조커&#39;) {
      sendAlert();
      return;
    }
    if (p === &#39;사루만&#39;) {
      sendAlert();
      return;
    }
  }
}


// 더 가다듬기
function checkForMiscreants(people) {
  if (people.some((p) =&gt; [&#39;조커&#39;, &#39;사루만&#39;].includes(p))) sendAlert();
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩터링 2판] - 데이터 조직화]]></title>
            <link>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%EC%A7%81%ED%99%94</link>
            <guid>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%EC%A7%81%ED%99%94</guid>
            <pubDate>Mon, 05 Sep 2022 17:03:09 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="http://www.yes24.com/Product/Goods/89649360">리팩터링 2판</a>의 Chatper 09를 보고 정리한 글입니다.</p>
</blockquote>
<p><strong>데이터 구조</strong>는 프로그램에서 중요한 역할을 한다. 이 장에서는 데이터 구조에 집중한 리팩터링에 대해서 다룬다.</p>
<h2 id="변수-쪼개기">변수 쪼개기</h2>
<h3 id="배경">배경</h3>
<p>나중에 쉽게 참조하려는 목적으로 쓰인 변수에는 값을 <strong>단 한번만 대입</strong>해야 한다. 대입이 두 번 이상 이뤄진다면 여러가지 역할을 수행한다는 신호이다. </p>
<blockquote>
<p>역할이 둘 이상인 변수가 있다면 코드를 읽는 이에게 혼란을 주기 때문에 쪼개자.</p>
</blockquote>
<h3 id="절차">절차</h3>
<ol>
<li>변수를 선언한 곳과 값을 처음 대입하는 곳에서 변수 이름을 바꾼다.</li>
<li>가능하면 이때 불변으로 선언한다.</li>
<li>이 변수에 두 번째로 값을 대입하는 곳 앞까지의 모든 참조(이 변수가 쓰인곳)를 새로운 변수 이름으로 바꾼다.</li>
<li>두 번째 대입 시 변수를 원래 이름으로 다시 선언한다.</li>
<li>테스트한다.</li>
<li>반복한다. 매 반복에서 변수를 새로운 이름으로 선언하고 다음번 대입 때까지의 모든 참조를 새 변수명으로 바꾼다. 이 과정을 마지막 대입까지 반복한다.</li>
</ol>
<h3 id="예시">예시</h3>
<p><code>리팩터링 전</code></p>
<p>음식이 다른 지역으로 전파된 거리를 구하는 코드</p>
<pre><code class="language-js">function distanceTravelled(scenario, time) {
  let result;
  let acc = scenario.primaryForce / scenario.mass; // 가속도(a) = 힘(F) / 질량(m)
  const primaryTime = Math.min(time, scenario.delay);
  result = 0.5 * acc * primaryTime * primaryTime; // 전파된 거리
  const secondaryTime = time - scenario.delay;
  if (secondaryTime &gt; 0) { // 두 번째 힘을 반영해 다시 계산
    const primaryVelocity = acc * scenario.delay;
    acc = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;
    result += primaryVelocity * secondaryTime + 0.5 * acc * secondaryTime * secondaryTime;
  }
  return result;
}</code></pre>
<blockquote>
<p>이 코드를 자세히보면, acc 변수에 값이 두 번 대입되는 것을 볼 수 있다. 하나는 첫 번째 힘이 유발한 초기 가속도를 저장하는 역할이고, 다른 하나는 두 번째 힘까지 반영된 후의 가속도를 저장하는 역할이다. 
→ 쪼개야 할 변수다.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function distanceTravelled(scenario, time) {
  let result;
  const primaryAcceleration = scenario.primaryForce / scenario.mass; 
  const primaryTime = Math.min(time, scenario.delay);
  result = 0.5 * primaryAcceleration * primaryTime * primaryTime; 
  const secondaryTime = time - scenario.delay;
  if (secondaryTime &gt; 0) {
    const primaryVelocity = primaryAcceleration * scenario.delay;
    // 여기 원래의 acc까지 1차 리팩터링
    const secondaryAcceleration = (scenario.primaryForce + scenario.secondaryForce) / scenario.mass;
    result += primaryVelocity * secondaryTime + 0.5 * secondaryAcceleration * secondaryTime * secondaryTime;
  }
  // 이후 2차 리팩터링
  return result;
}</code></pre>
<p>이 예제 말고 입력 매개변수의 값을 수정하는 예제도 나오는데, 입력 값에 기초하여 코드의 명확한 목적을 더 잘 드러내는 코드로 리팩터링하는 예제이다.</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function discount(inputValue, quantity) {
  if (inputValue &gt; 50) inputValue -= 2;
  if (quantity &gt; 100) inputValue -= 1;
  return inputValue;
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function discount(inputValue, quantity) {
  let result = inputValue;
  if (inputValue &gt; 50) result -= 2;
  if (quantity &gt; 100) result -= 1;
  return result;
}</code></pre>
<h2 id="필드-이름-바꾸기">필드 이름 바꾸기</h2>
<h3 id="배경-1">배경</h3>
<p>레코드 구조체의 <strong>필드 이름과 같은 데이터 구조는 프로그램을 이해하는 데 큰 역할</strong>을 한다.</p>
<p>클래스의 게터와 세터 이름 바꾸기도 <strong>사용자 입장에서 필드와 다름 없어서</strong> 레코드 구조체의 필드 이름 바꾸기와 똑같이 중요하다.</p>
<h3 id="절차-1">절차</h3>
<ol>
<li>레코드의 유효 범위가 제한적이라면 필드에 접근하는 모든 코드를 수정한 후 테스트한다. 이후 단계는 필요 없다.</li>
<li>레코드가 캡슐화되지 않았다면 우선 레코드를 캡슐화한다.</li>
<li>캡슐화된 객체 안의 private 필드명을 변경하고, 그에 맞게 내부 메서드들을 수정한다.</li>
<li>테스트한다.</li>
<li>생성자의 매개변수 중 필드와 이름이 겹치는 게 있다면 함수 선언 바꾸기로 결정한다.</li>
<li>접근자들의 이름도 바꿔준다.</li>
</ol>
<h3 id="예시-1">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">const organization = { name: &#39;애크미 구스베리&#39;, country: &#39;GB&#39; };</code></pre>
<blockquote>
<p>이 상수의 &#39;name&#39;을 &#39;title&#39;로 바꿔보자.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<ol>
<li>캡슐화 진행</li>
</ol>
<pre><code class="language-js">class Organization {
  constructor(data) {
    this._name = data.name;
    this._country = data.country;
  }

  get name() { return this._name; }
  set name(aString) { this._name = aString; }
  get country() { return this._country; }
  set country(aCountryCode) { this._country = aCountryCode; }
}</code></pre>
<ol start="2">
<li>게터, 세터, 내부 데이터 구조 변경과 constructor 내부에서 title 값 받는 로직 변경 + 호출하는 쪽 코드 변경</li>
</ol>
<pre><code class="language-js">class Organization {
  constructor(data) {
    this._title = (data.title !== undefined) ? data.title : data.name;
    this._country = data.country;
  }

  get title() { return this._title; }
  set title(aString) { this._title = aString; }
  get country() { return this._country; }
  set country(aCountryCode) { this._country = aCountryCode; }
}

const organization = new Organization({ title: &#39;애크미 구스베리&#39;, country: &#39;GB&#39; });
</code></pre>
<h2 id="파생-변수를-질의-함수로-바꾸기">파생 변수를 질의 함수로 바꾸기</h2>
<h3 id="배경-2">배경</h3>
<p>가변 데이터는 연쇄 효과를 일으켜 다른 쪽 코드에 원인을 찾기 어려운 문제를 야기한다. 
→ 저자는 <strong>가변 데이터의 유효 범위를 가능한 한 좁혀</strong>야 한다고 주장한다.</p>
<p>단, 새로운 데이터 구조를 생성하는 변형 연산과 같이 계산 결과가 일정한 불변인 경우를 제외하고.</p>
<h3 id="절차-2">절차</h3>
<ol>
<li>변수 값이 갱신되는 지점을 모두 찾는다. 필요하면 변수 쪼개기를 활용해 각 갱신 지점에서 변수를 분리한다.</li>
<li>해당 변수의 값을 계산해주는 함수를 만든다.</li>
<li>해당 변수가 사용되는 모든 곳에서 어서션을 추가하여 함수의 계산 결과가 변수의 값과 같은지 확인한다.</li>
<li>테스트한다.</li>
<li>변수를 읽는 코드를 모두 함수 호출로 대체한다.</li>
<li>테스트한다.</li>
<li>변수를 선언하고 갱신하는 코드를 죽은 코드 제거하기로 없앤다.</li>
</ol>
<h3 id="예시-2">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class ProductionPlan {
  ...

  get production() { return this._production; }
  applyAdjustment(anAdjustment) {
    this._adjustments.push(anAdjustment);
    this._production += anAdjustment.amount;
  }
}</code></pre>
<p><code>리팩터링 후</code></p>
<p>책에서는 어서션을 추가해보고, 실패하지 않으면 코드를 수정하여 계산 결과를 직접 반환받도록 리팩터링이 진행된다.</p>
<pre><code class="language-js">class ProductionPlan {
  ...

  get production() { return this._adjustments.reduce((sum, a) =&gt; sum + a.amount, 0); }
  applyAdjustment(anAdjustment) {
    this._adjustments.push(anAdjustment);
  }
}</code></pre>
<h2 id="참조를-값으로-바꾸기">참조를 값으로 바꾸기</h2>
<blockquote>
<p>반대 리팩터링: 값을 참조로 바꾸기</p>
</blockquote>
<h3 id="배경-3">배경</h3>
<p>객체(데이터 구조)를 다른 객체(데이터 구조)에 중첩하면 내부 객체를 참조 혹은 값으로 취급할 수 있다.
→ 불변을 원한다면 값으로 취급하는게 맞다.</p>
<p>그러나 이러한 리팩터링은 특정 객체를 여러 객체에서 공유하고자 한다면, 참조로 다뤄야하기 때문에 맞지 않다.</p>
<h3 id="절차-3">절차</h3>
<ol>
<li>후보 클래스가 불변인지, 혹은 불변이 될 수 있는지 확인한다.</li>
<li>각각의 세터를 하나씩 제거한다.</li>
<li>이 값 객체의 필드들을 사용하는 동치성 비교 메서드를 만든다.</li>
</ol>
<h3 id="예시-3">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }

  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  set officeAreaCode(arg) {this._telephoneNumber.areaCode = arg;}
  get officeNumber() {return this._telephoneNumber.number;}
  set officeNumber(arg) {this._telephoneNumber.number = arg;}
}

class TelephoneNumber {
  get areaCode() {return this._areaCode;}
  set areaCode(arg) {this._areaCode = arg;}
  get number() {return this._number;}
  set number(arg) {this._number = arg;}
}</code></pre>
<p><code>리팩터링 후</code></p>
<p>전화번호를 불변으로 만들고, 필드들의 세터를 제거하자.</p>
<pre><code class="language-js">class Person {
  constructor() {
    this._telephoneNumber = new TelephoneNumber();
  }

  get officeAreaCode() {return this._telephoneNumber.areaCode;}
  set officeAreaCode(arg) {this._telephoneNumber = new TelephoneNumber(arg, this.officeNumber);}
  get officeNumber() {return this._telephoneNumber.number;}
  set officeNumber(arg) {this._telephoneNumber = new TelephoneNumber(this.officeAreaCode, arg);}
}

class TelephoneNumber {
  constructor(areaCode, number) {
    this._areaCode = areaCode;
    this._number = number;
  }

  get areaCode() {return this._areaCode;}
  set areaCode(arg) {this._areaCode = arg;}
  get number() {return this._number;}
  set number(arg) {this._number = arg;}
}</code></pre>
<blockquote>
<p>이후 동치성을 값 기반으로 평가하여 동치성 검사를 수행하자.</p>
</blockquote>
<h2 id="값을-참조로-바꾸기">값을 참조로 바꾸기</h2>
<blockquote>
<p>반대 리팩터링: 참조를 값으로 바꾸기</p>
</blockquote>
<h3 id="배경-4">배경</h3>
<p>논리적으로 같은 데이터를 물리적으로 복제해 사용할 때 가장 크게 문제되는 상황은 <strong>데이터를 갱신</strong>해야 할 때다.
→ 이런 상황이라면 복제된 데이터들을 모두 참조로 바꿔주는게 좋다.</p>
<h3 id="절차-4">절차</h3>
<ol>
<li>같은 부류에 속하는 객체들을 보관할 저장소를 만든다.</li>
<li>생성자에서 이 부류의 객체들 중 특정 객체를 정확히 찾아내는 방법이 있는지 확인한다.</li>
<li>호스트 객체의 생성자들을 수정하여 필요한 객체를 이 저장소에서 찾도록 한다. 하나 수정할 때마다 테스트한다.</li>
</ol>
<h3 id="예시-4">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">class Order {
  constructor(data) {
    this._number = data.number;
    this._customer = new Customer(data.customer);
  }

  get customer() {return this._customer;}
}

class Customer {
  constructor(id){
    this._id = id;
  }

  get id() {return this._id;}
}</code></pre>
<blockquote>
<p>현재는 고객이 값이고, 주문 다섯 개를 생성한다면 독립된 고객 객체가 다섯 개 만들어진다. 이를 참조로 바꾸어 물리적으로 똑같은 고객 객체를 사용하도록 만들어보자.</p>
</blockquote>
<p><code>리팩터링 후</code></p>
<p>저자는 저장소 객체를 사용하여 리팩터링함</p>
<pre><code class="language-js">let _repositoryData;

export function initialize() {
  _repositoryData = {};
  _repositoryData.customers = new Map();
}

export function registerCustomer() {
  if(!_repositoryData.customers.has(id)) _repositoryData.customers.set(id, new Customer(id));
  return findCustomer(id);
}

export function findCustomer(id) {
  return _repositoryData.customers.get(id);
}

class Order {
  constructor(data) {
    this._number = data.number;
    this._customer = registerCustomer(data.customer);
    // 다른 데이터를 읽어 들인다.
  }

  get customer() {return this._customer}
}</code></pre>
<blockquote>
<p>위 repository는 ID 하나당 오직 고객 객체만 생성됨을 보장한다. 이를 통해 같은 고객을 공유하는 주문 모두에서 갱신된 데이터를 사용하게 된다.</p>
</blockquote>
<h2 id="매직-리터럴-바꾸기">매직 리터럴 바꾸기</h2>
<h3 id="배경-5">배경</h3>
<p>매직 리터럴 = 소스 코드에 등장하는 일반적인 리터럴 값</p>
<p>이러한 리터럴 값은 코드를 읽는사람이 의미를 모르는경우 가독성을 방해하며 코드 자체의 뜻을 분명하게 드러내는 게 좋다.</p>
<h3 id="절차-5">절차</h3>
<ol>
<li>상수를 선언하고 매직 리터럴을 대입한다.</li>
<li>해당 리터럴이 사용되는 곳을 찾는다.</li>
<li>찾은 곳 각각에서 리터럴이 새 상수와 똑같은 의미로 쓰였는지 확인하여, 같은 의미라면 상수로 대체한 후 테스트한다.</li>
</ol>
<h3 id="예시-5">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function potentialEnergy(mass, height) {
  return mass * 9.81 * height;
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">const STANDARD_GRAVITY = 9.81;
function potentialEnergy(mass, height) {
  return mass * STANDARD_GRAVITY * height;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리팩터링 2판] - 기능 이동]]></title>
            <link>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EA%B8%B0%EB%8A%A5-%EC%9D%B4%EB%8F%99</link>
            <guid>https://velog.io/@hustle-dev/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EA%B8%B0%EB%8A%A5-%EC%9D%B4%EB%8F%99</guid>
            <pubDate>Thu, 01 Sep 2022 17:17:50 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="http://www.yes24.com/Product/Goods/89649360">리팩터링 2판</a>의 Chatper 08를 보고 정리한 글입니다.</p>
</blockquote>
<p><strong>요소를 다른 컨텍스트로 옮기는 일 역시 리팩터링의 중요한 축</strong>이며 이에 대한 리팩터링 기법들을 알아보자.</p>
<h2 id="함수-옮기기">함수 옮기기</h2>
<h3 id="배경">배경</h3>
<p>좋은 소프트웨어 설계의 핵심은 <strong>모듈화가 얼마나 잘 되어 있느냐를 뜻하는 모듈성</strong>이다. 모듈성이란 <strong>프로그램의 어딘가를 수정하려 할 때 해당 기능과 깊이 관련된 작은 일부만 이해해도 가능하게 해주는 능력</strong>이다. </p>
<blockquote>
<p>이러한 모듈성은 <strong>프로그램의 이해도</strong>에 따라 구체적인 방법이 달라진다. 
ex) 함수를 어디(어떤 맥락에)에 둘지 결정하는 것은 사람마다 다르다. (이러한 것들은 많이 해보면서 적절한 위치를 찾게된다.)</p>
</blockquote>
<h3 id="절차">절차</h3>
<ol>
<li>선택한 함수가 현재 컨텍스트에서 사용 중인 모든 프로그램 요소를 살펴본다. 이 요소들 중에도 함께 옮겨야 할 게 있는지 고민해본다.</li>
<li>선택한 함수가 다형 메서드인지 확인한다.</li>
<li>선택한 함수를 타깃 컨텍스트로 복사한다. 타깃 함수가 새로운 터전에 잘 자리 잡도록 다듬는다.</li>
<li>정적 분석을 수행한다.</li>
<li>소스 컨텍스트에서 타깃 함수를 참조할 방법을 찾아 반영한다.</li>
<li>소스 함수를 타깃 함수의 위임 함수가 되도록 수정한다.</li>
<li>테스트한다.</li>
<li>소스 함수를 인라인할지 고민해본다.</li>
</ol>
<h3 id="예시">예시</h3>
<p>책에서는 2개의 예시로 중첩 함수를 최상위로 옮기기, 다른 클래스로 옮기기가 나온다. 예제들이 간단하기 때문에 책 참고.</p>
<h2 id="필드-옮기기">필드 옮기기</h2>
<h3 id="배경-1">배경</h3>
<p>프로그램의 진짜 힘은 데이터 구조에서 나온다. 
→ 주어진 문제에 적합한 데이터 구조를 활용하면 코드는 자연스럽게 단순하고 직관적으로 짜여지지만, 그렇지 않으면 데이터를 다루기 위한 코드로 범벅이 되기 때문</p>
<blockquote>
<p>구조체 여러 개에 정의된 똑같은 필드들을 갱신해야 한다면 한 번만 갱신해도 되는 다른 위치로 옮기라는 신호다 → 필드 옮기기 리팩터링 적용 필요</p>
</blockquote>
<h3 id="절차-1">절차</h3>
<ol>
<li>소스 필드가 캡슐화되어 있지 않다면 캡슐화한다.</li>
<li>테스트한다.</li>
<li>타깃 객체에 필드(와 접근자 메서드들)를 생성한다.</li>
<li>정적 검사를 수행한다.</li>
<li>소스 객체에서 타깃 객체를 참조할 수 있는지 확인한다.</li>
<li>접근자들이 타깃 필드를 사용하도록 수정한다.</li>
<li>테스트한다.</li>
<li>소스 필드를 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-1">예시</h3>
<p><code>리팩터링 전의 코드</code></p>
<pre><code class="language-js">class Customer {
  constructor(name, discountRate) {
    this._name = name;
    this._discountRate = discountRate;
    this._contract = new CustomerContract(dateToday());
  }

  get discountRate() { return this._discountRate;}
  becomePreferred() {
    this._discountRate += 0.3;
    // other things...
  }
  applyDiscount(amount) {
    return amount.subtract(amount.multiply(this._discountRate));
  }
}

class CustomerContract {
  constructor(startDate) {
    this._startDate = startDate;
  }
}</code></pre>
<p><code>리팩터링 이후 코드</code></p>
<ol>
<li>가장 먼저 할 일: 필드를 캡슐화하기(변수 캡슐화 하기)</li>
</ol>
<pre><code class="language-js">class Customer {
  constructor(name, discountRate) {
    this._name = name;
    this._setDiscountRate(discountRate);
    this._contract = new CustomerContract(dateToday());
  }

  get discountRate() { return this._discountRate;}
  _setDiscountRate(aNumber) {this._discountRate = aNumber;}
  becomePreferred() {
    this._setDiscountRate(this.discountRate + 0.03);
    // other things...
  }
  applyDiscount(amount) {
    return amount.subtract(amount.multiply(this.discountRate));
  }
}</code></pre>
<ol start="3">
<li>CustomerContract 클래스에 필드 하나와 접근자들을 추가한다.</li>
</ol>
<pre><code class="language-js">class CustomerContract {
  constructor(startDate, discountRate) {
    this._startDate = startDate;
    this._discountRate = discountRate;
  }

  get discountRate() { return this._discountRate; }
  set discountRate(arg) { this._discountRate = arg; }
}</code></pre>
<ol start="6">
<li>Customer의 접근자들이 새로운 필드를 사용하도록 수정한다.</li>
</ol>
<pre><code class="language-js">class Customer {
  constructor(name, discountRate) {
    this._name = name;
    this._contract = new CustomerContract(dateToday());
    this._setDiscountRate(discountRate);
  }

  get discountRate() { return this._contract.discountRate;}
  _setDiscountRate(aNumber) {this._contract.discountRate = aNumber;}
}</code></pre>
<p>두 번쨰 예제로 공유 객체로 이동하는 리팩터링이 등장하는데 이러한 <strong>리팩터링의 경우 겉보기 동작이 달라지는지 잘 확인하고 리팩터링을 해야함에 유의</strong>하자. 예시는 간단하여 따로 첨부 X</p>
<h2 id="문장을-함수로-옮기기">문장을 함수로 옮기기</h2>
<blockquote>
<p>반대 리팩터링: 문장을 호출한 곳으로 옮기기</p>
</blockquote>
<h3 id="배경-2">배경</h3>
<p>특정 함수를 호출하는 코드가 나올 때마다 그 앞이나 뒤에 똑같은 코드가 추가로 실행된다면, 반복되는 부분을 피호출 함수로 합쳐보자. </p>
<blockquote>
<p>반복되는 부분에서 무언가 수정할 일이 생겼을 때 단 한곳만 수정하면 된다.</p>
</blockquote>
<h3 id="절차-2">절차</h3>
<ol>
<li>반복 코드가 함수 호출 부분과 멀리 떨어져 있따면 문장 슬라이드하기를 적용해 근처로 옮긴다.</li>
<li>타깃 함수를 호출하는 곳이 한 곳뿐이면, 단순히 소스 위치에서 해당 코드를 잘라내어 피호출 함수로 복사하고 테스트한다. 이 경우라면 나머지 단계는 무시한다.</li>
<li>호출자가 둘 이상이면 호출자 중 하나에서 &#39;타깃 함수 호출 부분과 그 함수로 옮기려는 문장들을 함께&#39; 다른 함수로 추출한다. 추출한 함수에 기억하기 쉬운 임시 이름을 지어준다.</li>
<li>다른 호출자 모두가 방금 추출한 함수를 사용하도록 수정한다. 하나씩 수정할 때마다 테스트한다.</li>
<li>모든 호출자가 새로운 함수를 사용하게 되면 원래 함수를 새로운 함수 안으로 인라인한 후 원래 함수를 제거한다.</li>
<li>새로운 함수의 이름을 원래 함수의 이름으로 바꿔준다.</li>
</ol>
<h3 id="예시-2">예시</h3>
<p>예시에서는 절차의 과정에 따라 변하는 과정을 보여주는데, 간단하여 리팩터링 전과 후의 코드를 보자.</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function renderPerson(outStream, person) {
  const result = [];
  result.push(`&lt;p&gt;${person.name}&lt;/p&gt;`);
  result.push(renderPhoto(person.photo));
  result.push(`&lt;p&gt;제목: ${person.photo.title}&lt;/p&gt;`);
  result.push(emitPhotoData(person.photo));
  return result.join(&#39;\n&#39;);
}

function photoDiv(p) {
  return [&#39;&lt;div&gt;&#39;, `&lt;p&gt;제목: ${p.title}&lt;/p&gt;`, emitPhotoData(p), &#39;&lt;/div&gt;&#39;].join(&#39;\n&#39;);
}

function emitPhotoData(aPhoto) {
  const result = [];
  result.push(`&lt;p&gt;위치: ${aPhoto.location}&lt;/p&gt;`);
  result.push(`&lt;p&gt;날짜: ${aPhoto.date.toDateString()}&lt;/p&gt;`);
  return result.join(&#39;\n&#39;);
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function renderPerson(outStream, person) {
  const result = [];
  result.push(`&lt;p&gt;${person.name}&lt;/p&gt;`);
  result.push(renderPhoto(person.photo));
  result.push(emitPhotoData(person.photo));
  return result.join(&#39;\n&#39;);
}

function photoDiv(p) {
  return [&#39;&lt;div&gt;&#39;, emitPhotoData(p), &#39;&lt;/div&gt;&#39;].join(&#39;\n&#39;);
}

function emitPhotoData(aPhoto) {
  return [
    `&lt;p&gt;제목: ${person.photo.title}&lt;/p&gt;`, // 안으로 가져옴
    `&lt;p&gt;위치: ${aPhoto.location}&lt;/p&gt;`,
    `&lt;p&gt;날짜: ${aPhoto.date.toDateString()}&lt;/p&gt;`,
  ].join(&#39;\n&#39;);
}</code></pre>
<h2 id="문장을-호출한-곳으로-옮기기">문장을 호출한 곳으로 옮기기</h2>
<blockquote>
<p>반대 리팩터링: 문장을 함수로 옮기기</p>
</blockquote>
<h3 id="배경-3">배경</h3>
<p>코드베이스의 기능 범위가 달라지면 추상화의 경계도 움직이게 된다.</p>
<blockquote>
<p>이러한 경우 호출자와 호출 대상의 경계를 완전히 다시 정하고, 새로운 추상화를 위해 문장을 호출한 곳으로 리팩터링을 진행해보자.</p>
</blockquote>
<h3 id="절차-3">절차</h3>
<ol>
<li>호출자가 한두 개 뿐이고 피호출 함수도 간단한 단순한 상황이면, 피호출 함수의 처음줄을 잘라내어 호출자로 복사해 넣는다. 테스트만 통과하면 이번 리팩터링은 여기서 끝이다.</li>
<li>더 복잡한 상황에서는, 이동하지 &#39;않길&#39; 원하는 모든 문장을 함수로 추출한 다음 검색하기 쉬운 임시 이름을 지어준다.</li>
<li>원래 함수를 인라인한다.</li>
<li>추출된 함수의 이름을 원래 함수의 이름으로 변경한다.</li>
</ol>
<h3 id="예시-3">예시</h3>
<p>listRecentPhotos()가 위치 정보를 다르게 렌더링하도록 만들어야 한다고 가정해보자.</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function renderPerson(outStream, person) {
  outStream.write(`&lt;p&gt;${person.name}&lt;/p&gt;\n`);
  renderPhoto(outStream, person.photo);
  emitPhotoData(outStream, person.photo);
}

function listRecentPhotos(outStream, photos) {
  photos.filter(p =&gt; p.date &gt; recentDateCutoff()).forEach(p =&gt; {
    outStream.write(&quot;&lt;div&gt;\n&quot;);
    emitPhotoData(outStream, p);
    outStream.write(&quot;&lt;div&gt;\n&quot;);
  })
}

function emitPhotoData(outStream, photo) {
  outStream.write(`&lt;p&gt;제목: ${photo.title}&lt;/p&gt;\n`);
  outStream.write(`&lt;p&gt;날짜: ${photo.date.toDateString()}&lt;/p&gt;\n`);
  outStream.write(`&lt;p&gt;위치: ${photo.location}&lt;/p&gt;\n`);
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function renderPerson(outStream, person) {
  outStream.write(`&lt;p&gt;${person.name}&lt;/p&gt;\n`);
  renderPhoto(outStream, person.photo);
  emitPhotoData(outStream, person.photo);
  outStream.write(`&lt;p&gt;위치: ${person.photo.location}&lt;/p&gt;\n`); // 이동한 부분
}

function listRecentPhotos(outStream, photos) {
  photos.filter(p =&gt; p.date &gt; recentDateCutoff()).forEach(p =&gt; {
    outStream.write(&quot;&lt;div&gt;\n&quot;);
    emitPhotoData(outStream, p);
    outStream.write(`&lt;p&gt;위치: ${p.location}&lt;/p&gt;\n`); // 이동한 부분
    outStream.write(&quot;&lt;div&gt;\n&quot;);
  })
}

function emitPhotoData(outStream, photo) {
  outStream.write(`&lt;p&gt;제목: ${photo.title}&lt;/p&gt;\n`);
  outStream.write(`&lt;p&gt;날짜: ${photo.date.toDateString()}&lt;/p&gt;\n`);
}</code></pre>
<h2 id="인라인-코드를-함수-호출로-바꾸기">인라인 코드를 함수 호출로 바꾸기</h2>
<h3 id="배경-4">배경</h3>
<p>함수의 이름이 코드의 동작 방식보다는 목적을 말해주기 때문에 함수를 활용하면 코드를 이해하기가 쉬워진다. 
→ 인라인된 코드보다 제대로 작성된 함수이름을 호출하는 편이 가독성이 좋다는 의미</p>
<h3 id="절차-4">절차</h3>
<ol>
<li>인라인 코드를 함수 호출로 대체한다.</li>
<li>테스트한다. </li>
</ol>
<h3 id="예시-4">예시</h3>
<p>책에 따로 존재 X</p>
<h2 id="문장-슬라이드하기">문장 슬라이드하기</h2>
<h3 id="배경-5">배경</h3>
<p>관련된 코드들이 가까이 모여 있다면 이해하기가 더 쉽다.</p>
<blockquote>
<p>코드를 관련된 것끼리 묶자.</p>
</blockquote>
<h3 id="절차-5">절차</h3>
<ol>
<li>코드 조각을 이동할 목표 위치를 찾는다. 코드 조각의 원래 위치와 목표 위치 사이의 코드들을 훑어보면서, 조각을 모으고 나면 동작이 달라지는 코드가 있는지 살핀다.</li>
<li>코드 조각을 원래 위치에서 잘라내어 목표 위치에 붙여 넣는다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-5">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">const pricingPlan = retrievePricingPlan();
const order = retrieveOrder();
let charge;
const chargePerUnit = pricingPlan.unit();</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">const pricingPlan = retrievePricingPlan();
const chargePerUnit = pricingPlan.unit;
const order = retrieveOrder();
let charge;</code></pre>
<h2 id="반복문-쪼개기">반복문 쪼개기</h2>
<h3 id="배경-6">배경</h3>
<p>하나의 반복문 안에서 두 가지 이상의 일을 수행하는 모습을 볼 수 있다. 그러나 반복문을 수정해야 할 때마다 두 가지 일 모두를 잘 이해하고 진행해야하기 때문에 반복문을 분리해보자.</p>
<blockquote>
<p>반복문 쪼개기를 통해 최적화와 멀어지는 경우도 있지만, 다른 더 강력한 최적화를 적용할 수 있는 길을 열어주기도 한다.</p>
</blockquote>
<h3 id="절차-6">절차</h3>
<ol>
<li>반복문을 복제해 두 개로 만든다.</li>
<li>반복문이 중복되어 생기는 부수효과를 파악해서 제거한다.</li>
<li>테스트한다.</li>
<li>완료됐으면, 각 반복문을 함수로 추출할지 고민해본다.</li>
</ol>
<h3 id="예시-6">예시</h3>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">let youngest = people[0] ? people[0].age : Infinity;
let totalSalary = 0;
for (const p of people) {
  if (p.age &lt; youngest) youngest = p.age;
  totalSalary += p.salary;
}

return `최연소: ${youngest}, 총 급여: ${totalSalary}`;</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">let youngest = people[0] ? people[0].age : Infinity;
let totalSalary = 0;
for (const p of people) {
  totalSalary += p.salary;
}

for (const p of people) {
  if (p.age &lt; youngest) youngest = p.age;

}

return `최연소: ${youngest}, 총 급여: ${totalSalary}`;</code></pre>
<p><code>더 가다듬기 적용(함수 추출후 반복문을 파이프라인과 알고리즘 교체하기 적용)</code></p>
<pre><code class="language-js">function totalSalary() {
  return people.reduce((total, p) =&gt; total + p.salary, 0);
}

function youngestAge() {
  return Math.min(...people.map((p) =&gt; p.age));
}

return `최연소: ${youngestAge()}, 총 급여: ${totalSalary()}`;</code></pre>
<h2 id="반복문을-파이프라인으로-바꾸기">반복문을 파이프라인으로 바꾸기</h2>
<h3 id="배경-7">배경</h3>
<p>파이프라인을 이용하면 처리 과정을 일련의 연산으로 표현할 수 있다.
→ 반복문 대신 map, filter 등을 사용하는 리팩터링</p>
<h3 id="절차-7">절차</h3>
<ol>
<li>반복문에서 사용하는 컬렉션을 가리키는 변수를 하나 만든다.</li>
<li>반복문의 첫 줄부터 시작해서, 각각의 단위 행위를 적절한 컬렉션 파이프라인 연산으로 대체한다. 이때 컬렉션 파이프라인 연산은 1에서 만든 반복문 컬렉션 변수에서 시작하여, 이전 연산의 결과를 기초로 연쇄적으로 수행된다. 하나를 대체할 때마다 테스트한다.</li>
<li>반복문의 모든 동작을 대체했다면 반복문 자체를 지운다.</li>
</ol>
<h3 id="예시-7">예시</h3>
<p>인도에 자리한 사무실을 찾아서 도시명과 전화번호를 반환하는 함수</p>
<p><code>리팩터링 전</code></p>
<pre><code class="language-js">function acquireDate(input) {
  const lines = input.split(&#39;\n&#39;);
  let firstLine = true;
  const result = [];
  for (const line of lines) {
    if (firstLine) {
      firstLine = false;
      continue;
    }
    if (line.trim() === &#39;&#39;) continue;
    const record = line.split(&#39;,&#39;);
    if (record[1].trim() === &#39;india&#39;) {
      result.push({ city: record[0].trim(), phone: record[2].trim() });
    }
  }
  return result;
}</code></pre>
<p><code>리팩터링 후</code></p>
<pre><code class="language-js">function acquireDate(input) {
  const lines = input.split(&#39;\n&#39;);
  const result = lines
    .slice(1)
    .filter((line) =&gt; line.trim() !== &#39;&#39;)
    .map((line) =&gt; line.split(&#39;,&#39;))
    .filter((record) =&gt; record[1].trim() === &#39;india&#39;)
    .map((record) =&gt; ({ city: record[0].trim(), phone: record[2].trim() }));

  return result;
}</code></pre>
<h2 id="죽은-코드-제거하기">죽은 코드 제거하기</h2>
<h3 id="배경-8">배경</h3>
<p>사용되지 않는 코드가 있다면 다른 누군가가 그 코드를 보았을 때, 무슨 의도로 그 코드가 작성되었는지 헷갈리기 때문에 지워버리자.</p>
<h3 id="절차-8">절차</h3>
<ol>
<li>죽은 코드를 외부에서 참조할 수 있는 경우라면 혹시라도 호출하는 곳이 있는지 확인한다.</li>
<li>없다면 죽은 코드를 제거한다.</li>
<li>테스트한다.</li>
</ol>
<h3 id="예시-8">예시</h3>
<p>따로 존재 X</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바스크립트 완벽가이드] - 노드와 서버 사이드 자바스크립트]]></title>
            <link>https://velog.io/@hustle-dev/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C-%EB%85%B8%EB%93%9C%EC%99%80-%EC%84%9C%EB%B2%84-%EC%82%AC%EC%9D%B4%EB%93%9C-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@hustle-dev/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%99%84%EB%B2%BD%EA%B0%80%EC%9D%B4%EB%93%9C-%EB%85%B8%EB%93%9C%EC%99%80-%EC%84%9C%EB%B2%84-%EC%82%AC%EC%9D%B4%EB%93%9C-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Wed, 31 Aug 2022 11:18:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>자바스크립트 완벽가이드 16장에 해당하는 부분이고, 읽으면서 자바스크립트에 대해 새롭게 알게된 부분만 정리한 내용입니다.</p>
</blockquote>
<p>노드는 운영 체제와 연결된 자바스크립트이며 자바스크립트 프로그램에서 <strong>파일을 읽고 쓰고, 자식 프로세스를 실행하고, 네트워크를 통해 통신</strong>할 수 있게 한다.</p>
<p>노드의 용도</p>
<ul>
<li>배시나 기타 유닉스 셸의 복잡한 문법에서 해방된 최신 셸 스크립트</li>
<li>신뢰할 수 없는 코드를 다루는 웹 브라우저에 적용되는 보안 제한에서 벗어나, 신뢰할 수 있는 프로그램을 실행하는 범용 프로그래밍 언어</li>
<li>효율적이고 병렬화된 웹 서버 환경</li>
</ul>
<blockquote>
<p>노드의 가장 분명한 특징은 <strong>비동기적인 API를 바탕으로 한, 싱글 스레드의 이벤트 기반 병렬 환경</strong>이라는 것이다.</p>
</blockquote>
<p>이 장에서 다루는 것</p>
<ul>
<li>노드 프로그래밍 모델</li>
<li>노드의 동시성</li>
<li>스트리밍 데이터를 다루는 API</li>
<li>이진 데이터를 다루는 버퍼 타입</li>
<li>파일과 네트워크, 프로세스, 스레드를 다루는 몇 가지 가장 중요한 노드 API</li>
</ul>
<h2 id="노드-프로그래밍-기본">노드 프로그래밍 기본</h2>
<h3 id="콘솔-출력">콘솔 출력</h3>
<p>웹 브라우저의 console.log(), console.warn(), console.error()는 일반적으로 그 메시지 종류를 나타내는 작은 아이콘을 표시한다. 그러나 노드에는 이런 기능이 없고, <strong>console.error()는 표준 에러(stderr) 스트림에 기록한다는 점</strong>이 console.log()와 다르다. 프로그램에서 표준 출력을 파일이나 파이프에 리다이렉트한다면 console.log()의 텍스트는 사용자에게 보이지 않고 console.error()의 텍스트만 콘솔에 출력된다.</p>
<h3 id="명령행-인자와-환경-변수">명령행 인자와 환경 변수</h3>
<p>노드 프로그램은 문자열 배열인 process.argv에서 명령행 인자를 읽을 수 있다. 첫 번째 요소는 항상 노드 실행 파일의 경로이며, 두 번째는 노드가 실행하고 있는 자바스크립트 코드 파일 경로이다.</p>
<p><img src="https://velog.velcdn.com/images/hustle-dev/post/cfc90397-aabb-4927-a33c-a4a61e44fc9d/image.png" alt=""></p>
<blockquote>
<p>첫 번째와 두 번째는 항상 노드 실행 파일과 자바스크립트 파일의 절대 경로이다. 또한 --trace-uncaught와 같이 노드 실행 파일 자체에서 사용하는 명령행 인자는 process.env에 포함되지 않는다.</p>
</blockquote>
<h3 id="프로그램-수명">프로그램 수명</h3>
<p>노드 프로그램은 대부분 비동기적이며 콜백과 이벤트 핸들러를 바탕으로 동작한다. 노드 프로그램은 초기 파일을 실행하고 콜백을 모두 호출한 뒤 남은 이벤트가 없을 때까지 종료되지 않는다.</p>
<blockquote>
<p>네트워크 연결을 주시하는 노드 기반 서버 프로그램은 항상 이벤트를 기다리고 있으므로 이론적으로 영원히 실행된다.</p>
</blockquote>
<p>프로그램에서 예외가 발생했을 때, 전역 핸들러 함수를 등록해 프로그램이 충돌하는 일을 막을 수 있다.</p>
<pre><code class="language-js">process.setUncaughtExceptionCaptureCallback((e) =&gt; {
  console.error(&#39;Uncaught exception:&#39;, e);
});</code></pre>
<p>유사하게 프라미스가 미처리 상태로 거부되어 에러 메시지를 출력하거나 프로그램을 종료하는 일을 막으려면 다음과 같이 전역 핸들러 함수를 등록하라.</p>
<pre><code class="language-js">process.on(&#39;unhandledRejection&#39;, (reason, promise) =&gt; {
    // reason은 .catch() 함수에 전달된 값
    // promise는 거부된 프라미스 객체
})</code></pre>
<h2 id="노드는-기본적으로-비동기적입니다">노드는 기본적으로 비동기적입니다</h2>
<p>노드는 네트워크 서버처럼 입출력 집약적인 프로그램에 맞게 설계되고 최적화되었다. 특히 최대한 많은 요청을 동시에 처리할 수 있는 서버를 쉽게 만들 수 있도록 설계되었다.</p>
<p>노드는 기본적으로 비동기, 비차단 방식 API를 채택하여 싱글스레드 프로그램이 모델을 유지하면서도 높은 수준의 동시성을 구현한다. 
→ 네트워크 연결을 초기화하거나 파일의 마지막 변경 시간을 구하는 함수조차도 기본 함수는 비차단 방식으로 설계</p>
<p>노드는 프라미스 클래스를 도입하기 전에 만들어졌으므로 비동기 API는 콜백 기반이다. </p>
<blockquote>
<p><strong>오류 우선 콜백</strong>을 사용!
→ 에러 인자를 첫 번째로 사용하는 이유는 이를 생략할 수 없게 강제하기 위해서</p>
</blockquote>
<pre><code class="language-js">const fs = require(&#39;fs&#39;);

function readConfigFile(path, callback) {
  fs.readFile(path, &#39;utf8&#39;, (err, text) =&gt; {
    if (err) {
      console.error(err);
      callback(null);
      return;
    }
    let data = null;
    try {
      data = JSON.parse(text);
    } catch (e) {
      console.error(e);
    }
    callback(data);
  });
}</code></pre>
<h2 id="버퍼">버퍼</h2>
<p>버퍼 클래스는 파일이나 네트워크에서 데이터를 읽을 때 자주 사용한다. 버퍼가 Uint8Array와 다른 점은 자바스크립트 문자열과 상호 운용되도록 설계됐다는 점이다. 정상적으로 인코드된 바이트 시퀀스와 문자 인코딩이 주어지면 그 바이트를 문자 시퀀스로 디코드할 수 있다.</p>
<p>지원되는 인코딩 목록</p>
<ul>
<li>&#39;utf8&#39;: 인코딩을 지정하지 않았을 때 사용하는 기본 값</li>
<li>&#39;utf16le&#39;: 리틀 엔디안 순서를 사용하는 2바이트 유니코드 문자</li>
<li>&#39;latin1&#39;: 문자당 1바이트를 사용하는 ISO-8859-1 인코딩</li>
<li>&#39;ascii&#39;: 영어로만 이루어진 7비트 ASCII 인코딩</li>
<li>&#39;hex&#39;: 각 바이트를 ASCII 16진수 숫자 쌍으로 변환</li>
<li>&#39;base64&#39;: 3바이트 시퀀스를 ASCII 문자 네 개로 변환</li>
</ul>
<blockquote>
<p>실제로 이진 데이터를 조작하는 노드 프로그램을 만든다면 버퍼 클래스를 아주 많이 사용하게 될 것이다.</p>
</blockquote>
<h2 id="이벤트와-이벤트-이미터">이벤트와 이벤트 이미터</h2>
<p>복잡한 API 중에는 이벤트 기반 API도 있다. API가 객체 위주로 설계돼썩나, 콜백 함수가 여러 번 호출되어야 하거나, 콜백 함수가 여러 가지 필요한 경우가 이에 해당한다.</p>
<p>예시: net.Server 클래스</p>
<p>이 클래스는 연결을 청므으로 주시하기 시작할 때 listening 이벤트를, 클라이언트가 연결할때마다 connection 이벤트를, 연결이 끊길 때 close 이벤트를 방출한다.</p>
<pre><code class="language-js">const EventEmitter = require(&#39;events&#39;);
const net = require(&#39;net&#39;);
const server = new net.Server();
server instanceof EventEmitter; // true</code></pre>
<blockquote>
<p>자세한 내용은 책 참조.</p>
</blockquote>
<h2 id="스트림">스트림</h2>
<p>파일을 복사하는 함수같은 경우 다음과 같이 작성하게 되면 문제가 생길 수 있다.</p>
<pre><code class="language-js">// 비동기적이지만 스트림을 사용하지 않는(그래서 비효율적인) 함수
function copyFile(sourceFilename, destinationFilename, callback) {
  fs.readFile(sourceFilename, (err, buffer) =&gt; {
    if (err) {
      callback(err);
    } else {
      fs.writeFile(destinationFilename, buffer, callback);
    }
  });
}</code></pre>
<blockquote>
<p>복사할 파일이 아주 크거나 동시에 여러 파일을 복사한다면 실패할 수 있다.(메모리 문제) 또한 파일을 다 읽기 전에는 사본 파일의 기록을 시작할 수 없다.</p>
</blockquote>
<p>이러한 문제의 해결책은 <strong>스트리밍 알고리즘</strong>이다. 이 알고리즘은 데이터를 항상 작은 덩어리로 처리하며 데이터 전체를 한 번에 메모리에 담지 않는다는 것이다. 스트리밍 솔루션은 항상 메모리 효율적인 데다 속도도 더 빠를 것이다.</p>
<p>노드의 4가지 스트리밍 타입</p>
<ul>
<li>리더블(데이터 소스)</li>
<li>라이터블(데이터가 향하는 대상)</li>
<li>듀플렉스(리더블 스티림과 라이터블 스트림을 객체 하나에 조합)</li>
<li>트랜스폼(듀플렉스 슽릠과의 차이로, 트랜스폼 스트림에 출력된 데이터는 같은 스트림에서 읽을 수 있다.)</li>
</ul>
<p>이어지는 아래 내용은 노드의 스트림 클래스에서 데이터를 읽고 쓰는 방법을 설명하기 위해 다음의 내용들을 다룬다.</p>
<ul>
<li>파이프(데이터를 다른 스트림에 쓰는 단순한 작업)</li>
<li>비동기 순회(리더블 스트림은 비동기 이터레이터이므로 async 함수 안에서 for/await 루프와 함께 사용하여 순회)</li>
<li>스트림 기록과 배압 처리(write 메서드를 설명하며 boolean 반환 값(false)에 따른 배압(경고) 설명)</li>
<li>이벤트로 스트림 읽기<ul>
<li>플로모드: 읽을 수 있는 데이터가 도착하는 즉시 data 이벤트 형태로 방사</li>
<li>일시정지모드: 스트림은 기본적으로 이 모드로 시작하며, read() 메서드를 직접 호출해 스트림에서 데이터를 가져와야함.</li>
</ul>
</li>
</ul>
<h2 id="프로세스-cpu-운영-체제-세부-사항">프로세스, CPU, 운영 체제 세부 사항</h2>
<p>알아 두어야 할 중요한 프로퍼티와 함수들</p>
<pre><code class="language-js">process.argv; // 명령행 인자 배열
process.arch; // x64 같은 CPU 아키텍처
process.cwd(); // 현재 작업 디렉터리
process.chdir(); // 현재 작업 디렉터리를 변경
process.cpuUsage(); // CPU 사용 현황을 보고
process.env; // 환경 변수 객체
process.execPath; // 노드 실행 파일의 파일시스템 절대 경로
process.exit(); // 프로그램 종료
process.exitCode; // 프로그램 종료할 때 보고할 정수 코드
process.getuid(); // 현재 사용자의 유닉스 사용자 ID
process.hrtime.bigint(); // 정밀한 나노초 타임스탬프
process.kill(); // 다른 프로세스에 신호를 보냄
process.memoryUsage(); // 메모리 사용현황을 객체로 반환
process.nextTick(); // setImmediate()와 마찬가지로 함수를 곧 호출
process.pid; // 현재 프로세스의 프로세스 ID
process.ppid; // 부모 프로세스의 ID
process.platform; // 운영 체제. linux, darwin, win32등
process.resourceUsage(); // 자원 사용 현황을 객체로 반환
process.setuid(); // ID나 이름으로 현재 사용자를 변경
process.title; // ps 리스트에 나타나는 프로세스 이름
process.umask(); // 새로운 파일에 대한 기본 퍼미션을 설정하거나 반환
process.uptime(); // 노드 실행 시간을 초 단위로 반환
process.version; // 노드 버전 문자열
process.versions; // 노드가 사용하는 라이브러리의 버전 문자열</code></pre>
<h2 id="http-클라이언트와-서버">HTTP 클라이언트와 서버</h2>
<p>노드에 있는 http, https, http2 모듈은 HTTP 프로토콜의 모든 기능을 갖추긴 했지만 비교적 저수준으로 구현되었다. </p>
<p>책안에 예제는 http 모듈을 사용하여 기본적인 클라이언트와 서버를 만드는 방법에 대해서 설명하는데 일반적으로 아주 기본적인 서버를 제외하면 Express 프레임워크 같은 외부 라이브러리를 사용해 만든다.</p>
<h2 id="http를-사용하지-않는-네트워크-서버와-클라이언트">HTTP를 사용하지 않는 네트워크 서버와 클라이언트</h2>
<p>HTTP이외의 <strong>다른 형태의 네트워크 서버와 클라이언트를 노드에서 충분히 지원</strong>할 수 있다.</p>
<blockquote>
<p>노드의 net 모듈 사용!(서버와 소켓 클래스가 정의되어 있음)</p>
</blockquote>
<p>노드의 net 모듈은 TCP 기반 서버 이외에도, 포트 번호가 아니라 파일시스템 경로로 식별되는 &#39;유닉스 도메인 소켓&#39;의 프로세스 간 통신도 지원한다.</p>
<p>그 외에도 노드는 UDP 기반 클라이언트와 서버를 지원하는 dgram 모듈, net 모듈에 보안을 추가한 tls 모듈 역시 지원한다.</p>
<blockquote>
<p>tls.Server, tls.TLSSocket과 같은 클래스를 사용하여 SSL 암호화를 사용하게 만들 수 있음</p>
</blockquote>
<h2 id="자식-프로세스">자식 프로세스</h2>
<p>자식 프로세스는 <strong>노드에서 다른 프로그램을 실행하는 스크립트를 만드는데 사용</strong>된다.</p>
<h3 id="execsync와-execfilesync">execSync()와 execFileSync()</h3>
<p>셸을 새로 생성하여 기동하는 형태</p>
<p>child_process.execSync()
→ 자식 프로세스를 생성하고 그 프로세스 안에서 셸을 실행하며, 셸 안에서 전달받은 명령어를 실행한다. 그리고 명령어와 셸이 종료될 때까지 다른 프로세스를 차단한다.</p>
<p>셸의 기능이 필요하지 않을때는 child_process.execFileSync()를 써도 됨</p>
<h3 id="exec와-execfile">exec()와 execFile()</h3>
<p>위 메서드들과 달리 이 메서드들은 비동기 함수여서 동시에 여러 개를 실행할 수 있다.</p>
<h3 id="spawn">spawn()</h3>
<p>자식 프로세스가 실행 중일 때도 그 출력에 스트림으로 접근할 수 있는 함수이다. 또한 자식 프로세스에 데이터를 보낼 수 있고, 자식 프로세스는 표준 입력 스트림을 통해 이 데이터를 받는다.</p>
<blockquote>
<p>자식 프로세스와 동적으로 상호작용하면서 자식 프로세스의 출력에 따라 입력을 보낼 수 있다. (셸 사용 X)</p>
</blockquote>
<h3 id="fork">fork()</h3>
<p>이 메서드는 자식인 노드 프로세스에서 자바스크립트 코드 모듈을 실행하는 데 특화된 함수이다.</p>
<blockquote>
<p>send() 메서드와 message 이벤트 주시를 통해 메시지를 주고받을 수 있다.</p>
</blockquote>
<h2 id="워커-스레드">워커 스레드</h2>
<p>JS 스레드는 노드나 브라우저 모두 기본적으로 메모리를 공유하지 않으므로, 멀티스레드 프로그래밍의 위험성이나 어려움은 자바스크립트 &#39;워커&#39;에는 적용되지 않는다.</p>
<p>노드에서 워커를 사용하는 이유 3가지</p>
<ul>
<li>CPU 코어 하나로 감당하기 어려운 계산할 시</li>
<li>메인 스레드의 반응성을 더 높이기 위해</li>
<li>동기적인 동작이 비차단적이고 비동기적인 작업으로 바꾸고 싶을 때</li>
</ul>
<blockquote>
<p>워커 스레드는 자식 프로세스 만큼 부하가 크지는 않지만 그래도 가벼운 작업은 아니다. 워커에 맡길 일이 무거운 작업이 아니라면 굳이 워커를 사용할 필요는 없다.</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>