<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>dom_hxrdy.log</title>
        <link>https://velog.io/</link>
        <description>Dom Hardy : 멋쟁이 개발자 되기 인생 프로젝트 진행중</description>
        <lastBuildDate>Thu, 27 Oct 2022 06:47:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>dom_hxrdy.log</title>
            <url>https://images.velog.io/images/dom_hxrdy/profile/b07283fc-8942-404f-819d-8b83d2e2770b/me.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. dom_hxrdy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/dom_hxrdy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[비즈니스 로직 분리 방황기]]></title>
            <link>https://velog.io/@dom_hxrdy/%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81-%EB%B6%84%EB%A6%AC-%EB%B0%A9%ED%99%A9%EA%B8%B0</link>
            <guid>https://velog.io/@dom_hxrdy/%EB%B9%84%EC%A6%88%EB%8B%88%EC%8A%A4-%EB%A1%9C%EC%A7%81-%EB%B6%84%EB%A6%AC-%EB%B0%A9%ED%99%A9%EA%B8%B0</guid>
            <pubDate>Thu, 27 Oct 2022 06:47:11 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<p>SPA 프론트엔드 프로젝트 개발을 하다보면 라우팅 레이어를 만들 수 밖에 없다.
라우터 레이어에서 해주는 역할은 각기 다른 path 에 각기 다른 페이지 컴포넌트를 렌더해주는 역할이다.
그렇다면 각각의 페이지에서 가장 큰 컴포넌트는 <strong>페이지 컴포넌트</strong>일 것이다. </p>
<p>페이지 컴포넌트 하위로 재사용 목적, 혹은 유지보수를 수월하게 하기 위한 목적으로 세부 컴포넌트로 나눈다.
이를 컨테이너 컴포넌트라고 해보자. 중간 사이즈인 컨테이너 컴포넌트 내부에도 세부 컴포넌트들을 포함할 수 있다.
예를 들어, 버튼이나 텍스트 등을 아주 작은 단위의 컴포넌트로 구성한다면 재사용성이 높은 컴포넌트로 개발하여 생산성을 향상할 수 있을 것이다.</p>
<p>이 상황에서 여러 뷰와 상호작용하며 상태를 관리할 것이고 해당 상태의 상당 부분은 서버 상태일 것이다.
프로젝트에서 <em>react-query</em> 라이브러리를 사용하여 서버 상태를 관리해주고 있기 때문에 useQuery 등의 훅을 사용해서 서버 데이터를 가져와서 뷰에 보여준다.
사용자 인터렉션에 의해 서버 데이터의 수정이 필요할 때는 <em>useMutation</em> 훅을 사용해서 서버 상태를 변경해줄 것이다.</p>
<h2 id="문제">문제</h2>
<p>이런 로직들을 비즈니스 로직이라고 하면 이런 로직들을 편하게 관리할 수 있는 방법에 대해 고민하기 시작했다.
프로젝트 초기 페이지 컴포넌트 하위에 컨테이너 컴포넌트를 구성했다. <code>/container</code> 디렉토리 하위에 각각의 컨테이너 컴포넌트를 위치시켰다.</p>
<p>컨테이너 컴포넌트에서는 위와 같은 비즈니스 로직을 포함하고 페이지 컴포넌트에서도 필요하다면 비즈니스 로직을 포함했다.</p>
<p>하지만, 이렇게 구조를 짜고 프로젝트를 진행하면서 문제가 생겼다. 디버그 및 유지보수를 할 때 어느 부분에서 <strong>문제가 발생했는지 추적하는데 비용이 크다</strong>는 것이다.
문제가 발생한 페이지에서 컨테이너 컴포넌트를 찾고 컨테이너 컴포넌트 하위의 하위의 하위의 컴포넌트 안에 있는 비즈니스 로직을 찾아야 한다면 너무 힘든 일이 될 것이라고 생각했다.</p>
<h2 id="해결">해결</h2>
<p>이러한 문제를 해결하기 위해서 <strong>모든 비즈니스 로직을 페이지 컴포넌트에 통합</strong>하고 하위 컴포넌트는 뷰만 책임지도록 했다. 
리액트에서 로직 분리를 쉽게 할 수 있는 커스텀 훅을 사용해서 페이지 안에 코드가 비대해지는 것도 방지할 수 있다.</p>
<p>이렇게 했을 경우 디버그를 하는 과정 또는 리팩토링을 하는 과정에서 하위 컨테이너 어디서 문제가 발생했는지 일일이 찾아볼 필요없이 페이지단에서 문제를 빠르게 찾을 수 있다.</p>
<blockquote>
<p>page-컴포넌트.tsx</p>
</blockquote>
<pre><code class="language-tsx">function ReviewOverViewPage() {
  const navigate = useNavigate();
  const snackbar = useSnackbar();

  const { reviewFormCode = &#39;&#39;, displayMode: displayModeParams = &#39;&#39; } = useParams();
...
  const pageQueries = useReviewOverviewPage(reviewFormCode, displayMode);

  if (!pageQueries) return &lt;&gt;{/* Error Boundary, Suspense Used */}&lt;/&gt;;

  const {
    infiniteScrollContainerRef,
    reviewsLikeStack,
    reviewMutations,
    reviews,
    reviewForm,
    reviewsOptimisticUpdater,
    isReviewsFetching,
    isFormLoading,
    addFetch,
  } = pageQueries;

/* 사용자 인터렉션에 따른 각종 handler들이 전부 여기에 위치하고 아래 뷰 컴포넌트에 prop으로 내려줌 이 */

  return (
    ...
    &lt;Questions.EditButtons
      isVisible={info.isSelf}
      onClickEdit={handleEditAnswer(id)}
      onClickDelete={handleDeleteAnswer(id)}
    &gt;&lt;/Questions.EditButtons&gt;
    ...
  );
}</code></pre>
<p>위와 같이 모든 비즈니스 로직은 페이지 컴포넌트에 커스텀 훅을 활용하여 통합돼있으며 <strong>하위 뷰 컴포넌트에 prop으로 핸들러</strong>를 넘겨준다.
해당 페이지단에서 핸들러를 하위 컴포넌트에 prop으로 전달해줌으로써 어떤 하위 뷰 컴포넌트에서 해당 로직이 사용되는지를 페이지만 봐도 쉽게 찾을 수 있으니 이런 전략으로 프로젝트를 구성했다.</p>
<h2 id="다시-문제">다시 문제</h2>
<h3 id="네트워크-워터폴로-인한-로딩-속도-지연">네트워크 워터폴로 인한 로딩 속도 지연</h3>
<p>이런 전략에서 문제점을 발견했다. 네트워크 워터폴이 발생한다는 것이다.</p>
<p>우리 프로젝트에서는 Suspense 를 페이지단에 적용하여 페이지 전체에 Loading UI를 보여주고 있었다. 
<img width="1164" alt="스크린샷 2022-10-27 오후 3 18 16" src="https://user-images.githubusercontent.com/51396282/198206249-07861bec-520f-4a36-bf0b-7b69116119e2.png"></p>
<p>위 UI 가 Suspense를 활용하여 페이지 이동간 Loading UI 를 보여주는 모습이다.</p>
<p>위와 같이 하나의 Suspense를 사용하고 있는데 그 안에서 다수의 useQuery를 통해 데이터를 불러오면 네트워크 요청이 동시에 처리되지 않고 차례대로 처리되는 문제점이 있다.
자세한 내용은 다음 <a href="https://happysisyphe.tistory.com/54">아티클</a>을 참고하면 좋다.</p>
<img width="587" alt="스크린샷 2022-10-27 오후 3 21 21" src="https://user-images.githubusercontent.com/51396282/198206640-f12781a9-0b76-4641-9b34-a02852f20cf8.png">

<p>하나의 페이지에서 모든 비즈니스 로직을 갖게 됨으로써 발생하는 문제이다. 다수의 useQuery를 실행하면서 위 사진과 같이 network waterfall 이 발생하여 로딩 시간이 길어지는 문제점이 있다.</p>
<h3 id="또-다른-문제는">또 다른 문제는?</h3>
<p>이 문제로 인해서 여러 가지 본질적인 문제점이 있다는 사실을 인지했다.</p>
<p>지금 프로젝트 같이 한 페이지에서 보여주는 정보가 많지 않다면 네트워크 워터폴 문제나 유지보수 등에서 큰 문제가 없겠지만 대량의 정보를 포함하는 UI 라면 이런 구조는 적절치 않다.</p>
<ol>
<li><p><strong>리렌더링 이슈</strong>
페이지에서 모든 서버 상태를 관리하고 해당 상태를 통해 하위 뷰를 보여주고 있는 상태라면 독립적으로 리렌더링 될 수도 있는 뷰 컴포넌트가 있어도 페이지 전체를 리렌더링 하게 된다.</p>
</li>
<li><p><strong>페이지 컴포넌트 비대화</strong>
커스텀훅을 사용해 로직을 분리하는 것도 한계가 있을 것 같다. 모든 비즈니스 로직의 부하를 페이지 컴포넌트에 일임하는 것은 컴포넌트 비대화의 원인이 될 것이다.</p>
</li>
</ol>
<h2 id="해결-1">해결</h2>
<p>관리하는 서버 상태가 컴포넌트별로 독립적이라면 <strong>하위 컴포넌트에 비즈니스 로직을 위임</strong>해서 처리하는 방법이 더 낫다라는 결론을 냈다.
유지보수를 위한 관점에서 시도해본 전략이었지만 여러 방면에서 문제가 발생한 것을 경험했다. 하지만 하위 컴포넌트에 핸들러를 넘겨서 하위 컴포넌트는 뷰를 보여주는 역할에 충실하게
하는 패턴도 나름 유지보수에 상당한 이점을 준다. 하지만 이 전략의 문제는 <strong>단편의 문제를 해결하기 위해서 극단적인 전략을 취했다</strong>는 것이다.</p>
<blockquote>
<p>이 계기로 여러 방면으로 생각하게 되고 다양한 방법을 균형있게 사용해야 겠다는 생각을 한다.</p>
</blockquote>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://happysisyphe.tistory.com/54">혹시 무분별하게 Suspense 를 사용하고 계신가요? (react-query)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Pagination Bar 방황기]]></title>
            <link>https://velog.io/@dom_hxrdy/Pagination-Bar-%EC%9D%98-%EC%8B%A4%ED%8C%A8</link>
            <guid>https://velog.io/@dom_hxrdy/Pagination-Bar-%EC%9D%98-%EC%8B%A4%ED%8C%A8</guid>
            <pubDate>Mon, 17 Oct 2022 11:20:33 GMT</pubDate>
            <description><![CDATA[<h2 id="상황">상황</h2>
<p>MUI 같이 범용적으로 어디서든 사용될 수 있는 컴포넌트를 프로젝트 내부에서 직접 구현해서 사용하고자 했다. <code>PaginationBar</code> 라는 이름의 범용 컴포넌트를 구현했다.</p>
<p>꽤나 확장성있게 구조를 잡았다. 보여질 버튼의 개수를 prop으로 받아서 페이지 버튼이 5개씩 보이게 할 수도 있고 10개씩 보이게 할 수도 있다. 사용처에서 개발자 마음대로 UI 디자인에 맞게 사용하면 되도록 한 것이다.</p>
<p>게다가 해당 페이지에서 몇 개의 item 을 보여줄지도 정할 수 있다. </p>
<pre><code class="language-ts">export interface PaginationBarProps extends HTMLAttributes&lt;HTMLDivElement&gt; {
  visiblePageButtonLength: number;    //몇 개의 페이지 버튼으로 이루어진 바를 만들것인가 결정
  itemCountInPage: number;    //한 페이지 안에서 몇 개의 item을 보여줄 것인지 결정
  totalItemCount: number;    //내부적으로 페이지를 계산하기 위해 item 총 개수를 넘겨줘야함
  focusedPage: number;    //현재 페이지
  onClickPageButton: (pageNumber: number) =&gt; void;    //페이지를 눌렀을 때의 핸들러
}</code></pre>
<p>위 컴포넌트의 prop type 을 보면 이 컴포넌트를 어떻게 사용할지 감이 올 것이다. 실제 이 컴포넌트를 사용한 모습은 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/dom_hxrdy/post/30a69abb-3971-4d9e-81cd-8769a207ba32/image.png" alt=""><img src="https://velog.velcdn.com/images/dom_hxrdy/post/8ea37b1f-20eb-492e-89fb-f55f8ab3659c/image.png" alt=""></p>
<p>물론 <strong>마지막 페이지가 몇 페이지인지 등등의 계산 로직은 컴포넌트 내부로 분리</strong>돼있어서 사용자는 계산에 필요한 prop만 넘겨주면 아주 편리하게 사용할 수 있는 컴포넌트이다.</p>
<h2 id="문제">문제</h2>
<p>우선 이 컴포넌트를 사용하다가 허점을 발견했다. 일단 기본적으로 이 컴포넌트의 버튼을 누르면 현재 url에 query param으로 <code>page=?</code> 을 추가해서 페이지를 리다이렉트하는 방식으로 사용했다.</p>
<blockquote>
<p>이 컴포넌트는 위처럼 url 변경 방식에 의존적이지 않은 컴포넌트로 설계했다. 그래서 page 정보가 url이 아닌 컴포넌트 내부 state로 갖고 있어도 이 컴포넌트를 활용할 수 있도록 구현했다.</p>
</blockquote>
<p>url 변경 방식을 예로 들자면, 3번 페이지 버튼을 누르면 <code>www.helloworld.com/list?page=3</code> 이런 식으로 url이 변경이 되면서 해당 페이지에 있는 item 들을 API 요청을 통해 불러오는 방식이다.</p>
<p>이 정도로만 생각하고 넘어간다면 이 동작 방식이 매우 허술하다는 것을 눈치채지 못하고 버그를 양산했을 것이다.</p>
<p>해당 컴포넌트의 prop 중에 <code>focusedPage</code>가 있다는 것을 알 수 있다. 이 컴포넌트를 사용하는 페이지에서 그럼 해당 prop 을 어떻게 넘겨줄 수 있을까? 당연히 url에 있는 query string 으로 부터 받아와서 넘겨주었다.</p>
<blockquote>
<p>이-컴포넌트를-사용하는-페이지.tsx</p>
</blockquote>
<pre><code class="language-tsx">...
const pageNumberParams = searchParam.get(&#39;page&#39;);
const pageNumber = isNumberString(pageNumberParams) ? Number(pageNumberParams) : 1;
...
return (
...
  &lt;PaginationBar
    className={styles.pagination}
    visiblePageButtonLength={PAGE_OPTION.TEMPLATE_BUTTON_LENGTH}
    itemCountInPage={PAGE_OPTION.TEMPLATE_ITEM_SIZE}
    totalItemCount={numberOfTemplates}
    focusedPage={Number(pageNumber)}
    onClickPageButton={handleClickPagination}
  /&gt;
...
)</code></pre>
<p>그런데 이렇게 사용하는 방식으로 인해 생기는 문제가 있다.</p>
<p>만약에 사용자가 외부에서 url을 직접 조작해서 <code>page=9999</code>와 같이 존재하지 않는 페이지를 불러올 때는 어떻게 대처할 수 있을까?</p>
<p><img src="https://velog.velcdn.com/images/dom_hxrdy/post/e9d4d69e-46dc-40c5-b128-5e2983004035/image.png" alt=""></p>
<p>이런 식의 버그가 발생한다.</p>
<p>추가로, 만약에 페이지에서 item을 삭제할 수 있다고 해보자. 마지막 페이지에서 item을 삭제하다보면 해당 페이지에 표시할 item이 없어질 것이다. 그렇다면 새로 갱신된 마지막 페이지로 이동해야 할 것이다. 그런데 이 컴포넌트의 사용 방식으로는 따로 처리를 해주지 않는다면 해당 페이지에 그대로 머무르면서 보여줄 item이 없는 상태가 될 것이다. 이것도 버그이다.</p>
<h2 id="해결">해결</h2>
<p>이런 문제를 해결하기 위해서는 컴포넌트 내부에서 가장 마지막 페이지보다 더 큰 숫자의 페이지넘버가 들어왔을 때 마지막 페이지를 보여주는 방법으로 해결해줄 수 있다. (위 두 버그 둘 다 해결 가능)</p>
<blockquote>
<p>PaginationBar.tsx</p>
</blockquote>
<pre><code class="language-tsx">...
  const [searchParams, setSearchParams] = useSearchParams();
...
  useEffect(() =&gt; {
    if (focusedPage &gt; totalPageLength) {
      setSearchParams({
        tab: searchParams.get(&#39;tab&#39;) || FILTER.USER_PROFILE_TAB.REVIEWS,
        page: String(totalPageLength),
      });
      window.scrollTo(0, 0);
    }
  }, [focusedPage, totalItemCount]);</code></pre>
<p>이렇게 훅을 추가해줌으로써 페이지가 변경될때마다 현재 요청하는 페이지의 번호가 마지막 페이지보다 크다면 마지막 페이지를 가리킬 수 있도록 <code>setSearchParams</code> 을 실행해서 url을 바로 잡아줄 수 있다.</p>
<h2 id="또-다시-문제">또 다시 문제</h2>
<p>그러나 이렇게 했을 경우 생기는 문제가 또 있다.</p>
<p>우선 가장 먼저 이 컴포넌트에 의존성이 강하게 형성된다. url에서 page 정보를 받아와 사용하는 경우에만 의존하는 예외처리 로직이 된다. 만약에 이 의존성이 문제가 되지 않는다 하더라도 다른 문제가 있다.</p>
<p>또 다른 문제는 <code>setSearchParams</code> 에 있다. 이 컴포넌트를 사용하는 페이지의 url에 page 정보를 제외한 query string 이 여럿 있을 수 있다. 예를 들어, <code>/profile/51396282?tab=reviews&amp;page=7</code> 라고 한다면 이 컴포넌트 내부에서 위 코드 스니펫과 같이 <code>tab: searchParams.get(&#39;tab&#39;) || FILTER.USER_PROFILE_TAB.REVIEWS,</code> 이렇게 지정을 해줘야 한다. 만약에 다른 페이지에서는 &#39;tab&#39; 이 아니라 &#39;sert&#39; 이런 식의 query string이 사용된다면? 그럼 그 부분에 대해서도 또 처리를 해줘야 할 것이다.</p>
<p>실제로 다른 페이지에서 이 컴포넌트를 사용하다가 페이지가 의도치 않게 리다이렉트 되는 버그가 발생했다. 원인을 찾다 보니까 결국엔 범용적인 컴포넌트 내부 로직에 의해서 발생한 버그라는 것을 알게 되었다.</p>
<p>이 컴포넌트의 가장 큰 문제는 범용적인 컴포넌트라고 하기엔 <strong>외부 url에 의존성이 너무 강하다</strong>. 그리고 이 컴포넌트를 사용하기 위해서는 <strong>무조건 query param 을 사용해야 한다고 강제</strong>하고 있다.</p>
<p>추가적인 prop 으로 받아와서 해결할 수 있긴 하지만 외부에 의존성이 강한 실패한 컴포넌트라는 생각이 들었다.</p>
<p>이래서 재사용 가능하고 범용적인 컴포넌트 및 훅을 만들 때는 외부 의존성을 최대한 없애야 한다.</p>
<h2 id="다시-해결">다시 해결</h2>
<p>본질적으로 범용 컴포넌트 내부에서 리액트 라우터를 사용하는 것이 문제였기 때문에 이 부분을 해결해주기 위해서 이 컴포넌트를 사용하는 외부에서 예외 처리 로직을 만들고 해당 함수를 onPageError prop으로 받아와서 해결해주는 방식을 채택했다.</p>
<blockquote>
<p>PaginationBar.tsx</p>
</blockquote>
<pre><code class="language-tsx">export interface PaginationBarProps extends React.HTMLAttributes&lt;HTMLDivElement&gt; {
  visiblePageButtonLength?: number;
  itemCountInPage: number;
  totalItemCount: number;
  focusedPage?: number;
  scrollReset?: boolean;
  onClickPageButton: (pageNumber: number) =&gt; void;
  onPageError?: () =&gt; void;    //새로 추가
}
...
  useEffect(
    function handleError() {
      return () =&gt; {
        if (onPageError) {
          onPageError();    //에러 핸들링 로직이 있는 경우에만 외부 함수 실행
        }
      };
    },
    [focusedPage, onPageError],
  );</code></pre>
<p>이렇게 함으로써 이 컴포넌트의 사용 방식을 외부에 강제하지 않을 수 있게 되고 의존성이 해결되었다. page 정보를 url 에서 받아오든, 컴포넌트 state로 관리하든 전혀 상관이 없다. </p>
<p>다만 없는 페이지 정보를 처리할 로직을 prop으로 받아와서 처리해줄 수 있도록 구조를 개선했다.</p>
<p>이 컴포넌트를 사용하는 외부 페이지에서는 다음과 같이 페이지 버튼을 눌렀을 때 핸들러와 처리하고 싶은 예외 상황 핸들러를 prop으로 넘겨줄 수 있다.</p>
<blockquote>
<p>이-컴포넌트를-사용하는-페이지.tsx</p>
</blockquote>
<pre><code class="language-tsx">// 버튼을 눌렀을 때 현재 query string을 유지한 상태로 url상에서 page만 이동하는 핸들러 (onClickPageButton prop으로 PaginationBar 컴포넌트로 넘겨줌)
  const handleClickPagination = (pageNumber: number, replace = false) =&gt; {
    if (searchQueryString) {
      setSearchParam({ search: searchQueryString, page: String(pageNumber) }, { replace });
    } else {
      setSearchParam({ sort: currentTab, page: String(pageNumber) }, { replace });
    }
  };

// 적절하지 않은 page 정보가 url을 통해 들어왔을 때 가장 마지막 페이지로 url을 이동시켜주는 예외 처리 핸들러(onPageError prop으로 PaginationBar 컴포넌트로 넘겨줌)
  const handlePageError = () =&gt; {
    const totalPageLength = Math.ceil(numberOfTemplates / PAGE_OPTION.TEMPLATE_ITEM_SIZE);
    const redirectReplace = true;

    if (pageNumber &gt; totalPageLength || pageNumber &lt;= 0) {
      handleClickPagination(totalPageLength, redirectReplace);
    }
  };</code></pre>
<h2 id="결론">결론</h2>
<p>프론트엔드 프로그래밍을 할 때 의존성을 잘 생각하고 설계하고 로직을 분리하는 일이 까다로울 때가 있다. </p>
<p>범용적으로 만든 컴포넌트의 경우 의존성이 생겨서 유지보수가 어려워지거나 확장성이 떨어지는 것을 항상 염두에 두고 구조를 짜고 설계를 해야 한다는 생각을 다시 한 번 느끼게 됐다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 리렌더링 시 html element에 애니메이션 적용하기]]></title>
            <link>https://velog.io/@dom_hxrdy/React-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%8B%9C-%ED%83%9C%EA%B7%B8%EC%97%90-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dom_hxrdy/React-%EB%A6%AC%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%8B%9C-%ED%83%9C%EA%B7%B8%EC%97%90-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 10 Jul 2022 10:38:50 GMT</pubDate>
            <description><![CDATA[<h2 id="상황">상황</h2>
<p>사용자 입력폼을 만드는 작업에서 어떤 입력 input 태그에 사용자가 focus를 하면 좌측에 해당 질문의 제목이 애니메이션 효과와 함께 나타나는 UI를 디자인했고 적용하려고 했다.</p>
<p><img src="https://velog.velcdn.com/images/dom_hxrdy/post/bad2b8a9-b115-4cb5-995a-e7a4cdca4f3c/image.png" alt=""></p>
<p>위 그림과 같이 사용자가 현재 입력하고 있는 input 의 제목을 좌측 화면에 크게 표시하는데 이 때 우측의 사용자 입력폼의 focus가 달라질 때 좌측의 제목이 animation 효과를 매번 주고 싶었다.</p>
<p>이를 위해서 <code>currentQuestion</code> 이라는 컴포넌트 내에서 관리하는 state를 정의하고 focus가 바뀔때마다 해당 Input의 제목으로 상태를 업데이트 해주는 식으로 좌측의 제목을 변경해주고 싶었다.</p>
<p>아래 코드에 위 설명한 내용이 코드로 구현돼있다.</p>
<blockquote>
<p>SubmitReviewPage.tsx</p>
</blockquote>
<pre><code class="language-tsx">function SubmitReviewPage() {
  //좌측에 애니메이션과 함께 크게 출력되는 현재 질문의 제목 state
  const [currentQuestion, setCurrentQuestion] = useState&lt;Question&gt;(dummyData.questions[0]);
  ...
  return (
    &lt;&gt;
      &lt;div className={cn(styles.container)}&gt;
        &lt;Logo /&gt;

        // 좌측에 큰 폰트로 애니메이션 효과와 함께 보여질 질문의 제목
        &lt;Text
          className={cn(styles.title)}
          size={40}
          weight=&quot;bold&quot;
        &gt;
          {currentQuestion.questionValue}
        &lt;/Text&gt;
        ...
      &lt;div className={cn(styles.container)}&gt;
        &lt;form onSubmit={onSubmitReviewForm}&gt;
          ...
          {questions.map((question, index) =&gt; (
            //사용자가 입력할 input
            &lt;div className={cn(styles.fieldSetContainer)} key={question.questionId}&gt;
              &lt;FieldSet
                size=&quot;large&quot;
                title={question.questionValue}
                description={question.questionDescription}
              &gt;
                &lt;TextBox
                  value={questions[index].answerValue}
                  onFocus={() =&gt; onUpdateCurrentQuestion(index)}
                  onChange={(e) =&gt; onUpdateAnswer(e.target.value, index)}
                /&gt;
              &lt;/FieldSet&gt;
            &lt;/div&gt;
          ))}
          ...
      &lt;/div&gt;
    &lt;/&gt;
  );
}</code></pre>
<blockquote>
<p>styles.module.scss</p>
</blockquote>
<pre><code class="language-scss">.title {
  width: 80%;
  margin-top: 1.75rem;
  letter-spacing: 0.25rem;
  line-height: 3.5rem;
  word-break: keep-all;

  @include animate(0.5s ease) {
    from {
      opacity: 0;
      filter: blur(1rem);
      transform: scale(1.2) translateX(-50%);
    }

    to {
      opacity: 1;
      filter: blur(0rem);
      transform: scale(1) translateY(0%);
    }
  }
}</code></pre>
<h2 id="문제">문제</h2>
<p>그런데 맨 처음에 화면이 렌더링 될 때는 제목에 애니메이션 효과가 적용이 되었다. 그런데 우측 사용자 입력폼에서 사용자가 다른 질문폼에 focus를 줬을 때 좌측의 <code>currentQuestion</code>으로 관리되는 상태가 업데이트되면서 제목이 리렌더링 되면서 바뀌기는 하지만 애니메이션 효과가 적용되지 않는 버그가 있었다. </p>
<p>이런 버그가 발생하는 이유는 리액트가 재조정과정을 통해서 Virtual DOM을 업데이트할 때 태그 자체가 아니라 같은 태그 안에 Text만 바뀌었다고 인지하고 업데이트를 하기 때문에 태그 자체가 리렌더링 되지 않는 것이다. 그러면 당연히 애니메이션 효과는 태그에 지정돼있기 때문에 발생하지 않는 것이다.</p>
<h2 id="해결">해결</h2>
<p>리액트의 재조정과정에서 개발자가 직접 코드레벨에서 리액트에게 재조정시 리렌더링 할 수 있도록 하는 장치가 있는데 그것이 바로 <code>key</code> 속성이다.</p>
<p>리액트는 <code>key</code>속성을 재조정 과정에서 참고한다. 키 값이 바뀌면 해당 태그가 완전히 바뀌었다고 리액트는 판단하고 태그 자체를 Virtual DOM tree에서 제거하고 다시 생성할 것이다. 그렇게 되면 태그가 다시 렌더링 되기 때문에 애니메이션 효과가 적용이 된다.</p>
<p>위 코드 스니펫을 아래 코드와 같이 수정하면 의도한대로 입력폼의 focus가 이동할 떄 좌측의 큰 질문 제목에 애니메이션 효과가 적용되면서 업데이트가 된다.</p>
<blockquote>
<p>SubmitReviewPage.tsx</p>
</blockquote>
<pre><code class="language-tsx">function SubmitReviewPage() {
  //좌측에 애니메이션과 함께 크게 출력되는 현재 질문의 제목 state
  const [currentQuestion, setCurrentQuestion] = useState&lt;Question&gt;(dummyData.questions[0]);
  ...
  return (
    &lt;&gt;
      &lt;div className={cn(styles.container)}&gt;
        &lt;Logo /&gt;

        // 좌측에 큰 폰트로 애니메이션 효과와 함께 보여질 질문의 제목
        &lt;Text
          key={currentQuestion.questionValue}
          //key값을 업데이트해주는 state로 넣어주면 key값이 바뀔때마다
          //React는 재조정과정에서 해당 컴포넌트(&lt;Text /&gt;)를 다시 렌더링한다
          className={cn(styles.title)}
          size={40}
          weight=&quot;bold&quot;
        &gt;
          {currentQuestion.questionValue}
        &lt;/Text&gt;
        ...
      &lt;div className={cn(styles.container)}&gt;
        &lt;form onSubmit={onSubmitReviewForm}&gt;
          ...
          {questions.map((question, index) =&gt; (
            //사용자가 입력할 input
            &lt;div className={cn(styles.fieldSetContainer)} key={question.questionId}&gt;
              &lt;FieldSet
                size=&quot;large&quot;
                title={question.questionValue}
                description={question.questionDescription}
              &gt;
                &lt;TextBox
                  value={questions[index].answerValue}
                  onFocus={() =&gt; onUpdateCurrentQuestion(index)}
                  onChange={(e) =&gt; onUpdateAnswer(e.target.value, index)}
                /&gt;
              &lt;/FieldSet&gt;
            &lt;/div&gt;
          ))}
          ...
      &lt;/div&gt;
    &lt;/&gt;
  );
}
</code></pre>
<h3 id="참조">참조</h3>
<p><a href="https://stackoverflow.com/questions/63186710/how-to-trigger-a-css-animation-on-every-time-a-react-component-re-renders">stack overflew</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Virtual DOM ]]></title>
            <link>https://velog.io/@dom_hxrdy/Virtual-DOM</link>
            <guid>https://velog.io/@dom_hxrdy/Virtual-DOM</guid>
            <pubDate>Sat, 28 May 2022 15:01:24 GMT</pubDate>
            <description><![CDATA[<h2 id="탄생-배경">탄생 배경</h2>
<p>우선 브라우저 렌더링 과정에 대해서 간략하게 살펴보겠습니다. 이 과정을 알아야 하는 이유는 화면에 변화가 많은 요즘 웹 애플리케이션에서 DOM 조작이 얼마나 번거로운 작업인지를 설명하기 위함입니다.</p>
<h3 id="브라우저-렌더링-과정">브라우저 렌더링 과정</h3>
<h4 id="1-parsing">1. Parsing</h4>
<p>HTML 파일과 CSS 파일을 파싱해서 각각 Tree를 만든다.</p>
<h4 id="2-style">2. Style</h4>
<p>두 Tree를 결합하여 Rendering Tree를 만든다.</p>
<h4 id="3-layout">3. Layout</h4>
<p>Rendering Tree에서 각 노드의 위치와 크기를 계산한다.</p>
<h4 id="4-paint">4. Paint</h4>
<p>계산된 값을 이용해 각 노드를 화면상의 실제 픽셀로 변환하고, 레이어를 만든다.</p>
<h4 id="5-composite">5. Composite</h4>
<p>레이어를 합성하여 실제 화면에 나타낸다.</p>
<p>기존에는 화면의 변화를 렌더링하기 위해서 DOM에 접근하여 DOM을 직접 수정해줬습니다. DOM의 상태에 변화가 생기면 html 을 파싱하는 과정부터 시작해서 렌더트리를 다시 생성하고 모든 요소들의 스타일이 다시 계산되고 실제 화면에 렌더링하는 과정까지 모두를 반복하게 됩니다. </p>
<p>이런 과정은 비용이 많이 들고 SPA와 같은 형태에서 클라이언트에서 화면의 상태 변화가 많은 애플리케이션의 경우에는 성능이 저하되는 문제가 있습니다.</p>
<h2 id="virtual-dom">Virtual DOM</h2>
<p>기존의 방식과는 다르게 DOM을 직접 업데이트해주는 대신에 화면에 변화가 생길 때 변화된 모습의 가상의 DOM 트리를 생성합니다.</p>
<p>Virtual DOM은 실제 DOM node tree 를 복제한 자바스크립트 객체입니다.</p>
<p>Virtual DOM 은 실제 DOM의 가벼운 버전이라고 생각하면 됩니다. 가상돔은 실제 DOM과 같은 속성들을 갖고 있지만 화면에 변화를 직접 줄 수 있는 기능(예, getElementById 등 DOM api)들은 갖고 있지 않습니다. </p>
<p>화면에 보여질 상태에 변화가 생겼을 때 실제 DOM 을 업데이트 하기 이전에 가상의 DOM(Virtual DOM)에 먼저 적용을 시키고 그 최종적인 결과를 실제 DOM으로 전달합니다. 이러한 방식으로 브라우저 내에서 발생하는 연산의 양을 줄임으로써 성능이 개선되는 것입니다. 이러한 특징을 봤을 때 Virtual DOM은 DOM 캐싱, DOM 버퍼링 이라고 볼 수 있습니다.</p>
<p>DOM 조작에서 가장 비용이 많이 드는 작업은 레이아웃 변화, 트리를 재생성하고 렌더링을 새로 일으키는 부분입니다. 예를 들어 여러분이 30개의 노드를 하나 하나 수정하면 30번의 레이아웃 재계산, 30번의 리렌더링을 초래합니다.</p>
<p>그러나 Virtual DOM은 실제 DOM에 적용될 화면의 변화가 발생하면 가상돔 트리에 적용시킵니다. 가상돔은 렌더링을 하지 않기 때문에 실제 DOM 렌더링과 같은 높은 비용의 행동을 하지 않습니다. 가상돔은 변화를 하나로 묶어서 한 번만 DOM에 전달합니다. 이렇게 성능을 향상시키는 것입니다.</p>
<h2 id="react-와-virtual-dom">React 와 Virtual DOM</h2>
<blockquote>
<p>Virtual DOM (VDOM)은 UI의 이상적인 또는 “가상”적인 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 “실제” DOM과 동기화하는 프로그래밍 개념입니다.</p>
<p>-React</p>
</blockquote>
<p>React에서 상태 변화가 발생하여 Virtual DOM에 변화가 생기면 React는 직전의 Virtual DOM과 새로 업데이트된 Virtual DOM의 스냅샷을 비교하게 됩니다. 이 과정을 통해서 가상돔의 전체를 다시 만드는 것이 아니라 변화된 부분만 업데이트를 할 수 있는데 이 과정을 <strong>diffing</strong>이라고 합니다.</p>
<p>이렇게 변화된 가상돔만 활용하여 실제 DOM을 업데이트 해줍니다. 변화된 부분만 업데이트를 해주는 이 방식으로 React는 복잡한 상태변화 상황에서 성능을 개선할 수 있었습니다.</p>
<h3 id="재조정-reconciliation">재조정 (Reconciliation)</h3>
<blockquote>
<p>Virtual DOM (VDOM)은 UI의 이상적인 또는 “가상”적인 표현을 메모리에 저장하고 ReactDOM과 같은 라이브러리에 의해 “실제” DOM과 동기화하는 프로그래밍 개념입니다. 이 과정을 재조정이라고 합니다.</p>
<p>-React</p>
</blockquote>
<p>두 개의 트리를 비교할 때 React는 두 엘리먼트의 루트 엘리먼트부터 비교합니다. </p>
<p>엘리먼트의 타입이 다른 경우(<code>&lt;a&gt;</code>와 <code>&lt;img&gt;</code>, 혹은 서로 다른 컴포넌트) React는 이전 트리를 버리고 완전히 새로운 트리를 구축합니다. 트리를 만들 때 이전 DOM 노드들은 전부 파괴됩니다. 컴포넌트의 경우는 언마운트되면서 삭제됩니다.</p>
<p>만약에 두 변경된 엘리먼트의 타입이 같은 경우(<code>&lt;div&gt;</code>와 <code>&lt;dic&gt;</code>) 동일한 내용은 유지되고 변경된 속성들만 갱신합니다.</p>
<h3 id="key값을-넣어야-하는-이유">key값을 넣어야 하는 이유</h3>
<p>React는 변경된 내용을 DOM에 업데이트하고 자식 노드에 대해서 재귀적으로 처리를 합니다.</p>
<pre><code class="language-html">//before
&lt;ul&gt;
  &lt;li&gt;first&lt;/li&gt;
  &lt;li&gt;second&lt;/li&gt;
&lt;/ul&gt;

//after
&lt;ul&gt;
  &lt;li&gt;first&lt;/li&gt;
  &lt;li&gt;second&lt;/li&gt;
  &lt;li&gt;third&lt;/li&gt;
&lt;/ul&gt;</code></pre>
<p>위 코드와 같은 경우 <code>&lt;ul&gt;</code> 태그의 자식 노드를 처리하는데 first 태그가 변경사항이 없으므로 넘어가고 second 태그도 변경사항이 없으므로 넘어가고 third 가 새로 생겼기 때문에 이 부분을 업데이트할 것입니다.</p>
<p>그런데 다음의 경우를 보면 얘기가 다릅니다.</p>
<pre><code class="language-html">  // before
  &lt;ul&gt;
    &lt;li&gt;Duke&lt;/li&gt;
    &lt;li&gt;Villanova&lt;/li&gt;
  &lt;/ul&gt;

  // after
  &lt;ul&gt;
    &lt;li&gt;Connecticut&lt;/li&gt;
    &lt;li&gt;Duke&lt;/li&gt;
    &lt;li&gt;Villanova&lt;/li&gt;
  &lt;/ul&gt;</code></pre>
<p>이렇게 새로 추가된 노드가 첫 번째 위치로 들어가는 경우 자식노드를 처리할 때 전부 다 새롭게 업데이트되었다고 판단하여 자식노드를 전부 다시 업데이트하게 됩니다.</p>
<p>이런 문제를 방지하기 위해서 <code>key</code> prop을 사용하는 것입니다. 자식들이 key prop을 갖고 있다면 key를 통해 기존 트리와 이후 트리의 자식들이 일치하는지를 확인합니다. </p>
<h3 id="출처">출처</h3>
<p><a href="https://github.com/Matt-Esch/virtual-dom">Virtual DOM github</a>
<a href="https://velopert.com/3236">[번역] 리액트에 대해서 그 누구도 제대로 설명하기 어려운 것 – 왜 Virtual DOM 인가?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Redux action creator의 존재 이유에 대한 사색]]></title>
            <link>https://velog.io/@dom_hxrdy/Redux-action-creator%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%9D%B4%EC%9C%A0%EC%97%90-%EB%8C%80%ED%95%9C-%EC%82%AC%EC%83%89</link>
            <guid>https://velog.io/@dom_hxrdy/Redux-action-creator%EC%9D%98-%EC%A1%B4%EC%9E%AC-%EC%9D%B4%EC%9C%A0%EC%97%90-%EB%8C%80%ED%95%9C-%EC%82%AC%EC%83%89</guid>
            <pubDate>Mon, 16 May 2022 05:27:31 GMT</pubDate>
            <description><![CDATA[<h2 id="redux의-action-creator">Redux의 action creator</h2>
<p>redux 를 프로젝트에 사용하는데 action 이라는 객체를 활용한다.</p>
<p>여기서 action 객체는 필수적으로 <code>type</code> 이라는 키 값을 갖는다.</p>
<p><code>value</code>로 문자열을 갖고 그 문자열에 해당하는 함수를 실행하여 store에 있는 state의 값을 업데이트해주는 방식이다.</p>
<p>그런데 예제 코드 등을 보다보면 action 객체를 <code>dispatch()</code> 함수 인자로 넣어주는데 action creator라는 함수를 이용해서 넣어주는 방식을 많이 사용한다.</p>
<pre><code class="language-js">const actionCreator = () =&gt; (
  {
    type: SOME_TYPE,
    data: {..some data}
  }
)

dispatch(actionCreator());</code></pre>
<p>위와 같은 방식으로 사용되는데 여기서 <code>actionCreator</code> 가 해주는 역할은 단순히 객체를 반환해주는 역할밖에 하지 않는다.</p>
<p>그냥 인자로 객체를 넣어주면 되는데 왜 이런 식으로 사용하는지에 대해서 궁금해지기 시작했다.</p>
<p>redux를 입문하는 단계이고 어떤 특별한 장치가 있거나 더 활용될 수 있는 확장성을 갖고 있진 않을까 고민을 많이 해봤지만 어디에서도 찾아볼 수가 없었다.</p>
<p>그러다가 redux thunk를 이해하기 위해 아티클을 보다가 action creator를 사용하는 원인에 대해서 공감가는 근거를 하나 찾았다.</p>
<p><a href="https://medium.com/fullstack-academy/thunks-in-redux-the-basics-85e538a3fe60">Thunks In Redux: The Basic</a>의 글쓴이의 의견에 따르면 <strong>dispatch함수에 넣어줄때마다 일일이 객체를 선언해서 넣어주면 인간적인 실수를 할 수도 있기 때문에 일관된 포맷을 유지한 객체를 반환해주는 action creator를 사용해서 인간적인 실수를 줄이기 위해</strong>사용한다는 것이다.</p>
<p>생각보다 너무 간단한 이유였지만 충분히 일리있는 근거였고 궁금증이 해소되었다.</p>
<p>여기에 더해서 한 가지 이점이 더 있다. <a href="https://bestalign.github.io/translation/cartoon-guide-to-flux/">Cartoon Guide To Flux</a>의 글쓴이에 따르면 아래와 같이 말하고 있다.</p>
<blockquote>
<p>모든 가능한 액션들을 아는 시스템(action creator)을 가짐으로써 부차적으로 갖는 멋진 효과가 있다. 새로운 개발자가 프로젝트에 들어와서 행동 생성자 파일을 열면 시스템에서 제공하는 API 전체 — 모든 가능한 상태변경 — 를 바로 확인할 수가 있다는 점이다.</p>
</blockquote>
<p>action creator 들을 갖고 있는 파일을 보면 어떤 행동들을 할 수 있는지를 다른 개발자가 쉽게 이해할 수 있다는 것이다. 한 파일에 일목요연하게 갖고 있다면 어떤 action들을 사용해야 할 지 찾아보기 쉬울 것이다.</p>
<h3 id="출처">출처</h3>
<p><a href="https://medium.com/fullstack-academy/thunks-in-redux-the-basics-85e538a3fe60">Thunks In Redux: The Basic</a>
<a href="https://bestalign.github.io/translation/cartoon-guide-to-flux/">Cartoon Guide To Flux</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Context API 와 useMemo warning]]></title>
            <link>https://velog.io/@dom_hxrdy/Context-API-%EC%99%80-useMemo-warning</link>
            <guid>https://velog.io/@dom_hxrdy/Context-API-%EC%99%80-useMemo-warning</guid>
            <pubDate>Sun, 15 May 2022 10:40:56 GMT</pubDate>
            <description><![CDATA[<h2 id="상황">상황</h2>
<p>다양한 컴포넌트로 이루어진 제어컴포넌트에서 prop drilling이 발생하고 있었고 이를 해결하기 위해서 제어 컴포넌트 내에서 context API 를 활용해서 이 문제를 해결하려고 했다.</p>
<p>예를 들어, AddCard 컴포넌트 하위의 CardForm 컴포넌트가 있고 그 하위에 CardNumberInput 컴포넌트가 있고 또 하위에 Input 컴포넌트가 있는 상황을 생각해보자.</p>
<p>input 값으로 사용자의 입력이 들어오면 제어 컴포넌트 중 가장 상위에 있는 AddCard 가 갖고 있는 card state를 업데이트 해줘야 한다.</p>
<p>Context API 를 사용하지 않았을 때는 updateCard 라는 state를 변경해주는 함수를 모든 컴포넌트가 props로 가져야 했다. 실제로 CardForm, CardNumberInput 컴포넌트에서는 prop을 넘겨주기만 할 뿐 사용하지 않기 때문에 불필요한 prop을 갖고 있게 되는 것이다.</p>
<h2 id="문제">문제</h2>
<p>위와 같은 상황에서 제어 컴포넌트 안에서만 공유되는 객체를 관리하기 위해서 Context API를 사용했다.</p>
<p>그런데 현재 Context 가 앱의 최상위 컴포넌트에서 전역으로 사용되는 것이 아니라 앱의 일부인 제어 컴포넌트의 범주 안에서만 공유되는 객체로 사용되고 있다.</p>
<blockquote>
<p>AddCard.jsx</p>
</blockquote>
<pre><code class="language-js">function AddCard() {
  const [card, setCard] = useState(getCard());

  const updateCard = (name, value) =&gt; {
    setCard((prevCard) =&gt; {
      return { ...prevCard, [name]: value };
    });
  };
  ...

  return (
    &lt;AddCardContext.Provider value={card, updateCard}&gt;
      &lt;h2 className=&quot;page-title&quot;&gt;카드 추가&lt;/h2&gt;
      &lt;Card /&gt;
      &lt;AddCardForm /&gt;
    &lt;/AddCardContext.Provider&gt;
  );
}
...</code></pre>
<p>그래서 Context를 선언하고 있는 AddCard 컴포넌트에서 context Provider를 통해서 value를 제공하고 있다. 해당 컴포넌트가 상위의 state 변화에 따라 리렌더링 될 때 위와 같이 value에 객체나 배열 등의 형태로 넘겨주게 되면 컴포넌트가 리렌더링 될 때 매번 새로운 객체를 생성해서 비교 알고리즘에 의해 다른 값이라고 생각해서 context를 구독하는 하위의 컴포넌트가 값이 변경되지 않았음에도 불구하고 다시 렌더링 되는 문제가 생긴다.</p>
<p>그렇기 때문에 useMemo 훅을 사용하라는 warning을 주게 되고 이 문제를 해결하기 위해서 useMemo 훅을 사용했다.</p>
<h2 id="해결">해결</h2>
<p>위 코드에서 useMemo 훅만 추가를 해주면 의미없이 리렌더링되는 부분을 최적화할 수 있다.</p>
<p><code>AddCard</code> 컴포넌트 안에 아래 코드를 추가하고 value 값에 넣어줌으로써 문제를 해결했다.</p>
<pre><code class="language-js">const contextValue = useMemo(() =&gt; ({ card, updateCard }), [card]);

...

  return (
    &lt;AddCardContext.Provider value={contextValue}&gt;
      &lt;Card /&gt;
      &lt;AddCardForm /&gt;
    &lt;/AddCardContext.Provider&gt;
  );</code></pre>
<p>이렇게 하면 값이 변경될때만 하위 컴포넌트들을 리렌더링 하기 때문에 최적화 달성이 가능해진다.</p>
<h3 id="출처">출처</h3>
<p><a href="https://blog.agney.dev/useMemo-inside-context/">useMemo inside context</a>
<a href="https://stackoverflow.com/questions/62230532/is-usememo-required-to-manage-state-via-the-context-api-in-reactjs">StackOverflow - is useMemo required to manage state via the context API in reactjs?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Input 컴포넌트와 Validator의 강한 종속성 해결과정[리팩토링]]]></title>
            <link>https://velog.io/@dom_hxrdy/Input-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-Validator%EC%9D%98-%EA%B0%95%ED%95%9C-%EC%A2%85%EC%86%8D%EC%84%B1-%ED%95%B4%EA%B2%B0%EA%B3%BC%EC%A0%95%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</link>
            <guid>https://velog.io/@dom_hxrdy/Input-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%99%80-Validator%EC%9D%98-%EA%B0%95%ED%95%9C-%EC%A2%85%EC%86%8D%EC%84%B1-%ED%95%B4%EA%B2%B0%EA%B3%BC%EC%A0%95%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81</guid>
            <pubDate>Mon, 09 May 2022 07:46:41 GMT</pubDate>
            <description><![CDATA[<h2 id="문제">문제</h2>
<p>리액트로 <strong>폼을 제출하는 컴포넌트</strong>를 구성해야 하는 상황이었다. <strong>CDD</strong>(Component-Drived Development)를 적용하면서 스토리북에 작은 컴포넌트를 작성하고 점점 큰 컴포넌트를 작성하는 방식으로 개발을 진행했다.</p>
<img src="https://velog.velcdn.com/images/dom_hxrdy/post/35c08544-d1de-4a43-94b5-ef9252c979f0/image.png" width="200px" height="200px">

<p>위와 같은 제출 폼에서 각 input 들을 컴포넌트로 분리해서 재사용할 수 있는 상황이다. 그래서 <code>Input</code> 이라는 컴포넌트를 만들었다. </p>
<p>그런데 제출 폼의 각 입력값들을 보면 카드번호는 4자리 숫자가 들어와야 하고 보안코드는 3자리 숫자가 들어와야 하는 등의 <strong>유효성 검사 로직이 <code>Input</code> 컴포넌트와 결부돼야 한다</strong>는 사실을 알 수 있다.</p>
<h2 id="강한-결속-상태">강한 결속 상태</h2>
<p><code>Input</code> 컴포넌트가 유연하게 validator 로직을 prop으로 받아와서 들어온 로직에 대해서만 실행하면 확장성있고 유연하게 유효성 검사를 할 수 있다는 생각으로 처음에는 <strong>유효성 검사 로직 함수를 갖고 있는 객체</strong>를 props로 넘겨주었다.</p>
<blockquote>
<p>AddCardForm.jsx</p>
</blockquote>
<pre><code class="language-js">function AddCardForm({ updateCard, addCard }) {
    ...
    return (
          ...
        &lt;Input
            length={MAX_LENGTH.CARD_NUMBER}
            value={cardForm.firstCardNumber}
            name=&quot;firstCardNumber&quot;
            updateCardForm={updateCardForm}
            validators={{ isOverMaxLength, isNaN: Number.isNaN }}
        /&gt;
        ...
        &lt;Input
            placeholder=&quot;MM&quot;
            length={MAX_LENGTH.DATE}
            minLength={MIN_LENGTH.MONTH}
            min={RANGE.MONTH_MIN}
            max={RANGE.MONTH_MAX}
            value={cardForm.expireMonth}
            name=&quot;expireMonth&quot;
            updateCardForm={updateCardForm}
            validators={{ isOverMaxLength, isNaN: Number.isNaN, isOutOfRange }}
            validators={{ checkMaxLength, checkIsNaN, checkRange }}
        /&gt;
        ...
    );
    ...
}</code></pre>
<p>위와 같은 형태로 <code>Input</code> 컴포넌트를 사용할 때 해당 <code>Input</code> 값의 유효성 검사 로직을 <code>validators</code> 라는 이름의 prop으로 객체를 넘겨준다. 그렇다면 <code>Input</code> 컴포넌트에서는 해당 로직을 어떻게 사용할 수 있을까?</p>
<blockquote>
<p>Input.jsx</p>
</blockquote>
<pre><code class="language-js">function Input({ ...(생략), validators }) {
  const checkValidation = (event, targetValue) =&gt; {
    if (validators.isNaN &amp;&amp; Number.isNaN(+targetValue)) {
      event.target.value = targetValue.substring(0, value.length - 1);
      throw new Error(ERROR_MESSAGE.NOT_NUMBER);
    }
    if (validators.isOverMaxLength &amp;&amp; validators.isOverMaxLength(targetValue, length)) {
      event.target.value = targetValue.substring(0, length);
      throw new Error(ERROR_MESSAGE.OVER_MAX_LENGTH);
    }
    if (validators.isOutOfRange &amp;&amp; validators.isOutOfRange(min, max, +targetValue)) {
      event.target.value = targetValue.substring(0, targetValue.length - 1);
      throw new Error(ERROR_MESSAGE.INVALID_MONTH_RANGE);
    }
  };
  ...
}</code></pre>
<p><code>Input</code> 컴포넌트에서는 <code>validators</code> 객체를 prop으로 받아오고 <code>checkValidation</code> 함수에서는 <strong>해당 함수가 객체에 있는지 하드코딩으로 확인하고 있으면 검사하고 없으면 넘어가는 형태</strong>이다.</p>
<p>이런 식으로 구현을 했을 때 카드 제출 폼이 아닌 다른 성격의 입력값을 받을 때 또 다른 유효성 검사로직이 생기는데 그러면 그렇게 늘어나는 유효성 검사로직에 따라 <code>Input</code> 컴포넌트에서 늘어난 만큼 그 함수들이 있는지 없는지를 하드코딩으로 체크하면서 유효성을 검사하게 될 것이다.</p>
<p>이렇게 되면 <code>Input</code> 컴포넌트는 validator와 <strong>매우 강한 결속상태</strong>를 가진다고 할 수 있다. 이렇게 하드코딩이 연속되면 <strong>유지보수가 어렵고 버그가 발생하기 쉬운 문제점</strong>이 있다.</p>
<h2 id="해결방안">해결방안</h2>
<p><code>Input</code> 컴포넌트와 Validator의 종속성을 완화하기 위해서는 <code>Input</code> 컴포넌트를 사용하는 측에서는 필요한 유효성 검사 로직들을 똑같이 넘겨주고 <code>Input</code> 컴포넌트에서는 해당 로직이 있는지 없는지를 하드코딩으로 일일이 검사하는 것이 아니라 그냥 <strong>넘어온 모든 validator 들을 전부 실행만 시켜주는 구조</strong>로 바꾸면 된다.</p>
<p>이를 실현하기 위해서는 <strong>각 유효성 검사 함수에 들어올 매개변수와 함수를 한 번에 받아야 한다.</strong></p>
<p>그래서 아래와 같은 Validator 규격을 만들어서 사용했다.</p>
<pre><code class="language-js">export const validator = (validate, ...args) =&gt; {
  return {
    validate: () =&gt; validate(...args),
  };
};</code></pre>
<p>위 함수를 뜯어보면 첫 번째 인자로 들어오는 <code>validate</code>는 <code>checkMaxLength</code>와 같은 유효성 검사 함수들 중 하나이다. 그리고 <code>...args</code>에는 <code>validate</code> 함수에 들어갈 인자들을 받아온다.</p>
<p>이제 <code>Input</code> 컴포넌트를 사용하는 측에서 아래와 같이 사용할 수 있다.</p>
<pre><code class="language-js">    &lt;Input
      size=&quot;w-25&quot;
      type=&quot;password&quot;
      length={MAX_LENGTH.SECURITY_CODE}
      value={value}
      name={name}
      updateCard={updateCard}
      validators={[
        validator(checkMaxLength, value, MAX_LENGTH.SECURITY_CODE),
        validator(checkIsNaN, value),
      ]}
    /&gt;</code></pre>
<p><code>validators</code> 라는 props에 유효성 검사 로직 함수들의 배열을 담아서 <code>Input</code> 컴포넌트에 넘겨준다. 그리고 해당 함수에서 필요한 인자들 또한 사용하는 측에서 넘겨준다. 그러면 해당 인자들이 첫 번째 인자로 들어간 함수의 인자로 들어가서 실행될 것이다.</p>
<p>이제 <code>Input</code> 컴포넌트에서는 props로 받은 <code>validators</code> 를 아래와 같이 실행만 시켜주면 된다.</p>
<pre><code class="language-js">function Input({ ...(생략), validators }) {
    ...
    const checkValidation = () =&gt; {
        validators.forEach((validator) =&gt; {
        validator.validate();
    });
  };
    ...
}</code></pre>
<h2 id="결과">결과</h2>
<p>아래와 같이 코드를 수정함으로써 <code>validators</code>가 어떤 값이 들어오는지 <code>Input</code> 컴포넌트는 일일이 알아야 할 필요가 사라졌다. 어떤 값이 들어오든 해당 <code>Input</code> 컴포넌트가 수행해야할 유효성 검사 로직을 실행만 시킬 뿐이다.</p>
<p><code>Input</code> 컴포넌트를 사용하는 측에서 어떤 유효성 검사를 할 지 정하고 필요한 인자들까지 같이 넘겨주기 때문에 <code>Input</code> 컴포넌트와 validator의 강한 결합상태가 완화되었다.</p>
<p>이와 같이 <strong>독립적인 컴포넌트는 외부의 어떤 상황에서도 독립적으로 유연하고 확장성있는 모습을 갖춰야 한다</strong>는 것을 느끼는 리팩토링 과정이었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[javascript]IntersectionObserver를 활용한 스크롤 lazy loading]]></title>
            <link>https://velog.io/@dom_hxrdy/javascriptIntersectionObserver%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-lazy-loading</link>
            <guid>https://velog.io/@dom_hxrdy/javascriptIntersectionObserver%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-lazy-loading</guid>
            <pubDate>Fri, 11 Mar 2022 02:00:33 GMT</pubDate>
            <description><![CDATA[<h3 id="상황">상황</h3>
<p>프로젝트를 진행하면서 API 콜을 통해 데이터들을 받아오는데 받아온 데이터를 화면에 보여주고 싶다. 그런데 한 번에 모든 데이터를 다 보여주는 것은 성능에 무리가 있다.(10000개를 한 번에? 렉 걸리겠죠) 그래서 있는 개념이 <strong>레이지 로딩</strong>이다.</p>
<blockquote>
<p><strong>레이지 로딩(lazy loading)</strong>: 페이지를 읽어들이는 시점에 중요하지 않은 리소스 로딩을 추 후에 하는 기술</p>
</blockquote>
<p>그래서 내 프로젝트에서는 API콜을 통해 서버에서 받아온 데이터 10개를 화면에 보여주고 스크롤을 내려서 10번째 데이터의 50퍼센트가 사용자 화면에 노출됐을 때 그 다음 10개를 추가로 API콜을 해서 화면에 보여주고 싶었다.</p>
<h3 id="intersectionobserver-api-활용하기">IntersectionObserver API 활용하기</h3>
<p>[intersectionObserver API] (<a href="https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API)%EB%8A%94">https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API)는</a> 위 상황을 해결할 수 있는 API 이다.</p>
<p>간단한 사용방법을 보자면 </p>
<pre><code class="language-js">let observer = new IntersectionObserver(callback, options);</code></pre>
<p>위와 같이 할 수 있다. 객체를 선언하여 <code>observer</code>를 만들어서 어떤 대상(element)을 target으로 정하고 <code>observer.observe(target)</code> 으로 관찰할 수 있다.</p>
<p>해당 target이 사용자 화면에 어느 정도 노출됐을 때 어떠한 행동을 할 수 있도록 하는 것이 기본 골자이다.</p>
<p><strong>options</strong> 는 { root, rootMargin, threshold } 의 객체 형태이다.
여기서 내가 활용한 옵션 프로퍼티는 바로 threshold 이다. 이는 <strong>한계점</strong>이라는 사전적 정의가 있는데 소수점으로 백분율을 표현하면 target element의 몇 퍼센트가 노출됐을 때 콜백을 실행할지를 정할 수 있다.</p>
<p><strong>callback</strong> 에서는 target element가 노출됐을 때 할 행동을 정의한다.</p>
<h3 id="내-프로젝트에서는-어떻게-했을까요">내 프로젝트에서는 어떻게 했을까요</h3>
<pre><code class="language-js">this.requestMoreResult.observe(this.videoList.lastChild);</code></pre>
<blockquote>
<p>우선 <code>this.requestMoreResult</code>는 <code>new Observer(callback, options)</code>로 만든 observer 객체이다. </p>
</blockquote>
<p>10개의 비디오를 처음에 화면에 보여준 후의 위 코드가 추가되는데 10개의 비디오리스트의 마지막 element를 target으로 설정한 코드이다.</p>
<p>이제 저 target element는 observe되는 대상이 됐다. 이제 target이 화면에 보여졌을 때 할 행동에 대해서 알아보자.</p>
<pre><code class="language-js">...
  #handleScrollToLastItem() {
    return new IntersectionObserver(
      async (entries) =&gt; {
        if (entries[0].isIntersecting) {
          this.requestMoreResult.unobserve(this.videoList.lastChild);
          this.#loadSkeleton();    //로딩이미지
          const moreResult = await this.sendLoadMoreRequest();    //API호출
          this.#renderSearchResult(moreResult);    //새로운데이터 화면에 보여줌
        }
      },
      { threshold: 0.5 }    //target이 50% 노출되면 콜백실행
    );
  }
 ...</code></pre>
<blockquote>
<p>위 코드의 return 되는 객체가 this.requestMoreResult 에 대입된다.</p>
</blockquote>
<p>new 생성자 첫 번째 매개변수인 콜백함수를 살펴보면 entries에는 관찰되고 있는 target들의 리스트가 담긴다. 내 경우에는 하나만 observe 했으므로 해당 target이 <code>entries[0]</code>에 담겨있다. 그 속성으로 <code>isIntersecting</code>이 있는데 화면에 노출이 됐는지 여부를 boolean으로 알려준다.</p>
<p>그리고 이 프로젝트의 경우에는 마지막 데이터의 50%가 노출됐을 때 콜백을 실행해야하기 때문에 그 전에 관찰하고 있던 target은 <code>unobserve</code>를 해준다. 그 이후에 로딩이미지를 보여주고 API 호출을 해서 이어지는 다음 데이터들을 받아온다. 그러고 화면에 보여주는 것으로 콜백 함수를 구성한다.</p>
<h4 id="참조-httpsdevelopermozillaorgkodocswebapiintersection_observer_api">참조: <a href="https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API">https://developer.mozilla.org/ko/docs/Web/API/Intersection_Observer_API</a></h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webpack]file-loader 로 html에서 이미지 참조하기]]></title>
            <link>https://velog.io/@dom_hxrdy/webpackfile-loader-%EB%A1%9C-html%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B0%B8%EC%A1%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dom_hxrdy/webpackfile-loader-%EB%A1%9C-html%EC%97%90%EC%84%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%B0%B8%EC%A1%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 02 Mar 2022 06:47:34 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>VanillaJS 로 프로젝트를 진행하는 과정에서 웹팩을 활용해서 이미지를 빌드하는 방법에 대한 게시글입니다. 웹팩 기본 설정에 대한 기초 지식이 있는 상태라고 가정하고 글을 작성한 점 참고바랍니다.</p>
</blockquote>
<h3 id="html-에서-이미지-태그-경로-참조-실패">HTML 에서 이미지 태그 경로 참조 실패</h3>
<p>index.html 에서 <code>&lt;img src=&quot;./images/closer.svg&quot; /&gt;</code> 를 사용했다. 그런데 웹팩을 사용하기 때문에 그냥 이렇게 했을 때는 이미지를 찾을 수가 없다.</p>
<blockquote>
<p>참고: 웹팩을 이용해서 루트에 위치한 dist 라는 폴더에 bundle된 파일들을 위치시켜놨다. 위 images 라는 폴더는 dist 와 같은 깊이에 위치한 폴더이다.
<img src="https://images.velog.io/images/dom_hxrdy/post/0e96d604-5e1d-4e1f-86c5-9726212e4ff6/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.26.04.png" alt="">
이유는 웹팩을 이용하면 <code>bundle.js</code> 파일을 만들어서 이 안에 파일 내용을 전부 모아서 dist 안에 있는 파일들만 빌드하기 때문이다. 그래서 html 에서 위와 같이 dist 폴더 안에서 참조할 수 없는 경로를 설정해주면 찾을 수 없는 것이다.</p>
</blockquote>
<p>그러면 어떻게 해야 할까? 웹팩을 사용할 때는 따로 로더를 설치해서 이미지 파일을 따로 빌드해줘야 한다.</p>
<p>그 때 사용하는 것이 <strong>file-loader</strong> 라는 것이다.</p>
<blockquote>
<p>npm i file-loader -D</p>
</blockquote>
<p><strong>file-loader</strong>를 설치해주고 세팅을 해보자.</p>
<h3 id="webpackconfigjs">webpack.config.js</h3>
<pre><code class="language-js">  ...
  module: {
    rules: [
      ...
      {
        test: /\.svg$/,
        use: [
          {
            loader: &#39;file-loader&#39;,
          },
        ],
      },
    ],
  },
  ...</code></pre>
<p>위와 같이 file-loader 관련한 설정을 추가하면 file-loader를 사용할 수 있게 된다.</p>
<h3 id="파일로더-사용법">파일로더 사용법</h3>
<p>그런데 웹팩 <a href="https://v4.webpack.js.org/loaders/file-loader/">공식문서</a>를 확인해보면 이미지를 사용할 번들될 파일들 중 한 곳에 import 를 하라고 한다.</p>
<p>그런데 나같은 경우는 js 파일 내에서 이미지를 DOM에 추가하는 것이 아닌 html 에서 바로 사용할 것이기 때문에 안 해도 된다고 생각했지만 틀린 생각이었다. js 안에서 import를 해야만 그 파일을 참조할 수 있는 것 같다.</p>
<p>그래서 <code>index.js</code> 파일 안에 import를 이용해서 이미지 파일을 참조해준다.</p>
<blockquote>
<p>index.js</p>
</blockquote>
<pre><code class="language-js">import &quot;./css/reset&quot;;
import &quot;./css/index&quot;;
import &quot;../images/closer.svg&quot;;
import &quot;./js/app&quot;;</code></pre>
<h3 id="이름이-이상하다">이름이 이상하다?</h3>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/5723bf27-f8eb-43dd-b91a-e83171508d11/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.44.41.png" alt="">
위와 같이 설정을 한 상태에서 <code>npm run build</code> 를 하면 dist 폴더 안에 새로운 파일이 생성되는 것을 확인할 수 있다.
그런데 그 파일의 이름이 뭔가 이상하다. 알 수 없는 해시값으로 파일의 이름이 저장되는 것을 확인할 수 있다.
그러면 html 에서 <code>&lt;img src=&quot;&quot; /&gt;</code>이렇게 참조를 해야 하는데 랜덤으로 생성되는 이름을 내가 어떻게 알고 참조할까 싶다. </p>
<blockquote>
<p>참고: <code>&quot;scripts&quot;: {
    &quot;build&quot;: &quot;webpack&quot;
  },</code> 로 스크립트를 미리 만들어놨다.</p>
</blockquote>
<p>그 때는 설정 파일에서 option을 설정해주면 된다. 위 설정 파일을 다음과 같이 수정했다.</p>
<pre><code class="language-js">  ...
  module: {
    rules: [
      ...
      {
        test: /\.svg$/,
        use: [
          {
            loader: &#39;file-loader&#39;,
            options: {
              name: &#39;[name].[ext]&#39;
            }
          },
        ],
      },
    ],
  },
  ...</code></pre>
<p>options 에 <strong>name</strong> 을 추가해줬다. 이 옵션을 주면 알 수 없는 해시값으로 파일의 이름이 저장되는 것이 아니라 원래 파일의 이름대로 저장이 된다. 이렇게 설정을 바꿔준 후에 다시 빌드를 하면 이제는 dist 폴더 안에 원래 있던 이미지 파일의 이름 그대로 <code>closer.svg</code>라고 파일이 생성된다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/e9680b27-d98d-4583-825a-9552b9b797d8/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202022-03-02%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%203.45.48.png" alt=""></p>
<h3 id="결론">결론</h3>
<p>마지막으로 이제 다시 <code>index.html</code> 파일로 돌아와서 <code>&lt;img src=&quot;./closer.svg&quot; /&gt;</code> 라고 참조를 하면 빌드를 했을 때 정상적으로 브라우저 화면에 이미지가 뜨는 것을 확인할 수 있다.</p>
<p><code>./closer.svg</code> 라고 경로를 해준 이유는 dist 폴더 안에 <code>index.html</code> 파일이 위치해 있기 때문에 그 파일의 현재위치를 기준으로 참조할 이미지파일의 위치를 찾아야하기 때문이다. 같은 폴더 안에 위치하기 때문에 현재 디렉토리 안에 <code>closer.svg</code>를 찾아서 참조할 수 있는 것이다.</p>
<h4 id="참조">참조:</h4>
<p><a href="https://stackoverflow.com/questions/37671342/how-to-load-image-files-with-webpack-file-loader">https://stackoverflow.com/questions/37671342/how-to-load-image-files-with-webpack-file-loader</a></p>
<p><a href="https://develoger.kr/frontend/webpack-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EA%B2%BD%EB%A1%9C-%EC%B2%98%EB%A6%AC/">https://develoger.kr/frontend/webpack-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EA%B2%BD%EB%A1%9C-%EC%B2%98%EB%A6%AC/</a></p>
<p><a href="https://v4.webpack.js.org/loaders/file-loader/">https://v4.webpack.js.org/loaders/file-loader/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스]프리코스 3주차 학습 내용 및 회고]]></title>
            <link>https://velog.io/@dom_hxrdy/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%EB%82%B4%EC%9A%A9-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@dom_hxrdy/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-3%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%EB%82%B4%EC%9A%A9-%EB%B0%8F-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 14 Dec 2021 08:16:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-자판기">1. 자판기</h2>
<p>우아한테크코스 4기의 프리코스 3주차 미션은 자판기 구현이다. 구현 내용은 <a href="https://github.com/DomMorello/javascript-vendingmachine-precourse/tree/dom">프리코스 3주차 미션 저장소</a>에 업로드했다.</p>
<p>3주차 미션을 진행하면서 고민한 내용들에 대해 개인적으로 공부하고 정리해보았다.</p>
<br>

<h2 id="2-구현-중에-했던-고민들">2. 구현 중에 했던 고민들</h2>
<hr>
<h3 id="2-1-클래스가-서로-어떻게-상호작용할-것인가">2-1. 클래스가 서로 어떻게 상호작용할 것인가?</h3>
<p><strong>여러 개의 클래스를 분리한 후 서로 관계를 맺어 하나의 프로그램을 완성</strong>하는 것이 3주차 미션의 목표이다. 클래스를 어떻게 <strong>객체지향</strong>적으로 분리하고 어떻게 서로 관계를 맺을지를 고민해서 적용했다. 기본적으로 모든 프리코스 과제와 같이 <strong>MVC 패턴</strong>을 유지하려고 노력했다.</p>
<p>이번 주차를 구현할 때는 지난 2주 동안의 미션과는 조금 다른 방향으로 구조를 잡았다. 디렉토리 구조를 MVC 패턴에 맞추기 위해서 view, input, game 이런 식으로 나눠서 진행했었는데 이 방법에 대한 확신이 없었기 때문에 다른 방식으로 시도를 해봤다.</p>
<p>가장 우선시 했던 내용은 가장 <strong>객체가 실제를 담도록 노력</strong>했다.</p>
<ol>
<li>자판기 객체: 실제 자판기가 가질 수 있는 정보와 행동을 변수와 메서드로 구현.</li>
<li>동전 객체: 동전이 하는 행동과 동전 개수 정보를 담고 있음.</li>
<li>제품 객체: 자판기 객체 안에 추가되는 객체.</li>
<li>유효성 검사 클래스: static 함수로만 구성돼있음.</li>
</ol>
<p>위의 클래스와 객체들은 각각의 역할이 분명하고 행동과 정보를 전부 적절하게 담도록 했다. 그런데 DOM 노드를 만들어서 초기화하고 이벤트리스너를 어디서 등록하고 해야 할 지는 고민이 많았다.</p>
<p>그래서 전체적으로 프로그램의 구조는 다음과 같다.</p>
<blockquote>
<p>뷰 초기화 작업 -&gt; 각 뷰에 맞는 이벤트리스너 등록 -&gt; 이벤트 발생시 객체들의 상호작용</p>
</blockquote>
<p>이렇게 구조를 만듦으로써 다음 2-2. 의 목적을 이룰 수 있었다.</p>
<h3 id="2-2-비즈니스-로직과-ui-를-분리하라">2-2. 비즈니스 로직과 UI 를 분리하라</h3>
<p>1,2주차 과제를 진행하면서 <strong>MVC 패턴</strong>을 유지하려고 노력했다. 특히 디렉토리 구조를 명확하게 나눠서 input, view 디렉토리를 두고 UI를 모두 view 디렉토리에 넣고 진행했다. 그런데 2주차 피드백을 보고 이렇게 하는 것이 답은 아니라는 것을 느꼈다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/0d59d94d-d657-4552-88d5-70fa1a8c60d0/feedback.png" alt=""></p>
<p>피드백 내용을 살펴보면 위 사진과 같다. 내가 지금까지 했던 방식과는 다르게 <code>Car</code> 클래스 내부에서 UI 로직을 갖고 있는 것을 볼 수 있다. 이 피드백을 받고 지금까지 했던 방식과는 다르게 해보려고 생각했다. 우선 <code>Car</code>와 밀접하게 관련된 UI 를 보여주는 함수가 <code>Car</code> 클래스 내부에 선언돼 있는 것이 적절하게 보인다. 내가 기존에 했던 방식으로 했을 때는 <strong>View 클래스에 서로 관련 없는 UI 로직이 다 뭉쳐져 있었다</strong>. 이런 부분은 확실히 그 해당 클래스에 역할이 무엇인지를 명확하게 밝힐 수 없는 구조인 것 같다.</p>
<p>그래서 <strong>VendineMachine, Coins, Product</strong> 객체들 각각이 해당 객체의 정보를 갖고 뷰를 렌더할 수 있도록 했다.</p>
<blockquote>
<p>Product.js</p>
</blockquote>
<pre><code class="language-js">...
export default class Product {
  ...
    addedTemplate() {
    return `
      &lt;tr class=&quot;${ADDED_PRODUCT_CLASS}&quot;align=&quot;center&quot; bgcolor=&quot;white&quot; height=&quot;40&quot;&gt;
        &lt;td class=&quot;${PRODUCT_MANAGE_NAME_CLASS}&quot; align=&quot;center&quot; width=&quot;160&quot;&gt;${this.name}&lt;/td&gt;
        &lt;td class=&quot;${PRODUCT_MANAGE_PRICE_CLASS}&quot; align=&quot;center&quot; width=&quot;100&quot;&gt;${this.price}&lt;/td&gt;
        &lt;td class=&quot;${PRODUCT_MANAGE_QUANTITY_CLASS}&quot; align=&quot;center&quot; width=&quot;100&quot;&gt;${this.quantity}&lt;/td&gt;
      &lt;/tr&gt;
    `;
  }

  render(dom) {
    dom.insertAdjacentHTML(&#39;beforeend&#39;, this.addedTemplate());
  }
  ...
}</code></pre>
<p>위 코드를 보면 상품을 추가할 때 뷰를 새롭게 보여줘야 하는데 그 부분을 Product 클래스 내부에 위치시켰다. 해당 뷰는 Product 객체의 정보를 담고 있어야 하고 정확히 Product 객체가 하는 일이기 때문에 피드백의 내용대로 이런 식으로 <strong>뷰와 비즈니스 로직 분리</strong>를 실현했다.</p>
<h3 id="2-3-ui에-보여질-상태를-어떻게-관리할-것인가">2-3. UI에 보여질 상태를 어떻게 관리할 것인가?</h3>
<p>이번 주차 과제 요구사항 중 하나는 &#39;<strong>새로고침을 하여도 최근 작업내역이 보여야 한다</strong>&#39;이다. 이는 <code>localStorage</code>를 이용해서 마치 백엔드의 DB에 저장된 값을 불러와서 화면을 보여주는 것과 같은 효과를 낼 수 있다. 그런데 어떤 이벤트가 발생했을 때 어떤 클래스의 메서드를 이용해서 뷰를 보여주면 새로고침을 했을 때는 조작된(추가된) DOM 정보가 모두 사라지기 때문에 최근 작업내역을 보여줄 수가 없다.</p>
<p>그래서 이렇게 생각했다. <strong>새로고침을 하지 않고 프로그램을 사용자가 계속 조작하는 경우</strong>와 <strong>새로고침을 했을 때의 경우</strong>를 나눠서 생각했다. 사용자가 새로고침을 하지 않고 입력값을 넣고 하는 등의 문제는 각 클래스에 있는 <code>render()</code> 메서드를 사용해서 DOM을 조작하면 된다. 그러면 UI에 보여줄 상태 데이터들을 최신의 상태로 잘 볼 수 있을 것이다.</p>
<p>그렇다면 새로고침을 했을 때는 어떻게 최근 작업내역을 보여줄까? <code>localStorage</code>에 최근까지 작업한 내용을 전부 불러와서 view를 render할 때 보여주면 된다. </p>
<blockquote>
<p>Coins.js</p>
</blockquote>
<pre><code class="language-js">export default class Coins {
  ...
  template() {
    const ids = [
      CHARGE_500_QUANTITY_ID,
      CHARGE_100_QUANTITY_ID,
      CHARGE_50_QUANTITY_ID,
      CHARGE_10_QUANTITY_ID,
    ];
    const title = [TITLE_500, TITLE_100, TITLE_50, TITLE_10];
    return (
      ids.map((id, index) =&gt; `
        &lt;tr align=&quot;center&quot; bgcolor=&quot;white&quot; height=&quot;40&quot;&gt;
          &lt;td align=&quot;center&quot; width=&quot;62&quot;&gt;${title[index]}&lt;/td&gt; 
          &lt;td id=&quot;${id}&quot; align=&quot;center&quot; width=&quot;62&quot;&gt;${this.coins[index]}개&lt;/td&gt;
        &lt;/tr&gt;
      `).join(&#39;&#39;)
    );
  }

  render(dom) {
    const $table = $(`#${dom}`);

    $table.innerHTML = `
      ${coinListHeaderTemplate()}
      ${this.template()}
    `;
  }
  ...
}</code></pre>
<p>위 코드를 보면 사용자가 잔돈을 반환했을 때 위 코드의 <code>render</code> 메서드를 실행해서 뷰를 업데이트한다. 이렇게 사용자가 새로고침을 하지 않고 직접 조작할 때 즉각적으로 뷰를 업데이트 해준다. 그러면 다른 탭을 갔다가 돌아오는 경우나 새로고침을 했을 때는 어떻게 할까?</p>
<p>그 때는 최초 렌더를 할 때 <code>localStorage</code>에서 받아온 데이터를 갖고 있는 객체의 내부 데이터를 사용해서 아래와 같이 보여주면 된다.</p>
<blockquote>
<p>renderCharge.js</p>
</blockquote>
<pre><code class="language-js">...
function coinListTemplate(change) {
  const coins = JSON.parse(localStorage.getItem(COINS_STORAGE_KEY));
  const titles = [TITLE_500, TITLE_100, TITLE_50, TITLE_10];
  const ids = [
    CHARGE_500_QUANTITY_ID,
    CHARGE_100_QUANTITY_ID,
    CHARGE_50_QUANTITY_ID,
    CHARGE_10_QUANTITY_ID,
  ];
  return (
    ids.map((id, index) =&gt; `
      &lt;tr align=&quot;center&quot; bgcolor=&quot;white&quot; height=&quot;40&quot;&gt;
        &lt;td align=&quot;center&quot; width=&quot;62&quot;&gt;${titles[index]}&lt;/td&gt; 
        &lt;td id=&quot;${id}&quot; align=&quot;center&quot; width=&quot;62&quot;&gt;${coins ? `${change.coins[index]}개` : &#39;&#39;}&lt;/td&gt;
      &lt;/tr&gt;
    `).join(&#39;&#39;)
  );
}
...</code></pre>
<p>위 <code>coinListTemplate</code> 함수를 보면 매개변수로 <code>change</code>를 받아온다. 이 <code>change</code>가 <code>Coins</code> 클래스의 객체이다. 해당 객체를 초기화 할 때 <code>localStorage</code>에서 값을 가져와 저장하기 때문에 최근의 정보들을 전부 갖고 있다.</p>
<p>그래서 그 정보를 이용해서 <code>change.coins[index]</code> 처럼 값을 보여주면 된다.</p>
<p>이렇게 하면 사용자가 직접 조작할 때는 해당 부분만 업데이트를 해주고 새로고침을 하거나 다른 탭에서 돌아올 때는 최근 정보를 갖고 있는 객체의 변수를 이용해서 뷰를 보여주면 일관된 데이터를 보여 줄 수 있다.</p>
<h3 id="2-4-dom-append-이후-참조에-대한-문제">2-4. DOM append 이후 참조에 대한 문제</h3>
<p>DOM 노드를 만들어서 보여줄 때 어떤 문제를 발견했다. 이러한 문제가 발생하는 원인에 대해서 깊이있게 공부하진 못했지만 합리적인 추측이 가능했다. 문제는 다음과 같다.</p>
<blockquote>
<p>initProductAdd.js</p>
</blockquote>
<pre><code class="language-js">function renderProductAddList($productAdd) {
  const $listContainer = document.createElement(&#39;div&#39;);

  $listContainer.innerHTML = `
    &lt;h3&gt;${PRODUCT_LIST_TITLE}&lt;/h3&gt;
    &lt;table id=&quot;${PRODUCT_LIST_TABLE_ID}&quot; bgcolor=&quot;black&quot; border=&quot;1&quot; style=&quot;border-collapse:collapse;&quot;&gt;
      &lt;tr align=&quot;center&quot; bgcolor=&quot;white&quot; height=&quot;40&quot;&gt;
        &lt;td align=&quot;center&quot; width=&quot;160&quot;&gt;${PRODUCT_NAME_TITLE}&lt;/td&gt;
        &lt;td align=&quot;center&quot; width=&quot;100&quot;&gt;${PRODUCT_PRICE_TITLE}&lt;/td&gt;
        &lt;td align=&quot;center&quot; width=&quot;100&quot;&gt;${PRODUCT_QUANTITY_TITLE}&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/table&gt;
  `;
  $productAdd.append($listContainer);
  console.log(&#39;test: &#39;, document.querySelector(`#${PRODUCT_LIST_TABLE_ID}`));
  ...
}</code></pre>
<p>위 코드를 보면 <code>innerHTML</code> 함수를 이용해서 새로운 <code>&lt;table&gt;</code>을 만들어서 새로 생성한 <code>&lt;div&gt;</code> 에 <code>append</code>해서 화면을 구성한다.</p>
<p>그런데 쉽게 생각했을 때 코드가 순서대로 실행되면서 DOM 노드를 만들어서 조작 과정을 전부 마치고 <code>console.log()</code>로 생성된 DOM 노드를 참조해서 출력하는 것이니까 정상적으로 작동할 것처럼 보인다.</p>
<p>하지만 결과는 <code>test: null</code> 이 출력된다. 즉, DOM 노드를 생성해서 조작하는 과정을 다 거쳤지만 그 직후 참조를 하려고 하면 해당 노드를 참조할 수 없다.</p>
<p>여기서 신기한 점은 만약에 <code>setTimeout</code> 함수를 이용해서 <code>console.log()</code> 부분을 지연실행시키면 어떻게 될까?</p>
<pre><code class="language-js">function renderProductAddList($productAdd) {
  const $listContainer = document.createElement(&#39;div&#39;);

  $listContainer.innerHTML = `
    &lt;h3&gt;${PRODUCT_LIST_TITLE}&lt;/h3&gt;
    &lt;table id=&quot;${PRODUCT_LIST_TABLE_ID}&quot; bgcolor=&quot;black&quot; border=&quot;1&quot; style=&quot;border-collapse:collapse;&quot;&gt;
      ${productListHeaderTemplate()}
    &lt;/table&gt;
  `;
  $productAdd.append($listContainer);
  setTimeout(() =&gt; {
    console.log(&#39;test: &#39;, document.querySelector(`#${PRODUCT_LIST_TABLE_ID}`));
  }, 0);
  ...
}</code></pre>
<p>위 코드에서 변경된 부분은 <code>console.log()</code> 부분이 <code>setTimeout</code> 함수 안으로 이동한 것 뿐이다. 이렇게 수정한 결과 정상적으로 콘솔에 해당 DOM node를 참조할 수 있었다.</p>
<p>이렇게 되는 이유를 깊이 있게 탐구하지는 못했지만 다음과 같을 것이다.</p>
<p>JS는 코드를 한 줄 한 줄 내려가면서 실행한다. <code>innerHTML</code> 함수를 실행하고 <code>append</code>함수를 실행한다. 그렇게 정상적으로 DOM node가 생성되었을 것이다. 하지만 그 직후 바로 해당 DOM node를 참조하면 참조할 수가 없다. 아마도 <strong>document.querySelector가 실제로 브라우저에 렌더링된 DOM 노드를 참조하려고 하는데 아직 렌더링과정까지는 완료가 안 됐기 때문</strong>이다.</p>
<p>브라우저는 DOM 을 파싱하고 렌더링하는 일련의 과정을 거치는데 그 과정에 시간이 걸리는 것이다. 그래서 <code>setTimeout</code>으로 지연 실행시켰을 때는 정상적으로 작동한 것이다. </p>
<p>그렇다면 이 문제를 어떤 방식으로 해결해야 할까?</p>
<p>위와 같이 <code>setTimeout</code>으로 지연실행시키면 되겠지만 정확히 어떤 식으로 문제가 해결돼서 동작하는지 확신이 없기 때문에 더 안전한 방법을 찾아야 한다.</p>
<p>위의 코드에서 <code>querySelector</code>로 DOM 노드를 참조하려는 이유는 <code>localStorage</code>에 있는 데이터를 가져와서 있으면 보여주기 위해서였다. 그런데 이 의도를 다른 방법으로 구현하면 문제 해결이 가능했다.</p>
<p><strong>template literal안에서 map 함수를 통해 배열을 만든 후 <code>join</code>함수로 문자열로 만들면 된다.</strong></p>
<pre><code class="language-js">function productListTemplate({ name, price, quantity }) {
  return `
    &lt;tr align=&quot;center&quot; bgcolor=&quot;white&quot; height=&quot;40&quot;&gt;
      ...
    &lt;/tr&gt;
  `;
}

function renderProductAddList($productAdd) {
  const $listContainer = document.createElement(&#39;div&#39;);
  const products = JSON.parse(localStorage.getItem(PRODUCTS_STORAGE_KEY))
    ?.map((product) =&gt; productListTemplate(product))
    .join(&#39;&#39;);

  $listContainer.innerHTML = `
    &lt;h3&gt;${PRODUCT_LIST_TITLE}&lt;/h3&gt;
    &lt;table id=&quot;${PRODUCT_LIST_TABLE_ID}&quot; bgcolor=&quot;black&quot; border=&quot;1&quot; style=&quot;border-collapse:collapse;&quot;&gt;
      ${productListHeaderTemplate()}
      ${products || &#39;&#39;}
    &lt;/table&gt;
  `;
  $productAdd.append($listContainer);
}
}</code></pre>
<p>위 코드를 보면 <code>localStorage</code>에서 데이터를 가져와서 하나의 문자열로 만들어 <code>products</code>라는 변수에 저장한다.
그 다음에 그것을 그냥 <strong>template literal 안에 추가</strong>하는 것으로 의도를 정확하게 구현할 수 있었다.</p>
<h3 id="2-5-탭-구현에-대한-고찰">2-5. 탭 구현에 대한 고찰</h3>
<p>탭을 구현하기 위해서 이런 방법을 생각했다. 처음에 모든 DOM 을 생성하여 렌더링한다. 그런데 여기서 기본값으로는 상품 추가 탭 화면만 먼저 보여주고 잔돈 추가, 상품 구매 뷰는 <code>hidden</code> 속성을 이용해서 가려두는 것이다. 이렇게 하면 탭을 누를때마다 그 탭에 해당하는 전체 뷰를 다 지우고 다시 생성해서 보여줄 필요가 없기 때문이다.</p>
<p>간략하게 설명하자면, </p>
<ol>
<li>상품 구매 뷰를 보여준다.</li>
<li>잔돈 추가 뷰를 보여준다.</li>
<li>상품 구매 뷰를 보여준다.</li>
<li>세 개의 뷰를 <code>hidden</code> 속성으로 가린다.</li>
<li>현재 탭에 대한 뷰만 <code>hidden</code> 속성을 지워 보여준다.</li>
</ol>
<p>이렇게 하는 것이 탭을 누를때마다 새로운 뷰를 생성해서 보여주는 것보다 더 효율적이고 좋을 것이라고 생각했다.</p>
<p>그러면 업데이트되는 뷰들은 어떻게 해야 할까? 예를 들어 상품 추가 탭에서 상품을 추가하면 표에 추가한 상품이 추가돼야 한다. 처음 뷰를 보여줄때 모든 DOM 을 생성했기 때문에 그 DOM에 추가해서 업데이트할 DOM만 데이터를 반영해서 업데이트 해주면 된다.</p>
<p>이렇게 DOM이 업데이트 되는 부분들의 시작은 무엇일까? 바로 <strong>이벤트리스너</strong>이다. 이벤트가 발생했을 때 해당 탭의 DOM을 업데이트 해주면 되는 것이다. 그런데 한 가지 예외 상황을 맞닥뜨리면서 이런 뷰 설계에 문제가 있다는 것을 깨달았다.</p>
<p><strong>상품 추가 탭에서 상품을 추가하면 상품 추가 탭의 상품 현황 DOM만 업데이트 되는 것이 아니라 상품 구매 탭의 구매할 수 있는 상품 현황 DOM도 업데이트가 돼야 하는 것</strong>이다.</p>
<p>그러면 상품 추가 탭에서 상품을 추가하는 이벤트가 발생했을 때 상품 구매 탭의 표를 업데이트 해줘야 하는 상황이다.</p>
<p>나는 이 상황이 마음에 들지 않았다. 상품 추가하는 이벤트를 처리하는 함수에서 상품 구매의 뷰를 업데이트해줘야 하기 때문에 해당 함수가 할 일을 벗어나는 느낌이다. 해당 함수의 의도된 역할을 넘어서서 다른 일까지 하게 되므로 이 설계에는 문제가 있다고 생각했다.</p>
<p>리액트를 생각해보면 각 탭에서 보여주는 뷰들을 하나의 컴포넌트로 볼 수 있다. 각 컴포넌트는 고유의 state값을 갖고 해당 state값들을 백엔드에서 불러와서 최신화된 데이터들을 뷰에 보여주는 방식으로 구현이 가능하다. 이렇게 하기 위해서는 이 프로그램에서는 객체가 갖고 있는 정보를 보여주는 것이 가장 적절하다.</p>
<p>이런 생각들을 기반으로 해서 기존에 했던 방식을 바꿨다. 처음부터 전부 다 뷰를 렌더하는 것이 아니라 현재 <strong>탭 정보만 누를때마다 뷰를 새로 업데이트해서 렌더하는 것</strong>이다. 이렇게 한다면 해당 뷰에서 다룰 데이터는 해당 뷰에서만 다룰 수 있다. 그러고 객체에 저장된 데이터를 각 뷰에서 접근하기 때문에 데이터의 일관성도 잘 유지된다.</p>
<p>탭을 눌러서 다른 뷰를 보려고 하면 현재 보이는 DOM을 삭제해버린다. 그리고나서 새롭게 보여질 뷰를 만들어서 보여준다. 리액트도 이런 식으로 컴포넌트를 보여주기 때문에 이런 방식으로 구현했다.</p>
<h2 id="3-javascript">3. Javascript</h2>
<hr>
<h3 id="3-1-localstorage">3-1. Localstorage</h3>
<p><code>localStorage</code>를 사용하면 웹 브라우저에 데이터를 저장하고 사용할 수 있기 때문에 페이지가 새로고침이 되더라도 값을 유지할 수 있는 장점이 있다. 그런데 localStorage를 사용할 때 주의할 점이 있다.</p>
<p><code>localStorage</code>는 값을 저장할 때 전부 <code>string</code>으로 변환해서 저장한다. 값을 불러올 때도 저장된 값이 <code>string</code>이니까 당연히 문자열로 데이터를 가져온다. 그런데 <strong>데이터가 만약에 객체라면 어떻게 사용해야 할까?</strong></p>
<pre><code class="language-js">&gt; localStorage.setItem(&#39;obj&#39;, {a: 1, b: 2})
undefined
&gt; localStorage.getItem(&#39;obj&#39;)
&quot;[object Object]&quot;</code></pre>
<p>위 예시를 보면 객체를 그대로 <code>localStorage</code>에 저장하려고 하면 객체를 문자열로 변환해서 저장하게 된다.</p>
<pre><code class="language-js">&gt; String({a: 1, b: 2})
&quot;[object Object]&quot;</code></pre>
<p>이렇게 변환되기 때문에 객체에 담은 데이터를 전혀 쓸 수 없게 되는 버그를 만들게 된다. 이러한 문제를 해결하기 위해서 <strong>JSON.parse 와 JSON.stringify</strong> 함수를 사용해야 한다.</p>
<pre><code class="language-js">&gt; localStorage.setItem(&#39;json&#39;, JSON.stringify({a: 1, b: 2}))
undefined
&gt; JSON.parse(localStorage.getItem(&#39;json&#39;))
{a: 1, b: 2}</code></pre>
<p><code>JSON.stringify</code> 함수를 이용해서 객체의 정보를 그대로 담은 문자열로 변환(직렬화)을 해서 저장하고 값을 불러올 때는 <code>JSON.parse</code>를 통해서 문자열로 된 객체를 실제 객체로 변환(역직렬화)해서 사용하면 객체가 갖고 있는 데이터에 의도한대로 접근할 수 있다.</p>
<h3 id="3-2-innerhtml-vs-insertadjscenthtml">3-2. innerHTML vs insertAdjscentHTML</h3>
<p>innerHTML 을 사용하게 되면 코드 가독성이 높아진다는 장점이 있다. 문자열들을 전부 DOM 노드로 파싱해서 새롭게 노드를 만들어준다.</p>
<p>그런데 <a href="https://developer.mozilla.org/ko/docs/Web/API/Element/innerHTML">MDN 공식문서</a>를 보면 다음과 같은 말이 있다.</p>
<blockquote>
<p>요소(element)의 내용을 변경하는 대신 HTML을 문서(document)에 삽입하려면, insertAdjacentHTML() 메서드를 사용하십시오.</p>
</blockquote>
<p>innerHTML을 사용해서 기존의 뷰 일부를 업데이트하면 문제가 발생한다. 만약에 업데이트 하는 뷰에 이벤트리스너가 등록돼있으면 전부 삭제해버리는 것이다. <strong>innerHTML은 내부적으로 기존에 있던 노드를 전부 삭제하고 새롭게 만들기 때문에 이벤트 리스너까지 전부 삭제가 된다.</strong></p>
<p>그래서 공식문서의 제안대로 상품을 추가하는 부분에는 <code>insertAdjacentHTML</code> 함수를 사용해서 구현했다.</p>
<h2 id="4-git">4. Git</h2>
<hr>
<h3 id="4-1-직전-커밋-수정하기">4-1. 직전 커밋 수정하기</h3>
<p>README 를 수정할 일이 있어서 수정을 한 후에 <code>commit</code>을 한 상황이다. 그런데 파일에 오타가 있다는 것을 발견하고 방금 commit한 내용에 대해서 수정하고 싶을 수 있다.</p>
<p>이런 상황에서 아주 간단하게 적용할 수 있는 명령어가 있다.</p>
<p>이미 알고 있는 명령어였지만 보통은 직전의 커밋 메세지의 내용을 수정할 때 쓰곤 했다.</p>
<p><code>git commit --amend</code> 를 사용해서 명령어를 입력하면 직전의 커밋로그 작성하던 텍스트 편집기가 화면에 보인다. 이제 커밋로그를 수정한 다음에 저장하면 commit message가 수정돼서 로그에 저장된다.</p>
<p>그런데 만약에 코드를 바꾼다거나 어떤 수정이 필요할 떄는 어떻게 할까?</p>
<p>아주 간단하다. <strong>바로 직전의 커밋을 수정하는 상황</strong>이라면 현재 상태에서 수정하고 싶은 파일을 수정한다. 그리고 나서 <code>git add &lt;수정한 파일&gt;</code> 을 해주고 <code>git commit --amend</code>를 해주면 똑같이 커밋로그를 작성하는 텍스트 편집기가 화면에 보인다. 이제 메세지를 수정할거면 수정하고 아니면 그대로 저장하면 커밋에 수정사항이 반영돼서 수정된다.</p>
<h2 id="5-우아한테크코스-4기-프리코스-후기">5. 우아한테크코스 4기 프리코스 후기</h2>
<hr>
<p>3주 동안 프리코스를 진행하면서 구현하면서 자연스럽게 습관이 된 긍정적 변화들이 있다.</p>
<h3 id="의미-있는-단위의-커밋-로그-만들기">의미 있는 단위의 커밋 로그 만들기</h3>
<p>첫번째로, <strong>의미 있는 커밋 단위</strong>를 만들려고 노력하게 됐다. 팀 프로젝트를 할 때도 커밋로그를 신경쓴다고 했지만 이 정도로 세분화하고 타인을 배려하는 마음으로 해 본 적은 없었다. 그런데 미션들을 구현하면서 누군가가 내 코드를 볼 때 빠르고 정확하게 의도와 흐름을 파악할 수 있게 의미 있는 단위로 커밋을 나누기 위해 노력했다.</p>
<p>물론 이게 쉽지는 않았다. 왜냐하면 기능목록을 미리 만들어 놓고 구현을 하다보면 버그를 발견하거나 지금 기능과는 관련이 없지만 어떤 실수를 발견할 때가 많기 때문이다. 이런 상황들을 직면할 때마다 커밋을 어떤 식으로 나눠야 할까를 고민하는 것 자체가 좋은 개발자로서 한 걸은 더 다가서는 성장이라고 생각한다.</p>
<p>구현을 어느 정도 완료하고 잘 정돈된 커밋로그를 보면 내가 어떻게 개발을 해왔는지를 한 눈에 볼 수 있고 이런 부분들이 <strong>협업하는 다른 개발자에게는 좋은 배려</strong>로 다가갈 것 같다.</p>
<h3 id="코딩-컨벤션과-클린코드">코딩 컨벤션과 클린코드</h3>
<p>둘째로, <strong>코딩 컨벤션</strong>을 유지하려고 노력하게 됐다. 나의 경우는 <a href="https://github.com/ParkSB/javascript-style-guide">Airbnb 코딩컨벤션</a>을 선택했는데 문서에 보면 컨벤션에 대한 이유들이 적혀있다. 이에 더해서 <a href="https://github.com/qkraudghgh/clean-code-javascript-ko">클린코드 javascript</a> 를 컨벤션이라고 생각하고 원칙들을 항상 적용하려고 노력하게 됐다. 컨벤션과 클린코드 원칙은 서로 깊은 연관이 있다고 생각한다. 그래서 이 두 개의 문서를 코딩 구현하면서 반복해서 보고 적용하려고 노력했다.</p>
<p>이러한 컨벤션을 유지하려고 노력하면서 <strong>다른 개발자가 봤을 때 가독성이 좋은 코드</strong>를 만들기 위해서 노력하는 습관에 힘을 더해주는 것 같았다. 또한 컨벤션과 클린코드 원칙에는 버그를 최소화하고 안전한 프로그래밍을 할 수 있도록 하는 장치들이 녹아있다고 생각해서 앞으로 코딩을 할 때 항상 가져가야할 습관이라고 생각한다.</p>
<h3 id="설계와-살아있는-문서">설계와 살아있는 문서</h3>
<p>셋째로, <strong>설계와 계획을 먼저하고 구현</strong>에 들어가는 습관이 생겼다. 원래도 설계와 계획이 어느 정도 있고 구현에 들어간 것은 맞지만 미션을 구현하는것처럼 상세한 <strong>기능구현목록을 작성</strong>해 보는 것이 전체 프로그램이 어떤 모습이 될까를 그리는데 큰 도움이 되었다. 세부적인 디테일까지 전부 설계하고 계획하는 것은 아니지만 어떤 문제를 파악하고 이를 해결하기 위해서 코드를 만들 때 큰 그림을 그리는 연습이 많이 되었다.</p>
<p>그리고 무엇이든 계획은 완벽할 수가 없다. 항상 구현하면서 미처 생각하지 못했던 예외상황 등을 발견하게 되고 계획은 수정돼야 할 떄가 있다. 그럴 때면 기능 구현 목록을 수정해서 <strong>살아있는 문서</strong>를 만들었다. 개발 문서가 단순히 계획으로 그치는 것이 아니라 개발 과정도 반영하게 되면서 계획을 다시 한 번 돌아보고 또 다시 계획하고 구현하는 사이클이 반복되면서 더 안전하고 생산성있는 방식으로 코딩을 하게 된 것 같다.</p>
<h3 id="디자인-패턴과-프로그래밍-방법론">디자인 패턴과 프로그래밍 방법론</h3>
<p>넷째로, 원래는 크게 생각해보지 않았던 <strong>디자인 패턴과 프로그래밍 방법론</strong>에 대해서 생각해보게 되는 시간이었다. 사실 이 부분이 가장 아직도 모르겠고 충분히 학습된 부분이 아니라 아쉬움이 많이 남는 부분이다. JS를 이용해서 React를 한 번 다뤄보긴 했지만 VanillaJS 로만 프로그램을 만드는 것이 처음이라 한 주의 미션을 깔끔하게 완성하는 것 자체가 빠듯했다.</p>
<p>하지만 그 시간 안에서 어떤 식으로 프로그램의 구조를 설계해야 가장 좋고 효과적일까를 생각하는 시간에 <strong>MVC 패턴, 객체지향 프로그래밍</strong>등에 대해서 알게 됐다. 아직 디자인 패턴이나 어떤 프로그래밍 방법론에 대해서 깊이 있게 학습하지는 못했지만 개괄적으로 어떤 것인지에 대해서 알게 되었고 앞으로 내가 더 알아가야할 숙제가 생긴 것 같아서 기쁘다.</p>
<p>이런 이론들을 공부하면서 왜 이런 이론들이 만들어졌고 어떻게 구조를 만들어야 견고하고 효과적인 프로그램을 만들 수 있을까를 고민했다. 피드백에 이런 구조를 만드는 내용에 대한 것은 없었기 때문에 사실 프리코스가 끝나는 이 시점에서도 이 부분은 전혀 궁금증이 해소되지 않은 상태로 남아있다. 하지만 앞으로 내가 알아가고 해결해야갈 과제라고 생각한다.</p>
<h3 id="아쉬운점">아쉬운점</h3>
<p>프리코스를 진행하면서 공통 피드백을 받아서 나에게 해당되는 부분에 대해서 개선하고 성장하는 부분은 가장 좋았다. 그런데 바로 위에서 썼듯이 어떤 구조에 대한 피드백은 없었기 때문에 내가 하고 있는 방법에 대한 의문은 계속 들었다. 그렇다고 해서 소프트웨어를 만드는데 정해진 답은 없기 때문에 답을 바라는 것은 아니다. 하지만 같이 하고 있는 <strong>동료나 숙련된 개발자로부터의 피드백같은 것이 있었다면 훨씬 더 좋은 피드백이 됐을 것 같은 아쉬움</strong>이 남는다.</p>
<p>일주일을 집중해서 만든 프로그램이라 그런지 코드 하나하나에 정성이 많이 들어간만큼 개선될 부분에 대해서 코드리뷰를 받을 수 있는 기회가 있었다면 여러 사람의 생각을 공유하고 흡수하여 더 큰 성장을 했을 것 같은 생각이 든다.</p>
<p>한 가지 더 아쉬운 점은 <strong>다른 사람들의 코드를 충분히 공부하지 못했다는 것</strong>이다. 프리코스를 하면서 스스로 원칙을 하나 세웠다. 프로그램을 만들기 전에 어떤 사람의 코드도 보지 않고 내 스스로 생각해보고 만들어보려고 했다. 그렇게 하다보니까 코드를 완성하고 나서 다른 사람의 코드를 볼 수 있었다. 그런데 생각보다 시간이 오래 걸렸고 다른 사람들이 제출한 코드를 분석하고 공부할 시간이 생각보다 부족했다.</p>
<p>그렇기 때문에 다른 사람의 코드를 보고 이런 방식으로 접근했구나를 느끼지만 제출 시간때문에 코드를 수정한다거나 보수하는 과정이 쉽게 되지는 않았다. 그래서 프리코스에 합격해서 집중해서 만든 여러 사람들의 코드를 공부하고 리뷰하면서 서로 성장하는 기회들이 있었으면 좋겠다.</p>
<h2 id="references">References</h2>
<ul>
<li>모던 자바스크립트 Deep Dive (이웅모 저)</li>
<li><a href="https://github.com/qkraudghgh/clean-code-javascript-ko">클린코드 Javascript</a></li>
<li><a href="https://ko.javascript.info/">모던 자바스크립트 튜토리얼</a></li>
<li><a href="https://www.daleseo.com/js-web-storage/">LocalStorage</a></li>
<li><a href="https://d2.naver.com/helloworld/59361">NAVER D2 - 브라우저는 어떻게 동작하는가?</a></li>
<li><a href="https://mizzo-dev.tistory.com/entry/git-commit-edit">git commit 수정하기</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/API/Element/innerHTML">MDN - innerHTML</a></li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/API/Element/insertAdjacentHTML">MDN - insertAdjecentHTML</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스]프리코스 2주차 학습 내용]]></title>
            <link>https://velog.io/@dom_hxrdy/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%EB%82%B4%EC%9A%A9</link>
            <guid>https://velog.io/@dom_hxrdy/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-2%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%EB%82%B4%EC%9A%A9</guid>
            <pubDate>Tue, 07 Dec 2021 13:44:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-자동차-경주-게임">1. 자동차 경주 게임</h2>
<hr>
<p>우아한테크코스 4기의 프리코스 2주차 미션은 자동차 경주 게임 구현이다. 구현 내용은 <a href="https://github.com/DomMorello/javascript-racingcar-precourse/tree/dom">프리코스 2주차 미션 저장소</a>에 업로드했다.</p>
<p>2주차 미션을 진행하면서 고민한 내용들에 대해 개인적으로 공부하고 정리해보았다.</p>
<br>

<h2 id="2-구현-중에-했던-고민들">2. 구현 중에 했던 고민들</h2>
<hr>
<h3 id="2-1-클래스를-어떤-구조로-나눠야-할까">2-1. 클래스를 어떤 구조로 나눠야 할까?</h3>
<p><strong>클래스를 분리하는 것</strong>이 2주차 미션의 목표이다. 클래스를 분리한다면 클래스를 통해 인스턴스를 만들어서 사용하는 구조를 만들어야 하는데 바로 <strong>객체지향</strong>이라는 키워드가 떠올랐다. 평소에 관심도 갖지 않았고 실습도 해 본 적이 없었기 때문에 이 키워드에 대해서 먼저 알아봐야겠다고 생각했다.</p>
<p><a href="https://techblog.woowahan.com/">우아한형제들 기술블로그</a>에 <a href="https://techblog.woowahan.com/2502/">생각하라, 객체지향처럼</a> 이라는 제목의 글이 객체지향이 무엇인지 쉽게 설명을 해놨다. 그 글을 읽으면서 객체지향이 무엇인지에 대해서 알아보는데 해당 글에서 드는 예시가 이번 주에 만드는 자동차 경주와 어느 정도 비슷하다고 생각했다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/76607e0b-1c6e-4679-88a5-37e2a9f7c62a/OOP.png" alt=""></p>
<p>위 글에서는 커피전문점에서 커피를 주문하는 상황을 하나의 도메인으로 가정하고 설명을 하는데 이 상황을 자동차 경주 게임에 대입할 수 있다.</p>
<ol>
<li>사용자의 입력(주문)을 받아서 게임을 시작하기 때문에 바리스타 객체를 <strong>Input 객체</strong>로</li>
<li>만들어져야 하는 객체들인 커피 객체는 <strong>Car 객체</strong>로</li>
<li>자동차를 움직여서 경주를 진행하고 결과를 내는 <strong>gameController 객체</strong>를 만든다.</li>
<li>마지막으로 전광판처럼 걸과를 보여줄 <strong>View 객체</strong>를 만든다.</li>
</ol>
<p><strong>객체지향 세계에서는 모든 객체들이 능동적이고 자율적인 존재로 생각해야 한다</strong>고 한다. 그래서 동물이 아닌 것도 &#39;의인화&#39; 해서 생각해야 한다고 하는데 그래서 Input을 바리스타처럼, 게임을 진행하는 것이 마치 아이가 자동차들을 갖고 노는 것이라고 생각해서 GameController로하고 전광판이 게인 진행상황을 관중들에게 보여준다고 생각해서 View로 했다.</p>
<p>위 글에서 객체지향 설계를 할 때 <strong>&#39;협력을 설계할 때는 객체보다는 메시지를 먼저 선택하고 그 후에 메시지를 수신하기에 적절한 객체를 선택해야 합니다&#39;</strong> 라고 말한다.</p>
<p>프로그램에서 필요한 메시지들을 순차적으로 나열해 보면 다음과 같다.</p>
<ol>
<li>입력을 받아라. (Input 객체가 예외를 처리하고 입력값을 저장한다)</li>
<li>경주할 자동차들을 만들어라. (Input의 정보를 이용해 CarController가 Car 객체를 생성한다)</li>
<li>경주를 시작해라. (CarController 객체가 Car객체를 이용해 경주를 시작한다)</li>
<li>결과를 보여줘라. (CarController로부터 결과를 받아와서 View 객체를 이용해 화면을 보여준다)</li>
</ol>
<p>전반적인 디렉토리 구조를 보면 지난 주차에 새로 배워서 적용해봤던 <strong>MVC 패턴</strong>을 유지하려고 노력했다. 사용자의 입력값을 받고 예외를 처리해주는 <strong>input 디렉토리가 Model</strong>, 비즈니스 로직을 진행하는 <strong>game 디렉토리가 Controller</strong>, 이를 바탕으로 데이터를 화면에 보여주는 <strong>view 디렉토리가 View</strong>이다.</p>
<h3 id="2-2-생성자-함수-vs-클래스">2-2. 생성자 함수 VS 클래스</h3>
<p>과제에서 <strong>클래스를 분리하는 것</strong>이 2주차의 목표라고 했다. 그런데 JS 문법을 보면 생성자 함수도 있고 클래스 문법도 있다. 클래스 문법은 비교적 최신 JS 문법이다. 클래스에 대해서 공부를 해봤는데 생성자 함수와의 차이점에 대해서 크게 모르겠다. 아래에서 보다시피 과제에서도 클래스 형식을 쓸지 생성자 함수 형식을 쓸지를 선택하라고 한다.</p>
<pre><code class="language-js">function Car(name) {
  this.name = name;
}

class Car {
  constructor(name) {
    this.name = name;
  }
}</code></pre>
<p>그렇다는 말은 생성자 함수로 객체를 만들어 사용하는 것도 2주차 목표 중 하나인 클래스의 분리라고 할 수 있다. 그래서 처음에는 생성자 함수를 사용하는 방식으로 하려고 했다. 그런데 생성자 함수를 통해서 객체를 생성하면 <strong>객체를 생성할 때마다 생성자 함수 안에 정의된 함수가 새로 정의되고 할당</strong>된다. 같은 객체들이 공통적으로 사용할 같은 함수인데 여러 번 정의, 할당되는 것은 낭비기 때문에 이를 해결할 방법이 필요했다.</p>
<p>그 방법은 <strong>프로토타입</strong>을 사용하는 것이다.</p>
<pre><code class="language-js">function Person(name) {
  this.name = name;
}

Person.prototype.sayHi = function() {
  console.log(this.name + &#39; says hi&#39;);
};
const dom = new Person(&#39;dom&#39;);
const morello = new Person(&#39;morello&#39;);

dom.hi();
morello.hi();</code></pre>
<p>위 코드에서처럼 <strong>프로토타입</strong> 함수를 선언하면 생성자 함수를 통해 객체가 생성되더라도 <code>sayHi</code> 함수는 한 번만 정의되고 할당된다.</p>
<p>좋은 방법처럼 보이지만 이 방법에 문제가 있었다. <a href="https://github.com/ParkSB/javascript-style-guide"><strong>에어비엔비 코드컨벤션</strong></a>을 보면 다음과 같은 항목이 있다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/a5bb1f95-39d9-4ed0-b8d5-482daff4252c/airbnb.png" alt=""></p>
<p>위와 같이 <strong>프로토타입을 직접 조작</strong>하는 것을 허용하지 않는다. 대신에 클래스를 사용하라고 한다.
그렇다면 클래스는 객체를 생성할때마다 새로운 함수를 정의하고 할당하는 낭비를 해결할 방법은 없을까?</p>
<pre><code class="language-js">class Person {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    console.log(&#39;..&#39;);
  }
}</code></pre>
<p>위 함수처럼 <code>sayHi</code> 함수를 클래스 내부에 정의하면 그 함수는 <strong>프로토타입</strong>에 저장된다. 즉, <code>Person.prototype</code> 에 저장이 되기 때문에 위에서 말한 문제는 클래스 문법을 사용함으로써 알아서 해결되는 것이다.</p>
<h3 id="2-3-클래스-내부에서만-사용되는-함수">2-3. 클래스 내부에서만 사용되는 함수</h3>
<p>기능 구현을 하다가 <code>Input</code> 클래스를 만들어서 내부에 유효성을 검사하는 메소드를 만들었다. 입력값과 관련이 있기 때문에 입력값을 set하기 전에 유효성을 검사하기 위해서다.</p>
<p>다음 코드를 보면 처음에 만든 <code>Input</code> 클래스이다.</p>
<blockquote>
<p>Input.js</p>
</blockquote>
<pre><code class="language-js">export default class Input {
  ...
  isValidCarNames(carNames) {
    /* 유효성 검증 코드 */
  }

  setCarNames(carNames) {
    if (this.isValidCarNames(carNames)) {
      this.carNames = carNames;
      return ;
    }
    this.carNames = null;
  }
  ...
}</code></pre>
<p>위 코드를 보면 <code>setCarNames</code> 함수에서 유효성을 검증한 후에 값을 할당한다. 그렇게 유효성을 검증하기 위해서 사용하는 <code>isValidCarNames</code> 함수는 해당 클래스 내부에서만 사용된다. 이 클래스를 통해서 만들어진 객체를 사용할 때 클래스 외부에서 다음과 같이 사용할 수 있다는 말이다.</p>
<pre><code class="language-js">const userInput = new Input();

userInput.isValidCarNames([&#39;123&#39;,&#39;456&#39;]);</code></pre>
<p>그런데 이렇게 사용하려고 만든 함수가 아니다. 객체가 전혀 사용할 일이 없고 함수 내부에서만 필요에 의해서 사용되는 함수이기 때문에 이 방법에 대해서 의문이 생겼다.</p>
<p><strong>정적 메서드</strong>와 <strong>프로토타입 메서드</strong>의 차이는 프로토타입 체인이 다르다는 것이다. <strong>정적 메서드</strong>는 메서드 내부에서 클래스를 호출한 객체의 프로퍼티에 접근할 수 없다. 왜냐하면 객체가 호출할 수 없는 메서드이기 때문이다. 그래서 자바스크립트 Deep Dive에 따르면 &#39;<strong>this를 사용하지 않는 메서드는 정적 메서드로 정의하는 것이 좋다</strong>&#39;라고 한다.</p>
<p>이러한 이유로 위 코드를 다음과 같이 정적 메서드를 사용하는 방법으로 수정했다.</p>
<pre><code class="language-js">export default class Input {
  ...
  static isValidCarNames(carNames) {
    /* 유효성 검증 코드 */
  }

  setCarNames(carNames) {
    if (Input.isValidCarNames(carNames)) {
      this.carNames = carNames;
      return ;
    }
    this.carNames = null;
  }
  ...
}</code></pre>
<p>그런데, 이렇게 하는 방식의 문제점은 클래스와 관련이 깊은 함수라서 클래스 내부에 선언하긴 했지만 외부에서 <strong>클래스이름으로 접근해서 이 함수를 사용할 수 있다는 것</strong>이다. 그렇다면 다른 개발자가 이 코드를 봤을 때 다른 곳에서 이 클래스에 접근해서 이 함수들을 사용하라는 의도로 잘못파악할 여지가 있다고 생각했다.</p>
<p>그래서 생각한 방법은 그렇다면 외부에서 접근할 수 없는 함수고 클래스 내부에서만 사용하려면 어떻게 함수를 위치시켜야 할까를 고민하다가 모듈의 <strong>import, export</strong>를 생각했다. 함수를 파일 안에서 정의하고 <strong>export를 하지 않으면 해당 파일 외부에서는 사용될 리 없는 함수</strong>라고 의도를 잘 담을 수 있겠다는 생각을 했다.</p>
<p>그래서 아래와 같이 최종적으로 수정했다.</p>
<blockquote>
<p>Input.js</p>
</blockquote>
<pre><code class="language-js">function isValidCarNames(carNames) {
  /* 유효성 검증 코드 */
}

export default class Input {
  ...
  setCarNames(carNames) {
    if (isValidCarNames(carNames)) {
      this.carNames = carNames;
      return ;
    }
    this.carNames = null;
  }
  ...
}</code></pre>
<p>이렇게 한다면 <code>isValidNames</code> 라는 함수는 export 되지 않으니 이 파일 내에서만 사용된다는 의도를 담을 수 있고 클래스를 통헤서 외부에서 호출할 수도 없으니 개발자의 의도를 잘 구현했다고 생각했다. 하지만 이렇게 하는 방법이 과연 정답에 가까운지는 사실 아직 잘 모르겠다.</p>
<hr>
<h2 id="3-javascript">3. JavaScript</h2>
<hr>
<h3 id="3-1-클래스와-생성자-함수의-차이점">3-1. 클래스와 생성자 함수의 차이점</h3>
<ol>
<li>클래스는 <code>new</code> 연산자 없이 호출하면 에러가 발생한다.</li>
<li>클래스는 상속을 지원하는 <code>extends</code> 와 <code>super</code> 키워드를 제공한다.</li>
<li>클래스는 호이스팅이 발생하지 않는 것처럼 동작한다. 하지만 함수 선언문으로 정의된 생성자 함수는 함수 호이스팅이, 함수 표현식으로 정의한 생성자 함수는 변수 호이스팅이 발생한다.</li>
<li>클래스 내의 모든 코드에는 <strong>strict mode</strong>가 지정되어 실행되며 이를 해제할 수 없다.</li>
<li>클래스의 <code>constructor</code>, 프로토타입 메서드, 정적 메서드는 모두 프로퍼티 어트리뷰트 <code>[[Enumerable]</code>]의 값이 <code>false</code>다. 다시 말해, 열거되지 않는다.</li>
</ol>
<h3 id="3-2-every">3-2. every</h3>
<blockquote>
<p>every() 메서드는 배열 안의 모든 요소가 주어진 판별 함수를 통과하는지 테스트합니다. Boolean 값을 반환합니다.</p>
<p><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/every">MDN - every</a></p>
</blockquote>
<p>every는 callback이 거짓을 반환하는 요소를 찾을 때까지 배열에 있는 각 요소에 대해 한 번씩 콜백함수를 실행한다. 콜백함수에서 제시하는 조건에 만족하지 못하는 요소를 찾으면 바로 <code>false</code>를 반환한다. 즉, 배열의 모든 요소가 콜백함수의 조건을 만족할 때 <code>true</code>를 반환한다.</p>
<blockquote>
<p>참고: 빈 배열에서 호출하면 무조건 true를 반환한다.</p>
</blockquote>
<blockquote>
<p>Input.js</p>
</blockquote>
<pre><code class="language-js">  static isEmptyName(names) {
    return names.find((name) =&gt; name === &#39;&#39;);
  }</code></pre>
<p>입력값의 예외를 처리할 때 위와 같이 <code>find</code>함수를 사용했는데 <code>every</code>함수를 사용해서도 문제를 처리할 수 있다.</p>
<h3 id="3-3-js-class-constructor-overload">3-3. JS class constructor overload</h3>
<p>클래스를 생성하고 객체를 만드는데 <code>constructor</code>를 때때로 다르게 사용하고 싶은 경우가 생겼다. 그래서 검색을 해봤는데 JS 에서는 Java나 다른 언어처러 <strong>constructor overload를 허용하지 않는다</strong>.</p>
<p>그렇다면 객체를 생성할 때 다음과 같은 문제는 어떻게 해결할 수 있을까?</p>
<pre><code class="language-js">const DEFAULT_WHEEL_NUMBER = 4;
const DEFAULT_COLOR = &quot;black&quot;;    
const DEFAULT_NAME = &quot;myCar&quot;;

class Car{
  constructor(numberWheels, aName, aColor){
    this.wheelsNum = numberWheels;
    this.name = aName;
    this.color = aColor;
  }

  constructor(aName){
    this(DEFUALT_WHEEL_NUMBER, aName, DEFAULT_COLOR);
  }

  constructor(){
    this(DEFUALT_WHEEL_NUMBER, DEFAULT_NAME, DEFAULT_COLOR);
  }
}</code></pre>
<p>이렇게 다양한 <code>constructor</code>를 사용하고 싶은 경우에는 <strong>default parameters</strong>를 사용하면 된다.
매개변수에 값이 들어올 때는 그 값을 참조하고 값이 들어오지 않는 경우에는 매개변수에 기본값을 설정하는 것이다. 다음과 같이 구현할 수 있다.</p>
<pre><code class="language-js">class Car {
  constructor(numberWheels = 4, aName = &quot;myCar&quot;, aColor = &quot;black&quot;){
    this.wheelsNum = numberWheels;
    this.name = aName;
    this.color = aColor;
  }
}</code></pre>
<h2 id="4-git">4. Git</h2>
<hr>
<h3 id="4-1-rebase를-이용한-지난-커밋로그-수정">4-1. rebase를 이용한 지난 커밋로그 수정</h3>
<p>의미있는 커밋로그를 만드려고 기능단위로 커밋을 하는 것이 프리코스에서 강조하는 부분 중에 하나이다. 생각보다 이를 잘 실천하는 것이 어려웠는데 2주차가 되면서 기능단위 커밋을 계속해서 신경쓰다보니까 생각보다 이런 방식이 금방 익숙해지는 느낌이 들었다. </p>
<p>이제부터는 어떤 프로젝트를 해도 이렇게 의미있는 커밋로그를 남기는 것이 협업에 매우 중요한 역할을 할 것이라고 생각했고 앞으로는 항상 이런 커밋 습관을 들이는 것을 실천할 것이다.</p>
<p>그런데 이런 커밋습관을 잘 실천하려고 하다보면 실수가 생긱기 마련이다. 한 번은 이미 push까지 한 상황에서 지난 커밋로그에 실수가 있다는 것을 발견했다. 그래서 검색을 해봤는데 <strong>rebase</strong>라는 git 명령어를 통해서 지난 커밋에 대한 내용을 수정할 수 있다는 사실을 새로 배웠다.</p>
<p><code>git rebase -i HEAD~</code> 명령어를 통해서 수정하고자 하는 커밋을 지정하면 그 커밋부터 현재까지의 커밋이 전부 아래와 같이 출력된다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/daa30cac-0f29-4b47-abde-afd25cd9eb4e/rebase.png" alt=""></p>
<p>위 사진을 보면 <strong><code>pick</code></strong> 이라고 각 커밋마다 돼있다. 이 부분을 텍스트에디터로 직접 수정을 해주면 되는데 여기서 만약에 단순히 커밋로그만 수정하려고 한다면 <code>pick</code>을 지우고 <strong><code>reword</code> 라고 수정</strong> 해주면 된다. 그러면 커밋로그를 수정할 수 있는 텍스트 에디터 화면이 뜨고 거기서 수정한 후에 저장을 해주면 된다. 커밋 내용을 수정해야 한다면 <strong><code>edit</code> 으로 수정</strong>해주면 된다.</p>
<p>그러고 나서 파일을 열어 원하는대로 수정을 하고 나서 <code>git add &lt;file&gt;</code> 명령어를 통해 수정한 파일을 <code>add</code>하고 <code>git commit --amend</code> 명령어를 쳐주면 된다. 이제 마지막으로 rebase 과정을 종료하기 위해서 <code>git rebase --continue</code> 명령어를 쳐주면 정상적으로 과거의 커밋 내용이 수정된다.</p>
<p>그런데 나의 경우에는 원격 저장소에 <code>push</code>를 한 상태에서 과거의 커밋을 수정한 경우였다. 이 상태에서 원격 저장소에 다시 <code>push</code>를 하면 에러메세지가 뜬다. 원격 저장소에 저장돼있는 커밋과 다르기 떄문이다. 여기서 강제로 업로드할 수 있는 <code>git push --force</code>명령어로 강제로 현재의 수정된 커밋으로 원격 저장소 내용을 덮어쓸 수 있다.</p>
<p>나는 혼자서 작업하고 있고 단순한 수정이었기 때문에 이렇게 강제로 업로드하는 것이 문제가 되지 않았지만 협업을 할 때는 이렇게 했을 때는 다른 작업을 하고 있는 팀원에게 분명히 문제가 생기기 때문에 협업관계에서는 이렇게 해서는 안 되고 다른 방법을 찾아야 한다. </p>
<h2 id="references">References</h2>
<ul>
<li>모던 자바스크립트 Deep Dive (이웅모 저)</li>
<li><a href="https://github.com/qkraudghgh/clean-code-javascript-ko">클린코드 Javascript</a></li>
<li><a href="https://ko.javascript.info/">모던 자바스크립트 튜토리얼</a></li>
<li><a href="https://techblog.woowahan.com/2502/">우아한형제들 기술블로그 - 생각하라, 객체지향처럼</a></li>
<li><a href="https://uiyoji-journal.tistory.com/101">함수 생성자와 클래스의 차이</a></li>
<li><a href="https://stackoverflow.com/questions/38240744/how-to-overload-constructors-in-javascript-ecma6">JS contructor overload</a></li>
<li><a href="https://backlog.com/git-tutorial/kr/stepup/stepup7_6.html">rebase -i 로 커밋 수정하기</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[우아한테크코스]프리코스 1주차 학습 내용]]></title>
            <link>https://velog.io/@dom_hxrdy/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%EB%82%B4%EC%9A%A9</link>
            <guid>https://velog.io/@dom_hxrdy/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-1%EC%A3%BC%EC%B0%A8-%ED%95%99%EC%8A%B5-%EB%82%B4%EC%9A%A9</guid>
            <pubDate>Tue, 30 Nov 2021 10:52:34 GMT</pubDate>
            <description><![CDATA[<h2 id="1-숫자-야구-게임">1. 숫자 야구 게임</h2>
<hr>
<p>우아한테크코스 4기의 프리코스 1주차 미션은 숫자 야구 게임 구현이다. 구현 내용은 <a href="https://github.com/DomMorello/javascript-baseball-precourse/tree/dom">프리코스 1주차 미션 저장소</a>에 업로드했다.</p>
<p>1주차 미션을 진행하면서 고민한 내용들에 대해 개인적으로 공부하고 정리해보았다.</p>
<br>

<h2 id="2-구현-중에-했던-고민들">2. 구현 중에 했던 고민들</h2>
<hr>
<h3 id="2-1-어떤-기준으로-모듈을-나눠야-할까">2-1. 어떤 기준으로 모듈을 나눠야 할까?</h3>
<p>과제에서 요구하는 항목 중 하나가 모듈로 나눠서 구현을 하라는 것이다. VanillaJS로 프로그램을 만드는 것이 익숙하지 않아서 뭔가 React나 다른 라이브러리처럼 주어진 틀이 없는 기분이었다. 그래서 차근차근히 이 프로그램이 동작하는데 크게 어떤 기능들로 나뉘는지를 생각해보고 적용하려고 했다. 그러다가 알게 된 키워드가 <strong>MVC 패턴</strong>이라는 것이다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/3b039541-73d5-4713-8a89-f4f473c24fe6/mvc.png" alt=""></p>
<p>MVC(Model View Controller)에서 <strong>모델</strong>은 애플리케이션의 정보(데이터)를 나타내며, <strong>뷰</strong>는 텍스트, 체크박스 항목 등과 같은 사용자 인터페이스 요소를 나타내고, <strong>컨트롤러</strong>는 데이터와 비즈니스 로직 사이의 상호동작을 관리한다.</p>
<p>이 패턴을 사용하면 <strong>사용자 인터페이스로부터 비즈니스 로직을 분리</strong>하여 애플리케이션의 시각적 요소나 그 이면에서 실행되는 비즈니스 로직을 서로 영향 없이 쉽게 고칠 수 있는 애플리케이션을 만들 수 있다.</p>
<p>보통 이 패턴을 사용할 때는 백엔드와 프론트엔드 전반에 걸쳐 사용되는 디자인 패턴이다. 그런데 이번 과제에서도 충분히 각 요소에 맞는 기능들을 분리할 수 있을 것 같아서 디렉토리 구조와 모듈을 나누는 기준으로 사용했다.</p>
<ol>
<li>모델: 게임의 중추가 되는 비즈니스 로직을 담당한다. <code>game</code> 디렉토리 안에 있으며 컨트롤러를 통해서 요청한 정보를 받아와 게임을 진행한다.</li>
<li>뷰: <code>view</code> 디렉토리에 있으며 말 그대로 사용자에게 보여지는 UI에 대한 기능들만 모아놨다. (경고창, 게임 재시작 UI 등)</li>
<li>컨트롤러: <code>input</code> 디렉토리에 있으며 사용자가 뷰를 통해 입력하는 값에 대한 유효성 검사를 하고 이 값을 통해 모델에 비즈니스 로직을 실행할 것을 요청한다.</li>
</ol>
<p>사실 백엔드, 프론트엔드 전반에 걸쳐 실행되는 디자인 패턴인데 작은 프로그램 안에서 나름의 기능을 나누고 디렉토리 구조를 나누는 기준으로 사용하다보니 MVC 패턴이 의도하는 정확한 설계가 된 것 같지는 않다.</p>
<p>하지만 이렇게 함으로써 MVC패턴에서 가장 이점인 기능단위로 적절하게 구조가 짜여진 것 같고 각 기능마다 고유의 일만 하기 때문에 그 부분의 코드만 수정해도 다른 부분에는 영향을 주지 않아 리팩토링을 할 때 실제로 편하고 유용하다고 느끼는 경험을 했다.</p>
<h3 id="2-2-첫-설계의-착오">2-2. 첫 설계의 착오</h3>
<p>코드를 짤 때 항상 필요없는 행동을 최소화하기 위해서 노력한다. 그래서 이 게임의 시작은 항상 사용자의 입력이 있는 순간 시작된다는 사실을 인지하고 사용자의 입력을 받는 것을 최우선으로 코드를 짜기 시작했다.</p>
<p>그런데 처음에 구현할 기능 순서대로 목록을 작성했는데 개발을 하다 보니 로직이 잘못 됐다는 것을 깨달았다. 사용자 입력이 있을 때 컴퓨터의 입력을 생성하고 비교하는 게임 로직을 실행하려고 했는데 사용자 입력을 받는 이벤트가 발생할 때마다 그 후에 종속적으로 computerInput을 랜덤생성하면 계속해서 computerInput이 사용자 입력이 있을 때마다 바뀌기 때문에 정답을 맞출 가능성이 현저히 떨어지고 게임의 본질적인 기능을 하지 못한다는 사실을 알았다.</p>
<p>그래서 처음 설계한 로직을 수정하여 컴퓨터의 입력값이 먼저 있는 상태에서 사용자의 입력을 받고 비교하여 게임을 진행하는 로직이 다음으로 오도록 수정했다.</p>
<blockquote>
<p>playGame.js</p>
</blockquote>
<pre><code class="language-js">export default function playGame(play) {
  const $userSubmitButton = document.querySelector(&quot;#submit&quot;);
  const computerInput = getComputerInput();

  $userSubmitButton.addEventListener(&quot;click&quot;, (event) =&gt; {
    event.preventDefault();
    const userInput = getUserInput();
    console.log(&quot;test: &quot;, computerInput, userInput);
  });
}</code></pre>
<p>위 코드와 같이 컴퓨터 입력 값을 먼저 생성한 후에 사용자의 입력값을 받고 그 두 개의 입력값을 비교하여 게임 로직을 실행하는 순서로 수정했다.</p>
<br>

<h3 id="2-3-함수를-기능-단위로-나눠야-한다-vs-구조적으로-가능한-설계를-해야-한다">2-3. 함수를 기능 단위로 나눠야 한다 vs 구조적으로 가능한 설계를 해야 한다</h3>
<p>원래는 이렇게 구조를 생각했다. 기능별로 함수를 나눠야 하기 때문에 <code>getUserInput</code> 함수에서는 userInput의 값을 검사해서 유효하면 얻어오도록 하려고 했다. 그런데 이벤트가 발생하는 시점에 콜백함수를 실행하는데 위와 같이 코드를 짜면 userInput에는 항상 null값만 들어온다. 왜냐하면 다른 곳에서 이 함수를 호출해서 return 값을 받아서 사용하려고 했는데 함수를 실행하는 시점에는 <code>userInput</code>에 null값이 들어가있기 때문이다. 즉, 야구게임의 경우 사용자의 입력을 받는 순간 그 콜백함수에서 모든 게임 로직이 연속되게 실행돼야 한다는 것이다. 이벤트가 발생했을 때마다 새로운 값을 return 받으려고 했지만 구조적으로 불가능하다는 사실을 알아서 다음과 같이 구조를 바꿨다.</p>
<blockquote>
<p>getUserInput.js</p>
</blockquote>
<pre><code class="language-js">export default function getUserInput() {
  const $userSubmitButton = document.querySelector(&quot;#submit&quot;);

  $userSubmitButton.addEventListener(&quot;click&quot;, (event) =&gt; {
    event.preventDefault();
    const userInputArray = document
      .querySelector(&quot;#user-input&quot;)
      .value.split(&quot;&quot;)
      .map((elem) =&gt; +elem);
    if (alertErrorMessage(isValidInput(userInputArray))) {
      compareComputerUserInput(getComputerInput(), userInputArray); //콜백함수 안에서 다음 게임 로직을 연속해서 실행
    }
  });
}</code></pre>
<p>위 코드를 보면 userInput 값을 반환하는 것이 아니라 이 함수 안에서 이벤트가 발생하면 입력값의 유효성을 검사한 후에 게임로직을 이 콜백함수 안에서 연속해서 실행하는 것이다. </p>
<p>게임이 진행되는 방식이 이벤트의 발생에 따라서 결과값을 보여줘야 하기 때문에 이렇게 하는 것이 옳은 구조라고 생각해서 바꾸게 되었다. </p>
<p>하지만 이런 식으로 구조를 만들면 <code>getUserInput</code> 이라는 함수 안에서 게임로직까지 실행되므로 한 기능을 가진 함수를 실행하는 원칙에 어긋나고 유지보수하기 어려운 코드가 될 것이다.</p>
<p>그래서 이벤트리스너를 게임을 시작하는 로직에 모아두고 그 안에서 userInput을 받아오면 한 가지 기능을 하는 함수를 만들 수 있다는 생각이 들었다.</p>
<blockquote>
<p>playGame.js</p>
</blockquote>
<pre><code class="language-js">export default function playGame(play) {
  const $userSubmitButton = document.querySelector(&quot;#submit&quot;);

  $userSubmitButton.addEventListener(&quot;click&quot;, (event) =&gt; {
    event.preventDefault();
    const userInput = getUserInput();
    // userInput만 받아오는 한 가지 기능을 하는 함수
  });
}</code></pre>
<blockquote>
<p>getUserInput.js</p>
</blockquote>
<pre><code class="language-js">export default function getUserInput() {
  let userInput = null;
  const userInputArray = document
    .querySelector(&quot;#user-input&quot;)
    .value.split(&quot;&quot;)
    .map((elem) =&gt; +elem);
  if (alertErrorMessage(isValidInput(userInputArray))) {
    userInput = userInputArray;
  }
  return userInput;
}</code></pre>
<p>위와 같이 <code>playGame</code> 함수에서 전체적인 이벤트리스너 구조를 만들어 놓고 그 콜백함수 안에서 userInput을 받아오면 <code>getUserInput</code> 함수는 userInput을 받아오는 한 가지 기능을 하는 함수로 탈바꿈이 가능했다. 이렇게 구조를 바꾸니까 한 가지 기능을 가진 함수를 만들 수 있었고 구조적으로도 충분히 가능한 코드를 만들 수 있었다.</p>
<br>

<h3 id="2-4-상수는-어디에-위치해야-할까">2-4. 상수는 어디에 위치해야 할까?</h3>
<p>기능을 구현하다 보면 절대 변하지 않을 값들이 있고 이를 하드코딩하기 보다는 의미있는 이름을 지어줘서 상수에 할당해서 사용하는 것이 더 좋을 것이라고 생각했다. </p>
<blockquote>
<p>getComputerInput.js</p>
</blockquote>
<pre><code class="language-js">const COMPUTER_INPUT_LENGTH = 3;

export default function getComputerInput() {
  const computerInput = new Set();

  while (computerInput.size &lt; COMPUTER_INPUT_LENGTH) {
    computerInput.add(window.MissionUtils.Random.pickNumberInRange(1, 9));
  }
  return [...computerInput].join(&quot;&quot;);
}</code></pre>
<p>위 코드를 보면 이 프로그램에서 컴퓨터 입력값은 무조건 3자리여야 한다. 그래서 <code>COMPUTER_INPUT_LENGTH</code>를 3 으로 정해놓고 사용을 했다. 연관있는 변수, 상수, 함수들은 전부 한 파일에 있는 것이 좋을 것이라고 생각해서 처음에 <code>getComputerInput.js</code> 파일 안에 상수를 위치시켜놨다.</p>
<p>그런데 기능 구현을 계속 하다보니까 사용자의 입력값의 길이를 사용해야 하는 경우가 있었다. 그래서 상수의 이름이 적절치 않다는 것을 깨닫고 <code>COMPUTER_INPUT_LENGTH</code> 에서 <code>INPUT_LENGTH</code>로 수정했다. 이 프로그램에서 컴퓨터, 사용자 모두의 입력값의 길이는 세 자리여야 하기 때문이다.</p>
<p>그리고 또 하나 문제가 <code>getComputerInput</code> 함수에서만 사용될 것 같았던 저 상수가 다른 파일의 다른 함수에서도 필요하다는 것을 알게 되었고 그래서 위 코드에서 상수부분에 <strong>export</strong> 를 추가해줬다. 이렇게 하면 다른 파일에서 <strong>import</strong> 해서 사용할 수 있기 때문이다.</p>
<p>그런데 이렇게 상수를 한 파일에 종속시키고 외부에서 필요하면 export 하고 import하는 과정을 반복하다보니 이 상수가 도대체 어디에 종속돼야 하고 어디에 연관성이 깊은 것인지를 알기가 어려워지는 것 같았다.</p>
<p>그래서 만약에 한 파일에서만 사용되는 상수가 아니고 <strong>여러 파일에서 반복적으로 사용되는 상수라면</strong> 독립적인 하나의 파일 안에 상수들만 모아놓는 것이 더 효율적일 것이라고 생각했다. </p>
<p>한 파일안에서만 사용되는 상수라면 그 파일에 상수를 위치시키는 것이 더 좋겠지만 여러 파일에서 사용되는 상수라면 <strong>독립적인 공간</strong>에 두는 것이 export, import 관계도 더 명확해지고 논리적이라고 생각했다. 또한 다른 개발자가 내 코드를 볼 때 contant 라는 디렉토리 안에 상수를 모아놓으면 이 상수들은 여러 곳에서 사용된다는 것을 더 쉽게 유추할 수 있지 않을까라는 생각을 했다.</p>
<p>하지만 1주차 프로그램은 아주 작은 프로그램이라서 더 효율적인 것처럼 보이지만 큰 프로그램의 경우에는 어떤 식으로 상수를 위치시키는지에 대해서는 아직 확신이 없다. 앞으로도 계속 생각해볼 문제라고 생각한다.</p>
<h3 id="2-5-html-태그는-어떤-방식으로-생성해야-할까">2-5. HTML 태그는 어떤 방식으로 생성해야 할까?</h3>
<p>기능을 구현하다가 DOM노드를 건드려야 하는 상황이 왔다. DOM노드를 JS를 통해 조작할 수 있는데 두 가지 방법 중에 무엇을 사용해야 하는지가 고민이었다. <code>createElement</code> 함수를 통해서 노드를 만들고 상위 함수를 찾아서 <code>append</code> 하는 방법이 있고, <code>innerHTML</code> 함수를 이용해서 문자열로 HTML 태그들을 만들어서 넣을 수 있다.</p>
<p>이 부분에 대해서 조사를 해봤는데 <a href="https://www.javascripttutorial.net/javascript-dom/javascript-innerhtml-vs-createelement/">이 포스트</a>에 따르면 <code>createElement</code>로 생성하고 <code>append</code>로 붙이는 것이 성능 상 이점이 있다고 한다. <code>innerHTML</code>은 문자열을 파싱해서 내부적으로 DOM노드를 만들고 붙이는 작업을 하기 때문이다. 또한 <code>innerHTML</code>은 보안상 문제점이 있다. <code>innerHTML</code>의 위험성에 대해서 자세한 설명은 다음 게시글을 참고하면 좋다.</p>
<blockquote>
<p><a href="https://betterprogramming.pub/dom-manipulation-the-dangers-of-innerhtml-602f4119d905">DOM Manipulation and the Dangers of ‘innerHTML’</a><br>
<a href="https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML">MDN - Element.innerHTML Security considerations</a></p>
</blockquote>
<p><code>innerHTML</code>에 성능, 보안상 문제가 있지만 지금 내가 하고 있는 기능은 어떤 중요한 정보를 담고 있지도 않고 단순히 몇 개의 태그를 추가하는 것이기 때문에 가독성을 위해서 <code>innerHTML</code>방식을 채택했다.</p>
<blockquote>
<p>showResult.js</p>
</blockquote>
<pre><code class="language-js">function renderAskRegame() {
  const $resultDivElement = document.getElementById(&quot;result&quot;);
  const $newDivElement = document.createElement(&quot;div&quot;);
  const $newSpanElement = document.createElement(&quot;span&quot;);
  const $newButtonElement = document.createElement(&quot;button&quot;);
  $newDivElement.innerText = &quot;🎉정답을 맞추셨습니다!🎉&quot;;
  $newDivElement.setAttribute(&quot;style&quot;, &quot;font-weight: bold&quot;);
  $newSpanElement.innerText = &quot;게임을 새로 시작하시겠습니까?&quot;;
  $newButtonElement.innerText = &quot;게임 재시작&quot;;
  $newButtonElement.setAttribute(&quot;id&quot;, &quot;game-restart-button&quot;);
  $resultDivElement.append($newDivElement);
  $resultDivElement.append($newSpanElement);
  $resultDivElement.append($newButtonElement);
}</code></pre>
<p>위 코드를 보면 가독성이 별로 좋지 않다. 하지만 <code>innerHTML</code>을 사용하면 아래와 같이 간단하게 바뀔 수 있다.</p>
<blockquote>
<p>showResult.js</p>
</blockquote>
<pre><code class="language-js">function renderAskRestartView() {
  const $resultDiv = document.getElementById(&quot;result&quot;);
  $resultDiv.innerHTML = `
    &lt;div&gt;🎉&lt;strong&gt;정답을 맞추셨습니다!&lt;/strong&gt;🎉&lt;/div&gt;
    &lt;span&gt;게임을 새로 시작하시겠습니까?&lt;/span&gt;
    &lt;button id=&quot;game-restart-button&quot;&gt;게임 재시작&lt;/button&gt;
  `;
}</code></pre>
<p><code>innerHTML</code>을 사용하면 해당 DOM 노드 전체를 다시 파싱하고 만들기 때문에 이벤트 리스너가 있었다면 그 이벤트 리스너가 동작하지 않는 문제도 있다. 하지만 이 프로젝트의 경우 이벤트 리스너는 다른 곳에 있기 때문에 그 부분도 문제가 되지 않았다. 여러 단점에도 불구하고 내 경우에는 큰 문제가 되지 않는 단점들이기 때문에 상황에 맞게 적절하게 선택하면 될 것 같다.</p>
<br>

<h3 id="2-6-call-by-reference-를-활용하는-것은-어떨까">2-6. call by reference 를 활용하는 것은 어떨까?</h3>
<p>기능을 구현하다가 정답을 맞추고 <code>게임 재시작</code> 버튼을 누르면 <strong>computerInput</strong>을 세 자리 랜덤한 숫자로 다시 생성해야 했다.</p>
<blockquote>
<p>gameEventHandler.js</p>
</blockquote>
<pre><code class="language-js">export default function gameEventHandler(play) {
  const $userSubmitButton = document.getElementById(&quot;submit&quot;);
  const $userResultDiv = document.getElementById(&quot;result&quot;);
  let computerInput = getComputerInput();

  $userSubmitButton.addEventListener(&quot;click&quot;, (event) =&gt; {
    event.preventDefault();
    const userInput = getUserInput();
    if (userInput) {
      if (showResult(play(computerInput, userInput)) === ANSWER) {
        computerInput = getComputerInput();
        //게임 정답을 맞추면 computerInput을 다시 생성한다.
      }
    }
  });
}</code></pre>
<p>위 코드에서처럼 정답을 맞추면 <code>computerInput</code>을 갱신하고 다시 게임을 처음부터 진행하도록 하려고 했는데 위 코드대로 하면 depth가 3이 돼버려서 원칙에 어긋나게 돼버린다.</p>
<p>그래서 생각한 것이 C 언어에서처럼 포인터를 매개변수로 넘겨주면 넘겨받은 함수에서 그 값의 주소값을 갖고 있기 때문에 그 값을 변경할 수가 있다. 이런 것을 <strong>call by reference</strong> 라고 하는데 값만 매개변수에 복사해서 인자로 넘기는 것이 아니라 그 값 자체의 메모리주소값을 참조할 수 있도록 넘기는 것이다.</p>
<pre><code class="language-js">...
  $userSubmitButton.addEventListener(&quot;click&quot;, (event) =&gt; {
    event.preventDefault();
    const userInput = getUserInput();
    if (userInput) {
      showResult(play(computerInput, userInput), computerInput);
      //computerInput은 배열이기 때문에 call by reference로 넘어간다.
    }
  });

  //showResult.js
  function showResult(resultString, computerInput) {
    ...
    computerInput = getComputerInput();
    ...
  }
  //두 번째 매개변수로 받은 computerInput을 수정한다.</code></pre>
<p>JS에서는 원시값이 아닌 것들은 매개변수로 넘어갈 때 <strong>call by reference</strong>로 넘어간다. 그래서 메모리 주소값을 참조하게 되므로 넘겨받은 함수에서 수정을 하면 원래의 값에도 영향을 미친다.</p>
<p>그래서 위 코드와 같이 <code>computerInput</code>을 매개변수로 넘기고 <code>showResult</code>함수 내부에서 <code>computerInput</code>을 수정하면 된다.</p>
<p>하지만, <a href="https://github.com/airbnb/javascript">에어비엔비 코드컨벤션</a>에 따르면 위 행동을 금지한다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/f7ca4790-4dbd-4924-8762-a733acf7a829/%E1%84%8B%E1%85%A6%E1%84%8B%E1%85%A5%E1%84%87%E1%85%B5%E1%84%8B%E1%85%A6%E1%86%AB%E1%84%87%E1%85%B5%E1%84%86%E1%85%A2%E1%84%80%E1%85%A2%E1%84%87%E1%85%A7%E1%86%AB%E1%84%89%E1%85%AE.png" alt=""></p>
<ol>
<li>절대로 매개변수를 바꾸지 마세요. eslint: no-param-reassign</li>
<li>절대로 매개변수를 재할당하지 마세요. eslint: no-param-reassign</li>
</ol>
<p>매개변수로 전달된 객체를 조작하면 원래 호출처에서 원치 않는 사이드 이펙트를 일으킬 수 있다. 또한 매개변수를 재할당하는 것은 예측할 수 없는 결과를 불러 일으킨다. 특히 arguments 객체에 접근할 때이다. 또한 V8에서 최적화 문제를 일으킬 수도 있다.</p>
<p>프로젝트를 진행하면서 에어비엔비 컨벤션을 자주 확인하는데 <strong>모든 컨벤션에는 이유가 있다</strong>.</p>
<p>이러한 이유로 위와 같이 하지 않았고 구조를 바꾸는 방식으로 수정했다.</p>
<br>

<h3 id="2-7-변수를-항상-함수의-맨-위에-뒀었는데-다시-생각해보자">2-7. 변수를 항상 함수의 맨 위에 뒀었는데 다시 생각해보자.</h3>
<p>원래 코딩을 할 떄 항상 습관적으로 함수 내부에서만 사용되는 변수들을 선언할 때 항상 위에 모아두는 버릇이 있었다. 정확히 왜 그렇게 하기 시작했는지 모르겠는데 함수 중간에 <code>const a = 10;</code> 이런 식으로 들어가있는 게 상당히 보기 싫었다.</p>
<p>그런데 기능 구현을 하다가 이런 경우가 있었다.</p>
<blockquote>
<p>showResult.js</p>
</blockquote>
<pre><code class="language-js">export default function showResult(resultString) {
  const $resultDiv = document.getElementById(&quot;result&quot;);

  if (resultString === ANSWER) {
    renderAskRestartView();
    return;
  }
  $resultDiv.innerText = resultString;
}</code></pre>
<p>원래 내 코딩 스타일대로 했다면 위와 같이 했을 것이다. 그런데 위 함수의 로직을 보면 <code>resultString === ANSWER</code>인 경우에 다른 함수를 실행하고 이 함수는 <code>return</code>을 통해서 끝내버린다.
그렇게 되면 맨 위에 선언했던 <code>const $resultDiv = document.getElementById(&quot;result&quot;);</code> 이 상수 대입 연산은 실행해놓고 사용하지를 않고 함수가 끝나는 것이다. 게다가 DOM node를 불러오는 연산이기 때문에 다른 간단한 대입 연산보다는 분명히 더 무거운 행동일 것이다. 코드 스타일상 모든 변수,상수가 그 함수에서 사용된다면 맨 위에 모아서 선언하는 것은 좋은 방법이라고 생각한다. 하지만 위와 같은 경우에서는 예외처리를 먼저 해주고 변수, 상수를 선언하는 것이 맞는 것 같다. </p>
<blockquote>
<p>showResult.js</p>
</blockquote>
<pre><code class="language-js">export default function showResult(resultString) {
  if (resultString === ANSWER) {
    renderAskRestartView();
    return;
  }
  const $resultDiv = document.getElementById(&quot;result&quot;);
  $resultDiv.innerText = resultString;
}</code></pre>
<p>그래서 위와 같이 예외처리를 먼저 해주고 그 다음에 <code>const $resultDiv = document.getElementById(&quot;result&quot;)</code> 와 같이 선언 및 대입을 해주는 것이 더 맞는 방법이라고 생각한다. </p>
<br>

<h2 id="3-javascript-기초">3. Javascript 기초</h2>
<hr>
<p>VanillaJS 를 충분히 공부하지 않고 React를 다루다 보니 JS에 대한 기본기 훈련이 잘 안 돼있다고 생각한다. 그래서 이번 프리코스를 통해서 부족했던 JS 기본 역량을 보완하는 것이 목표 중에 하나이다. <strong>모던 자바스크립트 Deep dive</strong> 와 여러 레퍼런스를 참고해서 잘 몰랐던 내용에 대해서 정리하면서 학습했다.</p>
<h3 id="3-1-new와-생성자-함수">3-1. new와 생성자 함수</h3>
<pre><code class="language-js">export default function BaseballGame() {
  // this = {};  (빈 객체가 암시적으로 만들어짐)

  this.play = function (computerInputNumbers, userInputNumbers) {
    return &quot;결과 값 String&quot;;
  };

  // return this;  (this가 암시적으로 반환됨)
}</code></pre>
<p>위 코드는 미션에서 주어지는 코드인데 <code>this</code> 키워드를 이용해서 함수를 정의하고 있고 함수의 컨벤션이 대문자로 시작하는 것으로 보아 생성자 함수임을 알 수 있다.</p>
<p>new BaseballGame()과 같이 코드를 작성하면</p>
<ol>
<li>빈 객체를 만들어 this에 할당한다.</li>
<li>함수 본문을 실행한다. 위의 경우 this에 새로운 프로퍼티(play 함수)를 추가해 this를 수정한다.</li>
<li>this를 반환한다.</li>
</ol>
<br>

<h3 id="3-2-this">3-2. this</h3>
<pre><code class="language-js">let user = {
  firstName: &quot;dom&quot;,
  sayHi() {
    let arrow = () =&gt; alert(this.firstName);
    arrow();
  }
};

user.sayHi(); // dom</code></pre>
<p>화살표 함수는 일반 함수와는 다르게 <code>this</code>를 고유하게 갖지 않는다. 위와 같이 화살표 함수 내부에서 <code>this</code>를 쓰면 더 상위의 범위에서 <code>this</code>를 참조한다. 위 경우에서는 user 객체를 가리킨다.</p>
<p>즉, 별개의 this가 만들어지는 건 원하지 않고, 외부 컨텍스트에 있는 this를 이용하고 싶은 경우 화살표 함수가 유용하다.</p>
<br>

<h3 id="3-3-foreach-문에서-조건-만족시-break-하기">3-3. forEach 문에서 조건 만족시 break 하기</h3>
<p><code>forEach</code> 반복문을 사용해서 검사를 하면서 어떤 조건을 만족하면 반복을 멈추고 <code>break</code>나 <code>return</code>을 사용해서 나오고 싶을 것이다. 조건을 찾았는데 계속해서 반복문을 찾는 것은 무의미하기 때문이다.</p>
<pre><code class="language-js">const a = [1,2,3,4];

a.forEach((element) =&gt; {
    console.log(element);
    if (element === 2) return;
});

//outut
//1
//2
//3
//4</code></pre>
<p>하지만 <code>forEach</code> 구문에서 어떤 조건을 만족했다고 해서 <code>return</code>을 해봤자 <code>forEach</code> 구문 내부의 콜백함수기 때문에 그 콜백함수만 빠져나오는 것이지 아무 의미가 없다. 위 코드를 실행해보면 1, 2 까지만 출력하고 3, 4는 실행하지 않을 것이라고 생각할 수도 있지만 이는 틀리다.</p>
<blockquote>
<p>예외를 던지지 않고는 forEach()를 중간에 멈출 수 없습니다. 중간에 멈춰야 한다면 forEach()가 적절한 방법이 아닐지도 모릅니다.<br><br>
MDN – forEach</p>
</blockquote>
<p>MDN 설명에 나와있듯이 <code>forEach</code> 에서 중간에 반복을 멈추고 나올 방법은 없다. 조건을 검색하는 반복에서는 이 함수를 사용하는 것이 적절하지 않다.</p>
<br>

<h3 id="3-4-옵셔널-체이닝-연산자">3-4. 옵셔널 체이닝 연산자</h3>
<blockquote>
<p>ES11(ECMANSript2020)에서 도입된 옵셔널 체이닝 연산자 <code>?.</code> 는 좌항의 피연산자가 null 또는 undefined인 경우 undefined를 반환하고, 그렇지 않으면 우항의 프로퍼티 참조를 이어간다.<br><br>
모던 자바스크립트 Deep Dive p.122</p>
</blockquote>
<p>사용자의 입력값인 <code>userInput</code> 을 받아오는데 여기서 유효하지 않은 값이 들어오면 <code>userInput</code>이 <code>null</code>이 되도록 했다. 그런데 return을 할 때 <code>userInput.join(&quot;&quot;)</code> 을 하는데 여기서 <code>userInput</code>이 null 이면 에러가 나는 상황에 봉착했다.</p>
<p>그래서 return을 하기 전에 null 검사를 해야 하는 상황인데 여기서 옵셔널 체이닝 연산자를 쓰지 않으면 다음과 같이 했었어야 했다.</p>
<pre><code class="language-js">export default function getUserInput() {
  let userInput = null;
  const userInputArray = {
    ...
    userInput = userInputArray;
  }
  return userInput &amp;&amp; userInput.join(&quot;&quot;);
  //여기서 userInput이 null일 수도 있기 때문에 &amp;&amp; 연산자를 통해 검사
}</code></pre>
<p>그런데 옵셔널 체이닝 연산자를 이용하면 다음과 같이 수정할 수 있다.</p>
<pre><code class="language-js">export default function getUserInput() {
  let userInput = null;
  const userInputArray = {
    ...
    userInput = userInputArray;
  }
  return userInput?.join(&quot;&quot;);
  //옵셔널 체이닝 연산자 사용
}</code></pre>
<p>위 코드와 같이 <strong>옵셔널 체이닝 연산자</strong>를 사용하면 userInput이 null이거나 undefined이면 undefined를 반환하고 아니라면 우항의 프로퍼티를 참조한다.</p>
<br>

<h2 id="4-git">4. git</h2>
<hr>
<h3 id="4-1-git-reset---hard-실수">4-1. git reset --hard 실수</h3>
<p>여러 디렉토리가 있는 프로젝트를 진행하는 중에 현재 디렉토리의 위치를 모르고 <code>git add .</code> 명령어를 통해서 commit 을 하는 경우가 있다. 그런데 add할 파일들이 여러 개가 있는데 현재 디렉토리의 위치가 하위 디렉토리에 있다는 것을 모르고 전체가 다 add된 줄 알고 진행을 해버렸다. </p>
<p>결국엔 commit에 포함돼야 할 상위 디렉토리의 파일들을 제외하고 commit을 해버려서 기능 단위의 커밋 로그 작성이 실패한 적이 있었다.</p>
<p>그래서 commit을 취소하는 방법에 대해서 조사를 하다가 <strong>git reset</strong> 명령어에 대해서 공부하게 됐다.</p>
<p><code>git reset</code> 과 <code>git checkout</code>의 차이는 reset은 브랜치를 바꾸지 않고 현재 가리키고 있는 헤드의 위치를 바꾸는 것이다. 이와는 다르게 checkout은 브랜치 자체를 바꿔버린다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/c6dd2503-c3f8-46e6-a4af-cb518e8c7ed8/gitResetSoft.png" alt=""></p>
<p>위 그림을 이해하기 위한 설명을 하자면 아래 쪽에 있는 HEAD, Index, Working Directory를 각각 설명하자면,</p>
<blockquote>
<p><strong>HEAD</strong>는 현재 브랜치가 가리키는 포인터이다. 가장 최근에 commit한 내용을 가리키고 있다.<br>
<strong>Index</strong>는 바로 다음에 커밋할 것들이다. 즉, staging area를 말한다.즉, <code>git add</code> 까지 한 파일들을 말한다.<br>
<strong>Working Directory</strong>는 현재 로컬에서 가장 최근까지 수정한 코드들을 담고 있는 파일들이다. <br></p>
</blockquote>
<p>위 그림에서 보다시피 <code>git reset --soft HEAD~</code> 를 하면 브랜치의 헤드의 위치만 원하는 commit 으로 되돌린다. 그림 아래 쪽에 Index와 Working Directory 를 보면 변화가 없이 그대로 있고 HEAD 부분만 이전의 commit으로 돌아간 것을 확인할 수 있다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/4e154374-8d4b-4021-a0d5-bd75749ba214/gitResetMixed.png" alt=""></p>
<p>그런데 옵션을 주지 않으면 기본값을 <code>git reset --mixed HEAD~</code> 명령어를 실행하게 된다. </p>
<p>위 그림에서 보면 헤드가 이전 커밋으로 이동하는 것 뿐 아니라 <code>soft</code>옵션과 다르게 Index 가 이전 커밋 내용으로 변경되는 것이다. 이 말은 staging area를 비운다는 것이다. 즉, <code>git add</code>, <code>git commit</code> 한 행동까지 되돌린다는 것이다. 하지만 working directory가 그대로 있다는 얘기는 파일이 수정된 것은 로컬에 그대로 남아있다는 것을 의미한다.</p>
<p>그렇다면 내가 실수한 <code>git reset --hard</code> 는 무엇일까?</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/e412ede1-c8f2-42e0-bde1-96fb8ae459dc/gitResetHard.png" alt=""></p>
<p>위 그림을 보면 Working Directory까지 그 전 커밋 내용으로 변경된 것을 볼 수 있다. 즉, 내가 작업하고 있는 로컬 환경에서도 과거로 이동하는 것이다. 그런데 이 명령어는 매우 위험하다. git에서 실제로 데이터를 삭제해버리는 경우는 별로 없는데 이 경우가 데이터를 삭제하는 경우 중의 하나이다. </p>
<p>나는 마지막 커밋을 수정하기 위해서 <code>--hard</code>옵션을 줬는데 이렇게 했더니 최근에 수정한 코드들이 전부 날아가 버렸다. </p>
<p>이제 <code>git reset</code> 명령어에 대해서 다시 공부를 해서 그런 실수를 하는 일이 없도록 했다.</p>
<br>

<h2 id="5-클린코드">5. 클린코드</h2>
<hr>
<h3 id="5-1-함수는-하나의-행동만-해야-한다">5-1. 함수는 하나의 행동만 해야 한다</h3>
<p><a href="https://github.com/qkraudghgh/clean-code-javascript-ko">클린코드 Javascript</a>에 따르면 다음과 같은 예시를 주고 있다.</p>
<blockquote>
<p>안 좋은 예</p>
</blockquote>
<pre><code class="language-js">function emailClients(clients) {
  clients.forEach(client =&gt; {
    const clientRecord = database.lookup(client);
    if (clientRecord.isActive()) {
      email(client);
    }
  });
}</code></pre>
<p>위 코드를 보면 <code>emailClients</code> 함수에서 하는 행동은</p>
<ol>
<li>클라이언트가 활성화돼있는지를 검사</li>
<li>이메일 보내기</li>
</ol>
<p>이렇게 두 가지로 볼 수 있다.</p>
<blockquote>
<p>좋은 예</p>
</blockquote>
<pre><code class="language-js">function emailClients(clients) {
  clients
    .filter(isClientActive)
    .forEach(email);
}

function isClientActive(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}</code></pre>
<p>하지만 위 코드에서 수정된 사항을 보면 <code>emailClients</code> 함수에서 활성화돼있는 고객을 걸러서 이메일을 보낸다. 그리고 <code>isClientActive</code> 라는 함수를 분리해서 활성화돼있는지를 검사하는 로직을 이 함수 안에 넣었다.</p>
<p><strong>좋은 예</strong>로 수정을 하면서 이메일을 보내는 행동과 클라이언트가 활성화돼있는지를 검사하는 행동을 함수로 나눈 것을 알 수 있다.</p>
<p>이 원칙을 최대한 지키려고 기능 구현을 하는데 생각보다 어떤 <strong>행동</strong>을 나누는 기준이 상당히 모호하다는 생각을 했다. 어떻게든 논리적으로 함수를 분리할 수 있는대로 분리한다면 한 가지의 기능을 하는 함수로 나누는 목표를 달성할 수 있지 않을까 생각했지만 아직도 이 방법에 대해서는 내가 잘 하고 있는지에 대한 확신이 없다.</p>
<blockquote>
<p>getComputerInput.js</p>
</blockquote>
<pre><code class="language-js">export default function getComputerInput() {
  const computerInput = new Set();
  while (computerInput.size &lt; INPUT_LENGTH) {
    computerInput.add(window.MissionUtils.Random.pickNumberInRange(1, 9));
  }
  return Number([...computerInput].join(&#39;&#39;));
}</code></pre>
<p>위 코드를 보면 중복없이 Set에 랜던생성한 숫자를 <code>add</code> 해서 중복없는 세 자리의 number를 반환한다. 이 경우에는 <code>getComputerInput</code> 함수 이름에 걸맞게 한 가지 행동을 하는 것 같다.</p>
<p>하지만, 아래의 경우를 한 번 살펴보자.</p>
<blockquote>
<p>getGameResult.js</p>
</blockquote>
<pre><code class="language-js">export default function getGameResult(computerInput, userInput) {
  if (computerInput === userInput) return ANSWER;
  const { ballCount, strikeCount } = countBallStrike(
    String(computerInput).split(&#39;&#39;),
    String(userInput).split(&#39;&#39;)
  );
  if (ballCount === 0 &amp;&amp; strikeCount === 0) return NOTHING;
  if (ballCount === 0 &amp;&amp; strikeCount !== 0) return `${strikeCount}스트라이크`;
  if (ballCount !== 0 &amp;&amp; strikeCount === 0) return `${ballCount}볼`;
  return `${ballCount}볼 ${strikeCount}스트라이크`;
}</code></pre>
<p>위 코드는 단순하지가 않다. <code>getGameResult</code> 함수에서는 경우에 따라 게임의 결과를 string 으로 반환한다. 하지만 이 함수 안에서 <code>ballCount</code>, <code>strikeCount</code> 를 각각 구해서 그 값에 따라서 다른 결과물을 보여줘야 했다. 그렇다면 이 함수는 <strong>볼, 스트라이크 개수를 구하는 행동</strong>과 <strong>결과값을 반환하는 행동</strong>으로 두 가지 행동을 하는 걸까? 이러한 의문에 대한 기준이 명확하지 않은 것 같다.</p>
<p>하지만 위 의문을 어느 정도 해소해주는 클린코드 원칙(<strong>5-2.</strong>)이 있었다.</p>
<h3 id="5-2-함수는-단일-행동을-추상화-해야한다">5-2. 함수는 단일 행동을 추상화 해야한다</h3>
<p>함수를 만들다보면 그 함수 내부에서 여러 가지의 일을 수행하고 그 일의 결과값에 따라서 어떤 행동을 해야 하는 다소 추상적이고 복잡한 함수를 만들어야 할 때가 있다.</p>
<p>과연 그런 경우에는 한 가지 기능을 하는 함수인가라고 의문이 들었는데 다음 예를 보면서 어느 정도 해소가 되었다.</p>
<blockquote>
<p>안 좋은 예</p>
</blockquote>
<pre><code class="language-js">function parseBetterJSAlternative(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(&#39; &#39;);
  const tokens = [];
  REGEXES.forEach(REGEX =&gt; {
    statements.forEach(statement =&gt; {
      // ...
    });
  });

  const ast = [];
  tokens.forEach(token =&gt; {
    // lex...
  });

  ast.forEach(node =&gt; {
    // parse...
  });
}</code></pre>
<p>위의 함수를 보면 여러 가지의 복합적인 행동을 한 함수 안에서 수행해야 하는 경우이다. 그런데 함수를 분리하지 않고 한 함수 내에서 코드로써 모든 행동들을 연속해서 나열하고 있다.</p>
<blockquote>
<p>좋은 예</p>
</blockquote>
<pre><code class="language-js">function tokenize(code) {
  const REGEXES = [
    // ...
  ];

  const statements = code.split(&#39; &#39;);
  const tokens = [];
  REGEXES.forEach(REGEX =&gt; {
    statements.forEach(statement =&gt; {
      tokens.push( /* ... */ );
    });
  });

  return tokens;
}

function lexer(tokens) {
  const ast = [];
  tokens.forEach(token =&gt; {
    ast.push( /* ... */ );
  });

  return ast;
}

function parseBetterJSAlternative(code) {
  const tokens = tokenize(code);
  const ast = lexer(tokens);
  ast.forEach(node =&gt; {
    // parse...
  });
}</code></pre>
<p>위 코드에서는 기능들을 나눠서 복합적인 기능을 하는 함수(<code>parseBetterJSAlternative</code>)내부에서 호출해서 사용하는 것을 볼 수 있다. 그렇다면 다소 추상적인 함수도 당연히 있을 수 밖에 없는 것이고 그 함수 내에서 기능을 나눌 수 있다면 여러 함수를 나눠서 사용한다면 테스트할 때도 함수 단위로 좋고 가독성도 더 좋을 것이다.</p>
<br>

<h3 id="5-3-조건문을-캡슐화-해라">5-3. 조건문을 캡슐화 해라</h3>
<blockquote>
<p>안 좋은 예</p>
</blockquote>
<pre><code class="language-js">if (fsm.state === &#39;fetching&#39; &amp;&amp; isEmpty(listNode)) {
  // ...
}</code></pre>
<blockquote>
<p>좋은 예</p>
</blockquote>
<pre><code class="language-js">function shouldShowSpinner(fsm, listNode) {
  return fsm.state === &#39;fetching&#39; &amp;&amp; isEmpty(listNode);
}

if (shouldShowSpinner(fsmInstance, listNodeInstance)) {
  // ...
}</code></pre>
<p>단순한 조건물을 함수로 만들어서 얻는 이점은 무엇일까? 바로 함수명을 적절하게 지어줌으로써 다른 개발자가 이 조건절을 봤을 때 함수명으로 어떤 행동을 할 지 예측 가능하게 해준다는 것이다. 그래서 이번 프로젝트에서 조건문을 캡슐화했다가 너무 당연한 부분이라서 다시 함수를 지우고 풀어서 조건문 안에 코드로 넣었었는데 이 부분을 다시 수정했다. 함수명을 적절하게 작성해준다면 함수 내부를 보지 않고도 유추가 가능하고 좀 더 가독성이 좋은 코드가 될 것이다.</p>
<blockquote>
<p>getGameResult.js</p>
</blockquote>
<pre><code class="language-js">export default function getGameResult(computerInput, userInput) {
  if (computerInput === userInput) return ANSWER;
  ...
}</code></pre>
<p>원래는 위 코드처럼 <code>computerInput</code>과 <code>userInput</code>을 비교해서 정답인 경우를 반환하는 코드였는데 조건 캡슐화를 위해서 다음과 같이 수정했다.</p>
<blockquote>
<p>getGameResult.js</p>
</blockquote>
<pre><code class="language-js">function isComputerUserInputSame(computerInput, userInput) {
  return computerInput === userInput;
}

export default function getGameResult(computerInput, userInput) {
  if (isComputerUserInputSame(computerInput, userInput)) return ANSWER;
  ...
}</code></pre>
<p><code>isComputerUserInputSame</code>이라는 이름의 함수를 만들어서 캡슐화를 하고 함수명을 통해서 의미가 명확하게 전달되도록 했다. 물론 위 같은 경우는 굳이 캡슐화를 하지 않아도 의미가 명확할 수는 있지만 이런 습관을 들인다는 것이 클린코드를 작성하는 첫 걸음이라고 생각한다.</p>
<hr>
<h2 id="references">References</h2>
<ul>
<li>모던 자바스크립트 Deep Dive (이웅모 저)</li>
<li>클린코드 (Robert C. Martin 저)</li>
<li><a href="https://ko.javascript.info/">모던 자바스크립트 튜토리얼</a></li>
<li><a href="https://git-scm.com/book/ko/v2/Git-%EB%8F%84%EA%B5%AC-Reset-%EB%AA%85%ED%99%95%ED%9E%88-%EC%95%8C%EA%B3%A0-%EA%B0%80%EA%B8%B0">git reset 자세히 알기</a></li>
<li><a href="https://stackoverflow.com/questions/11550461/when-do-you-use-dom-based-generation-vs-using-strings-innerhtml-jquery-to-gener">DOM 생성 방법에 대한 스택오버플로우 문답</a></li>
<li><a href="https://www.javascripttutorial.net/javascript-dom/javascript-innerhtml-vs-createelement/">createElement vs innerHTML</a></li>
<li><a href="https://betterprogramming.pub/dom-manipulation-the-dangers-of-innerhtml-602f4119d905">DOM Manipulation and the Dangers of ‘innerHTML’</a><br></li>
<li><a href="https://developer.mozilla.org/en-US/">MDN</a></li>
<li><a href="https://ko.wikipedia.org/wiki/%EB%AA%A8%EB%8D%B8-%EB%B7%B0-%EC%BB%A8%ED%8A%B8%EB%A1%A4%EB%9F%AC">MVC 패턴</a></li>
<li><a href="https://github.com/qkraudghgh/clean-code-javascript-ko">클린코드 javascript</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[vscode]live server 무한 새로고침 되는 상황 해결(infinite reload / refresh)]]></title>
            <link>https://velog.io/@dom_hxrdy/vscodelive-serve-%EB%AC%B4%ED%95%9C-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8-%EB%90%98%EB%8A%94-%EC%83%81%ED%99%A9-%ED%95%B4%EA%B2%B0infinite-reload-refresh</link>
            <guid>https://velog.io/@dom_hxrdy/vscodelive-serve-%EB%AC%B4%ED%95%9C-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8-%EB%90%98%EB%8A%94-%EC%83%81%ED%99%A9-%ED%95%B4%EA%B2%B0infinite-reload-refresh</guid>
            <pubDate>Thu, 25 Nov 2021 09:48:09 GMT</pubDate>
            <description><![CDATA[<h2 id="상황">상황</h2>
<p>vscode로 간단한 vanillaJS 코드를 사용하려고 하는데 모듈을 사용해야 하는 상황이었다.
<code>file://</code> 프로토콜은 <code>import</code>, <code>export</code> 키워드를 알 수가 없다. 그래서 다른 프로토콜을 사용해야 하는데 이를 쉽게 해주는 vscode    플러그인이 있는데 바로 <strong>live server</strong> 라는 플러그인이다.</p>
<p>이 플러그인을 vscode에서 설치를 하고 <strong>Go Live</strong> 버튼을 누르면 웹 브라우저에 http 프로토콜로 서버가 돌아가게 된다. 
그래서 import, export를 사용하는 모듈을 쉽게 사용할 수 있고 아주 간단한 개발 환경을 구축할 수 있다.</p>
<h2 id="에러">에러</h2>
<p>그런데 정말 신기하게도 live server를 실행시키면 브라우저에서 내 html파일이 정상적으로 켜지는데 계속해서 무한하게 새로고침이 되는 것이다.
이 문제를 해결하기 위해서 몇 시간 동안이나 구글링을 했지만 해결책을 찾을 수가 없었다.</p>
<p>그러다가 컴퓨터 네트워크의 문제인가 싶어서 다른 컴퓨터로 해보는데 잘 되는 것이었다. 원인을 찾아보는데 그러다가도 잘 안 될 때가 많았다.
정말 어처구니가 없게도 아주 간단한 방법으로 해결이 되었다.
막상 해결책을 찾고 나니까 이런 차이로 하나는 치명적인 실행오류가 생기고 다른 하나는 정상적으로 작동한다는 사실이 참 안타까웠다.</p>
<p>나같은 경우는 내가 <strong>live server를 실행하는 컨텍스트</strong>가 문제가 됐었다.
vscode를 켜면 open folder 를 통해서 워크스페이스를 열 수가 있다. 그런데 나는 왼쪽에 디렉토리들을 통해서 쉽게 들어갔다 나갔다 하기 위해서 내가 지금 하고 있는 프로젝트 디렉토리가 아닌 상위 디렉토리에서 open folder를 해서 워크스페이스로 해 놓고 있었다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/4e7df503-a7f4-41fa-9dbc-5de8815dcc33/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.39.51.png" alt=""></p>
<p>위 그림에서 내가 작업해야 하는 프로젝트 디렉토리는 현재 선택돼있는 <code>javascript-baseball-p...</code> 이라는 디렉토리이다. 그런데 위 캡쳐본에서 볼 수 있듯이 한 단계 상위 폴더에서 워크스페이스를 열었다는 것을 알 수 있다. 이 상태에서 프로젝트 디렉토리 내부 <code>index.html</code> 을 live server로 여니까 무한 새로고침되는 문제가 발생했었다. </p>
<h2 id="해결">해결</h2>
<p>팀원과 함께 해결하려고 이것저것 해보다가 우연히 알게 된 사실인데 워크스페이스를 위와 같이 하니까 이상하게 작동한 것이었다.
<img src="https://images.velog.io/images/dom_hxrdy/post/f7fd8843-0d1b-463d-88a8-6154ce678cf8/%E1%84%89%E1%85%B3%E1%84%8F%E1%85%B3%E1%84%85%E1%85%B5%E1%86%AB%E1%84%89%E1%85%A3%E1%86%BA%202021-11-25%20%E1%84%8B%E1%85%A9%E1%84%92%E1%85%AE%206.43.58.png" alt=""></p>
<p>위와 같이 file-&gt;open 에서 다시 선택해서 내가 live server로 열고자 하는 파일이 들어있는 프로젝트 루트경로를 워크스페이스로 다시 연 후에 다시 live server 를 실행시키면 무한히 새로고침되는 이상한 동작이 사라진다. </p>
<p>이렇게 작은 차이로 인해서 치명적인 이상동작과 정상동작이 나뉜다는 사실에 설명할 수 없는 감정을 느꼈다. 나와 같은 경험을 하는 사람이 이 게시글을 보고 나와 같은 시간낭비를 하지 않길 바랄 뿐이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[TypeScript] click() 함수 실행시 타입 에러 해결하기]]></title>
            <link>https://velog.io/@dom_hxrdy/TypeScript-click-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89%EC%8B%9C-%ED%83%80%EC%9E%85-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dom_hxrdy/TypeScript-click-%ED%95%A8%EC%88%98-%EC%8B%A4%ED%96%89%EC%8B%9C-%ED%83%80%EC%9E%85-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 27 Aug 2021 16:25:52 GMT</pubDate>
            <description><![CDATA[<h3 id="배경">배경</h3>
<pre><code class="language-JSX">    &lt;div id=&quot;user-id&quot;&gt;
        &lt;input className=&quot;mf-edit-nick&quot; /&gt;
        &lt;span className=&quot;mf-nick&quot;&gt;domHardy&lt;/span&gt;
        &lt;img src=&quot;/public/check.png&quot;/&gt;
    &lt;/div&gt;</code></pre>
<p>대략적으로 이러한 html 코드가 있는데 여기서 img를 클릭하면 input에 <strong>click</strong> 이벤트가 자동으로 실행돼서 input에 있던 텍스트들이 전부 하이라이트 되도록 하고 싶었다. </p>
<p>다시 말해서, 닉네임 수정 버튼을 눌렀을 때 현재 닉네임이 input 창에 표시되는데 그 텍스트가 전부 드래그돼서 하이라이트 된 상태이고 그 상태에서 글자 하나만 눌러도 원래 input 창에 있는 텍스트가 전부 사라지고 새롭게 입력하는 문자가 들어가도록 하고 싶었다.</p>
<h3 id="문제">문제</h3>
<p>일단 의도한대로 하기 위해서는 input 창에 자동으로 click 이벤트를 발생시켜야 했다. 클릭을 하지 않더라도 click 이벤트가 실행되도록 강제해야 했다. </p>
<pre><code class="language-js">document.getElementsByClassName(&quot;mf-edit-nick&quot;)[0].click();</code></pre>
<p>그래서 이렇게 했더니 typeScript 에서 에러가 발생했다. 에러 메세지는
<code>Property &#39;click&#39; does not exist on type &#39;Element&#39;</code>
위와 같다. </p>
<p>자바스크립트의 <code>click()</code> 함수 <a href="https://developer.mozilla.org/ko/docs/Web/API/HTMLElement/click">MDN 공식문서</a>를 확인해보면 <code>HTMLElement.click()</code> 이라고 제목이 돼있다. 이 부분에서 문제가 생겼던 것이다. </p>
<h3 id="해결방법">해결방법</h3>
<p>타입을 잘 정해주면 문제가 해결되는 것이었다.</p>
<pre><code class="language-js">const editInput : HTMLElement = document.getElementsByClassName(&quot;mf-edit-nick&quot;)[0] as HTMLElement;

editInput.click();</code></pre>
<p>위 코드와 같이 <strong>HTMLElement</strong> 타입을 제대로 명시해주면 <code>click()</code> 함수를 확인하고 에러를 발생시키지 않는다.</p>
<h5 id="출처">출처:</h5>
<p>(<a href="https://stackoverflow.com/questions/4067469/selecting-all-text-in-html-text-input-when-clicked?rq=1">https://stackoverflow.com/questions/4067469/selecting-all-text-in-html-text-input-when-clicked?rq=1</a>)
(<a href="https://stackoverflow.com/questions/46204003/trigger-click-in-typescript-property-click-does-not-exist-on-type-element">https://stackoverflow.com/questions/46204003/trigger-click-in-typescript-property-click-does-not-exist-on-type-element</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[withRouter 사용시 location, match undefined 문제]]></title>
            <link>https://velog.io/@dom_hxrdy/withRouter-%EC%82%AC%EC%9A%A9%EC%8B%9C-location-match-undefined-%EB%AC%B8%EC%A0%9C</link>
            <guid>https://velog.io/@dom_hxrdy/withRouter-%EC%82%AC%EC%9A%A9%EC%8B%9C-location-match-undefined-%EB%AC%B8%EC%A0%9C</guid>
            <pubDate>Thu, 12 Aug 2021 13:56:45 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 진행하는 중에 <strong>withRouter</strong>를 사용해야 하는 일이 생겼다. 그래서 아래와 같이 컴포넌트를 만들었다.</p>
<pre><code class="language-ts">import { BrowserRouter, Link, Route, RouteComponentProps, Switch, useHistory, withRouter } from &quot;react-router-dom&quot;;
...

const NavBar: FC&lt;navBarProps &amp; RouteComponentProps&gt; = (props, {match, location}): JSX.Element =&gt; {
...
  console.log(&quot;test: &quot;, match, location);
  /* 이렇게 했을 때 콘솔창에는 undefined가 나온다. */
  return (
    &lt;nav className=&quot;menu&quot;&gt;
      &lt;BrowserRouter&gt;
      ...
      &lt;/BrowserRouter&gt;
    &lt;/nav&gt;
  );
};

export default withRouter(NavBar);</code></pre>
<p>위와 같이 코드를 짰을 때 <code>console.log()</code> 부분에서 undefined가 나온다. 이유를 찾아보니까 아주 단순한 사실이었다. match, location, history 는 withRouter 를 사용할 때 <strong>props</strong>로 담겨서 넘어오는 <strong>props</strong>이다. </p>
<p>그렇기 때문에 위 코드와 같이 props를 인자로 받아오는 부분에 <code>(props, {match, location})</code> 이런 식으로 해서 undefined가 나오는 것이었다.</p>
<p>그래서 그냥 인자를 받아올 때 단순히 props만 놔두고 <code>props.location</code> 과 같이 접근하면 원하는 값을 얻을 수 있다.</p>
<pre><code class="language-ts">import { BrowserRouter, Link, Route, RouteComponentProps, Switch, useHistory, withRouter } from &quot;react-router-dom&quot;;
...

const NavBar: FC&lt;navBarProps &amp; RouteComponentProps&gt; = (props): JSX.Element =&gt; {
...
  console.log(&quot;test: &quot;, props.match, props.location);
  /* 이제는 원하는 결과가 나온다 */
  return (
    &lt;nav className=&quot;menu&quot;&gt;
      &lt;BrowserRouter&gt;
      ...
      &lt;/BrowserRouter&gt;
    &lt;/nav&gt;
  );
};

export default withRouter(NavBar);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Cannot start service backend: Ports are not available: listen tcp 0.0.0.0:3001: bind: An attempt was made to access a socket in a way forbidden by its access permissions. 에러 해결하기]]></title>
            <link>https://velog.io/@dom_hxrdy/Cannot-start-service-backend-Ports-are-not-available-listen-tcp-0.0.0.03001-bind-An-attempt-was-made-to-access-a-socket-in-a-way-forbidden-by-its-access-permissions.-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@dom_hxrdy/Cannot-start-service-backend-Ports-are-not-available-listen-tcp-0.0.0.03001-bind-An-attempt-was-made-to-access-a-socket-in-a-way-forbidden-by-its-access-permissions.-%EC%97%90%EB%9F%AC-%ED%95%B4%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 05 Aug 2021 14:25:33 GMT</pubDate>
            <description><![CDATA[<h2 id="사건-개요">사건 개요</h2>
<p>docker desktop을 windows 10 환경에서 wsl2 로 잘 활용하고 있었다. 프로젝트를 진행하는 도중에 windows 10 업데이트가 있었고 그 이후에도 조금 하다가 컴퓨터를 리부팅하고 다시 <code>docker-compose up</code> 을 했더니 위와 같은 에러가 발생했다. 프로젝트에서는 frontend가 3000번 포트를 사용하고 backend가 3001번 포트를 사용했다. </p>
<h2 id="원인">원인</h2>
<p>어떤 이유에선지 모르겠지만 나와 같은 경우를 다른 사람들도 역시나 겪었다. 구글링을 통해서 여러 방법들을 시도해보다가 비교적 빠른 시간 안에 문제를 해결할 수 있었다. </p>
<p>일단 에러메세지를 읽어보면 해당 port를 사용할 수 없다는 것이다. 그렇다면 내가 사용할 수 없는 포트를 볼 수 있는 방법은 무엇일까?</p>
<p>powershell을 켜서 </p>
<pre><code class="language-shell">~$ netsh interface ipv4 show excludedportrange protocol=tcp</code></pre>
<p>다음 명령어를 쳐보자. 그러면 다음과 같은 결과가 출력될 것이다.</p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/3bbe5c80-3b9d-446b-a017-3f9ab56194e5/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<p>위의 화면에서 시작포트부터 끝 포트까지는 사용할 수 없다는 것이다.</p>
<p>그런데 신기하게도 컴퓨터를 다시 껏다 켜고 docker desktop을 실행하면 그 전과는 사용할 수 없는 포트의 범위가 달라져있다. 그래서 처음에는 계속 껏다 켜면서 3000번과 3001번을 사용할 수 있을 때까지 해야하나? 라는 무식한 생각도 했었지만 그건 아닌 것 같았다.</p>
<h2 id="해결방법">해결방법</h2>
<p>그런데 게시글 맨 아래 출처에 적어놓은 스택오버플로우를 보면 어떤 사람이 작성한 방법이 내 경우에는 문제를 해결해줬다. </p>
<p>우선, <strong>1.docker desktop을 실행시켜서 설정</strong>으로 들어간다.
<img src="https://images.velog.io/images/dom_hxrdy/post/a7681f7b-9f89-40e9-abf1-5d78bb537295/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<p><strong>2.노란색 네모박스 부분을 위 그림과 같이 <code>Start Docker Desktop when you log in</code>을 비활성화</strong> 시킨다. 나같은 경우에는 컴퓨터가 켜질 때 docker가 자동으로 실행되도록 하는 것을 설정변경해서 자동 시작이 안 되도록 해놨는데 그 설정도 해야 할 지도 모르겠다. </p>
<p>그 다음에 컴퓨터를 재부팅한다. </p>
<blockquote>
<p>그러고 나서 docker desktop을 실행시키기 전에 </p>
</blockquote>
<pre><code class="language-shell">~$ netsh interface ipv4 show excludedportrange protocol=tcp</code></pre>
<p>이 명령어를 다시 쳐보면 제한된 포트의 범위가 훨씬 줄어있는 것을 볼 수 있다. 이것으로 포트의 범위를 제한시키는 대부분은 docker가 하는 일이라는 것을 알 수 있다. </p>
<p><strong>3. docker를 실행하기 전에 ubuntu를 실행</strong>시킨다.</p>
<p>그러고 나서 <strong>4.docker desktop을 실행</strong>한다.</p>
<p>그러고나서 다시 제한된 포트 범위를 확인해봤더니 3000번, 3001번이 해당 범위안에 없는 것이다. 즉 사용할 수 있는 포트가 된 것이다. </p>
<p>이렇게 해서 다시 프로젝트를 정상적으로 진행할 수 있었다.</p>
<h2 id="해결방법2">해결방법2</h2>
<p>위와 같이 했을 때는 약간 완벽한 해결책이라고 보기는 어렵다. 사용자 환경에 따라 다를 수도 있고 보통 저렇게 했을 때 3000, 3001번 포트를 확보해서 사용하는 것은 대부분 될 수도 있으나 3000, 3001번 포트를 제외하고 다른 포트를 사용하고 싶은데 그 포트를 docker, ubuntu가 사용할 가능성도 있기 때문이다. docker와 Hyper-V가 실행을 하기 위해서 어떤 포트를 항상 예약을 해놓고 그 포트를 사용자가 사용하지 못하게 한다. </p>
<p>이런 문제를 해결하기 위해서는 사용자가 원하는 포트를 직접 예약을 해놓고 다른 프로그램 (예를 들어, docker, Hyper-V)가 그 포트를 사용하지 못하도록 미리 설정해놓으면 미리 예약한 포트를 사용자가 사용할 수 있다.</p>
<p>방법은 매우 간단하다. <strong>1.일단 Hyper-V나 docker를 실행시키기 전(예를 들어, ubuntu를 실행시키기 전, docker를 실행시키기 전)에 powershell을 관리자권한으로 실행</strong>한다.</p>
<p>그러면 처음에 windows시스템이 예약해놓은 포트는 생각보다 적은 범위이다. </p>
<pre><code class="language-shell">~$ netsh interface ipv4 show excludedportrange protocol=tcp</code></pre>
<p>이 명령어를 컴퓨터를 부팅시킨 직후 아무 프로그램도 실행시키지 않고 쳐보면 예약된 포트가 상당히 적다는 것을 알 수 있다.</p>
<p>이 시점에서 <strong>2.다음 명령어를 이용해서 사용하고자하는 포트를 예약</strong>한다.</p>
<pre><code>netsh int ipv4 add excludedportrange protocol=tcp startport=5432 numberofports=1 store=persistent</code></pre><p><code>startport=5432</code> 이 부분부터 <code>numberofports=1</code> 1개의 포트를 사용하겠다는 의미이다. <code>5432</code>를 원하는 포트로 설정하고 그 이후부터 몇 개의 포트를 예약할지 <code>numberofports=??</code>로 설정하면 된다.</p>
<p>이렇게 예약을 하고 다시 </p>
<pre><code class="language-shell">~$ netsh interface ipv4 show excludedportrange protocol=tcp</code></pre>
<p>명령어로 확인을 해보면 예약된 포트에 (*) 표시가 붙으면서 원하는 포트가 예약돼있는 것을 확인할 수 있다.</p>
<blockquote>
<p>여기서 주의할 점은 powershell 프로그램을 우클릭해서 관리자권한으로 실행해야 포트 예약을 설정할 수 있다.</p>
</blockquote>
<p>이제 ubuntu, docker를 실행시켜도 미리 포트를 사용자가 예약해 놨기 때문에 자동으로 위 프로그램들이 다른 포트를 사용하게 된다. 그러면 이제 예약된 포트는 사용자가 사용가능한 상태가 된 것이다. </p>
<h5 id="출처--httpsstackoverflowcomquestions65272764ports-are-not-available-listen-tcp-0-0-0-0-50070-bind-an-attempt-was-made-to6702225367022253--httpspomeroyme202009solved-windows-10-forbidden-port-bind">출처 : (<a href="https://stackoverflow.com/questions/65272764/ports-are-not-available-listen-tcp-0-0-0-0-50070-bind-an-attempt-was-made-to/67022253#67022253">https://stackoverflow.com/questions/65272764/ports-are-not-available-listen-tcp-0-0-0-0-50070-bind-an-attempt-was-made-to/67022253#67022253</a>)  (<a href="https://pomeroy.me/2020/09/solved-windows-10-forbidden-port-bind/">https://pomeroy.me/2020/09/solved-windows-10-forbidden-port-bind/</a>)</h5>
]]></description>
        </item>
        <item>
            <title><![CDATA[[css]position 속성에 대한 이해]]></title>
            <link>https://velog.io/@dom_hxrdy/cssposition-%EC%86%8D%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4</link>
            <guid>https://velog.io/@dom_hxrdy/cssposition-%EC%86%8D%EC%84%B1%EC%97%90-%EB%8C%80%ED%95%9C-%EC%9D%B4%ED%95%B4</guid>
            <pubDate>Thu, 22 Jul 2021 09:28:17 GMT</pubDate>
            <description><![CDATA[<h2 id="position-fixed">position: fixed;</h2>
<p>우선 div 박스 하나를 화면에 표시하고 body의 <code>height</code>를 100vh로 둔 후에 div 박스의 <code>position</code>을 <code>fixed</code> 로 설정하면 차이가 전혀 없는 것을 볼 수 있다. 그런데 아래 코드와 같이 body의 <code>height</code>를 <code>1000vh</code> 로 높히면 브라우저 화면에 scroll이 활성화되면서 브라우저의 높이가 매우 커진 것을 확인할 수 있는데 여기서 스크롤을 내려보면 div 박스가 화면에서 사라지는 것이 아니라 스크롤을 따라서 계속 정해진 위치에 보이게 된다. </p>
<pre><code class="language-html">&lt;html&gt;
  &lt;head&gt;
    &lt;style&gt;
      body {
        height: 1000vh;
        margin: 20px;
      }
      div {
        width: 300px;
        height: 300px;
        background-color: teal;
      }
      #second {
        position: fixed;
        background-color: khaki;
        width: 350px;
      }
    &lt;/style&gt;
    &lt;/head&gt;
  &lt;body&gt;
    &lt;div&gt;1&lt;/div&gt;
    &lt;div id=&quot;second&quot;&gt;2&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>위 코드를 보면 두 개의 div 박스가 있는데 <code>#second</code> 아이디를 가진 div 박스에만 <code>position: fixed</code> 를 주면 스크롤을 아래로 내릴 때 첫번째 div는 화면에서 점점 사라지지만 fixed된 두 번째(#second) 박스는 화면에 따라서 이동하면서 계속 정해진 위치에 보이게 된다.</p>
<p>아래 그림과 같이 보인다.
<img src="https://images.velog.io/images/dom_hxrdy/post/e716d164-00c0-48b7-9bf7-a7a029f637e5/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<h3 id="top-bottom-left-right">top, bottom, left, right</h3>
<p>그런데 위의 코드를 실행해보면 처음에 박스들의 상태가 1 번 박스가 맨 위에 있고 2번 박스가 그 바로 아래 딱 붙어서 위치해있다. 이는 div 가 block 속성이기 때문에 새로운 줄에 2번 박스가 생긴 것이다. <code>position</code>이 fixed 되는 부분은 초기의 그 박스의 위치를 기준으로 한다. 그런데 이 위치를 속성을 추가함으로써 바꿔줄 수 있다.</p>
<p>위 코드에 <code>#second</code> 부분에 한 번 <code>top: 5px</code> 을 추가해보자. 원래는 같은 레이어에서 두 개의 박스가 맞붙어 보였는데 <code>top</code> 속성을 추가해주면서 1번 박스 위에 2번 박스가 놓인 것처럼 된다. 이제는 다른 레이어에서 보여지는 것이다. </p>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/c2320ef1-1bb7-4136-bc47-801c2c6883f1/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<p>이렇게 박스의 초기값을 <code>top,left,right,bottom</code> 을 이용해서 지정해 줄 수 있다.</p>
<h5 id="출처-httpsnomadcodersco">출처: (<a href="https://nomadcoders.co">https://nomadcoders.co</a>)</h5>
]]></description>
        </item>
        <item>
            <title><![CDATA[[css] display: flex]]></title>
            <link>https://velog.io/@dom_hxrdy/css-display-flex</link>
            <guid>https://velog.io/@dom_hxrdy/css-display-flex</guid>
            <pubDate>Thu, 15 Jul 2021 11:51:22 GMT</pubDate>
            <description><![CDATA[<p><code>display: inline-box</code>를 하면 여러 단점이 있어서 실제로는 사용하기 조금 부족한 기능이다. 그래서 그런 문제점들을 해결하기 좋은 방법이 <strong>flex box</strong>를 사용하는 것이다.</p>
<h3 id="flex-box">flex box</h3>
<p>flex box를 사용하면 화면의 크기에 따라서 원하는 효과를 넣어줄 수 있기 때문에 유용하다. 아래의 실습들을 따라해보면서 화면의 크기를 줄였다가 늘렸다가 하면 그 속성의 맞는 위치에 반응하면서 위치하는 것을 알 수 있을 것이다.</p>
<p>그런데 이를 사용하기 위해서는 부모 태그에 적용을 하고 자식 태그에는 어떤 속성도 넣어주면 안 된다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;style&gt;
      body {
        display: flex;
        margin: 20px;
      }
      #second {
          background-color: wheat;
      }
      div {
        width: 50px;
        height: 50px;
        background-color: teal;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div&gt;&lt;/div&gt;
    &lt;div id=&quot;second&quot;&gt;&lt;/div&gt;
    &lt;div&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>위와 같이 <code>&lt;div&gt;</code>의 부모태그인 <code>&lt;body&gt;</code>에 <code>display: flex</code>를 줄 수 있다. </p>
<h3 id="justify-content-align-items">justify-content, align-items</h3>
<p>이제 <code>display</code> 가 <code>flex</code>가 됐기 때문에 해당 태그에 추가적인 속성을 적용시킬 수 있는데 바로 <strong>justify-content</strong>와 <strong>align-item</strong> 이다.</p>
<pre><code class="language-css">justify-content: space-evenly;</code></pre>
<p>위와 같이 하면 flex box 안에 있는 요소들이 균등하게 위치하는 효과를 볼 수 있다.
<img src="https://images.velog.io/images/dom_hxrdy/post/79094648-0d0f-45c7-aa0a-799a5cafc93a/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<pre><code class="language-css">justify-content: space-evenly;
align-items: center;</code></pre>
<p><strong>align-items</strong> 를 적용시키면 아래와 같은 그림이 된다. </p>
<blockquote>
<p>단, align-items 는 해당 flex-box가 height를 가져야만 적용이 될 수 있다. 당연한 얘기지만 flex-box의 height가 이미 꽉차있는 경우에 <code>align-items: center</code> 를 한다 하더라도 이미 위,아래로 꽉 차 있기 때문에 변경된 모습이 안 보일 것이다. 그래서 body 태그에 <code>height: 100vh</code> 이런 식으로 위,아래로 움직일 여지가 있어야 <code>align-items</code> 속성이 적용될 것이다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/5b8e1d77-86a3-4f39-a959-c139b850472c/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<p>위, 아래를 기준으로 가운데에 flex box가 위치한 것을 볼 수 있다.</p>
<p>위 두 개의 예시를 통해서 다음과 같은 사실을 알 수 있다.</p>
<p><strong>justify-content</strong> 는 <strong>main axis(기본값은 수평)</strong> 를 기준으로 움직이고 <strong>align-item</strong> 은 <strong>cross axis(기본값은 수직)</strong>을 기준으로 움직인다.</p>
<p>기본값이 있는 이유는 나중에 main axis 의 방향을 수정할 수 있기 때문이다.</p>
<h3 id="flex-direction">flex-direction</h3>
<p>flex 상태에서 flex-direction 을 통해서 main axis와 cross axis를 변경할 수 있다. 두 개의 값이 있는데(row, column) 기본값은 row 이다. </p>
<p>여기서 <code>flex-direction: column</code> 으로 바꾸게 되면 main axis와 cross axis 가 각각 수직, 수평으로 바뀌기 때문에 <code>justify-content</code>
는 수직을 기준으로 변경될 것이고 <code>align-items</code>는 수평을 기준으로 변경될 것이다.</p>
<p><code>column-reverse, row-reverse</code> 도 있다. </p>
<h3 id="추가">추가</h3>
<p>맨 처음에 flex 는 부모에만 속성을 추가하면 자식들이 적용된다고 했다. 그런데 위의 <code>&lt;div&gt;</code>태그에 글자들을 넣어주게 되면 <code>&lt;div&gt;</code>태그에 속성을 추가해서 글자들에 플렉스를 적용시킬 수 있다. 아래 코드를 보자.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;style&gt;
      body {
        display: flex;
        margin: 20px;
      }
      div {
        width: 50px;
        height: 50px;
        background-color: teal;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div&gt;1&lt;/div&gt;
    &lt;div&gt;2&lt;/div&gt;
    &lt;div&gt;3&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>위 코드에서 보는것과 같이 <code>div</code> 아래에 1,2,3 이라는 값들이 들어갔는데 이제 <code>div</code> 태그에 flex 속성을 추가해서 글자들을 <code>div</code> 안에서 flex 속성들을 적용시킬 수 있게 된다. </p>
<pre><code class="language-css">    &lt;style&gt;
      body {
        height: 100vh;
        display: flex;
        margin: 20px;
        justify-content: space-evenly;
        align-items: center;
      }
      div {
        display: flex;
        justify-content: center;
        align-items: center;
        width: 50px;
        height: 50px;
        background-color: teal;
      }
      #second {
        background-color: wheat;
      }
    &lt;/style&gt;</code></pre>
<p>위와 같이 스타일을 바꾸게 되면 <code>&lt;div&gt;</code> 태그에 <code>display: flex;</code> 속성이 추가 되고 그에 따라서 <code>justify-content</code>와 <code>align-items</code> 속성값을 추가할 수 있게 된다. 둘 다 <strong>center</strong> 로 적용을 했더니 각가의 <code>&lt;div&gt;</code>태그 안에 글자들이 수직,수평 가운데로 정렬되는 것을 볼 수 있다. 
<img src="https://images.velog.io/images/dom_hxrdy/post/bc1903b7-e9e3-4ff4-ad8b-59f29b77f864/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<h3 id="flex-wrap">flex-wrap</h3>
<p>flex-wrap 은 <code>nowrap, wrap, wrap-reverse</code> 값이 있는데 요소들이 강제로 한줄에 배치되게 할 것인지, 또는 가능한 영역 내에서 벗어나지 않고 여러행으로 나누어 표현 할 것인지 결정하는 속성이다. </p>
<p>우리의 코드를 보자.</p>
<pre><code class="language-html">div {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 50px;
  height: 50px;
  background-color: teal;
}</code></pre>
<p>width 값이 50px 로 정해져 있는데 flexbox 에서는 이 값을 초기값으로만 생각할 뿐이다. 그래서 <code>justify-content</code>와 <code>align-items</code> 가 center 로 정해져있을 때는 화면의 크기가 줄어들 때 이 width값을 무시하고 최대한 center를 유지하려고 한다. 이 일이 가능한 이유는 <code>flex-wrap</code>의 기본값이 <code>wrap</code> 이기 때문이다. 한 화면 안에 강제적으로 다 배치하려고 하는 것이다. 이 속성을 <code>nowrap</code>으로 바꾸면 width값 이하로 화면이 줄어들면 한 줄이 아니라 다음 줄로 <code>&lt;div&gt;</code>값들이 내려갈 것이다. </p>
<h5 id="출처-httpsnomadcodersco">출처: (<a href="https://nomadcoders.co">https://nomadcoders.co</a>)</h5>
]]></description>
        </item>
        <item>
            <title><![CDATA[[css] display: inline-block; 을 잘 사용하지 않는 이유]]></title>
            <link>https://velog.io/@dom_hxrdy/css-display-inline-block-%EC%9D%84-%EC%9E%98-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@dom_hxrdy/css-display-inline-block-%EC%9D%84-%EC%9E%98-%EC%82%AC%EC%9A%A9%ED%95%98%EC%A7%80-%EC%95%8A%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Thu, 15 Jul 2021 11:29:46 GMT</pubDate>
            <description><![CDATA[<p>inline-block은 자주 사용되지 않고 좋지 않다.
그 이유는 컨트롤 하기가 어렵기 때문이다. 아래 코드를 실행해보면 브라우저에서 가로세로 50px이고 배경색이 teal 인 박스들이 좌우로 생긴다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;style&gt;
      div {
        width: 50px;
        height: 50px;
        background-color: teal;
        display: inline-block;
      }
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div&gt;&lt;/div&gt;
    &lt;div&gt;&lt;/div&gt;
    &lt;div&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<p><img src="https://images.velog.io/images/dom_hxrdy/post/ed7637bb-2a8d-4677-81db-08f53f39c018/%EC%BA%A1%EC%B2%98.PNG" alt=""></p>
<p>위 그림과 같이 나타나는데 여기서 저 블록들 사이에 있는 간격은 컨트롤 할 수가 없다. 실제로 <code>margin: 0; padding: 0;</code> 이런식으로 해도 간격은 사라지지 않는다. </p>
<p>위 html 코드에 <code>&lt;div&gt;</code> 태그를 계속 추가해서 좌우로 화면에 꽉 차게 만들어보면 맨 마지막에 남는 간격이 블록들 사이에 있는 간격과는 다르게 간격이 생기는 것도 컨트롤 할 수가 없다. 그 간격을 잘 맞추기 위해 margin 값을 정확한 픽셀값을 넣는다 해도 다른 모니터 화면에서는 또 망가진 간격이나 블럭이 하나 아래로 내려오거나 할 것이다. </p>
<p>이런 이유들로 <code>display: inline-block</code> 은 잘 사용하지 않는다. </p>
<h5 id="출처-httpsnomadcodersco">출처: (<a href="https://nomadcoders.co">https://nomadcoders.co</a>)</h5>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React]모달(Modal) 컴포넌트 마운트 된 상태에서 브라우저 뒤로가기 눌렀을 때 모달 컴포넌트만 언마운트 시키기 구현]]></title>
            <link>https://velog.io/@dom_hxrdy/React%EB%AA%A8%EB%8B%ACModal-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%88%EC%9A%B4%ED%8A%B8-%EB%90%9C-%EC%83%81%ED%83%9C%EC%97%90%EC%84%9C-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EB%88%8C%EB%A0%80%EC%9D%84-%EB%95%8C-%EB%AA%A8%EB%8B%AC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A7%8C-%EC%96%B8%EB%A7%88%EC%9A%B4%ED%8A%B8-%EC%8B%9C%ED%82%A4%EA%B8%B0-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@dom_hxrdy/React%EB%AA%A8%EB%8B%ACModal-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%88%EC%9A%B4%ED%8A%B8-%EB%90%9C-%EC%83%81%ED%83%9C%EC%97%90%EC%84%9C-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%92%A4%EB%A1%9C%EA%B0%80%EA%B8%B0-%EB%88%8C%EB%A0%80%EC%9D%84-%EB%95%8C-%EB%AA%A8%EB%8B%AC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EB%A7%8C-%EC%96%B8%EB%A7%88%EC%9A%B4%ED%8A%B8-%EC%8B%9C%ED%82%A4%EA%B8%B0-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 14 Jul 2021 09:56:43 GMT</pubDate>
            <description><![CDATA[<h3 id="상황">상황</h3>
<p>프로젝트를 진행하는 중에 모든 새로운 화면들을 모달 컴포넌트를 이용해서 진행중이었다. 그런데 어떤 버튼을 눌렀을 때 모달 컴포넌트가 화면에 마운트 되면서 보여지게 되는데 이 때는 당연히 React Router를 이용해서 주소를 옮기거나 하지 않았다. 그러다보니 브라우저 세션에 저장되는 <code>history</code>에 어떤 추가가 없기 때문에 모달 창을 열어놓은 상태에서 뒤로가기를 누르면 모달 창의 상위 컴포넌트인 mainPage가 아닌 최초의 로그인 화면으로 가버리는 문제가 있었다. 그래서 모달창을 열어놓은 상태에서 브라우저의 <strong>뒤로가기</strong> 버튼을 눌렀을 때 모달창만 닫히도록 개선하고 싶었다.</p>
<h3 id="history">history</h3>
<p>이를 위해서 <strong>history</strong> 에 대해서 알아야 했으며 이를 활용해서 뒤로가기를 눌렀을 때를 제어할 수 있었다. </p>
<p>브라우저를 자주 사용하다보니 <code>뒤로가기</code>, <code>앞으로가기</code> 버튼에 대해서는 익히 잘 알고 있을 것이다. 이전의 url로 다시 이동하고 다시 앞으로 가고 하는 역할을 한다. 그런데 이를 javascript로 제어할 수 있는데 그 때 알아야 할 것이 <strong>history API</strong> 이다. <a href="https://developer.mozilla.org/ko/docs/Web/API/History_API">공식문서</a>를 통해서 간단한 사용방법을 알 수 있다. </p>
<p>공식문서를 보면 <code>history.pushState()</code> 를 사용하는데 이를 활용해서 문제를 해결할 수 있다. 사용방법은 <a href="https://developer.mozilla.org/ko/docs/Web/API/History/pushState">공식문서</a>를 참고하자.</p>
<h3 id="해결방법">해결방법</h3>
<p>일단 기본적으로 <code>MainPage.tsx</code> 라는 컴포넌트가 상위 컴포넌트로 있고 여기서 버튼을 누르면 <code>Modal.tsx</code> 컴포넌트가 보여지면서 모달창을 구현했다. 여기서 중요한 것이 url이 어떤 식으로도 변경되지 않았다는 것이다. 그렇기 때문에 <strong>뒤로가기</strong>를 눌렀을 때 이전의 url인 <strong>로그인페이지</strong>로 이동하는 것이다.</p>
<p>그래서 생각해낸 로직은 다음과 같다.</p>
<ol>
<li>모달 창을 열었을 때 history에 새로운 상태를 push 해줌으로써 현재는 모달이라는 상태에 있다는 것을 브라우저에게 알려준다.</li>
<li>이렇게 하면 브라우저의 뒤로가기를 눌렀을 때 새로운 모달이라는 상태가 history에 저장됐기 때문에 이전의 상태인 mainpage로 이동할 것이다. 그러면 모달창이 활성화돼있는 상태에서 뒤로가기를 누르면 로그인페이지가 아니라 원래의 메인페이지, 즉 모달창만 닫히는 효과를 볼 수 있다.</li>
</ol>
<p>1번과 2번을 코드로 어떻게 구현했는지를 보자.</p>
<p>우선 <code>1번</code>을 구현하기 위해서 컴포넌트의 <code>useEffect</code>훅을 사용하면 된다. 컴포넌트가 마운트될 때(실행될 때) 실행되는 함수인데 이 안에서 모달창이 켜질때마다 <strong>새로운 history 상태를 push 해주는 것</strong>이다.</p>
<p><code>history.pushState({page:&quot;modal&quot;}, document.title);</code></p>
<p>를 <code>useEffect</code> 안에 추가하면 모달창이 켜질때마다 history에 새로운 상태가 저장이 될 것이다.</p>
<p>그리고 뒤로가기 버튼을 눌렀을 때를 제어하려면 뒤로가기버튼을 눌렀을 때에 대한 이벤트 리스너의 등록이 필요하다. 이 또한 <code>useEffect</code> 훅 안에 구현해서 모달이 활성화돼있을 때만 해당 함수가 실행되도록 했다.</p>
<p><code>window.addEventListener(&quot;popstate&quot;, goBack);</code></p>
<p>이렇게 뒤로가기를 눌렀을 때 <code>popstate</code> 이벤트가 발생하는데 이 때 내가 작성한 <code>goBack</code> 함수가 실행되도록 이벤트리스너를 등록한다.
그렇다면 <code>goBack()</code> 함수를 한 번 봐보자.</p>
<pre><code class="language-ts">const goBack = () =&gt; {
  closer();
};</code></pre>
<p>단순히 모달을 닫아주는 <code>closer()</code> 함수를 호출한다. 아래 전체 코드를 한 번 봐보자.</p>
<pre><code class="language-ts">//복잡한 interface는 무시하고 그냥 Modal 컴포넌트라고 생각하자
const Modal: FC&lt;modalPros&gt; = ({ content, stateSetter }): JSX.Element =&gt; {
  ...
  const goBack = () =&gt; {
      closer();
  };

  //모달창에 있는 X 버튼을 누르면 실행되는 모달창을 닫는 함수
  const closer = () =&gt; {
    setTimeout(() =&gt; { stateSetter(false); }, 700);
    hideAnimatedModal();
  }
  ...
  useEffect(() =&gt; {
    //모달이 켜질때마다 새로운 state를 추가한다.
    history.pushState({page:&quot;modal&quot;}, document.title);
    showAnimatedModal();

    document.addEventListener(&quot;keyup&quot;, detectESC);
    //뒤로가기 버튼을 눌렀을 때 실행하는 이벤트리스너 등록
    window.addEventListener(&quot;popstate&quot;, goBack);
    //cleanup 함수로 컴포넌트가 사라질 때 모든 리스너를 제거해준다.
    return () =&gt; {
      document.removeEventListener(&quot;keyup&quot;, detectESC);
      window.removeEventListener(&quot;popstate&quot;, goBack);
    };
  }, []);
  ...
}</code></pre>
<h3 id="문제점">문제점</h3>
<p>그런데 이렇게 했을 경우 문제점이 생긴다.</p>
<p>위 코드는 모달창이 활성화돼있을 때 뒤로가기를 누르면 모달창 <code>closer()</code> 함수가 실행되면서 모달창만 닫히고 <code>mainpage</code>로 이동하고 또 그냥 모달창을 띄운 상태에서 x버튼을 누르거나 esc키를 눌러도 <code>mainpage</code>로 이동해서 잘 작동하는 것처럼 보인다.</p>
<p>하지만 잘 분석해보면 모달 창이 켜질때마다 <code>pushState()</code> 함수로 <code>history</code>에 자꾸 새로운 상태를 추가한다. 그런데 이 상태에서 <strong>뒤로가기</strong>버튼을 눌렀을 때는 <code>popstate</code>이벤트가 발생하면서 추가된 새로운 상태가 pop돼서 문제가 없지만 문제는 모달창을 뒤로가기가 아닌 다른 식으로 종료했을 때(esc키를 누르거나 X버튼을 누른 경우) 이 때는 popstate 이벤트가 발생하지 않기 때문에 history에 새로 추가한 state가 그대로 남아있게 된다. </p>
<p>그래서 모달창을 켰다가 뒤로가기버튼이 아닌 X버튼을 눌러서 끄는 식으로 3번 반복을 하면 <code>mainpage</code>에서 뒤로가기버튼을 3번 눌러야 <code>login</code>페이지로 이동하는 버그가 발생한다.</p>
<p>그래서 이를 해결하기 위해서는 X버튼을 누르거나 ESC 키를 눌렀을 때도 popstate 이벤트가 발생하도록 해야한다.</p>
<h3 id="추가-해결방법">추가 해결방법</h3>
<p>다음과 같은 코드를 추가해서 해결해보자.</p>
<p>우선, 뒤로가기버튼을 눌렀는지 안 눌렀는지 상태를 체크할 수 있는 boolean 변수가 하나 필요하다.</p>
<p><code>let isGoBackClicked = false</code></p>
<p>코드 한 줄을 Modal 컴포넌트 최상위에 우선 false로 선언한다.</p>
<p>그리고 <code>goBack()</code> 함수에 플래그를 바꿔주는 코드를 삽입한다.</p>
<pre><code class="language-js">const goBack = () =&gt; {
  isGoBackClicked = true;
  closer();
};</code></pre>
<p><code>goBack()</code>함수는 popstate 이벤트가 발생했을 때만 실행되는 이벤트리스너에 등록된 콜백함수이므로 이 함수 안에서 <code>isGoBackClicked</code> 변수를 true로 바꿔주는 것이다. 그리고 이제 마지막으로 해야 할 일은 뒤로가기버튼을 누르지 않고 끄는 경우에도 popstate 이벤트를 발생시켜야 하는 것이다.</p>
<p>그래서 마지막으로 <code>useEffect</code> 함수의 cleanup 함수 부분에 조건을 걸어서 popstate 이벤트를 발생시키는 코드를 추가해야 한다.</p>
<pre><code class="language-ts">useEffect(() =&gt; {
  ...
 //뒤로가기를 누르지 않고 종료할 경우엔 state가 새로 있으니
 //뒤로가기를 해줘서 popstate 이벤트를 발생시켜줘야 함
  if (!isGoBackClicked) {
    history.back();
  }
  ...
}, []);</code></pre>
<p>이렇게 해서 완성된 코드는 다음과 같다.</p>
<pre><code class="language-ts">const Modal: FC&lt;modalPros&gt; = ({ content, stateSetter }): JSX.Element =&gt; {
  //뒤로가기 버튼을 눌렀는지 여부를 저장
  let isGoBackClicked = false;

  ...

  /*!
  * @author donglee
  * @brief 모달 컴포넌트를 종료할 때 실행하는 함수.
  */
  const closer = () =&gt; {
    setTimeout(() =&gt; { stateSetter(false); }, 700);
    hideAnimatedModal();
  }

  const goBack = () =&gt; {
    isGoBackClicked = true;
    closer();
  };

  useEffect(() =&gt; {
    //모달이 켜질때마다 새로운 state를 history에 추가
    history.pushState({page:&quot;modal&quot;}, document.title);
    showAnimatedModal();

    document.addEventListener(&quot;keyup&quot;, detectESC);
    //뒤로가기 눌렀을 때 goBack 함수 실행
    window.addEventListener(&quot;popstate&quot;, goBack);
    return () =&gt; {
      //이벤트 리스너들 모달창 꺼질 때 제거해주기
      document.removeEventListener(&quot;keyup&quot;, detectESC);
      window.removeEventListener(&quot;popstate&quot;, goBack);
      //뒤로가기를 누르지 않고 종료할 경우엔 state가 새로 있으니 뒤로가기를 해줘서 popstate 이벤트를 발생시켜야 함
      if (!isGoBackClicked) {
        history.back();
      }
    };
  }, []);
  ...
};</code></pre>
<h5 id="출처">출처:</h5>
<ul>
<li>(<a href="https://ifuwanna.tistory.com/194">https://ifuwanna.tistory.com/194</a>)</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>