<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>For Remind.io</title>
        <link>https://velog.io/</link>
        <description>함께 일하고 싶어지는 동료, 프론트엔드 개발자입니다.</description>
        <lastBuildDate>Wed, 10 Jan 2024 09:21:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>For Remind.io</title>
            <url>https://velog.velcdn.com/images/syo_ee/profile/b936a920-2162-4e2f-b849-11d4b3db181c/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. For Remind.io. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/syo_ee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[third-party cookie]]></title>
            <link>https://velog.io/@syo_ee/third-party-cookie</link>
            <guid>https://velog.io/@syo_ee/third-party-cookie</guid>
            <pubDate>Wed, 10 Jan 2024 09:21:44 GMT</pubDate>
            <description><![CDATA[<h2 id="1-이슈-발생">1. 이슈 발생</h2>
<p><code>Third-party cookie will be blocked. Learn more in the Issues tab.</code></p>
<ul>
<li>프로젝트 진행 중 어느순간 이러한 issue가 발생했다.</li>
</ul>
<h2 id="2-이유">2. 이유</h2>
<ul>
<li>웹 브라우저 중 주로 Chrome을 사용하는데 2024년부터 third-party cookie를 지원하지 않겠다고 발표했어서 이러한 issue가 발생한 것으로 보인다.</li>
</ul>
<h2 id="3-third-party-cookie란">3. third-party cookie란?</h2>
<ul>
<li><p>웹 사이트에 방문할 때 브라우저에 저장되는 작은 텍스트 파일 중에서, 방문한 사이트와 다른 도메인에서 생성된 쿠키를 말한다.</p>
</li>
<li><p>예를 들어, 네이버에 들어가면 광고나 이미지 같은 다른 사이트의 컨텐츠가 보이는데, 이런 컨텐츠가 만든 쿠키가 바로 third-party cookie이다.</p>
</li>
<li><p>웹 사이트의 기능이나 성능을 개선하거나, 사용자의 관심사나 행동을 분석하여 맞춤형 광고를 제공하는데 사용되는데, 이렇게 되면 사용자의 개인정보를 침해하거나, 웹 사이트의 보안을 해칠 수도 있기 때문에 많은 브라우저들이 third-party cookie를 차단하거나 제한하고있다.</p>
</li>
</ul>
<h2 id="4-회고">4. 회고</h2>
<ul>
<li>프로젝트만 진행하느라 third-party cookie는 생각하지도 못했는데, 이 기회로 무엇인지 알게되어서 즐거웠다.</li>
</ul>
<hr/>

<h2 id="참조">참조</h2>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/en-US/blog/goodbye-third-party-cookies/">https://developer.mozilla.org/en-US/blog/goodbye-third-party-cookies/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (8)]]></title>
            <link>https://velog.io/@syo_ee/typescript08</link>
            <guid>https://velog.io/@syo_ee/typescript08</guid>
            <pubDate>Tue, 09 Jan 2024 12:06:45 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-8-제네릭">Chapter 8. 제네릭</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 제네릭의 Hello World (Hello World of Generics)</strong>
<strong>3. 제네릭 타입 변수 작업 (Working with Generic Type Variables)</strong>
<strong>4. 제네릭 타입 (Generic Types)</strong>
<strong>5. 제네릭 클래스 (Generic Classes)</strong>
<strong>6. 제네릭 제약조건 (Generic Constraints)</strong>
<strong>7. 제네릭 제약조건에서 타입 매개변수 사용 (Using Type Parameters in Generic Constraints)</strong>
<strong>8. 제네릭에서 클래스 타입 사용 (Using Class Types in Generics)</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>잘 정의되고 일관된 API뿐만 아닌 재사용 가능한 컴포넌트를 구축하는 것도 소프트웨어 엔지니어링에서의 주요한 부분이다.</p>
</li>
<li><p>현재의 데이터와 미래의 데이터 모두를 다룰 수 있는 컴포넌트는 거대한 소프트웨어 시스템을 구성하는 데 있어 가장 유연한 능력을 제공할 것이다.</p>
</li>
<li><p>C#과 Java 같은 언어에서, 재사용 가능한 컴포넌트를 생성하는 도구상자의 주요 도구 중 하나는 제네릭이다.
<em>즉, 단일 타입이 아닌 다양한 타입에서 작동하는 컴포넌트를 작성할 수 있다.</em></p>
</li>
<li><p>사용자는 제네릭을 통해 여러 타입의 컴포넌트나 자신만의 타입을 사용할 수 있다.</p>
</li>
</ul>
<h3 id="2-제네릭의-hello-world-hello-world-of-generics">2. 제네릭의 Hello World (Hello World of Generics)</h3>
<ul>
<li><p>먼저 제네릭의 &quot;hello world&quot;인 identity 함수를 해보면 identity 함수는 인수로 무엇이 오던 그대로 반환하는 함수이다.</p>
</li>
<li><p><code>echo</code> 명령과 비슷하게 생각할 수 있습니다.</p>
</li>
<li><p>제네릭이 없다면, identity 함수에 특정 타입을 주어야 한다.</p>
</li>
</ul>
<pre><code>function identity(arg: number): number {
    return arg;
}</code></pre><ul>
<li>또는 <code>any</code> 타입을 사용하여 identity 함수를 기술할 수 있다.</li>
</ul>
<pre><code>function identity(arg: any): any {
    return arg;
}</code></pre><ul>
<li><p><code>any</code> 를 쓰는 것은 함수의 <code>arg</code> 가 어떤 타입이든 받을 수 있다는 점에서 제네릭이지만, 실제로 함수가 반환할 때 어떤 타입인지에 대한 정보는 잃게 된다.</p>
</li>
<li><p>만약 number 타입을 넘긴다고 해도 any 타입이 반환된다는 정보만 얻을 뿐이다.</p>
</li>
<li><p>대신에 우리는 무엇이 반환되는지 표시하기 위해 인수의 타입을 캡처할 방법이 필요하다.</p>
</li>
<li><p>여기서는 값이 아닌 타입에 적용되는 <em>타입 변수</em> 를 사용할 것이다.</p>
</li>
</ul>
<pre><code>function identity&lt;T&gt;(arg: T): T {
    return arg;
}</code></pre><ul>
<li><p>identity 함수에 T라는 타입 변수를 추가했다.</p>
</li>
<li><p><code>T</code> 는 유저가 준 인수의 타입을 캡처하고 (예 - <code>number</code> ), 이 정보를 나중에 사용할 수 있게 한다.</p>
</li>
<li><p>여기에서는 <code>T</code> 를 반환 타입으로 다시 사용한다.</p>
</li>
<li><p>인수와 반환 타입이 같은 타입을 사용하고 있는 것을 확인할 수 있다.</p>
</li>
<li><p>이를 통해 타입 정보를 함수의 한쪽에서 다른 한쪽으로 운반할 수 있게끔 한다.</p>
</li>
<li><p>이 버전의 identity 함수는 타입을 불문하고 동작하므로 제네릭이라 할 수 있다.</p>
</li>
<li><p><code>any</code> 를 쓰는 것과는 다르게 인수와 반환 타입에 number를 사용한 첫 번째 identity 함수만큼 정확하다. (즉, 어떤 정보도 잃지 않는다.)</p>
</li>
<li><p>일단 제네릭 identity 함수를 작성하고 나면, 두 가지 방법 중 하나로 호출할 수 있다.</p>
</li>
<li><p>첫 번째 방법은 함수에 타입 인수를 포함한 모든 인수를 전달하는 방법이다.</p>
</li>
</ul>
<pre><code>let output = identity&lt;string&gt;(&quot;myString&quot;); // 출력 타입은 &#39;string&#39;입니다.</code></pre><ul>
<li><p>여기서 우리는 함수를 호출할 때의 인수 중 하나로써 <code>T</code> 를 <code>string</code> 으로 명시해 주고 인수 주변에 <code>()</code> 대신 <code>&lt;&gt;</code> 로 감싸주었다.</p>
</li>
<li><p>두 번째 방법은 아마 가장 일반적인 방법이다.</p>
</li>
<li><p>여기서는 타입 인수 추론 을 사용합니다 <code>--</code> 즉, 우리가 전달하는 인수에 따라서 컴파일러가 <code>T</code> 의 값을 자동으로 정하게 하는 것이다.</p>
</li>
</ul>
<pre><code>let output = identity(&quot;myString&quot;); //출력 타입은 &#39;string&#39;입니다.</code></pre><ul>
<li><p>타입 인수를 꺾쇠괄호(<code>&lt;&gt;</code> )에 담아 명시적으로 전달해 주지 않은 것을 주목하자
컴파일러는 값인 &quot;<code>myString</code> &quot;를 보고 그것의 타입으로 <code>T</code> 를 정한다.</p>
</li>
<li><p>인수 추론은 코드를 간결하고 가독성 있게 하는 데 있어 유용하지만 더 복잡한 예제에서 컴파일러가 타입을 유추할 수 없는 경우엔 명시적인 타입 인수 전달이 필요할 수도 있다.</p>
</li>
</ul>
<h3 id="3-제네릭-타입-변수-작업-working-with-generic-type-variables">3. 제네릭 타입 변수 작업 (Working with Generic Type Variables)</h3>
<ul>
<li><p>제네릭을 사용하기 시작하면, <code>identity</code> 와 같은 제네릭 함수를 만들 때, 컴파일러가 함수 본문에 제네릭 타입화된 매개변수를 쓰도록 강요한다.</p>
</li>
<li><p>즉, 이 매개변수들은 실제로 <code>any</code> 나 모든 타입이 될 수 있는 것처럼 취급할 수 있게 된다.</p>
</li>
</ul>
<pre><code>function identity&lt;T&gt;(arg: T): T {
  return arg;
}</code></pre><ul>
<li>함수 호출 시마다 인수 <code>arg</code> 의 길이를 로그에 찍으려면 어떻게 해야할까?</li>
</ul>
<pre><code>function loggingIdentity&lt;T&gt;(arg: T): T {
  console.log(arg.length); // 오류: T에는 .length 가 없습니다.
  return arg;
}</code></pre><ul>
<li><p>이렇게 하면, 컴파일러는 <code>arg</code> 의 멤버 <code>.length</code> 를 사용하고 있다는 오류를 낼 것이지만, 어떤 곳에서도 <code>arg</code> 가 이 멤버가 있다는 것이 명시되어 있지 않는다.</p>
</li>
<li><p>이전에 이러한 변수 타입은 any나 모든 타입을 의미한다고 했던 것을 기억하자.</p>
</li>
<li><p>따라서 이 함수를 쓰고 있는 사용자는 <code>.length</code> 멤버가 없는 number를 대신 전달할 수도 있다.</p>
</li>
<li><p>실제로 이 함수가 <code>T</code> 가 아닌 <code>T</code> 의 배열에서 동작하도록 의도했다고 가정해보자.</p>
</li>
<li><p>배열로 사용하기 때문에 <code>.length</code> 멤버는 사용 가능하다.</p>
</li>
<li><p>다른 타입들의 배열을 만드는 것처럼 표현할 수 있다.</p>
</li>
</ul>
<pre><code>function loggingIdentity&lt;T&gt;(arg: T[]): T[] {
  console.log(arg.length); // 배열은 .length를 가지고 있습니다. 따라서 오류는 없습니다.
  return arg;
}</code></pre><ul>
<li><p><code>loggingIdentity</code> 의 타입을 &quot;제너릭 함수 <code>loggingIdentity</code> 는 타입 매개변수 <code>T</code> 와 <code>T</code> 배열인 인수 <code>arg</code> 를 취하고 <code>T</code> 배열을 반환한다.&quot;라고 읽을 수 있다.</p>
</li>
<li><p>만약 우리가 <code>number</code> 배열을 넘기면 <code>T</code> 가 <code>number</code> 에 바인딩 되므로 함수는 <code>number</code> 배열을 얻게 된다.</p>
</li>
<li><p>전체 타입변수를 쓰는 것보다 하나의 타입으로써 제네릭 타입변수 <code>T</code> 를 사용하는 것은 굉장한 유연함을 제공한다.</p>
</li>
<li><p>위 예제를 아래처럼 대체할 수 있다.</p>
</li>
</ul>
<pre><code>function loggingIdentity&lt;T&gt;(arg: Array&lt;T&gt;): Array&lt;T&gt; {
  console.log(arg.length); // 배열은 .length를 가지고 있습니다. 따라서 오류는 없습니다.
  return arg;
}</code></pre><h3 id="4-제네릭-타입-generic-types">4. 제네릭 타입 (Generic Types)</h3>
<ul>
<li><p>이번 섹션에서는 함수 자체의 타입과 제네릭 인터페이스를 만드는 방법에 대해 살펴보자.</p>
</li>
<li><p>제네릭 함수의 타입은 함수 선언과 유사하게 타입 매개변수가 먼저 나열되는, 비-제네릭 함수의 타입과 비슷합니다.</p>
</li>
</ul>
<pre><code>function identity&lt;T&gt;(arg: T): T {
  return arg;
}

let myIdentity: &lt;T&gt;(arg: T) =&gt; T = identity;</code></pre><ul>
<li>또한 타입 변수의 수와 타입 변수가 사용되는 방식에 따라 타입의 제네릭 타입 매개변수에 다른 이름을 사용할 수도 있다.</li>
</ul>
<pre><code>function identity&lt;T&gt;(arg: T): T {
  return arg;
}

let myIdentity: &lt;U&gt;(arg: U) =&gt; U = identity;</code></pre><ul>
<li>제네릭 타입을 객체 리터럴 타입의 함수 호출 시그니처로 작성할 수도 있다.</li>
</ul>
<pre><code>function identity&lt;T&gt;(arg: T): T {
  return arg;
}

let myIdentity: { &lt;T&gt;(arg: T): T } = identity;</code></pre><ul>
<li><p>이것들로 첫 번째 제네릭 인터페이스를 작성할 수 있다.</p>
</li>
<li><p>앞서 예제의 객체 리터럴을 인터페이스로 가져온다.</p>
</li>
</ul>
<pre><code>interface GenericIdentityFn {
  &lt;T&gt;(arg: T): T;
}

function identity&lt;T&gt;(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn = identity;</code></pre><ul>
<li><p>이를 통해 제네릭 타입을 확인할 수 있습니다.
(예 - <code>Dictionary</code> 가 아닌 <code>Dictionary&lt;string&gt;</code> ).</p>
</li>
<li><p>이렇게 하면 인터페이스의 다른 모든 멤버가 타입 매개변수를 볼 수 있다.</p>
</li>
</ul>
<pre><code>interface GenericIdentityFn&lt;T&gt; {
  (arg: T): T;
}

function identity&lt;T&gt;(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn&lt;number&gt; = identity;</code></pre><ul>
<li><p>제네릭 함수를 작성하는 것 대신 제네릭 타입의 일부인 비-제네릭 함수 시그니처를 가진다.</p>
</li>
<li><p><code>GenericIdentityFn</code> 함수를 사용할 때, 시그니처가 사용할 것을 효과적으로 제한할 특정한 타입 인수가 필요하다.(여기서는 <code>number</code> )</p>
</li>
<li><p>타입 매개변수를 호출 시그니처에 바로 넣을 때와 인터페이스 자체에 넣을 때를 이해하는 것은 타입의 제네릭 부분을 설명하는 데 도움이 된다.</p>
</li>
<li><p>제네릭 인터페이스 외에도 제네릭 클래스를 만들 수 있다.</p>
</li>
<li><p>제네릭 열거형과 네임스페이스는 만들 수 없다.</p>
</li>
</ul>
<h3 id="5-제네릭-클래스-generic-classes">5. 제네릭 클래스 (Generic Classes)</h3>
<ul>
<li><p>제네릭 클래스와 제네릭 인터페이스는 형태가 비슷하다.</p>
</li>
<li><p>제네릭 클래스는 클래스 이름 뒤에 꺾쇠괄호(<code>&lt;&gt;</code> ) 안쪽에 제네릭 타입 매개변수 목록을 가진다.</p>
</li>
</ul>
<pre><code>class GenericNumber&lt;T&gt; {
    zeroValue: T;
    add: (x: T, y: T) =&gt; T;
}

let myGenericNumber = new GenericNumber&lt;number&gt;();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };</code></pre><ul>
<li><p>이것은 <code>GenericNumber</code> 클래스의 문자 그대로 사용하지만 <code>number</code> 타입만 쓰도록 제한하는 것은 없다.</p>
</li>
<li><p>대신 <code>string</code> 이나 훨씬 복잡한 객체를 사용할 수 있다.</p>
</li>
</ul>
<pre><code>let stringNumeric = new GenericNumber&lt;string&gt;();
stringNumeric.zeroValue = &quot;&quot;;
stringNumeric.add = function(x, y) { return x + y; };

console.log(stringNumeric.add(stringNumeric.zeroValue, &quot;test&quot;));</code></pre><ul>
<li><p>인터페이스와 마찬가지로 클래스 자체에 타입 매개변수를 넣으면 클래스의 모든 프로퍼티가 동일한 타입으로 동작하는 것을 확인할 수 있다.</p>
</li>
<li><p>클래스에서 다뤘던 것처럼 클래스는 정적 측면과 인스턴스 측면, 두 가지 타입을 가진다.</p>
</li>
<li><p>제네릭 클래스는 정적 측면이 아닌 인스턴스 측면에서만 제네릭이므로 클래스로 작업할 때 정적 멤버는 클래스의 타입 매개변수를 쓸 수 없다.</p>
</li>
</ul>
<h3 id="6-제네릭-제약조건-generic-constraints">6. 제네릭 제약조건 (Generic Constraints)</h3>
<ul>
<li><p>앞쪽의 예제를 기억한다면 특정 타입들로만 동작하는 제네릭 함수를 만들고 싶을 수 있다.</p>
</li>
<li><p>앞서 <code>loggingIdentity</code> 예제에서 <code>arg</code> 의 프로퍼티 <code>.length</code> 에 접근하기를 원했지만, 컴파일러는 모든 타입에서 <code>.length</code> 프로퍼티를 가짐을 증명할 수 없으므로 경고한다.</p>
</li>
</ul>
<pre><code>function loggingIdentity&lt;T&gt;(arg: T): T {
    console.log(arg.length);  // 오류: T에는 .length가 없습니다.
    return arg;
}</code></pre><ul>
<li><p><code>any</code> 와 모든 타입에서 동작하는 대신에, <code>.length</code> 프로퍼티가 있는 <code>any</code> 와 모든 타입들에서 작동하는 것으로 제한하고 싶다.</p>
</li>
<li><p>타입이 이 멤버가 있는 한 허용하지만, 최소한 <code>.length</code> 가 있어야 한다.</p>
</li>
<li><p>그렇게 하려면 <code>T</code> 가 무엇이 될 수 있는지에 대한 제약 조건을 나열해야 한다.</p>
</li>
<li><p>이를 위해 우리의 제약조건이 명시하는 인터페이스를 만든다.</p>
</li>
<li><p>여기 하나의 프로퍼티 <code>.length</code> 를 가진 인터페이스를 생성하였고, 우리의 제약사항을 <code>extends</code> 키워드로 표현한 인터페이스를 이용해 명시한다.</p>
</li>
</ul>
<pre><code>interface Lengthwise {
    length: number;
}

function loggingIdentity&lt;T extends Lengthwise&gt;(arg: T): T {
    console.log(arg.length);  // 이제 .length 프로퍼티가 있는 것을 알기 때문에 더 이상 오류가 발생하지 않습니다.
    return arg;
}</code></pre><ul>
<li>제네릭 함수는 이제 제한되어 있기 때문에 모든 타입에 대해서는 동작하지 않는다.</li>
</ul>
<pre><code>loggingIdentity(3);  // 오류, number는 .length 프로퍼티가 없습니다.</code></pre><p>대신 필요한 프로퍼티들이 있는 타입의 값을 전달해야 한다.</p>
<pre><code>loggingIdentity({length: 10, value: 3});</code></pre><h3 id="7-제네릭-제약조건에서-타입-매개변수-사용-using-type-parameters-in-generic-constraints">7. 제네릭 제약조건에서 타입 매개변수 사용 (Using Type Parameters in Generic Constraints)</h3>
<ul>
<li><p>다른 타입 매개변수로 제한된 타입 매개변수를 선언할 수 있다.</p>
</li>
<li><p>이름이 있는 객체에서 프로퍼티를 가져오고 싶은 경우를 예로 들어 보면 실수로 <code>obj</code> 에 존재하지 않는 프로퍼티를 가져오지 않도록 하기 위해 두 가지 타입에 제약조건을 두었다.</p>
</li>
</ul>
<pre><code>function getProperty&lt;T, K extends keyof T&gt;(obj: T, key: K) {
    return obj[key];
}

let x = { a: 1, b: 2, c: 3, d: 4 };

getProperty(x, &quot;a&quot;); // 성공
getProperty(x, &quot;m&quot;); // 오류: 인수의 타입 &#39;m&#39; 은 &#39;a&#39; | &#39;b&#39; | &#39;c&#39; | &#39;d&#39;에 해당되지 않음.</code></pre><h3 id="8-제네릭에서-클래스-타입-사용-using-class-types-in-generics">8. 제네릭에서 클래스 타입 사용 (Using Class Types in Generics)</h3>
<ul>
<li>제네릭을 사용하는 TypeScript에서 팩토리를 생성할 때 생성자 함수로 클래스 타입을 참조해야 합니다.</li>
</ul>
<pre><code>function create&lt;T&gt;(c: {new(): T; }): T {
    return new c();
}</code></pre><ul>
<li>고급 예제에서는 prototype 프로퍼티를 사용하여 생성자 함수와 클래스 타입의 인스턴스 사이의 관계를 유추하고 제한한다.</li>
</ul>
<pre><code>class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance&lt;A extends Animal&gt;(c: new () =&gt; A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // 타입검사!
createInstance(Bee).keeper.hasMask;   // 타입검사!</code></pre><hr/>

<h2 id="typescript-handbook-정리-후기">TypeScript HandBook 정리 후기</h2>
<ul>
<li>시작 동기와 같이 type을 부정확하게 사용하고 있지는 않는지, 제대로 사용하고 있는지 검토 목적은 달성 한 것 같지만, 다른 공부를 더 많이 해야겠다는 생각이 든다.</li>
<li>더 열심히 살자.</li>
</ul>
<hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (7)]]></title>
            <link>https://velog.io/@syo_ee/typescript07</link>
            <guid>https://velog.io/@syo_ee/typescript07</guid>
            <pubDate>Mon, 08 Jan 2024 13:46:49 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-7-열거형">Chapter 7. 열거형</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 숫자 열거형 (Numeric enums)</strong>
<strong>3. 문자열 열거형 (String enums)</strong>
<strong>4. 이종 열거형 (Heterogeneous enums)</strong>
<strong>5. 계산된 멤버와 상수 멤버 (Computed and constant members)</strong>
<strong>6. 유니언 열거형과 열거형 멤버 타입 (Union enums and enum member types)</strong>
<strong>7. 런타임에서 열거형 (Enums at runtime)</strong>
<strong>8. 컴파일 시점에서 열거형 (Enums at compile time)</strong>
<strong>9. Ambient 열거형 (Ambient enums)</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>열거형으로 이름이 있는 상수들의 집합을 정의할 수 있다.</p>
</li>
<li><p>열거형을 사용하면 의도를 문서화 하거나 구분되는 사례 집합을 더 쉽게 만들수 있다.</p>
</li>
<li><p>TypeScript는 숫자와 문자열-기반 열거형을 제공한다.</p>
</li>
</ul>
<h3 id="2-숫자-열거형-numeric-enums">2. 숫자 열거형 (Numeric enums)</h3>
<ul>
<li>다른 언어를 배워 보았다면 친숙하게 느끼실 수 있는 숫자 열거형에 대해서 먼저 배워보겠습니다. 열거형은 enum 키워드를 사용해 정의할 수 있습니다.</li>
</ul>
<pre><code>enum Direction {
    Up = 1,
    Down,
    Left,
    Right,
}</code></pre><ul>
<li><p>위 코드에서 <code>Up</code> 이 <code>1</code> 로 초기화된 숫자 열거형을 선언했습니다.</p>
</li>
<li><p>그 지점부터 뒤따르는 멤버들은 자동으로-증가된 값을 갖는다.</p>
</li>
<li><p>즉 <code>Direction.Up</code> 은 <code>1</code> , <code>Down</code> 은 <code>2</code> , <code>Left</code> 는 <code>3</code> , <code>Right</code> 은 <code>4</code> 을 값으로 가진다.</p>
</li>
</ul>
<p>원한다면, 전부 초기화 하지 않을 수도 있다.</p>
<pre><code>enum Direction {
    Up,
    Down,
    Left,
    Right,
}</code></pre><ul>
<li><p>위 경우 <code>Up</code> 값은 <code>0</code> , <code>Down</code> 은 <code>1</code> 이 된다.</p>
</li>
<li><p>자동-증가하는 기능은 멤버 값 자체에는 신경 쓰지 않지만, 각 값이 같은 열거형의 다른 값과 구별돼야 하는 경우에 유용하다.</p>
</li>
<li><p>열거형을 사용하는 것은 간단하다.</p>
</li>
<li><p>그냥 열거형 자체에서 프로퍼티로 모든 멤버에 접근하며, 열거형의 이름을 사용해 타입을 선언한다.</p>
</li>
</ul>
<pre><code>enum Response {
    No = 0,
    Yes = 1,
}

function respond(recipient: string, message: Response): void {
    // ...
}

respond(&quot;Princess Caroline&quot;, Response.Yes)</code></pre><ul>
<li><p>숫자 열거형은 계산된 멤버와 상수 멤버 (아래 참조)를 섞어서 사용할 수 있다.</p>
</li>
<li><p>간단히 말해, 초기화되지 않은 열거형이 먼저 나오거나, 숫자 상수 혹은 다른 상수 열거형 멤버와 함께 초기화된 숫자 열거형 이후에 와야 한다.</p>
</li>
<li><p>즉 아래 방식은 허용되지 않는다.</p>
</li>
</ul>
<pre><code>enum E {
    A = getSomeValue(),
    B, // 오류! 앞에 나온 A가 계산된 멤버이므로 초기화가 필요합니다.
}</code></pre><h3 id="3-문자열-열거형-string-enums">3. 문자열 열거형 (String enums)</h3>
<ul>
<li><p>문자열 열거형은 유사한 개념이지만 아래 설명된 것과 같이 런타임에서 열거형의 동작이 약간 다르다.</p>
</li>
<li><p>문자열 열거형에서 각 멤버들은 문자열 리터럴 또는 다른 문자열 열거형의 멤버로 상수 초기화 해야한다.</p>
</li>
</ul>
<pre><code>enum Direction {
    Up = &quot;UP&quot;,
    Down = &quot;DOWN&quot;,
    Left = &quot;LEFT&quot;,
    Right = &quot;RIGHT&quot;,
}</code></pre><ul>
<li><p>문자열 열거형은 숫자 열거형처럼 자동-증가하는 기능은 없지만, &quot;직렬화&quot;를 잘한다는 이점이 있다.</p>
</li>
<li><p>다시 말해서 만약 당신이 숫자 열거형을 이용해서 디버깅하고 있고 그 값을 읽어야 한다면, 종종 그 값이 불확실한 경우가 있다.</p>
<ul>
<li>숫자만으로는 이것이 어떤 의미인지 유의미한 정보를 제공해주지 않기 때문이다.
(역 매핑 을 이용하면 도움이 될지라도),</li>
</ul>
</li>
<li><p>반면 문자열 열거형을 이용하면 코드를 실행할 때, 열거형 멤버에 지정된 이름과는 무관하게 유의미하고 읽기 좋은 값을 이용하여 실행할 수 있다.</p>
</li>
</ul>
<h3 id="4-이종-열거형-heterogeneous-enums">4. 이종 열거형 (Heterogeneous enums)</h3>
<ul>
<li>기술적으로 열거형은 숫자와 문자를 섞어서 사용할 수 있지만 굳이 그렇게 할 이유는 없다.</li>
</ul>
<pre><code>enum BooleanLikeHeterogeneousEnum {
    No = 0,
    Yes = &quot;YES&quot;,
}</code></pre><ul>
<li>확실하게 JavaScript 런타임에서 장점을 취하려는 것이 아니라면, 이렇게 사용하지 않는 것을 권장한다.</li>
</ul>
<h3 id="5-계산된-멤버와-상수-멤버-computed-and-constant-members">5. 계산된 멤버와 상수 멤버 (Computed and constant members)</h3>
<ul>
<li><p>각 열거형의 멤버는 상수이거나 계산된 값일 수 있습니다. 열거형의 멤버는 아래의 경우 상수로 간주한다.</p>
</li>
<li><p>열거형의 첫 번째 데이터이며 초기화 값이 없는 경우, 0으로 값이 할당된다.</p>
</li>
</ul>
<pre><code>// E.X는 상수이다.
enum E { X }</code></pre><ul>
<li>초기화 값이 없으며 숫자 상수로 초기화된 열거형 멤버 뒤에 따라 나오는 경우. 앞에 나온 상수 값에 1씩 증가한 값을 상수로 갖습니다.</li>
</ul>
<pre><code>// &#39;E1&#39; 과 &#39;E2&#39; 의 모든 열거형 멤버는 상수입니다.

enum E1 { X, Y, Z }

enum E2 {
    A = 1, B, C
}</code></pre><ul>
<li><p>열거형 멤버는 상수 열거형 표현식으로 초기화된다.</p>
</li>
<li><p>상수 열거형 표현식은 컴파일 시 알아낼 수 있는 TypeScript 표현식의 일부이다.</p>
</li>
<li><p>아래의 경우 상수 열거형 표현식이라고 한다.</p>
</li>
<li><p>리터럴 열거형 표현식 (기본적으로 문자 리터럴 또는 숫자 리터럴)</p>
<ol>
<li>이전에 정의된 다른 상수 열거형 멤버에 대한 참조 (다른 열거형에서 시작될 수 있음)</li>
<li>괄호로 묶인 상수 열거형 표현식</li>
<li>상수 열거형 표현식에 단항 연산자 <code>+</code> , <code>-</code> , <code>~</code> 를 사용한 경우</li>
<li>상수 열거형 표현식을 이중 연산자 <code>+</code> , <code>-</code> , <code>*</code> , <code>/</code> , <code>%</code> , <code>&lt;&lt;</code> , <code>&gt;&gt;</code> , <code>&gt;&gt;&gt;</code> , <code>&amp;</code> , <code>|</code> , <code>^</code> 의 피연산자로 사용할 경우</li>
<li>상수 열거형 표현식 값이 <code>NaN</code> 이거나 <code>Infinity</code> 이면 컴파일 시점에 오류가 난다.</li>
</ol>
</li>
<li><p>이외 다른 모든 경우 열거형 멤버는 계산된 것으로 간주합니다.</p>
</li>
</ul>
<pre><code>enum FileAccess {
    // 상수 멤버
    None,
    Read    = 1 &lt;&lt; 1,
    Write   = 1 &lt;&lt; 2,
    ReadWrite  = Read | Write,
    // 계산된 멤버
    G = &quot;123&quot;.length
}</code></pre><h3 id="6-유니언-열거형과-열거형-멤버-타입-union-enums-and-enum-member-types">6. 유니언 열거형과 열거형 멤버 타입 (Union enums and enum member types)</h3>
<ul>
<li><p>계산되지 않는 상수 열거 멤버의 특수한 부분 집합이 있다.</p>
</li>
<li><p>리터럴 열거형 멤버 리터럴 열거형 멤버는 초기화 값이 존재하지 않거나, 아래 값들로 초기화되는 멤버이다.</p>
<ul>
<li>문자 리터럴 (예시. <code>&quot;foo&quot;</code> , <code>&quot;bar&quot;</code> , <code>&quot;baz&quot;</code> )</li>
<li>숫자 리터럴 (예시. <code>1</code>,  <code>100</code>)</li>
<li>숫자 리터럴에 단항 연산자 - 가 적용된 경우 (e.g. <code>-1</code> , <code>-100</code> )</li>
</ul>
</li>
<li><p>열거형의 모든 멤버가 리터럴 열거형 값을 가지면 특별한 의미로 쓰이게 된다.</p>
</li>
<li><p>첫째로 열거형 멤버를 타입처럼 사용할 수 있습니다! 예를 들어 특정 멤버는 오직 열거형 멤버의 값만 가지게 할 수 있다.</p>
</li>
</ul>
<pre><code>enum ShapeKind {
    Circle,
    Square,
}

interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}

let c: Circle = {
    kind: ShapeKind.Square, // 오류! &#39;ShapeKind.Circle&#39; 타입에 &#39;ShapeKind.Square&#39; 타입을 할당할 수 없습니다.
    radius: 100,
}</code></pre><ul>
<li><p>또 다른 점은 열거형 타입 자체가 효율적으로 각각의 열거형 멤버의 유니언이 된다는 점이다.</p>
</li>
<li><p>아직 유니언 타입에 대해서 배우진 않았지만, 유니언 타입 열거형을 사용하면 타입 시스템이 열거형 자체에 존재하는 정확한 값의 집합을 알고 있다는 사실을 활용할 수 있다는 점만 알아두면 된다.</p>
</li>
<li><p>이 때문에 TypeScript는 값을 잘못 비교하는 어리석은 버그를 잡을 수 있다.</p>
</li>
</ul>
<pre><code>enum E {
    Foo,
    Bar,
}

function f(x: E) {
    if (x !== E.Foo || x !== E.Bar) {
        //             ~~~~~~~~~~~
        // 에러! E 타입은 Foo, Bar 둘 중 하나이기 때문에 이 조건은 항상 true를 반환합니다.
    }
}</code></pre><ul>
<li><p>이 예제에서 우리는 <code>x</code> 가 <code>E.Foo</code> 가 아닌지 확인한다.</p>
</li>
<li><p>만약 이 조건이 <code>true</code> 라면, <code>||</code> 조건은 더는 체크할 필요가 없으므로 <code>if</code> 아래의 <code>body</code> 가 실행될 것이다.</p>
</li>
<li><p>그러나 만약 이 조건이 통과되지 않는다면, <code>x</code> 는 반드시 <code>E.Foo</code> 이기 때문에, <code>x</code> 가 <code>E.Bar</code> 가 아닌지 묻는 조건과 비교하는 것은 적절하지 않다.</p>
</li>
</ul>
<h3 id="7-런타임에서-열거형-enums-at-runtime">7. 런타임에서 열거형 (Enums at runtime)</h3>
<ul>
<li>열거형은 런타임에 존재하는 실제 객체이다.
아래와 같은 열거형은</li>
</ul>
<pre><code>enum E {
    X, Y, Z
}</code></pre><ul>
<li>실제로 아래와 같이 함수로 전달될 수 있다.</li>
</ul>
<pre><code>function f(obj: { X: number }) {
    return obj.X;
}

// E가 X라는 숫자 프로퍼티를 가지고 있기 때문에 동작하는 코드입니다.
f(E);</code></pre><h3 id="8-컴파일-시점에서-열거형-enums-at-compile-time">8. 컴파일 시점에서 열거형 (Enums at compile time)</h3>
<ul>
<li><p>열거형이 런타임에 존재하는 실제 객체라고 할지라도, <code>keyof</code> 키워드는 일반적인 객체에서 기대하는 동작과 다르게 동작한다.</p>
</li>
<li><p>대신, <code>keyof typeof</code> 를 사용하면 모든 열거형의 키를 문자열로 나타내는 타입을 가져온다.</p>
</li>
</ul>
<pre><code>enum LogLevel {
    ERROR, WARN, INFO, DEBUG
}

/**
 * 이것은 아래와 동일합니다. :
 * type LogLevelStrings = &#39;ERROR&#39; | &#39;WARN&#39; | &#39;INFO&#39; | &#39;DEBUG&#39;;
 */
type LogLevelStrings = keyof typeof LogLevel;

function printImportant(key: LogLevelStrings, message: string) {
    const num = LogLevel[key];
    if (num &lt;= LogLevel.WARN) {
       console.log(&#39;Log level key is: &#39;, key);
       console.log(&#39;Log level value is: &#39;, num);
       console.log(&#39;Log level message is: &#39;, message);
    }
}
printImportant(&#39;ERROR&#39;, &#39;This is a message&#39;);</code></pre><p><strong>8-1. 역 매핑 (Reverse mappings)</strong></p>
<ul>
<li>숫자 열거형 멤버는 멤버의 프로퍼티 이름을 가진 객체를 생성하는 것 외에도 열거형 값에서 열거형 이름으로 역 매핑 을 받는다.</li>
</ul>
<pre><code>enum Enum {
    A
}
let a = Enum.A;
let nameOfA = Enum[a]; // &quot;A&quot;</code></pre><ul>
<li>TypeScript는 아래와 같은 JavaScript 코드로 컴파일할 것이다.</li>
</ul>
<pre><code>var Enum;
(function (Enum) {
    Enum[Enum[&quot;A&quot;] = 0] = &quot;A&quot;;
})(Enum || (Enum = {}));
var a = Enum.A;
var nameOfA = Enum[a]; // &quot;A&quot;</code></pre><ul>
<li><p>이렇게 생성된 코드에서, 열거형은 정방향 (<code>name</code> -&gt; <code>value</code> ) 매핑과 역방향 (<code>value</code> -&gt; <code>name</code> ) 매핑 두 정보를 모두 저장하는 객체로 컴파일된다.</p>
</li>
<li><p>다른 열거형 멤버 참조는 항상 프로퍼티 접근으로 노출되며 인라인되지 않는다.</p>
</li>
<li><p>문자열 열거형은 역 매핑을 생성하지 않는다 는 것을 명심하시길 바란다.</p>
</li>
</ul>
<p><strong>8-2. <code>const</code> 열거형 (<code>const</code> enums)</strong></p>
<ul>
<li><p>대부분의 경우, 열거형은 완벽하게 유효한 해결책이다.</p>
</li>
<li><p>하지만 종종 열거형의 요구사항이 좀 더 엄격해 진다.</p>
</li>
<li><p>열거형 값에 접근할 때, 추가로 생성된 코드 및 추가적인 간접 참조에 대한 비용을 피하기 위해 <code>const</code> 열거형을 사용할 수 있다.</p>
</li>
<li><p><code>const</code> 열거형은 <code>const</code> 지정자를 열거형에 붙여 정의한다.</p>
</li>
</ul>
<pre><code>const enum Enum {
    A = 1,
    B = A * 2
}</code></pre><ul>
<li><p><code>const</code> 열거형은 상수 열거형 표현식만 사용될 수 있으며 일반적인 열거형과 달리 컴파일 과정에서 완전히 제거된다.</p>
</li>
<li><p><code>const</code> 열거형은 사용하는 공간에 인라인된다.</p>
</li>
<li><p>이러한 동작은 <code>const</code> 열거형이 계산된 멤버를 가지고 있지 않기 때문에 가능하다.</p>
</li>
</ul>
<pre><code>const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]</code></pre><ul>
<li>위 코드는 아래와 같이 컴파일된다.</li>
</ul>
<pre><code>var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];</code></pre><h3 id="9-ambient-열거형-ambient-enums">9. Ambient 열거형 (Ambient enums)</h3>
<ul>
<li>Ambient 열거형은 이미 존재하는 열거형 타입의 모습을 묘사하기 위해 사용된다.</li>
</ul>
<pre><code>declare enum Enum {
    A = 1,
    B,
    C = 2
}</code></pre><ul>
<li><p>ambient 열거형과 비-ambient 열거형의 가장 큰 차이점은, 일반적인 열거형에서 초기화되지 않은 멤버가 상수로 간주하는 멤버 뒤에 있다면, 이 멤버도 상수로 간주할 것이다.</p>
</li>
<li><p>반면 (const가 아닌) ambient 열거형에서 초기화되지 않은 멤버는 항상 계산된 멤버로 간주한다.</p>
</li>
</ul>
<hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (6)]]></title>
            <link>https://velog.io/@syo_ee/typescript06</link>
            <guid>https://velog.io/@syo_ee/typescript06</guid>
            <pubDate>Sun, 07 Jan 2024 13:25:50 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-6-클래스">Chapter 6. 클래스</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 클래스 (Classes)</strong>
<strong>3. 상속 (Inheritance)</strong>
<strong>4. Public, private 그리고 protected 지정자 (Public, private, and protected modifiers)</strong>
<strong>5. 읽기전용 지정자 (Readonly modifier)</strong>
<strong>6. 접근자 (Accessors)</strong>
<strong>7. 전역 프로퍼티 (Static Properties)</strong>
<strong>8. 추상 클래스 (Abstract Classes)</strong>
<strong>9. 고급 기법 (Advanced Techniques)</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>기존 JavaScript는 재사용할 수 있는 컴포넌트를 만들기 위해 함수와 프로토타입-기반 상속을 사용했지만, 객체 지향 접근 방식에 익숙한 프로그래머의 입장에서는 클래스가 함수를 상속받고 이런 클래스에서 객체가 만들어지는 것에 다소 어색함을 느낄 수 있다.</p>
</li>
<li><p>ECMAScript 6로도 알려진 ECMAScript 2015를 시작으로 JavaScript 프로그래머들은 이런 객체-지향적 클래스-기반의 접근 방식을 사용해서 애플리케이션을 만들 수 있습니다.</p>
</li>
<li><p>TypeScript에서는 다음 버전의 JavaScript를 기다릴 필요 없이 개발자들이 이러한 기법들을 사용할 수 있게 해주며, 기존의JavaScript로 컴파일하여 주요 브라우저와 플랫폼에서 동작하게 한다.</p>
</li>
</ul>
<h3 id="2-클래스-classes">2. 클래스 (Classes)</h3>
<pre><code>class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return &quot;Hello, &quot; + this.greeting;
    }
}

let greeter = new Greeter(&quot;world&quot;);</code></pre><ul>
<li><p>C# 이나 Java를 사용해봤다면, 익숙한 구문일 것이다.</p>
</li>
<li><p>새로운 클래스 <code>Greeter</code> 를 선언했습니다.</p>
</li>
<li><p>이 클래스는 3개의 멤버를 가지고 있다.
<code>greeting</code> 프로퍼티, 생성자 그리고 <code>greet</code> 메서드 이다.</p>
</li>
<li><p>클래스 안에서 클래스의 멤버를 참조할 때 <code>this.</code> 를 앞에 덧붙이는 것을 알 수 있다.
이것은 멤버에 접근하는 것을 의미한다.</p>
</li>
<li><p>마지막 줄에서, <code>new</code> 를 사용하여 <code>Greeter</code> 클래스의 인스턴스를 생성한다.</p>
</li>
<li><p>이 코드는 이전에 정의한 생성자를 호출하여 <code>Greeter</code> 형태의 새로운 객체를 만들고, 생성자를 실행해 초기화한다.</p>
</li>
</ul>
<h3 id="3-상속-inheritance">3. 상속 (Inheritance)</h3>
<ul>
<li><p>TypeScript에서는, 일반적인 객체-지향 패턴을 사용할 수 있다.</p>
</li>
<li><p>클래스-기반 프로그래밍의 가장 기본적인 패턴 중 하나는 상속을 이용하여 이미 존재하는 클래스를 확장해 새로운 클래스를 만들 수 있다는 것이다.</p>
</li>
</ul>
<pre><code>class Animal {
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}

class Dog extends Animal {
    bark() {
        console.log(&#39;Woof! Woof!&#39;);
    }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();</code></pre><ul>
<li><p>상속 기능을 보여주는 가장 기본적인 예제이다.
클래스는 기초 클래스로부터 프로퍼티와 메서드를 상속받는다.</p>
</li>
<li><p>여기서, <code>Dog</code> 은 <code>extends</code> 키워드를 사용하여 <code>Animal</code> 이라는 기초 클래스로부터 파생된 파생 클래스이다.</p>
</li>
<li><p>파생된 클래스는 하위클래스(<em>subclasses</em>), 기초 클래스는 상위클래스(<em>superclasses</em>) 라고 불리기도 한다.</p>
</li>
<li><p><code>Dog</code> 는 <code>Animal</code> 의 기능을 확장하기 때문에, <code>bark()</code> 와 <code>move()</code> 를 모두 가진 <code>Dog</code> 인스턴스를 생성할 수 있다.</p>
</li>
</ul>
<pre><code>class Animal {
    name: string;
    constructor(theName: string) { this.name = theName; }
    move(distanceInMeters: number = 0) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}

class Snake extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 5) {
        console.log(&quot;Slithering...&quot;);
        super.move(distanceInMeters);
    }
}

class Horse extends Animal {
    constructor(name: string) { super(name); }
    move(distanceInMeters = 45) {
        console.log(&quot;Galloping...&quot;);
        super.move(distanceInMeters);
    }
}

let sam = new Snake(&quot;Sammy the Python&quot;);
let tom: Animal = new Horse(&quot;Tommy the Palomino&quot;);

sam.move();
tom.move(34);</code></pre><ul>
<li><p>이 예제는 앞에서 언급하지 않은 몇 가지 기능을 다룬다.
이번에도 <code>extends</code> 키워드를 사용하여 <code>Animal</code> 의 하위클래스를 생성한다: <code>Horse</code> 와 <code>Snake</code>.</p>
</li>
<li><p>이전 예제와 한 가지 다른 부분은 파생된 클래스의 생성자 함수는 기초 클래스의 생성자를 실행할 <code>super()</code> 를 호출해야 한다는 점이다.</p>
</li>
<li><p>더욱이 생성자 내에서 <code>this</code> 에 있는 프로퍼티에 접근하기 전에 <code>super()</code> 를 먼저 호출해야 한다.</p>
</li>
<li><p><em>이 부분은 TypeScript에서 중요한 규칙이다.</em></p>
</li>
<li><p>또한 이 예제는 기초 클래스의 메서드를 하위클래스에 특화된 메서드로 오버라이드하는 방법을 보여준다.</p>
</li>
<li><p>여기서 <code>Snake</code> 와 <code>Horse</code> 는 <code>Animal</code> 의 <code>move</code> 를 오버라이드해서 각각 클래스의 특성에 맞게 기능을 가진 <code>move</code> 를 생성한다.</p>
</li>
<li><p><code>tom</code> 은 <code>Animal</code> 로 선언되었지만 <code>Horse</code> 의 값을 가지므로 <code>tom.move(34)</code> 는 <code>Horse</code> 의 오버라이딩 메서드를 호출힌다.</p>
</li>
</ul>
<pre><code>Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.</code></pre><h3 id="4-public-private-그리고-protected-지정자-public-private-and-protected-modifiers">4. Public, private 그리고 protected 지정자 (Public, private, and protected modifiers)</h3>
<p><strong>4-1. 기본적으로 공개 (Public by default)</strong></p>
<ul>
<li><p>예제에서는, 프로그램 내에서 선언된 멤버들에 자유롭게 접근할 수 있다.</p>
</li>
<li><p>다른 언어의 클래스가 익숙하다면, 위 예제에서 <code>public</code> 을 사용하지 않아도 된다는 점을 알 수 있다.</p>
</li>
<li><p>예를 들어, C#에서는 노출 시킬 각 멤버에 <code>public</code> 을 붙여야 한다.</p>
</li>
<li><p>TypeScript에서는 기본적으로 각 멤버는 <code>public</code> 입니다.</p>
</li>
<li><p>명시적으로 멤버를 <code>public</code> 으로 표시할 수도 있다.</p>
</li>
<li><p>이전 섹션의 <code>Animal</code> 클래스를 다음과 같은 방식으로 작성할 수 있다.</p>
</li>
</ul>
<pre><code>class Animal {
    public name: string;
    public constructor(theName: string) { this.name = theName; }
    public move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}</code></pre><p><strong>4-2. ECMAScript 비공개 필드 (ECMAScript Private Fields)</strong></p>
<ul>
<li>TypeScript 3.8에서, TypeScript는 비공개 필드를 위한 JavaScript의 새로운 문법을 지원한다.</li>
</ul>
<pre><code>class Animal {
    #name: string;
    constructor(theName: string) { this.#name = theName; }
}

new Animal(&quot;Cat&quot;).#name; // 프로퍼티 &#39;#name&#39;은 비공개 식별자이기 때문에 &#39;Animal&#39; 클래스 외부에선 접근할 수 없다.</code></pre><ul>
<li><p>이 문법은 JavaScript 런타임에 내장되어 있으며, 각각의 비공개 필드의 격리를 더 잘 보장할 수 있다.</p>
</li>
<li><p>현재 TypeScript 3.8 릴리즈 노트에 비공개 필드에 대해 자세히 나와있다.</p>
</li>
</ul>
<p><strong>4-3. TypeScript의 <code>private</code> 이해하기 (Understanding TypeScript’s <code>private</code> )</strong></p>
<ul>
<li>TypeScript에는 멤버를 포함하는 클래스 외부에서 이 멤버에 접근하지 못하도록 멤버를 <code>private</code> 으로 표시하는 방법이 있다.</li>
</ul>
<pre><code>class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

new Animal(&quot;Cat&quot;).name; // 오류: &#39;name&#39;은 비공개로 선언되어 있습니다;</code></pre><ul>
<li><p>TypeScript는 구조적인 타입 시스템이다.</p>
</li>
<li><p>두개의 다른 타입을 비교할 때 어디서 왔는지 상관없이 모든 멤버의 타입이 호환 된다면, 그 타입들 자체가 호환 가능하다고 말한다.</p>
</li>
<li><p>그러나 <code>private</code> 및 <code>protected</code> 멤버가 있는 타입들을 비교할 때는 타입을 다르게 처리한다.</p>
</li>
<li><p>호환된다고 판단되는 두 개의 타입 중 한 쪽에서 <code>private</code> 멤버를 가지고 있다면, 다른 한 쪽도 무조건 동일한 선언에 <code>private</code> 멤버를 가지고 있어야 한다.</p>
</li>
<li><p>이것은 <code>protected</code> 멤버에도 적용된다.</p>
</li>
</ul>
<pre><code>class Animal {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
    constructor() { super(&quot;Rhino&quot;); }
}

class Employee {
    private name: string;
    constructor(theName: string) { this.name = theName; }
}

let animal = new Animal(&quot;Goat&quot;);
let rhino = new Rhino();
let employee = new Employee(&quot;Bob&quot;);

animal = rhino;
animal = employee; // 오류: &#39;Animal&#39;과 &#39;Employee&#39;은 호환될 수 없음.</code></pre><ul>
<li><p>이 예제에서는 <code>Animal</code> 과 <code>Animal</code> 의 하위클래스인 <code>Rhino</code> 가 있다.</p>
</li>
<li><p><code>Animal</code> 과 형태가 같아보이는 <code>Employee</code> 라는 새로운 클래스도 있다.</p>
</li>
<li><p>이 클래스들의 인스턴스를 생성하여 할당하고 어떻게 작동하는지 살펴보면 <code>Animal</code> 과 <code>Rhino</code> 는 <code>Animal</code> 의 <code>private name:string</code> 이라는 동일한 선언으로부터 <code>private</code> 부분을 공유하기 때문에 호환이 가능하다.</p>
</li>
<li><p>하지만 <code>Employee</code> 경우는 그렇지 않다.</p>
</li>
<li><p><code>Employee</code> 를 <code>Animal</code> 에 할당할 때, 타입이 호환되지 않다는 오류가 발생한다.</p>
</li>
<li><p><code>Employee</code> 는 <code>name</code> 이라는 <code>private</code> 멤버를 가지고 있지만, <code>Animal</code> 에서 선언한 것이 아니기 때문이다.</p>
</li>
</ul>
<p><strong>4-4. <code>protected</code> 이해하기 (Understanding protected)</strong></p>
<ul>
<li><code>protected</code> 지정자도 <code>protected</code> 로 선언된 멤버를 파생된 클래스 내에서 접근할 수 있다는 점만 제외하면 <code>private</code> 지정자와 매우 유사하게 동작한다.</li>
</ul>
<pre><code>class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}

class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee(&quot;Howard&quot;, &quot;Sales&quot;);
console.log(howard.getElevatorPitch());
console.log(howard.name); // 오류</code></pre><ul>
<li><p><code>Person</code> 외부에서 <code>name</code> 을 사용할 수 없지만, <code>Employee</code> 는 <code>Person</code> 에서 파생되었기 때문에 <code>Employee</code> 의 인스턴스 메서드 내에서는 여전히 사용할 수 있다.</p>
</li>
<li><p>생성자 또한 <code>protected</code> 로 표시될 수도 있다.</p>
</li>
<li><p>이는 클래스를 포함하는 클래스 외부에서 인스턴스화 할 수 없지만 확장 할 수 있음을 의미한다.</p>
</li>
</ul>
<pre><code>class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
}

// Employee는 Person을 확장할 수 있습니다.
class Employee extends Person {
    private department: string;

    constructor(name: string, department: string) {
        super(name);
        this.department = department;
    }

    public getElevatorPitch() {
        return `Hello, my name is ${this.name} and I work in ${this.department}.`;
    }
}

let howard = new Employee(&quot;Howard&quot;, &quot;Sales&quot;);
let john = new Person(&quot;John&quot;); // 오류: &#39;Person&#39;의 생성자는 protected 입니다.</code></pre><h3 id="5-읽기전용-지정자-readonly-modifier">5. 읽기전용 지정자 (Readonly modifier)</h3>
<ul>
<li><p><code>readonly</code> 키워드를 사용하여 프로퍼티를 읽기전용으로 만들 수 있다.</p>
</li>
<li><p>읽기전용 프로퍼티들은 선언 또는 생성자에서 초기화해야한다.</p>
</li>
</ul>
<pre><code>class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus(&quot;Man with the 8 strong legs&quot;);
dad.name = &quot;Man with the 3-piece suit&quot;; // 오류! name은 읽기전용 입니다.</code></pre><p><strong>5-1. 매개변수 프로퍼티 (Parameter properties)</strong></p>
<ul>
<li><p>마지막 예제의 <code>Octopus</code> 클래스 내에서 <code>name</code> 이라는 읽기전용 멤버와 <code>theName</code> 이라는 생성자 매개변수를 선언했다.</p>
</li>
<li><p>이는 <code>Octopus</code> 의 생성자가 수행된 후에 <code>theName</code> 의 값에 접근하기 위해서 필요하다.</p>
</li>
<li><p>매개변수 프로퍼티를 사용하면 한 곳에서 멤버를 만들고 초기화할 수 있다.</p>
</li>
<li><p>다음은 매개변수 프로퍼티를 사용한 더 개정된 <code>Octopus</code> 클래스이다.</p>
</li>
</ul>
<pre><code>class Octopus {
    readonly numberOfLegs: number = 8;
    constructor(readonly name: string) {
    }
}</code></pre><ul>
<li><p>생성자에 짧아진 <code>readonly name: string</code> 파라미터를 사용하여 <code>theName</code> 을 제거하고 <code>name</code> 멤버를 생성하고 초기화했다.</p>
</li>
<li><p>즉 선언과 할당을 한 곳으로 통합했다.</p>
</li>
<li><p>매개변수 프로퍼티는 접근 지정자나 <code>readonly</code> 또는 둘 모두를 생성자 매개변수에 접두어로 붙여 선언한다.</p>
</li>
<li><p>매개변수 프로퍼티에 <code>private</code> 을 사용하면 비공개 멤버를 선언하고 초기화한다.</p>
</li>
<li><p>마찬가지로, <code>public</code> , <code>protected</code> , <code>readonly</code> 도 동일하게 작용한다.</p>
</li>
</ul>
<h3 id="6-접근자-accessors">6. 접근자 (Accessors)</h3>
<ul>
<li><p>TypeScript는 객체의 멤버에 대한 접근을 가로채는 방식으로 getters/setters 를 지원한다.</p>
</li>
<li><p>이를 통해 각 객체의 멤버에 접근하는 방법을 세밀하게 제어할 수 있다.</p>
</li>
<li><p>간단한 클래스를 <code>get</code> 과 <code>set</code> 을 사용하도록 변환해보자.</p>
</li>
</ul>
<pre><code>class Employee {
    fullName: string;
}

let employee = new Employee();
employee.fullName = &quot;Bob Smith&quot;;
if (employee.fullName) {
    console.log(employee.fullName);
}</code></pre><ul>
<li><p>사람들이 임의로 <code>fullName</code> 을 직접 설정할 수 있도록 허용하는 것은 매우 편리하지만, 우리는 <code>fullName</code> 이 설정될 때 몇 가지 제약 조건이 적용되는 것을 원할 수 있다.</p>
</li>
<li><p>이 버전에서는 백업 데이터베이스 필드의 최대 길이와 호환되는지 확인하기 위해 <code>newName</code> 의 길이를 확인하는 setter 를 추가한다.</p>
</li>
<li><p>만약 최대 길이를 초과한다면, 클라이언트 코드에 문제가 있다는 것을 알리기 위해 오류를 발생시킨다.</p>
</li>
<li><p>기존의 기능을 유지하기 위해, <code>fullName</code> 을 수정하지 않는 간단한 getter 도 추가한다.</p>
</li>
</ul>
<pre><code>const fullNameMaxLength = 10;

class Employee {
    private _fullName: string;

    get fullName(): string {
        return this._fullName;
    }

    set fullName(newName: string) {
        if (newName &amp;&amp; newName.length &gt; fullNameMaxLength) {
            throw new Error(&quot;fullName has a max length of &quot; + fullNameMaxLength);
        }

        this._fullName = newName;
    }
}

let employee = new Employee();
employee.fullName = &quot;Bob Smith&quot;;
if (employee.fullName) {
    console.log(employee.fullName);
}</code></pre><ul>
<li><p>접근자가 값의 길이를 확인하고 있는지 검증하기 위해서, 10자가 넘는 이름을 할당하고 오류가 발생함을 확인할 수 있다.</p>
</li>
<li><p>접근자에 대해 주의해야 할 사항:</p>
<ul>
<li><p>먼저 접근자는 ECMAScript 5 이상을 출력하도록 컴파일러를 설정해야한다.</p>
</li>
<li><p>ECMAScript 3으로의 하향 조정은 지원되지 않는다.</p>
</li>
<li><p><code>get</code> 과 <code>set</code> 이 없는 접근자는 자동으로 <code>readonly</code> 로 유추된다. 이는 프로퍼티 내의 사용자들이 변경할 수 없음을 알 수 있기 때문에 코드 내에서 <code>.d.ts</code> 파일을 생성할 때 유용하다.</p>
</li>
</ul>
</li>
</ul>
<h3 id="7-전역-프로퍼티-static-properties">7. 전역 프로퍼티 (Static Properties)</h3>
<ul>
<li><p>인스턴스가 아닌 클래스 자체에서 보이는 전역 멤버를 생성할 수 있다.</p>
</li>
<li><p>이 예제에서는 모든 <code>grid</code> 의 일반적인 값이기 때문에 <code>origin</code> 에 <code>static</code> 을 사용한다.</p>
</li>
<li><p>각 인스턴스는 클래스 이름을 앞에 붙여 이 값에 접근할 수 있다.</p>
</li>
<li><p>인스턴스 접근 앞에 <code>this.</code> 를 붙이는 것과 비슷하게 여기선 전역 접근 앞에 <code>Grid.</code> 를 붙인다.</p>
</li>
</ul>
<pre><code>class Grid {
    static origin = {x: 0, y: 0};
    calculateDistanceFromOrigin(point: {x: number; y: number;}) {
        let xDist = (point.x - Grid.origin.x);
        let yDist = (point.y - Grid.origin.y);
        return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
    }
    constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));</code></pre><h3 id="8-추상-클래스-abstract-classes">8. 추상 클래스 (Abstract Classes)</h3>
<ul>
<li><p>추상 클래스는 다른 클래스들이 파생될 수 있는 기초 클래스이다.</p>
</li>
<li><p>추상 클래스는 직접 인스턴스화할 수 없다.</p>
</li>
<li><p>추상 클래스는 인터페이스와 달리 멤버에 대한 구현 세부 정보를 포함할 수 있다.</p>
</li>
<li><p><code>abstract</code> 키워드는 추상 클래스뿐만 아니라 추상 클래스 내에서 추상 메서드를 정의하는데 사용된다.</p>
</li>
</ul>
<pre><code>abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log(&quot;roaming the earth...&quot;);
    }
}</code></pre><ul>
<li><p>추상 클래스 내에서 추상으로 표시된 메서드는 구현을 포함하지 않으며 반드시 파생된 클래스에서 구현되어야 한다.</p>
</li>
<li><p>추상 메서드는 인터페이스 메서드와 비슷한 문법을 공유한다.</p>
</li>
<li><p>둘 다 메서드 본문을 포함하지 않고 메서드를 정의한다.</p>
</li>
<li><p>그러나 추상 메서드는 반드시 <code>abstract</code> 키워드를 포함해야 하며, 선택적으로 접근 지정자를 포함할 수 있다.</p>
</li>
</ul>
<pre><code>abstract class Department {

    constructor(public name: string) {
    }

    printName(): void {
        console.log(&quot;Department name: &quot; + this.name);
    }

    abstract printMeeting(): void; // 반드시 파생된 클래스에서 구현되어야 합니다.
}

class AccountingDepartment extends Department {

    constructor() {
        super(&quot;Accounting and Auditing&quot;); // 파생된 클래스의 생성자는 반드시 super()를 호출해야 합니다.
    }

    printMeeting(): void {
        console.log(&quot;The Accounting Department meets each Monday at 10am.&quot;);
    }

    generateReports(): void {
        console.log(&quot;Generating accounting reports...&quot;);
    }
}

let department: Department; // 추상 타입의 레퍼런스를 생성합니다
department = new Department(); // 오류: 추상 클래스는 인스턴스화 할 수 없습니다
department = new AccountingDepartment(); // 추상이 아닌 하위 클래스를 생성하고 할당합니다
department.printName();
department.printMeeting();
department.generateReports(); // 오류: 선언된 추상 타입에 메서드가 존재하지 않습니다</code></pre><h3 id="9-고급-기법-advanced-techniques">9. 고급 기법 (Advanced Techniques)</h3>
<p><strong>9-1. 생성자 함수 (Constructor functions)</strong></p>
<ul>
<li>TypeScript에서는 클래스를 선언하면 실제로 여러 개의 선언이 동시에 생성된다.
첫 번째로 클래스의 인스턴스 타입이다.</li>
</ul>
<pre><code>class Greeter {
    greeting: string;
    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        return &quot;Hello, &quot; + this.greeting;
    }
}

let greeter: Greeter;
greeter = new Greeter(&quot;world&quot;);
console.log(greeter.greet()); // &quot;Hello, world&quot;&quot;</code></pre><ul>
<li><p>여기서 <code>let greeter: Greeter</code> 라고 할 때, <code>Greeter</code> 클래스의 인스턴스 타입으로 <code>Greeter</code> 를 사용한다.</p>
</li>
<li><p>이것은 거의 다른 객체 지향 언어를 사용하는 프로그래머들에겐 자연스러운 성질이다.</p>
</li>
<li><p>또한 생성자 함수라고 불리는 또 다른 값을 생성하고 있다.</p>
</li>
<li><p>이것은 클래스의 인스턴스를 <code>new</code> 할 때 호출되는 함수이다.</p>
</li>
</ul>
<pre><code>let Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return &quot;Hello, &quot; + this.greeting;
    };
    return Greeter;
})();

let greeter;
greeter = new Greeter(&quot;world&quot;);
console.log(greeter.greet()); // &quot;Hello, world&quot;</code></pre><ul>
<li><p>여기서, <code>let Greeter</code> 는 생성자 함수를 할당받을 것이다.</p>
</li>
<li><p><code>new</code> 를 호출하고 이 함수를 실행할 때, 클래스의 인스턴스를 얻는다.</p>
</li>
<li><p>또한 생성자 함수는 클래스의 모든 전역 변수들을 포함하고 있다.</p>
</li>
<li><p>각 클래스를 생각하는 또 다른 방법은 인스턴스 측면과 정적 측면이 있다는 것이다.</p>
</li>
</ul>
<pre><code>class Greeter {
    static standardGreeting = &quot;Hello, there&quot;;
    greeting: string;
    greet() {
        if (this.greeting) {
            return &quot;Hello, &quot; + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet()); // &quot;Hello, there&quot;

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = &quot;Hey there!&quot;;

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet()); // &quot;Hey there!&quot;</code></pre><ul>
<li><p>이 예제에서 <code>greeter1</code> 은 이전과 비슷하게 작동한다.</p>
</li>
<li><p><code>Greeter</code> 클래스를 인스턴스화하고 이 객체를 사용한다.</p>
</li>
<li><p>클래스를 직접 사용한다.</p>
</li>
<li><p>여기서 <code>greeterMaker</code> 라는 새로운 변수를 생성한다.</p>
</li>
<li><p>이 변수는 클래스 자체를 유지하거나 생성자 함수를 다르게 설명한다.</p>
</li>
<li><p>여기서 <code>typeof Greeter</code> 를 사용하여 인스턴스 타입이 아닌 &quot;<code>Greeter</code> 클래스 자체의 타입을 제공한다&quot;.</p>
</li>
<li><p>혹은 더 정확하게 생성자 함수의 타입인 &quot;<code>Greeter</code> 라는 심볼의 타입을 제공한다&quot;.</p>
</li>
<li><p>이 타입은 <code>Greeter</code> 클래스의 인스턴스를 만드는 생성자와 함께 <code>Greeter</code> 의 모든 정적 멤버를 포함할 것이다.</p>
</li>
<li><p><code>greeterMaker</code> 에 <code>new</code> 를 사용함으로써 <code>Greeter</code> 의 새로운 인스턴스를 생성하고 이전과 같이 호출한다.</p>
</li>
</ul>
<p><strong>9-2. 인터페이스로써 클래스 사용하기 (Using a class as an interface)</strong></p>
<ul>
<li><p>클래스 선언은 클래스의 인스턴스를 나타내는 타입과 생성자 함수라는 두 가지를 생성한다.</p>
</li>
<li><p>클래스는 타입을 생성하기 때문에 인터페이스를 사용할 수 있는 동일한 위치에서 사용할 수 있다.</p>
</li>
</ul>
<pre><code>class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

let point3d: Point3d = {x: 1, y: 2, z: 3};</code></pre><hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (5)]]></title>
            <link>https://velog.io/@syo_ee/typescript05</link>
            <guid>https://velog.io/@syo_ee/typescript05</guid>
            <pubDate>Sat, 06 Jan 2024 12:15:05 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-5-유니언-타입">Chapter 5. 유니언 타입</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 유니언 타입 (Union Types)</strong>
<strong>3. 교차 타입 (Intersection Types)</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>지금까지, 핸드북은 원자 객체의 타입들을 다뤄왔다.</p>
</li>
<li><p>하지만, 더 많은 타입을 모델링할수록 처음부터 타입을 만들어내기보다는 이미 존재하는 타입을 구성하거나 결합하는 방법들을 찾게 될 것이다.</p>
</li>
<li><p>유니언 타입과 교차 타입은 타입을 구성할 수 있는 방법 중 하나이다.</p>
</li>
</ul>
<h3 id="2-유니언-타입-union-types">2. 유니언 타입 (Union Types)</h3>
<ul>
<li>가끔, <code>number</code> 나 <code>string</code> 을 매개변수로 기대하는 라이브러리를 사용할 때가 있다.</li>
</ul>
<pre><code>/**
 * 문자열을 받고 왼쪽에 &quot;padding&quot;을 추가합니다.
 * 만약 &#39;padding&#39;이 문자열이라면, &#39;padding&#39;은 왼쪽에 더해질 것입니다.
 * 만약 &#39;padding&#39;이 숫자라면, 그 숫자만큼의 공백이 왼쪽에 더해질 것입니다.
 */
function padLeft(value: string, padding: any) {
  if (typeof padding === &quot;number&quot;) {
    return Array(padding + 1).join(&quot; &quot;) + value;
  }
  if (typeof padding === &quot;string&quot;) {
    return padding + value;
  }
  throw new Error(`Expected string or number, got &#39;${padding}&#39;.`);
}

padLeft(&quot;Hello world&quot;, 4); // &quot;Hello world&quot;를 반환합니다.</code></pre><ul>
<li><p>위 예제에서 <code>padLeft</code> 의 문제는 매개변수 <code>padding</code> 이 <code>any</code> 타입으로 되어있다는 것이다.</p>
</li>
<li><p>즉, <code>number</code> 나 <code>string</code> 둘 다 아닌 인수로 함수를 호출할 수 있다는 것이고, TypeScript는 이를 괜찮다고 받아들일 것이다.</p>
</li>
</ul>
<pre><code>declare function padLeft(value: string, padding: any): string;
// ---생략---
// 컴파일 타임에는 통과하지만, 런타임에는 오류가 발생합니다.
let indentedString = padLeft(&quot;Hello world&quot;, true);</code></pre><ul>
<li><p>전통적인 객체지향 코드에서, 타입의 계층을 생성하여 두 타입을 추상화할 수 있다.
이는 더 명시적일 수는 있지만, 조금 과하다고 할 수도 있다.</p>
</li>
<li><p>기존 방법의 <code>padLeft</code> 에서 좋은 점 중 하나는 원시 값을 단지 전달할 수 있다는 것이다.</p>
</li>
<li><p>즉 사용법이 간단하고 간결하다.</p>
</li>
<li><p>또한 이 새로운 방법은 단지 다른 곳에 이미 존재하는 함수를 사용하고자 할 때 도움이 되지 않는다.</p>
</li>
<li><p><code>any</code> 대신에, 유니언 타입을 매개변수 <code>padding</code> 에 사용할 수 있다.</p>
</li>
</ul>
<pre><code>// @errors: 2345
/**
 * 문자열을 받고 왼쪽에 &quot;padding&quot;을 추가합니다.
 * 만약 &#39;padding&#39;이 문자열이라면, &#39;padding&#39;은 왼쪽에 더해질 것입니다.
 * 만약 &#39;padding&#39;이 숫자라면, 그 숫자만큼의 공백이 왼쪽에 더해질 것입니다.
 */
function padLeft(value: string, padding: string | number) {
  // ...
}

let indentedString = padLeft(&quot;Hello world&quot;, true);</code></pre><ul>
<li>유니언 타입은 여러 타입 중 하나가 될 수 있는 값을 의미합니다. 세로 막대 (<code>|</code>)로 각 타입을 구분하여, <code>number | string | boolean</code> 은 값의 타입이 <code>number, string</code> 혹은 <code>boolean</code>이 될 수 있음을 의미한다.</li>
</ul>
<p><strong>1-1. 공통 필드를 갖는 유니언 (Unions with Common Fields)</strong></p>
<ul>
<li>유니언 타입인 값이 있으면, 유니언에 있는 모든 타입에 공통인 멤버들에만 접근할 수 있습니다.</li>
</ul>
<pre><code>// @errors: 2339

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

declare function getSmallPet(): Fish | Bird;

let pet = getSmallPet();
pet.layEggs();

// 두 개의 잠재적인 타입 중 하나에서만 사용할 수 있습니다.
pet.swim();</code></pre><ul>
<li><p>여기서 유니언 타입은 약간 까다로울 수 있으나, 익숙해지는 데 약간의 직관만 있으면 된다.</p>
</li>
<li><p>만약 값이 <code>A | B</code> 타입을 갖고 있으면, 확신할 수 있는 것은 그 값이 <code>A</code> 와 <code>B</code> 둘 다 가지고 있는 멤버들을 갖고 있다는 것뿐이다.</p>
</li>
<li><p>이 예제에서, <code>Bird</code> 는 <code>fly</code> 라는 멤버를 갖고 있다.</p>
</li>
<li><p><code>Bird | Fish</code> 로 타입이 지정된 변수가 <code>fly</code> 메서드를 가지고 있는지 확신할 수 없다.</p>
</li>
<li><p>만약 변수가 실제로 런타임에 <code>Fish</code>이면, <code>pet.fly()</code> 를 호출하는 것은 오류이다.</p>
</li>
</ul>
<p><strong>1-2. 유니언 구별하기 (Discriminating Unions)</strong></p>
<ul>
<li><p>유니언을 사용하는 데 있어서 일반적인 기술은 TypeScript가 현재 가능한 타입 추론의 범위를 좁혀나가게 해줄 수 있는 리터럴 타입을 갖는 단일 필드를 사용하는 것이다.</p>
</li>
<li><p>예를 들어, 하나의 공통 필드를 가지고 있는 세 가지 타입의 유니언을 만들어보자.</p>
</li>
</ul>
<pre><code>type NetworkLoadingState = {
  state: &quot;loading&quot;;
};

type NetworkFailedState = {
  state: &quot;failed&quot;;
  code: number;
};

type NetworkSuccessState = {
  state: &quot;success&quot;;
  response: {
    title: string;
    duration: number;
    summary: string;
  };
};

// 위 타입들 중 단 하나를 대표하는 타입을 만들었지만,
// 그것이 무엇에 해당하는지 아직 확실하지 않습니다.
type NetworkState =
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState;</code></pre><ul>
<li>위 타입들은 모두 <code>state</code> 라는 필드를 갖고 있으며, 그들 각자만의 필드도 갖고 있다.</li>
</ul>
<p><code>NetworkLoadingState</code>    | <code>NetworkFailedState</code> | <code>NetworkSuccessState</code>
| --- | --- | --- |
state | state | state</p>
<ul>
<li><p>| code | response</p>
</li>
<li><p><code>state</code> 필드가 <code>NetworkState</code> 안의 모든 타입에 공통으로 존재한다는 점을 안다면 <code>-</code> 존재 여부를 체크하지 않고도 접근할 수 있다.</p>
</li>
<li><p>리터럴 타입으로서 <code>state</code> 를 갖고 있다면, <code>state</code> 의 값은 대응하는 동일한 문자열과 대조되고 TypeScript는 현재 어떤 타입이 사용되고 있는지 알 것이다.</p>
</li>
</ul>
<p><code>NetworkLoadingState</code> | <code>NetworkFailedState</code> | <code>NetworkSuccessState</code>
| --- | --- | --- |
&quot;loading&quot; | &quot;failed&quot; | &quot;success&quot;</p>
<ul>
<li>이 경우, 런타임에 나타나는 타입의 범위를 좁히기 위하여 <code>switch</code> 문을 사용할 수 있다.</li>
</ul>
<pre><code>// @errors: 2339
type NetworkLoadingState = {
  state: &quot;loading&quot;;
};

type NetworkFailedState = {
  state: &quot;failed&quot;;
  code: number;
};

type NetworkSuccessState = {
  state: &quot;success&quot;;
  response: {
    title: string;
    duration: number;
    summary: string;
  };
};
// ---생략---
type NetworkState =
  | NetworkLoadingState
  | NetworkFailedState
  | NetworkSuccessState;

function networkStatus(state: NetworkState): string {
  // 현재 TypeScript는 셋 중 어떤 것이
  // state가 될 수 있는 잠재적인 타입인지 알 수 없습니다.

  // 모든 타입에 공유되지 않는 프로퍼티에 접근하려는 시도는
  // 오류를 발생시킵니다.
  state.code;

  // state에 swtich문을 사용하여, TypeScript는 코드 흐름을 분석하면서
  // 유니언 타입을 좁혀나갈 수 있습니다.
  switch (state.state) {
    case &quot;loading&quot;:
      return &quot;Downloading...&quot;;
    case &quot;failed&quot;:
      // 여기서 타입은 NetworkFailedState일 것이며,
      // 따라서 `code` 필드에 접근할 수 있습니다.
      return `Error ${state.code} downloading`;
    case &quot;success&quot;:
      return `Downloaded ${state.response.title} - ${state.response.summary}`;
  }
}</code></pre><h3 id="3-교차-타입-intersection-types">3. 교차 타입 (Intersection Types)</h3>
<ul>
<li><p>교차 타입은 유니언 타입과 밀접한 관련이 있지만, 사용 방법은 매우 다르다.</p>
</li>
<li><p>교차 타입은 여러 타입을 하나로 결합한다.</p>
</li>
<li><p>기존 타입을 합쳐 필요한 기능을 모두 가진 단일 타입을 얻을 수 있다.</p>
</li>
<li><p>예를 들어, <code>Person &amp; Serializable &amp; Loggable</code> 은 <code>Person</code> 과 <code>Serializable</code> 그리고 <code>Loggable</code> 이다.</p>
</li>
<li><p>즉, 이 타입의 객체는 세 가지 타입의 모든 멤버를 갖게 된다.</p>
</li>
<li><p>예를 들어, 일관된 에러를 다루는 여러 네트워크 요청이 있다면 해당 에러 핸들링을 분리하여 하나의 응답 타입에 대응하는 결합된 자체 타입으로 만들 수 있다.</p>
</li>
</ul>
<pre><code>interface ErrorHandling {
  success: boolean;
  error?: { message: string };
}

interface ArtworksData {
  artworks: { title: string }[];
}

interface ArtistsData {
  artists: { name: string }[];
}

// 이 인터페이스들은
// 하나의 에러 핸들링과 자체 데이터로 구성됩니다.

type ArtworksResponse = ArtworksData &amp; ErrorHandling;
type ArtistsResponse = ArtistsData &amp; ErrorHandling;

const handleArtistsResponse = (response: ArtistsResponse) =&gt; {
  if (response.error) {
    console.error(response.error.message);
    return;
  }

  console.log(response.artists);
};</code></pre><p><strong>3-1. 교차를 통한 믹스인 (Mixins via Intersections)</strong></p>
<ul>
<li>교차는 믹스인 패턴을 실행하기 위해 사용된다.</li>
</ul>
<pre><code>class Person {
  constructor(public name: string) {}
}

interface Loggable {
  log(name: string): void;
}

class ConsoleLogger implements Loggable {
  log(name: string) {
    console.log(`Hello, I&#39;m ${name}.`);
  }
}

// 두 객체를 받아 하나로 합칩니다.
function extend&lt;First extends {}, Second extends {}&gt;(
  first: First,
  second: Second
): First &amp; Second {
  const result: Partial&lt;First &amp; Second&gt; = {};
  for (const prop in first) {
    if (first.hasOwnProperty(prop)) {
      (result as First)[prop] = first[prop];
    }
  }
  for (const prop in second) {
    if (second.hasOwnProperty(prop)) {
      (result as Second)[prop] = second[prop];
    }
  }
  return result as First &amp; Second;
}

const jim = extend(new Person(&quot;Jim&quot;), ConsoleLogger.prototype);
jim.log(jim.name);</code></pre><hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (4)]]></title>
            <link>https://velog.io/@syo_ee/typescript04</link>
            <guid>https://velog.io/@syo_ee/typescript04</guid>
            <pubDate>Wed, 03 Jan 2024 09:32:46 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-리터럴-타입">Chapter 4. 리터럴 타입</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 리터럴 타입 좁히기 (Literal Narrowing)</strong>
<strong>3. 문자열 리터럴 타입 (String Literal Types)</strong>
<strong>4. 숫자형 리터럴 타입 (Numeric Literal Types)</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>리터럴 타입은 집합 타입의 보다 구체적인 하위 타입이다.</p>
</li>
<li><p>이것이 의미하는 바는 타입 시스템 안에서 <code>&quot;Hello World&quot;</code> 는 <code>string</code> 이지만, <code>string</code> 은 <code>&quot;Hello World&quot;</code> 가 아니란 것이다.</p>
</li>
<li><p>오늘날 TypeScript에는 문자열과 숫자, 두 가지 리터럴 타입이 있는데 이를 사용하면 문자열이나 숫자에 정확한 값을 지정할 수 있다.</p>
</li>
</ul>
<h3 id="2-리터럴-타입-좁히기-literal-narrowing">2. 리터럴 타입 좁히기 (Literal Narrowing)</h3>
<ul>
<li><p><code>var</code> 또는 <code>let</code> 으로 변수를 선언할 경우 이 변수의 값이 변경될 가능성이 있음을 컴파일러에게 알린다.</p>
</li>
<li><p>반면, <code>const</code> 로 변수를 선언하게 되면 TypeScript에게 이 객체는 절대 변경되지 않음을 알린다.</p>
</li>
</ul>
<pre><code>// const를 사용하여 변수 helloWorld가
// 절대 변경되지 않음을 보장합니다.

// 따라서, TypeScript는 문자열이 아닌 &quot;Hello World&quot;로 타입을 정합니다.
const helloWorld = &quot;Hello World&quot;;

// 반면, let은 변경될 수 있으므로 컴파일러는 문자열이라고 선언할 것입니다.
let hiWorld = &quot;Hi World&quot;;</code></pre><ul>
<li>무한한 수의 잠재적 케이스들 (문자열 값은 경우의 수가 무한대)을 유한한 수의 잠재적 케이스 (<code>helloWorld</code> 의 경우: 1개)로 줄여나가는 것을 타입 좁히기(narrowing)라 한다.</li>
</ul>
<h3 id="3-문자열-리터럴-타입-string-literal-types">3. 문자열 리터럴 타입 (String Literal Types)</h3>
<ul>
<li><p>실제로 문자열 리터럴 타입은 유니언 타입, 타입 가드 그리고 타입 별칭과 잘 결합된다.</p>
</li>
<li><p>이런 기능을 함께 사용하여 문자열로 enum과 비슷한 형태를 갖출 수 있다.</p>
</li>
</ul>
<pre><code>// @errors: 2345
type Easing = &quot;ease-in&quot; | &quot;ease-out&quot; | &quot;ease-in-out&quot;;

class UIElement {
  animate(dx: number, dy: number, easing: Easing) {
    if (easing === &quot;ease-in&quot;) {
      // ...
    } else if (easing === &quot;ease-out&quot;) {
    } else if (easing === &quot;ease-in-out&quot;) {
    } else {
      // 하지만 누군가가 타입을 무시하게 된다면
      // 이곳에 도달하게 될 수 있습니다.
    }
  }
}

let button = new UIElement();
button.animate(0, 0, &quot;ease-in&quot;);
button.animate(0, 0, &quot;uneasy&quot;);</code></pre><ul>
<li>허용된 세 개의 문자열이 아닌 다른 문자열을 사용하게 되면 오류가 발생한다.</li>
</ul>
<pre><code>&#39;&quot;uneasy&quot;&#39; 타입은 &#39;&quot;ease-in&quot; | &quot;ease-out&quot; | &quot;ease-in-out&quot;&#39; 타입의 매개 변수에 할당할 수 없습니다.</code></pre><ul>
<li>문자열 리터럴 타입은 오버로드를 구별하는 것과 동일한 방법으로 사용될 수 있다.</li>
</ul>
<pre><code>function createElement(tagName: &quot;img&quot;): HTMLImageElement;
function createElement(tagName: &quot;input&quot;): HTMLInputElement;
// ... 추가적인 중복 정의들 ...
function createElement(tagName: string): Element {
  // ... 여기에 로직 추가 ...
}</code></pre><h3 id="4-숫자형-리터럴-타입-numeric-literal-types">4. 숫자형 리터럴 타입 (Numeric Literal Types)</h3>
<ul>
<li>TypeScript에는 위의 문자열 리터럴과 같은 역할을 하는 숫자형 리터럴 타입도 있다.</li>
</ul>
<pre><code>function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
  return (Math.floor(Math.random() * 6) + 1) as 1 | 2 | 3 | 4 | 5 | 6;
}

const result = rollDice();</code></pre><ul>
<li>이는 주로 설정값을 설명할 때 사용된다.</li>
</ul>
<pre><code>/** loc/lat 좌표에 지도를 생성합니다. */
declare function setupMap(config: MapConfig): void;
// ---생략---
interface MapConfig {
  lng: number;
  lat: number;
  tileSize: 8 | 16 | 32;
}

setupMap({ lng: -73.935242, lat: 40.73061, tileSize: 16 });</code></pre><hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (3)]]></title>
            <link>https://velog.io/@syo_ee/typescript03</link>
            <guid>https://velog.io/@syo_ee/typescript03</guid>
            <pubDate>Mon, 01 Jan 2024 09:33:28 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-3-함수">Chapter 3. 함수</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 함수 (Function)</strong>
<strong>3. 함수 타입 (Function Types)</strong>
<strong>4. 선택적 매개변수와 기본 매개변수 (Optional and Default Parameter)</strong>
<strong>5. 나머지 매개변수 (Rest Parameters)</strong>
<strong>6. this</strong>
<strong>7. 오버로드 (Overloads)</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>함수는 JavaScript로 된 모든 애플리케이션에서의 기본적인 구성 요소이다.</p>
</li>
<li><p>JavaScript 함수는 추상화 계층을 구축하거나 클래스 모방, 정보 은닉, 모듈에 대한 방법을 제공한다.</p>
</li>
<li><p>TypeScript에는 클래스, 네임스페이스, 모듈이 있지만, 함수는 여전히 이 일을 어떻게 할 것인지를 설명하는 데 있어 핵심 역할을 수행한다.</p>
</li>
<li><p>TypeScript에서는 표준 JavaScript 함수가 작업을 수월하게 하도록 몇 가지 새로운 기능을 추가한다.</p>
</li>
</ul>
<h3 id="2-함수-function">2. 함수 (Function)</h3>
<ul>
<li><p>TypeScript 함수는 JavaScript와 마찬가지로 기명 함수(named function)과 익명 함수(anonymous function)로 만들 수 있다.</p>
</li>
<li><p>이를 통해 API에서 함수 목록을 작성하든 일회성 함수를 써서 다른 함수로 전달하든 애플리케이션에 가장 적합한 방법을 선택할 수 있다.</p>
</li>
<li><p>JavaScript에서의 이 두 가지 방법에 대한 예시를 보자.</p>
</li>
</ul>
<pre><code>// 기명 함수
fucntion add(x, y) {
  return x + y;
}

// 익명 함수
let myAdd = function(x, y) { return x + y };</code></pre><ul>
<li><p>JavaScript에서처럼, 함수는 함수 외부의 변수를 참조할 수 있습니다. 이런 경우를, 변수를 <em>캡처</em> (capture) 한다고 한다.</p>
</li>
<li><p>이것이 어떻게 작동하는지 (그리고 이 기술을 사용할 때의 장단점)를 이해하는 것은 이 본문의 주제를 벗어나는 것이지만, 이 메커니즘이 어떻게 작동하는지에 대한 확실한 이해는 JavaScript 및 TypeScript를 사용하는 데 있어 중요하다.</p>
</li>
</ul>
<pre><code>let z = 100;

function addToZ(x, y) {
  return x + y + z;
}</code></pre><h3 id="3-함수-타입-function-types">3. 함수 타입 (Function Types)</h3>
<p><strong>3-1. 함수의 타이핑 (Typing the function)</strong></p>
<ul>
<li>이전에 사용했던 예시에 타입을 더해보자.</li>
</ul>
<pre><code>function add(x: number, y: number): number {
    return x + y;
}

let myAdd = function(x: number, y: number): number { return x + y };</code></pre><ul>
<li><p>각 파라미터와 함수 자신의 반환될 타입을 정해줄 수 있다.</p>
</li>
<li><p>TypeScript는 반환 문을 보고 반환 타입을 파악할 수 있으므로 반환 타입을 생략할 수 있다.</p>
</li>
</ul>
<p><strong>3-2. 함수 타입 작성하기 (Writing the function type)</strong></p>
<ul>
<li>함수에 타입을 붙였으니, 이제 함수 타입들을 살펴보고 함수의 전체 타입을 작성해 보자.</li>
</ul>
<pre><code>let myAdd: (x: number, y: number) =&gt; number =
    function(x: number, y: number): number { return x + y; };</code></pre><ul>
<li><p>함수의 타입은 매개변수의 타입과 반환 타입이 있다.</p>
</li>
<li><p>전체 함수 타입을 작성하고자 한다면 이 두 가지 타입이 필요하다.</p>
</li>
<li><p>매개변수 목록처럼 각 매개변수에 이름과 타입 타입을 작성해 주자.</p>
</li>
<li><p>작성하는 이름은 가독성을 위한 것이다.</p>
</li>
<li><p>위의 코드 대신 이렇게 쓸 수도 있다.</p>
</li>
</ul>
<pre><code>let myAdd: (baseValue: number, increment: number) =&gt; number =
    function(x: number, y: number): number { return x + y; };</code></pre><ul>
<li><p>매개변수의 타입들이 올바르게 나열되어 있다면 함수 타입에 이름을 붙이더라도 유효한 타입으로 간주한다.</p>
</li>
<li><p>두 번째로 반환 타입이다.
매개변수 타입들과 반환 타입 사이에 &#39;화살표 표기&#39;( <code>=&gt;</code> )를 써서 반환 타입을 분명히 할 수 있다.</p>
</li>
<li><p>이전에 언급했듯이, 함수 표기에 필요한 부분이다.
그래서 만약 함수가 값을 반환하지 않는다면 비워두는 대신 <code>void</code> 를 써서 표시한다.</p>
</li>
<li><p>참고로, 매개변수 타입과 반환 타입만이 함수 타입을 구성한다.</p>
</li>
<li><p>캡처된 변수는 타입에 반영되지 않는다.</p>
</li>
<li><p>사실상 캡처된 변수는 함수의 &quot;숨겨진 상태&quot;의 일부이고 API를 구성하지 않는다.</p>
</li>
</ul>
<p><strong>3-3. 타입의 추론 (Inferring the types)</strong></p>
<ul>
<li>TypeScript 컴파일러가 방정식의 한쪽에만 타입이 있더라도 타입을 알아낼 수 있다.</li>
</ul>
<pre><code>// myAdd는 전체 함수 타입을 가집니다
let myAdd = function(x: number, y: number): number { return  x + y; };

// 매개변수 x 와 y는 number 타입을 가집니다
let myAdd: (baseValue: number, increment: number) =&gt; number =
    function(x, y) { return x + y; };</code></pre><ul>
<li><p>이러한 타입 추론 형태를 &quot;contextual typing&quot; 이라 부른다.</p>
</li>
<li><p>이를 통해 여러분의 프로그램에서 타입을 유지하기 위한 노력을 줄일 수 있다.</p>
</li>
</ul>
<h3 id="4-선택적-매개변수와-기본-매개변수-optional-and-default-parameter">4. 선택적 매개변수와 기본 매개변수 (Optional and Default Parameter)</h3>
<ul>
<li><p>TypeScript에서는 모든 매개변수가 함수에 필요하다고 가정한다.</p>
</li>
<li><p>이것이 <code>null</code> 이나 <code>undefined</code> 를 줄 수 없다는 걸 의미하는 것은 아니다.</p>
</li>
<li><p>대신 함수가 호출될 때, 컴파일러는 각 매개변수에 대해 사용자가 값을 제공했는지를 검사한다.</p>
</li>
<li><p>또한, 컴파일러는 매개변수들이 함수로 전달될 유일한 매개변수라고 가정한다.</p>
</li>
<li><p>요약하자면, <em>함수에 주어진 인자의 수는 함수가 기대하는 매개변수의 수와 일치해야 한다.</em></p>
</li>
</ul>
<pre><code>function buildName(firstName: string, lastName: string) {
    return firstName + &quot; &quot; + lastName;
}

let result1 = buildName(&quot;Bob&quot;);                  // 오류, 너무 적은 매개변수
let result2 = buildName(&quot;Bob&quot;, &quot;Adams&quot;, &quot;Sr.&quot;);  // 오류, 너무 많은 매개변수
let result3 = buildName(&quot;Bob&quot;, &quot;Adams&quot;);         // 정확함</code></pre><ul>
<li><p>JavaScript에서는 모든 매개변수가 선택적이고, 사용자는 적합하다고 생각하면 그대로 둘 수 있다.</p>
</li>
<li><p>그렇게 둔다면 그 값은 <code>undefined</code> 가 된다.</p>
</li>
<li><p>TypeScript에서도 선택적 매개변수를 원한다면 매개변수 이름 끝에 <code>?</code> 를 붙임으로써 해결할 수 있다.</p>
</li>
<li><p>그 예시로 성을 선택적 매개변수로 하는 경우를 들어보자.</p>
</li>
</ul>
<pre><code>function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + &quot; &quot; + lastName;
    else
        return firstName;
}

let result1 = buildName(&quot;Bob&quot;);                  // 지금은 바르게 동작
let result2 = buildName(&quot;Bob&quot;, &quot;Adams&quot;, &quot;Sr.&quot;);  // 오류, 너무 많은 매개변수
let result3 = buildName(&quot;Bob&quot;, &quot;Adams&quot;);         // 정확함</code></pre><ul>
<li><p>어느 선택적 매개변수든 반드시 매개변수 정의가 필요하다.</p>
</li>
<li><p><code>lastName</code> 대신 <code>firstName</code> 을 선택적으로 하고 싶다면 매개변수의 순서를 변경해야 한다.</p>
</li>
<li><p>TypeScript에서는 유저가 값을 제공하지 않거나 <code>undefined</code>로 했을 때에 할당될 매개변수의 값을 정해 놓을 수도 있다.</p>
</li>
<li><p>이것을 <code>기본-초기화 매개변수</code>라고 한다.</p>
</li>
<li><p>이전 예시에서 <code>lastName</code> 을 <code>&quot;Smith&quot;</code> 라고 지정해 보자.</p>
</li>
</ul>
<pre><code>function buildName(firstName: string, lastName = &quot;Smith&quot;) {
    return firstName + &quot; &quot; + lastName;
}

let result1 = buildName(&quot;Bob&quot;);                  // 올바르게 동작, &quot;Bob Smith&quot; 반환
let result2 = buildName(&quot;Bob&quot;, undefined);       // 여전히 동작, 역시 &quot;Bob Smith&quot; 반환
let result3 = buildName(&quot;Bob&quot;, &quot;Adams&quot;, &quot;Sr.&quot;);  // 오류, 너무 많은 매개변수
let result4 = buildName(&quot;Bob&quot;, &quot;Adams&quot;);         // 정확함</code></pre><ul>
<li><p>모든 필수 매개변수 뒤에 오는 <code>기본-초기화 매개변수</code> 는 선택적으로 처리되며, 선택적 매개변수와 마찬가지로 해당 함수를 호출할 때 생략할 수 있다.</p>
</li>
<li><p>이는 선택적 매개변수와 뒤따르는 기본 매개변수의 타입들이 공통성을 공유함을 의미한다.</p>
</li>
</ul>
<pre><code>function buildName(firstName: string, lastName?: string) {
    // ...
}
와

function buildName(firstName: string, lastName = &quot;Smith&quot;) {
    // ...
}</code></pre><ul>
<li><p><code>(firstName: string, lastName?: string) =&gt; string</code> 라는 공통된 타입을 공유한다.</p>
</li>
<li><p>lastName의 기본값은 타입에서 사라지고 오직 선택적 매개변수라는 사실만 남깁니다.</p>
</li>
<li><p>순수한 선택적 매개변수와는 다르게 <code>기본-초기화 매개변수</code> 는 필수 매개변수 뒤에 오는 것이 강요되지 않는다.</p>
</li>
<li><p>만약 <code>기본-초기화 매개변수</code> 가 필수 매개변수보다 앞에 오게 된다면 사용자가 명시적으로 <code>undefined</code> 를 전달해 주어야 <code>기본-초기화 매개변수</code> 를 볼 수 있다.</p>
</li>
<li><p>앞서 사용했던 예시에 기본 초기화를 <code>firstName</code> 에 적용한 것이다.</p>
</li>
</ul>
<pre><code>function buildName(firstName = &quot;Will&quot;, lastName: string) {
    return firstName + &quot; &quot; + lastName;
}

let result1 = buildName(&quot;Bob&quot;);                  // 오류, 너무 적은 매개변수
let result2 = buildName(&quot;Bob&quot;, &quot;Adams&quot;, &quot;Sr.&quot;);  // 오류, 너무 많은 매개변수
let result3 = buildName(&quot;Bob&quot;, &quot;Adams&quot;);         // 성공, &quot;Bob Adams&quot; 반환
let result4 = buildName(undefined, &quot;Adams&quot;);     // 성공, &quot;Will Adams&quot; 반환</code></pre><h3 id="5-나머지-매개변수-rest-parameters">5. 나머지 매개변수 (Rest Parameters)</h3>
<ul>
<li><p>필수, 선택적, 기본 매개변수는 한 번에 하나의 매개변수만을 가지고 이야기한다.</p>
</li>
<li><p>때로는 다수의 매개변수를 그룹 지어 작업하기를 원하거나, 함수가 최종적으로 얼마나 많은 매개변수를 취할지 모를 때도 있을 것이다.</p>
</li>
<li><p>JavaScript에서는 모든 함수 내부에 위치한 <code>arguments</code> 라는 변수를 사용해 직접 인자를 가지고 작업할 수 있다.</p>
</li>
<li><p>TypeScript에서는 이 인자들을 하나의 변수로 모을 수 있다.</p>
</li>
</ul>
<pre><code>function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + &quot; &quot; + restOfName.join(&quot; &quot;);
}

// employeeName 은 &quot;Joseph Samuel Lucas MacKinzie&quot; 가 될것입니다.
let employeeName = buildName(&quot;Joseph&quot;, &quot;Samuel&quot;, &quot;Lucas&quot;, &quot;MacKinzie&quot;);</code></pre><ul>
<li><p>나머지 매개변수는 선택적 매개변수들의 수를 무한으로 취급한다.</p>
</li>
<li><p>나머지 매개변수로 인자들을 넘겨줄 때는 당신이 원하는 만큼 넘겨 줄 수도 있다.
아무것도 넘겨주지 않을 수도 있다.</p>
</li>
<li><p>컴파일러는 생략 부호 (<code>...</code>) 뒤의 이름으로 전달된 인자 배열을 빌드하여 함수에서 사용할 수 있도록 한다.</p>
</li>
<li><p>생략 부호는 나머지 매개변수가 있는 함수의 타입에도 사용된다.</p>
</li>
</ul>
<pre><code>function buildName(firstName: string, ...restOfName: string[]) {
    return firstName + &quot; &quot; + restOfName.join(&quot; &quot;);
}

let buildNameFun: (fname: string, ...rest: string[]) =&gt; string = buildName;</code></pre><h3 id="6-this">6. this</h3>
<ul>
<li><p>this 가 JavaScript에서 어떻게 쓰이는지 아는 것은 일종의 통과의례이다.</p>
</li>
<li><p>TypeScript는 JavaScript의 상위 집합이므로 TypeScript 개발자들 역시 this가 어떻게 쓰이는지 또는 this가 잘못 쓰일 때를 발견하는 방법을 배울 필요가 있다.</p>
</li>
<li><p>다행히도 TypeScript는 몇 가지 기술들로 잘못된 this 사용을 잡아낼 수 있다.</p>
</li>
<li><p>만약 JavaScript에서 this가 어떻게 동작하는지 알고 싶다면 먼저 Yehuda Katz의 글 <a href="https://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/">JavaScript 함수 호출과 &quot;this&quot; 이해하기</a> 을 참고하도록 하자.
Yehuda의 글은 this의 내부 동작을 아주 잘 설명한다.</p>
</li>
</ul>
<p><strong>6-1. <code>this</code> 와 화살표 함수 (this and arrow functions)</strong></p>
<ul>
<li><p>JavaScript에서, this는 함수가 호출될 때 정해지는 변수이다.</p>
</li>
<li><p>매우 강력하고 유연한 기능이지만 이것은 항상 함수가 실행되는 콘텍스트에 대해 알아야 한다는 수고가 생긴다.</p>
</li>
<li><p>특히 함수를 반환하거나 인자로 넘길 때의 혼란스러움은 악명높다.</p>
</li>
</ul>
<pre><code>let deck = {
    suits: [&quot;hearts&quot;, &quot;spades&quot;, &quot;clubs&quot;, &quot;diamonds&quot;],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert(&quot;card: &quot; + pickedCard.card + &quot; of &quot; + pickedCard.suit);</code></pre><ul>
<li><p><code>createCardPicker</code> 가 자기 자신을 반환하는 함수임을 주목해보자.</p>
</li>
<li><p>이 예제를 작동시키면 기대했던 경보 창 대신 오류가 발생할 것이다.</p>
</li>
<li><p><code>createCardPicker</code> 에 의해 생성된 함수에서 사용 중인 <code>this</code> 가 <code>deck</code> 객체가 아닌 <code>window</code> 에 설정되었기 때문이다.</p>
</li>
<li><p><code>cardPicker()</code> 의 자체적인 호출 때문에 생긴 일이다.</p>
</li>
<li><p>최상위 레벨에서의 비-메서드 문법의 호출은 <code>this</code> 를 <code>window</code> 로 한다. (Note: strict mode에서는 this가 window 대신 undefined 가 됩니다. )</p>
</li>
<li><p>이 문제는 나중에 사용할 함수를 반환하기 전에 바인딩을 알맞게 하는 것으로 해결할 수 있다.</p>
</li>
<li><p>이 방법대로라면 나중에 사용하는 방법에 상관없이 원본 deck 객체를 계속해서 볼 수 있다.</p>
</li>
<li><p>이를 위해, 함수 표현식을 ES6의 화살표 함수로 바꿀 것이다.</p>
</li>
<li><p>화살표 함수는 함수가 호출 된 곳이 아닌 함수가 생성된 쪽의 this를 캡처한다.</p>
</li>
</ul>
<pre><code>let deck = {
    suits: [&quot;hearts&quot;, &quot;spades&quot;, &quot;clubs&quot;, &quot;diamonds&quot;],
    cards: Array(52),
    createCardPicker: function() {
        // NOTE: 아랫줄은 화살표 함수로써, &#39;this&#39;를 이곳에서 캡처할 수 있도록 한다
        return () =&gt; {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

alert(&quot;card: &quot; + pickedCard.card + &quot; of &quot; + pickedCard.suit);</code></pre><ul>
<li><p><code>--noImplicitThis</code> 플래그를 컴파일러에 전달하는 실수를 하게 된다면 TypeScript는 경고를 표시할 것이다.</p>
</li>
<li><p><code>this.suits[pickedSuit]</code> 의 <code>this</code> 는 <code>any</code> 타입인 것을 짚고 넘어가자.</p>
</li>
</ul>
<p><strong>6-2. <code>this</code> 매개변수 (this parameter)</strong></p>
<ul>
<li><p><code>this.suits[pickedSuit]</code> 의 타입은 여전히 <code>any</code>이다.</p>
</li>
<li><p><code>this</code> 가 객체 리터럴 내부의 함수에서 왔기 때문이다.</p>
</li>
<li><p>이것을 고치기 위해 명시적으로 <code>this</code> 매개변수를 줄 수 있다.</p>
</li>
<li><p><code>this</code> 매개변수는 함수의 매개변수 목록에서 가장 먼저 나오는 가짜 매개변수이다.</p>
<pre><code></code></pre></li>
</ul>
<p>function f(this: void) {
    // 독립형 함수에서 <code>this</code>를 사용할 수 없는 것을 확인함
}</p>
<pre><code>
- 명확하고 재사용하기 쉽게 ```Card``` 와 ```Deck``` 두 가지 인터페이스 타입들의 예시보자.
</code></pre><p>interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () =&gt; Card;
}
let deck: Deck = {
    suits: [&quot;hearts&quot;, &quot;spades&quot;, &quot;clubs&quot;, &quot;diamonds&quot;],
    cards: Array(52),
    // NOTE: 아래 함수는 이제 callee가 반드시 Deck 타입이어야 함을 명시적으로 지정합니다.
    createCardPicker: function(this: Deck) {
        return () =&gt; {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);</p>
<pre><code>        return {suit: this.suits[pickedSuit], card: pickedCard % 13};
    }
}</code></pre><p>}</p>
<p>let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();</p>
<p>alert(&quot;card: &quot; + pickedCard.card + &quot; of &quot; + pickedCard.suit);</p>
<pre><code>
- 이제 TypeScript는 ```createCardPicker```가 ```Deck``` 객체에서 호출된다는 것을 알게 됐다.

- 이것은 ```this``` 가 ```any``` 타입이 아니라 ```Deck``` 타입이며 따라서 ```--noImplicitThis``` 플래그가 어떤 오류도 일으키지 않는다는 것을 의미한다.

**6-3. 콜백에서 ```this``` 매개변수 (this parameters in callbacks)**

- 나중에 호출할 콜백 함수를 라이브러리에 전달할 때 ```this``` 때문에 오류가 발생할 수 있다.

- 라이브러리는 콜백을 일반 함수처럼 호출하므로 ```this``` 는 ```undefined``` 가 된다.

- 일부 작업에서는 ```this``` 매개변수를 콜백 오류를 막는데 사용할 수 있다.

- 먼저 라이브러리 작성자는 콜백 타입을 ```this``` 로 표시를 해주어야 한다.
</code></pre><p>interface UIElement {
    addClickListener(onclick: (this: void, e: Event) =&gt; void): void;
}
this: void는 addClickListener가 onclick이 this타입을 요구하지 않는 함수가 될 것으로 예상하는 것을 의미합니다. 두 번째로, 호출 코드를 this로 표시합니다.</p>
<p>class Handler {
    info: string;
    onClickBad(this: Handler, e: Event) {
        // 이런, <code>this</code>가 여기서 쓰이는군요. 이 콜백을 쓰면 런타임에서 충돌을 일으키겠군요.
        this.info = e.message;
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // 오류!</p>
<pre><code>
- ```this``` 로 표시를 한 상태에서 ```onClickBad``` 가 반드시 ```Handler``` 의 인스턴스로써 호출되어야 함을 명시해 주어야 한다.

- 그러면 TypeScript는 ```addClickListener``` 가 ```this: void``` 를 갖는 함수를 필요로 한다는 것을 감지한다.

- 오류를 고치기 위해 ```this``` 의 타입을 바꿔준다.
</code></pre><p>class Handler {
    info: string;
    onClickGood(this: void, e: Event) {
        // void 타입이기 때문에 this는 이곳에서 쓸 수 없습니다!
        console.log(&#39;clicked!&#39;);
    }
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);</p>
<pre><code>
- ```onClickGood``` 이 ```this``` 타입을 ```void``` 로 지정하고 있기 때문에 ```addClickListener``` 로 넘겨지는데 적합하다.

- 물론, 이것이 ```this.info``` 를 쓸 수 없는 것을 의미하기도 하다.

- 만약 당신이 ```this.info``` 까지 원한다면 화살표 함수를 사용해야 할 것이다.
</code></pre><p>class Handler {
    info: string;
    onClickGood = (e: Event) =&gt; { this.info = e.message }
}</p>
<pre><code>
- 이러한 작업은 화살표 함수가 외부의 ```this``` 를 사용하기 때문에 가능하므로 ```this: void``` 일 것으로 기대하는 무언가라면 전달에 문제가 없다.

- ```Handler``` 타입 객체마다 하나의 화살표 함수가 작성된다는 점이 단점이다.

- 반면, 메서드들은 하나만 작성되어 ```Handler``` 의 프로토타입에 붙는다.

- 그들은 ```Handler``` 타입의 모든 객체 간에 공유된다.


### 7. 오버로드 (Overloads)

- JavaScript는 본질적으로 매우 동적인 언어이다.

- 하나의 JavaScript 함수가 전달된 인자의 형태에 따라 다른 타입의 객체들을 반환하는 것은 흔한 일이다.
</code></pre><p>let suits = [&quot;hearts&quot;, &quot;spades&quot;, &quot;clubs&quot;, &quot;diamonds&quot;];</p>
<p>function pickCard(x): any {
    // 인자가 배열 또는 객체인지 확인
    // 만약 그렇다면, deck이 주어지고 card를 선택합니다.
    if (typeof x == &quot;object&quot;) {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // 그렇지 않다면 그냥 card를 선택합니다.
    else if (typeof x == &quot;number&quot;) {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}</p>
<p>let myDeck = [{ suit: &quot;diamonds&quot;, card: 2 }, { suit: &quot;spades&quot;, card: 10 }, { suit: &quot;hearts&quot;, card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert(&quot;card: &quot; + pickedCard1.card + &quot; of &quot; + pickedCard1.suit);</p>
<p>let pickedCard2 = pickCard(15);
alert(&quot;card: &quot; + pickedCard2.card + &quot; of &quot; + pickedCard2.suit);</p>
<pre><code>
- 여기 사용자가 전달하는 것에 따라 두 가지 다른 결과를 반환하는 함수가 있다.

- 사용자가 ```deck``` 을 의미하는 객체 값을 전달한다면 함수가 카드를 선택한다.

- 사용자가 카드를 선택하면 선택한 카드가 무엇인지 대답해 준다.
하지만 타입 시스템에서 이것을 어떻게 구현할까?

- 정답은 오버로드 목록으로 동일한 함수에 다중 함수 타입을 제공하는 것이다.

- 오버로드 목록은 컴파일러가 함수 호출들을 해결할 때 사용하는 것이다.

- 오버로드 목록으로 ```pickCard```가 동작을 승인하고 반환하는 것을 구현해 보자.
</code></pre><p>let suits = [&quot;hearts&quot;, &quot;spades&quot;, &quot;clubs&quot;, &quot;diamonds&quot;];</p>
<p>function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // 인자가 배열 또는 객체인지 확인
    // 만약 그렇다면, deck이 주어지고 card를 선택합니다.
    if (typeof x == &quot;object&quot;) {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // 그렇지 않다면 그냥 card를 선택합니다.
    else if (typeof x == &quot;number&quot;) {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}</p>
<p>let myDeck = [{ suit: &quot;diamonds&quot;, card: 2 }, { suit: &quot;spades&quot;, card: 10 }, { suit: &quot;hearts&quot;, card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert(&quot;card: &quot; + pickedCard1.card + &quot; of &quot; + pickedCard1.suit);</p>
<p>let pickedCard2 = pickCard(15);
alert(&quot;card: &quot; + pickedCard2.card + &quot; of &quot; + pickedCard2.suit);</p>
<p>```</p>
<ul>
<li><p>이 변화를 통해, 오버로드는 <code>pickCard</code> 함수에 대해 타입 검사 호출을 제공한다.</p>
</li>
<li><p>컴파일러가 알맞은 타입 검사를 하기 위해서, JavaScript와 비슷한 프로세스를 따른다.</p>
</li>
<li><p>오버로드 목록에서 첫 번째 오버로드를 진행하면서 제공된 매개변수를 사용하여 함수를 호출하려고 시도한다.</p>
</li>
<li><p>만약 일치하게 된다면 해당 오버로드를 알맞은 오버로드로 선택하여 작업을 수행한다.</p>
</li>
<li><p>이러한 이유로 가장 구체적인 것부터 오버로드 리스팅을 하는 것이 일반적이다.</p>
</li>
<li><p>위 예제에서 <code>function pickCard(x): any</code> 는 오버로드 목록에 해당되지 않음을 유의하라.</p>
</li>
<li><p>그래서 두 가지 오버로드만을 가진다.
객체를 받는것 하나와 숫자를 받는 것 하나, 다른 매개변수 타입으로 <code>pickCard</code> 를 호출하는 것은 오류가 발생한다.</p>
</li>
</ul>
<hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
<h3 id="yehudas-blog">Yehuda&#39;s Blog</h3>
<p><a href="https://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/">https://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (2)]]></title>
            <link>https://velog.io/@syo_ee/typescript02</link>
            <guid>https://velog.io/@syo_ee/typescript02</guid>
            <pubDate>Fri, 15 Dec 2023 18:22:27 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-2-인터페이스">Chapter 2. 인터페이스</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 첫 번째 인터페이스 (Our First Interface)</strong>
<strong>3. 선택적 프로퍼티 (Optional Properties)</strong>
<strong>4. 읽기전용 프로퍼티 (Readonly properties)</strong>
<strong>5. 초과 프로퍼티 검사 (Excess Property Checks)</strong>
<strong>6. 함수 타입 (Function Types)</strong>
<strong>7. 인덱서블 타입 (Indexable Types)</strong>
<strong>8. 클래스 타입 (Class Types)</strong>
<strong>9. 클래스의 스태틱과 인스턴스의 차이점
(Difference between the static and instance sides of classes)</strong>
<strong>10. 인터페이스 확장하기 (Extending Interfaces)</strong>
<strong>11. 하이브리드 타입 (Hybrid Types)</strong>
<strong>12. 클래스를 확장한 인터페이스 (Interfaces Extending Classes)</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>TypeScript의 핵심 원칙 중 하나는 타입 검사가 값의 <em>형태</em> 에 초점을 맞추고 있다는 것이다.</p>
</li>
<li><p>이를 &quot;<a href="https://ko.wikipedia.org/wiki/%EB%8D%95_%ED%83%80%EC%9D%B4%ED%95%91">덕 타이핑(duck typing)</a>&quot; 혹은 &quot;<a href="https://en.wikipedia.org/wiki/Structural_type_system">구조적 서브타이핑 (structural subtyping)</a>&quot;이라고도 한다.</p>
</li>
<li><p>TypeScript에서, 인터페이스는 이런 타입들의 이름을 짓는 역할을 하고 코드 안의 계약을 정의하는 것뿐만 아니라 프로젝트 외부에서 사용하는 코드의 계약을 정의하는 강력한 방법이다.</p>
</li>
</ul>
<h3 id="2-첫-번째-인터페이스-our-first-interface">2. 첫 번째 인터페이스 (Our First Interface)</h3>
<ul>
<li>어떻게 인터페이스가 동작하는지 확인하는 가장 쉬운 방법은 간단한 예제로 시작하는 것이다.</li>
</ul>
<pre><code>function printLabel(labeledObj: { label: string }) {
    console.log(labeledObj.label);
}

let myObj = {size: 10, label: &quot;Size 10 Object&quot;};
printLabel(myObj);</code></pre><ul>
<li><p>타입 검사는 <code>printLabel</code> 호출을 확인한다.</p>
</li>
<li><p><code>printLabel</code> 함수는 <code>string</code> 타입 <code>label</code> 을 갖는 객체를 하나의 매개변수로 가진다.</p>
</li>
<li><p>이 객체가 실제로는 더 많은 프로퍼티를 갖고 있지만, 컴파일러는 <em>최소한</em> 필요한 프로퍼티가 있는지와 타입이 잘 맞는지만 검사한다.</p>
</li>
<li><p>이번엔 같은 예제를, 문자열 타입의 프로퍼티 <code>label</code> 을 가진 인터페이스로 다시 작성해 보겠습니다:</p>
</li>
</ul>
<pre><code>interface LabeledValue {
    label: string;
}

function printLabel(labeledObj: LabeledValue) {
    console.log(labeledObj.label);
}

let myObj = {size: 10, label: &quot;Size 10 Object&quot;};
printLabel(myObj);</code></pre><ul>
<li><p><code>LabeledValue</code> 인터페이스는 이전 예제의 요구사항을 똑같이 기술하는 이름으로 사용할 수 있다.</p>
</li>
<li><p>이 인터페이스는 여전히 문자열 타입의 <code>label</code> 프로퍼티 하나를 가진다는 것을 의미한다.</p>
</li>
<li><p>다른 언어처럼 <code>printLabel</code> 에 전달한 객체가 이 인터페이스를 구현해야 한다고 명시적으로 얘기할 필요는 없다.
여기서 중요한 것은 형태뿐입니다.</p>
</li>
<li><p>함수에 전달된 객체가 나열된 요구 조건을 충족하면, 허용된다.</p>
</li>
<li><p>타입 검사는 프로퍼티들의 순서를 요구하지 않는다.</p>
</li>
<li><p>단지 인터페이스가 요구하는 프로퍼티들이 존재하는지와 프로퍼티들이 요구하는 타입을 가졌는지만을 확인한다.</p>
</li>
</ul>
<h3 id="3-선택적-프로퍼티-optional-properties">3. 선택적 프로퍼티 (Optional Properties)</h3>
<ul>
<li><p>인터페이스의 모든 프로퍼티가 필요한 것은 아니다.</p>
</li>
<li><p>어떤 조건에서만 존재하거나 아예 없을 수도 있다.</p>
</li>
<li><p>선택적 프로퍼티들은 객체 안의 몇 개의 프로퍼티만 채워 함수에 전달하는 &quot;option bags&quot; 같은 패턴을 만들 때 유용하다.</p>
</li>
</ul>
<pre><code>interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: &quot;white&quot;, area: 100};
    if (config.color) {
        newSquare.color = config.color;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: &quot;black&quot;});</code></pre><ul>
<li><p>선택적 프로퍼티를 가지는 인터페이스는 다른 인터페이스와 비슷하게 작성되고, 선택적 프로퍼티는 선언에서 프로퍼티 이름 끝에 <code>?</code> 를 붙여 표시합니다.</p>
</li>
<li><p>선택적 프로퍼티의 이점은 인터페이스에 속하지 않는 프로퍼티의 사용을 방지하면서, 사용 가능한 속성을 기술하는 것이다.</p>
</li>
<li><p>예를 들어, <code>createSquare</code>안의 <code>color</code> 프로퍼티 이름을 잘못 입력하면, 오류 메시지로 알려준다.</p>
</li>
</ul>
<pre><code>interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    let newSquare = {color: &quot;white&quot;, area: 100};
    if (config.clor) {
        // Error: Property &#39;clor&#39; does not exist on type &#39;SquareConfig&#39;
        newSquare.color = config.clor;
    }
    if (config.width) {
        newSquare.area = config.width * config.width;
    }
    return newSquare;
}

let mySquare = createSquare({color: &quot;black&quot;});</code></pre><h3 id="4-읽기전용-프로퍼티-readonly-properties">4. 읽기전용 프로퍼티 (Readonly properties)</h3>
<ul>
<li><p>일부 프로퍼티들은 객체가 처음 생성될 때만 수정 가능해야한다.</p>
</li>
<li><p>프로퍼티 이름 앞에 readonly를 넣어서 이를 지정할 수 있다.</p>
</li>
</ul>
<pre><code>interface Point {
    readonly x: number;
    readonly y: number;
}</code></pre><ul>
<li><p>객체 리터럴을 할당하여 Point를 생성한다.</p>
</li>
<li><p>할당 후에는 x, y를 수정할 수 없다.</p>
</li>
</ul>
<pre><code>let p1: Point = { x: 10, y: 20 };
p1.x = 5; // 오류!
TypeScript에서는 모든 변경 메서드(Mutating Methods)가 제거된 Array&lt;T&gt;와 동일한 ReadonlyArray&lt;T&gt; 타입을 제공합니다. 그래서 생성 후에 배열을 변경하지 않음을 보장할 수 있습니다.

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray&lt;number&gt; = a;
ro[0] = 12; // 오류!
ro.push(5); // 오류!
ro.length = 100; // 오류!
a = ro; // 오류!</code></pre><ul>
<li><p>예제 마지막 줄에서 <code>ReadonlyArray</code>를 일반 배열에 재할당이 불가능한 것을 확인할 수 있다.</p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Assertions">타입 단언(type assertion)</a>으로 오버라이드하는 것은 가능하다.</p>
</li>
</ul>
<pre><code>a = ro as number[];</code></pre><blockquote>
<p><span style="color:coral">KeyNote</span></p>
</blockquote>
<ul>
<li><strong>readonly vs const</strong>
<code>readonly</code>와 <code>const</code> 중에 어떤 것을 사용할 지 기억하기 가장 쉬운 방법은 변수와 프로퍼티중 어디에 사용할지 질문해 보는 것이다.</li>
<li>변수는 const를 사용하고 프로퍼티는 <code>readonly</code>를 사용한다.</li>
</ul>
<h3 id="5-초과-프로퍼티-검사-excess-property-checks">5. 초과 프로퍼티 검사 (Excess Property Checks)</h3>
<ul>
<li><p>인터페이스의 첫 번째 예제에서 TypeScript가 { <code>label: string;</code> }을 기대해도 { <code>size: number; label: string;</code> }를 허용해주었다.</p>
</li>
<li><p>또한 선택적 프로퍼티를 배우고, 소위 &quot;option bags&quot;을 기술할 때, 유용하다는 것을 배웠다.</p>
</li>
<li><p>하지만, 그냥 그 둘을 결합하면 에러가 발생할 수 있다.</p>
</li>
</ul>
<pre><code>interface SquareConfig {
    color?: string;
    width?: number;
}

function createSquare(config: SquareConfig): { color: string; area: number } {
    // ...
}

let mySquare = createSquare({ colour: &quot;red&quot;, width: 100 });</code></pre><ul>
<li><p><code>createSquare</code> 의 매개변수가 <code>color</code> 대신 <code>colour</code> 로 전달된 것에 유의하자.
이 경우 JavaScript에선 조용히 오류가 발생합니다.</p>
</li>
<li><p><code>width</code> 프로퍼티는 적합하고, <code>color</code> 프로퍼티는 없고, 추가 <code>colour</code> 프로퍼티는 중요하지 않기 때문에, 이 프로그램이 올바르게 작성되었다고 생각할 수 있다.</p>
</li>
<li><p>하지만, TypeScript는 이 코드에 버그가 있을 수 있다고 생각한다.</p>
</li>
<li><p>객체 리터럴은 다른 변수에 할당할 때나 인수로 전달할 때, 특별한 처리를 받고, 초과 프로퍼티 검사 (excess property checking)를 받는다.</p>
</li>
<li><p>만약 객체 리터럴이 &quot;대상 타입 (target type)&quot;이 갖고 있지 않은 프로퍼티를 갖고 있으면, 에러가 발생한다.</p>
</li>
</ul>
<pre><code>// error: Object literal may only specify known properties, but &#39;colour&#39; does not exist in type &#39;SquareConfig&#39;. Did you mean to write &#39;color&#39;?
let mySquare = createSquare({ colour: &quot;red&quot;, width: 100 });</code></pre><ul>
<li><p>이 검사를 피하는 방법은 정말 간단하다.</p>
</li>
<li><p>가장 간단한 방법은 타입 단언을 사용하는 것이다.</p>
</li>
</ul>
<pre><code>let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);</code></pre><ul>
<li><p>하지만 특별한 경우에, 추가 프로퍼티가 있음을 확신한다면, 문자열 인덱스 서명(string index signatuer)을 추가하는 것이 더 나은 방법이다.</p>
</li>
<li><p>만약 <code>SquareConfig color</code> 와 <code>width</code> 프로퍼티를 위와 같은 타입으로 갖고 있고, 또한 다른 프로퍼티를 가질 수 있다면, 다음과 같이 정의할 수 있다.</p>
</li>
</ul>
<pre><code>interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;
}</code></pre><ul>
<li><p>하지만 여기서는 <code>SquareConfig</code> 가 여러 프로퍼티를 가질 수 있고, 그 프로퍼티들이 <code>color</code> 나 <code>width</code> 가 아니라면, 그들의 타입은 중요하지 않다.</p>
</li>
<li><p>이 검사를 피하는 마지막 방법은 놀랍게도 객체를 다른 변수에 할당하는 것이다.</p>
</li>
<li><p><code>squareOptions</code> 가 추가 프로퍼티 검사를 받지 않기 때문에, 컴파일러는 에러를 주지 않는다.</p>
</li>
</ul>
<pre><code>let squareOptions = { colour: &quot;red&quot;, width: 100 };
let mySquare = createSquare(squareOptions);</code></pre><ul>
<li><p><code>squareOptions</code>와 <code>SquareConfig</code> 사이에 공통 프로퍼티가 있는 경우에만 위와 같은 방법을 사용할 수 있다.</p>
</li>
<li><p>이 예제에서는, <code>width</code> 가 그 경우이다.</p>
</li>
<li><p>하지만 만약에 변수가 공통 객체 프로퍼티가 없으면 에러가 난다.</p>
</li>
</ul>
<pre><code>let squareOptions = { colour: &quot;red&quot; };
let mySquare = createSquare(squareOptions);</code></pre><ul>
<li><p>위처럼 간단한 코드의 경우, 이 검사를 &quot;피하는&quot; 방법을 시도하지 않는 것이 좋다.</p>
</li>
<li><p>메서드가 있고 상태를 가지는 등 더 복잡한 객체 리터럴에서 이 방법을 생각해볼 수 있다.</p>
</li>
<li><p>하지만 초과 프로퍼티 에러의 대부분은 실제 버그이다.</p>
</li>
<li><p>그 말은, 만약 옵션 백 같은 곳에서 초과 프로퍼티 검사 문제가 발생하면, 타입 정의를 수정해야 할 필요가 있다.</p>
</li>
<li><p>예를 들어, 만약 <code>createSquare</code> 에 <code>color</code> 나 <code>colour</code> 모두 전달해도 괜찮다면, <code>squareConfig</code> 가 이를 반영하도록 정의를 수정해야 한다.</p>
</li>
</ul>
<h3 id="6-함수-타입-function-types">6. 함수 타입 (Function Types)</h3>
<ul>
<li><p>인터페이스는 JavaScript 객체가 가질 수 있는 넓은 범위의 형태를 기술할 수 있다.</p>
</li>
<li><p>프로퍼티로 객체를 기술하는 것 외에, 인터페이스는 함수 타입을 설명할 수 있다.</p>
</li>
<li><p>인터페이스로 함수 타입을 기술하기 위해, 인터페이스에 호출 서명 (call signature)를 전달한다.</p>
</li>
<li><p>이는 매개변수 목록과 반환 타입만 주어진 함수 선언과 비슷하다.</p>
</li>
<li><p>각 매개변수는 이름과 타입이 모두 필요하다.</p>
</li>
</ul>
<pre><code>interface SearchFunc {
    (source: string, subString: string): boolean;
}</code></pre><ul>
<li><p>한번 정의되면, 함수 타입 인터페이스는 다른 인터페이스처럼 사용할 수 있다.</p>
</li>
<li><p>여기서 함수 타입의 변수를 만들고, 같은 타입의 함수 값으로 할당하는 방법을 보여준다.</p>
</li>
</ul>
<pre><code>let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
    let result = source.search(subString);
    return result &gt; -1;
}</code></pre><ul>
<li><p>올바른 함수 타입 검사를 위해, 매개변수의 이름이 같을 필요는 없다.</p>
</li>
<li><p>예를 들어, 위의 예제를 아래와 같이 쓸 수 있다.</p>
</li>
</ul>
<pre><code>let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
    let result = src.search(sub);
    return result &gt; -1;
}</code></pre><ul>
<li><p>함수 매개변수들은 같은 위치에 대응되는 매개변수끼리 한번에 하나씩 검사한다.</p>
</li>
<li><p>만약 타입을 전혀 지정하지 않고 싶다면, <code>SearchFunc</code> 타입의 변수로 직접 함수 값이 할당되었기 때문에 TypeScript의 문맥상 타이핑 (contextual typing)이 인수 타입을 추론할 수 있다.</p>
</li>
<li><p>이 예제에서, 함수 표현의 반환 타입이 반환하는 값으로 추론된다. (여기서는 <code>false</code> 와 <code>true</code> )</p>
</li>
</ul>
<pre><code>let mySearch: SearchFunc;
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result &gt; -1;
}</code></pre><ul>
<li>함수 표현식이 숫자 나 문자열을 반환했다면, 타입 검사는 반환 타입이 <code>SearchFunc</code> 인터페이스에 정의된 반환 타입과 일치하지 않는다는 에러를 발생시킨다.</li>
</ul>
<pre><code>let mySearch: SearchFunc;

// error: Type &#39;(src: string, sub: string) =&gt; string&#39; is not assignable to type &#39;SearchFunc&#39;.
// Type &#39;string&#39; is not assignable to type &#39;boolean&#39;.
mySearch = function(src, sub) {
  let result = src.search(sub);
  return &quot;string&quot;;
};</code></pre><h3 id="7-인덱서블-타입-indexable-types">7. 인덱서블 타입 (Indexable Types)</h3>
<ul>
<li><p>인터페이스로 함수 타입을 설명하는 방법과 유사하게, <code>a[10]</code> 이나 <code>ageMap[&quot;daniel&quot;]</code> 처럼 타입을 &quot;인덱스로&quot; 기술할 수 있다.</p>
</li>
<li><p>인덱서블 타입은 인덱싱 할때 해당 반환 유형과 함께 객체를 인덱싱하는 데 사용할 수 있는 타입을 기술하는 인덱스 시그니처 (index signature)를 가지고 있다.</p>
</li>
</ul>
<pre><code>interface StringArray {
    [index: number]: string;
}

let myArray: StringArray;
myArray = [&quot;Bob&quot;, &quot;Fred&quot;];

let myStr: string = myArray[0];</code></pre><ul>
<li><p>위에서 인덱스 서명이 있는 <code>StringArray</code> 인터페이스가 있다.</p>
</li>
<li><p>이 인덱스 서명은 <code>StringArray</code> 가 <code>number</code> 로 색인화(indexed)되면 string을 반환할 것을 나타낸다.</p>
</li>
<li><p>인덱스 서명을 지원하는 타입에는 문자열과 숫자가 있다.</p>
</li>
<li><p>두 타입의 인덱서(indexer)를 모두 지원하는 것은 가능하지만, 숫자 인덱서에서 반환된 타입은 반드시 문자열 인덱서에서 반환된 타입의 하위 타입(subtype)이어야한다.</p>
</li>
<li><p>이 이유는 <code>number</code> 로 인덱싱 할 때, <code>JavaScript</code> 는 실제로 객체를 인덱싱하기 전에 <code>string</code> 으로 변환하기 때문이다.</p>
</li>
<li><p>즉, <code>100</code> (number)로 인덱싱하는 것은 <code>&quot;100&quot;</code> (string)로 인덱싱하는 것과 같기 때문에, 서로 <strong>일관성</strong> 있어야 한다.</p>
</li>
</ul>
<pre><code>class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}

// 오류: 숫자형 문자열로 인덱싱을 하면 완전히 다른 타입의 Animal을 얻게 될 것입니다!
interface NotOkay {
    [x: number]: Animal;
    [x: string]: Dog;
}</code></pre><ul>
<li><p>문자열 인덱스 시그니처는 &quot;사전&quot; 패턴을 기술하는데 강력한 방법이지만, 모든 프로퍼티들이 반환 타입과 일치하도록 강제한다.</p>
</li>
<li><p>문자열 인덱스가 <code>obj.property</code> 가 <code>obj[&quot;property&quot;]</code> 로도 이용 가능함을 알려주기 때문이다.</p>
</li>
<li><p>다음 예제에서, <code>name</code> 의 타입은 문자열 인덱스 타입과 일치하지 않고, 타입 검사는 에러를 발생시킨다.</p>
</li>
</ul>
<pre><code>interface NumberDictionary {
    [index: string]: number;
    length: number;    // 성공, length는 숫자입니다
    name: string;      // 오류, `name`의 타입은 인덱서의 하위타입이 아닙니다
}</code></pre><ul>
<li>하지만, 인덱스 시그니처가 프로퍼티 타입들의 합집합이라면 다른 타입의 프로퍼티들도 허용할 수 있다.</li>
</ul>
<pre><code>interface NumberOrStringDictionary {
    [index: string]: number | string;
    length: number;    // 성공, length는 숫자입니다
    name: string;      // 성공, name은 문자열입니다
}</code></pre><ul>
<li>마지막으로, 인덱스의 할당을 막기 위해 인덱스 시그니처를 읽기 전용으로 만들 수 있다.</li>
</ul>
<pre><code>interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = [&quot;Alice&quot;, &quot;Bob&quot;];
myArray[2] = &quot;Mallory&quot;; // 오류!</code></pre><ul>
<li>인덱스 시그니처가 읽기 전용이기 때문에 <code>myArray[2]</code> 의 값을 할당할 수 없다.</li>
</ul>
<h3 id="8-클래스-타입-class-types">8. 클래스 타입 (Class Types)</h3>
<p><strong>8-1. 인터페이스 구현하기 (Implementing an interface)</strong></p>
<ul>
<li>클래스가 특정 계약(contract)을 충족시키도록 명시적으로 강제하는 C#과 Java와 같은 언어에서 인터페이스를 사용하는 가장 일반적인 방법은 TypeScript에서도 가능하다.</li>
</ul>
<pre><code>interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface {
    currentTime: Date = new Date();
    constructor(h: number, m: number) { }
}</code></pre><ul>
<li>아래 예제의 setTime 처럼 클래스에 구현된 메서드를 인터페이스 안에서도 기술할 수 있다.</li>
</ul>
<pre><code>interface ClockInterface {
    currentTime: Date;
    setTime(d: Date): void;
}

class Clock implements ClockInterface {
    currentTime: Date = new Date();
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}</code></pre><ul>
<li><p>인터페이스는 클래스의 public과 private 모두보다는, public을 기술한다.</p>
</li>
<li><p>그래서 클래스 인스턴스의 private에서는 특정 타입이 있는지 검사할 수 없다.</p>
</li>
</ul>
<h3 id="9-클래스의-스태틱과-인스턴스의-차이점-difference-between-the-static-and-instance-sides-of-classes">9. 클래스의 스태틱과 인스턴스의 차이점 (Difference between the static and instance sides of classes)</h3>
<ul>
<li><p>클래스와 인터페이스를 다룰 때, 클래스는 스태틱 타입과 인스턴스 타입을 가진다는 것을 기억하는 게 좋다.</p>
</li>
<li><p>생성 시그니처 (construct signature)로 인터페이스를 생성하고, 클래스를 생성하려고 한다면, 인터페이스를 implements 할 때, 에러가 발생하는 것을 확인할 수 있을 것이다.</p>
</li>
</ul>
<pre><code>interface ClockConstructor {
    new (hour: number, minute: number);
}

class Clock implements ClockConstructor {
    currentTime: Date;
    constructor(h: number, m: number) { }
}</code></pre><ul>
<li><p>클래스가 인터페이스를 implements 할 때, 클래스의 인스턴스만 검사하기 때문이다.</p>
</li>
<li><p>생성자가 스태틱이기 때문에, 이 검사에 포함되지 않는다.</p>
</li>
<li><p>대신에, 클래스의 스태틱 부분을 직접적으로 다룰 필요가 있다.</p>
</li>
<li><p>이번 예제에서, <code>ClockConstructor</code> 는 생성자를 정의하고, <code>ClockInterface</code> 는 인스턴스 메서드를 정의하는 두 인터페이스를 정의한다.</p>
</li>
<li><p>그리고, 편의를 위해, 전달된 타입의 인스턴스를 생성하는 <code>createClock</code> 생성자 함수를 정의한다.</p>
</li>
</ul>
<pre><code>interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick(): void;
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log(&quot;beep beep&quot;);
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log(&quot;tick tock&quot;);
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);</code></pre><ul>
<li><p><code>createClock</code> 의 첫 번째 매개변수는 <code>createClock(AnalogClock, 7, 32)</code> 안에 <code>ClockConstructor</code> 타입이므로, <code>AnalogClock</code> 이 올바른 생성자 시그니처를 갖고 있는지 검사한다.</p>
</li>
<li><p>또 다른 쉬운 방법은 클래스 표현을 사용하는 것이다.</p>
</li>
</ul>
<pre><code>interface ClockConstructor {
  new (hour: number, minute: number);
}

interface ClockInterface {
  tick();
}

const Clock: ClockConstructor = class Clock implements ClockInterface {
  constructor(h: number, m: number) {}
  tick() {
      console.log(&quot;beep beep&quot;);
  }
}</code></pre><h3 id="10-인터페이스-확장하기-extending-interfaces">10. 인터페이스 확장하기 (Extending Interfaces)</h3>
<ul>
<li><p>클래스처럼, 인터페이스들도 확장(extend)이 가능하다.</p>
</li>
<li><p>이는 한 인터페이스의 멤버를 다른 인터페이스에 복사하는 것을 가능하게 해주는데, 인터페이스를 재사용성 높은 컴포넌트로 쪼갤 때, 유연함을 제공해준다.</p>
</li>
</ul>
<pre><code>interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

let square = {} as Square;
square.color = &quot;blue&quot;;
square.sideLength = 10;</code></pre><ul>
<li>인터페이스는 여러 인터페이스를 확장할 수 있어, 모든 인터페이스의 조합을 만들어낼 수 있다.</li>
</ul>
<pre><code>interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = {} as Square;
square.color = &quot;blue&quot;;
square.sideLength = 10;
square.penWidth = 5.0;</code></pre><h3 id="11-하이브리드-타입-hybrid-types">11. 하이브리드 타입 (Hybrid Types)</h3>
<ul>
<li><p>일찍이 언급했듯이, 인터페이스는 실제 JavaScript 세계에 존재하는 다양한 타입들을 기술할 수 있다.</p>
</li>
<li><p>JavaScript의 동적이고 유연한 특성 때문에, 위에서 설명했던 몇몇 타입의 조합으로 동작하는 객체를 가끔 마주할 수 있다.</p>
</li>
<li><p>그러한 예제 중 하나는 추가적인 프로퍼티와 함께, 함수와 객체 역할 모두 수행하는 객체이다.</p>
</li>
</ul>
<pre><code>interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

function getCounter(): Counter {
    let counter = (function (start: number) { }) as Counter;
    counter.interval = 123;
    counter.reset = function () { };
    return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;</code></pre><ul>
<li>써드파티 (3rd-party) JavaScript와 상호작용할 때, 타입의 형태를 완전히 기술하기 위해 위와 같은 패턴을 사용해야할 수도 있다.</li>
</ul>
<h3 id="12-클래스를-확장한-인터페이스-interfaces-extending-classes">12. 클래스를 확장한 인터페이스 (Interfaces Extending Classes)</h3>
<ul>
<li><p>인터페이스 타입이 클래스 타입을 확장하면, 클래스의 멤버는 상속받지만 구현은 상속받지 않는다.</p>
</li>
<li><p>이것은 인터페이스가 구현을 제공하지 않고, 클래스의 멤버 모두를 선언한 것과 마찬가지이다. 인터페이스는 심지어 기초 클래스의 private과 protected 멤버도 상속받는다.</p>
</li>
<li><p>이것은 인터페이스가 private 혹은 protected 멤버를 포함한 클래스를 확장할 수 있다는 뜻이고, 인터페이스 타입은 그 클래스나 하위클래스에 의해서만 구현될 수 있다.</p>
</li>
<li><p>이는 거대한 상속계층을 가지고 있을 때 유용하지만, 특정 프로퍼티를 가진 하위클래스에서만 코드가 동작하도록 지정하는데도 유용하다.</p>
</li>
<li><p>하위클래스는 기초클래스에서 상속하는 것 외에는 관련이 있을 필요가 없다.</p>
</li>
</ul>
<pre><code>class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// Error: Property &#39;state&#39; is missing in type &#39;Image&#39;.
class Image implements SelectableControl {
    private state: any;
    select() { }
}

class Location {

}</code></pre><ul>
<li><p>위 예제에서, <code>SelectableControl</code> 은 private state 프로퍼티를 포함하여, Control의 모든 멤버를 가지고 있다.</p>
</li>
<li><p>state는 private 멤버이기 때문에, <code>SelectableControl</code> 를 구현하는 것은 <code>Control</code> 의 자식에게만 가능하다.</p>
</li>
<li><p><code>Control</code> 의 자식만 같은 선언에서 유래된 state private 멤버를 가질수 있기 때문이고, private 멤버들이 호환되기 위해 필요하다.</p>
</li>
<li><p><code>Control</code> 클래스 안에서 <code>SelectableControl</code> 의 인스턴스를 통해서 state private 멤버에 접근할 수 있다.</p>
</li>
<li><p><code>SelectableControl</code> 은 select 메서드를 가진 <code>Control</code> 과 같은 역할을 한다.</p>
</li>
<li><p><code>Button</code> 과 <code>TextBox</code> 클래스들은 <code>SelectableControl</code> 의 하위타입이지만 (<code>Control</code> 을 상속받고, <code>select</code> 메서드를 가지기 때문에), <code>Image</code> 와 <code>Location</code> 클래스는 아니다.</p>
</li>
</ul>
<hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TypeScript 알아보기 (1)]]></title>
            <link>https://velog.io/@syo_ee/typescript01</link>
            <guid>https://velog.io/@syo_ee/typescript01</guid>
            <pubDate>Fri, 15 Dec 2023 04:42:29 GMT</pubDate>
            <description><![CDATA[<h1 id="시작-동기">시작 동기</h1>
<ul>
<li>프로젝트 진행 중, TypeScript 사용 시, type을 부정확하게 사용하고 있지는 않는지, 제대로 사용하고 있는지 검토할까 생각하다 긴 생각않고 그냥 진행해보기로 하였다.</li>
<li>그리하여 TypeScript를 차근차근 정독해보며 정리해보려한다.</li>
</ul>
<hr/>

<h1 id="chapter-1-기본-타입">Chapter 1. 기본 타입</h1>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 소개 (Introduction)</strong>
<strong>2. 불리언 (Boolean)</strong>
<strong>3. 숫자 (Number)</strong>
<strong>4. 문자열 (String)</strong>
<strong>5. 배열 (Array)</strong>
<strong>6. 튜플 (Tuple)</strong>
<strong>7. 열거 (Enum)</strong>
<strong>8. Any</strong>
<strong>9. Void</strong>
<strong>10. Null and Undefined</strong>
<strong>11. Never</strong>
<strong>12. 객체 (Object)</strong>
<strong>13. 타입 단언 (Type assertions)</strong>
<strong>14. &#39;let&#39;에 관하여</strong></p>
<hr/>

<h3 id="1-소개-introduction">1. 소개 (Introduction)</h3>
<ul>
<li><p>프로그램이 유용하려면 숫자, 문자열, 구조체, 불리언 값과 같은 간단한 데이터 단위가 필요하다.</p>
</li>
<li><p>TypeScript는 JavaScript와 거의 동일한 데이터 타입을 지원하며, 열거 타입을 사용하여 더 편리하게 사용할 수 있습니다.</p>
</li>
</ul>
<h3 id="2-불리언-boolean">2. 불리언 (Boolean)</h3>
<ul>
<li>가장 기본적인 데이터 타입은 JavaScript, TypeScript에서 <code>boolean</code> 값이라고 일컫는 참/거짓(true/false) 값이다.</li>
</ul>
<pre><code>  let isDone: boolean = false;</code></pre><h3 id="3-숫자-number">3. 숫자 (Number)</h3>
<ul>
<li><p>JavaScript처럼, TypeScript의 모든 숫자는 <a href="https://ko.wikipedia.org/wiki/%EB%B6%80%EB%8F%99%EC%86%8C%EC%88%98%EC%A0%90">부동 소수</a> 값이다.</p>
</li>
<li><p>부동 소수에는 <code>number</code> 라는 타입이 붙혀진다.</p>
</li>
<li><p>TypeScript는 16진수, 10진수 리터럴에 더불어, ECMAScript 2015에 소개된 2진수, 8진수 리터럴도 지원한다.</p>
<pre><code>let decimal: number = 6;
 let hex: number = 0xf00d;
 let binary: number = 0b1010;
 let octal: number = 0o744;</code></pre></li>
</ul>
<h3 id="4-문자열-string">4. 문자열 (String)</h3>
<ul>
<li><p>웹 페이지, 서버 같은 프로그램을 JavaScript로 만들 때 기본적으로 텍스트 데이터를 다루는 작업이 필요하다.</p>
</li>
<li><p>다른 언어들처럼, TypeScript에서는 텍스트 데이터 타입을 <code>string</code> 으로 표현한다.</p>
</li>
<li><p>JavaScript처럼 TypeScript도 큰따옴표 (<code>&quot;</code>)나 작은따옴표 (<code>&#39;</code>)를 문자열 데이터를 감싸는데 사용한다.</p>
<pre><code>let color: string = &quot;blue&quot;;
 color = &#39;red&#39;;</code></pre></li>
<li><p>또한 템플릿 문자열 을 사용하면 여러 줄에 걸쳐 문자열을 작성할 수 있으며, 표현식을 포함시킬 수도 있다.</p>
</li>
<li><p>이 문자열은 백틱/백쿼트 (<code>`</code>) 문자로 감싸지며, <code>${ expr }</code> 과 같은 형태로 표현식을 포함시킬 수 있다.</p>
<pre><code>let fullName: string = `Bob Bobbington`;
 let age: number = 37;
 let sentence: string = `Hello, my name is ${ fullName }.
 I&#39;ll be ${ age + 1 } years old next month.`;</code></pre></li>
</ul>
<p>위는 아래 <code>sentence</code> 선언과 동일하다.</p>
<pre><code>  let sentence: string = &quot;Hello, my name is &quot; + fullName + &quot;.\n\n&quot; +
    &quot;I&#39;ll be &quot; + (age + 1) + &quot; years old next month.&quot;;</code></pre><h3 id="5-배열-array">5. 배열 (Array)</h3>
<ul>
<li><p>TypeScript는 JavaScript처럼 값들을 배열로 다룰 수 있게 해준다.
배열 타입은 두 가지 방법으로 쓸 수 있다.</p>
<ul>
<li>첫 번째 방법은, 배열 요소들을 나타내는 타입 뒤에 <code>[]</code>를 쓰는 것이다.</li>
</ul>
<pre><code>let list: number[] = [1, 2, 3];</code></pre><ul>
<li>두 번째 방법은 제네릭 배열 타입을 쓰는 것이다.
<code>Array&lt;elemType&gt;</code></li>
</ul>
<pre><code>let list: Array&lt;number&gt; = [1, 2, 3];</code></pre></li>
</ul>
<h3 id="6-튜플-tuple">6. 튜플 (Tuple)</h3>
<ul>
<li><p>튜플 타입을 사용하면, 요소의 타입과 개수가 고정된 배열을 표현할 수 있다.
단 요소들의 타입이 모두 같을 필요는 없다.</p>
</li>
<li><p>예를 들어, <code>number</code>, <code>string</code> 이 쌍으로 있는 값을 나타내고 싶을 수 있다.</p>
<pre><code>// 튜플 타입으로 선언
 let x: [string, number];

 // 초기화
 x = [&quot;hello&quot;, 10]; // 성공

 // 잘못된 초기화
 x = [10, &quot;hello&quot;]; // 오류</code></pre></li>
<li><p>정해진 인덱스에 위치한 요소에 접근하면 해당 타입이 나타난다.</p>
<pre><code>console.log(x[0].substring(1)); // 성공
 console.log(x[1].substring(1)); // 오류, &#39;number&#39;에는 &#39;substring&#39; 이 없다.</code></pre></li>
<li><p>정해진 인덱스 외에 다른 인덱스에 있는 요소에 접근하면, 오류가 발생하며 실패한다.</p>
<pre><code>x[3] = &quot;world&quot;; // 오류, &#39;[string, number]&#39; 타입에는 프로퍼티 &#39;3&#39;이 없다.
 console.log(x[5].toString()); // &#39;[string, number]&#39; 타입에는 프로퍼티 &#39;5&#39;가 없다.</code></pre></li>
</ul>
<h3 id="7-열거-enum">7. 열거 (Enum)</h3>
<ul>
<li><p>JavaScript의 표준 자료형 집합과 사용하면 도움이 될만한 데이터 형은 <code>enum</code> 이다.</p>
</li>
<li><p>C# 같은 언어처럼, <code>enum</code> 은 값의 집합에 더 나은 이름을 붙여줄 수 있다.</p>
<pre><code>enum Color {Red, Green, Blue}
 let c: Color = Color.Green;</code></pre></li>
<li><p>기본적으로, <code>enum</code> 은 <code>0</code> 부터 시작하여 멤버들의 번호를 매긴다.
멤버 중 하나의 값을 수동으로 설정하여 번호를 바꿀 수 있다.
예를 들어, 위 예제를 <code>0</code> 대신 <code>1</code> 부터 시작해 번호를 매기도록 바꿀 수 있다.</p>
<pre><code>enum Color {Red = 1, Green, Blue}
 let c: Color = Color.Green;</code></pre></li>
</ul>
<p>또는, 모든 값을 수동으로 설정할 수 있다.</p>
<pre><code>  enum Color {Red = 1, Green = 2, Blue = 4}
   let c: Color = Color.Green;</code></pre><ul>
<li><p><code>enum</code> 의 유용한 기능 중 하나는 매겨진 값을 사용해 enum 멤버의 이름을 알아낼 수 있다는 것이다.
예를 들어, 위의 예제에서 <code>2</code> 라는 값이 위의 어떤 <code>Color</code> enum 멤버와 매칭되는지 알 수 없을 때, 이에 일치하는 이름을 알아낼 수 있다.</p>
<pre><code>enum Color {Red = 1, Green, Blue}
 let colorName: string = Color[2];

 console.log(colorName); // 값이 2인 &#39;Green&#39;이 출력됩니다.</code></pre></li>
</ul>
<h3 id="8-any">8. Any</h3>
<ul>
<li>애플리케이션을 만들 때, 알지 못하는 타입을 표현해야 할 수도 있다.
이 값들은 사용자로부터 받은 데이터나 서드 파티 라이브러리 같은 동적인 컨텐츠에서 올 수도 있다.</li>
</ul>
<p>이 경우 타입 검사를 하지 않고, 그 값들이 컴파일 시간에 검사를 통과하길 원한다.
이를 위해, <code>any</code> 타입을 사용할 수 있다.</p>
<pre><code>  let notSure: any = 4;
   notSure = &quot;maybe a string instead&quot;;
   notSure = false; // 성공, 분명히 부울입니다.</code></pre><ul>
<li><p><code>any</code> 타입은 기존에 JavaScript로 작업할 수 있는 강력한 방법으로, 컴파일 중에 점진적으로 타입 검사를 하거나 하지 않을 수 있다.</p>
</li>
<li><p>혹 다른 언어에서 그렇듯, <code>Object</code>가 비슷한 역할을 할 수 있을 것 같다고 생각할 수도 있다.</p>
</li>
<li><p>그런데, <code>Object</code>로 선언된 변수들은 오직 어떤 값이든 그 변수에 할당할 수 있게 해주지만 실제로 메서드가 존재하더라도, 임의로 호출할 수는 없다.</p>
<pre><code>let notSure: any = 4;
 notSure.ifItExists(); // 성공, ifItExists 는 런타임엔 존재할 것이다.
 notSure.toFixed(); // 성공, toFixed는 존재한다. (하지만 컴파일러는 검사하지 않음)

 let prettySure: Object = 4;
 prettySure.toFixed(); // 오류: 프로퍼티 &#39;toFixed&#39;는 &#39;Object&#39;에 존재하지 않는다.</code></pre><blockquote>
<p><span style="color:coral">KeyNote</span></p>
</blockquote>
<ul>
<li><a href="https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#general-types">Do&#39;s and Don&#39;ts</a>에 설명 했듯이 <code>Object</code>를 no-primitive <code>object</code> 대신에 사용하지 말자.</li>
</ul>
</li>
<li><p>또한, any 타입은 타입의 일부만 알고 전체는 알지 못할 때 유용하다.
예를 들어, 여러 다른 타입이 섞인 배열을 다룰 수 있다.</p>
<pre><code>let list: any[] = [1, true, &quot;free&quot;];

 list[1] = 100;</code></pre></li>
</ul>
<h3 id="9-void">9. Void</h3>
<ul>
<li><p><code>void</code> 는 어떤 타입도 존재할 수 없음을 나타내기 때문에, <code>any</code> 의 반대 타입 같다.
<code>void</code> 는 보통 함수에서 반환 값이 없을 때 반환 타입을 표현하기 위해 쓰이는 것을 볼 수 있다.</p>
<pre><code>function warnUser(): void {
  console.log(&quot;This is my warning message&quot;);
}</code></pre></li>
<li><p><code>void</code>를 타입 변수를 선언하는 것은 유용하지 않은데, 왜냐하면 그 변수에는 <code>null</code>(<code>--strictNullChecks</code> 을 사용하지 않을 때만 해당, 자세한 건 다음 섹션을 참고)또는 <code>undefined</code> 만 할당할 수 있기 때문이다.</p>
<pre><code>let unusable: void = undefined;
 unusable = null; // 성공  `--strictNullChecks` 을 사용하지 않을때만</code></pre></li>
</ul>
<h3 id="10-null-and-undefined">10. Null and Undefined</h3>
<ul>
<li><p>TypeScript는 <code>undefined</code> 과 <code>null</code> 둘 다 각각 자신의 타입 이름으로 <code>undefined</code> , <code>null</code> 로 사용한다.
<code>void</code> 처럼 그 자체로 유용한 경우는 거의 없다.</p>
<pre><code>// 이 밖에 이 변수들에 할당할 수 있는 값이 없습니다!
 let u: undefined = undefined;
 let n: null = null;</code></pre></li>
<li><p>기본적으로 <code>null</code> 과 <code>undefined</code> 는 다른 모든 타입의 하위 타입이다.</p>
</li>
<li><p>이건, <code>null</code>과 <code>undefined</code>를 <code>number</code> 같은 타입에 할당할 수 있다는 것을 의미한다.</p>
</li>
<li><p>하지만, <code>--strictNullChecks</code>를 사용하면, <code>null</code> 과 <code>undefined</code> 는 오직 <code>any</code> 와 각자 자신들 타입에만 할당 가능하다.
(예외적으로 undefined는 void에 할당 가능하다.)</p>
</li>
<li><p>이건 많은 일반적인 에러를 방지하는 데 도움을 준다.
이 경우, <code>string</code> 또는 <code>null</code> 또는 <code>undefined</code> 를 허용하고 싶은 경우 유니언 타입인 <code>string | null | undefined</code> 를 사용할 수 있다.</p>
<blockquote>
<p><span style="color:coral">KeyNote</span></p>
</blockquote>
<ul>
<li>가능한 경우 <code>--strictNullChecks</code> 를 사용할 것을 권장한다.
하지만 핸드북의 목적에 따라, 사용하지 않는다고 가정한다.</li>
</ul>
</li>
</ul>
<h3 id="11-never">11. Never</h3>
<ul>
<li><p>never 타입은 절대 발생할 수 없는 타입을 나타낸다.
예를 들어, never는 함수 표현식이나 화살표 함수 표현식에서 항상 오류를 발생시키거나 절대 반환하지 않는 반환 타입으로 쓰인다.</p>
</li>
<li><p>변수 또한 타입 가드에 의해 아무 타입도 얻지 못하게 좁혀지면 never 타입을 얻게 될 수 있다.</p>
</li>
<li><p>never타입은 모든 타입에 할당 가능한 하위 타입이다.</p>
</li>
<li><p>하지만 어떤 타입도 never에 할당할 수 있거나, 하위 타입이 아닙니다.(never 자신은 제외) 심지어 any 도 never에 할당할 수 없다.</p>
</li>
<li><p>아래는 never를 반환하는 몇 가지 예제이다.</p>
<pre><code>// never를 반환하는 함수는 함수의 마지막에 도달할 수 없다.
 function error(message: string): never {
   throw new Error(message);
 }

 // 반환 타입이 never로 추론된다.
 function fail() {
  return error(&quot;Something failed&quot;);
 }

 // never를 반환하는 함수는 함수의 마지막에 도달할 수 없다.
 function infiniteLoop(): never {
   while (true) {
     }
 }</code></pre></li>
</ul>
<h3 id="12-객체-object">12. 객체 (Object)</h3>
<ul>
<li><p><code>object</code> 는 원시 타입이 아닌 타입을 나타낸다.
예를 들어, <code>number, string, boolean, bigint, symbol, null 또는 undefined</code>  가 아닌 나머지를 의미한다.</p>
</li>
<li><p><code>object</code> 타입을 쓰면, <code>Object.create</code> 같은 API 가 더 잘 나타난다.</p>
<pre><code>declare function create(o: object | null): void;

 create({ prop: 0 }); // 성공
 create(null); // 성공

 create(42); // 오류
 create(&quot;string&quot;); // 오류
 create(false); // 오류
 create(undefined); // 오류</code></pre></li>
</ul>
<h3 id="13-타입-단언-type-assertions">13. 타입 단언 (Type assertions)</h3>
<ul>
<li><p>가끔, TypeScript보다 개발자가 값에 대해 더 잘 알고 일을 때가 있다.</p>
</li>
<li><p>대개, 이런 경우는 어떤 엔티티의 실제 타입이 현재 타입보다 더 구체적일 때 발생한다.</p>
</li>
<li><p><em>타입 단언(Type assertions)</em> 은 컴파일러에게 &quot;날 믿어, 난 내가 뭘 하고 있는지 알아&quot;라고 말해주는 방법이다.</p>
</li>
<li><p><code>타입 단언</code> 은 다른 언어의 타입 변환(형 변환)과 유사하지만, 다른 특별한 검사를 하거나 데이터를 재구성하지는 않는다.</p>
</li>
<li><p>이는 런타임에 영향을 미치지 않으며, 온전히 컴파일러만 이를 사용한다.</p>
</li>
<li><p>타입 스크립트는 개발자가 필요한 어떤 특정 검사를 수행했다고 인지한다.</p>
</li>
<li><p>타입 단언에는 두 가지 형태가 있다.</p>
<ul>
<li>하나는, &quot;angle-bracket&quot; 문법이다.</li>
</ul>
<pre><code> let someValue: any = &quot;this is a string&quot;;

  let strLength: number = (&lt;string&gt;someValue).length;</code></pre><ul>
<li><p>다른 하나는 <code>as</code> -문법이다.</p>
<pre><code>let someValue: any = &quot;this is a string&quot;;

let strLength: number = (someValue as string).length;</code></pre><p>위 두 예제는 동일하다.</p>
</li>
</ul>
</li>
<li><p>어떤 것을 사용할지는 주로 선호에 따른 선택이다.</p>
</li>
<li><p>하지만 TypeScript를 JSX와 함께 사용할 때는, <code>as</code> -스타일의 단언만 허용된다.</p>
</li>
</ul>
<h3 id="14-let에-관하여">14. &#39;let&#39;에 관하여</h3>
<ul>
<li><p>지금까지 더 익숙할 수도 있는 JavaScript의 <code>var</code> 키워드 대신 <code>let</code> 키워드를 이용했다는 걸 알 수 있다.</p>
</li>
<li><p><code>let</code> 키워드는 JavaScript ES2015에서 소개되었고, <code>var</code> 보다 안전하다는 이유로, 현재 표준으로 여겨지고 있다.</p>
</li>
<li><p>대부분의 JavaScript의 문제들이 <code>let</code> 을 사용해서 해결되며, 때문에 가능한 경우 최대한 <code>var</code> 대신 <code>let</code> 을 사용하여야 한다.</p>
</li>
</ul>
<hr/>

<h3 id="typescript-handbook">TypeScript HandBook</h3>
<p><a href="https://www.typescriptlang.org/docs/">https://www.typescriptlang.org/docs/</a></p>
<h3 id="typescript-handbook-번역-문서">TypeScript HandBook 번역 문서</h3>
<p><a href="https://typescript-kr.github.io/">https://typescript-kr.github.io/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/">https://ko.wikipedia.org/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Hash]]></title>
            <link>https://velog.io/@syo_ee/Hash</link>
            <guid>https://velog.io/@syo_ee/Hash</guid>
            <pubDate>Thu, 14 Dec 2023 09:01:01 GMT</pubDate>
            <description><![CDATA[<h2 id="1-hash란">1. Hash란?</h2>
<ul>
<li><p>해시 함수(hash function) 또는 해시 알고리즘(hash algorithm) 또는 해시함수 알고리즘은 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수이다.
해시 함수에 의해 얻어지는 값은 해시 값, 해시 코드, 해시 체크섬 또는 간단하게 해시라고 한다.
그 용도 중 하나는 <a href="https://ko.wikipedia.org/wiki/%ED%95%B4%EC%8B%9C_%ED%85%8C%EC%9D%B4%EB%B8%94">해시 테이블</a>이라는 자료구조에 사용되며, 매우 빠른 데이터 검색을 위한 컴퓨터 소프트웨어에 널리 사용된다.</p>
</li>
<li><p>해시 함수는 큰 파일에서 중복되는 레코드를 찾을 수 있기 때문에 데이터베이스 검색이나 테이블 검색의 속도를 가속할 수 있다.</p>
</li>
<li><p>암호용 해시 함수는 매핑된 해싱 값만을 알아가지고는 원래 입력 값을 알아내기 힘들다는 사실에 의해 사용될 수 있다.
또한 전송된 데이터의 무결성을 확인해주는 데 사용되기도 하는데, 메시지가 누구에게서 온 것인지 입증해주는 <a href="https://ko.wikipedia.org/wiki/HMAC">HMAC</a>를 구성하는 블록으로 사용된다.</p>
</li>
<li><p>해시 함수는 결정론적으로 작동해야 하며, 따라서 두 해시 값이 다르다면 그 해시값에 대한 원래 데이터도 달라야 한다. <em>(역은 성립하지 않는다)</em></p>
</li>
<li><p>해시 함수의 질은 입력 영역에서의 해시 충돌 확률로 결정되는데, <a href="https://ko.wikipedia.org/wiki/%ED%95%B4%EC%8B%9C_%EC%B6%A9%EB%8F%8C">해시 충돌</a>의 확률이 높을수록 서로 다른 데이터를 구별하기 어려워지고 검색하는 비용이 증가하게 된다.</p>
</li>
</ul>
<blockquote>
<p><span style='color:coral'><strong>Recap</strong></span></p>
</blockquote>
<ul>
<li>해시함수는 하나의 주어진 출력에 대하여 이 출력으로 사상시키는 하나의 입력을 찾는 것이 계산적으로 불가능하고, 하나의 주어진 입력에 대하여 같은 출력으로 사상시키는 또 다른 입력을 찾는 것이 계산적으로 불가능하다는 두 가지 성질을 만족하면서 임의의 비트열을 고정된 길이의 비트열로 사상시키는 함수이다.</li>
</ul>
<h2 id="2-hash의-특징">2. Hash의 특징</h2>
<ul>
<li>같은 종류의 자료를 묶어서 파악할 수 있다.</li>
</ul>
<h2 id="3-알고리즘">3. 알고리즘</h2>
<ul>
<li>해시함수 알고리즘은 긴 길이의 데이터를 짧은 길이의 데이터로 변환하는 알고리즘으로 따라서 제3자는 짧은 길이의 데이터로부터 원래의 데이터를 복구할 수 없어야 하며, 동일한 출력을 갖는 서로 다른 데이터를 찾을 수 없어야 한다.</li>
</ul>
<h2 id="4-예시">4. 예시</h2>
<h3 id="프로그래머스해시완주하지-못한-선수-with-javascript"><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42576">프로그래머스,해시,완주하지 못한 선수</a> with JavaScript</h3>
<blockquote>
</blockquote>
<p><strong>문제</strong>
<br/></p>
<ul>
<li>수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.<p/></li>
<li>마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.<br/></li>
<li>제한사항
마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
completion의 길이는 participant의 길이보다 1 작습니다.
참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
참가자 중에는 동명이인이 있을 수 있습니다.<pre><code>function solution(participant, completion) {
  var answer = &#39;&#39;;
  return answer;
}</code></pre></li>
</ul>
<blockquote>
</blockquote>
<p><strong>해답</strong></p>
<pre><code>function solution(participant, completion) {
    const map = new Map();
    for(let i = 0; i &lt; participant.length; i++) {
        let a = participant[i], 
            b = completion[i];
        map.set(a, (map.get(a) || 0) + 1);
        map.set(b, (map.get(b) || 0) - 1);
    }
    for(let [k, v] of map) {
        if(v &gt; 0) return k;
    }
    return &#39;nothing&#39;;
}</code></pre><hr/>

<h3 id="회고">회고</h3>
<ul>
<li>본인이 작성한 코드보다 가독성이 좋은 코드를 들고 옴.</li>
<li>가독성이 좋고 예쁜(?)코드를 짜려고 할 때 마다 더욱 어렵게 생각함.</li>
<li>문제를 코드화 시키는 능력과, 해당 코드로 간결하고 가독성 좋게 하려는 코드를 작성하는 방향으로 하자.</li>
</ul>
<hr/>

<h3 id="programmers---hash">Programmers - Hash</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/parts/12077">https://school.programmers.co.kr/learn/courses/30/parts/12077</a></p>
<h3 id="wikipedia---hash">Wikipedia - Hash</h3>
<p><a href="https://ko.wikipedia.org/wiki/%ED%95%B4%EC%8B%9C_%ED%95%A8%EC%88%98">https://ko.wikipedia.org/wiki/%ED%95%B4%EC%8B%9C_%ED%95%A8%EC%88%98</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Greedy algorithm]]></title>
            <link>https://velog.io/@syo_ee/greedy-algorithm</link>
            <guid>https://velog.io/@syo_ee/greedy-algorithm</guid>
            <pubDate>Wed, 13 Dec 2023 14:57:10 GMT</pubDate>
            <description><![CDATA[<h2 id="1-탐욕-알고리즘greedy-algorithm이란">1. 탐욕 알고리즘(Greedy algorithm)이란?</h2>
<ul>
<li><strong>탐욕 알고리즘(Greedy algorithm)</strong> 은 <span style="color:red"><strong>최적의 해를 구하는 데에 사용되는 근사적인 방법</strong></span> 으로, 여러 경우 중 하나를 결정해야 할 때마다 그 순간에 최적이라고 생각되는 것을 선택해 나가는 방식으로 진행하여 최종적인 해답에 도달한다.</li>
<li>순간마다 하는 선택은 그 순간에 대해 지역적으로는 최적이지만, 그 선택들을 계속 수집하여 최종적(전역적)인 해답을 만들었다고 해서, 그것이 최적이라는 보장은 없다.</li>
<li>하지만 탐욕알고리즘을 적용할 수 있는 문제들은 지역적으로 최적이면서 전역적으로 최적인 문제들이다.</li>
</ul>
<h2 id="2-탐욕greedy의-조건">2. 탐욕(Greedy)의 조건</h2>
<ul>
<li><p>탐욕 알고리즘이 잘 작동하는 문제는 대부분 탐욕스런 선택 조건(greedy choice property)과 최적 부분 구조 조건(optimal substructure)이라는 두 가지 조건이 만족된다.
탐욕스런 선택 조건은 앞의 선택이 이후의 선택에 영향을 주지 않는다는 것이며, 최적 부분 구조 조건은 문제에 대한 최적해가 부분문제에 대해서도 역시 최적해라는 것이다.</p>
</li>
<li><p>이러한 조건이 성립하지 않는 경우에는 탐욕 알고리즘은 최적해를 구하지 못한다.
하지만, 이러한 경우에도 탐욕 알고리즘은 <a href="https://ko.wikipedia.org/wiki/%EA%B7%BC%EC%82%AC_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">근사 알고리즘</a>으로 사용이 가능할 수 있으며, 대부분의 경우 계산 속도가 빠르기 때문에 실용적으로 사용할 수 있다.
이 경우 역시 어느 정도까지 최적해에 가까운 해를 구할 수 있는지를 보장하려면 엄밀한 증명이 필요하다.</p>
</li>
<li><p>어떤 특별한 구조가 있는 문제에 대해서는 탐욕 알고리즘이 언제나 최적해를 찾아낼 수 있다. 이 구조를 <a href="https://ko.wikipedia.org/wiki/%EB%A7%A4%ED%8A%B8%EB%A1%9C%EC%9D%B4%EB%93%9C">매트로이드</a>라 한다.
매트로이드는 모든 문제에서 나타나는 것은 아니나, 여러 곳에서 발견되기 때문에 탐욕 알고리즘의 활용도를 높여 준다.</p>
</li>
</ul>
<h2 id="3-예시">3. 예시</h2>
<h3 id="프로그래머스-탐욕법-체육복-with-javascript"><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42862">프로그래머스, 탐욕법, 체육복</a> with JavaScript</h3>
<blockquote>
</blockquote>
<p><strong>문제</strong>
<br/></p>
<ul>
<li>점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다.
다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다.
학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다.
예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다.
체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.<p/></li>
<li>전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.<br/></li>
<li>제한사항
전체 학생의 수는 2명 이상 30명 이하입니다.
체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.
여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.<pre><code>function solution(n, lost, reserve) {
  var answer = 0;
  return answer;
}</code></pre></li>
</ul>
<blockquote>
</blockquote>
<p><strong>해답</strong></p>
<pre><code>function solution(n, lost, reserve) {
    const students = {};
    let answer = 0;
    for(let i = 1; i &lt;= n; i++){
        students[i] = 1;
    }
    lost.forEach(number =&gt; students[number] -= 1);
    reserve.forEach(number =&gt; students[number] += 1);
    for(let i = 1; i &lt;= n; i++){
        if(students[i] === 2 &amp;&amp; students[i-1] === 0){
                students[i-1]++;
                students[i]--;
        } else if(students[i] === 2 &amp;&amp; students[i+1] === 0){
                students[i+1]++;
                students[i]--;
        }
    }
    for(let key in students){
        if(students[key] &gt;= 1){
            answer++;
        }
    }
    return answer;
}</code></pre><hr/>

<h3 id="회고">회고</h3>
<ul>
<li>본인이 작성한 코드보다 가독성이 좋은 코드를 들고 옴.</li>
<li>이런 코드를 볼 때마다 현타가 강하게 오지만 현타만큼 더욱 배우고자하는 열정이 생김.</li>
<li>어려운 코드보다 가독성이 좋은 코드를 작성하려고 하자.</li>
</ul>
<hr/>

<h3 id="programmers---greedy">Programmers - Greedy</h3>
<p><a href="https://school.programmers.co.kr/learn/courses/30/parts/12244">https://school.programmers.co.kr/learn/courses/30/parts/12244</a></p>
<h3 id="wikipedia---greedy-algorithm">Wikipedia - Greedy algorithm</h3>
<p><a href="https://ko.wikipedia.org/wiki/%ED%83%90%EC%9A%95_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98">https://ko.wikipedia.org/wiki/%ED%83%90%EC%9A%95_%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DFS & BFS]]></title>
            <link>https://velog.io/@syo_ee/4hzby1er</link>
            <guid>https://velog.io/@syo_ee/4hzby1er</guid>
            <pubDate>Mon, 11 Dec 2023 14:58:57 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<h3 id="1-dfs">1. DFS</h3>
<p><strong>1-1. DFS란?</strong>
<strong>1-2. 깊이 제한과 백트래킹</strong>
<strong>1-3. 알고리즘</strong>
<strong>1-4. 장단점</strong>
*<em>1-5. *</em></p>
<h3 id="2-bfs">2. BFS</h3>
<p><strong>2-1. BFS란?</strong>
*<em>2-2. *</em></p>
<h2 id="1-dfs-1">1. DFS</h2>
<h3 id="1-1-dfs란">1-1. DFS란?</h3>
<ul>
<li><strong>깊이 우선 탐색(depth-first search, DFS)</strong>은 <a href="https://ko.wikipedia.org/wiki/%ED%87%B4%EA%B0%81%EA%B2%80%EC%83%89">맹목적 탐색</a>방법의 하나로 탐색트리의 최근에 첨가된 노드를 선택한다.
그 다음 이 노드에 적용 가능한 동작자 중 하나를 적용하여 트리에 다음 수준(level)의 한 개의 자식노드를 첨가하고, 첨가된 자식 노드가 목표노드일 때까지 앞의 자식 노드의 첨가 과정을 반복해 가는 방식이다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/syo_ee/post/2758207e-c0c3-4b3b-b51c-431b58de2869/image.gif" alt=""></p>
<h3 id="1-2-깊이-제한과-백트래킹">1-2. 깊이 제한과 백트래킹</h3>
<ul>
<li>탐색 과정이 시작 노드에서 한없이 깊이 진행되는 것을 막기 위해 깊이 제한(depth bound)을 사용한다.</li>
<li>깊이 제한에 도달할 때까지 목표노드가 발견되지 않으면 최근에 첨가된 노드의 부모노드로 되돌아와서, 부모노드에 이전과는 다른 동작자를 적용하여 새로운 자식노드를 생성한다.
여기서 부모노드로 되돌아오는 과정을 <a href="https://ko.wikipedia.org/wiki/%ED%87%B4%EA%B0%81%EA%B2%80%EC%83%89"><strong>백트래킹(backtracking)</strong></a>이라 한다.</li>
</ul>
<h3 id="1-3-알고리즘">1-3. 알고리즘</h3>
<ul>
<li>만일 트리가 아닌 그래프를 탐색하게 된다면 약간의 변화가 필요하다.</li>
<li>우선 그래프에서의 깊이(depth)를 결정할 필요가 있다.
일반적으로 그래프에서는 루트 노드의 깊이를 0으로 하며, 임의의 노드의 깊이는 이의 부모 중 가장 깊이가 작은 것의 깊이에 1을 더한 값으로 정의한다.
따라서 그래프에서의 깊이우선탐색은 OPEN에 있는 노드 중 가장 깊은 것을 택하여 확장시키게 된다.
후계 노드가 생성되어 이 중에 이미 OPEN이나 CLOSED에 있는 것이 있다면, 깊이를 재조정하여야 한다.</li>
<li>여기서 알 수 있는 것은 일반적인 그래프를 탐색하는 경우라도, 탐색 과정에 의하여 얻어지는 노드들과 포인터들은 역시 탐색 트리를 형성한다는 것이다. 즉, 포인터들은 오직 하나의 부모를 가리키게 된다.</li>
</ul>
<h3 id="1-4-장단점">1-4. 장단점</h3>
<ul>
<li><strong>장점</strong><ul>
<li>단지 현 경로상의 노드들만을 기억하면 되므로 저장공간의 수요가 비교적 적다.</li>
<li>목표노드가 깊은 단계에 있을 경우 해를 빨리 구할 수 있다.</li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>해가 없는 경로에 깊이 빠질 가능성이 있다.
따라서 실제의 경우 미리 지정한 임의의 깊이까지만 탐색하고 목표노드를 발견하지 못하면 다음의 경로를 따라 탐색하는 방법이 유용할 수 있다.</li>
<li>얻어진 해가 최단 경로가 된다는 보장이 없다.
이는 목표에 이르는 경로가 다수인 문제에 대해 깊이우선 탐색은 해에 다다르면 탐색을 끝내버리므로, 이때 얻어진 해는 최적이 아닐 수 있다는 의미이다.</li>
</ul>
</li>
</ul>
<h3 id="1-5-시간과-저장공간-분석">1-5. 시간과 저장공간 분석</h3>
<ul>
<li>깊이 우선 탐색에서 필요로 하는 시간과 저장공간을 분석해 보면, 일반적으로는 시간에 대해 말하기가 좀 어려운데, 이는 트리가 무한할 경우, 깊이우선 탐색은 목적을 만족시키는 노드를 찾지 못할 수도 있을 뿐 아니라 알고리즘이 끝나지 않을 수도 있기 때문이다.
트리의 깊이가 목적 노드 깊이와 같을 때에는 최악의 경우 모든 노드를 다 검사하게 된다.</li>
<li>이 경우 전체 시간은
$$
1+b+b^{2}+\cdots +b^{d}={\frac  {b^{{d+1}}-1}{b-1}}
$$
이 된다.</li>
<li>깊이우선 탐색에 의해 방문된 노드수의 평균은
$$
{\frac  {b^{{d+1}}+db+b-d-2}{2(b-1)}}
$$
인데, 이것은 $d+1$과 ${\frac  {b^{{d+1}}-1}{b-1}}$ 의 평균이며, 이 때 $d+1$은 목적 노드가 제일 왼쪽에 있을 때 방문하게 되는 노드의 수이고, ${\frac  {b^{{d+1}}-1}{b-1}}$ 은 목적 노드가 탐색트리의 맨 오른쪽에 있을 때 방문하는 노드의 수이다.</li>
<li>저장공간의 사용량을 계산해 보면 좀 더 희망적인 결과를 얻을 수 있다.</li>
<li>탐색공간 내의 각 노드 $n$에서 깊이우선 탐색 알고리즘이 기억해야 할 것은 루트 노드로부터 $n$까지의 경로 상에 있는 각 노드의 자식들 중 $n$을 제외한 것들이다.
그러한 경로 중 가장 긴 경로의 길이가 $d$이므로, 최악의 경우 깊이우선 탐색은 $d(b-1)+1$에 비례하는 비교적 적은 공간을 사용한다.
연구 결과에 따르면 깊이우선 탐색은 저장공간의 사용에 있어서 점근 최적인 것으로 알려져 있다.</li>
</ul>
<hr/>

<h2 id="2-bfs-1">2. BFS</h2>
<h3 id="2-1-bfs란">2-1. BFS란?</h3>
<ul>
<li><strong>너비 우선 탐색(Breadth-first search, BFS)</strong>은 맹목적 탐색방법의 하나로 시작 정점을 방문한 후 시작 정점에 인접한 모든 정점들을 우선 방문하는 방법이다.
더 이상 방문하지 않은 정점이 없을 때까지 방문하지 않은 모든 정점들에 대해서도 너비 우선 검색을 적용한다.
OPEN List는 큐를 사용해야만 레벨 순서대로 접근이 가능하다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/syo_ee/post/e404340f-5777-4aff-b8c2-34f52c4ed864/image.png" alt=""></p>
<h3 id="2-2-장단점">2-2. 장단점</h3>
<ul>
<li><strong>장점</strong><ul>
<li>출발노드에서 목표노드까지의 최단 길이 경로를 보장한다.<br/></li>
</ul>
</li>
<li><strong>단점</strong><ul>
<li>경로가 매우 길 경우에는 탐색 가지가 급격히 증가함에 따라 보다 많은 기억 공간을 필요로 하게 된다.</li>
<li>해가 존재하지 않는다면 유한 그래프(finite graph)의 경우에는 모든 그래프를 탐색한 후에 실패로 끝난다.</li>
<li>무한 그래프(infinite graph)의 경우에는 결코 해를 찾지도 못하고, 끝내지도 못한다.</li>
</ul>
</li>
</ul>
<hr/>

<h3 id="wikipedia---dfs">Wikipedia - DFS</h3>
<p><a href="https://ko.wikipedia.org/wiki/%EA%B9%8A%EC%9D%B4_%EC%9A%B0%EC%84%A0_%ED%83%90%EC%83%89">https://ko.wikipedia.org/wiki/%EA%B9%8A%EC%9D%B4_%EC%9A%B0%EC%84%A0_%ED%83%90%EC%83%89</a></p>
<h3 id="wikipedia---bfs">Wikipedia - BFS</h3>
<p><a href="https://ko.wikipedia.org/wiki/%EB%84%88%EB%B9%84_%EC%9A%B0%EC%84%A0_%ED%83%90%EC%83%89">https://ko.wikipedia.org/wiki/%EB%84%88%EB%B9%84_%EC%9A%B0%EC%84%A0_%ED%83%90%EC%83%89</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식문서 이해하기 (30)]]></title>
            <link>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-30</link>
            <guid>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-30</guid>
            <pubDate>Sun, 10 Dec 2023 11:58:50 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-escape-hatches">Chapter 4. Escape Hatches</h1>
<h2 id="8-커스텀-훅으로-로직-재사용하기">#8 커스텀 훅으로 로직 재사용하기</h2>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 커스텀 훅: 컴포넌트간의 로직 공유</strong>
<strong>2. 훅 사이에 반응형 값 전달하기</strong>
<strong>3. 언제 커스텀 훅을 사용할 것인가</strong></p>
<hr/>

<h3 id="1-커스텀-훅-컴포넌트간의-로직-공유">1. 커스텀 훅: 컴포넌트간의 로직 공유</h3>
<ul>
<li>대부분의 앱이 그렇듯이 네트워크에 크게 의존하는 앱을 개발한다고 가정해 보자.
사용자가 앱을 사용하는 동안 실수로 네트워크 연결이 끊어진 경우 사용자에게 주의를 줄 경우 어떻게 하면 좋을까?
이럴 경우에 컴포넌트에는 두 가지가 필요하다.<ol>
<li>네트워크가 온라인 상태인지 여부를 추적하는 state</li>
<li>전역 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/online_event"><code>online</code></a> 및 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/offline_event"><code>offline</code></a> 이벤트를 구독하고, state를 업데이트하는 Effect</li>
</ol>
</li>
<li>이렇게 하면 컴포넌트가 네트워크 state와 동기화된 상태로 유지된다.</li>
</ul>
<p><strong>1-1. 컴포넌트에서 커스텀 훅 추출하기</strong></p>
<ul>
<li><strong>컴포넌트 내부의 코드가 (브라우저 이벤트에 가입하여) 어떻게 할 것인가가 아니라 무엇을 할 것인가(온라인 상태 사용!)를 설명한다는 점이다.</strong></li>
<li>로직을 커스텀 훅으로 추출하면 외부 시스템이나 브라우저 API를 처리하는 방법에 대한 지저분한 세부 사항을 숨길 수 있다.
컴포넌트의 코드는 <em>구현_이 아니라 _의도를</em> 표현한다.</li>
</ul>
<p><strong>1-2. 훅의 이름은 언제나 use로 시작된다.</strong></p>
<ul>
<li>React 애플리케이션은 컴포넌트로 빌드된다.</li>
<li>컴포넌트는 빌트인이든 커스텀이든 상관없이 훅으로 빌드된다.</li>
<li>다른 사람이 만든 커스텀 훅을 사용하는 경우가 많지만, 가끔은 직접 작성할 수도 있다.
이때는 다음 명명 규칙을 따라야 한다.<ol>
<li>React 컴포넌트 이름은 <code>StatusBar</code>나 <code>SaveButton</code>과 같이 대문자로 시작해야한다.
또한 React 컴포넌트는 JSX와 같이 React가 표시하는 방법을 알고 있는 것을 반환해야한다.</li>
<li>훅의 이름은 <code>useState</code>(빌트인)이나 <code>useOnlineStatus</code>(커스텀)처럼 <strong><code>use</code>로 시작</strong>해야 하고, 그 다음의 첫글자는 대문자여야 합니다. 훅은 임의의 값을 반환할 수 있습니다.</li>
</ol>
</li>
<li>이 규칙은 컴포넌트를 보고 state, Effect 및 기타 React 기능이 어디에 “숨어 있는지” 항상 알 수 있도록 보장합니다. </li>
</ul>
<blockquote>
<p><span style="color:coral"><strong>KeyNote</strong></span></p>
</blockquote>
<ul>
<li>Linter가<a href="https://react.dev/learn/editor-setup#linting"> React용으로 구성된 경우</a>, 이 명명 규칙을 적용한다.
오직 훅과 컴포넌트만이 다른 훅을 호출할 수 있다.</li>
</ul>
<p><strong>1-3. 커스텀 훅은 state 자체가 아닌 상태적인 로직(stateful logic)을 공유한다.</strong></p>
<ul>
<li>커스텀 훅을 사용하면 상태 로직(stateful logic)은 공유할 수 있지만 state 자체는 공유할 수 없다.</li>
<li>각 훅 호출은 동일한 훅에 대한 다른 모든 호출과 완전히 독립적이다.</li>
<li>여러 컴포넌트 간에 state 자체를 공유해야 하는 경우, 대신 끌어올려 전달하기를 사용하자.</li>
</ul>
<h3 id="2-훅-사이에-반응형-값-전달하기">2. 훅 사이에 반응형 값 전달하기</h3>
<ul>
<li>컴포넌트를 다시 렌더링할 때마다 커스텀 훅 내부의 코드가 다시 실행된다.
이것이 컴포넌트와 마찬가지로 커스텀 훅도 순수해야 하는 이유이다.
커스텀 Hook의 코드를 컴포넌트 본문의 일부로 생각하자.</li>
<li>커스텀 훅은 컴포넌트와 함께 다시 렌더링되기 때문에 항상 최신 props와 state를 받는다.</li>
</ul>
<p><a href="https://react.dev/learn/reusing-logic-with-custom-hooks#passing-event-handlers-to-custom-hooks"><strong>2-1. 커스텀훅에게 이벤트 핸들러 전달하기</strong></a></p>
<h3 id="3-언제-커스텀-훅을-사용할-것인가">3. 언제 커스텀 훅을 사용할 것인가</h3>
<ul>
<li>중복되는 모든 코드에 대해 커스텀 훅을 추출할 필요는 없다. 약간의 중복은 괜찮다. </li>
<li>하지만 Effect를 작성할 때마다 커스텀 훅으로 감싸는 것이 더 명확할지 고려하자.
Effect는 자주 필요하지 않으므로, 만약 Effect를 작성한다면 외부 시스템과 동기화하거나 React에 빌트인 API가 없는 작업을 수행하기 위해 “React 외부로 나가야 한다”는 뜻이다.</li>
<li>Effect를 커스텀 훅으로 감싸면 의도와 데이터 흐름 방식을 정확하게 전달할 수 있다.</li>
</ul>
<p><strong>3-1. 커스텀 훅은 더 나은 패턴으로 마이그레이션하는데 도움을 준다.</strong></p>
<ul>
<li><strong>마이그레이션을 위해 컴포넌트를 변경할 필요가 없다.</strong></li>
<li>Effect는 “탈출구”:이다.
“React를 벗어나야 할 때”, 그리고 사용 사례에 더 나은 빌트인 솔루션이 없을 때 사용한다.
시간이 지남에 따라 React 팀의 목표는 더 구체적인 문제에 대한 더 구체적인 솔루션을 제공함으로써 앱에서 Effect의 수를 최소한으로 줄이는 것이다.
Effect를 커스텀 훅으로 감싸면 이러한 솔루션이 제공될 때 코드를 더 쉽게 업그레이드할 수 있다.</li>
<li>아래가 커스텀 훅으로 Effect를 감싸는 것이 좋은이유 중 하나이다.<ol>
<li>Effect와의 데이터 흐름을 매우 명확하게 만들 수 있다.</li>
<li>컴포넌트가 Effect의 정확한 구현보다는 의도에 집중할 수 있다.</li>
<li>React가 새로운 기능을 추가할 때 컴포넌트를 변경하지 않고도 해당 Effect를 제거할 수 있다.</li>
</ol>
</li>
<li><a href="https://uxdesign.cc/everything-you-need-to-know-about-design-systems-54b109851969">디자인 시스템</a>과 유사하게 앱의 컴포넌트에서 공통된 관용구를 추출하여 커스텀 훅으로 만드는 것이 도움이 될 수 있다.
이렇게 하면 컴포넌트의 코드가 의도에 집중할 수 있고, 원시 Effect를 자주 작성하는 것을 피할 수 있다.
<em>React 커뮤니티에서 관리하고 있는 훌륭한 커스텀 훅도 많이 있다.</em></li>
</ul>
<hr/>

<h2 id="요약">요약</h2>
<ul>
<li>커스텀 훅을 사용하면 컴포넌트 간에 로직을 공유할 수 있다.</li>
<li>커스텀 훅의 이름은 use로 시작하고 대문자로 끝나야한다.</li>
<li>커스텀 훅은 상태적 로직만 공유하며 state 자체는 공유하지 않는다.</li>
<li>반응형 값을 한 훅에서 다른 훅으로 전달할 수 있으며 최신 state로 유지된다.</li>
<li>컴포넌트가 다시 렌더링될 때마다 모든 훅이 다시 실행된다.</li>
<li>커스텀 훅의 코드는 컴포넌트의 코드와 같이 순수해야한다.</li>
<li>커스텀 훅이 수신한 이벤트 핸들러를 Effect Event로 감싸자.</li>
<li>useMount와 같은 커스텀 훅을 만들지 마세요. 용도를 명확히 하라.</li>
<li>코드의 경계를 어디에서 어떻게 선택할지는 여러분이 결정할 수 있다.</li>
</ul>
<hr/>

<h2 id="회고">회고</h2>
<ul>
<li>공식문서를 정독하며 정답이지만 모든 것이 정답은 아닌듯 했다.</li>
<li>여러 예시들이 있어 정리하기 힘들었지만 이해하기에는 그보다 더 용이했다.</li>
<li>당장 프로젝트에 적용하여 사용할 수는 있겠지만 러닝커브로 인해 다소 걸릴 것으로 보인다.</li>
<li>React를 사용한다면, 학생 때 영어 단어 외우듯 자주 공식문서를 봐줘야겠다는 생각이 든다.</li>
</ul>
<hr/>

<h3 id="react-공식-문서">React 공식 문서</h3>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<h3 id="react-비공식-번역-문서">React 비공식 번역 문서</h3>
<p><a href="https://react-ko.dev/">https://react-ko.dev/</a></p>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/ko/">https://developer.mozilla.org/ko/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/wiki/">https://ko.wikipedia.org/wiki/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식문서 이해하기 (29)]]></title>
            <link>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-29</link>
            <guid>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-29</guid>
            <pubDate>Sat, 09 Dec 2023 14:53:25 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-escape-hatches">Chapter 4. Escape Hatches</h1>
<h2 id="7-effect-의존성-제거하기">#7 Effect 의존성 제거하기</h2>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 의존성은 코드와 일치해야 한다.</strong>
<strong>2. 불필요한 의존성 제거하기</strong></p>
<hr/>

<h3 id="1-의존성은-코드와-일치해야-한다">1. 의존성은 코드와 일치해야 한다.</h3>
<ul>
<li>Effect를 작성할 때는 먼저 Effect가 수행하기를 원하는 작업을 시작하고 중지하는 방법을 지정한다.</li>
<li>그런 다음 Effect 의존성을 비워두면(<code>[]</code>) 린터가 올바른 의존성을 제안한다.</li>
<li>Effect는 반응형 값에 “반응”한다.</li>
</ul>
<p><strong>1-1. 의존성을 제거하려면 의존성이 아님을 증명하라.</strong></p>
<ul>
<li>Effect의 의존성을 “선택”할 수 없다는 점에 유의하라.
Effect의 코드에서 사용되는 모든 반응형 값은 의존성 목록에 선언되어야 합니다. 의존성 목록은 주변 코드에 의해 결정된다.</li>
<li>반응형 값에는 props와 컴포넌트 내부에서 직접 선언된 모든 변수 및 함수가 포함된다.</li>
</ul>
<p><strong>1-2. 의존성을 변경하려면 코드를 변경하라.</strong></p>
<ol>
<li>먼저 Effect의 코드 또는 반응형 값 선언 방식을 변경한다.</li>
<li>그런 다음, 변경한 코드에 맞게 의존성을 조정한다.</li>
<li>의존성 목록이 마음에 들지 않으면 첫 번째 단계로 돌아가서 코드를 다시 변경한다.
<em>(그리고 코드를 다시 변경하자.)</em></li>
</ol>
<ul>
<li>의존성을 변경하려면 먼저 주변 코드를 변경하라.
의존성 목록은 Effect의 코드에서 사용하는 모든 반응형 값의 목록이라고 생각하면 된다.
이 목록에 무엇을 넣을지는 사용자가 선택하지 않는다.
이 목록은 코드를 설명한다. 의존성 목록을 변경하려면 코드를 변경하자.</li>
<li>이것은 방정식을 푸는 것처럼 느껴질 수 있다.
예를 들어, 의존성 제거와 같은 목표를 설정하고 그 목표에 맞는 코드를 “찾아야” 한다.
모든 사람이 방정식을 푸는 것을 재미있어하는 것은 아니며, Effect를 작성할 때도 마찬가지이다.</li>
</ul>
<blockquote>
<p><span style="color:red"><strong>! 주의 !</strong></span></p>
</blockquote>
<ul>
<li>기존 코드베이스가 있는 경우 이와 같이 린터를 억제하는 Effect가 있을 수 있다.<pre><code>useEffect(() =&gt; {
  // ...
  // 🔴 Avoid suppressing the linter like this:
  // eslint-ignore-next-line react-hooks/exhaustive-deps
}, []);</code></pre></li>
<li><strong>의존성이 코드와 일치하지 않으면 버그가 발생할 위험이 매우 높다.</strong>
린터를 억제하면 Effect가 의존하는 값에 대해 React에 “거짓말”을 하게 된다.</li>
</ul>
<h3 id="2-불필요한-의존성-제거하기">2. 불필요한 의존성 제거하기</h3>
<ul>
<li>코드를 반영하기 위해 Effect의 의존성을 조정할 때마다 의존성 목록을 살펴보라.
이러한 의존성 중 하나라도 변경되면 Effect가 다시 실행되는 것이 합리적일까?
가끔 대답은 “아니오”이다.
다른 조건에서 Effect의 다른 부분을 다시 실행하고 싶을 수도 있다.
일부 의존성의 변경에 “반응”하지 않고 “최신 값”만 읽고 싶을 수도 있다.
의존성은 객체나 함수이기 때문에 의도치 않게 너무 자주 변경될 수 있다.</li>
</ul>
<p><a href="https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler"><strong>2-1. 이 코드를 이벤트 핸들러로 옮겨야 하는가?</strong></a>
<a href="https://react.dev/learn/removing-effect-dependencies#is-your-effect-doing-several-unrelated-things"><strong>2-2. Effect가 여러 관련 없는 일을 하고 있는가?</strong></a>
<a href="https://react.dev/learn/removing-effect-dependencies#are-you-reading-some-state-to-calculate-the-next-state"><strong>2-3. 다음 state를 계산하기 위해 어떤 state를 읽고 있는가?</strong></a>
<a href="https://react.dev/learn/removing-effect-dependencies#do-you-want-to-read-a-value-without-reacting-to-its-changes"><strong>2-4. 값의 변경에 ‘반응’하지 않고 값을 읽고 싶은가?</strong></a>
<a href="https://react.dev/learn/removing-effect-dependencies#does-some-reactive-value-change-unintentionally"><strong>2-5. 일부 반응형 값이 의도치 않게 변경되는가?</strong></a></p>
<ul>
<li>위의 <code>2-*</code> 내용들은 아직 안정된 버전의 React로 출시되지 않은 실험적인 API에 대해 설명하는 내용을 포함하고 있어 참고만 하였음.</li>
</ul>
<hr/>

<h2 id="요약">요약</h2>
<ul>
<li>의존성은 항상 코드와 일치해야한다.</li>
<li>의존성이 마음에 들지 않으면 코드를 수정해야한다.</li>
<li>린터를 억제하면 매우 혼란스러운 버그가 발생하므로 항상 피해야한다.</li>
<li>의존성을 제거하려면 해당 의존성이 필요하지 않다는 것을 린터에게 “증명”해야한다.</li>
<li>특정 상호작용에 대한 응답으로 일부 코드가 실행되어야 하는 경우 해당 코드를 이벤트 핸들러로 이동하자.</li>
<li>Effect의 다른 부분이 다른 이유로 다시 실행되어야 하는 경우 여러 개의 Effect로 분할하라.</li>
<li>이전 state를 기반으로 일부 state를 업데이트하려면 업데이터 함수를 전달하라.</li>
<li>“반응”하지 않고 최신 값을 읽으려면 Effect에서 Effect Event를 추출하자.</li>
<li>JavaScript에서 객체와 함수는 서로 다른 시간에 생성된 경우 서로 다른 것으로 간주된다.</li>
<li>객체와 함수의 의존성을 피하라. 컴포넌트 외부나 Effect 내부로 이동하자.</li>
</ul>
<hr/>


<h3 id="react-공식-문서">React 공식 문서</h3>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<h3 id="react-비공식-번역-문서">React 비공식 번역 문서</h3>
<p><a href="https://react-ko.dev/">https://react-ko.dev/</a></p>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/ko/">https://developer.mozilla.org/ko/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/wiki/">https://ko.wikipedia.org/wiki/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식문서 이해하기 (28)]]></title>
            <link>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-28</link>
            <guid>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-28</guid>
            <pubDate>Fri, 08 Dec 2023 13:56:20 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-escape-hatches">Chapter 4. Escape Hatches</h1>
<h2 id="6-이벤트와-effect-분리하기">#6 이벤트와 Effect 분리하기</h2>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 이벤트 핸들러와 Effect 중 선택하기</strong>
<strong>2. 반응형 값 및 반응형 로직</strong>
<strong>3. Effect에서 비반응형 로직 추출하기</strong></p>
<hr/>

<h3 id="1-이벤트-핸들러와-effect-중-선택하기">1. 이벤트 핸들러와 Effect 중 선택하기</h3>
<ul>
<li>채팅방 컴포넌트를 구현한다고 상상해보자.<ol>
<li>컴포넌트는 선택한 채팅방에 자동으로 연결되어야 합니다.</li>
<li>“전송” 버튼을 클릭하면, 채팅에 메시지를 전송해야 합니다.</li>
</ol>
</li>
<li>이미 코드를 구현했지만, 이 코드를 어디에 놓아야 할지 확실하지 않다.
이벤트 핸들러와 Effects 중 어떤 것을 사용해야 할까?</li>
<li><em>이 질문에 대답할 때마다, 코드가 실행되어야하는 이유를 생각해야한다.*</em></li>
</ul>
<p><strong>1-1. 이벤트 핸들러는 특정 상호 작용에 대한 응답으로 실행된다.</strong></p>
<ul>
<li>사용자 관점에서 메시지를 보내는 것은 특정 “전송” 버튼을 클릭했기 때문에 발생되어야한다.
메시지가 다른 시간이나 다른 이유로 보내지면 사용자들은 화가 날 것이다.
이것이 메시지를 보내는 것이 이벤트 핸들러여야하는 이유이다.
이벤트 핸들러를 사용하면 특정 상호 작용에 대한 처리를 할 수 있다.<pre><code>function ChatRoom({ roomId }) {
const [message, setMessage] = useState(&#39;&#39;);
// ...
function handleSendClick() {
  sendMessage(message);
}
// ...
return (
  &lt;&gt;
    &lt;input value={message} onChange={e =&gt; setMessage(e.target.value)} /&gt;
    &lt;button onClick={handleSendClick}&gt;Send&lt;/button&gt;;
  &lt;/&gt;
);
}</code></pre></li>
<li>이벤트 핸들러를 사용하면 사용자가 버튼을 누를 때만 <code>sendMessage(message)</code> 가 실행된다는 것을 확신할 수 있다.</li>
</ul>
<p><strong>1-2. Effect는 동기화가 필요할 때마다 실행된다.</strong></p>
<ul>
<li>코드를 실행해야 하는 <em>이유</em> 는 특정 상호 작용이 아니다.
사용자가 채팅방 화면으로 이동한 이유나 방법은 중요하지 않다.
사용자들이 채팅방 화면을 보고 상호작용 할 수 있어야하기 때문에, 컴포넌트는 선택한 채팅 서버에 계속 연결되어 있어야 한다.
앱의 초기 화면이 채팅방 컴포넌트이고, 사용자가 어떤 상호작용도 수행하지 않았더라도 <em>여전히</em> 채팅 서버에 연결되어야한다. 이것이 바로 Effect인 이유이다.<pre><code>function ChatRoom({ roomId }) {
// ...
useEffect(() =&gt; {
 const connection = createConnection(serverUrl, roomId);
 connection.connect();
 return () =&gt; {
   connection.disconnect();
 };
}, [roomId]);
// ...
}</code></pre></li>
<li>이 코드를 사용하면, 사용자가 수행한 특정 상호작용과는 무관하게, 항상 현재 선택된 채팅 서버에 대한 활성화된 연결이 있음을 확신할 수 있다.</li>
<li>사용자가 앱을 열었을 때 뿐만 아니라, 다른 채팅방을 선택하거나 다른 화면으로 이동했다가 다시 돌아와도 Effect가 현재 선택된 방과 동기화되어 컴포넌트에 항상 현재 선택된 채팅서버가 연결된 상태가 유지된다. 또한, 필요할 때마다 다시 연결된다.</li>
</ul>
<h3 id="2-반응형-값-및-반응형-로직">2. 반응형 값 및 반응형 로직</h3>
<ul>
<li>직관적으로, 이벤트 핸들러는 버튼을 클릭하는 등 항상 “수동”으로 트리거시킨다고 말할 수 있다.
반면에 Effect는 “자동”으로 동기화 상태를 유지하는 데 필요한 만큼 자주 다시 실행된다.</li>
<li>컴포넌트 본문 내부에 선언된 props, state, 변수를 반응형 값이라고 합니다.
아래 예제에서 <code>serverUrl</code>은 <code>반응형 값</code>이 아니지만 <code>roomId</code>와 <code>message</code>는 반응형 값이다.<pre><code>const serverUrl = &#39;https://localhost:1234&#39;;
</code></pre></li>
</ul>
<p>function ChatRoom({ roomId }) {
  const [message, setMessage] = useState(&#39;&#39;);</p>
<p>  // ...
}</p>
<pre><code>- 이와 같은 반응형 값은 리렌더링으로 인해 변경될 수 있습니다. 예를 들어, 사용자가 message를 수정하거나 드롭다운에서 다른 roomId를 선택할 수 있습니다. 이벤트 핸들러와 Effect는 변경 사항에 다르게 반응한다.
  - **이벤트 핸들러 내부의 로직은 반응형이 아니다.**
  사용자가 동일한 상호작용(예: 클릭)을 다시 수행하지 않는 한 다시 실행되지 않는다.
  이벤트 핸들러는 변경에 “반응”하지 않고 반응형 값을 읽을 수 있다.
  - **Effects 내부의 로직은 반응형이다.**
  Effect에서 반응형 값을 읽는 경우 의존성으로 지정해야 한다.
  그런 다음 리렌더링으로 인해 해당 값이 변경되면 React는 새 값으로 Effect의 로직을 다시 실행한다.

**2-1. 이벤트 핸들러 내부의 로직은 반응형이 아니다.**</code></pre><p> // ...
    sendMessage(message);
 // ...</p>
<pre><code>- 사용자의 관점에서 볼 때 message가 변경되었다고 해서 메시지를 보내겠다는 뜻은 아니다.
사용자가 입력 중이라는 의미일 뿐이다.
즉, 메시지를 전송하는 로직은 반응적이어서는 안 된다.
반응형 값이 변경되었다는 이유만으로 다시 실행되어서는 안 된다.
이것이 바로 이벤트 핸들러에 속하는 이유이다.</code></pre><p>  function handleSendClick() {
    sendMessage(message);
  }</p>
<pre><code>- 이벤트 핸들러는 반응형이 아니므로 사용자가 보내기 버튼을 클릭할 때만 ```sendMessage(message)```가 실행된다.

**2-2. Effect 내부의 로직은 반응형이다.**</code></pre><p>  // ...
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
  // ...</p>
<pre><code>- 사용자 입장에서 보면, ```roomId```가 변경되었다는 것은 다른 룸에 연결하고 싶다는 의미이다.
즉, 방에 연결하기 위한 로직은 반응형이어야한다.
이러한 코드 라인은 반응형 값을 “따라잡고”, 값이 달라지면 다시 실행되기를 원한다.
이것이 바로 Effect에 속하는 이유이다.</code></pre><p>  useEffect(() =&gt; {
    const connection = createConnection(serverUrl, roomId);
    connection.connect();
    return () =&gt; {
      connection.disconnect()
    };
  }, [roomId]);</p>
<pre><code>- Effect는 반응형이므로 ```createConnection(serverUrl, roomId)``` 및 ```connection.connect()``` 코드는 ```roomId```의 모든 고유값에 대해 실행된다.
Effect는 현재 선택된 방에 따라 채팅 연결을 동기화한다.


### 3. Effect에서 비반응형 로직 추출하기

- 반응형 로직과 비반응형 로직을 함께 사용하려는 경우 상황이 더 까다로워진다.
- 예를 들어, 사용자가 채팅에 연결할 때 알림을 표시하고 싶다고 가정해 보자.
props에서 현재 테마(dark or light)를 읽어 올바른 색상으로 알림을 표시한다.</code></pre><p>function ChatRoom({ roomId, theme }) {
  useEffect(() =&gt; {
    const connection = createConnection(serverUrl, roomId);
    connection.on(&#39;connected&#39;, () =&gt; {
      showNotification(&#39;Connected!&#39;, theme);
    });
    connection.connect();
// ...</p>
<pre><code>- 그러나, theme는 반응형 값이며(리렌더링의 결과로 변경될 수 있음), Effect에서 읽는 모든 반응형 값은 의존성으로 선언해야한다.
이제 theme를 Effect의 의존성으로 지정해야한다.</code></pre><p>function ChatRoom({ roomId, theme }) {
  useEffect(() =&gt; {
    const connection = createConnection(serverUrl, roomId);
    connection.on(&#39;connected&#39;, () =&gt; {
      showNotification(&#39;Connected!&#39;, theme);
    });
    connection.connect();
    return () =&gt; {
      connection.disconnect()
    };
  }, [roomId, theme]);
  // ...</p>
<p>```</p>
<p><a href="https://react.dev/learn/separating-events-from-effects#declaring-an-effect-event"><strong>3-1. Effect Event 선언하기</strong></a>
<a href="https://react.dev/learn/separating-events-from-effects#reading-latest-props-and-state-with-effect-events"><strong>3-2. Effect Event로 최신 props 및 state 읽기</strong></a>
<a href="https://react.dev/learn/separating-events-from-effects#limitations-of-effect-events"><strong>3-3. Effect Event의 제한사항</strong></a></p>
<ul>
<li>위의 <code>3-*</code> 내용들은 아직 안정된 버전의 React로 출시되지 않은 실험적인 API에 대해 설명하고 있어 참고만 하였음.</li>
</ul>
<hr/>

<h2 id="요약">요약</h2>
<ul>
<li>이벤트 핸들러는 특정 상호 작용에 대한 응답으로 실행된다.</li>
<li>Effect는 동기화가 필요할 때마다 실행된다.</li>
<li>이벤트 핸들러 내부의 로직은 반응형이 아니다.</li>
<li>Effect 내부의 로직은 반응적이다.</li>
<li>비반응적 로직을 Effect에서 Effect Event로 이동할 수 있다.</li>
<li>Effect 내부에서만 Effect Event를 호출하라.</li>
<li>Effect Event를 다른 컴포넌트나 Hook에 전달하지 말자.</li>
</ul>
<hr/>


<h3 id="react-공식-문서">React 공식 문서</h3>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<h3 id="react-비공식-번역-문서">React 비공식 번역 문서</h3>
<p><a href="https://react-ko.dev/">https://react-ko.dev/</a></p>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/ko/">https://developer.mozilla.org/ko/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/wiki/">https://ko.wikipedia.org/wiki/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식문서 이해하기 (27)]]></title>
            <link>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-27</link>
            <guid>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-27</guid>
            <pubDate>Thu, 07 Dec 2023 14:40:35 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-escape-hatches">Chapter 4. Escape Hatches</h1>
<h2 id="5-반응형-effect의-생명주기">#5 반응형 Effect의 생명주기</h2>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. Effect의 생명주기</strong>
<strong>2. Effect는 ‘반응형 값’에 반응한다.</strong></p>
<hr/>

<h3 id="1-effect의-생명주기">1. Effect의 생명주기</h3>
<ul>
<li>모든 React 컴포넌트는 동일한 생명주기를 거친다.<ul>
<li>컴포넌트는 화면에 추가될 때 마운트된다.</li>
<li>컴포넌트는 새로운 props나 state를 받으면 업데이트됩니다. 이는 보통 상호작용에 대한 응답으로 발생한다.</li>
<li>화면에서 제거되면 컴포넌트가 마운트 해제된다.</li>
</ul>
</li>
<li>컴포넌트에 대해 생각하는 좋은 방법이지만 Effect에 대해서는 생각하지 않는 것이 좋다.</li>
<li>대신 각 Effect를 컴포넌트의 생명주기와 독립적으로 생각해보자.
Effect는 외부 시스템을 현재 props 및 state에 동기화하는 방법을 설명한다.
코드가 변경되면 이 동기화를 더 자주 또는 덜 자주 수행해야한다.</li>
<li>직관적으로 React는 컴포넌트가 마운트될 때 동기화를 시작하고 컴포넌트가 마운트 해제될 때 동기화를 중지할 것이라고 생각할 수 있다.
때로는 컴포넌트가 마운트된 상태에서 동기화를 여러 번 시작하고 중지해야 할 수도 있다.</li>
</ul>
<blockquote>
<p><span style="color:coral"><strong>KeyNote</strong></span></p>
</blockquote>
<ul>
<li>일부 Effect는 클린업 함수를 전혀 반환하지 않는다.</li>
<li>대부분의 경우 함수를 반환하고 싶겠지만, 그렇지 않은 경우 React는 아무 작업도 하지 않는 빈 클린업 함수를 반환한 것처럼 동작한다.</li>
</ul>
<p><strong>1-1. 동기화가 두 번 이상 수행되어야 하는 이유</strong></p>
<ul>
<li>Effect의 본문은 동기화를 시작하는 방법을 지정하고, 클린업 함수는 동기화를 중지하는 방법을 지정합니다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#why-synchronization-may-need-to-happen-more-than-once"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>1-2. React가 Effect를 재동기화 하는 방법</strong></p>
<ul>
<li>다른 컴포넌트에 다시 연결하려면 React가 Effect를 다시 동기화해야한다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#how-react-re-synchronizes-your-effect"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>1-3. Effect의 관점에서 생각하기</strong></p>
<ul>
<li>항상 한 번에 하나의 시작/중지 사이클에만 집중하자.
컴포넌트를 마운트, 업데이트 또는 마운트 해제하는 것은 중요하지 않다.
동기화를 시작하는 방법과 중지하는 방법만 설명하면 된다.
이 작업을 잘 수행하면 필요한 횟수만큼 Effect를 시작하고 중지할 수 있는 탄력성을 확보할 수 있다.</li>
<li>JSX를 생성하는 렌더링 로직을 작성할 때 컴포넌트가 마운트되는지 업데이트되는지 생각하지 않는 것을 떠올리면 이해가 쉬울 것이다.
화면에 무엇이 표시되어야 하는지 설명하면 나머지는 React가 알아서 처리한다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#thinking-from-the-effects-perspective"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>1-4. React가 Effect의 재동기화 가능 여부를 확인하는 방법</strong></p>
<ul>
<li>개발 모드에서 React는 즉시 강제로 동기화를 수행하여 Effect가 다시 동기화될 수 있는지 확인한다.
도어락이 작동하는지 확인하기 위해 문을 열었다가 한 번 더 닫는 것과 비슷하다.
React는 개발 중에 Effect를 한 번 더 시작하고 중지하여 클린업 함수를 잘 구현했는지 확인한다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#how-react-verifies-that-your-effect-can-re-synchronize"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>1-5. React가 Effect의 재동기화 필요성을 인식하는 방법</strong></p>
<ul>
<li>컴포넌트가 다시 렌더링될 때마다 React는 사용자가 전달한 의존성 배열을 살펴보자.
배열의 값 중 하나라도 이전 렌더링 중에 전달한 동일한 지점의 값과 다르면 React는 Effect를 다시 동기화한다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#how-react-knows-that-it-needs-to-re-synchronize-the-effect"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>1-6. 각각의 Effect는 별도의 동기화 프로세스를 나타낸다.</strong></p>
<ul>
<li>일관된 로직을 별도의 Effect로 분리하면 코드가 “더 깔끔해” 보일 수 있지만 유지 관리가 더 어려워진다.
따라서 코드가 더 깔끔해 보이는지 여부가 아니라 프로세스가 동일한지 또는 분리되어 있는지를 고려해야 한다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#each-effect-represents-a-separate-synchronization-process"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<h3 id="2-effect는-반응형-값에-반응한다">2. Effect는 ‘반응형 값’에 반응한다.</h3>
<ul>
<li>컴포넌트 내부에서 선언된 props, state 및 기타 값은 렌더링 중에 계산되고 React 데이터 흐름에 참여하기 때문에 반응형이다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#effects-react-to-reactive-values"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>2-1. 빈 의존성을 가지고 있는 Effect의 의미</strong></p>
<ul>
<li>Effect의 관점에서 생각하면 마운트 및 마운트 해제에 대해 전혀 생각할 필요가 없다.
중요한 것은 Effect가 동기화를 시작하고 중지하는 작업을 지정한 것이다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>2-2. 컴포넌트 본문에서 선언된 모든 변수는 반응형이다.</strong></p>
<ul>
<li>props와 state만 반응형 값인 것은 아니다.
이들로부터 계산하는 값들 역시 반응형이다.</li>
<li>props나 state가 변경되면 컴포넌트가 다시 렌더링되고 그로부터 계산된 값도 변경된다.
그렇기 때문에 Effect가 사용하는 컴포넌트 본문의 모든 변수는 Effect 의존성 목록에 있어야 한다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#all-variables-declared-in-the-component-body-are-reactive"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<p><strong>2-3. React는 모든 반응형 값을 의존성으로 지정했는지 검토한다.</strong></p>
<ul>
<li>린터가 <a href="https://react.dev/learn/editor-setup#linting">React에 맞게 구성된 경우</a>, Effect 코드에서 사용되는 모든 반응형 값이 해당 의존성으로 선언되었는지 확인한다.</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#react-verifies-that-you-specified-every-reactive-value-as-a-dependency"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<blockquote>
<p><span style="color:coral"><strong>KeyNote</strong></span></p>
</blockquote>
<ul>
<li>어떤 경우에는 컴포넌트 내부에서 값이 선언되더라도 절대 변하지 않는다는 것을 React가 알고 있다.
예를 들어, useState에서 반환된 <a href="https://react-ko.dev/reference/react/useState#setstate"><code>설정자 함수</code></a>와 <a href="https://react-ko.dev/reference/react/useRef"><code>useRef</code></a>에서 반환된 ref 객체는 리렌더링 시 변경되지 않도록 보장되는 안정적인 값이다.</li>
<li>안정적인 값은 반응하지 않으므로 린터를 사용하면 목록에서 생략할 수 있다.
그러나 이러한 값을 포함하는 것은 허용된다. 변경되지 않으므로 상관없다.</li>
</ul>
<p><strong>2-4. 재동기화를 원치 않는 경우엔 어떻게 해야 하는가?</strong></p>
<ul>
<li><strong>Effect는 반응형 코드 블록이다.</strong>
내부에서 읽은 값이 변경되면 다시 동기화된다.
상호작용당 한 번만 실행되는 이벤트 핸들러와 달리 Effect는 동기화가 필요할 때마다 실행된다.</li>
<li><strong>의존성을 “선택”할 수는 없다.</strong>
의존성에는 Effect에서 읽은 모든 반응형 값이 포함되어야 한다.린터가 이를 강제한다.
때때로 이로 인해 무한 루프와 같은 문제가 발생하거나 Effect가 너무 자주 다시 동기화될 수 있다.
린터를 억제하여 이러한 문제를 해결하지 말자.
<em>대신 시도할 수 있는 방법은 다음과 같다.</em><ul>
<li><strong>Effect가 독립적인 동기화 프로세스를 나타내는지 확인하라.</strong>
Effect가 아무것도 동기화하지 않는다면 불필요한 것일 수 있다.
여러 개의 독립적인 것을 동기화하는 경우 분할하라.</li>
<li><strong>‘반응’하지 않고 Effect를 재동기화하지 않으면서 props나 state의 최신 값을 읽으려면</strong>, Effect를 반응하는 부분(Effect에 유지)과 반응하지 않는 부분(Effect Event라는 것으로 추출)으로 분리할 수 있다.</li>
<li><strong>객체와 함수를 의존성으로 사용하지 말자.</strong>
렌더링 중에 오브젝트와 함수를 생성한 다음 Effect에서 읽으면 렌더링할 때마다 오브젝트와 함수가 달라진다.
그러면 매번 Effect를 다시 동기화해야 한다.</li>
</ul>
</li>
<li><a href="https://react.dev/learn/lifecycle-of-reactive-effects#what-to-do-when-you-dont-want-to-re-synchronize"><del><em>예시 코드 참조</em></del></a></li>
</ul>
<blockquote>
<p><span style="color:red"><strong>! 주의 !</strong></span></p>
</blockquote>
<ul>
<li><strong>린터는 의존성이 잘못되었을 때만 알 수 있다.</strong>
각 사례를 해결하는 최선의 방법은 알지 못한다.</li>
<li>만약 린터가 의존성을 제안하지만 이를 추가하면 루프가 발생한다고 해서 린터를 무시해야 한다는 의미는 아니다.
해당 값이 반응적이지 않고 의존성이 될 필요가 없도록 Effect 내부(또는 외부)의 코드를 변경해야 한다는 뜻이다.</li>
</ul>
<hr/>

<h2 id="요약">요약</h2>
<ul>
<li>컴포넌트는 마운트, 업데이트, 마운트 해제할 수 있다.</li>
<li>각 Effect는 주변 컴포넌트와 별도의 생명주기를 가진다.</li>
<li>각 Effect는 <em>시작</em> 및 <em>중지</em> 할 수 있는 별도의 동기화 프로세스를 설명한다.</li>
<li>Effect를 작성하고 읽을 때는 컴포넌트의 관점(마운트, 업데이트 또는 마운트 해제 방법)이 아니라 각 개별 Effect의 관점(동기화 시작 및 중지 방법)에서 생각해야한다.</li>
<li>컴포넌트 본문 내부에 선언된 값은 “반응형”이다.</li>
<li>반응형 값은 시간이 지남에 따라 변경될 수 있으므로 Effect를 다시 동기화해야한다.</li>
<li>린터는 Effect 내부에서 사용된 모든 반응형 값이 의존성으로 지정되었는지 확인한다.</li>
<li>린터에 의해 플래그가 지정된 모든 오류는 합법적인 오류입니다. 규칙을 위반하지 않도록 코드를 수정할 수 있는 방법은 항상 있다.</li>
</ul>
<hr/>


<h3 id="react-공식-문서">React 공식 문서</h3>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<h3 id="react-비공식-번역-문서">React 비공식 번역 문서</h3>
<p><a href="https://react-ko.dev/">https://react-ko.dev/</a></p>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/ko/">https://developer.mozilla.org/ko/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/wiki/">https://ko.wikipedia.org/wiki/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식문서 이해하기 (26)]]></title>
            <link>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-26</link>
            <guid>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-26</guid>
            <pubDate>Wed, 06 Dec 2023 14:46:41 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-escape-hatches">Chapter 4. Escape Hatches</h1>
<h2 id="4-effect가-필요하지-않을-수도-있다">#4 Effect가 필요하지 않을 수도 있다.</h2>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 불필요한 Effect를 제거하는 방법</strong></p>
<hr/>

<h3 id="1-불필요한-effect를-제거하는-방법">1. 불필요한 Effect를 제거하는 방법</h3>
<ul>
<li><p>Effect가 필요하지 않은 흔한 경우는 두 가지가 있다.</p>
<ol>
<li><p><strong>렌더링을 위해 데이터를 변환하는 경우 Effect는 필요하지 않다.</strong>
예를 들어, 목록을 표시하기 전에 필터링하고 싶다고 가정해 보자.
목록이 변경될 때 state 변수를 업데이트하는 Effect를 작성하고 싶을 수 있다.
하지만 이는 비효율적이다.
컴포넌트의 state를 업데이트할 때 React는 먼저 컴포넌트 함수를 호출해 화면에 표시될 내용을 계산한다.
다음으로 이러한 변경 사항을 DOM에 “commit”하여 화면을 업데이트하고, 그 후에 Effect를 실행한다.
만약 Effect “역시” state를 즉시 업데이트한다면, 이로 인해 전체 프로세스가 처음부터 다시 시작될 것이다.
불필요한 렌더링을 피하려면 모든 데이터 변환을 컴포넌트의 최상위 레벨에서 하라.
그러면 props나 state가 변경될 때마다 해당 코드가 자동으로 다시 실행될 것아다.</p>
</li>
<li><p><strong>사용자 이벤트를 처리하는 데에 Effect는 필요하지 않다.</strong>
예를 들어, 사용자가 제품을 구매할 때 /api/buy POST 요청을 전송하고 알림을 표시하고 싶다고 하자.
구매 버튼 클릭 이벤트 핸들러에서는 정확히 어떤 일이 일어났는지 알 수 있다.
반면 Effect는 사용자가 무엇을 했는지(예: 어떤 버튼을 클릭했는지)를 알 수 없다.
그렇기 때문에 일반적으로 사용자 이벤트를 해당 이벤트 핸들러에서 처리한다.</p>
</li>
</ol>
</li>
<li><p>한편 외부 시스템과 동기화하려면 Effect가 필요하다.
예를 들어, <a href="https://jquery.com/">jQuery</a> 위젯을 React state와 동기화하는 Effect를 작성할 수 있다.
또한 검색 결과를 현재의 검색 쿼리와 동기화하기 위해 데이터 요청을 Effect로 처리할 수 있다.
최신 프레임워크는 컴포넌트에 직접 Effects를 작성하는 것보다 더 효율적인 빌트인 데이터 페칭 메커니즘을 제공한다는 점을 명심하라.</p>
</li>
</ul>
<p><strong>1-1. props 또는 state에 따라 state 업데이트하기</strong></p>
<ul>
<li>기존 props나 state에서 계산할 수 있는 것이 있으면 state에 넣지 말자.
대신 렌더링 중에 계산하라. 
이렇게 하면 코드가 더 빨라지고, <em>(추가적인 “계단식” 업데이트를 피함)</em>
더 간단해지고, <em>(일부 코드 제거)</em>
오류가 덜 발생한다. <em>(서로 다른 state 변수가 서로 동기화되지 않아 발생하는 버그를 피함)</em> 이 접근 방식이 생소하게 느껴진다면, React로 사고하기에서 state에 들어가야할 내용이 무엇인지 확인하자.</li>
</ul>
<p><strong>1-2. 복잡한 계산 캐싱하기</strong></p>
<ul>
<li><p>아래 컴포넌트는 props로 받은 <code>todos</code>를 <code>filter</code> prop에 따라 필터링하여 <code>visibleTodos</code>를 계산한다.
이 결과를 state 변수에 저장하고 Effect에서 업데이트하고 싶을 수도 있을 것이다.</p>
<pre><code>function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState(&#39;&#39;);
// 🔴 중복 state 및 불필요한 Effect 사용을 자제하자.
const [visibleTodos, setVisibleTodos] = useState([]);
useEffect(() =&gt; {
  setVisibleTodos(getFilteredTodos(todos, filter));
}, [todos, filter]);

// ...
}</code></pre></li>
<li><p>앞의 예시에서와 마찬가지로 이것은 불필요하고 비효율적이다.
state와 Effect를 제거하자.</p>
<pre><code>function TodoList({ todos, filter }) {
const [newTodo, setNewTodo] = useState(&#39;&#39;);
// ✅ getFilteredTodos()가 느리지 않다면 괜찮다.
const visibleTodos = getFilteredTodos(todos, filter);
// ...
}</code></pre></li>
<li><p><code>getFilteredTodos()</code>가 느리거나 <code>todos</code>가 많을 경우, <code>newTodo</code>와 같이 관련 없는 state 변수가 변경되더라도 <code>getFilteredTodos()</code>를 다시 계산하고 싶지 않을 수 있다.</p>
</li>
<li><p>이럴 땐 복잡한 계산을 <a href="https://react.dev/reference/react/useMemo"><code>useMemo</code></a> 훅으로 감싸서 캐시(또는 “<a href="https://en.wikipedia.org/wiki/Memoization">메모화 (memoize)</a>”)할 수 있다.</p>
<pre><code>import { useMemo, useState } from &#39;react&#39;;
</code></pre></li>
</ul>
<p>function TodoList({ todos, filter }) {
  const [newTodo, setNewTodo] = useState(&#39;&#39;);
  const visibleTodos = useMemo(() =&gt; {
    // ✅ todos나 filter가 변하지 않는 한 재실행되지 않음
    return getFilteredTodos(todos, filter);
  }, [todos, filter]);
  // ...
}</p>
<pre><code>또는 아래와 같이 한 줄로 작성할 수도 있다.</code></pre><p>import { useMemo, useState } from &#39;react&#39;;</p>
<p>function TodoList({ todos, filter }) {
  const [newTodo, setNewTodo] = useState(&#39;&#39;);
  // ✅ todos나 filter가 변하지 않는 한 getFilteredTodos()가 재실행되지 않음
  const visibleTodos = useMemo(() =&gt; getFilteredTodos(todos, filter), [todos, filter]);
  // ...
}</p>
<pre><code>- 이렇게 하면 ```todos```나 ```filter```가 변경되지 않는 한 내부 함수가 다시 실행되지 않기를 원한다는 것을 React에 알린다.
그러면 React는 초기 렌더링 중에 ```getFilteredTodos()```의 반환값을 기억한다.
그 다음부터는 렌더링 중에 할 일이나 필터가 다른지 확인한다.
지난번과 동일하다면 ```useMemo```는 마지막으로 저장한 결과를 반환한다.
같지 않다면, React는 내부 함수를 다시 호출하고 그 결과를 저장한다.
```useMemo```로 감싸는 함수는 렌더링 중에 실행되므로, 순수 계산에만 작동한다.

**1-3. prop이 변경되면 모든 state 재설정하기**

- 다음 ```ProfilePage``` 컴포넌트는 ```userId``` prop을 받는다.
이 페이지에는 코멘트 input이 포함되어 있으며, ```comment``` state 변수를 사용하여 그 값을 보관한다.
어느 날, 한 프로필에서 다른 프로필로 이동할 때 comment state가 재설정되지 않는 문제를 발견했다.
그 결과 의도치 않게 잘못된 사용자의 프로필에 댓글을 게시하기가 쉬운 상황이다.
이 문제를 해결하려면 ```userId```가 변경될 때마다 ```comment``` state 변수를 지워줘야 한다.</code></pre><p>export default function ProfilePage({ userId }) {
  const [comment, setComment] = useState(&#39;&#39;);
  // 🔴 prop 변경시 Effect에서 state 재설정 수행하므로 자제
  useEffect(() =&gt; {
    setComment(&#39;&#39;);
  }, [userId]);
  // ...
}</p>
<pre><code>- 이것은 ```ProfilePage```와 그 자식들이 먼저 오래된 값으로 렌더링한 다음 새로운 값으로 다시 렌더링하기 때문에 비효율적이다.
또한 ```ProfilePage``` 내부에 어떤 state가 있는 모든 컴포넌트에서 이 작업을 수행해야 하므로 복잡하다.
예를 들어, 댓글 UI가 중첩되어 있는 경우 중첩된 하위 댓글 state들도 모두 지워야 할 것이다.
- 그 대신 명시적인 키를 전달해 각 사용자의 프로필이 개념적으로 다른 프로필이라는 것을 React에 알릴 수 있다.
컴포넌트를 둘로 나누고 바깥쪽 컴포넌트에서 안쪽 컴포넌트로 ```key``` 속성을 전달하라.</code></pre><p>export default function ProfilePage({ userId }) {
  return (
    <Profile
      userId={userId}
      key={userId}
    />
  );
}</p>
<p>function Profile({ userId }) {
  // ✅ key가 변하면 이 컴포넌트 및 모든 자식 컴포넌트의 state가 자동으로 재설정됨
  const [comment, setComment] = useState(&#39;&#39;);
  // ...
}</p>
<pre><code>- 일반적으로 React는 같은 컴포넌트가 같은 위치에서 렌더링될 때 state를 유지한다.
**```userId```를 ```key```로 ```Profile``` 컴포넌트에 전달하는 것은 곧, ```userId```가 다른 두 ```Profile``` 컴포넌트를 state를 공유하지 않는 별개의 컴포넌트들로 취급하도록 React에게 요청하는 것이다.**
React는 (```userId```로 설정한) ```key```가 변경될 때마다 DOM을 다시 생성하고 state를 재설정하며, ```Profile``` 컴포넌트 및 모든 자식들의 state를 재설정할 것이다.
그 결과 ```comment``` 필드는 프로필들을 탐색할 때마다 자동으로 지워진다.
- 위 예제에서는 외부의 ```ProfilePage``` 컴포넌트만 export하였으므로 프로젝트의 다른 파일에서는 오직 ```ProfilePage``` 컴포넌트에만 접근 가능하다.
```ProfilePage```를 렌더링하는 컴포넌트는 ```key```를 전달할 필요 없이 일반적인 ```prop```으로 ```userId```만 전달하고 있다.
```ProfilePage```가 내부의 ```Profile``` 컴포넌트에 ```key```로 전달한다는 사실은 내부에서만 알고 있는 구현 세부 사항이다.

**1-4. props가 변경될 때 일부 state 조정하기**

- 때론 ```prop```이 변경될 때 state의 전체가 아닌 일부만 재설정하거나 조정하고 싶을 수 있다.
- 다음의 ```List``` 컴포넌트는 ```items``` 목록을 ```prop```으로 받고, ```selection``` state 변수에 선택된 항목을 유지한다.
```items prop```이 다른 배열을 받을 때마다 ```selection```을 ```null```로 재설정하고 싶다.</code></pre><p>function List({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selection, setSelection] = useState(null);
  // 🔴 prop 변경시 Effect에서 state 조정 자제
  useEffect(() =&gt; {
    setSelection(null);
  }, [items]);
  // ...
}</p>
<pre><code>- 이것 역시 이상적이지 않다. ```items```가 변경될 때마다 ```List```와 그 하위 컴포넌트는 처음에는 오래된 ```selection```값으로 렌더링된다.
그런 다음 React는 DOM을 업데이트하고 Effects를 실행한다.
마지막으로 ```setSelection(null)```호출은 ```List```와 그 자식 컴포넌트를 다시 렌더링하여 이 전체 과정을 재시작하게 된다.
- Effect를 삭제하는 것으로 시작하라. 대신 렌더링 중에 직접 state를 조정한다.</code></pre><p>function List({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selection, setSelection] = useState(null);
  // ✅ 렌더링 중에 state 조정하는 것이 낫다.
  const [prevItems, setPrevItems] = useState(items);
  if (items !== prevItems) {
    setPrevItems(items);
    setSelection(null);
  }
  // ...
}</p>
<pre><code>- 이렇게 이전 렌더링의 정보를 저장하는 것은 이해하기 어려울 수 있지만, Effect에서 동일한 state를 업데이트하는 것보다는 낫다.
위 예시에서는 렌더링 도중 ```setSelection```이 직접 호출된다.
React는 ```return```문과 함께 종료된 직후에 List를 다시 렌더링한다.
이 시점에서 React는 아직 ```List```의 자식들을 렌더링하거나 DOM을 업데이트하지 않았기 때문에, ```List```의 자식들은 기존의 ```selection``` 값에 대한 렌더링을 건너뛰게 된다.
- 렌더링 도중 컴포넌트를 업데이트하면, React는 반환된 JSX를 버리고 즉시 렌더링을 다시 시도한다.
React는 계단식으로 전파되는 매우 느린 재시도를 피하기 위해, 렌더링 중에 동일한 컴포넌트의 state만 업데이트할 수 있도록 허용한다.
렌더링 도중 다른 컴포넌트의 state를 업데이트하면 오류가 발생한다.
동일 컴포넌트가 무한으로 리렌더링을 반복 시도하는 상황을 피하기 위해 ```items !== prevItems```와 같은 조건이 필요한 것이다.
이런 식으로 state를 조정할 수 있긴 하지만, 다른 side effect(DOM 변경이나 timeout 설정 등)은 이벤트 핸들러나 Effect에서만 처리함으로써 컴포넌트의 순수성을 유지해야 한다.
- 이 패턴은 Effect보다 효율적이지만, 대부분의 컴포넌트에는 필요하지 않다.
어떻게 하든 ```props```나 다른 state들을 바탕으로 state를 조정하면 데이터 흐름을 이해하고 디버깅하기 어려워질 것이다.
항상 ```key```로 모든 state를 재설정하거나 렌더링 중에 모두 계산할 수 있는지를 확인하라.
예를 들어, 선택한 item을 저장(및 재설정)하는 대신, 선택한 item의 ID를 저장할 수 있다.</code></pre><p>function List({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selectedId, setSelectedId] = useState(null);
  // ✅ 가장 좋음: 렌더링 중에 모든 값을 계산
  const selection = items.find(item =&gt; item.id === selectedId) ?? null;
  // ...
}</p>
<p>```</p>
<ul>
<li>이제 state를 “조정”할 필요가 전혀 없다.
선택한 ID를 가진 항목이 목록에 있으면 선택된 state로 유지된다.
그렇지 않은 경우 렌더링 중에 계산된 <code>selection</code> 항목은 일치하는 항목을 찾지 못하므로 <code>null</code>이 된다.
이 방식은 <code>items</code>에 대한 대부분의 변경과 무관하게 ‘selection’ 항목은 그대로 유지되므로 대체로 더 나은 방법이다.</li>
</ul>
<p><strong>1-5. 이벤트 핸들러 간 로직 공유</strong></p>
<ul>
<li><strong>어떤 코드가 Effect에 있어야 하는지 이벤트 핸들러에 있어야 하는지 확실치 않은 경우, 이 코드가 실행되어야 하는 이유를 자문해 보자.</strong></li>
<li><strong>컴포넌트가 사용자에게 표시되었기 때문에 실행되어야 하는 코드에만 Effect를 사용하라.</strong></li>
</ul>
<p><strong>1-6. POST요청 보내기</strong></p>
<ul>
<li>어떤 로직을 이벤트 핸들러에 넣을지 Effect에 넣을지 선택할 때, 사용자 관점에서 어떤 종류의 로직인지에 대한 답을 찾아야 한다.
이 로직이 특정 상호작용으로 인해 발생하는 것이라면 이벤트 핸들러에 넣어라.
사용자가 화면에서 컴포넌트를 보는 것이 원인이라면 Effect에 넣자.</li>
</ul>
<p><strong>1-7. 연쇄 계산</strong></p>
<ul>
<li>이벤트 핸들러에서 직접 다음 state를 계산할 수 없는 경우도 있다.
예를 들어, 이전 드롭다운의 선택 값에 따라 다음 드롭다운의 옵션이 달라지는 form을 상상해 보자.
이를 네트워크와 동기화해야 한다면 Effect 체인이 적절할 것이다.</li>
</ul>
<p><strong>1-8. 애플리케이션 초기화하기</strong></p>
<ul>
<li>일부 로직은 앱이 로드될 때 한 번만 실행되어야한다.</li>
<li>상용 환경에서는 실제로 다시 마운트되지 않을 수 있지만, 모든 컴포넌트에서 동일한 제약 조건을 따르면 코드를 이동하고 재사용하기가 더 쉬워진다.
일부 로직이 컴포넌트 마운트당 한 번이 아니라 앱 로드당 한 번 실행되어야 하는 경우, 최상위 변수를 추가하여 이미 실행되었는지 여부를 추적하라.</li>
<li>컴포넌트를 import할 때 최상위 레벨의 코드는 렌더링되지 않더라도 일단 한 번 실행된다.
임의의 컴포넌트를 임포트할 때 속도 저하나 예상치 못한 동작을 방지하려면 이 패턴을 과도하게 사용하지 말자.</li>
</ul>
<p><strong>1-9. state변경을 부모 컴포넌트에 알리기</strong></p>
<ul>
<li>부모 컴포넌트가 더 많은 로직을 포함해야 하지만, 전체적으로 걱정해야 할 state가 줄어든다는 것을 의미힌다.</li>
<li>두 개의 서로 다른 state 변수를 동기화하려고 할 때마다, 대신 state를 끌어올려 보자.</li>
</ul>
<p><strong>1-10. 부모에게 데이터 전달하기</strong></p>
<ul>
<li>React에서 데이터는 부모 컴포넌트에서 자식 컴포넌트로 흐른다.
화면에 뭔가 잘못된 것이 보이면, 컴포넌트 체인을 따라 올라가서 어떤 컴포넌트가 잘못된 prop을 전달하거나 잘못된 state를 가지고 있는지 찾아냄으로써 정보의 출처를 추적할 수 있다.
자식 컴포넌트가 Effect에서 부모 컴포넌트의 state를 업데이트하면, 데이터 흐름을 추적하기가 매우 어려워진다.
자식과 부모 컴포넌트 모두 동일한 데이터가 필요하므로, 대신 부모 컴포넌트가 해당 데이터를 페치해서 자식에게 전달하도록 하라.</li>
</ul>
<p><strong>1-11. 외부 스토어 구독하기</strong></p>
<ul>
<li>때로는 컴포넌트가 React state 외부의 일부 데이터를 구독해야 할 수도 있다.
서드파티 라이브러리나 브라우저 빌트인 API에서 데이터를 가져와야 할 수도 있다.
이 데이터는 React가 모르는 사이에 변경될 수도 있는데, 그럴 땐 수동으로 컴포넌트가 해당 데이터를 구독하도록 해야 한다.
이 작업은 종종 Effect에서 수행한다.</li>
<li><a href="https://react.dev/reference/react/useSyncExternalStore">React 컴포넌트에서 외부 store를 구독하는 방법에 대해 자세히 읽어보자.</a></li>
</ul>
<p><strong>1-12. 데이터 페칭하기</strong></p>
<ul>
<li>많은 앱이 데이터 페칭을 시작하기 위해 Effect를 사용한다.</li>
<li>데이터 페칭을 구현할 때 경합 조건을 처리하는 것만 어려운 것은 아니다.
응답을 캐시하는 방법,
<em>(사용자가 Back을 클릭하고면 스피너 대신 이전 화면을 즉시 볼 수 있도록)</em>
서버에서 페치하는 방법,
<em>(초기 서버 렌더링 HTML에 스피너 대신 가져온 콘텐츠가 포함되도록)</em>
네트워크 워터폴을 피하는 방법
<em>(데이터를 페치해야 하는 하위 컴포넌트가 시작하기 전에 위의 모든 부모가 데이터 페치를 완료할 때까지 기다릴 필요가 없도록)</em> 등도 고려해볼 사항이다. </li>
<li>이런 문제는 React뿐만 아니라 모든 UI 라이브러리에 적용된다.
이러한 문제를 해결하는 것은 간단하지 않기 때문에 최신 프레임워크들은 컴포넌트에서 직접 Effect를 작성하는 것보다 더 효율적인 빌트인 데이터 페칭 메커니즘을 제공한다.</li>
<li>프레임워크를 사용하지 않고(또한 직접 만들고 싶지 않고) Effect에서 데이터 페칭을 보다 인체공학적으로 만들고 싶다면, 페칭 로직을 커스텀 훅으로 추출하는 것을 고려해 보자.</li>
<li>또한 오류 처리와 콘텐츠 로딩 여부를 추적하기 위한 로직을 추가하고 싶을 것이다.
이와 같은 훅을 직접 빌드하거나 React 에코시스템에서 이미 사용 가능한 많은 솔루션 중 하나를 사용할 수 있다.
이 방법만으로는 프레임워크 빌트인 데이터 페칭 메커니즘을 사용하는 것만큼 효율적이지는 않겠지만, 데이터 페칭 로직을 커스텀 훅으로 옮기면 나중에 효율적인 데이터 페칭 전략을 채택하기가 더 쉬워진다.</li>
</ul>
<hr/>

<h2 id="요약">요약</h2>
<ul>
<li>렌더링 중에 무언가를 계산할 수 있다면 Effect가 필요하지 않다.</li>
<li>비용이 많이 드는 계산을 캐시하려면 useEffect 대신 useMemo를 추가하라.</li>
<li>전체 컴포넌트 트리의 state를 재설정하려면 다른 key를 전달하자.</li>
<li>prop 변경에 대한 응답으로 특정 state 일부를 조정하려면 렌더링 중에 설정하라.</li>
<li>컴포넌트가 표시되었기 때문에 실행해야 하는 코드는 Effect에 있어야 하고, 나머지는 이벤트에 있어야 한다.</li>
<li>여러 컴포넌트의 state를 업데이트해야 하는 경우 단일 이벤트에서 처리하는 것이 좋다.</li>
<li>여러 컴포넌트에서 state 변수를 동기화하려고 할 때마다 state 끌어올리기를 고려하라.</li>
<li>Effect로 데이터를 페치할 수 있지만, 경쟁 조건을 피하기 위해 클린업 로직을 구현해야 한다.</li>
</ul>
<hr/>


<h3 id="react-공식-문서">React 공식 문서</h3>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<h3 id="react-비공식-번역-문서">React 비공식 번역 문서</h3>
<p><a href="https://react-ko.dev/">https://react-ko.dev/</a></p>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/ko/">https://developer.mozilla.org/ko/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/wiki/">https://ko.wikipedia.org/wiki/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식문서 이해하기 (25)]]></title>
            <link>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-25</link>
            <guid>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-25</guid>
            <pubDate>Tue, 05 Dec 2023 00:47:08 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-escape-hatches">Chapter 4. Escape Hatches</h1>
<h2 id="3-effect와-동기화하기">#3 Effect와 동기화하기</h2>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. Effect란 무엇이며 이벤트와는 어떤게 다른가?</strong>
<strong>2. Effect가 필요하지 않을 수도 있다.</strong>
<strong>3. Effect 작성 방법</strong>
<strong>4. 개발 환경에서 두 번씩 실행되는 Effect를 처리하는 방법은 무엇인가?</strong>
<strong>5. 한 곳에 모으기</strong></p>
<hr/>

<h3 id="1-effect란-무엇이며-이벤트와는-어떤게-다른가">1. Effect란 무엇이며 이벤트와는 어떤게 다른가?</h3>
<ul>
<li>Effect에 도달하기 전에 React 컴포넌트 내부의 두가지 유형의 논리에 익숙해져야 한다.<ul>
<li><strong>렌더링 코드</strong>(UI 구성하기에서 소개됨)는 컴포넌트의 최상위 레벨에 있다.
여기서 props와 state를 가져와 변환하고 화면에 표시할 JSX를 반환한다.
렌더링 코드는 순수해야한다.
수학 공식처럼 결과만 계산할 뿐 다른 작업은 수행하지 않는다.</li>
</ul>
</li>
<li>이벤트 핸들러(상호작용 추가하기에서 소개됨)는 컴포넌트 내부에 있는 중첩된 함수로, 계산만 하는 것이 아니라 별도의 작업도 수행한다.
이벤트 핸들러에서는 입력 필드를 업데이트하거나, HTTP POST요청을 제출하여 제품을 구매하거나, 사용자를 다른 화면으로 이동할 수 있다.
이벤트 핸들러에는 특정 사용자 작업(예:버튼 클릭 또는 입력)으로 인해 발생하는 “<a href="https://en.wikipedia.org/wiki/Side_effect_(computer_science)">사이드 이펙트</a>”(프로그램의 state를 변경함)가 포함되어 있다.</li>
<li>때로는 이것만으로는 충분하지 않을 수 있다.
화면에 표시될 때마다 채팅 서버에 연결해야 하는 ChatRoom 컴포넌트를 고려해 보자.
서버에 연결하는 것은 순수한 계산이 아니므로(사이드 이펙트) 렌더링 중에 발생할 수 없다.
그러나 ChatRoom 표시를 촉발하는 클릭과 같은 특정한 단일 이벤트는 없다.</li>
<li><strong>Effect를 사용하면 특정 이벤트가 아닌 렌더링 자체로 인해 발생하는 사이드 이펙트를 명시할 수 있다.</strong>
채팅에서 메시지를 보내는 것은 사용자가 특정 버튼을 클릭함으로써 직접적으로 발생하기 때문에 이벤트이다.
그러나 서버 연결을 설정하는 것은 컴포넌트를 표시하게 만든 상호작용에 관계없이 발생해야 하기 때문에 하나의 Effect이다.
Effect는 화면 업데이트 후 커밋이 끝날 때 실행된다.
이 때가 React 컴포넌트를 일부 외부 시스템(네트워크 또는 서드파티 라이브러리와 같은)과 동기화하기에 좋은 시기이다.</li>
</ul>
<blockquote>
<p><strong>KeyNote</strong></p>
</blockquote>
<ul>
<li>이 글에서 대문자로 시작하는 “Effect”는 위의 React에 한정된 정의,
즉, 렌더링으로 인해 발생하는 사이드 이펙트를 나타낸다.
이 후로 더 넓은 프로그래밍 개념을 언급할 때는 “사이드 이펙트”라고 하겠다.</li>
</ul>
<h3 id="2-effect가-필요하지-않을-수도-있다">2. Effect가 필요하지 않을 수도 있다.</h3>
<ul>
<li>Effect는 일반적으로 React 코드에서 벗어나 일부 외부 시스템과 동기화하는 데에 사용된다는 점을 인지하자.
여기에는 브라우저 API, 서드파티 위젯, 네트워크 등이 포함된다.
Effect가 다른 state를 기반으로 일부 state만을 조정하는 경우, Effect가 필요하지 않을 수도 있다.</li>
</ul>
<p><strong>2-1. Effect 작성 방법</strong></p>
<ul>
<li><p>Effect를 작성하려면 다음 세 단계를 따라가자.</p>
<ol>
<li><strong>Effect를 선언한다.</strong> 
기본적으로 Effect는 모든 렌더링 후에 실행된다.</li>
<li><strong>Effect의 의존성을 명시한다.</strong>
대부분의 Effect는 렌더링 할 때마다가 아니라 필요할 때만 다시 실행해야 한다.
예를 들어, 페이드 인 애니메이션은 컴포넌트가 나타날 때만 발동되어야 한다.
대화방 연결 및 해제는 컴포넌트가 나타났다가 사라지거나 대화방이 변경될 때만 발생해야한다.</li>
<li><strong>필요한 경우 클린업을 추가한다.</strong>
일부 Effect는 수행중이던 작업을 중지, 취소 또는 정리하는 방법을 명시해야한다.
예를 들어, “connect”에는 “disconnect”가 필요하고 “subscribe”에는 “unsubscribe”가 필요하며 “fetch”에는 “cancel”또는 “ignore”가 필요하다.</li>
</ol>
<blockquote>
<p><span style="color:red"><strong>! 주의 !</strong></span></p>
</blockquote>
<ul>
<li>기본적으로 Effect는 매번 렌더링 후에 실행된다.
그렇기 때문에 다음과 같은 코드는 무한 루프를 생성한다.<pre><code>const [count, setCount] = useState(0);
useEffect(() =&gt; {
setCount(count + 1);
});</code></pre></li>
<li>Effect는 렌더링의 결과로 실행된다. state를 설정하면 렌더링을 트리거한다.
Effect에서 즉시 state를 설정하는 것은 전원 콘센트를 꽂는 것과 같다.
Effect가 실행되고, state를 설정하면 다시 렌더링이 발생하고, 다시 렌더링이 발생하면 Effect가 실행되고, 다시 state를 설정하면 또 다시 렌더링이 발생하는 식이다.</li>
<li>Effect는 보통 컴포넌트를 외부 시스템과 동기화해야 한다.
외부 시스템이 없고 다른 state를 기반으로 일부 state만 조정하려는 경우 Effect가 필요하지 않을 수도 있다.</li>
<li>의존성 배열이 없는 경우와 비어 있는 <code>[]</code> 의존성 배열이 있는 경우의 동작은 다르다.<pre><code>useEffect(() =&gt; {
// This runs after every render
// 렌더시마다 실행됩니다.
});</code></pre><pre><code>useEffect(() =&gt; {
// This runs only on mount (when the component appears)
// 오직 마운트시(컴포넌트가 나타날 때)에만 실행됩니다.
}, []);</code></pre><pre><code>useEffect(() =&gt; {
// This runs on mount *and also* if either a or b have changed since the last render
// 마운트시 뿐만 아니라 a 또는 b가 직전 렌더와 달라졌을 때에도 실행됩니다.
}, [a, b]);</code></pre></li>
</ul>
</li>
</ul>
<h3 id="3-effect-작성-방법">3. Effect 작성 방법</h3>
<ul>
<li>React는 개발 환경에서 버그를 찾기 위해 컴포넌트를 의도적으로 다시 마운트한다.
<span style="color:red"><strong>“어떻게 하면 Effect를 한 번만 실행할 수 있는가”</span>가 아니라 <span style="color:lightgreen">“어떻게 다시 마운트한 후에도 Effect가 잘 작동하도록 수정하는가”</span> 이다.</strong></li>
<li>일반적으로 정답은 클린업 함수를 구현하는 것이다.
클린업 함수는 Effect가 수행 중이던 작업을 중지하거나 취소해야 한다.
경험상 (상용 환경에서) 한 번만 실행되는 Effect와 (개발 환경에서의) <em>설정 → 정리 → 설정</em> 시퀀스를 사용자가 구분할 수 없어야 한다.</li>
<li>작성하게 될 대부분의 Effect는 아래의 일반적인 패턴 중 하나에 해당한다.</li>
</ul>
<h3 id="4-개발-환경에서-두-번씩-실행되는-effect를-처리하는-방법은-무엇인가">4. 개발 환경에서 두 번씩 실행되는 Effect를 처리하는 방법은 무엇인가?</h3>
<p><strong>4-1. React가 아닌 위젯 제어하기</strong></p>
<ul>
<li>때론 React로 작성하지 않은 UI 위젯을 추가해야 하는 경우가 있다.
예를 들어, 페이지에 지도 컴포넌트를 추가한다고 가정해 보면 여기에는 <code>setZoomLevel()</code> 메서드가 있으며, 확대/축소 수준을 React 코드의 <code>zoomLevel</code> state 변수와 동기화하고 싶다. Effect는 다음과 비슷할 것이다.<pre><code>useEffect(() =&gt; {
const map = mapRef.current;
map.setZoomLevel(zoomLevel);
}, [zoomLevel]);</code></pre></li>
<li>이 경우 클린업이 필요하지 않다.
개발 환경에서 React는 Effect를 두 번 호출하지만 동일한 값으로 <code>setZoomLevel</code>을 두 번 호출해도 아무 작업도 수행하지 않기 때문에 문제가 되지 않는다.
약간 느릴 수는 있지만 상용 환경에서는 불필요하게 다시 마운트되지 않으므로 문제가 되지 않는다.</li>
<li>일부 API는 연속으로 두 번 호출하는 것을 허용하지 않을 수 있다.
예를 들어, 브라우저의 빌트인 요소인 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement"><code>&lt;dialog&gt;</code></a>의 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal"><code>showModal</code></a> 메서드는 두 번 호출하면 에러를 던잔다.
클린업 함수를 구현하고 대화 상자를 닫도록 하자.</li>
</ul>
<pre><code>useEffect(() =&gt; {
  const dialog = dialogRef.current;
  dialog.showModal();
  return () =&gt; dialog.close();
}, []);</code></pre><ul>
<li>개발 중에 Effect는 <code>showModal()</code>을 호출한 다음 즉시 <code>close()</code>를 호출하고, 다시 <code>showModal()</code>을 호출한다.
이는 상용 환경에서 볼 수 있는 것처럼 <code>showModal()</code>을 한 번 호출하는 것과 체감상 동일하다.</li>
</ul>
<p><strong>4-2. 이벤트 구독하기</strong></p>
<ul>
<li>Effect가 무언가를 구독하는 경우, 클린업 함수는 구독을 취소해야한다.<pre><code>useEffect(() =&gt; {
function handleScroll(e) {
  console.log(window.scrollX, window.scrollY);
}
window.addEventListener(&#39;scroll&#39;, handleScroll);
return () =&gt; window.removeEventListener(&#39;scroll&#39;, handleScroll);
}, []);</code></pre></li>
<li>개발 중에 Effect는 <code>addEventListener()</code>를 호출한 다음 즉시 <code>removeEventListener()</code>를 호출한다.
그런 다음 동일한 핸들러를 사용하여 다시 <code>addEventListener()</code>를 사용함으로써, 한 번에 하나의 구독만 활성화 되도록 한다.
는 상용 환경에서 <code>addEventListener()</code>를 한 번만 호출하는 것과 체감상 동일하다.</li>
</ul>
<p><strong>4-3. 트리거링 애니메이션</strong></p>
<ul>
<li>Effect가 무언가를 애니메이션하는 경우 클린업 함수는 애니메이션을 초기값으로 재설정해야한다.<pre><code>useEffect(() =&gt; {
const node = ref.current;
node.style.opacity = 1; // Trigger the animation
                        // 애니메이션 촉발
return () =&gt; {
  node.style.opacity = 0; // Reset to the initial value
                          // 초기값으로 재설정
};
}, []);</code></pre></li>
<li>개발 과정에서 불투명도는 <code>1</code>이었다가, <code>0</code>이었다가, 다시 <code>1</code>로 설정된다.
이것은 상용 환경에서 <code>1</code>을 한 번만 설정하는 것과 체감상 동일하다.
만약 트위닝을 지원하는 서드파티 애니메이션 라이브러리를 사용하는 경우라면, 클린업 함수에서 타임라인을 초기 state로 재설정해줘야 한다.</li>
</ul>
<p><strong>4-4. 데이터 페칭하기</strong></p>
<ul>
<li><p>Effect가 무언가를 페치하면 클린업 함수는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/AbortController">페치를 중단</a>하거나 그 결과를 무시해야한다.</p>
<pre><code>useEffect(() =&gt; {
let ignore = false;

async function startFetching() {
  const json = await fetchTodos(userId);
  if (!ignore) {
    setTodos(json);
  }
}

startFetching();

return () =&gt; {
  ignore = true;
};
}, [userId]);</code></pre></li>
<li><p>이미 발생한 네트워크 요청을 “실행 취소”할 수는 없으므로, 대신 클린업 함수에서 더 이상 관련이 없는 페치가 애플리케이션에 계속 영향을 미치지 않도록 해야힌다.
만약 <code>userId</code>가 <code>&#39;Alice&#39;</code>에서 <code>&#39;Bob&#39;</code>으로 변경되면 클린업은 <code>&#39;Alice&#39;</code> 응답이 <code>&#39;Bob&#39;</code> 이후에 도착하더라도 이를 무시하도록 한다.</p>
</li>
<li><p><strong>개발 환경에서는 네트워크 탭에 두 개의 페치가 표시된다.</strong>
위의 접근 방식을 사용하면 첫 번째 Effect가 즉시 정리되므로, <code>ignore</code> 변수의 복사본이 <code>true</code>로 설정된다.
따라서 추가 요청이 있더라도 <code>if (!ignore)</code> 검사 덕분에 state에 영향을 미치지 않는다.</p>
</li>
<li><p><strong>상용 환경에서는 요청이 하나만 있다.</strong>
발 중인 두 번째 요청이 귀찮은 경우 가장 좋은 방법은 요청을 중복 제거하고 컴포넌트 간에 응답을 캐시하는 솔루션을 사용하는 것이다.</p>
<pre><code>function TodoList() {
const todos = useSomeDataLibrary(`/api/user/${userId}/todos`);
// ...</code></pre></li>
<li><p>이렇게 하면 개발 경험이 향상될 뿐만 아니라 애플리케이션이 더 빠르게 느껴진다.
예를 들어, Back 버튼을 누르는 사용자는 일부 데이터가 캐시되기 때문에 다시 로드될 때까지 기다릴 필요가 없다.
이러한 캐시는 직접 구축할 수도 있고, Effect에서 수동으로 페칭하는 기능을 대체하는 많은 대안 중 하나를 사용할 수도 있다.</p>
</li>
</ul>
<p><strong>4-5. 분석 전송</strong></p>
<pre><code>useEffect(() =&gt; {
  logVisit(url); // Sends a POST request
}, [url]);</code></pre><ul>
<li>개발 환경에서는 모든 URL에 대해 logVisit이 두 번 호출되므로 이를 수정하고 싶을 수 있다.
이전 예제와 마찬가지로 한 번 실행하는 것과 두 번 실행하는 것 사이에 사용자가 볼 수 있는 동작 차이는 없다.
개발 머신의 로그가 상용 메트릭을 왜곡하는 것을 원하지는 않을 것이니, 실용적인 관점에서 개발 환경에서는 logVisit가 아무 것도 하지 않도록 하자.
차피 개발 환경에서는 파일을 저장할 때마다 컴포넌트가 다시 마운트될 것이고, 따라서 추가 방문이 기록될 것이기 때문이다.</li>
<li><strong>상용 환경에서는 중복 방문 로그가 없다.</strong></li>
<li>전송하는 분석 이벤트를 디버깅하려면 앱을 스테이징 환경(상용 모드에서 실행됨)에 배포하거나, <a href="https://react.dev/reference/react/StrictMode">Strict Mode</a> 및 개발 전용의 중복 마운트 검사를 일시적으로 해제할 수 있다.
Effect 대신 경로 변경 이벤트 핸들러에서 분석을 전송할 수도 있다.
보다 정확한 분석을 위해서는 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API">intersection observers</a>를 활용하면 뷰포트에 어떤 컴포넌트가 있고 얼마나 오래 표시되는지를 추적하는 데 도움이 될 수 있다.</li>
</ul>
<p><strong>4-6. Effect가 아님: 애플리케이션 초기화하기</strong></p>
<ul>
<li>일부 로직은 애플리케이션이 시작될 때 한 번만 실행되어야 한다.
이런 로직은 컴포넌트 외부에 넣을 수 있다.<pre><code>if (typeof window !== &#39;undefined&#39;) { // Check if we&#39;re running in the browser.
                                   // 실행환경이 브라우저인지 여부 확인
checkAuthToken();
loadDataFromLocalStorage();
}
</code></pre></li>
</ul>
<p>function App() {
  // ...
}</p>
<pre><code>- 이렇게 하면 위 로직은 브라우저가 페이지를 로드한 후 한 번만 실행된다.

**4-7. Effect가 아님: 제품 구매하기**
- 클린업 함수를 작성하더라도 Effect를 두 번 실행함으로써 체감상 결과가 달라지는 것을 막을 방법이 없는 경우도 있다.
예를 들어, Effect가 제품 구매와 같은 POST 요청을 보낸다고 하자.</code></pre><p>useEffect(() =&gt; {
  // 🔴 Wrong: This Effect fires twice in development, exposing a problem in the code.
  // 🔴 틀렸습니다: 이 Effect는 개발모드에서 두 번 실행되며, 문제를 일으킵니다.
  fetch(&#39;/api/buy&#39;, { method: &#39;POST&#39; });
}, []);</p>
<pre><code>- 제품을 두 번 사고 싶지는 않을 것이다. 이 로직을 Effect에 넣지 말아야 하는 이유이기도 하다.
사용자가 다른 페이지로 이동한 다음 Back(뒤로가기)를 누르면, Effect가 다시 실행될 것이다.
사용자는 페이지를 _방문_할 때 제품을 구매하고 싶지는 않을 것이다.
구매 버튼을 _클릭_할 때 구매하길 원할 것이다.
- 구매는 렌더링으로 인한 것이 아니다. 특정 상호 작용으로 인해 발생한다.
사용자가 버튼을 누를 때만 실행되어야한다.
**Effect를 삭제하고 ```/api/buy``` 요청을 구매 버튼 이벤트 핸들러로 이동한다.**</code></pre><p>function handleClick() {
    // ✅ Buying is an event because it is caused by a particular interaction.
    // ✅ 구매는 특정 상호작용으로 인해 발생하므로 이벤트입니다.
    fetch(&#39;/api/buy&#39;, { method: &#39;POST&#39; });
  }</p>
<pre><code>- **다시 마운트하면 애플리케이션의 로직이 깨지는 경우, 일반적으로 기존 버그를 발견할 수 있다.**
사용자 관점에서 페이지를 방문하는 것은 페이지를 방문하여 링크를 클릭하고 뒤로가기 버튼을 누르는 것과 다르지 않아야 한다.
React는 개발 단계에서 컴포넌트를 다시 한 번 마운트하여 이 원칙을 준수하는지 확인한다.

### 5. 한 곳에 모으기

- 이 플레이그라운드를 통해 실제로 Effect가 어떻게 작동하는지 “느껴볼” 수 있다.
- 이 예에서는 [setTimeout](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout)을 사용하여 Effect가 실행되고 3초 뒤에 input 텍스트가 포함된 콘솔 로그를 표시하도록 예약한다. 클린업 함수는 보류 중인 타임아웃을 취소합니다.</code></pre><p>import { useState, useEffect } from &#39;react&#39;;</p>
<p>function Playground() {
  const [text, setText] = useState(&#39;a&#39;);</p>
<p>  useEffect(() =&gt; {
    function onTimeout() {
      console.log(&#39;⏰ &#39; + text);
    }</p>
<pre><code>console.log(&#39;🔵 Schedule &quot;&#39; + text + &#39;&quot; log&#39;);
const timeoutId = setTimeout(onTimeout, 3000);

return () =&gt; {
  console.log(&#39;🟡 Cancel &quot;&#39; + text + &#39;&quot; log&#39;);
  clearTimeout(timeoutId);
};</code></pre><p>  }, [text]);</p>
<p>  return (
    &lt;&gt;
      <label>
        What to log:{&#39; &#39;}
        &lt;input
          value={text}
          onChange={e =&gt; setText(e.target.value)}
        /&gt;
      </label>
      <h1>{text}</h1>
    &lt;/&gt;
  );
}</p>
<p>export default function App() {
  const [show, setShow] = useState(false);
  return (
    &lt;&gt;
      &lt;button onClick={() =&gt; setShow(!show)}&gt;
        {show ? &#39;Unmount&#39; : &#39;Mount&#39;} the component
      </button>
      {show &amp;&amp; <hr />}
      {show &amp;&amp; <Playground />}
    &lt;/&gt;
  );
}</p>
<p>```</p>
<ul>
<li>처음에는 <code>Schedule &quot;a&quot; log</code>, <code>Cancel &quot;a&quot; log</code>, <code>Schedule &quot;a&quot; log</code>의 세 가지 로그가 표시된다. 3초 후에 <code>a</code>라는 로그도 표시된다.
앞서 배운 것처럼 schedule/cancel 쌍이 한 번 더 출력되는 이유는 개발 환경에서 React가 클린업을 잘 구현했는지 확인하기 위해 컴포넌트를 다시 마운트하기 때문이다.</li>
<li>이제 input을 편집하여 <code>abc</code>라고 입력하라.
충분히 빨리 입력하면 Schedule <code>&quot;ab&quot; log</code>와 Cancel <code>&quot;ab&quot; log</code>, Schedule <code>&quot;abc&quot; log</code>가 바로 표시될 것이다.
React는 항상 다음 렌더링의 Effect 전에 이전 렌더링의 Effect를 정리한다.
따라서 입력을 빠르게 입력하더라도 한 번에 최대 한 번만 타임아웃이 예약된다.</li>
<li>input에 무언가를 입력한 다음 즉시 “Unmount the component”를 눌러보자.
마운트를 해제하면 마지막 렌더링의 Effect가 어떻게 정리되는지 확인하자.
여기서는 Effect가 실행 기회를 갖기 전에 마지막 타임아웃을 지운다.</li>
<li>마지막으로 위의 컴포넌트를 편집하고 타임아웃이 취소되지 않도록 클린업 함수를 주석 처리 해보자.
<code>abcde</code>를 빠르게 입력해 보자. 3초 후에 어떤 일이 일어날까?</li>
<li>3초 후, 5개의 <code>abcde</code>가 아닌 일련의 <code>(a, ab, abc, abcd, abcde)</code>가 표시되어야 한다.
각 Effect는 해당 렌더링에서 <code>text</code> 값을 “캡처”한다.
<code>text</code> state가 변경되더라도, <code>text = &#39;ab&#39;</code>인 렌더링의 Effect는 항상 <code>&#39;ab&#39;</code>를 표시한다.
즉,각 렌더링의 Effect는 서로 분리되어 있다.
이것이 어떻게 작동하는지 궁금하다면 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures"><strong>클로저</strong></a>를 읽어 보자.</li>
</ul>
<hr/>

<h2 id="요약">요약</h2>
<ul>
<li>이벤트와 달리 Effect는 특정 상호 작용이 아닌 렌더링 자체에 의해 발생한다.</li>
<li>Effect를 사용하면 일부 외부 시스템(서드파티 API, 네트워크 등)과 컴포넌트를 동기화할 수 있다.</li>
<li>기본적으로 Effect는 모든 렌더링 후에 실행된다.
(초기 렌더링 포함)</li>
<li>React는 모든 의존성이 마지막 렌더링 시점과 동일한 값을 갖는 경우 Effect를 건너뛴다.</li>
<li>의존성을 “선택”할 수는 없다.
그들은 Effect 내부의 코드에 의해 결정된다.</li>
<li>빈 의존성 배열(<code>[]</code>)은 컴포넌트 “마운팅”, 즉,화면에 추가되는 시점에 대응한다.</li>
<li>Strict 모드에서 React는 컴포넌트를 두 번 마운트하여(<strong>개발 중인 경우에만</strong>) Effect를 스트레스 테스트한다.</li>
<li>다시 마운트를 수행함으로 인해 Effect가 깨지는 경우, 클린업 함수를 구현해야 한다.
= React는 다음 Effect가 실행되기 전 및 마운트 해제 시점에 클린업 함수를 호출한다.</li>
</ul>
<hr/>


<h3 id="react-공식-문서">React 공식 문서</h3>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<h3 id="react-비공식-번역-문서">React 비공식 번역 문서</h3>
<p><a href="https://react-ko.dev/">https://react-ko.dev/</a></p>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/ko/">https://developer.mozilla.org/ko/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/wiki/">https://ko.wikipedia.org/wiki/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 공식문서 이해하기 (24)]]></title>
            <link>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-24</link>
            <guid>https://velog.io/@syo_ee/React-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-24</guid>
            <pubDate>Mon, 04 Dec 2023 09:47:00 GMT</pubDate>
            <description><![CDATA[<h1 id="chapter-4-escape-hatches">Chapter 4. Escape Hatches</h1>
<h2 id="2-ref로-dom-조작하기">#2 ref로 DOM 조작하기</h2>
<h2 id="학습-목차">학습 목차</h2>
<p><strong>1. 노드에 대한 ref 가져오기</strong>
<strong>2. 다른 컴포넌트의 DOM 노드에 접근하기</strong>
<strong>3. React가 ref를 첨부할 때</strong>
<strong>4. ref를 이용한 DOM 조작 모범 사례</strong></p>
<hr/>

<h3 id="1-노드에-대한-ref-가져오기">1. 노드에 대한 ref 가져오기</h3>
<ul>
<li>React가 관리하는 DOM 노드에 접근하려면, 먼저 useRef 훅을 불러오자.
<code>import { useRef } from &#39;react&#39;;</code></li>
<li>그런 다음 컴포넌트 내부에서 ref를 선언하자.
<code>const myRef = useRef(null);</code></li>
<li>마지막으로, DOM 노드를 가져올 JSX 태그에 ref 속성으로 참조를 전달하라.
<code>&lt;div ref={myRef}&gt;</code></li>
<li>이 <code>useRef</code> 훅은 <code>current</code> 라고 하는 프로퍼티가 포함된 객체를 반환한다.
처음에는 <code>myRef.current</code> 는 <code>null</code> 이 될 것이다.
React가 이 <code>&lt;div&gt;</code>에 대한 DOM 노드를 생성하면, React는 이 노드에 대한 참조를 <code>myRef.current</code>에 넣는다.
그런 다음 이벤트 핸들러에서 이 DOM 노드에 액세스하고 여기에 정의된 빌트인 브라우저 API를 사용할 수 있다.<pre><code>// 모든 브라우저 API를 사용할 수 있습니다. 예를 들어:
myRef.current.scrollIntoView();</code></pre></li>
</ul>
<h3 id="2-다른-컴포넌트의-dom-노드에-접근하기">2. 다른 컴포넌트의 DOM 노드에 접근하기</h3>
<ul>
<li><code>&lt;input /&gt;</code>과 같은 브라우저 엘리먼트를 출력하는 빌트인 컴포넌트에 ref를 넣으면, React는 해당 ref의 <code>current</code> 프로퍼티를 해당 DOM 노드(예: 브라우저의 실제 <code>&lt;input /&gt;</code>)로 설정한다.</li>
<li>그러나 <code>&lt;MyInput /&gt;</code>과 같은 여러분이 만든 컴포넌트에 ref를 넣으려고 하면 기본적으로 <code>null</code>이 반환된다.</li>
<li>문제를 알아차리는 데 도움이 되도록 React는 콘솔에 오류를 출력하기도 한다.<pre><code>//Console
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
// 경고: 함수 컴포넌트에는 refs를 지정할 수 없습니다. 이 ref에 접근하려는 시도는 실패합니다. React.forwardRef()를 사용하려고 하셨나요?</code></pre></li>
<li>이는 기본적으로 React가 컴포넌트가 <em>다른</em> 컴포넌트의 DOM 노드에 접근하는 것을 허용하지 않기 때문이다.</li>
<li>ref는 탈출구이기 때문에 아껴서 사용해야 한다.</li>
<li>다른 컴포넌트의 DOM 노드를 수동으로 조작하면 코드가 훨씬 더 취약해진다.</li>
<li>대신, DOM 노드를 노출하길 원하는 컴포넌트에 해당 동작을 설정해야 한다.
컴포넌트는 자신의 ref를 자식 중 하나에 “전달”하도록 지정할 수 있습니다.</li>
<li>디자인 시스템에서 버튼, 입력 등과 같은 저수준 컴포넌트는 해당 ref를 DOM 노드로 전달하는 것이 일반적인 패턴이다.
반면 양식, 목록 또는 페이지 섹션과 같은 상위 수준 컴포넌트는 일반적으로 DOM 구조에 대한 우발적 의존성을 피하기 위해 해당 DOM 노드를 노출하지 않는다.</li>
</ul>
<h3 id="3-react가-ref를-첨부할-때">3. React가 ref를 첨부할 때</h3>
<ul>
<li>React에서 모든 업데이트는 두 단계로 나뉜다.<ul>
<li><strong>렌더링</strong> 하는 동안 React는 컴포넌트를 호출하여 화면에 무엇이 표시되어야 하는지 파악한다.</li>
<li><strong>커밋(commit)</strong> 하는 동안 React는 DOM에 변경 사항을 적용한다.</li>
</ul>
</li>
<li>일반적으로 렌더링 중에는 ref에 액세스하는 것을 원하지 않을 것이다.
DOM 노드를 보유하는 ref도 마찬가지이다.</li>
<li>첫 번째 렌더링 중에는 DOM 노드가 아직 생성되지 않았으므로 <code>ref.current</code>는 <code>null</code>이 된다.
그리고 업데이트를 렌더링하는 동안에는 DOM 노드가 아직 업데이트되지 않았다.
따라서 이를 읽기에는 너무 이르다.</li>
<li>React는 커밋하는 동안에 <code>ref.current</code>를 설정한다.
React는 DOM이 업데이트 되기 전에는 <code>ref.current</code>의 값을 <code>null</code>로 설정하였다가, DOM이 업데이트된 직후 해당 DOM 노드로 다시 설정한다.</li>
<li><strong>일반적으로 이벤트 핸들러에서 ref에 접근한다.</strong></li>
<li>ref로 무언가를 하고 싶지만 그 작업을 수행할 특정 이벤트가 없다면, Effect가 필요할 수 있다.</li>
</ul>
<h3 id="4-ref를-이용한-dom-조작-모범-사례">4. ref를 이용한 DOM 조작 모범 사례</h3>
<ul>
<li>Ref는 탈출구이다.
React 외부로 나가야” 할 때만 사용해야 한다.
일반적인 예로는 초점을 맞추거나, 스크롤 위치를 관리하거나 React가 노출하지 않는 브라우저 API를 호출하는 것이 있다.</li>
<li>포커스나 스크롤 같은 비파괴적 동작을 고수한다면 문제가 발생하지 않을 것이다.
그러나 DOM을 수동으로 <strong>수정</strong>하려고 하면 React가 수행하는 변경 사항과 충돌할 위험이 있다.</li>
<li><strong>React가 관리하는 DOM 노드를 변경하지 말자.</strong>
React가 관리하는 요소를 수정하거나, 자식을 추가하거나 제거하면, 위와 같이 일관성 없는 시각적 결과나 충돌이 발생할 수 있다.</li>
<li>그렇다고 전혀 할 수 없다는 건 아니고, 주의가 필요하다는 의미이다.</li>
<li><em>React가 업데이트할 <em>이유가 없는</em> DOM의 일부는 안전하게 수정할 수 있다.*</em>
예를 들어, JSX에서 일부 <code>&lt;div&gt;</code>가 항상 비어 있는 경우, React는 그 자식 목록을 건드릴 이유가 없다.
따라서 수동으로 요소를 추가하거나 제거하더라도 안전하다.</li>
</ul>
<hr/>

<h2 id="요약">요약</h2>
<ul>
<li>Ref는 일반적인 개념이긴 하지만, 대부분 DOM 엘리먼트를 보관할 때 사용한다.</li>
<li><code>&lt;div ref={myRef}&gt;</code>를 전달해 DOM 노드를 <code>myRef.current</code>에 넣으라고 React에 지시한다.</li>
<li>보통은 포커스, 스크롤, DOM 엘리먼트 측정과 같은 비파괴적인 동작에 ref를 사용한다.</li>
<li>컴포넌트는 기본적으로 DOM 노드를 노출하지 않는다.
<code>forwardRef</code>를 사용하고 두 번째 <code>ref</code> 인수를 특정 노드에 전달하여 DOM 노드를 노출하도록 설정할 수 있다.</li>
<li>React가 관리하는 DOM 노드를 변경하지 말자.</li>
<li>React가 관리하는 DOM 노드를 수정해야 한다면 React가 업데이트할 이유가 없는 부분을 수정하라.</li>
</ul>
<hr/>


<h3 id="react-공식-문서">React 공식 문서</h3>
<p><a href="https://react.dev/">https://react.dev/</a></p>
<h3 id="react-비공식-번역-문서">React 비공식 번역 문서</h3>
<p><a href="https://react-ko.dev/">https://react-ko.dev/</a></p>
<h3 id="mdn">MDN</h3>
<p><a href="https://developer.mozilla.org/ko/">https://developer.mozilla.org/ko/</a></p>
<h3 id="wikipedia">Wikipedia</h3>
<p><a href="https://ko.wikipedia.org/wiki/">https://ko.wikipedia.org/wiki/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Graph & Tree]]></title>
            <link>https://velog.io/@syo_ee/Graph-Tree</link>
            <guid>https://velog.io/@syo_ee/Graph-Tree</guid>
            <pubDate>Sun, 03 Dec 2023 03:52:33 GMT</pubDate>
            <description><![CDATA[<h1 id="graph">Graph</h1>
<p><img src="https://velog.velcdn.com/images/syo_ee/post/0d84329a-2a9e-401e-9357-de4b6de5bd1e/image.png" alt=""></p>
<p> &amp;nbsp <strong>Graph</strong>는 노드와 edge의 관계를 나타내는 추상적인 개념입니다.</p>
<h2 id="특징">특징</h2>
<p>• <strong>노드(node)</strong> 는 일반적으로 점으로 표시됩니다.
 &amp;nbsp 도시를 나타내는 노드는 지도에서 점으로 표시됩니다.</p>
<p>• <strong>edge</strong>는 노드 사이의 관계를 나타냅니다.
 &amp;nbsp ex ) 도로망에서 edge는 도로를 나타냅니다.</p>
<p>• <strong>edge</strong>는 방향성이 없는 경우를 undirected graph, 방향성이 있는 경우를 directed graph라고 합니다.</p>
<p>• <strong>edge</strong>에 가중치(weight)가 있을 수도 있습니다.
 &amp;nbsp ex ) 길이 또는 비용을 나타내는 값이 할당될 수 있습니다.</p>
<p>• <strong>Graph</strong>는 매우 다양한 형태로 사용됩니다.
 &amp;nbsp 네트워크, 지도, 계획, 추상 데이터 구조 등에서 사용됩니다.
 &amp;nbsp 또한 그래프 이론은 최단 경로, 흐름, 컷 등과 같은 다양한 문제를 연구합니다.</p>
<h1 id="tree">Tree</h1>
<p><img src="https://velog.velcdn.com/images/syo_ee/post/ad648a34-209f-415c-b604-b9b811f870d5/image.png" alt=""></p>
<p><strong>Tree</strong>는 <strong>노드</strong>와 <strong>edge</strong>의 집합으로 이루어져 있습니다.</p>
<h2 id="특징-1">특징</h2>
<p>• <strong>Tree</strong>는 <strong>비순환 그래프</strong>입니다.
 &amp;nbsp 즉, 어떤 노드에서 시작하여 어떤 다른 노드로 이동할 때, 한 번 이상 이동하지 않고 도달할 수 있는 모든 노드의 집합이 하나의 트리를 형성합니다.
• Tree는 하나의 루트 노드(root node)가 있습니다.
 &amp;nbsp 루트 노드는 트리의 가장 상위 노드로, 다른 모든 노드는 루트 노드에서부터 시작하는 경로 상에 있습니다.
• 루트 노드를 제외한 모든 노드는 단 하나의 부모 노드(parent node)를 갖습니다.
• 부모 노드와 자식 노드(child node)라는 용어를 사용합니다.
 &amp;nbsp 부모 노드는 자식 노드를 가지지만, 자식 노드는 부모 노드를 가지지 않습니다.
• 트리에서 노드의 깊이(depth)는 루트 노드에서부터 해당 노드까지의 edge의 수입니다.
• 트리에서 노드의 레벨(level)은 트리에서 루트 노드로부터 몇 번째 레벨에 있는지를 나타냅니다.
 &amp;nbsp 즉, 루트 노드의 레벨은 0이며, 레벨이 1인 노드는 루트 노드의 자식 노드입니다.
• 트리는 여러 종류의 트리가 존재하며, 대표적인 것으로 이진 트리, 이진 탐색 트리, AVL 트리, 레드-블랙 트리 등이 있습니다.
• 이진 트리(Binary Tree)는 각 노드가 최대 두 개의 자식 노드를 가지는 트리입니다.
 &amp;nbsp 이진 트리는 탐색, 정렬, 암호화, 해시 테이블 등 다양한 분야에서 사용됩니다.
• 이진 탐색 트리(Binary Search Tree)는 이진 트리의 일종으로, 모든 노드에서 왼쪽 서브트리의 값이 부모 노드의 값보다 작고, 오른쪽 서브트리의 값이 부모 노드의 값보다 큰 속성을 만족합니다.
 &amp;nbsp 이진 탐색 트리는 탐색, 정렬, 범위 검색 등에서 사용됩니다.
• AVL 트리(AVL Tree)는 균형 이진 탐색 트리의 일종으로, 모든 노드의 서브트리 높이 차이가 1 이하인 속성을 만족합니다.
 &amp;nbsp AVL 트리는 탐색, 정렬 등에서 사용됩니다.
• 레드-블랙 트리(Red-Black Tree)는 균형 이진 탐색 트리의 일종으로, 모든 노드가 레드 또는 블랙인 속성을 만족합니다.
 &amp;nbsp 레드-블랙 트리는 키를 기반으로 하는 검색 구조, 맵 및 딕셔너리와 같은 데이터 구조에서 사용됩니다.</p>
<p>그래프와 트리는 모두 유용한 추상적 개념으로, 데이터 구조 및 알고리즘에서 널리 사용됩니다. 그러나 그들은 구조와 특성에서 차이가 있으므로, 이를 고려하여 적절한 구조를 선택하여 사용하는 것이 중요합니다.</p>
<hr/>

<h3 id="wikipedia---graph">Wikipedia - Graph</h3>
<p><a href="https://ko.wikipedia.org/wiki/%EA%B7%B8%EB%9E%98%ED%94%84_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0)">https://ko.wikipedia.org/wiki/%EA%B7%B8%EB%9E%98%ED%94%84_(%EC%9E%90%EB%A3%8C_%EA%B5%AC%EC%A1%B0)</a></p>
<h3 id="wikipedia---tree">Wikipedia - Tree</h3>
<p><a href="https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%A6%AC_%EA%B5%AC%EC%A1%B0">https://ko.wikipedia.org/wiki/%ED%8A%B8%EB%A6%AC_%EA%B5%AC%EC%A1%B0</a></p>
]]></description>
        </item>
    </channel>
</rss>