<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.5">Jekyll</generator><link href="https://kjkandrea.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://kjkandrea.github.io/" rel="alternate" type="text/html" /><updated>2024-04-27T12:37:25+00:00</updated><id>https://kjkandrea.github.io/feed.xml</id><title type="html">kjkandrea.github.io</title><subtitle>An amazing website.</subtitle><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><entry><title type="html">상표 기법 : 매개변수에 제약 조건 만들기</title><link href="https://kjkandrea.github.io/frontend/nominal-brand-type/" rel="alternate" type="text/html" title="상표 기법 : 매개변수에 제약 조건 만들기" /><published>2023-01-26T11:00:00+00:00</published><updated>2023-01-26T11:00:00+00:00</updated><id>https://kjkandrea.github.io/frontend/nominal-brand-type</id><content type="html" xml:base="https://kjkandrea.github.io/frontend/nominal-brand-type/"><![CDATA[<p>프로그래밍을 하다보면 계산 함수들의 실행 순서에 따른 제약 혹은 특정 의미를 지닌 원시 타입만 허용하고 싶을 때가 있다.</p>

<p>첫번째 예시로 이진 탐색을 통해 특정 값의 존재여부를 리턴하는 binarySearch 함수를 만든다고 생각해보자.
이진 탐색은 선결 조건으로 정렬이 이루어진 목록 을 요구한다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">binarySearch</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">sortedArray</span><span class="p">:</span> <span class="nx">T</span><span class="p">[],</span> <span class="nx">findItem</span><span class="p">:</span> <span class="nx">T</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
  <span class="c1">// 구현 생략</span>
<span class="p">}</span>
</code></pre></div></div>

<p>‘정렬이 이루어진’ 이라는 선결조건을 명시적으로 나타내고자 매개변수명을 sortedArray 로 힌트를 두었으나 실수로 인해 정렬되지 않은 리스트를 인자로 넘길 경우가 존재한다.</p>

<p>두번째 예시로 간단한 환율 계산 앱을 만든다고 가정해보자. 
이 앱이 수행하는것은 사용자에게 계산하고자하는 원 단위를 입력받아 현재 기준 환율에 맞게 달러로 변환하여 화면에 보여주는 것이다.
현재 기준 원을 달러로 반환해주는 currencyCalculator.wonToDollar 메서드가 존재하고, renderDollar 라는 달러를 인자로 받아 화면에 렌더링하는 함수가 있다고 가정해보자.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nx">CurrencyCalculator</span> <span class="p">{</span>
  <span class="c1">//...</span>
  <span class="nx">wonToDollar</span><span class="p">(</span><span class="nx">won</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="kr">number</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">getExchangeRate</span><span class="p">()</span> <span class="o">*</span> <span class="nx">won</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">renderDollar</span><span class="p">(</span><span class="nx">dollar</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
  <span class="c1">//...</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">currencyCalculator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CurrencyCalculator</span><span class="p">()</span>

<span class="kd">const</span> <span class="nx">dollar</span> <span class="o">=</span> <span class="nx">currencyCalculator</span><span class="p">.</span><span class="nx">wonToDollar</span><span class="p">(</span><span class="mi">1</span><span class="nx">_000</span><span class="p">)</span>
<span class="nx">renderDollar</span><span class="p">(</span><span class="nx">dollar</span><span class="p">)</span>
</code></pre></div></div>

<p>마찬가지로 renderDollar 의 dollar 매개변수를 두었으나, number 타입으로는 할당되는 값이 won 인지, 계산이 완료된 dollar 인지 제약을 둘 수 없다.
마찬가지로 개발자의 실수로 인해 실행 문맥이 변경되면 잘못된 결과가 계산될 여지가 있는 것이다.</p>

<h2 id="상표brand-기법-을-통한-명목적nominal-타이핑">상표(brand) 기법 을 통한 명목적(nominal) 타이핑</h2>

<p>타입스크립트는 구조적 타이핑을 차용하지만 특성 사례에 이러한 제약을 두고싶은 경우 상표 기법으로 명목적 타이핑을 흉내낼 수 있다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Brand</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">K</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">T</span> <span class="o">&amp;</span> <span class="p">{</span> <span class="na">__brand</span><span class="p">:</span> <span class="nx">K</span> <span class="p">}</span>
</code></pre></div></div>

<p>Brand 타입에 제너릭 매개변수 T, K 를 통해 구조적 타입에 <code class="language-plaintext highlighter-rouge">__brand</code> 를 부여함으로 명목적 타입으로 사용할 수 있다.</p>

<p><strong>여기에서 교차 타입으로 선언된 <code class="language-plaintext highlighter-rouge">__brand</code> 는 말그대로 명목적 타이핑을 위한 이름이다.</strong> 런타임에서는 실제로 존재하지않으며 타입 시스템에서만 쓰이기에 런타임 오버헤드를 발생시키지 않는다.</p>

<h3 id="binarysearch-매개변수에-제약-두기">binarySearch 매개변수에 제약 두기</h3>

<p>이 타입을 사용하여 binarySearch 의 매개변수에 제약을 부여해보자.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">SortedArray</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">Brand</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">[],</span> <span class="dl">'</span><span class="s1">sorted</span><span class="dl">'</span><span class="o">&gt;</span> <span class="c1">// T[] &amp; { __brand: 'sorted' } </span>

<span class="kd">function</span> <span class="nx">binarySearch</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">sortedArray</span><span class="p">:</span> <span class="nx">SortedArray</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">,</span> <span class="nx">findItem</span><span class="p">:</span> <span class="nx">T</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
  <span class="c1">// 구현 생략</span>
<span class="p">}</span>
</code></pre></div></div>

<p>제약이 부여된 sortedArray 에 배열 리터럴로 배열을 넘기면 타입 에러가 발생하는 것을 확인할 수 있다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">binarySearch</span><span class="p">([</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">],</span> <span class="mi">3</span><span class="p">);</span>
<span class="cm">/** 
 * 🚨 TS2345: 
 * Argument of type 'number[]' is not assignable to parameter of type 'SortedArray&lt;number&gt;'. 
 * Property '__brand' is missing in type 'number[]' but required in type '{ __brand: "sorted"; }' 
 */</span>
</code></pre></div></div>

<p>이제 binarySearch 에 sortedArray 인자를 넘기기 위해서는 정렬된 배열임을 증명해야 한다.<br />
정렬함수를 거쳐 <code class="language-plaintext highlighter-rouge">SortedArray</code> 를 만들어내보자.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">SortedArray</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">Brand</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">[],</span> <span class="dl">'</span><span class="s1">sorted</span><span class="dl">'</span><span class="o">&gt;</span> <span class="c1">// T[] &amp; { __brand: 'sorted' } </span>

<span class="kd">function</span> <span class="nx">sortArray</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">array</span><span class="p">:</span> <span class="nx">T</span><span class="p">[]):</span> <span class="nx">SortedArray</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">[...</span><span class="nx">array</span><span class="p">].</span><span class="nx">sort</span><span class="p">()</span> <span class="k">as</span> <span class="nx">SortedArray</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">sortedNumbers</span> <span class="o">=</span> <span class="nx">sortArray</span><span class="p">([</span><span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">])</span> <span class="c1">// number[] &amp; { __brand: 'sorted' } </span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">sortedNumbers</span><span class="p">)</span> <span class="c1">// [1, 2, 3] </span>
</code></pre></div></div>

<p>sortArray 함수를 통해 <code class="language-plaintext highlighter-rouge">SortedArray</code> 상표가 부여된 sortedNumbers 는 비소로 안전하게 binarySearch 의 인자로 할당될 수 있다.</p>

<h3 id="renderdollar-매개변수에-제약-두기">renderDollar 매개변수에 제약 두기</h3>

<p>마찬가지로 Brand 를 통해 원시 타입 number 에 상표를 부여해보자.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Brand</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">K</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">T</span> <span class="o">&amp;</span> <span class="p">{</span> <span class="na">__brand</span><span class="p">:</span> <span class="nx">K</span> <span class="p">}</span>
<span class="kd">type</span> <span class="nx">Dollar</span> <span class="o">=</span> <span class="nx">Brand</span><span class="o">&lt;</span><span class="kr">number</span><span class="p">,</span> <span class="dl">'</span><span class="s1">dollar</span><span class="dl">'</span><span class="o">&gt;</span> <span class="c1">// number &amp; { __brand: 'dollar' } </span>
</code></pre></div></div>

<p>wonToDollar 메서드가 <code class="language-plaintext highlighter-rouge">Dollar</code> 타입을 리턴하도록 변경하고,  renderDollar 함수가 <code class="language-plaintext highlighter-rouge">Dollar</code> 타입을 매개변수로 받도록 변경한다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">renderDollar</span><span class="p">(</span><span class="nx">dollar</span><span class="p">:</span> <span class="nx">Dollar</span><span class="p">):</span> <span class="k">void</span> <span class="p">{</span>
  <span class="c1">//...</span>
<span class="p">}</span>

<span class="kd">class</span> <span class="nx">CurrencyCalculator</span> <span class="p">{</span>
  <span class="c1">//...</span>
  <span class="nx">wonToDollar</span><span class="p">(</span><span class="nx">won</span><span class="p">:</span> <span class="kr">number</span><span class="p">):</span> <span class="nx">Dollar</span> <span class="p">{</span>
    <span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">getExchangeRate</span><span class="p">()</span> <span class="o">*</span> <span class="nx">won</span> <span class="k">as</span> <span class="nx">Dollar</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="kd">const</span> <span class="nx">currencyCalculator</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">CurrencyCalculator</span><span class="p">()</span>

<span class="kd">const</span> <span class="nx">dollar</span> <span class="o">=</span> <span class="nx">currencyCalculator</span><span class="p">.</span><span class="nx">wonToDollar</span><span class="p">(</span><span class="mi">1</span><span class="nx">_000</span><span class="p">)</span>
<span class="nx">renderDollar</span><span class="p">(</span><span class="nx">dollar</span><span class="p">)</span>
</code></pre></div></div>

<p>이를 통해 currencyCalculator.wonToDollar 메서드의 리턴값만 인자로 할당 할 수 있도록 제약을 둘 수 있다.</p>

<h2 id="한계점--정리">한계점 &amp; 정리</h2>

<p>상표 기법은 산술 연산을 거치면 상표가 없어지기에 수많은 산술 연산을 거치는 과정에 사용하기엔 무리가 있을 수 있다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">Brand</span><span class="o">&lt;</span><span class="nx">T</span><span class="p">,</span> <span class="nx">K</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nx">T</span> <span class="o">&amp;</span> <span class="p">{</span> <span class="na">__brand</span><span class="p">:</span> <span class="nx">K</span> <span class="p">}</span>
<span class="kd">type</span> <span class="nx">Dollar</span> <span class="o">=</span> <span class="nx">Brand</span><span class="o">&lt;</span><span class="kr">number</span><span class="p">,</span> <span class="dl">'</span><span class="s1">dollar</span><span class="dl">'</span><span class="o">&gt;</span> <span class="c1">// number &amp; { __brand: 'dollar' } </span>

<span class="kd">const</span> <span class="nx">dollar</span><span class="p">:</span> <span class="nx">Dollar</span> <span class="o">=</span> <span class="nx">currencyCalculator</span><span class="p">.</span><span class="nx">wonToDollar</span><span class="p">(</span><span class="mi">1</span><span class="nx">_000</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">millionDollar</span> <span class="o">=</span> <span class="nx">dollar</span> <span class="o">*</span> <span class="mi">1</span><span class="nx">_000_000</span> <span class="c1">// number</span>

<span class="nx">renderDollar</span><span class="p">(</span><span class="nx">millionDollar</span><span class="p">)</span>
<span class="cm">/**
 * 🚨 TS2345:
 * Argument of type 'number' is not assignable to parameter of type 'Dollar'.
 * Type 'number' is not assignable to type '{ __brand: "dollar"; }'.
 */</span>
</code></pre></div></div>

<p>이와 같은 한계를 인지하고 상표 기법을 사용해야 한다.</p>

<p>이팩티브 타입스크립트는 상표 기법을 다음과 같이 요약한다.</p>

<ul>
  <li>타입스크립트는 구조적 타이핑(덕 타이핑)을 사용하기 때문에, 값을 세밀하게 구분하지 못하는 경우가 있다. 값을 구분하기 위해 공식 명칭이 필요하다면 상표를 붙이는것을 고려해야 한다.</li>
  <li>상표 기법은 타입 시스템에서 동작하지만 런타임에 상표를 검사하는 것과 동일한 효과를 얻을 수 있다.</li>
</ul>

<h2 id="reference">reference</h2>

<ul>
  <li><a href="https://effectivetypescript.com/">[댄 밴더캄] 이팩티브 타입스크립트 : 37. 공식 명칭에는 상표를 붙이기</a></li>
  <li><a href="https://toss.tech/article/typescript-type-compatibility">[toss.tech] TypeScript 타입 시스템 뜯어보기: 타입 호환성</a></li>
  <li><a href="https://michalzalecki.com/nominal-typing-in-typescript/">[Michal Zalecki] Nominal typing techniques in TypeScript</a></li>
</ul>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="frontend" /><summary type="html"><![CDATA[프로그래밍을 하다보면 계산 함수들의 실행 순서에 따른 제약 혹은 특정 의미를 지닌 원시 타입만 허용하고 싶을 때가 있다.]]></summary></entry><entry><title type="html">i18next : formatting 으로 을/를, 이/가 구분하기</title><link href="https://kjkandrea.github.io/frontend/i18next-josa/" rel="alternate" type="text/html" title="i18next : formatting 으로 을/를, 이/가 구분하기" /><published>2023-01-26T01:00:00+00:00</published><updated>2023-01-26T01:00:00+00:00</updated><id>https://kjkandrea.github.io/frontend/i18next-josa</id><content type="html" xml:base="https://kjkandrea.github.io/frontend/i18next-josa/"><![CDATA[<p>레트로 게임을 하다보면 을/를, 이/가 와 같은 조사들을 괄호를 통해 (을)를, (이)가 식으로 함께 표기해둔것을 흔히 볼수 있다.
예상하기로는 외국어를 한글로 현지화하는 과정에서 <code class="language-plaintext highlighter-rouge">{name}(이)가 승부를 걸어왔다!</code> 식으로 문자열 탬플릿을 작성해두고 <code class="language-plaintext highlighter-rouge">name</code> 변수를 조합하는 방식을 차용하여
아래와 같은 괄호 표기가 나온것이 아닐까 한다.</p>

<p><img src="/assets/img/pokemon.png" alt="pokemon gold game screenshot" /></p>

<p>탬플릿 문자열은 i18next 등으로 다국어를 지원할때에 흔히 사용하는 방식이다. 명사/대명사 등을 동적으로 변수를 통해 적용할때에 
json 형식으로 문자열을 선언해두고, 사용부에서 매개변수에 값을 넘기는 식으로 동적으로 변경되어야하는 문장의 현지화를 지원하고는 한다.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">{fieldName}(을)를 입력하세요.</code></li>
  <li><code class="language-plaintext highlighter-rouge">{a}(와)과 {b}(을)를 선택해주세요.</code></li>
  <li><code class="language-plaintext highlighter-rouge">{userName}(이)가 메시지를 보냈습니다.</code></li>
</ul>

<p><a href="https://www.i18next.com/">i18next</a> 형식으로 나타내보자면 아래와 같다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">translation</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">wild_pokemon_appeared</span><span class="p">:</span> <span class="dl">"</span><span class="s2">야생의 {pokemon}(이)가 튀어나왔다!</span><span class="dl">"</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">t</span><span class="p">(</span><span class="dl">'</span><span class="s1">wild_pokemon_appeared</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span><span class="na">pokemon</span><span class="p">:</span> <span class="dl">'</span><span class="s1">리자몽</span><span class="dl">'</span><span class="p">})</span> <span class="c1">// 야생의 리자몽(이)가 튀어나왔다! </span>
</code></pre></div></div>

<p>을/를, 이/가 중 무엇을 붙힐지 계산하는 방법은 검색을 통해 쉽게 찾을 수 있는데, <code class="language-plaintext highlighter-rouge">String.prototype.charCodeAt</code> 을 통해 변수로 들어올 명사의 마지막 글자의 유니코드를 확인하는 방법을 사용할 수 있다. (<a href="https://gun0912.tistory.com/65">참고</a>)</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/**
 * 마지막 글자가 받침을 가지는지 확인
 */</span>
<span class="kd">function</span> <span class="nx">checkBatchimEnding</span><span class="p">(</span><span class="nx">word</span><span class="p">:</span> <span class="kr">string</span><span class="p">):</span> <span class="nx">boolean</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">lastLetter</span> <span class="o">=</span> <span class="nx">word</span><span class="p">[</span><span class="nx">word</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span>
  <span class="kd">const</span> <span class="nx">uni</span> <span class="o">=</span> <span class="nx">lastLetter</span><span class="p">.</span><span class="nx">charCodeAt</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>

  <span class="c1">// 한글 범위 밖일 경우 false</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">uni</span> <span class="o">&lt;</span> <span class="mi">44032</span> <span class="o">||</span> <span class="nx">uni</span> <span class="o">&gt;</span> <span class="mi">55203</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>

  <span class="c1">// 받침이 있을 경우 true</span>
  <span class="k">return</span> <span class="p">(</span><span class="nx">uni</span> <span class="o">-</span> <span class="mi">44032</span><span class="p">)</span> <span class="o">%</span> <span class="mi">28</span> <span class="o">!==</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">checkBatchimEnding</span><span class="p">(</span><span class="dl">'</span><span class="s1">리자몽</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// true</span>
<span class="nx">checkBatchimEnding</span><span class="p">(</span><span class="dl">'</span><span class="s1">피카츄</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// false</span>
</code></pre></div></div>

<p>다음으로 <code class="language-plaintext highlighter-rouge">checkBatchimEnding</code> 을 이용하여 i18next 로 변수에 따라 조사를 달아주는 포멧팅 함수를 정의해보자.
i18next 의 <code class="language-plaintext highlighter-rouge">i18next.services.formatter.add</code> 을 이용하여 정의할 수 있다. (<a href="https://www.i18next.com/translation-function/formatting">참고</a>)</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">i18next</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">formatter</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">이가</span><span class="dl">'</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">value</span> <span class="o">+</span> <span class="p">(</span><span class="nx">checkBatchimEnding</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">이</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">가</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>

<span class="nx">i18next</span><span class="p">.</span><span class="nx">services</span><span class="p">.</span><span class="nx">formatter</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">을를</span><span class="dl">'</span><span class="p">,</span> <span class="nx">value</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">value</span> <span class="o">+</span> <span class="p">(</span><span class="nx">checkBatchimEnding</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">을</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">를</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<p>함수명이 <code class="language-plaintext highlighter-rouge">이가</code>, <code class="language-plaintext highlighter-rouge">을를</code> 인 받침이 있느냐 없느냐에 따라 조사를 붙혀주는 포멧 함수를 만들었다. 이 함수는 i18n 다국어 탬플릿내에 다음과 같이 사용할 수 있다. 함수명을 이례적으로 한글로 명명한 이유는 다국어 탬플릿만 보아도 자연스럽게 의미가 설명 될 수 있게하기 위함이다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">translation</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">wild_pokemon_appeared</span><span class="p">:</span> <span class="dl">"</span><span class="s2">야생의 {pokemon, 이가} 튀어나왔다!</span><span class="dl">"</span><span class="p">,</span>
  <span class="na">caught_a_pokemon</span><span class="p">:</span> <span class="dl">"</span><span class="s2">신난다! {pokemon, 을를} 잡았다!</span><span class="dl">"</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">t</span><span class="p">(</span><span class="dl">'</span><span class="s1">wild_pokemon_appeared</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span><span class="na">pokemon</span><span class="p">:</span> <span class="dl">'</span><span class="s1">리자몽</span><span class="dl">'</span><span class="p">})</span> <span class="c1">// 야생의 리자몽이 튀어나왔다!</span>
<span class="nx">t</span><span class="p">(</span><span class="dl">'</span><span class="s1">caught_a_pokemon</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span><span class="na">pokemon</span><span class="p">:</span> <span class="dl">'</span><span class="s1">피카츄</span><span class="dl">'</span><span class="p">})</span> <span class="c1">// 신난다! 피카츄를 잡았다!</span>
</code></pre></div></div>

<p>i18next 는 위와 같은 <a href="https://www.i18next.com/translation-function/formatting">formatting</a> 이외에도 많은 유틸리티 기능을 제공한다. 이와 같이 값에 따라 동적으로 문자열을 변경해야할때에 찾아보면 썩 도움이 될만한 기능들이 많은것같다.</p>

<p>예시로 작성한 실행 가능한 코드를 github 에 올려두었다.</p>

<p><a href="https://github.com/kjkandrea/i18next-josa/blob/main/src/main.ts">https://github.com/kjkandrea/i18next-josa/blob/main/src/main.ts</a></p>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="frontend" /><summary type="html"><![CDATA[레트로 게임을 하다보면 을/를, 이/가 와 같은 조사들을 괄호를 통해 (을)를, (이)가 식으로 함께 표기해둔것을 흔히 볼수 있다. 예상하기로는 외국어를 한글로 현지화하는 과정에서 {name}(이)가 승부를 걸어왔다! 식으로 문자열 탬플릿을 작성해두고 name 변수를 조합하는 방식을 차용하여 아래와 같은 괄호 표기가 나온것이 아닐까 한다.]]></summary></entry><entry><title type="html">Tagged Union : 태그로 데이터들의 조합을 안전하게 구분하기</title><link href="https://kjkandrea.github.io/frontend/tagged-union/" rel="alternate" type="text/html" title="Tagged Union : 태그로 데이터들의 조합을 안전하게 구분하기" /><published>2023-01-25T03:00:00+00:00</published><updated>2023-01-25T03:00:00+00:00</updated><id>https://kjkandrea.github.io/frontend/tagged-union</id><content type="html" xml:base="https://kjkandrea.github.io/frontend/tagged-union/"><![CDATA[<p>클라이언트에서 데이터들을 다루다보면 특정 상태일때에만 유효한 필드, 유효하지 않은 필드가 존재한다.<br />
<a href="https://en.wikipedia.org/wiki/Tagged_union">태그된 유니온(Tagged Union)</a> 을 사용하여 이를 구분하는 방법을 간단히 정리해보자.</p>

<p>전자상거래에서의 상품을 타입으로 나타낸다면 대략 다음과 같을 것이다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">Product</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">featured_image</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">alt</span><span class="p">:</span> <span class="kr">string</span>
  <span class="p">},</span>
  <span class="nx">price</span><span class="p">:</span> <span class="nx">Money</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위와 같은 상품 데이터를 토대로 사용자에게 장바구니 리스트, 장바구니 상품의 구매가능 여부등을 제공하는 장바구니 클라이언트를 개발한다고 생각해보자.</p>

<p><img src="/assets/img/cart_example.jpeg" alt="Cart Product List" /></p>

<p>개발을 하다보니 <strong>구매불가한 상품</strong>에 대한 처리가 필요해졌다. Product 인터페이스에 해당 프로퍼티를 추가한다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">Product</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">featured_image</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">alt</span><span class="p">:</span> <span class="kr">string</span>
  <span class="p">},</span>
  <span class="nx">price</span><span class="p">:</span> <span class="nx">Money</span><span class="p">,</span>
  <span class="nx">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ONSALE</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">UNAVAILABLE</span><span class="dl">'</span><span class="p">,</span>
  <span class="cm">/** saleStatus UNAVAILABLE 일때의 구매 불가 사유 */</span> 
  <span class="nx">unavailableReason</span><span class="p">?:</span> <span class="kr">string</span>
<span class="p">}</span>
</code></pre></div></div>

<p>status 를 추가하고 선택 속성(optional property) 을 통해 unavailableReason 를 추가했다.<br />
이에 따라 썸네일에 ‘구매 불가’ 텍스트를 씌우고, 구매불가 사유를 표기해야한다. JSX 탬플릿을 다음과 같이 업데이트한다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">&gt;</span>
  <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">FeaturedImage</span> 
    <span class="na">image</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">featured_image</span><span class="si">}</span>
    <span class="na">unavailable</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">UNAVAILABLE</span><span class="dl">'</span><span class="si">}</span> 
  <span class="p">/&gt;</span>
  // 📌 problem.
  <span class="si">{</span>
    <span class="nx">product</span><span class="p">.</span><span class="nx">unavailableReason</span> <span class="o">!==</span> <span class="kc">undefined</span>
    <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">GuideText</span> <span class="na">text</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">unavailableReason</span><span class="si">}</span> <span class="na">color</span><span class="p">=</span><span class="s">"warn"</span> <span class="p">/&gt;</span>
  <span class="si">}</span>
  <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">Title</span> <span class="na">text</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="p">/&gt;</span>
  <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">Price</span> <span class="na">price</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">price</span><span class="si">}</span> <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nc">CartProduct</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>어색한 부분이 보인다. <code class="language-plaintext highlighter-rouge">Product.unavailableReason</code> 은 <code class="language-plaintext highlighter-rouge">product.status === 'UNAVAILABLE'</code> 일 경우에만 존재하는 값이지만, <code class="language-plaintext highlighter-rouge">Product</code> 인터페이스는 <code class="language-plaintext highlighter-rouge">status</code> 와 <code class="language-plaintext highlighter-rouge">unavailableReason</code> 의 <strong>연관성을 나타내지 못한다.</strong></p>

<p>때문에 주석상의 📌부분에서는 <code class="language-plaintext highlighter-rouge">product.status === 'UNAVAILABLE'</code> 과 별도로 <code class="language-plaintext highlighter-rouge">unavailableReason</code> 의 존재 유무를 검사하고 있다.</p>

<p>현재 상태로 특정 <code class="language-plaintext highlighter-rouge">status</code> 에 대한 프로퍼티가  더 추가되거나 한다면 <code class="language-plaintext highlighter-rouge">Product</code> 의 이러한 구분은 더욱 모호해질 것이다.\</p>

<h2 id="불가능한-상태는-불가능하게">불가능한 상태는 불가능하게</h2>

<p>이러한 데이터의 경우 태그된 유니온 을 통해 속성과 속성간의 관계를 다음과 같이 명시적으로 나타내줄 수 있다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">BaseProduct</span> <span class="p">{</span>
  <span class="nl">id</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">name</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">featured_image</span><span class="p">:</span> <span class="p">{</span>
    <span class="nl">url</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
    <span class="nx">alt</span><span class="p">:</span> <span class="kr">string</span>
  <span class="p">},</span>
  <span class="nx">price</span><span class="p">:</span> <span class="nx">Money</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">OnSaleProduct</span> <span class="kd">extends</span> <span class="nx">BaseProduct</span> <span class="p">{</span>
  <span class="nl">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ONSALE</span><span class="dl">'</span>
<span class="p">}</span>

<span class="kr">interface</span> <span class="nx">UnavailableProduct</span> <span class="kd">extends</span> <span class="nx">BaseProduct</span> <span class="p">{</span>
  <span class="nl">status</span><span class="p">:</span> <span class="dl">'</span><span class="s1">UNAVAILABLE</span><span class="dl">'</span><span class="p">,</span>
  <span class="nx">unavailableReason</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>

<span class="kd">type</span> <span class="nx">Product</span> <span class="o">=</span> <span class="nx">OnSaleProduct</span> <span class="o">|</span> <span class="nx">UnavailableProduct</span> 
</code></pre></div></div>

<p>이제 <code class="language-plaintext highlighter-rouge">status</code> 필드는 해당 Product 의 유형을 설명하는 태그 역할을 한다.<br />
이를 통해 status 와 unavailableReason 의 관계가 명확해졌기에 JSX 도 다음과 같이 status 중심으로 작성될 수 있다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">&gt;</span>
  <span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">switch</span> <span class="p">(</span><span class="nx">product</span><span class="p">.</span><span class="nx">status</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">case</span> <span class="dl">'</span><span class="s1">ONSALE</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">(</span>
          <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">FeaturedImage</span> <span class="na">image</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">featured_image</span><span class="si">}</span> <span class="p">/&gt;</span>
        <span class="p">);</span>
      <span class="k">case</span> <span class="dl">'</span><span class="s1">UNAVAILABLE</span><span class="dl">'</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">(</span>
          <span class="p">&lt;&gt;</span>
            <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">FeaturedImage</span> <span class="na">image</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">featured_image</span><span class="si">}</span> <span class="na">unavailable</span><span class="p">=</span><span class="si">{</span><span class="kc">true</span><span class="si">}</span> <span class="p">/&gt;</span>
            <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">GuideText</span> <span class="na">text</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">unavailableReason</span><span class="si">}</span> <span class="na">color</span><span class="p">=</span><span class="s">"warn"</span> <span class="p">/&gt;</span>
          <span class="p">&lt;/&gt;</span>
        <span class="p">)</span>
    <span class="p">}</span>
  <span class="p">}</span><span class="si">}</span>
  <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">Title</span> <span class="na">text</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span> <span class="p">/&gt;</span>
  <span class="p">&lt;</span><span class="nc">CartProduct</span><span class="p">.</span><span class="nc">Price</span> <span class="na">price</span><span class="p">=</span><span class="si">{</span><span class="nx">product</span><span class="p">.</span><span class="nx">price</span><span class="si">}</span> <span class="p">/&gt;</span>
<span class="p">&lt;/</span><span class="nc">CartProduct</span><span class="p">&gt;</span>
</code></pre></div></div>

<h2 id="정리">정리</h2>

<p>위와 같이 태그된 유니온을 통해 선언된 타입은 가능한 상태와 불가능한 상태를 나타내는 일종의 문서 역할을 한다.<br />
이처럼 태그만을 추가하는 간단한 방법을 통해 데이터를 보다 명시적으로 모델링하고 안전하게 다룰 수 있다.</p>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="frontend" /><summary type="html"><![CDATA[클라이언트에서 데이터들을 다루다보면 특정 상태일때에만 유효한 필드, 유효하지 않은 필드가 존재한다. 태그된 유니온(Tagged Union) 을 사용하여 이를 구분하는 방법을 간단히 정리해보자.]]></summary></entry><entry><title type="html">React 제출 양식에서 입력 필드의 재사용성 찾기</title><link href="https://kjkandrea.github.io/frontend/reusable-form-field/" rel="alternate" type="text/html" title="React 제출 양식에서 입력 필드의 재사용성 찾기" /><published>2023-01-18T03:00:00+00:00</published><updated>2023-01-18T03:00:00+00:00</updated><id>https://kjkandrea.github.io/frontend/reusable-form-field</id><content type="html" xml:base="https://kjkandrea.github.io/frontend/reusable-form-field/"><![CDATA[<p>제출 양식(form)은 사용자와 서버가 상호작용 할 수 있는 대화형 개체이다. 
많은 정보 입력이 필요한 쇼핑몰의 관리자 페이지나 채용 플랫폼 같은 서비스에서의 제출 양식은 수많은 필드가 존재한다.</p>

<p>작성된 양식이 서버에 도달한 후 거부되면 사용자에게 지시하기 이전에 
잘못된 입력에 대한 정보를 보다 이른 시점에 제공하고자 유효성 검증(validate) 로직을 브라우저에서 컨트롤하곤 한다.</p>

<p>이러한 제출 양식이 어떠한 일을 하는지 한번 나열해보자.</p>

<ol>
  <li>입력 가능한 필드를 제공한다.</li>
  <li>입력이 완료되면 서버에 전송한다.</li>
  <li>(서버 처리 결과 알림) 서버에서 400 번대 에러가 응답되었다면, 사용자에게 피드백을 제공한다.</li>
  <li>(브라우저 측 검증) 필수 입력 필드를 입력하지 않았다면/입력 양식이 정합하지 않다면, 사용자에게 피드백을 제공한다.</li>
  <li>사용자가 입력 필드를 작성하다가 제출하지 않은 채 이탈하려 한다면, 작성중이던 양식이 사라질 수 있다는 걸 확인시킨다.</li>
  <li>입력 양식을 이용하여 검색을 한다면 search params 와 사용자가 입력한 양식을 동기화한다.</li>
</ol>

<p>5, 6 은 상황에 따라 구현이 필요한 불필요한 경우도 있지만, 1, 2, 3, 4 은 제출 양식을 구현할때에 대부분의 경우 필수적이다.</p>

<h2 id="제출-양식을-통한-사용자-인증-을-구현해보자">제출 양식을 통한 사용자 인증 을 구현해보자.</h2>

<p>1, 2, 3, 4 의 기능을 무작정 구현하여 복잡성을 체감해볼까?</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">signInMutation</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">store/auth</span><span class="dl">'</span>

<span class="kd">function</span> <span class="nx">SignIn</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">email</span><span class="p">,</span> <span class="nx">setEmail</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">password</span><span class="p">,</span> <span class="nx">setPassword</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">clientErrorMessage</span><span class="p">,</span> <span class="nx">setClientErrorMessage</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>

  <span class="kd">const</span> <span class="p">{</span>
    <span class="nx">mutate</span><span class="p">,</span>
    <span class="c1">// 3. (서버 처리 결과 알림) 서버에서 400 번대 에러가 응답되었다면, 사용자에게 피드백을 제공한다. </span>
    <span class="na">error</span><span class="p">:</span> <span class="p">{</span><span class="na">message</span><span class="p">:</span> <span class="nx">serverErrorMessage</span><span class="p">},</span>
  <span class="p">}</span> <span class="o">=</span> <span class="nx">signInMutation</span><span class="p">();</span>
  
  <span class="c1">// 4. (브라우저 측 검증) 필수 입력 필드를 입력하지 않았다면/입력 양식이 정합하지 않다면, 사용자에게 피드백을 제공한다.</span>
  <span class="kd">const</span> <span class="nx">validate</span> <span class="o">=</span> <span class="p">():</span> <span class="nx">boolean</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">email</span><span class="p">.</span><span class="nx">trim</span><span class="p">())</span> <span class="p">{</span>
      <span class="nx">setClientErrorMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">이메일을 입력하세요.</span><span class="dl">'</span><span class="p">);</span>
      <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">password</span><span class="p">.</span><span class="nx">trim</span><span class="p">())</span> <span class="p">{</span>
      <span class="nx">setClientErrorMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">비밀번호를 입력하세요.</span><span class="dl">'</span><span class="p">);</span>
      <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
    <span class="p">}</span>
      
    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="c1">// 2. 입력이 완료되면 서버에 전송한다. </span>
  <span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="p">(</span><span class="nx">e</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FormEvent</span><span class="o">&lt;</span><span class="nx">HTMLFormElement</span><span class="o">&gt;</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
      
    <span class="kd">const</span> <span class="nx">invalid</span> <span class="o">=</span> <span class="o">!</span><span class="nx">validate</span><span class="p">();</span>
    <span class="k">if</span><span class="p">(</span><span class="nx">invalid</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

    <span class="nx">mutate</span><span class="p">({</span>
      <span class="nx">email</span><span class="p">,</span>
      <span class="nx">password</span><span class="p">,</span>
    <span class="p">});</span>
  <span class="p">};</span>

  <span class="c1">// 1. 입력 가능한 필드를 제공한다.</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">form</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">handleSubmit</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">input</span>
        <span class="na">type</span><span class="p">=</span><span class="s">"text"</span>
        <span class="na">name</span><span class="p">=</span><span class="s">"email"</span>
        <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">email</span><span class="si">}</span>
        <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">setEmail</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span>
      <span class="p">/&gt;</span>
      <span class="p">&lt;</span><span class="nt">input</span>
        <span class="na">type</span><span class="p">=</span><span class="s">"password"</span>
        <span class="na">name</span><span class="p">=</span><span class="s">"password"</span>
        <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">password</span><span class="si">}</span>
        <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">setPassword</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span>
      <span class="p">/&gt;</span>
      <span class="si">{</span><span class="nx">clientErrorMessage</span><span class="si">}</span>
      <span class="si">{</span><span class="nx">serverErrorMessage</span><span class="si">}</span>
      <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span> <span class="na">value</span><span class="p">=</span><span class="s">"로그인"</span> <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>여러 관심사가 하나의 컴포넌트로 응집되어 있는 모습이다. 재사용성 관점에서 생각해보자.
이메일, 패스워드 필드가 이 양식에서만 등장할까? 이메일은 회원가입, 이메일을 통해 비밀번호 찾기 양식 등에서 등장할 수 있고 마찬가지로 비밀번호 필드도 여러 양식에서 등장할 수 있다.</p>

<p>각 필드들이 EmailField, PasswordField 컴포넌트로 정의된다면 어떨까? <code class="language-plaintext highlighter-rouge">*Field</code> 컴포넌트가 맡게 될 관심사는 다음과 같다.</p>

<ul>
  <li>1. 입력 가능한 필드를 제공한다.</li>
  <li>4. (브라우저 측 검증) 필수 입력 필드를 입력하지 않았다면/입력 양식이 정합하지 않다면, 사용자에게 피드백을 제공한다.</li>
</ul>

<p>구현 중점으로 조금 더 명확하게 정의해보자면 다음과 같다.</p>

<ul>
  <li>제출 이벤트가 일어날 때에 부모로 부터 메시지를 받을 수 있다.</li>
  <li>입력 가능한 Input 을 제공한다.</li>
  <li>입력된 값을 부모에게 전달할 수 있다.</li>
  <li>유효성 검증 규칙을 내부에서 지니고 있다.</li>
</ul>

<h3 id="리액트의-설계원칙대로-부모에서-자식으로-메시지-전달하기">리액트의 설계원칙대로 부모에서 자식으로 메시지 전달하기</h3>

<p><code class="language-plaintext highlighter-rouge">*Field</code> 를 사용하는 부모 컴포넌트에서 <code class="language-plaintext highlighter-rouge">*Field</code> 에게 제출되는 시점에 유효성 검증을 요청해야한다.</p>

<p>리액트 컴포넌트가 아닌 객체라고 가정하면 부모 객체에서 자식 객체로 메시지를 보내는것은 비교적 간단하다.
제출이 일어나는 시점에 <code class="language-plaintext highlighter-rouge">this.emailField.validate()</code> 자식 객체에서 지닌 메서드를 다음과 같이 호출하면 될것이다.</p>

<p>리액트는 이러한 부모가 자식을 제어하는 구조를 권장하지않고, 예상 가능한 데이터의 흐름과 구조의 단순화를 위해 단방향 데이터 흐름을 통해 컴포넌트를 설계하기를 권장한다.</p>

<p>그렇다면 조금 다르게 사고해보자. 제출이 일어나는 특정 시점에 메서드등 자식이 내부에서 지니고 있는 기능을 호출하는 것이 아닌, 특정 시점에 호출될 메시지를 <strong>자식에서 사전에 정의하여 부모에게 전달해둔다면 어떨까?</strong></p>

<p>위 정보를 토대로 <code class="language-plaintext highlighter-rouge">FieldRegister</code> 를 구성해보자.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 자식 컴포넌트에서 *Field 컴포넌트를 정의하려면 fieldRegister 를 거쳐야한다. </span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">FieldRegister</span> <span class="o">=</span> <span class="p">(</span>
  <span class="c1">// field 명, 유효성 검증 규칙을 fieldRegister 를 통해 전달해야한다.</span>
  <span class="nx">fieldName</span><span class="p">:</span> <span class="kr">string</span><span class="p">,</span>
  <span class="nx">validate</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span>
<span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="na">name</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">fieldRegister</span><span class="p">:</span> <span class="nx">FieldRegister</span> <span class="o">=</span> <span class="p">(</span><span class="nx">fieldName</span><span class="p">,</span> <span class="nx">validate</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="c1">// ...</span>
  <span class="k">return</span> <span class="p">{</span>
    <span class="na">name</span><span class="p">:</span> <span class="nx">fieldName</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// ...</span>
<span class="p">&lt;</span><span class="nc">EmailField</span> <span class="na">fieldRegister</span><span class="p">=</span><span class="si">{</span><span class="nx">fieldRegister</span><span class="si">}</span> <span class="p">/&gt;</span>
</code></pre></div></div>

<p>FieldRegister 를 통해 이메일 필드를 생성하는 EmailField 컴포넌트도 러프하게 정의해볼까?</p>

<p>예제에서는 적당한 복잡성을 유지하기위해 입력값이 있는지만을 검증하겠지만, 앞으로 정의될 <code class="language-plaintext highlighter-rouge">validate</code> 의 검증 규칙이 정규표현식이 들어가고 길이 제한이 있는 등 매우 디테일하다고 상상해보자.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">EmailField</span><span class="p">({</span> <span class="nx">fieldRegister</span> <span class="p">}:</span> <span class="p">{</span> <span class="nl">fieldRegister</span><span class="p">:</span> <span class="nx">FieldRegister</span> <span class="p">})</span> <span class="p">{</span>
  <span class="c1">// 유효성 검증 실패시 보여줄 안내 문구 state</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">guideMessage</span><span class="p">,</span> <span class="nx">setGuideMessage</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">div</span> <span class="na">class</span><span class="p">=</span><span class="s">"field"</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">input</span>
        <span class="na">type</span><span class="p">=</span><span class="s">"email"</span>
        <span class="c1">// 📌 FieldRegister 의 규칙에 따라 fieldName, validate 를 정의한다.</span>
        <span class="si">{</span><span class="p">...</span><span class="nx">fieldRegister</span><span class="p">(</span><span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">,</span> <span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
          <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">value</span><span class="p">.</span><span class="nx">trim</span><span class="p">())</span> <span class="p">{</span>
            <span class="nx">setGuideMessage</span><span class="p">(</span><span class="dl">'</span><span class="s1">이메일을 입력하세요.</span><span class="dl">'</span><span class="p">);</span>
            <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
          <span class="p">}</span>

          <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
        <span class="p">})</span><span class="si">}</span>
      <span class="p">/&gt;</span>
      <span class="si">{</span><span class="nx">guideMessage</span><span class="si">}</span>
    <span class="p">&lt;/&gt;</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>주석의 📌 부분은 리액트의 디자인 패턴인 <a href="https://simsimjae.medium.com/react-design-pattern-props-getter-pattern-5d3cf6f0b495">Props Getter Pattern</a> 패턴을 따른다. register 의 리턴값이 <code class="language-plaintext highlighter-rouge">&lt;input&gt;</code> 에 바인딩 됨으로
fieldRegister 의 리턴값을 통해 부모에서 인풋을 제어할 수 있다. 가령 onChange 를 리턴한다면 실시간으로 값을 부모에서 조회할 수 있는 구조를 갖출 수 있을것이다.</p>

<h3 id="제출이-일어나는-시점에-유효성-검증-실행">제출이 일어나는 시점에 유효성 검증 실행</h3>

<p>부모에서 validate 를 자식에게 전달받을 수 있음으로, 부모에서 원하는 시점에 유효성 검증을 실행할 수 있다.</p>

<p>제출(submit) 과 fieldRegister 규칙을 커스텀 훅을 통해 캡슐화할 수 있도록 만들어보자. 커스텀 훅의 이름은 <code class="language-plaintext highlighter-rouge">useForm</code> 이다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">FormField</span> <span class="p">{</span>
  <span class="nl">validate</span><span class="p">:</span> <span class="p">(</span><span class="nx">value</span><span class="p">:</span> <span class="kr">string</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">boolean</span><span class="p">;</span>
  <span class="nl">value</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">useForm</span><span class="o">&lt;</span><span class="nx">FormValues</span><span class="o">&gt;</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">formFieldsRef</span> <span class="o">=</span> <span class="nx">useRef</span><span class="o">&lt;</span><span class="p">{</span>
    <span class="c1">// 딕셔너리 구조로 각 필드의 validate, value 를 관리한다.</span>
    <span class="p">[</span><span class="na">key</span><span class="p">:</span> <span class="kr">string</span><span class="p">]:</span> <span class="nx">FormField</span><span class="p">;</span>
  <span class="p">}</span><span class="o">&gt;</span><span class="p">({});</span>

  <span class="kd">const</span> <span class="nx">register</span><span class="p">:</span> <span class="nx">FieldRegister</span> <span class="o">=</span> <span class="p">(</span><span class="nx">fieldName</span><span class="p">,</span> <span class="nx">validate</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="p">{</span>
      <span class="nx">validate</span><span class="p">,</span>
      <span class="na">value</span><span class="p">:</span> <span class="dl">''</span><span class="p">,</span>
    <span class="p">};</span>
    <span class="c1">// formFieldsRef 에 register 를 통해 생성된 필드를 등록한다.</span>
    <span class="nx">formFieldsRef</span><span class="p">.</span><span class="nx">current</span> <span class="o">=</span> <span class="p">{</span>
      <span class="p">...</span><span class="nx">formFieldsRef</span><span class="p">.</span><span class="nx">current</span><span class="p">,</span>
      <span class="p">[</span><span class="nx">fieldName</span><span class="p">]:</span> <span class="nx">field</span><span class="p">,</span>
    <span class="p">};</span>

    <span class="k">return</span> <span class="p">{</span>
      <span class="na">name</span><span class="p">:</span> <span class="nx">fieldName</span><span class="p">,</span>
      <span class="na">onChange</span><span class="p">:</span> <span class="nx">e</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">field</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
      <span class="p">},</span>
    <span class="p">};</span>
  <span class="p">};</span>
 
  <span class="c1">// 훅에서 제출 이벤트 핸들러를 제공한다.</span>
  <span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="p">(</span>
    <span class="nx">e</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">FormEvent</span><span class="o">&lt;</span><span class="nx">HTMLFormElement</span><span class="o">&gt;</span><span class="p">,</span>
    <span class="c1">// 양식 검증이 완료되었다면 next 콜백을 호출한다.</span>
    <span class="nx">next</span><span class="p">:</span> <span class="p">(</span><span class="nx">result</span><span class="p">:</span> <span class="nx">FormValues</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="k">void</span>
  <span class="p">)</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nx">e</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>

    <span class="c1">// formFieldsRef 에 등록된 모든 양식이 validate: true 라면 검증된(true) 값이다.  </span>
    <span class="kd">const</span> <span class="nx">valid</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">values</span><span class="p">(</span><span class="nx">formFieldsRef</span><span class="p">.</span><span class="nx">current</span><span class="p">).</span><span class="nx">every</span><span class="p">(</span>
      <span class="p">({</span><span class="nx">validate</span><span class="p">,</span> <span class="nx">value</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="nx">validate</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span>
    <span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nx">valid</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

    <span class="kd">const</span> <span class="nx">formValues</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nx">entries</span><span class="p">(</span><span class="nx">formFieldsRef</span><span class="p">.</span><span class="nx">current</span><span class="p">).</span><span class="nx">map</span><span class="p">(</span>
      <span class="p">([</span><span class="nx">fieldName</span><span class="p">,</span> <span class="p">{</span><span class="nx">value</span><span class="p">}])</span> <span class="o">=&gt;</span> <span class="p">[</span><span class="nx">fieldName</span><span class="p">,</span> <span class="nx">value</span><span class="p">]</span>
    <span class="p">);</span>

    <span class="c1">// 검증이 성공하였을 경우 next 를 호출한다.</span>
    <span class="nx">next</span><span class="p">(</span><span class="nb">Object</span><span class="p">.</span><span class="nx">fromEntries</span><span class="p">(</span><span class="nx">formValues</span><span class="p">));</span>
  <span class="p">};</span>

  <span class="k">return</span> <span class="p">{</span>
    <span class="nx">register</span><span class="p">,</span>
    <span class="nx">handleSubmit</span><span class="p">,</span>
  <span class="p">};</span>
<span class="p">}</span>
</code></pre></div></div>

<p>자 이제 준비된 useForm 커스텀 훅을 부모 컴포넌트에서 사용해보자.<br />
<a href="https://react-hook-form.com">react-hook-form</a> 라이브러리를 사용해보았다면 react-hook-form 의 <code class="language-plaintext highlighter-rouge">useForm</code> 과 유사한 구조를 지님을 알아차렸을 수도 있겠다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">SignIn</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span><span class="nx">handleSubmit</span><span class="p">,</span> <span class="nx">register</span><span class="p">}</span> <span class="o">=</span> <span class="nx">useForm</span><span class="o">&lt;</span><span class="p">{</span>
    <span class="na">email</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
    <span class="nl">password</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span>
  <span class="p">}</span><span class="o">&gt;</span><span class="p">();</span>

  <span class="kd">const</span> <span class="p">{</span>
    <span class="nx">mutate</span><span class="p">,</span>
    <span class="na">error</span><span class="p">:</span> <span class="p">{</span><span class="na">message</span><span class="p">:</span> <span class="nx">serverErrorMessage</span><span class="p">},</span>
  <span class="p">}</span> <span class="o">=</span> <span class="nx">signInMutation</span><span class="p">();</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">form</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">handleSubmit</span><span class="p">(</span><span class="nx">e</span><span class="p">,</span> <span class="nx">mutate</span><span class="p">)</span><span class="si">}</span> <span class="na">noValidate</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">EmailField</span> <span class="na">register</span><span class="p">=</span><span class="si">{</span><span class="nx">register</span><span class="si">}</span> <span class="p">/&gt;</span>
      <span class="p">&lt;</span><span class="nc">PasswordField</span> <span class="na">register</span><span class="p">=</span><span class="si">{</span><span class="nx">register</span><span class="si">}</span> <span class="p">/&gt;</span>
      <span class="si">{</span><span class="nx">serverErrorMessage</span><span class="si">}</span>
      <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span> <span class="na">value</span><span class="p">=</span><span class="s">"로그인"</span> <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>무엇이 나아졌는가? 가장 눈에 띄는 부분은 부모 컴포넌트 SignIn 과 자식 컴포넌트 <code class="language-plaintext highlighter-rouge">*Field</code> 가 각자의 관심사를 나눠가졌다는 것이다.</p>

<p><strong>부모 컴포넌트 SignIn</strong></p>

<ul>
  <li>입력이 완료되면 서버에 전송한다.</li>
  <li>(서버 처리 결과 알림) 서버에서 400 번대 에러가 응답되었다면, 사용자에게 피드백을 제공한다.</li>
</ul>

<p><strong>자식 컴포넌트 EmailField, PasswordField</strong></p>

<ul>
  <li>입력 가능한 필드를 제공한다.</li>
  <li>(브라우저 측 검증) 필수 입력 필드를 입력하지 않았다면/입력 양식이 정합하지 않다면, 사용자에게 피드백을 제공한다.</li>
</ul>

<p><strong>커스텀 훅 useForm</strong></p>

<ul>
  <li>부모, 자식이 메시지를 주고받는 방식을 제공한다.</li>
  <li>Field 의 생성 규칙을 정의한다.</li>
</ul>

<p>또한 자식 컴포넌트는 재사용이 가능하다. 이 글에서는 입력요소(<code class="language-plaintext highlighter-rouge">&lt;input&gt;</code>) 만을 다루었지만 입력요소 조합된 형태의 fieldSet 이나, 체크 박스, 선택 입력 등을 Field 계층과 동일한 접근법을 통해 정의할 수 있음을 예상할 수 있다.</p>

<p>Field 의 관심사가 명확해졌고, 그에 따라 정의된 EmailField, PasswordField 컴포넌트는 회원가입, 이메일을 통해 비밀번호 찾기, 비밀번호 변경 등에 범용적으로 사용될 수 있다.</p>

<h2 id="react-hook-form-소개">react-hook-form 소개</h2>

<p><a href="https://react-hook-form.com/api/useform">react-hook-form</a> 은 위의 예제와 유사한 방식으로 동작하는 리액트 폼 라이브러리이다.</p>

<p>사실 ‘React 제출 양식에서 입력 필드의 재사용성 찾기’ 의 방안을 찾아보던 중 <code class="language-plaintext highlighter-rouge">react-hook-form</code> 을 먼저 접하게 되었고, 위 코드는 해당 라이브러리에서 <a href="https://react-hook-form.com/api/useform"><code class="language-plaintext highlighter-rouge">useForm</code></a>, <a href="https://react-hook-form.com/api/useform/register"><code class="language-plaintext highlighter-rouge">register</code></a> 의 설계를 이해하기 위해 직접 구현해본 것이다.</p>

<p>유효성 검증 시점을 선택할 수 있다거나, 구체적인 validate 규칙을 <code class="language-plaintext highlighter-rouge">pattern</code> 객체를 통해 제공하는 등 uncontrolled 하게 form 을 관리하기 위한 많은 부가기능과 여러 룰을 제공한다.</p>

<p>팀 내의 특정한 규칙이 없다면 입력 양식 관리는 개인 스타일에 따라 중구난방이 되기 마련인데, 
<code class="language-plaintext highlighter-rouge">react-hook-form</code> 은 팀 단위로 일하는 클라이언트 개발 조직에서 규칙성있게 제출 양식을 관리하는 합리적인 대안으로 보인다.</p>

<p>react-hook-form 으로 구현된 로그인 양식은 다음과 같다. 아래 예시에서는 register 를 prop 으로 전달하는 방식대신 <a href="https://react-hook-form.com/api/useformcontext">useFormContext</a> 훅을 사용하여 부모와 메시지를 주고받도록 하였다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">SubmitHandler</span><span class="p">,</span> <span class="nx">useForm</span><span class="p">,</span> <span class="nx">FormProvider</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-hook-form</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">SignInPage</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">methods</span> <span class="o">=</span> <span class="nx">useForm</span><span class="o">&lt;</span><span class="nx">SignInFormValues</span><span class="o">&gt;</span><span class="p">({</span>
    <span class="na">mode</span><span class="p">:</span> <span class="dl">'</span><span class="s1">onSubmit</span><span class="dl">'</span><span class="p">,</span>
  <span class="p">});</span>

  <span class="kd">const</span> <span class="p">{</span>
    <span class="nx">mutate</span><span class="p">,</span>
    <span class="na">error</span><span class="p">:</span> <span class="p">{</span><span class="na">message</span><span class="p">:</span> <span class="nx">serverErrorMessage</span><span class="p">},</span>
  <span class="p">}</span> <span class="o">=</span> <span class="nx">signInMutation</span><span class="p">();</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">FormProvider</span> <span class="si">{</span><span class="p">...</span><span class="nx">methods</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Form</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">methods</span><span class="p">.</span><span class="nx">handleSubmit</span><span class="p">(</span><span class="nx">mutate</span><span class="p">)</span><span class="si">}</span> <span class="na">noValidate</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nc">EmailField</span> <span class="p">/&gt;</span>
        <span class="p">&lt;</span><span class="nc">PasswordField</span> <span class="p">/&gt;</span>
        <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span><span class="p">&gt;</span>로그인<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
      <span class="p">&lt;/</span><span class="nc">Form</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nc">FormProvider</span><span class="p">&gt;</span>
  <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">useFormContext</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-hook-form</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">EmailField</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span>
    <span class="nx">register</span><span class="p">,</span>
    <span class="na">formState</span><span class="p">:</span> <span class="p">{</span><span class="nx">errors</span><span class="p">},</span>
  <span class="p">}</span> <span class="o">=</span> <span class="nx">useFormContext</span><span class="o">&lt;</span><span class="nx">EmailValues</span><span class="o">&gt;</span><span class="p">();</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">Form</span><span class="p">.</span><span class="nc">Input</span>
      <span class="na">id</span><span class="p">=</span><span class="s">"email"</span>
      <span class="na">type</span><span class="p">=</span><span class="s">"email"</span>
      <span class="na">label</span><span class="p">=</span><span class="s">"email"</span>
      <span class="si">{</span><span class="p">...</span><span class="nx">register</span><span class="p">(</span><span class="dl">'</span><span class="s1">email</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">required</span><span class="p">:</span> <span class="dl">'</span><span class="s1">이메일을 입력하세요.</span><span class="dl">'</span><span class="p">,</span>
      <span class="p">})</span><span class="si">}</span>
      <span class="na">helperText</span><span class="p">=</span><span class="si">{</span><span class="nx">errors</span><span class="p">.</span><span class="nx">email</span><span class="p">?.</span><span class="nx">message</span><span class="si">}</span>
    <span class="p">/&gt;</span>
  <span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="마치며">마치며</h2>

<p>예전에 경험했던 프로젝트에서 입력 양식을 규칙 없이 관리하여 수십개의 동일한 브랜드 검색 기능을 지니는 폼을 일괄 수정해야할 때 터무니없는 오랜 시간이 걸려 팀의 사기가 저하되는 경험을 한적이 있다.</p>

<p>이렇게라도 정리해두니 동일한 시행착오를 밞지 않을것같아 한시름 놓인다.</p>

<p>특정 라이브러리의 최소 기능 버전을 러프하게 구현해보는 것은 해당 라이브러리를 이해하기에 많은 도움이 되는것같다.</p>

<h2 id="reference">reference</h2>

<ul>
  <li><a href="https://react-hook-form.com/api/useform">react-hook-form</a></li>
  <li><a href="https://blog.qoddi.com/create-a-reusable-text-input-with-react-hook-form/">Qoddi : Create a Reusable Text Input With React Hook Form</a></li>
  <li><a href="https://simsimjae.medium.com/react-design-pattern-props-getter-pattern-5d3cf6f0b495">심재철 : [React Design Pattern] Props Getter Pattern</a></li>
</ul>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="frontend" /><summary type="html"><![CDATA[제출 양식(form)은 사용자와 서버가 상호작용 할 수 있는 대화형 개체이다. 많은 정보 입력이 필요한 쇼핑몰의 관리자 페이지나 채용 플랫폼 같은 서비스에서의 제출 양식은 수많은 필드가 존재한다.]]></summary></entry><entry><title type="html">최소단위의 React 컴포넌트 만들기</title><link href="https://kjkandrea.github.io/frontend/atom-component/" rel="alternate" type="text/html" title="최소단위의 React 컴포넌트 만들기" /><published>2023-01-16T03:00:00+00:00</published><updated>2023-01-16T03:00:00+00:00</updated><id>https://kjkandrea.github.io/frontend/atom-component</id><content type="html" xml:base="https://kjkandrea.github.io/frontend/atom-component/"><![CDATA[<p><a href="https://bradfrost.com/blog/post/atomic-web-design/">아토믹 디자인</a> 이란 원자(atom)-분자(molecule)-유기체(organism) 순으로
최소 단위의 atom 컴포넌트를 조합하여 단계별로 나타내는 설계 기준이 있다.</p>

<p>최근 팀 내 프로젝트에서 이런 패턴으로 컴포넌트를 단계별로 구현하는 경험을 하였는데, 어려웠던 점은 최소단위인 atom 컴포넌트를 구현하는 방법이었다.
최소단위의 컴포넌트는 HTML 엘리먼트와 닮아있다. 가령 버튼 atom 컴포넌트를 만든다고 가정해보자.</p>

<p>이 버튼은 특정 자바스크립트 이벤트를 트리거 하기 위한 버튼일 수도 있고, 양식을 제출하기 위한 버튼일 수도 있다. 특정 페이지로 이동시키는 링크를 제공하는 버튼일수도 있다.<br />
최소단위를 이루는 컴포넌트는 위와 같이 넒은 범위의 쓰임새에 대한 많은 가정을 해야하며 그 때문에 구현하기 까다로워 보인다.</p>

<p>특정 문제를 해결하는 도메인에 집중하는 서비스 개발과는 달리 라이브러리를 만들듯이 <strong>범용성</strong>을 갖추어야 한다.
React UI 라이브러리인 <a href="https://mui.com/">머티리얼 UI</a>의 Button 컴포넌트의 API 를 살펴보자. clickable 한 요소가 수용할 수 있는 넒은 범위의 수많은 옵션(Prop)을 제공한다.</p>

<p><img src="/assets/img/mui-props.png" alt="MUI Button Prop Options" /></p>

<p>‘어떻게 보여질 것인가’ 를 정의하는 옵션이외에 대략 다음과 같은 옵션을 확인할 수 있다.</p>

<ul>
  <li>component(as) : 어떤 ElementNode 로 렌더링할 것인가</li>
  <li>disabled : 클릭 이벤트를 비활성화</li>
  <li>href : 링크 기능으로 쓰일때에 이동 주소</li>
</ul>

<p>문서내에 언급되진 않았지만 이 외 onClick, type 등 다양한 기본 옵션들이 존재한다. 이제 여러 사용케이스를 포괄하는 다형성을 지닌 버튼을 만들어보자.</p>

<h2 id="button-을-만들어보자">Button 을 만들어보자.</h2>

<p>‘어떻게 보여질 것인가’ 를 제외한 몇가지 옵션이 구현된 버튼을 만들어보자. 어떻게 보여질것인가를 다루지 않기때문에 완성된 형태는 <code class="language-plaintext highlighter-rouge">&lt;button&gt;</code> ReactElement 와 동일할 것이다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">React</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>

<span class="kr">interface</span> <span class="nx">ButtonProps</span> <span class="p">{</span>
  <span class="nl">children</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactNode</span><span class="p">;</span>
  <span class="nl">onClick</span><span class="p">?:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">MouseEventHandler</span><span class="o">&lt;</span><span class="nx">HTMLButtonElement</span><span class="o">&gt;</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Button</span><span class="p">({</span><span class="nx">onClick</span><span class="p">,</span> <span class="nx">children</span><span class="p">}:</span> <span class="nx">ButtonProps</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
  <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"button"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">onClick</span><span class="si">}</span><span class="p">&gt;</span>
  <span class="si">{</span><span class="nx">children</span><span class="si">}</span>
  <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span>   
</code></pre></div></div>

<p>현재 상태로는 클릭 이벤트를 바인딩 할 수 있지만 type, disabled 등 <code class="language-plaintext highlighter-rouge">&lt;button&gt;</code> 이 지니는 기본 어트리뷰트를 제어할 수 없다.</p>

<p><code class="language-plaintext highlighter-rouge">ComponentPropsWithoutRef</code>(<a href="https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase#wrappingmirroring-a-html-element">참고</a>) 를 이용하여 조금 더 범용적으로 타입을 정의해보자.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">interface</span> <span class="nx">ButtonProps</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ComponentPropsWithoutRef</span><span class="o">&lt;</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="na">children</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ReactNode</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Button</span><span class="p">({</span><span class="nx">children</span><span class="p">,</span> <span class="p">...</span><span class="nx">rest</span><span class="p">}:</span> <span class="nx">ButtonProps</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="si">{</span><span class="p">...</span><span class="nx">rest</span><span class="si">}</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">children</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이제 Button atom 요소를 다양한 사용 케이스를 포괄하여 사용할 수 있게 되었다.</p>

<h2 id="다형적-컴포넌트">다형적 컴포넌트</h2>

<p>머티리얼 UI 에서 제공하는 버튼 컴포넌트를 살펴보면 <code class="language-plaintext highlighter-rouge">&lt;button&gt;</code> 처럼 사용할 수도 있지만 href, target 등 어트리뷰트를 넘길 수 있는 <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> 로 사용할 수도 있다.</p>

<p><code class="language-plaintext highlighter-rouge">href</code> 가 있느냐 없느냐를 기준으로 어떤 엘리먼트로 렌더링할지 분기해보자.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">type</span> <span class="nx">ButtonElementProps</span> <span class="o">=</span> <span class="nx">JSX</span><span class="p">.</span><span class="nx">IntrinsicElements</span><span class="p">[</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">];</span>
<span class="kd">type</span> <span class="nx">AnchorElementProps</span> <span class="o">=</span> <span class="nx">JSX</span><span class="p">.</span><span class="nx">IntrinsicElements</span><span class="p">[</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">];</span>
<span class="kd">type</span> <span class="nx">ButtonProps</span> <span class="o">=</span> <span class="nx">ButtonElementProps</span> <span class="o">|</span> <span class="nx">AnchorElementProps</span><span class="p">;</span>

<span class="kd">function</span> <span class="nx">isPropsForAnchorElement</span><span class="p">(</span>
  <span class="nx">props</span><span class="p">:</span> <span class="nx">ButtonProps</span>
<span class="p">):</span> <span class="nx">props</span> <span class="k">is</span> <span class="nx">AnchorElementProps</span> <span class="p">{</span>
  <span class="k">return</span> <span class="dl">'</span><span class="s1">href</span><span class="dl">'</span> <span class="k">in</span> <span class="nx">props</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Button</span><span class="p">(</span><span class="nx">props</span><span class="p">:</span> <span class="nx">ButtonProps</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">isPropsForAnchorElement</span><span class="p">(</span><span class="nx">props</span><span class="p">))</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="si">{</span><span class="p">...</span><span class="nx">props</span><span class="si">}</span> <span class="p">/&gt;;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="si">{</span><span class="p">...</span><span class="nx">props</span><span class="si">}</span> <span class="p">/&gt;;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이제 Button 컴포넌트는 아래와 같은 두가지 사용 사례를 받아들 일 수 있게 되었다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Button</span> <span class="na">type</span><span class="p">=</span><span class="s">"button"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">clicked!</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>
  클릭이벤트
<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nc">Button</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://google.com"</span> <span class="na">target</span><span class="p">=</span><span class="s">"_blank"</span><span class="p">&gt;</span>
  google 로 이동
<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>조금 더 욕심을 부려 ReactRouter 를 사용할때에 <code class="language-plaintext highlighter-rouge">&lt;NavLink /&gt;</code> 등 다른 컴포넌트를 Button 컴포넌트를 통해 렌더링 할 수 있을까? NavLink 는 자주 쓰이기에 합리적인 가정으로 보인다.</p>

<p><code class="language-plaintext highlighter-rouge">as</code> 를 통해 어떠한 Element 로 렌더링할지 선택할 수 있는 옵션을 추가해보자.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. &lt;button&gt; &amp; &lt;a&gt; 사용 시의 Props 타입</span>
<span class="kd">type</span> <span class="nx">ButtonElementProps</span> <span class="o">=</span> <span class="nx">JSX</span><span class="p">.</span><span class="nx">IntrinsicElements</span><span class="p">[</span><span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">];</span>
<span class="kd">type</span> <span class="nx">AnchorElementProps</span> <span class="o">=</span> <span class="nx">JSX</span><span class="p">.</span><span class="nx">IntrinsicElements</span><span class="p">[</span><span class="dl">'</span><span class="s1">a</span><span class="dl">'</span><span class="p">];</span>

<span class="c1">// 2. as 를 통해 ElementType 를 지정할 경우의 Props 타입</span>
<span class="kd">type</span> <span class="nx">PolymorphicElementProps</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
  <span class="na">as</span><span class="p">:</span> <span class="nx">T</span><span class="p">;</span>
<span class="p">}</span> <span class="o">&amp;</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ComponentPropsWithoutRef</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">ButtonProps</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span> <span class="o">=</span>
  <span class="o">|</span> <span class="nx">ButtonElementProps</span>
  <span class="o">|</span> <span class="nx">AnchorElementProps</span>
  <span class="o">|</span> <span class="nx">PolymorphicElementProps</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">;</span>

<span class="c1">// 3. 렌더링 분기를 위한 is 함수</span>
<span class="kd">function</span> <span class="nx">isPropsForPolymorphicElement</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span><span class="p">(</span>
  <span class="nx">props</span><span class="p">:</span> <span class="nx">ButtonProps</span><span class="o">&lt;</span><span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span>
<span class="p">):</span> <span class="nx">props</span> <span class="k">is</span> <span class="nx">PolymorphicElementProps</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span> <span class="p">{</span>
  <span class="k">return</span> <span class="dl">'</span><span class="s1">as</span><span class="dl">'</span> <span class="k">in</span> <span class="nx">props</span><span class="p">;</span>
<span class="p">}</span>

<span class="kd">function</span> <span class="nx">isPropsForAnchorElement</span><span class="p">(</span>
  <span class="nx">props</span><span class="p">:</span> <span class="nx">ButtonProps</span><span class="o">&lt;</span><span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span>
<span class="p">):</span> <span class="nx">props</span> <span class="k">is</span> <span class="nx">AnchorElementProps</span> <span class="p">{</span>
  <span class="k">return</span> <span class="dl">'</span><span class="s1">href</span><span class="dl">'</span> <span class="k">in</span> <span class="nx">props</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Button</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span><span class="p">(</span>
  <span class="nx">props</span><span class="p">:</span> <span class="nx">ButtonProps</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span>
<span class="p">)</span> <span class="p">{</span>
  <span class="k">if</span> <span class="p">(</span><span class="nx">isPropsForPolymorphicElement</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">(</span><span class="nx">props</span><span class="p">))</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span><span class="k">as</span><span class="p">,</span> <span class="p">...</span><span class="nx">rest</span><span class="p">}</span> <span class="o">=</span> <span class="nx">props</span><span class="p">;</span>
  <span class="kd">const</span> <span class="nx">Element</span><span class="p">:</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span> <span class="o">=</span> <span class="k">as</span><span class="p">;</span>
  <span class="k">return</span> <span class="p">&lt;</span><span class="nc">Element</span> <span class="si">{</span><span class="p">...</span><span class="nx">rest</span><span class="si">}</span> <span class="p">/&gt;;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="nx">isPropsForAnchorElement</span><span class="p">(</span><span class="nx">props</span><span class="p">))</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">a</span> <span class="si">{</span><span class="p">...</span><span class="nx">props</span><span class="si">}</span> <span class="p">/&gt;;</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">&lt;</span><span class="nt">button</span> <span class="si">{</span><span class="p">...</span><span class="nx">props</span><span class="si">}</span> <span class="p">/&gt;;</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">&lt;</span><span class="nc">Button</span> <span class="na">as</span><span class="p">=</span><span class="si">{</span><span class="nx">NavLink</span><span class="si">}</span> <span class="na">to</span><span class="p">=</span><span class="s">"/getting-started"</span><span class="p">&gt;</span>
  시작하려면 이 버튼을 누르세요.
<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nc">Button</span> <span class="na">type</span><span class="p">=</span><span class="s">"button"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">clicked!</span><span class="dl">'</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>
  클릭이벤트
<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nc">Button</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://google.com"</span> <span class="na">target</span><span class="p">=</span><span class="s">"_blank"</span> <span class="na">disabled</span><span class="p">&gt;</span>
  google 로 이동
<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
<span class="p">&lt;</span><span class="nc">Button</span> <span class="na">as</span><span class="p">=</span><span class="s">"div"</span> <span class="na">role</span><span class="p">=</span><span class="s">"button"</span><span class="p">&gt;</span>
  나도 버튼 역할을 한다.
<span class="p">&lt;/</span><span class="nc">Button</span><span class="p">&gt;</span>
</code></pre></div></div>

<p>이제 Button 컴포넌트를 다형적으로 사용할 수 있게 되었다.</p>

<p>다만 Button 컴포넌트에 3가지 사용 유형을 정의하게 되면서 교차 타입을 사용하게되며 주석이 필요할 정도로 내부 구현이 상당히 복잡해졌다.</p>

<p><code class="language-plaintext highlighter-rouge">href</code> 를 넘기면 자동으로 <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> 로 추론되는 편의 기능을 트레이드오프를 한다면 조금 더 내부 구현을 간단명료하게 변경할 수 있다. <code class="language-plaintext highlighter-rouge">&lt;button&gt;</code> 외에 다른 Element 로 렌더링하고 싶다면 무조건 <code class="language-plaintext highlighter-rouge">as</code> 를 필수 값으로 넘기도록 하는것이다.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">defaultElement</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">ButtonProps</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
  <span class="k">as</span><span class="p">?:</span> <span class="nx">T</span><span class="p">;</span>
<span class="p">}</span> <span class="o">&amp;</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ComponentPropsWithoutRef</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Button</span><span class="o">&lt;</span>
  <span class="nx">T</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">defaultElement</span>
<span class="o">&gt;</span><span class="p">({</span><span class="k">as</span><span class="p">,</span> <span class="p">...</span><span class="nx">rest</span><span class="p">}:</span> <span class="nx">ButtonProps</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">Element</span> <span class="o">=</span> <span class="k">as</span> <span class="o">??</span> <span class="nx">defaultElement</span><span class="p">;</span>

  <span class="k">return</span> <span class="p">&lt;</span><span class="nc">Element</span> <span class="si">{</span><span class="p">...</span><span class="nx">rest</span><span class="si">}</span> <span class="p">/&gt;;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>href 사용 시 <code class="language-plaintext highlighter-rouge">&lt;a&gt;</code> 로 추론 되는 기능이 사라져 공짜는 아니지만, 장황한 선언을 제거하니 이제 어느정도 읽을만한 녀석이 되었다. 나는 이 버전이 마음에 든다.</p>

<h2 id="부모에서-ref-전달할-수-있도록-하기">부모에서 ref 전달할 수 있도록 하기</h2>

<p>앱에 진입 시 ‘시작하려면 이 버튼을 누르세요.’ 에 자동으로 포커스를 주고자 한다거나, 다른 여러 이유에 의해서 Button 에 ref 가 필요할 수 있다.</p>

<p>forwardRef 를 통해 ref 를 전달 받을 수 있도록 하자.</p>

<div class="language-tsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">defaultElement</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">button</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">ButtonProps</span><span class="o">&lt;</span><span class="nx">T</span> <span class="kd">extends</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ElementType</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">{</span>
  <span class="k">as</span><span class="p">?:</span> <span class="nx">T</span><span class="p">;</span>
<span class="p">}</span> <span class="o">&amp;</span> <span class="nx">React</span><span class="p">.</span><span class="nx">ComponentPropsWithoutRef</span><span class="o">&lt;</span><span class="nx">T</span><span class="o">&gt;</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">Button</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">forwardRef</span><span class="p">(</span>
  <span class="p">&lt;</span><span class="nc">T</span> <span class="na">extends</span> <span class="na">React</span><span class="err">.</span><span class="na">ElementType</span> <span class="p">=</span> <span class="na">typeof</span> <span class="na">defaultElement</span><span class="p">&gt;</span>(
    <span class="si">{</span><span class="k">as</span><span class="p">,</span> <span class="p">...</span><span class="nx">rest</span><span class="si">}</span>: ButtonProps<span class="p">&lt;</span><span class="nc">T</span><span class="p">&gt;</span>,
    ref: React.Ref<span class="p">&lt;</span><span class="nt">any</span><span class="p">&gt;</span>
  ) =&gt; <span class="si">{</span>
    <span class="kd">const</span> <span class="nx">Element</span> <span class="o">=</span> <span class="k">as</span> <span class="o">??</span> <span class="nx">defaultElement</span><span class="p">;</span>

    <span class="k">return</span> <span class="p">&lt;</span><span class="nc">Element</span> <span class="na">ref</span><span class="p">=</span><span class="si">{</span><span class="nx">ref</span><span class="si">}</span> <span class="si">{</span><span class="p">...</span><span class="nx">rest</span><span class="si">}</span> <span class="p">/&gt;;</span>
  <span class="si">}</span>
);

export default Button;
</code></pre></div></div>

<h2 id="마치며">마치며</h2>

<p>라이브러리 성격의 컴포넌트는 서비스 어플리케이션을 구현할때와 조금 다른 사고가 필요하다고 느꼈다. 특정한 문제를 해결하는 개발을 할때에는 불필요한 인터페이스를 최대한 줄이고 당장 필요하지 않다면 구현하지 않는(YAGNI) 원칙을 따르고자 노력했는데 많은 사용 사례를 가정하는 컴포넌트를 구현하다보니 영 익숙치 않았다.</p>

<p>React 자체에서 제공하는 타입에 대한 이해도가 어느정도 필요하다고 느꼈다. 머릿속의 의도대로 타입을 작성하는것이 쉽지 않아 여러 레퍼런스들을 검색해가며 Button 하나 만드는데에 상당부분 시간을 할애했다.</p>

<h2 id="참고-자료">참고 자료</h2>

<ul>
  <li><a href="https://evan-moon.github.io/2020/11/28/making-your-components-extensible-with-typescript/">evan-moon : 타입스크립트와 함께 컴포넌트를 단계 별로 추상화해보자</a></li>
  <li><a href="https://scottbolinger.com/create-a-polymorphic-component-with-typescript-and-react/">Create a Polymorphic Component with Typescript and React</a></li>
  <li><a href="https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase">typescript-cheatsheets/react : Useful Patterns by Use Case</a></li>
</ul>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="frontend" /><summary type="html"><![CDATA[아토믹 디자인 이란 원자(atom)-분자(molecule)-유기체(organism) 순으로 최소 단위의 atom 컴포넌트를 조합하여 단계별로 나타내는 설계 기준이 있다.]]></summary></entry><entry><title type="html">테스트 코드 작성을 실천하기</title><link href="https://kjkandrea.github.io/develop/test-introduction/" rel="alternate" type="text/html" title="테스트 코드 작성을 실천하기" /><published>2022-12-08T17:30:00+00:00</published><updated>2022-12-08T17:30:00+00:00</updated><id>https://kjkandrea.github.io/develop/test-introduction</id><content type="html" xml:base="https://kjkandrea.github.io/develop/test-introduction/"><![CDATA[<p>프로덕트를 구현할 때 자동화 테스트를 개발 프로세스에 도입해야 한다는 말을 자주듣는다. 
나의 클라이언트 개발에 테스트를 도입하는것은 쉽고 완만한 여정만은 아닌것같다.</p>

<h2 id="테스트-주도-개발">테스트 주도 개발?</h2>

<p>재작년 이맘때 쯔음 작은 어드민 대시보드 성격의 프로덕트를 개발한 적이 있는데 일정 내에 충분히 감당할 수 있겠다고 여겨지는 적당한 규모의 프로젝트 였다.
때문에 나는 그간 들어왔던 TDD 를 개발 중 시도해보았다. 
Jest 와 React Testing Library 를 이용하여 만들고자하는 함수와 컴포넌트 기능 하나하나마다 테스트를 작성하였다.</p>

<p>첫번째로 mocking 개념을 이해하는게 여간 어려운게 아니었다. 테스트를 작성할때에 벡엔드 API 의 응답을 모방하고 외부 라이브러리 훅들을 모방해야 했는데
이 부분을 작성하는데에 시간이 많이 소요되었다.</p>

<p>둘째로 구체적으로 어떠한 테스트를 작성해야하는지가 잘 연상되지 않았다.<br />
데이터가 주입하면 리스트가 렌더링 되는 컴포넌트가 있는데 한 화면에 몇개의 테스트를 보여주는지까지만 테스트해야되는지, 리스트 아이템들의 콘텐츠들을
mock 데이터와 하나하나 비교하는 식으로 테스트를 작성해야하는지, hover 동작에 따라 아이템이 강조되는 동작이 있을 경우 이러한 테스트는 어떻게 작성해야하는지,
작성하는게 맞는지 등 테스트를 작성하며 여러 모호한 고민이 꼬리에 꼬리를 물었다.</p>

<p>결국 떠오르는 테스트를 모두 작성했고, 2주 동안 이렇게 개발을 하며 커맨드를 입력하면 내가 작성한 테스트의 갯수와 green 이 뜨며 테스트가 통과하는걸 동료들에게 
보여주며 뿌듯해 했다. 거기까지 였다. 이후 프로젝트가 진행되며 스팩이 크고 작게 변경됨에 따라 많은 테스트 케이스들이 유효하지 않게 되었다.<br />
테스트를 수정해가며 변경에 대응했으면 좋았겠지만 이때 생산성 측면에서 발목을 크게 잡히는 느낌이였다. 결국 이 시점에서 테스트를 수정하지 않고 
코드를 수정하여 기민하게 대응하는 것을 택했고, 어느정도 시간이 지난 이후 이따금 상한 테스트 코드들을 실행해보면 수십개의 테스트를 실패하는 모습을 보게되었다.</p>

<p>결론적으로 당시 내가 느낀 어려움은 다음과 같았다.</p>

<ol>
  <li>3 만큼의 시간이 있다면 테스트 작성을 하는데 2, 제품을 구현하는데에 1의 시간이 걸렸다. 숙련도 부족으로 테스트에 대한 자신감이 저하되었다.</li>
  <li>내가 작성한 테스트 케이스들이 제품에 도움이 되는 테스트 케이스라는것을 확신할 수 없었다.</li>
  <li>결과적으로 자신감보다는 테스트를 실행하는데에 또 어느 테스트가 망가졌을지 공포감을 느끼게 되었고 테스트를 개발 프로세스에서 배재했다.</li>
</ol>

<p>당시를 회고해보면 나는 테스트 자체가 가져다주는 실용주의적 측면보다는, TDD 라는 트랜디한 키워드 자체에 매료되었던것 같다.</p>

<p>당시를 떠올려보면 적절한 타협안을 찾았더라면 좋았겠다 싶다. 테스트 커버리지의 욕심, TDD 의 프로세스에 매몰되기보다는
‘자동화 테스트를 통해 결과물에 대한 자신감을 얻자’ 는 측면에 집중하며 시도해볼만하다 싶은 부분에는 TDD 를 적용해보고, 숙련도가 부족하다면 구현을 먼저하고 단위 테스트를 작성해보는 식으로 욕심을 덜어냈더라면 결과가 달라졌을 것이다.</p>

<h2 id="e2e-테스트와-cypress">e2e 테스트와 Cypress</h2>

<p>현재는 cypress 라는 도구로 e2e 테스트 를 작성하는 방법을 동료들과 스터디하고 있는데 나는 이 도구가 썩 마음에 든다.
“특정 이벤트가 발생되면 사용자 브라우저에 콘텐츠가 노출되는지” 등을 모킹에 대한 걱정없이 스무스하게 작성할 수 있었고 
이러한 테스트를 검증하는 과정을 브라우저를 통해 실시간으로 확인할 수 있단 점이 시각적으로 나에게 매우 큰 즐거움을 준다.</p>

<p>무엇보다 즐거운 점은 QA 엔지니어들과 공감대를 형성할 수 있고 QA 엔지니어들과 함께 테스트를 작성할 수도 있겠다는 점이다. 
<a href="https://docs.cypress.io/guides/core-concepts/testing-types#What-you-ll-learn">Cypress : Testing Types</a> 
란 아티클을 보면 다음과 같이 e2e테스트와 단위테스트의 장점을 비교해둔 테이블을 확인할 수 있다.</p>

<table>
  <thead>
    <tr>
      <th>종류</th>
      <th>E2E 테스트</th>
      <th>단위 테스트</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>테스트 대상</td>
      <td>앱</td>
      <td>앱의 구성 요소</td>
    </tr>
    <tr>
      <td>특성</td>
      <td>포괄적이고 느리다.</td>
      <td>특정적이고 신속하다.</td>
    </tr>
    <tr>
      <td>검증 주제</td>
      <td>결합된 구성요소들의 작동을 검증</td>
      <td>개별 구성 요소의 테스트</td>
    </tr>
    <tr>
      <td>작성자</td>
      <td>개발자, QA 팀, 테스트 엔지니어</td>
      <td>개발자, 디자이너</td>
    </tr>
    <tr>
      <td>CI Infrastructure</td>
      <td>종종 복잡한 설명이 필요하곤 함</td>
      <td>불필요</td>
    </tr>
  </tbody>
</table>

<p>앱의 기능에 대한 검증이라는 테스트의 본질을 이점으로 가져가기 쉽다.</p>

<p>또하나의 재밌는 점으로 Cypress 는 jQuery 를 이용하여 DOM 에 엑세스 한다는 점이다.
jQuery 는 검증하고자 하는 DOM 에 접근할때에 정말 편하게 접근할 수 있고 선택과 검증을 메소드 체이닝을 통해 표현 할 수 있다.</p>

<p><code class="language-plaintext highlighter-rouge">$.fn</code> 으로 필요한 메소드를 커스텀하여 정의하듯이 <a href="https://docs.cypress.io/api/cypress-api/custom-commands">Custom Commands</a> 를 통해 필요한 메서드를 정의하여 재사용할 수 있다.
다분히 익숙하게 느껴지는 방식이다.</p>

<p>아래는 내가 cypress를 연습하며 작성한 다마고찌 앱의 청소 관련된 기능을 테스트하는 코드인데 테스트가 이러한 장점 덕에 쉽게 읽히게 작성할 수 있단 점이 너무 마음에 든다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Cypress</span><span class="p">.</span><span class="nx">Commands</span><span class="p">.</span><span class="nx">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">cleanPoop</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> 
  <span class="nx">cy</span>
    <span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="dl">'</span><span class="s1">Clean Poop</span><span class="dl">'</span><span class="p">)</span>
    <span class="p">.</span><span class="nx">click</span><span class="p">()</span>
    <span class="p">.</span><span class="nx">root</span><span class="p">()</span>
<span class="p">)</span>

<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">Clean Poop 버튼을 누르면 Poop 이 지워진다.</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">cy</span>
    <span class="p">.</span><span class="nx">cleanPoop</span><span class="p">()</span>
    <span class="p">.</span><span class="nx">hasPooped</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="p">});</span>

<span class="nx">it</span><span class="p">(</span><span class="dl">'</span><span class="s1">생성된 Poop 을 치울 때 마다 Clean Count 가 증가한다.</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">cy</span>
    <span class="p">.</span><span class="nx">cleanPoop</span><span class="p">()</span>
    <span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="dl">'</span><span class="s1">Clean Count : 1</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div></div>

<h2 id="결론">결론</h2>

<p>현재 나는 클라이언트 측의 로직상 중요한 계산을 하는 함수나, 리팩토링을 할때에 before &amp; after 를 보장하기위해 간간히 단위테스트를 작성한다. 
여전히 물흐르듯 단위 테스트를 작성하지는 못한다.</p>

<p>e2e 테스트도 즐겁게 학습을 하는 단계이지 무언가 가시적인 성과는 아직 거두지 못하였다.</p>

<p>조급함에 항상 주의하며 자동화 테스트를 나의 개발 프로세스에서 서서히 스며들게 해보고 싶다. 작은 시도들을 통해 경험치를 천천히 쌓아가며 그를 통해 자신감을 얻자.</p>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="develop" /><summary type="html"><![CDATA[프로덕트를 구현할 때 자동화 테스트를 개발 프로세스에 도입해야 한다는 말을 자주듣는다. 나의 클라이언트 개발에 테스트를 도입하는것은 쉽고 완만한 여정만은 아닌것같다.]]></summary></entry><entry><title type="html">도구와 경계</title><link href="https://kjkandrea.github.io/develop/boundary/" rel="alternate" type="text/html" title="도구와 경계" /><published>2022-07-30T04:30:00+00:00</published><updated>2022-07-30T04:30:00+00:00</updated><id>https://kjkandrea.github.io/develop/boundary</id><content type="html" xml:base="https://kjkandrea.github.io/develop/boundary/"><![CDATA[<p>하나의 프로그램을 구성할때 우리는 여러 도구와 미리 구성된 소스코드들에 의존한다.<br />
도구는 단순한 함수 단위 부터 react 와 같은 UI 라이브러리 까지 다양하다.
잘 만든 도구들은 알기 쉽게 추상화 되어 있으며, 우리는 최저 수준의 고민을 도구에 맡기며 고수준의 과업을 해결해나간다.</p>

<p>내가 이야기하고 싶어하는 도구란 무엇일까? 도구를 무조건적으로 사용하는것을 옳은가?<br />
우선 도구의 기준을 정의해보고 어떻게 도구를 다루어야할 지 살펴보자.</p>

<h2 id="도구는-저수준의-문제를-감춘다">도구는 저수준의 문제를 감춘다.</h2>

<p>내가 생각하는 도구에 부합하는 가장 근본적인 개념 중 하나는 <strong>고차함수</strong> 이다.
자바스크립트의 <code class="language-plaintext highlighter-rouge">Array.prototype.map</code> 을 보자.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">].</span><span class="nx">map</span><span class="p">(</span><span class="nx">num</span> <span class="o">=&gt;</span> <span class="nx">num</span> <span class="o">*</span> <span class="mi">5</span><span class="p">)</span> <span class="c1">// [5, 10, 15, 20, 25]</span>
</code></pre></div></div>

<p>이 아름다운 빌트인 메서드는 다음과 같은 주제를 추상화 한다.<br />
복잡한 개념을 추상화 하고 프로그래머는 ‘어떻게’ 할 것인가를 정의한다.</p>

<ol>
  <li><em>반복을 어떻게 할 것인가?</em> : 배열의 항목 만큼 반복할 것이다.</li>
  <li><em>반복하며 무엇을 할 것인가?</em> : 배열의 각 항목에 <strong>하고자 하는바</strong> 를 대입하여 새로운 값을 리턴한다.</li>
</ol>

<p>이처럼 <code class="language-plaintext highlighter-rouge">Array.prototype.map</code> 의 함의하고 있는 주제는 ‘매핑’ 이다.<br />
프로그래머는 매핑이 어떻게 이루어지는지에 대해는 관심을 가지지 않는다. 단지 <strong>하고자 하는바</strong> 를 함수라는 형태로 도구에 전달하고
도구는 그에 맡게 내가 원하는 바를 수행하여 나에게 돌려준다.<br />
다음으로 좀 더 고수준의 도구를 살펴보자.</p>

<h2 id="도구는-여러-프로그래머에-의해-정의된다">도구는 여러 프로그래머에 의해 정의된다.</h2>

<p>도구는 다른 프로그래머에 의해 정의되고, 사용된다.<br />
A 는 특정 문제를 해결하는 함수나 객체생성자를 만든다. 그리고 같은 팀원인 A, B, C 는 이 도구를 사용하여
같은 주제를 다루는 문제를 쉽게 해결한다.<br />
가령 상품의 소비자가와 판매가에 대한 할인률을 계산하여야 하는 주제가 있다면 다음과 같은 도구를 정의하여 해결한다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nx">getDiscountRateBy</span> <span class="p">(</span><span class="nx">fullPrice</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">salesPrice</span><span class="p">:</span> <span class="kr">number</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">((</span><span class="nx">fullPrice</span> <span class="o">-</span> <span class="nx">salesPrice</span><span class="p">)</span> <span class="o">/</span> <span class="nx">fullPrice</span><span class="p">)</span> <span class="o">*</span> <span class="mi">100</span>
<span class="p">}</span>

<span class="nx">getDiscountRateBy</span><span class="p">(</span><span class="mi">2500</span><span class="p">,</span> <span class="mi">1750</span><span class="p">)</span> <span class="c1">// 30. 30% 할인이 적용된 금액이다.</span>
</code></pre></div></div>

<p>이러한 <code class="language-plaintext highlighter-rouge">getDiscountRateBy</code>를 A, B, C 개발자가 소스코드 전반에 사용한다면, 다음과 같은 현상이 나타난다.</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">getDiscountRateBy</code> 는 소스코드 전반에 결합도를 지니게 된다.</li>
  <li><code class="language-plaintext highlighter-rouge">getDiscountRateBy</code> 의 공식이 재정의 될 경우 소스코드 전반에 영향을 미친다.</li>
  <li>따라서 <code class="language-plaintext highlighter-rouge">getDiscountRateBy</code> 는 우리의 소프트웨어와 <strong>강하게 결합된다.</strong></li>
</ol>

<p>도구를 정의하고 사용하는것은 문제해결에 큰 도움을 준다.<br />
도구를 신뢰하고자 할 경우 테스트코드 등을 작성하여 신뢰도를 높힐 수 있다.<br />
만일 해당 도구의 내부 수식이 잘못되었을 경우에는 해당 도구를 수정하여
실수를 코드 전반을 수정하는 것이 아니라 내부 구현을 수정함으로 해결할 수 있다.</p>

<p>그렇다면 이러한 도구가 가지는 트레이드오프는 없는가?<br />
트레이드오프는 존재한다. 해당 도구와 우리의 소프트웨어가 <strong>결합</strong>되는 것이다.</p>

<p>그럼에도 이러한 ‘공공재’ 도구를 정의하는것은 권장된다.<br />
개개별 리소스를 절약할 수 있으며, 검증된 도구는 소프트웨어 전반의 신뢰도를 향상시킨다.
소프트웨어의 중복을 줄임으로 관리를 용이하게 한다.</p>

<p>다만 도구가 한가지 문제를 해결하는것이 아니라, 여러가지 문제를 해결하고자 하는 도구일 경우 상황이 복잡해진다.</p>

<h2 id="타인이-쥐어준-맥가이버-나이프">타인이 쥐어준 맥가이버 나이프</h2>

<p><img src="/assets/img/swiss-army-knife.jpeg" alt="swiss army knife" /></p>

<p>맥가이버 나이프는 멀티 툴 이다.<br />
하나의 개체로 존재함에도 여러가지 문제를 해결하는데에 도움을 준다.<br />
이러한 도구를 제조하는 관점에서는 이 도구가 유용하게 쓰이고자, 또는 시장에 잘 팔리고자 보다 유용한 기능을 많이 담고자 한다.</p>

<p>사용자의 관점에서는 나에게 당장 필요없는 기능이 몇가지 있더라도, 다른 많은 기능이 필요하다면 기꺼이 이 도구를 구매할 것이다.</p>

<p>소프트웨어의 세계에서는 이러한 맥가이버 나이프가 많다.<br />
라이브러리와 프레임워크가 그 것 이다.</p>

<h3 id="날짜와-시간의-맥가이버-나이프-momentjs">날짜와 시간의 맥가이버 나이프. momentJS</h3>

<p><a href="https://momentjs.com/">momentJS</a> 는 날짜와 시간이라는 개념을 다루는데 특화된 라이브러리 이다.
이 도구는 대략 다음과 같은 기능을 제공한다.</p>

<p>이 도구의 유용함을 들여다보자. 직접 시험하고 싶다면 <a href="https://momentjs.com">https://momentjs.com</a> 에 접속 후 콘솔창에 <code class="language-plaintext highlighter-rouge">moment</code> 를 입력하면 실행가능한 전역 객체를 제공하는 것이 보일 것이다.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">moment</span><span class="p">.</span><span class="nx">locale</span><span class="p">(</span><span class="dl">'</span><span class="s1">ko</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// 사용자의 로케일을 지정한다.</span>

<span class="nx">moment</span><span class="p">().</span><span class="nx">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// 2022-07-31. 현재 날짜를 리턴한다.</span>
<span class="nx">moment</span><span class="p">().</span><span class="nx">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">LTS</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// 1:09:34 PM. 현재의 시간을 리턴한다.</span>

<span class="kd">const</span> <span class="nx">tomorrow</span> <span class="o">=</span> <span class="nx">moment</span><span class="p">().</span><span class="nx">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">days</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// 내일의 시간을 담는다.</span>
<span class="nx">tomorrow</span><span class="p">.</span><span class="nx">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// 2022-08-01. 내일의 날짜를 리턴한다.</span>

<span class="nx">moment</span><span class="p">().</span><span class="nx">subtract</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="dl">'</span><span class="s1">year</span><span class="dl">'</span><span class="p">).</span><span class="nx">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// 100 년 전 날짜를 리턴한다.</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">moment</code> 는 이처럼 날짜와 시간을 다루는데에 있어 유용하다.<br />
세부사항에 대한 이해 없이도 메서드만 이해하면 사용할 수 있도록 적절히 추상화 되어있기 때문에
이 도구를 사용하면 할 일은 내가 필요한 시간대를 입력한 후, 그에 맞는 포멧을 입력하는 것 뿐이다.</p>

<p>이 유용한 도구를 우리의 소프트웨어에 초대하려면 해당 도구를 다운로드 받아 프로젝트에 import 한 다음 필요한 소스 코드 전반에 사용할 수 있다.</p>

<p>하지만 이러한 결정을 하기전에 조금 더 생각해볼 필요가 있다.</p>

<h3 id="맥가이버-나이프는-부식된다">맥가이버 나이프는 부식된다.</h3>

<p>소프트웨어에서의 도구는 현실의 도구와 닮아있다.</p>

<p>오픈소스의 기반 아래 시간이 지날수록 더 가볍고, 더 유용한 기능을 갖춘 도구가 생산된다.<br />
잘 사용하던 도구가 유지보수 종료선언을 하기도 하거나 취약점이 발견되거나, <a href="https://www.bloter.net/newsView/blt201604040002">망가져서 소프트웨어 생태계 전반에 영향을 미치기도 한다</a>.</p>

<p>이러한 상황이 발생하면 우리는 해당 도구를 다른 유용한 도구로 교체하거나,
기존에 도구가 해주던 역할을 우리가 직접 구현하던지 해야할 것이다.</p>

<p>나의 경우 <code class="language-plaintext highlighter-rouge">momentJS</code> 를 한동안 즐겨 사용하였고, 이 도구는 현재 <code class="language-plaintext highlighter-rouge">deprecated</code> 되었다.
구조가 오래되었고, 때문에 번들링 과정에서 트리 쉐이킹을 지원하기 어려우며, 불변성 관련된 문제가 있단것이 그 이유였다.</p>

<p>현재는 <a href="https://www.npmjs.com/package/dayjs">dayJS</a> 라는 다른 대안이 사용되곤 한다.
그럼에도 내가 관리하는 일부 소프트웨어들은 아직 <code class="language-plaintext highlighter-rouge">momentJS</code> 의존성을 지니고 있다.</p>

<p>그 이유는 <code class="language-plaintext highlighter-rouge">momentJS</code> 가 이미 <strong>내가 구성한 프로젝트 전반에 강하게 결합되어 있고, 강하게 결합되어 있기 때문에 moment 와 결별하는데에 지나치게 많은 리소스가 들어가게 되는것</strong> 이 그 이유이다.</p>

<p>그렇다면 어떻게 다루어야 했을까?</p>

<h3 id="대부분-맥가이버-나이프의-모든-기능을-사용하지-않는다">대부분, 맥가이버 나이프의 모든 기능을 사용하지 않는다.</h3>

<p>라이브러리, 프레임워크로 분류되는 도구들은 대부분 유용한 기능을 많이 담고자 하며,
그를 통해 유용하게 사용되고 주제에 부합하는 한 넒은 범위에 대한 해결 능력을 사전에 갖추어 놓고자 한다.</p>

<p>때문에 실제로 사용하지 않는 기능이 많음에도, 유용한 기능이 많기 때문에 이러한 도구들은 손쉽게 프로젝트에 초대되곤 한다.</p>

<p>이러한 도구가 초대되었을 때 프로젝트 전반에 도구의 어떠한 기능이 사용되고, 어떠한 기능은 사용되지 않는지 알 수 있는가?</p>

<p>가령 <code class="language-plaintext highlighter-rouge">momentJS</code> 를 사용할때에 우리의 프로젝트에서 <code class="language-plaintext highlighter-rouge">format</code> 기능만 사용하는지, <code class="language-plaintext highlighter-rouge">subtract</code> 기능은 사용하지 않는 지 알 수 있는 방법은 없는가?</p>

<p>도구가 부식되고, 교체가 필요해졌을 경우 이러한 범위를 신속하게 파악하고 교체할 수 있는가?</p>

<h2 id="도구를-경계하라-경계를-만들어라">도구를 경계하라. 경계를 만들어라.</h2>

<p>신중하게 도구를 사용한다면 상황이 닥쳤을 때 신속하게 이별하고 재정의 할 수 있다.</p>

<p>나의 소프트웨어 계층으로 도구를 입국 시킬때에는 신중하게 비자를 발급하자.<br />
<code class="language-plaintext highlighter-rouge">momentJS</code> 를 <code class="language-plaintext highlighter-rouge">datetime</code> 이란 비자로 랩핑하여 우리의 소프트웨어로 신중히 입국시켜보자.</p>

<h3 id="경계를-처리하는-wrapper-를-만들어라">경계를 처리하는 wrapper 를 만들어라.</h3>

<p><code class="language-plaintext highlighter-rouge">momentJS</code> 를 그대로 사용하지 않는다.
함수나 객체생성자를 이용하여 <code class="language-plaintext highlighter-rouge">momentJS</code> 를 숨기고 <strong>소프트웨어에서 직접적으로 접근하여 사용하지 못하게 하자.</strong></p>

<p>Datetime 을 다루는 객체생성자를 만듬을 통해 특정 소스코드만 moment 의존성을 지니도록 래핑하여보자.</p>

<p>현재 사용이 예상되는 <code class="language-plaintext highlighter-rouge">moment.add</code>, <code class="language-plaintext highlighter-rouge">moment.subtract</code>, <code class="language-plaintext highlighter-rouge">moment.format</code> 기능의 일부를 동일하게 메서드 체이닝이 가능하도록 제공하는것을 목적으로 한다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// wrapper/datetime.ts</span>
<span class="k">import</span> <span class="nx">moment</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">moment</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">DateFormat</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">LTS</span><span class="dl">'</span>
<span class="kd">type</span> <span class="nx">DateUnit</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">day</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">month</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">year</span><span class="dl">'</span>

<span class="kd">class</span> <span class="nx">Datetime</span> <span class="p">{</span>
	<span class="k">private</span> <span class="nx">_date</span><span class="p">:</span> <span class="nb">Date</span><span class="p">;</span>

	<span class="kd">constructor</span><span class="p">(</span><span class="nx">date</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">())</span> <span class="p">{</span>
		<span class="k">this</span><span class="p">.</span><span class="nx">_date</span> <span class="o">=</span> <span class="nx">date</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">static</span> <span class="nx">datetime</span><span class="p">(</span><span class="nx">date</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">())</span> <span class="p">{</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nx">Datetime</span><span class="p">(</span><span class="nx">date</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="k">public</span> <span class="nx">format</span><span class="p">(</span><span class="nx">dateFormat</span><span class="p">:</span> <span class="nx">DateFormat</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">moment</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_date</span><span class="p">).</span><span class="nx">format</span><span class="p">(</span><span class="nx">dateFormat</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="k">public</span> <span class="nx">add</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">unit</span><span class="p">:</span> <span class="nx">DateUnit</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">Datetime</span><span class="p">.</span><span class="nx">datetime</span><span class="p">(</span><span class="nx">moment</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_date</span><span class="p">).</span><span class="nx">add</span><span class="p">(</span><span class="nx">amount</span><span class="p">,</span> <span class="nx">unit</span><span class="p">).</span><span class="nx">toDate</span><span class="p">());</span>
	<span class="p">}</span>

	<span class="k">public</span> <span class="nx">subtract</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">unit</span><span class="p">:</span> <span class="nx">DateUnit</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">Datetime</span><span class="p">.</span><span class="nx">datetime</span><span class="p">(</span><span class="nx">moment</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_date</span><span class="p">).</span><span class="nx">subtract</span><span class="p">(</span><span class="nx">amount</span><span class="p">,</span> <span class="nx">unit</span><span class="p">).</span><span class="nx">toDate</span><span class="p">());</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">datetime</span> <span class="o">=</span> <span class="nx">Datetime</span><span class="p">.</span><span class="nx">datetime</span><span class="p">;</span>
</code></pre></div></div>

<p>완성된 래핑 계층은 위와 같다.</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span><span class="nx">datetime</span><span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./wrappers/datetime</span><span class="dl">"</span><span class="p">;</span>

<span class="nx">datetime</span><span class="p">().</span><span class="nx">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// 2022-07-31</span>
<span class="nx">datetime</span><span class="p">().</span><span class="nx">add</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">year</span><span class="dl">'</span><span class="p">).</span><span class="nx">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// 2023-07-31</span>
<span class="nx">datetime</span><span class="p">().</span><span class="nx">subtract</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="dl">'</span><span class="s1">year</span><span class="dl">'</span><span class="p">).</span><span class="nx">format</span><span class="p">(</span><span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span><span class="p">)</span> <span class="c1">// 2021-07-31</span>
</code></pre></div></div>

<p>사용례는 위와 같다.</p>

<p>moment 는 <code class="language-plaintext highlighter-rouge">moment()</code> 를 통해 메인함수를 호출하였을때 <code class="language-plaintext highlighter-rouge">moment.Moment</code> 라는 여러 메서드가 정의된 인스턴스 리턴하는데 반해,
<code class="language-plaintext highlighter-rouge">datetime()</code> 은 <code class="language-plaintext highlighter-rouge">Datetime</code> 클래스에 정의된 좁은 범위의 인스턴스를 리턴한다.</p>

<p>따라서 소프트웨어 단에서 <code class="language-plaintext highlighter-rouge">datetime</code> 을 사용하는 한 다음과 같은 이점을 지닌다.</p>

<ul>
  <li><strong>통제 가능</strong> : 라이브러리 내에서 어떠한 기능을 사용할지 경계를 둠으로 사용되는 유형을 제한할 수 있다.</li>
  <li><strong>경계의 분리</strong> :  소프트웨어는 <code class="language-plaintext highlighter-rouge">moment</code> 에 직접적으로 의존하지 않는다. <code class="language-plaintext highlighter-rouge">moment</code> 에 직접적으로 의존하는것은 래핑 계층 뿐이다.</li>
</ul>

<p>이러한 간단한 기교를 통해 맥가이버 나이프에서 내가 필요한 기능만 안정적으로 사용할 수 있다.<br />
기성복을 맞춤형 정장으로 수선하는 것이다.</p>

<h3 id="우아하게-의존성을-교체하라">우아하게 의존성을 교체하라</h3>

<p>시간이 흘러 <code class="language-plaintext highlighter-rouge">moment</code> 는 부식되었다. 우리는 트리쉐이킹이 가능한 경량화 라이브러리 <code class="language-plaintext highlighter-rouge">dayjs</code> 로 내부 부품을 교체하기를 원한다.</p>

<p>이 때 우리가 할 일은 <code class="language-plaintext highlighter-rouge">datetime</code> 래핑 계층 내부의 moment 의존성을 dayjs 의존성으로 대체하면 된다.</p>

<p>코드에 변경되는 부분은 moment 라는 선언을 dayjs 로 변경하는 것 뿐이다.</p>

<div class="language-ts highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="nx">dayjs</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">dayjs</span><span class="dl">"</span><span class="p">;</span>

<span class="kd">type</span> <span class="nx">DateFormat</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">YYYY-MM-DD</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">LTS</span><span class="dl">'</span>
<span class="kd">type</span> <span class="nx">DateUnit</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">day</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">month</span><span class="dl">'</span> <span class="o">|</span> <span class="dl">'</span><span class="s1">year</span><span class="dl">'</span>

<span class="kd">class</span> <span class="nx">Datetime</span> <span class="p">{</span>
	<span class="k">private</span> <span class="nx">_date</span><span class="p">:</span> <span class="nb">Date</span><span class="p">;</span>

	<span class="kd">constructor</span><span class="p">(</span><span class="nx">date</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">())</span> <span class="p">{</span>
		<span class="k">this</span><span class="p">.</span><span class="nx">_date</span> <span class="o">=</span> <span class="nx">date</span>
	<span class="p">}</span>

	<span class="k">static</span> <span class="nx">datetime</span><span class="p">(</span><span class="nx">date</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">())</span> <span class="p">{</span>
		<span class="k">return</span> <span class="k">new</span> <span class="nx">Datetime</span><span class="p">(</span><span class="nx">date</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="k">public</span> <span class="nx">format</span><span class="p">(</span><span class="nx">dateFormat</span><span class="p">:</span> <span class="nx">DateFormat</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">dayjs</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_date</span><span class="p">).</span><span class="nx">format</span><span class="p">(</span><span class="nx">dateFormat</span><span class="p">)</span>
	<span class="p">}</span>

	<span class="k">public</span> <span class="nx">add</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">unit</span><span class="p">:</span> <span class="nx">DateUnit</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">Datetime</span><span class="p">.</span><span class="nx">datetime</span><span class="p">(</span><span class="nx">dayjs</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_date</span><span class="p">).</span><span class="nx">add</span><span class="p">(</span><span class="nx">amount</span><span class="p">,</span> <span class="nx">unit</span><span class="p">).</span><span class="nx">toDate</span><span class="p">())</span>
	<span class="p">}</span>

	<span class="k">public</span> <span class="nx">subtract</span><span class="p">(</span><span class="nx">amount</span><span class="p">:</span> <span class="kr">number</span><span class="p">,</span> <span class="nx">unit</span><span class="p">:</span> <span class="nx">DateUnit</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="nx">Datetime</span><span class="p">.</span><span class="nx">datetime</span><span class="p">(</span><span class="nx">dayjs</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_date</span><span class="p">).</span><span class="nx">subtract</span><span class="p">(</span><span class="nx">amount</span><span class="p">,</span> <span class="nx">unit</span><span class="p">).</span><span class="nx">toDate</span><span class="p">())</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">datetime</span> <span class="o">=</span> <span class="nx">Datetime</span><span class="p">.</span><span class="nx">datetime</span><span class="p">;</span>
</code></pre></div></div>

<p>시간이 흘러 <code class="language-plaintext highlighter-rouge">dayjs</code> 가 교체될 날이 온다면 동일한 방법을 적용할 수 있을것이다.</p>

<p>외부 라이브러리에 더 이상 의존하기 싫어진다면 제한된 인터페이스에 맞추어 직접 메서드를 구현할 수도 있을것이다.</p>

<p><a href="https://github.com/kjkandrea/datetime-wrapper">github 레포지토리</a>에 관련 소스코드를 남겨 두었다.</p>

<h2 id="정리">정리</h2>

<p>경계를 처리하는 간단한 계층을 도입함으로 도구와 소프트웨어의 거리감을 유지할 수 있다.</p>

<p>이러한 간단한 wrapper 를 도입하여 소프트웨어를 외부 도구로 부터 보호할 수 있는 라이브러리는 많다.</p>

<p>다만 jQuery 나 React 와 같은 UI 라이브러리 의 경우라면 어떨까?<br />
이와 같은 경계를 만드는것은 도전해 볼만한 주제이나 쉽지 않을것이다.</p>

<p>다만 이러한 라이브러리와 소프트웨어의 코어 비즈니스 로직을 분리함으로 우리의 핵심 구현을 지킬 수 있을것이다.</p>

<p>시간이 된다면 이러한 비즈니스 로직을 라이브러리와 분리하는 주제에 대해 포스팅 해보았으면 좋겠다.</p>

<p>이 아티클은 로버트 C. 마틴 의 <em>클린 코드</em> 의 <em>8장. 경계</em> 를 상당부분 참고하였다.</p>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="develop" /><summary type="html"><![CDATA[하나의 프로그램을 구성할때 우리는 여러 도구와 미리 구성된 소스코드들에 의존한다. 도구는 단순한 함수 단위 부터 react 와 같은 UI 라이브러리 까지 다양하다. 잘 만든 도구들은 알기 쉽게 추상화 되어 있으며, 우리는 최저 수준의 고민을 도구에 맡기며 고수준의 과업을 해결해나간다.]]></summary></entry><entry><title type="html">2021년을 딛고 행복한 사람이 되자</title><link href="https://kjkandrea.github.io/develop/2021-retrospective/" rel="alternate" type="text/html" title="2021년을 딛고 행복한 사람이 되자" /><published>2022-01-22T14:00:00+00:00</published><updated>2022-01-22T14:00:00+00:00</updated><id>https://kjkandrea.github.io/develop/2021-retrospective</id><content type="html" xml:base="https://kjkandrea.github.io/develop/2021-retrospective/"><![CDATA[<p>2021년은 많은 것을 시도해봤고, 많은 것들이 마음대로 되지않는 한해였다.
여러 프로젝트에 참여해보고 업무적으로 여러 사람들과 대면했다.</p>

<p>전반적으로 나 자신에 대한 아쉬움을 많이 느낀 한 해 였는데 무엇이 부족했고, 2022년은 무엇을 개선 할 수 있을지 되돌아보자.</p>

<h2 id="함께-를-항상-생각하자">‘함께’ 를 항상 생각하자</h2>

<p><img src="/assets/img/no-growth.jpeg" alt="no-growth" /></p>

<blockquote>
  <p>“이해가 되지 않습니다. 저 역시 그렇게 하려고 깃의 장점에 대한 발표도 하고 교육도 몇 번에 걸쳐 해줬는데 결국 사람들이 쓰게 하는 데 실패했습니다. 사람들이 너무 수동적이고 보수적이에요”</p>

  <p>“그 조직원들이 선생님을 좋아하나요?”<br />
– <em>함께 자라기 : 애자일로 가는 길</em></p>
</blockquote>

<h3 id="독단에-치우치는-사람이-되지말자">독단에 치우치는 사람이 되지말자</h3>

<p>개발을 하다보면 독단적인 사고에 매몰될 때가 유난히 많은것같다.</p>

<p>개발자는 저마다 중요하다 여기는 것의 차이가 있고, 동일한 기능을 구현하더라도 그에따라 각기 다른 코드가 생산된다.</p>

<p>기능 수정 및 유지보수 비용이 높아질 수 있기에 일관된 자연스레 스타일을 추구하게 된다. 
프로젝트 초기 단계에서 사용할 도구들과 컨벤션에 대해 의논하고, 각 중요 피쳐에 코드리뷰를 하며 서로의 스타일을 맞추어 나간다.</p>

<p>이 과정에서 자연스레 서로의 의견을 공유하게 되고, 여러 의견 중 우리의 방향을 도출해나가야하는 상황이 생겨난다.</p>

<p>이러한 과정은 진리를 추구하는 숭고한 여정이 아닌, 미래의 우리의 시간과 정신을 보살피기 위함이다.</p>

<p>내가 새로 도입하고 싶은 도구나 패턴이 있다면 그것의 필요성을 증명하고 가르치려하기보단,<br />
공감대를 먼저 형성하고 그에 대해 모두가 충분히 생각해볼 수 있는 시간을 마련하자.</p>

<p>잔에 깨끗한 물을 담으려고, 복잡한 파이프라인을 만들어 놓고 여과기를 층층이 설치하면 결국 잔에 한방울의 물도 담기지않고, 그 누구도 물을 담으려 하지 않을 수 있다.</p>

<h3 id="잘못된-피드백은-피드백을-안하니만-못하게-된다">잘못된 피드백은 피드백을 안하니만 못하게 된다.</h3>

<p>내가 실수한것이 있는지, 무엇이 잘못되었고 무엇을 개선할 수 있는 지 가장 빨리 인지할 수 있는것은 나의 결과물을 다른 시각으로 바라봐 줄 수 있는 타인에게 피드백을 받는 것이다.</p>

<p>나는 동료들에게 많은 피드백을 제공하려 노력한다. 개선할 점이 보이면 개선을 종용하고, 결과물에 버그가 예상되는 부분이 있으면 사전에 알려주고자 한다.</p>

<p>다만 이러한 의견을 전달할때에 작년을 돌이켜보니 부족한 부분이 많았던 것 같다. 
다음과 같은 피드백은 그 안에 담긴 내용의 가치여부를 떠나서 공격적으로 보일 수 있다.</p>

<ul>
  <li>특정 주제에 대해 별다른 설명없이 내가 옳다고 생각하는 기술 문서나 아티클을 공유하는 행동</li>
  <li>정답을 제공하지 않는 상호간에 소모적인 의사소통. 이렇게 하면 ‘A’와 같은 문제점이 예상되니 ‘A’가 발생하지 않도록 더욱 고려해주셔야 할것같아요.</li>
  <li>특정 문제에 대해 상대방을 무작정 가르치려는 행동</li>
</ul>

<p>이러한 섣부른 피드백은 문제를 이슈업 할 수는 있겠으나 공격적으로 받아들여 질 수 있고, 서로의 신뢰관계에 독성으로 작용한다.</p>

<p>내가 많은 피드백을 제공한다고, 나도 많은 피드백을 받을 수 있을것이라고 생각하는건 유아적인 생각일 수 있다.
잘못에 대해 가르치려 들지말고, 문제에 대해 생각할 수 있는 계기를 제공하자.</p>

<p>그리하면 자연스레 긴장이 줄어들고, 나도 많은 피드백을 제공받을 수 있을것이다.</p>

<h2 id="인지하는것과-아는것의-차이-책을-통해-개념을-숙달하자">인지하는것과 아는것의 차이. 책을 통해 개념을 숙달하자.</h2>

<p><img src="/assets/img/hurry-up.jpeg" alt="hurry-up" /></p>

<p>2년 전 나에게 가장 필요한것은 구현능력 이었다.</p>

<p>일터에 가니 나에게 생소한 도구들이 있었고, 당장은 쓰지않더라도 이건 모름직이 알아야 한다고 생각되는 도구들이 많았다.</p>

<p>그에 나는 우선 익숙해질 수 있는 도움을 주는 클론코딩 등 강의를 보며 정신없이 따라하며, 무엇이라도 우선 만드는 능력을 얻는것에 집중했다.</p>

<p>조급한 나의 눈에 들어오는 텍스트는 기술 Documentation 이나, 특정 개념에 대해 축약하여 설명하여 주는 기술 아티클 뿐이였다.</p>

<p>그때의 버릇인지 나는 책에서 한동안 멀어져 있었다.</p>

<p>나는 내가 아는것에 대해 설명하는것에 어려움을 느낀다. 최근엔 신규입사자들에게 언어에 대해 교육하는 업무를 맡아볼 기회가 생겼는데, 
개념에 대해 설명하는것에 큰 어려움을 겪었다.</p>

<p>몸으로는 체득하고 있었으나 그것을 타인에게 설명하려니 도무지 입 밖으로 나오지않아 예제 코드와 동작을 보여줌으로서 모면하고자 하였다. 부끄러운 일이다.</p>

<p>책을 읽으며 개념과 용어에 대해 익숙해지고, 알게된 지식을 다른사람에게 설명해주는 연습을 하자.</p>

<p>책은 강의와 달리, 내가 모르는 부분을 표시해두고 이해가 안가는 부분이 있다면 그 부분을 세번이고 네번이고 읽을 수 있다.
지금까지 구현 방법을 키우는 공부를 했다면, 이제 그 방법이 왜 생겨났고 어떻게 동작하는 가에 대한 동작원리를 잘 설명할 수 있는 사람이 되고싶다.</p>

<p>##나 자신이 개선할 것이 많다는것에 즐거워하자</p>

<p><img src="/assets/img/horse-drawing.jpeg" alt="horse-drawing" /></p>

<p>개선할 부분이 많다는것을 나 자신이 더 많이 성장할 수 있는 기회라고 생각하자.</p>

<p>내년에 2022년을 돌아봤을때는 조금은 더 자신에게 떳떳한 사람이 되도록 노력하자.</p>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="develop" /><summary type="html"><![CDATA[2021년은 많은 것을 시도해봤고, 많은 것들이 마음대로 되지않는 한해였다. 여러 프로젝트에 참여해보고 업무적으로 여러 사람들과 대면했다.]]></summary></entry><entry><title type="html">폼 데이터 양방향 바인딩은 항상 필요한가?</title><link href="https://kjkandrea.github.io/frontend/input-value/" rel="alternate" type="text/html" title="폼 데이터 양방향 바인딩은 항상 필요한가?" /><published>2021-12-20T03:00:00+00:00</published><updated>2021-12-20T03:00:00+00:00</updated><id>https://kjkandrea.github.io/frontend/input-value</id><content type="html" xml:base="https://kjkandrea.github.io/frontend/input-value/"><![CDATA[<h1 id="폼-데이터-양방향-바인딩은-항상-필요한가">폼 데이터 양방향 바인딩은 항상 필요한가?</h1>

<p>우리는 유저에게 입력값을 받아 입력된 값을 저장하거나 수정하고자 할때 Form 을 사용한다.</p>

<p>React, Vue 등으로 Form 을 다루고자 할때에는 해당값을 바인딩해서 제어하고 싶어하는 경향이 있다.
프레임워크의 방식대로 사고하다보면 자연스레 <a href="https://ko.reactjs.org/docs/forms.html#controlled-components">해당 값을 상태로서 제어하고 싶어하기 때문</a> 이다.</p>

<p>가령 <code class="language-plaintext highlighter-rouge">nickname</code> 을 입력받는 Form 을  React 로 Controlled 하게 작성해보자.</p>

<h2 id="controlled-form">Controlled Form</h2>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ReactForm</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">nickname</span><span class="p">,</span> <span class="nx">setNickname</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">()</span> <span class="c1">// 1. nickname 의 상태</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">label</span><span class="p">&gt;</span>
        nickname:
        <span class="p">&lt;</span><span class="nt">input</span> 
          <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> 
          <span class="na">name</span><span class="p">=</span><span class="s">"nickname"</span>
          <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">nickname</span><span class="si">}</span>
          <span class="na">onChange</span><span class="p">=</span> <span class="si">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">setNickname</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span> <span class="c1">// 2. change 마다 username 동기화</span>
          <span class="p">/&gt;</span>
      <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span><span class="p">&gt;</span>Join the room<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
      
      <span class="si">{</span><span class="cm">/* 3. username 상태가 동기화 되어 업데이트 된다. */</span><span class="si">}</span>
      <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>nickname is : <span class="si">{</span><span class="nx">nickname</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
  <span class="p">)</span>
<span class="p">}</span>

<span class="nx">ReactDOM</span><span class="p">.</span><span class="nx">render</span><span class="p">(&lt;</span><span class="nc">ReactForm</span> <span class="p">/&gt;,</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">"</span><span class="s2">#app</span><span class="dl">"</span><span class="p">))</span>
</code></pre></div></div>

<ol>
  <li>1번에서 nickname 의 상태를 생성하였고</li>
  <li>2번에서 입력값과 상태의 동기화를 위하여 이벤트를 생성하였다.</li>
  <li>3번은 불필요하나, 상태가 실시간으로 동기화된다는것을 보여주고자 추가하였다.</li>
</ol>

<p><code class="language-plaintext highlighter-rouge">nickname</code> 상태에 입력값이 항상 동기화 되므로 우리는 아래와 같이 submit 이벤트를 처리하여,
유저가 submit 을 발생시켰을 때에 nickname 을 원하는 바 대로 처리할 수 있다.
가령 다음과 같이 말이다.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ReactForm</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">[</span><span class="nx">nickname</span><span class="p">,</span> <span class="nx">setNickname</span><span class="p">]</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useState</span><span class="p">()</span>
  
  <span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
  	<span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">()</span>
  	<span class="nx">alert</span><span class="p">(</span><span class="nx">nickname</span><span class="p">)</span> <span class="c1">// 입력받은 nickname 으로 하고싶은 일을 한다.</span>
  <span class="p">}</span>

  <span class="k">return</span> <span class="p">(</span>
    <span class="c1">// ...</span>
  <span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">handleSubmit</code> 을 통해 입력받은 nickname 값을 원하는 시점에 처리할 수 있다.</p>

<p>이제 반문해보자.</p>

<p><em><code class="language-plaintext highlighter-rouge">onChange</code> 를 왜 사용하였는가?</em><br />
<code class="language-plaintext highlighter-rouge">nickname</code> 과 <code class="language-plaintext highlighter-rouge">input.nickname.value</code> 의 value 를 동기화 하기 위해서이다.</p>

<p><em><code class="language-plaintext highlighter-rouge">input.nickname.value</code>가 변경되는 시점에 <code class="language-plaintext highlighter-rouge">nickname</code> 이 왜 매번 동기화 되어야하는가?</em><br />
<code class="language-plaintext highlighter-rouge">submit</code> 이벤트가 발생하는 시점에 <code class="language-plaintext highlighter-rouge">nickname</code> 이 준비되어 있어야 하기 때문이다.</p>

<p>위와 같은 이유 때문에 양방향 바인딩이 필요하다.</p>

<p>위와 같은 양방향 바인딩은 React 의 세계에서는 자주쓰이나, 다음과 같은 비용을 지불해야 할 수 있다.</p>

<p>가령 입력을 받아야하는 필드가 추가되었고, 그에 따라 <code class="language-plaintext highlighter-rouge">nickname</code> 과 유사한 <code class="language-plaintext highlighter-rouge">phoneNo</code>, <code class="language-plaintext highlighter-rouge">age</code> 등 여러 상태들이 생겨났다고 가정해보자.
Form 하위에 <code class="language-plaintext highlighter-rouge">TextField</code> 등 서브컴포넌트가 생겨났다고 가정해보자.</p>

<p><code class="language-plaintext highlighter-rouge">ReactForm</code> 는 특정 상태가 업데이트 될때에 해당 상태를 양방향 바인딩하기 위해서 리렌더를 시도할것이다.
리렌더가 필요하다면 React 는 diffing check 를 해가며 변경되어야 하는 요소만 DOM 에 업데이트 시킬 것이다.</p>

<p>성능을 챙기고 싶다면 개발자는 이를 도와주기 위해서 <code class="language-plaintext highlighter-rouge">useMemo</code> 등을 이용하여 각 <code class="language-plaintext highlighter-rouge">TextField</code> 가 
해당 <code class="language-plaintext highlighter-rouge">TextField</code> 에 해당하는 상태가 업데이트 될때만 리렌더링 하도록 하는 코드를 작성할 것이다.</p>

<p>위와 같은 작업은 필요한가? Submit 과 같이 사용자가 값을 제출하였을때만 각 필드들의 값이 필요할때에는 필요 없을 수 있다.</p>

<p>우리가 애써 React 의 상태로 관리하려 한 값은 이미 DOM 의  <code class="language-plaintext highlighter-rouge">input.nickname.value</code> 에 실시간으로 바인딩되어있다.</p>

<p>그렇다면 이제는 <code class="language-plaintext highlighter-rouge">uncontrolled</code> 하게 관리해보자.</p>

<h2 id="uncontrolled-form">UnControlled Form</h2>

<p>React 에서 소개하는 <a href="https://ko.reactjs.org/docs/uncontrolled-components.html">비제어 컴포넌트</a> 는 요약해보면 상태를 만들지도 않고 관리하지도 않는것이다.
단지 해당 데이터가 필요할때에 해당 값을 읽어오면 된다.</p>

<p>값이 바인딩된 nickname Form 을 아무런 상태도 없는 순진무구한 상태로 되돌려보자.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ReactForm</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">(</span>
  <span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">label</span><span class="p">&gt;</span>
      nickname:
      <span class="p">&lt;</span><span class="nt">input</span>
        <span class="na">type</span><span class="p">=</span><span class="s">"text"</span>
        <span class="na">name</span><span class="p">=</span><span class="s">"nickname"</span>
      <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span><span class="p">&gt;</span>Join the room<span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
  <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;</span>
<span class="p">)}</span>
</code></pre></div></div>

<p>그리고 useCallback 을 이용하여 submit 이벤트 핸들러를 작성한다.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">ReactForm</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  
<span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useCallback</span><span class="p">(</span><span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">()</span>
  <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">submit</span><span class="dl">'</span><span class="p">)</span>
<span class="p">},</span> <span class="p">[])</span>

<span class="k">return</span> <span class="p">(</span>
  <span class="p">&lt;</span><span class="nt">form</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">handleSubmit</span><span class="si">}</span><span class="p">&gt;</span>
  // ...
  <span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
)}
</code></pre></div></div>

<p>그리고 <code class="language-plaintext highlighter-rouge">handleSubmit</code> 에서 input 에 접근하여 value 를 가져오면 된다.</p>

<p>현재 공식문서에는 <code class="language-plaintext highlighter-rouge">useRef</code> 를 사용하여 <code class="language-plaintext highlighter-rouge">input.nickname.value</code> 를 가져오나,
나는 훌륭한 Web API 인 <a href="https://developer.mozilla.org/ko/docs/Web/API/FormData">FormData</a> 를 이용하여 value 를 가져올 것이다.</p>

<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">getFormData</span> <span class="o">=</span> <span class="nx">formEl</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">formData</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">FormData</span><span class="p">(</span><span class="nx">formEl</span><span class="p">);</span>
  
  <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="p">{};</span>
  <span class="k">for</span> <span class="p">(</span><span class="kd">const</span> <span class="p">[</span><span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">]</span> <span class="k">of</span> <span class="nx">formData</span><span class="p">.</span><span class="nx">entries</span><span class="p">())</span> 
    <span class="nb">Object</span><span class="p">.</span><span class="nx">assign</span><span class="p">(</span><span class="nx">result</span><span class="p">,</span> <span class="p">{</span> <span class="p">[</span><span class="nx">key</span><span class="p">]:</span> <span class="nx">value</span> <span class="p">});</span>
  
  <span class="k">return</span> <span class="nx">result</span><span class="p">;</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">ReactForm</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  
<span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="nx">React</span><span class="p">.</span><span class="nx">useCallback</span><span class="p">(</span><span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="p">{</span> <span class="nx">nickname</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">getFormData</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">currentTarget</span><span class="p">)</span>
  <span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">()</span>
  <span class="nx">alert</span><span class="p">(</span><span class="nx">nickname</span><span class="p">)</span>
<span class="p">},</span> <span class="p">[])</span>

<span class="k">return</span> <span class="p">(</span>
  <span class="p">&lt;</span><span class="nt">form</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">handleSubmit</span><span class="si">}</span><span class="p">&gt;</span>
  // ...
  <span class="p">&lt;</span><span class="nt">form</span><span class="p">&gt;</span>
)}
</code></pre></div></div>

<p>이제 nickname 을 입력 후 submit 을 동작하여보면 alert 에 입력한 nickname 값이 담겨서 출력되는것을 볼 수 있다.</p>

<h2 id="정리">정리</h2>

<p>UnControlled Form 은 상태를 양방향 바인딩 하는 방식에 비해서 성능상의 이점과, 작성의 편의를 제공하나
입력 즉시 validation 을 실행하여 잘못된 입력값이 있음을 알려주고자하는 기능을 구현하고자 할때에는 적절 치 않을 수 있다.</p>

<p>하지만 무작정 모든 값을 프레임워크의 상태 값으로 관리하는것보다는 더 나은 선택 일 수 있다는것을 기억하자.</p>

<ul>
  <li>상태를 양방향 바인딩할때는, 양방향 바인딩이 꼭 필요한 행위인지 반문하여 보자.</li>
  <li>양방향 바인딩이 필요없다면 양방향 바인딩을 하지 않으면 된다.</li>
  <li>UnControlled 하게 input 에 기본 값을 바인딩하고자 할때에는 <code class="language-plaintext highlighter-rouge">defaultValue</code> 를 사용하자.</li>
  <li>복잡한 Form 을 UnControlled 하게 관리하고자 한다면 <a href="https://react-hook-form.com/">react-hook-form</a> 모듈을 활용하는것도 좋은 대안이 될 수 있다.</li>
</ul>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="frontend" /><summary type="html"><![CDATA[폼 데이터 양방향 바인딩은 항상 필요한가? 우리는 유저에게 입력값을 받아 입력된 값을 저장하거나 수정하고자 할때 Form 을 사용한다.]]></summary></entry><entry><title type="html">Synchronous ajax 요청에 대해</title><link href="https://kjkandrea.github.io/frontend/ajax-mystery/" rel="alternate" type="text/html" title="Synchronous ajax 요청에 대해" /><published>2021-07-04T13:00:00+00:00</published><updated>2021-07-04T13:00:00+00:00</updated><id>https://kjkandrea.github.io/frontend/ajax-mystery</id><content type="html" xml:base="https://kjkandrea.github.io/frontend/ajax-mystery/"><![CDATA[<h2 id="asynchronous-call">Asynchronous call</h2>

<p>서버와 클라이언트와 통신은 모두 비동기 요청으로 이루어진다.
서버가 언제 응답을 줄지 알 수 없기 때문이다.</p>

<p>때문에 개발을 할때에서는 서버로 부터 응답이 온 후에(비동기) 다음 로직이 실행될 수 있도록 콜백, 프로미스 등 여러가지 처리를 한다.
가령 다음 코드처럼 말이다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="nx">getData</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="nx">fetchData</span><span class="p">()</span> <span class="c1">// await. 응답이 올때까지 기다려.</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="c1">// 응답이 오면 data를 표시해줘.</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이러한 비동기 요청의 응답을 기다린 이후, 다음 코드를 실행하는 패턴이 나에게는 당연하다고 여겨지는 패턴이다.
이러한 http call 을 처리해주는 jQuery.ajax 에 대해 간단히 살펴보자.</p>

<h2 id="jqueryajax">jQuery.ajax</h2>

<p><a href="https://www.zerocho.com/category/jQuery/post/57b1a48f432b8e586ae4a973">jQuery.ajax : zerocho</a></p>

<h2 id="jqueryajaxasync--false">jQuery.ajax.async = false</h2>

<p>레거시 프로젝트에서 api 요청을 할때에는 jQuery.ajax 를 사용하곤 한다.</p>

<p>이 프로젝트는 fetch, axios 등을 사용하지않고 jQuery.ajax 를 통해 api 요청을 웹핑한다.<br />
사용부에서 이해가 안가는 부분을 발견하였는데 다음과 같다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">getData</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="nx">api</span><span class="p">.</span><span class="nx">fetchData</span><span class="p">()</span> <span class="c1">// 어..? 비동기 처리를 안하네..?</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="c1">// 아니 비동기 처리를 안했는데 어떻게 이 시점에 response 를 받을 수 있지??</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이는 여태까지 써왔던 패턴과 사뭇 다른데, 이러한 패턴이 가능한 이유를 풀어보면 다음과 같았다.<br />
async 란 $.ajax 옵션을 사용하는것이다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">plainSyncCall</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span>  <span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
    <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:3004/posts/1</span><span class="dl">'</span><span class="p">,</span>
    <span class="na">async</span><span class="p">:</span> <span class="kc">false</span> <span class="c1">// 이 녀석이 범인이다.</span>
  <span class="p">})</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">res</span><span class="p">.</span><span class="nx">responseJSON</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<h3 id="이게-어떻게-가능한데">이게 어떻게 가능한데?</h3>

<p><a href="https://developer.mozilla.org/ko/docs/Web/API/XMLHttpRequest/Synchronous_and_Asynchronous_Requests">Synchronous and asynchronous requests : MDN</a></p>

<p><code class="language-plaintext highlighter-rouge">XMLHttpRequest</code> 는 요청을 동기로 처리할지 비동기로 처리할지 정할 수 있는 패터미터가 있<strong>었</strong>다.
세번째 인자가 그것인데 아래와 같이 xhr 을 동기식으로 요청할 수 있다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">request</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">XMLHttpRequest</span><span class="p">();</span>
<span class="nx">request</span><span class="p">.</span><span class="nx">open</span><span class="p">(</span><span class="dl">'</span><span class="s1">GET</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">/bar/foo.txt</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span> <span class="c1">// 세번째 인자 async: false</span>
</code></pre></div></div>

<p>서버의 응답이 올때까지 javascript 의 다음 행을 실행하지 않는것이다.
MDN 에서는 이를 이렇게 표현하고 있다.</p>

<blockquote>
  <p>Synchronous requests block the execution of code which causes “freezing” on the screen and an unresponsive user experience</p>
</blockquote>

<blockquote>
  <p>동기 요청은 코드 실행을 차단하여 화면이 “얼어 붙어” 버리고 응답하지없는 듯한 사용자 경험을 만듭니다.</p>
</blockquote>

<h3 id="이는-안티-패턴인가">이는 안티 패턴인가?</h3>

<p>모든 ajax 요청을 동기적으로 처리한다면 개발자는 더이상 비동기 요청을 처리하는 케이스에 대해서 생각하지 않아도 될지 모른다.</p>

<p>이 방식으로 개발하게 된다면 마치 내가 구현하는 서비스에 비동기적 요청이 일체 없는듯한 착각을 불러일으킬것이다.</p>

<p>허나 이러한 xhr 요청을 하거나, <code class="language-plaintext highlighter-rouge">jQuery.ajax.async = false</code> 를 사용하면 브라우저 콘솔에서 다음과 같은 경고문을 볼 수 있다.</p>

<blockquote>
  <p>[Deprecation] Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help, check https://xhr.spec.whatwg.org/.</p>
</blockquote>

<blockquote>
  <p>[Deprecation] 메인 스레드의 동기 XMLHttpRequest 는 최종 사용자의 경험에 해로운 영향을 미치기 때문에 더 이상 사용되지 않습니다. 자세한 도움말은 https://xhr.spec.whatwg.org/를 확인하세요.</p>
</blockquote>

<p>그럼 왜 동기적 요청 패턴을 사용하면 안되는가??</p>

<p>‘사용하면 브라우저 콘솔에 Deprecation warning 이 뜨니깐요..’ 라는 대답은 근본에 비켜나간 대답인것같고 내가 느끼기엔 조금 멍청해보인다.</p>

<p>그래서 나는 이것에 대해 타당한 근거를 제시하는 여러 아티클을 찾아보았으나, 
안타깝게도 ‘이러이러해서 안티패턴이구나!’ 라고 느껴지는 아티클을 찾지못하였다.</p>

<p>대부분 다음 코드 실행을 의도치않게 중지시킬 수 있기때문에 사용하면 안된다는 내가 느끼기에는 조금 모호해보이는 대답이 부지기수였다.</p>

<h3 id="나의-생각">나의 생각</h3>

<p>다시 처음으로 돌아가서 맨 처음 살펴보았던 async await 패턴을 보자.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="nx">getData</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">api</span><span class="p">.</span><span class="nx">fetchData</span><span class="p">()</span> <span class="c1">// await. 응답이 올때까지 기다려.</span>
  <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span> <span class="c1">// 응답이 오면 data를 표시해줘.</span>
<span class="p">}</span>
</code></pre></div></div>

<p>이 코드의 목적성은 <code class="language-plaintext highlighter-rouge">jQuery.ajax.async = false</code> 나 <code class="language-plaintext highlighter-rouge">Synchronous XMLHttpRequest</code> 와 근본적으로 동일하다.
 응답이 온 이후에 응답값을 표시하기를 바라기 때문이다.</p>

<p>다만 위 동기적 api 콜들과 한가지 다른점은 다음과 같다.</p>

<ol>
  <li>개발자가 이 코드가 비동기 요청이라는것을 명확하게 인지하고 동기적으로 처리하고있디.</li>
  <li>다른 개발자가 이 코드를 보더라도 비동기요청을 동기적으로 처리한다는 것을 명확하게 알 수 있다.</li>
</ol>

<p>만일 jQuery.ajax api 웹핑을 할때 <code class="language-plaintext highlighter-rouge">async = false</code> 를 기본값으로 두고 이 이름을 api 가 아닌 <strong>전혀 비동기임이 연상되지않는 네이밍</strong>으로 지었다고 생각해보자.</p>

<p>가령 웹핑시 이름을 <code class="language-plaintext highlighter-rouge">data</code> 라고 지었다면 다음과 같은 코드가 될것이다.</p>

<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 사용자의 프로필을 서버요청을 통해 받아와 렌더링 하는 코드 </span>

<span class="nx">displayProfile</span><span class="p">()</span> <span class="p">{</span>
  <span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">getId</span><span class="p">()</span> <span class="c1">// 'id: foo'</span>
  <span class="kd">const</span> <span class="nx">profile</span> <span class="o">=</span> <span class="nx">data</span><span class="p">.</span><span class="nx">getProfile</span><span class="p">({</span> <span class="nx">id</span> <span class="p">})</span>
  <span class="k">this</span><span class="p">.</span><span class="nx">renderProfile</span><span class="p">(</span><span class="nx">profile</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">data.getId</code>, <code class="language-plaintext highlighter-rouge">data.getProfile</code> 이 비동기 요청임에도 코드상에 전혀 들어나지 않는다.</p>

<p>의도에 맞게 실행될 것이지만, 데이터를 서버에서 받아오는지, 스토리지에서 가져오는지, 인스턴스에서 가져오는지가 코드상에 전혀 들어나지않는다.</p>

<p>이는 추후 특정 응답에 문제가 있거나, 코드를 해석하고자 할때 큰 혼란을 불러일으킬 것이다.</p>

<p>어느 부분이 비동기이고, 어느 부분에 브라우저가 <code class="language-plaintext highlighter-rouge">await</code> 를 하고있는지를 혼탁하게 만든다.</p>

<p>이러한 이유때문에 목적성은 같더라도 callback, promise, async, await 등으로 비동기 처리를 하는 것이라고 생각한다.</p>

<p>이러한 일반적인 비동기 요청 핸들링은 동기적 코드와 비동기적 코드를 구분지어주며 <strong>브라우저가 기다릴 부분과 기다리지 않아도 될 부분을 명확히 분간하여 개발자에게 인지시켜준다.</strong></p>

<p>때문에 Synchronous XMLHttpRequest 가 안티패턴으로 정의되었고, 이러한 패턴이 지양되어야한다는 의견이 지배적인것이 아닌지 생각해본다.</p>

<h3 id="추가로-알아보고-싶은-주제">추가로 알아보고 싶은 주제</h3>

<p>jQuery.ajax 에서 병렬 요청이 가능한가요?</p>

<p>https://sharepoint.stackexchange.com/questions/263675/how-to-use-promise-all-and-foreach-to-make-ajax-call-per-array-item</p>]]></content><author><name>Karenin</name><email>kjkandrea@gmail.com</email></author><category term="frontend" /><summary type="html"><![CDATA[Asynchronous call]]></summary></entry></feed>