<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jeonghoon_dev.log</title>
        <link>https://velog.io/</link>
        <description>Develop myself, FE developer!</description>
        <lastBuildDate>Sun, 01 May 2022 06:23:51 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jeonghoon_dev.log</title>
            <url>https://images.velog.io/images/do_dadu/profile/99148de4-58df-4ff5-adac-0c7700da9c32/1669acd5f7214d1181db69b28c79f0a6.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jeonghoon_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/do_dadu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[익숙하지만 낯선 package-lock.json]]></title>
            <link>https://velog.io/@do_dadu/%EC%9D%B5%EC%88%99%ED%95%98%EC%A7%80%EB%A7%8C-%EB%82%AF%EC%84%A0-package-lock.json</link>
            <guid>https://velog.io/@do_dadu/%EC%9D%B5%EC%88%99%ED%95%98%EC%A7%80%EB%A7%8C-%EB%82%AF%EC%84%A0-package-lock.json</guid>
            <pubDate>Sun, 01 May 2022 06:23:51 GMT</pubDate>
            <description><![CDATA[<h1 id="다룰-내용">다룰 내용</h1>
<p>npm을 사용한다면 package-lock.json, yarn을 사용한다면 yarn.lock 파일을 본적이 있었을 것이다. 나도 이 파일을 꽤나 오랫동안 봤지만 정확히 왜 만들어지고 어떻게 사용되는지에 대해서 잘 모르고 있기에 이번 기회에 제대로 공부하고 넘어가기로 했다.</p>
<hr>
<h1 id="왜-만들어지는-것인가">왜 만들어지는 것인가?</h1>
<h2 id="version-range">version range</h2>
<p>package-lock.json은 잘 몰라도 package.json은 잘 알 것이다. 우리는 모듈을 설치하고 설치된 모듈의 정보들을 package.json에서 확인한다. 하지만 모듈의 버전은 package.json에 상세히 적혀있지 않아도 된다. package.json에서는 모듈의 버전 정보를 저장할 때 <code>version range</code>를 사용할 수 있기 때문이다. version range는 정확하게 1.0.12 버전을 사용할 것이라고 기입하는게 아닌 1.0.12 버전 이상을 사용할 것이라고 범위를 지정하는 버전 정보 기입 방식이다. 그렇다면 왜 명확하게 기입하지 않고 범위를 통하여 기입하는 것일까? package.json에 패키지 버전을 명확하게 기입하면 패키지의 버그가 수정이 되어도 업데이트가 되지 않아 package.json에 적힌 버전을 손수 수정해야하기 때문이다.
<img src="https://velog.velcdn.com/images/do_dadu/post/35a09e3e-679b-4036-939f-d11831fe06c6/image.jpg" alt=""></p>
<h2 id="package-lockjson">package-lock.json</h2>
<p>이제는 왜 package-lock.json이 생겨났는지는 눈치를 챌 수 있을 것이다. 바로 사용하는 모듈의 버전을 명확하게 관리하기 위해 package-lock.json 파일이 생겨났다. 모듈을 version-range로만 관리를 하게된다면 같은 프로젝트를 하는 개발자의 컴퓨터들 안에 모두 다른 버전의 모듈이 설치될 가능성이 있다. 설치된 버전이 다르다면 지원하는 기능도 차이가 생겨 프로덕트에 에러를 초래할 가능성이 크다.</p>
<hr>
<h1 id="언제-만들어지나">언제 만들어지나?</h1>
<p><a href="https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json">npm 공식문서</a>에 따르면, package-lock.json은 npm이 node_modules 트리 또는 package.json을 수정하는 모든 작업에 대해 자동으로 생성된다고 한다. 그리고 package-lock.json이 생겨난 이후에는 중간 종속성 업데이트에 관계없이 후속 설치에서 동일한 트리를 생성할 수 있도록 생성된 정확한 트리를 활용한다고 한다. 추가적으로 이 파일은 소스 리포지토리에 커밋하기 위한 것라고 설명하고 있다.</p>
<hr>
<h1 id="정리">정리</h1>
<p>package-lock.json 파일은 package.json 파일에서 모듈들의 버전을 범위로 기입하기 때문에 프로젝트에서 사용하고 있는 정확한 버전을 관리하는 파일이다. 가끔 package-lock.json 파일에 익숙치 않다보니 깃에 올려 관리해야하는지 고민하는 분들이 있었는데 무조건 올려야합니다~!!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SWR의 컨셉과 Next JS와의 페어링]]></title>
            <link>https://velog.io/@do_dadu/SWR%EC%9D%98-%EC%BB%A8%EC%85%89%EA%B3%BC-Next-JS%EC%99%80%EC%9D%98-%ED%8E%98%EC%96%B4%EB%A7%81</link>
            <guid>https://velog.io/@do_dadu/SWR%EC%9D%98-%EC%BB%A8%EC%85%89%EA%B3%BC-Next-JS%EC%99%80%EC%9D%98-%ED%8E%98%EC%96%B4%EB%A7%81</guid>
            <pubDate>Sun, 01 May 2022 03:43:25 GMT</pubDate>
            <description><![CDATA[<h1 id="다룰-내용">다룰 내용</h1>
<p>SWR을 사용했을 때 여러 이점이 있겠지만 이 글에서는 SWR의 컨셉과 Next JS의 Pre-Render와 어떻게 페어링 하는지에 대해서 다루도록 하겠습니다.</p>
<h1 id="stale-while-revalidate">stale-while-revalidate</h1>
<blockquote>
<p>&quot;SWR&quot;이라는 이름은 HTTP RFC 5861에 의해 알려진 HTTP 캐시 무효 전략인 stale-while-revalidate에서 유래되었습니다. -<a href="https://swr.bootcss.com/ko">SWR 홈페이지</a>-</p>
</blockquote>
<p>SWR의 이름의 유래는 stale-while-revalidate에서 왔다고 한다. 그렇다면 stale-while-revalidate은 무엇일까?
stale-while-revalidate은 Cache 제어 방법 중에 하나다. 웹 개발자는 주로 HTTP를 사용하여 서버와 클라이언트가 데이터를 주고받는다. 데이터를 주고 받다가 요청과 응답하는 리소스가 매번 같은 경우 필요없는 통신 리소스를 줄이기위해서 Cache를 활용한다. </p>
<blockquote>
<p>Cache: 이전의 응답 리소스의 복사복을 저장하고 있다가 요청 시에 복사복을 제공하는 기술</p>
</blockquote>
<p>Cache는 클라이언트와 가까이 있어 서버와 직접 데이터를 주고 받는 것보다 더 빠르게 결과를 보여줄 수 있다. 그리고 서버에 요청을 줄여 서버의 부하를 줄여줄 수 있으며 네트워크 트래픽을 줄여줄 수 있다.</p>
<p>HTTP는 이러한 Cache를 제어할 수 있는 여러가지 방법이 있는데 그것을 Cache-Control이라고 하며 그 중 하나가 <code>stale-while-revalidate</code>다. 다른 여러가지 Cache-Control은 <a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Cache-Control">MDN 문서</a>에서 확인이 가능하며 이 글에서는 그 중에 stale-while-revalidate과 max-age를 비교해가며 알아보겠다. 
우선 max-age는 Cache의 유효기간을 설정할 수 있다. 해당 유효기간 동안은 Cache를 업데이트 하지않고 재사용한다. stale-while-revalidate도 기간을 설정 할 수 있는데 max-age의 유효기간과는 다르다. stale-while-revalidate의 기간동안은 요청시 이전 캐시 데이터를 즉시 사용하지만 비동기적으로 데이터 통신을 하여 업데이트를 진행한다. SWR은 이와같은 방식으로 데이터 통신의 부하를 조절한다.</p>
<h2 id="사용-사례">사용 사례</h2>
<p>stale-while-revalidate는 max-age와 같이 사용된다. 두가지 옵션에 시간(초)를 설정하여 사용한다. 예를 들어 max-age=1; stale-while-revalidate=59;로 설정을 했다고하자. 1초 이내에 HTTP 요청이 다시 들어온다면 max-age 이내이므로 지금 갖고 있는 Cache가 최신이라고 판단하므로 Cache 데이터를 재사용한다. 1초 이후 60초 이내에 HTTP 요청이 들어온다면 캐시된 값은 오래되었지만 캐싱된 값을 반환한다. 그리고 백그라운드에서 향후 사용을 위한 캐시를 새로운 값으로 채우도록 재검증 요청이 이루어지게 된다. 60초 이후에는 캐싱된 데이터를 사용하지 않고 서버에 요청하여 받은 응답을 사용한다.</p>
<p><img src="https://web-dev.imgix.net/image/admin/C8lg2FSEqhTKR6WmYky3.svg" alt=""></p>
<hr>
<h1 id="with-next-js-feat-pre-render">with Next JS (feat. Pre-Render)</h1>
<p>SWR은 SWRConfig의 fallback 옵션을 사용하여 Next JS의 SSR과 SSG와의 호환성을 높일 수 있다.</p>
<h2 id="예시-코드">예시 코드</h2>
<pre><code class="language-javascript">// getStaticProps 혹은 getServerSideProps
export async function getStaticProps () {
  const article = await getArticleFromAPI()
  return {
    props: {
      fallback: {
        &#39;/api/article&#39;: article
      }
    }
  }
}

function Article() {
  // `data`는 `fallback`에 있기 때문에 항상 사용할 수 있습니다.
  const { data } = useSWR(&#39;/api/article&#39;, fetcher)
  return &lt;h1&gt;{data.title}&lt;/h1&gt;
}

export default function Page({ fallback }) {
  // `SWRConfig` 경계 내부에 있는 SWR hooks는 해당 값들을 사용합니다.
  return (
    &lt;SWRConfig value={{ fallback }}&gt;
      &lt;Article /&gt;
    &lt;/SWRConfig&gt;
  )
}</code></pre>
<p>위와 같이 SWR을 사용하여 SSR 혹은 SSG같은 Pre-Render와 호환을 할 수 있다. Pre-Render에서는 SWR이 아닌 axios 또는 fetch를 활용하여 데이터 패칭을 하여 props로 데이터를 넘겨준다. 넘겨 받은 데이터를 SWRConfig의 fallback에 설정해주면 Pre-Render에 손쉽게 데이터를 넘겨줄 수 있다.</p>
<hr>
<h1 id="정리">정리</h1>
<p>이 글을 쓰며 SWR의 컨셉과 Next JS와의 페어링이 왜 좋은지에 대해서 알아봤다. 이전에는 단순히 편하다는 느낌만 갖고 사용하였지만 SWR의 컨셉을 통해 왜 SWR이 다른 데이터 패칭 라이브러리에 비해 편하다고 느껴졌는지 알 수 있었다.
그리고 아직은 사용해본적은 없지만 SWR과 동일한 stale-while-revalidate를 주요 컨셉으로 갖고 있는 React Query에 대해서도 많은 정보를 접하게 되었고 흥미가 생기게 되었다. 만약 React Query에 대해서 궁금증이 생긴다면 <a href="https://react-query.tanstack.com/comparison">이 링크</a>(React query 공식 페이지)에 들어가 다른 데이터 페칭 라이브러리와 비교한 표를 확인하고 좀 더 자세히 SWR과 비교된 글을 원한다면 <a href="https://tech.madup.com/react-query-vs-swr/">이 블로그 글</a>을 추천한다. 나는 위 글들을 읽고 React Query에 대해 궁금증이 생겨서 다음 프로젝트에서는 React Query를 사용할 것 같다. React Query를 직접 사용하고나면 사용하며 느낀 SWR과 React Query를 비교하는 글로 돌아오겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[롱프레스 이벤트를 사용하자]]></title>
            <link>https://velog.io/@do_dadu/%EB%A1%B1%ED%94%84%EB%A0%88%EC%8A%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@do_dadu/%EB%A1%B1%ED%94%84%EB%A0%88%EC%8A%A4-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90</guid>
            <pubDate>Fri, 08 Apr 2022 12:10:19 GMT</pubDate>
            <description><![CDATA[<h1 id="롱프레스-이벤트-리스너란">롱프레스 이벤트 리스너란?</h1>
<p>롱프레스 이벤트란 말이 익숙치 않은 사람들이 많을 것이다. 하지만 설명을 듣는 순간 모두들 아 그거~라고 할 것이다. 롱프레스 이벤트란 터치 홀드라고도 불린다. 한 요소를 길게 누르고 있는 이벤트다. 우리가 보통 핸드폰에서 앱을 지우기위해 메뉴를 띄우거나 이동하게 하기 위해서 길게 누르는 그 액션을 롱프레스라고 한다. 스마트폰이 익숙한 우리에게는 이 이벤트가 익숙하지 않을 수 없다. 
<img src="blob:https://velog.io/b4e623a3-0ab7-430e-b0b4-4ddf403640d6" alt="업로드중.."></p>
<p>보통 롱프레스 이벤트는 핸드폰의 앱아이콘들처럼 단순 터치를 할 때 이미 다른 기능이 있거나 혹은 자주 터치가 일어나는 요소에 새로운 이벤트 리스너를 넣고 싶을 때 많이 사용된다. 밑미 프로젝트를 할 때 우리는 댓글을 삭제/수정하거나 신고를 하기위한 모달을 띄워야했다. 모바일 사용을 위한 웹앱 구현이였기 때문에 댓글 요소에 많은 터치가 생길 수 밖에 없었기에 롱프레스 이벤트가 일어나면 모달을 띄우기로 결정했다.</p>
<hr>
<h1 id="구현-방법">구현 방법</h1>
<p>우리는 롱프레스의 기준을 3초로 정했다. 3초동안 댓글을 누르면 해당 댓글에 대한 모달이 뜨게하고 만약 그전에 손을 땐다면 모달이 뜨지 않도록 구현했다. </p>
<p>모달이 뜨게하기 위해서 onTouchStart가 일어난다면 setTimeout API를 활용하여 3초 이후에 함수가 실행되도록 예약을 했다. 그리고 모달이 뜨지 않도록하기 위해서는 onTouchEnd가 일어났을 때 clearTimeout API를 사용하였다. 이러한 과정에서 해당 타겟 요소와 timeout API를 기억하기 위해서 useRef를 활용하였다.</p>
<p>위의 코드는 롱프레스 이벤트 리스너 기능을 커스텀 훅으로 만든 결과물이다. </p>
<pre><code class="language-javascript">// useLongPress.ts
import React, { useCallback, useRef } from &#39;react&#39;;

const useLongPress = (
  onLongPress: () =&gt; void,
  { delay = 300 } = {},
) =&gt; {
  const timeout = useRef&lt;NodeJS.Timeout&gt;();
  const target = useRef&lt;HTMLElement&gt;();

  const start = useCallback(
    (event) =&gt; {
      if (event.target) {
        event.target.addEventListener(&#39;touchend&#39;, preventDefault, {
          passive: false,
        });
        target.current = event.target;
      }
      timeout.current = setTimeout(() =&gt; {
        onLongPress();
      }, delay);
    },
    [onLongPress],
  );

  const clear = useCallback(
    () =&gt; {
      if (timeout.current) clearTimeout(timeout.current);
      if (target.current) {
        target.current.removeEventListener(&#39;touchend&#39;, preventDefault);
      }
    },
    [],
  );

  return {
    onTouchStart: (e: React.TouchEvent) =&gt; start(e),
    onTouchEnd: () =&gt; clear(),
  };
};

const isTouchEvent = (event: TouchEvent) =&gt; &#39;touches&#39; in event;

const preventDefault = (event: TouchEvent) =&gt; {
  if (!isTouchEvent(event)) return;

  if (event.touches.length &lt; 2 &amp;&amp; event.preventDefault) {
    event.preventDefault();
  }
};

// targetComponent.tsx
...
const { onMouseDown, onMouseLeave, onMouseUp, onTouchEnd, onTouchStart } =
    useLongPress(() =&gt; funtion());
...
&lt;div
  onTouchEnd={onTouchEnd}
  onTouchStart={(e) =&gt; onTouchStart(e)}
&gt;
  타겟 요소
&lt;/div&gt;</code></pre>
<hr>
<h1 id="정리">정리</h1>
<p>단순히 기능만 보면 간단하게 구현이 될 것이라고 생각했지만 생각보다 복잡했었다. 해당 글에서 다루지는 않았지만 여러 댓글 중에 어떤 댓글을 위한 모달인지 확인을 하기위해서 댓글 데이터를 따로 저장하는 기능을 추가 구현하여 원하던 기능을 완성하였다. 많은 생각을 하고 많은 에러를 만나면서 구현했던 기능 중 하나였고 매우 유용하게 사용하였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[커스텀하기 편한 위지윅 에디터 (TinyMCE)]]></title>
            <link>https://velog.io/@do_dadu/%EC%BB%A4%EC%8A%A4%ED%85%80%ED%95%98%EA%B8%B0-%ED%8E%B8%ED%95%9C-%EC%9C%84%EC%A7%80%EC%9C%85-%EC%97%90%EB%94%94%ED%84%B0-TinyMCE</link>
            <guid>https://velog.io/@do_dadu/%EC%BB%A4%EC%8A%A4%ED%85%80%ED%95%98%EA%B8%B0-%ED%8E%B8%ED%95%9C-%EC%9C%84%EC%A7%80%EC%9C%85-%EC%97%90%EB%94%94%ED%84%B0-TinyMCE</guid>
            <pubDate>Wed, 06 Apr 2022 06:55:31 GMT</pubDate>
            <description><![CDATA[<h1 id="wysiwyg-에디터란">WYSIWYG 에디터란</h1>
<p>WYSIWYG는 What You See Is What You Get, &quot;보는 대로 얻는다&quot;라는 문장의 줄임말이다. 문서를 편집하는 과정에서 화면에 보이는 문장이나 글 맵시 등을 동일하게 화면에 출력해주는 방식이다. 위지위그 에디터는 이러한 기능을 위해서 사용자는 모르게 <code>&lt;p&gt;</code>, <code>&lt;span&gt;</code>, <code>&lt;em style=&gt;</code> 등 다양한 HTML 소스를 입력해주고 있다.</p>
<p>프로젝트를 하다보면 단순 input이나 textarea로는 부족한 경우가 많다. 예를 들면 블로그 글을 작성할 때나 쇼핑몰에 제품 설명을 적어야 하는 경우를 떠올리면 이해가 빠를 것이다. 잘 구현을 한다면 충분할 수도 있지만 위지위그 방식이 아니라면 사용자 입장에서 내가 쓴 글이 어떻게 출력이 되는지 확인 후에 다시 수정을 하는 번거로운 과정이 추가된다. 그래서 이번 프로젝트에서 위지위그 에디터를 활용하게 되었다.</p>
<hr>
<h1 id="tinymce-선택한-이유">TinyMCE 선택한 이유</h1>
<p>시중에는 정말로 많은 WYSIWYG 에디터가 존재하기 때문에 우리는 TinyMCE를 선택하기 위한 몇가지 조건을 만들었다. 우선 UI 커스터마이징에서 자유로워야할 것. 아무리 이쁜 디자인, 좋은 기능의 에디터여도 우리가 만들 사이트에 녹아들지 않는다면 사용할 수 없었다. </p>
<p>두번째, 무료 버전으로도 충분히 기능 구현이 가능할 것. 개발을 하다보면 기능이 추가되거나 줄어들거나한다. 그렇기 때문에 무료 버전이 지원하는 기능이 너무 타이트하다면 원치 않는 지출이 늘어나거나 최악의 경우 다른 에디터로 변경해야하는 일이 생길 수 있었다. 그렇기 때문에 무료 버전으로도 충분한 기능 구현이 되는지가 중요했다.</p>
<p>마지막으로는 지속적인 인지도였다. 인지도가 있다는 것에는 많은 의미가 포함되어 있었다. 사람들이 많이 사용을 했고 그에 대한 참조 문서가 많았으면 했다. Docs에도 충분한 설명이 있지만 가끔은 필요한 기능을 구현하기 위해서 Docs에 없는 방식으로 구현을 해야할 때도 있었다. 그리고 지속적으로 많은 사람들이 사용하고 있다는 것은 많은 사람들이 많은 실험 혹은 개발 경험을 거쳤을 것이라는 의미라고 생각되었다.</p>
<p>TinyMCE 이외에도 <a href="https://ckeditor.com/">CKeditor</a>, <a href="https://trix-editor.org/">Trix</a>, <a href="https://summernote.org/">Summernote</a>, <a href="https://draftjs.org/">Draft.js</a> 등 많은 에디터들이 후보에 올랐었다. 하지만 결국 위 모든 조건을 가장 넉넉하게 통과한 에디터는 TinyMCE였다.</p>
<hr>
<h1 id="tinymce-커스텀하기">TinyMCE 커스텀하기</h1>
<h2 id="기본-스타일-커스텀">기본 스타일 커스텀</h2>
<p>TinyMCE는 기본적인 UI 옵션을 제공한다. 해당 옵션들은 <a href="https://www.tiny.cloud/docs/configure/editor-appearance/#toolbar">TinyMCE docs</a>에서도 설명이 잘 되어있느니 내가 사용했던 것들만 간단히 집고 넘어 가겠다.</p>
<p>우선 TinyMCE는 메뉴바와 툴바 모두 제공한다. 하지만 우리의 경우 툴바만 필요했기에 <code>menubar: false,</code>를 추가하여 메뉴바는 보이지 않게하였다.</p>
<p>그리고 툴바를 제공하는 것뿐만 아니라 내부 요소들을 원하는 것들로만 원하는 위치에 원하는 것들끼리 묶을 수 있게 커스터마이징이 가능하다. <code>toolbar: &#39;undo redo | bold italic underline&#39;,</code> 이와 같이 원하는 버튼의 키워드를 string 형식으로 적어주고 <code>|</code>기호를 사용하여 나눠주면 툴바 버튼들 사이에 보더가 생긴다. 
<img src="https://media.vlpt.us/images/do_dadu/post/5c885ae9-e994-4fec-ba8e-4084b2b4ef8a/7A8E9C24-45D0-4C2E-BB39-E8C6CC507D17.png" alt=""></p>
<p>그리고 좀더 깔끔한 UI를 위해서 상태바를 삭제하였다. 현재 상태를 알 수 있지만 디자인적으로 지저분해보였다. <code>statusbar: false,</code> 이 옵션을 false로 두면 상태바를 없앨 수 있다.</p>
<hr>
<h2 id="tinymce에서-제공하지-않는-스타일-커스텀">TinyMCE에서 제공하지 않는 스타일 커스텀</h2>
<p>우리는 TinyMCE의 디자인을 좀더 세세하게 커스텀하기를 원했다. 툴바 버튼 사이의 보더의 굵기나 색을 변경한다거나 툴바 버튼의 크기를 바꾸거나 TinyMCE 전체 보더를 안보이게 하거나 등등.</p>
<p>우리 팀은 TinyMCE를 처음 써봤기 때문에 이 문제를 어떻게 해결할 수 있는지 몰랐기에 Docs를 탈탈 털었지만 결국 Docs에서는 해당 기능을 찾지 못하였다. 위처럼 TinyMCE가 제공하는 옵션으로 커스터마이징이 되었다면 좋았겠지만 그렇지 않았던 것이다. 결국 TinyMCE에 스타일을 !important로 주는 것으로 해결했다. 
<img src="https://media.vlpt.us/images/do_dadu/post/8dc8349c-916a-48b0-b00e-a9a548da766c/D92A2D43-BBF2-4EA5-91C5-6A6C5CD30222.png" alt="">
TinyMCE의 소스 코드를 보면 자체적으로 class명이 설정되어있다. 우리가 원하는 요소의 class명을 찾아 직접적으로 스타일링을 해주었다.</p>
<pre><code class="language-javascript">// Emotion 코드입니다.
&amp; .tox-toolbar__group {
    border-color: #F9F5F3 !important;
  }</code></pre>
<hr>
<h2 id="이미지-모달-버튼--이미지-삽입">이미지 모달 버튼 + 이미지 삽입</h2>
<p>게시물에는 글뿐만 아니라 이미지도 넣어야 때가 많다. 우리 프로젝트에서도 이미지를 넣어야했었고 TinyMCE에서 이미지 플러그인과 UI를 제공해준다. 하지만 문제는 제공해주는 이미지 선택 UI가 이쁘지 않았다. 
<img src="https://media.vlpt.us/images/do_dadu/post/cc235b1a-069e-4081-b1e9-39cf0ea61a6e/C2AB6689-7857-49F7-A554-AD8610E74BED.png" alt=""></p>
<p>그래서 직접 디자인한 모달을 사용하여 이미지를 삽입하기로 결정했다. 여기서 두가지 기능이 필요하다. 우선 TinyMCE의 버튼을 활용하여 모달을 켜고 끄는 것과 외부 모달을 이용하여 이미지를 TinyMCE 에디터 안에 삽입하는 기능이다.</p>
<h3 id="툴바-버튼-만들기">툴바 버튼 만들기</h3>
<p>TinyMCE는 툴바 또는 메뉴바 버튼을 새롭게 만들 수 있다. 우리는 툴바 버튼을 생성하여 해당 버튼을 모달에 연결해주었다.</p>
<pre><code class="language-javascript">&lt;Editor
  init={{
    toolbar: &#39;custom_button&#39;,
    setup: (editor) =&gt; {
      editor.ui.registry.addButton(&#39;custom_button&#39;, {
      icon: &#39;image&#39;,
      onAction: () =&gt; setIsShowModal(true),
      });
    },
  }}
/&gt;</code></pre>
<p>위의 toolbar의 <code>custom_button</code>은 TinyMCE에서 제공해주는 옵션이 아니다. 우리가 새롭게 생성할 버튼이다. toolbar에서 설정 후 setup에서 위와 같이 버튼을 만들어주면 된다.</p>
<p>editor.ui.registry.addButton()의 경우 두가지 인자를 받는다. 첫번째는 버튼 이름이다. toolbar에서 미리 설정해놓은 이름을 적어주면 된다. 두번째는 객체로 되어있고 객체에는 버튼에 필요한 요소들을 설정할 수 있다. 위처럼 해당 객체에 icon에 기존에 TinyMCE에서 제공해주는 아이콘명을 적어줘도 되고 이미지를 따로 임포트해줘도 괜찮다. 그리고 onAction 안에 해당 버튼이 어떤 동작을 할지 설정하면 툴바 버튼 만들기는 마무리된다.</p>
<h3 id="이미지-삽입하기">이미지 삽입하기</h3>
<p> 이미지 삽입하기는 간단하다. 하지만 예제가 모두 바닐라 JS용 예제만 존재해서 방법을 찾는데 오래걸렸다. TinyMCE 자체도 하나의 textarea이기 때문에 내용을 useState로 관리를 한다. </p>
<pre><code class="language-javascript"> setState((state) =&gt; state+&lt;img src=&quot;이미지 경로&quot; /&gt;);</code></pre>
<p> 위 코드로 간단하게 이미지를 삽입 가능하다.</p>
<hr>
<h2 id="tinymce-설정-추상화">TinyMCE 설정 추상화</h2>
<p> TinyMCE 에디터를 처음 글을 등록할 때도 사용하지만 이미 쓴 글을 수정할 때도 에디터가 필요했다. 수정페이지에서 사용해야하는 재사용성의 문제도 있지만 커스텀을 많이 하다보니 설정을 위한 코드가 점점 길어졌고 해당 코드를 JSX의 HTML부분에 넣었을 때 파일 구조를 보기 힘들어져서 설정 코드를 따로 추상화하기로 결정했다.</p>
<pre><code class="language-javascript">// config.ts
export const EDITOR_CONFIG = {
  id: &#39;tinyEditor&#39;,
  init: {
    menubar: false,
    statusbar: false,
    ...설정
  },
};

// EditorContainer.tsx
...
&lt;Editor
  {...EDITOR_CONFIG}
/&gt;;
...</code></pre>
<p>위의 코드처럼 <code>EDITOR_CONFIG</code> 코드를 따로 추상화하여 불러와서 사용하였다.</p>
<hr>
<h1 id="마무리">마무리</h1>
<p>이 글에서는 TinyMCE 위지위그 에디터를 사용해보며 커스터마이징한 경험에 대해 작성했다. 기본 설정이나 TinyMCE에서 제공하는 커스터마이징은 Docs에도 잘 나와있지만 Docs에 나와있지 않은 것들도 충분히 자유롭게 커스터마이징이 가능했기 때문에 만족감이 매우 높았다. </p>
<p>많은 분들이 프로젝트에 위지위그 에디터를 사용할텐데 안정적이고 무료 버전으로도 충분한 기능 구현이 가능하며 커스터마이징이 자유로운 에디터를 찾는다면 TinyMCE를 추천한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[이미지 미리보기 기능 구현 방법 비교]]></title>
            <link>https://velog.io/@do_dadu/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</link>
            <guid>https://velog.io/@do_dadu/%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%AF%B8%EB%A6%AC%EB%B3%B4%EA%B8%B0-%EA%B8%B0%EB%8A%A5-%EA%B5%AC%ED%98%84</guid>
            <pubDate>Wed, 06 Apr 2022 01:17:33 GMT</pubDate>
            <description><![CDATA[<h1 id="이미지-미리보기-구현-이유">이미지 미리보기 구현 이유</h1>
<p>많은 서비스에 이미지 업로드 기능이 들어간다. 내가 이전 프로젝트들에서 이미지를 업로드했었다. 하지만 업로드할 이미지 미리보기 기능은 필수는 아니였다. 이미지를 제대로 서버에 올리고 나중에 제대로 받아오는 것이 더 중요하다. 하지만 사용자가 내가 올릴 이미지가 어떤 것인지 확인하고 잘못올렸을 경우 다른 이미지로 대체할 수 있도록 한다면 더 나은 사용자 경험을 줄 수 있다. 그래서 많은 서비스들이 이미지 미리보기 기능을 거의 필수로 구현한다.</p>
<p>이번 프로젝트에서는 이미지 미리보기 기능을 필수로 구현하게 되었다. 해당 기능을 구현하기 위해 여러가지 이미지 미리보기 기능을 구현할 수 있는 방법을 찾아보게 되었고 찾아보며 생각한 것들을 정리하게 되었다.</p>
<hr>
<h1 id="이미지-미리보기-구현-방법-비교">이미지 미리보기 구현 방법 비교</h1>
<h2 id="이미지-서버에-올린-후에-보여주기">이미지 서버에 올린 후에 보여주기</h2>
<p>이미지를 미리보기를 구현하는 여러가지 방법이 있다. 
첫번째는 이미지 서버에 올리고 반환된 이미지 주소를 활용하여 보여주는 것이다. 이 경우 몇가지 문제가 있다. 우선 이미지 서버에 이미지를 올리는데 꽤나 시간이 걸린다. 그렇기 때문에 이미지를 선택한 후 이미지를 화면에 띄우기까지 시간 틈이 있어 사용자가 느끼기에 즉각적이지 않다. 만약 여러번 이미지를 교체하게 된다면 대기시간의 불편함은 더 커지게 될 것이다. 두번째는 이미지를 올릴 때마다 서버에 저장되는 이미지가 생기기 때문에 이미지 서버의 비용이 당연히 늘어날 수 밖에 없다. 개인 프로젝트라면 큰 차이가 없겠지만 많은 사람들이 사용하는 서비스라면 엄청난 금액이 지불될 수 있다.</p>
<hr>
<h2 id="filereader">FileReader</h2>
<h3 id="구현-방법">구현 방법</h3>
<p>이미지 서버와 통신하지 않고 이미지 미리보기를 구현하는 방법으로는 크게 두가지가 있다. 바로 FileReader와 createObjectURL이다. 그중 FileReader를 활용한 구현 방법 먼저 확인해보자.</p>
<pre><code class="language-javascript">// JS
const [imgSrc, setImgSrc] = useState(&#39;&#39;);

const encodeFileToBase64 = (fileBlob) =&gt; {
  const reader = new FileReader();
  reader.readAsDataURL(fileBlob);
  return new Promise((resolve) =&gt; {
    reader.onload = () =&gt; {
      setImgSrc(reader.result);
      resolve();
    };
  });
};

// HTML(input)
&lt;input
  type=&quot;file&quot;
  onChange={(e) =&gt; {
    encodeFileToBase64(e.target.files[0]);
  }}
/&gt;</code></pre>
<p>위의 <code>encodeFileToBase64</code>함수는 다음과 같이 진행된다.</p>
<ul>
<li>FileReader의 인스턴스(reader)를 생성한다.</li>
<li>인자로 받아온 fileBlob을 base64 포맷으로 인코딩한다.</li>
<li>인코딩이 마무리되면 reader.result 안의 문자열을 imgSrc에 저장한다.</li>
<li>resolve를 호출하여 Promise가 이행상태가 된다.</li>
</ul>
<h3 id="filereader의-특성">FileReader의 특성</h3>
<ul>
<li>FileReader의 경우 IE10을 포함한 모든 모던 브라우저를 지원한다. </li>
<li>많은 메모리를 사용하고 해당 메모리를 우리가 지울 수 없다. 가비지 컬렉터에 의해 자동으로 지워지게 된다.</li>
<li>이미지를 띄우는데 즉각적이지 않고 약간의 시간이 걸린다.</li>
</ul>
<hr>
<h2 id="createobjecturl">createObjectURL</h2>
<h3 id="구현-방법-1">구현 방법</h3>
<p>마지막으로 내가 선택한 createObjectURL이다.</p>
<pre><code class="language-javascript">// JS
const [imgSrc, setImgSrc] = useState(&#39;&#39;);

const saveFileImage = (fileBlob) =&gt; {
  const fileUrl = URL.createObjectURL(fileBlob);
  setImgSrc(fileUrl);
};

// HTML(input)
&lt;input
  type=&quot;file&quot;
  onChange={(e) =&gt; {
    saveFileImage(e.target.files[0]);
  }}
/&gt;</code></pre>
<p>코드를 보면 훨씬 간결해졌다. <code>saveFileImage</code>함수는 다음과 같이 진행된다.</p>
<ul>
<li>URL.createObjectURL을 사용하여 인자로 받아온 fileBlob을 가리키는 URL을 DOMString으로 만들어준다.</li>
<li>만들어진 URL을 imgSrc에 저장한다.</li>
</ul>
<h3 id="revokeobjecturl을-활용한-객체-url-해제">revokeObjectURL을 활용한 객체 URL 해제</h3>
<p>createObjectURL을 활용한 방식은 같은 객체를 사용하더라도, createObjectURL()을 매번 호출할 때마다 새로운 객체 URL을 생성한다. 그렇기 때문에 생성된 객체 URL을 직접 하나씩 해제해주어야한다.</p>
<pre><code class="language-javascript">URL.revokeObjectURL(objURL);</code></pre>
<p>objURL을 해제해주는 방법은 간단한다. 위처럼 해제하려는 objURL <code>URL.revokeObjectURL()</code>  함수 안에 넣어서 호출하면 된다.</p>
<h3 id="createobjurl-특성">createObjURL 특성</h3>
<ul>
<li>createObjectURL은 IE10을 포함한 모든 모던 브라우저를 지원한다. </li>
<li>해시가 포함된 URL을 반환한다.</li>
<li>document의 언로드 이벤트가 일어나거나 직접 revokeObjectURL을 실행하지 않으면 메모리가 남아있게된다.</li>
<li>이미지를 즉각적으로 띄울 수 있다.</li>
</ul>
<hr>
<h1 id="마무리">마무리</h1>
<p>위에서 이미지 미리보기를 구현 할 수 있는 여러가지 방법을 소개했다. 여러가지 방법을 소개했지만 앞선 두가지 방법은 장점보다 단점이 많았기 때문에 createObjectURL 방식을 추천한다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내가 사용하는 GitHub Actions]]></title>
            <link>https://velog.io/@do_dadu/%EB%82%B4%EA%B0%80-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-GitHub-Actions</link>
            <guid>https://velog.io/@do_dadu/%EB%82%B4%EA%B0%80-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94-GitHub-Actions</guid>
            <pubDate>Tue, 05 Apr 2022 13:08:59 GMT</pubDate>
            <description><![CDATA[<h1 id="github-actions란">GitHub Actions란?</h1>
<blockquote>
<p>GitHub Actions는 빌드, 테스트 및 배포 파이프라인을 자동화할 수 있는 CI/CD 플랫폼입니다. 레포지토리에 대한 모든 풀 요청을 빌드 및 테스트하는 워크플로를 생성하거나 병합된 풀 요청을 프로덕션에 배포할 수 있습니다.
GitHub Actions는 단순한 DevOps를 넘어 리포지토리에서 다른 이벤트가 발생할 때 워크플로를 실행할 수 있도록 합니다. 예를 들어, 누군가가 저장소에 새 문제를 생성할 때마다 적절한 레이블을 자동으로 추가하는 워크플로를 실행할 수 있습니다. 
-<a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions">GitHub 홈페이지</a>-</p>
</blockquote>
<p>깃헙에서는 GitHub Actions를 위와 같이 설명하고 있다. 쉽게 얘기하면 깃헙에서 특정 이벤트가 발생했을 때 어떤 동작을 할지 설정해놔서 리액션을 자동화하는 서비스라고 생각하면 편할 것이다. 우리가 사용할 리소스를 순수 개발에 더 신경쓸 수 있게된다.</p>
<hr>
<h1 id="github-actions-적용">GitHub Actions 적용</h1>
<p>나는 주로 3가지의 설정을 많이 사용한다. 3가지 모두 Pull request를 날렸을 때의 액션이 일어난다. 린트 확인, 빌드 테스트, 배포이다. 이 세가지를 사용하여 CI/CD를 자동화하여 사용한다. GitHub Actions 파일들은 .github/workflows 폴더에서 관리해주면된다.</p>
<h2 id="린트-확인">린트 확인</h2>
<p>우선 전체 코드를 보여주고 한줄씩 설명하도록 하겠습니다.</p>
<pre><code class="language-yml">name: Lint check 

on: pull_request

jobs:
  build-test:
    name: Next Lint test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: &#39;16&#39;
      - run: npm i -g yarn
      - run: yarn
      - run: yarn lint</code></pre>
<ul>
<li><p>우선 첫 줄의 <code>name</code>은 위크플로우의 이름이다. 위 값은 다른 워크플로우와 겹치지 않게 사용해야한다.</p>
</li>
<li><p><code>on</code>에서는 레포지토리의 어떤 이벤트를 감지할 것인지 설정해야한다. 위에서는 pull_request를 사용했지만 push를 사용하거나 특정 브랜치, 태그, path에서 작동하도록 설정이 가능하다.</p>
</li>
<li><p>그 다음은 <code>jobs</code>에 관련된 설정이다. name에서 job의 이름을 설정해주고 runs-on에서 어떤 환경에서 사용할지 설정이 가능하다. steps에서는 이제 어떤 일을 해줄지에 대해서 설정을 할 수 있다. Github 마켓플레이스에 올라온 액션의 사용도 가능하고 shell script 실행도 가능하다. 누군가 만들어 놓은 액션을 사용할 때는 uses를 사용해야한다. 그리고 with를 사용하여 액션에 값을 전달할 수도있다. 위의 코드는 node-version이 16이라는 의미다. shell script를 실행할 때는 run 키워드를 사용하면된다. 우리는 npm i -g yarn, yarn, yarn lint 명령어를 실행 시켰다.</p>
</li>
</ul>
<hr>
<h2 id="빌드-테스트">빌드 테스트</h2>
<pre><code class="language-yml">name: Build test

on: pull_request

jobs:
  build-test:
    name: Next build test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: &#39;14&#39;
      - run: npm i -g yarn
      - run: yarn
      - run: yarn build</code></pre>
<p>위의 린트 확인 코드와 거의 동일하며 run에 yarn build 코드만 다르다.</p>
<hr>
<h2 id="vercel-배포">Vercel 배포</h2>
<p>GitHub Actions로 Vercel 배포도 자동화가 가능하다. 이 부분도 코드를 먼저 확인한 후 설명을 진행하겠다.</p>
<pre><code class="language-yml">name: Deploy CI
on:
  push:
    branches: [main]
  pull_request:
    types: [opened, synchronize, reopened]
jobs:
  deploy:
    runs-on: ubuntu-latest
    if: &quot;!contains(github.event.head_commit.message, &#39;[skip ci]&#39;)&quot;
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Deploy to Vercel Action
        uses: BetaHuhn/deploy-to-vercel-action@latest
        with:
          GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
          VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
          VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
          VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}</code></pre>
<p>Vercel 배포는 다른 액션들과는 다르게 push 옵션에 브랜치가 설정이 되어있다. main 브랜치에 푸시할 때만 진행된다. pull_request에서는 타입 설정이 되어있다. pull_request가 위의 세가지 타입일 때만 해당 액션이 진행될 것이다.</p>
<p>jobs에 if 키워드를 사용하여 커밑 메시지에 <code>skip ci</code>가 포함되면 액션이 진행되지 않도록 설정되어 있다.</p>
<p>steps의 다른 키워드들은 이전에 봤지만 with 내부의 값들이 처음 보는 방식으로 값이 전달되고 있다. 위의 secret들이 어떻게 설정할 수 있고 위 값들은 어디서 가져와서 쓰는 것인지도 추가로 설명하겠다.</p>
<h3 id="github-secrets">GitHub Secrets</h3>
<p>Secrets의 설정은 간단하다. 원하는 레포지토리의 Settings&gt;Secrets&gt;Actions로 들어가면 된다. 들어가면 우측 상단의 <code>New repository secret</code> 버튼을 눌러서 설정하면 된다.
<img src="https://media.vlpt.us/images/do_dadu/post/5c32dc5d-5377-4faf-95b3-587cd0146028/8EA22278-BA17-4E7E-AF6B-D47DCBA0DF5F.png" alt=""></p>
<h3 id="github_token">GITHUB_TOKEN</h3>
<p>GitHub Token은 레포지토리가 아닌 오른쪽 위 프로필에서 Settings로 들어가야 발급이 가능하다. </p>
<ul>
<li><p>Settings&gt;Developer settings&gt;Personal access tokens에서 생성이 가능하다. 깃헙에 로그인이 되어있다면 <a href="https://github.com/settings/tokens">이 링크</a>로 접근이 가능할 것이다. </p>
</li>
<li><p>알맞은 페이지에 들어갔다면 우측 상단의 <code>Generate new token</code> 버튼을 눌러서 토큰을 생성하면된다. 토큰은 workflow 옵션 하나만 설정해주면 된다.
<img src="https://media.vlpt.us/images/do_dadu/post/f904cd4d-311d-4bf4-8635-cf33dd320722/C02DC142-B3BB-4A54-8226-3B4637D56599.png" alt=""></p>
</li>
<li><p>모든 설정 후 토큰을 만들면 토큰 값을 복사할 수 있게 나올 것이다. 해당 값을 복사하여 위의 GitHub Secrets에 들어가서 새 Secret을 만들면 된다.
<img src="https://media.vlpt.us/images/do_dadu/post/87c7ba6b-d3b8-4d84-a8f3-259edc784ee8/342E4FBE-6307-4B38-A86B-1A9000B03133.png" alt=""></p>
</li>
</ul>
<h3 id="vercel_token">VERCEL_TOKEN</h3>
<p>이제는 Vercel token을 받으러 Vercel로 이동하여야한다. </p>
<ul>
<li>Vercel의 우측 상단의 프로필의 Settings&gt;Tokens로 가면된다. 이것도 마찬가지로 Vercel에 로그인이 되어있다면 <a href="https://vercel.com/account/tokens">이 링크</a>로 접근가능하다.</li>
<li>해당 페이지의 Tokens 섹션에서 <code>Create</code> 버튼을 눌러 토큰을 생성하고 SCOPE는 Full Account를 설정해주면 된다.
<img src="https://media.vlpt.us/images/do_dadu/post/4d3ef5d8-efa4-429f-a2ff-4035a928b356/58519475-2D5B-40A9-9377-7889E0EABF4E.png" alt=""></li>
</ul>
<p>마찬가지로 생성된 토큰의 값을 GitHub Secrets에 Secret으로 저장하면 된다.</p>
<h3 id="vercel_org_id--vercel_project_id">VERCEL_ORG_ID &amp; VERCEL_PROJECT_ID</h3>
<p>이 두개는 한번에 받아올 수 있다. 이 두개는 vercel CLI를 사용해서 받아올 수 있다. </p>
<ul>
<li><p>우선 vercel을 깔아야한다.</p>
<pre><code>npm i -g vercel</code></pre></li>
<li><p>이후 Vercel에 로그인을 진행해준다.</p>
<pre><code>vercel login</code></pre></li>
<li><p>로그인이 다 되었다면 배포를 진행한다.</p>
<pre><code>vercel</code></pre><p><img src="https://media.vlpt.us/images/do_dadu/post/2415ae92-9faf-46a7-a6af-b0ecab2872b1/58E7728E-7A13-48B4-A182-75BBAC727FD1.png" alt="">
위의 과정이 끝나면 <code>.vercel</code> 폴더가 생긴다. 해당 폴더의 <code>project.json</code>파일을 보면 orgId, projectId가 있다. 이 값도 GitHub Secrets에 Secret으로 저장하면 모든 설정은 끝난다.</p>
</li>
</ul>
<hr>
<h1 id="마무리">마무리</h1>
<p>위의 세가지를  풀 리퀘스트에서 자동화해주면 배포 시에 생길 문제를 자동으로 체크가 가능해지고 문제가 없다면 배포까지 자동화가 진행된다. 
개발을 하다보면 자동화에 대해서 항상 생각하게된다. 개발하는 것 이외에 리소스를 사용하는 것이 너무 아깝기 때문이다. 혹시 저와 같은 생각을 하고 있다면 이 기회에 GitHub Actions를 사용하시는 것을 추천합니다. 그리고 오늘 소개해드린 세가지 말고도 GitHub에서 확인해보면 정말로 많은 workflow가 만들어져있으니 한번 보셨으면 좋겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[GraphQL은 완벽할까?]]></title>
            <link>https://velog.io/@do_dadu/GraphQL%EC%9D%80-%EC%99%84%EB%B2%BD%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@do_dadu/GraphQL%EC%9D%80-%EC%99%84%EB%B2%BD%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Wed, 23 Mar 2022 14:20:40 GMT</pubDate>
            <description><![CDATA[<h1 id="1-graphql이란">1. GraphQL이란?</h1>
<blockquote>
<p>GraphQL은 API용 쿼리 언어이자 기존 데이터로 이러한 쿼리를 수행하기 위한 런타임입니다. GraphQL은 API의 데이터에 대한 완전하고 이해하기 쉬운 설명을 제공하고, 클라이언트가 필요한 것을 정확하게 요청할 수 있는 기능을 제공하며, 시간이 지남에 따라 API를 더 쉽게 발전시키고 강력한 개발자 도구를 활성화합니다. -<a href="https://graphql.org">GraphQL 공식홈페이지</a></p>
</blockquote>
<p>GraphQL은 우선 SQL과 마찬가지로 쿼리 언어이다. 하지만 그래프큐엘과 SQL의 사용성 측면에서 큰 차이가 있다. 우선 SQL은 데이터베이스에 저장된 데이터를 효율적으로 가져오는 것이 목적이지만, 그래프큐엘은 웹 클라이언트가 데이터를 서버로부터 효율적으로 가져오는 것이 목적이다. 그래서 SQL은 백엔드에서 작성 및 호출하지만 그래프큐엘은 클라이언트에서 작성하고 호출한다. 그렇기 때문에 그래프큐엘이 대체하는 것은 기존의 쿼리 언어들이 아닌 REST API이다.</p>
<p>그렇다면 프론트엔드에서 무엇이 불편했길래 메타(<del>구 페이스북</del>)는 그래프큐엘이라는 쿼리 언어를 만들었고 사람들은 그래프큐엘을 사랑하게 되었는지 알아보자.
<img src="https://images.velog.io/images/do_dadu/post/0c281c0f-b9a5-462c-9757-d62b4439da50/CC164688-FF5F-4684-8F0B-2E1765E0B092.png" alt=""></p>
<h1 id="2-graphql이-나오게-된-배경">2. GraphQL이 나오게 된 배경</h1>
<p>기존의 REST API는 리소스와 URI를 조합하여 요청을 보내면 이에 대응하는 응답을 JSON이나 XML 형식으로 넘겨주었다. REST API의 가장 큰 강점은 예측 가능하고 일정한 정보와 작업을 요청하는 것이였다. 하지만 여기서 생기는 문제는 정해진 응답만을 받을 수 있다는 것이다. 예를 들어 A의 전화번호 정보를 받기위해서 A의 모든 정보를 받아야할 수 있다. 이렇게 필요없는 정보까지 받기 위해서 불필요한 리소스(시간, 통신비)가 생긴다.(Overfatching) 이러한 다른 모양의 다양한 요청과 응답이 필요한 서비스들이 많이 생겨났고 그로인한 리소스들을 줄이기 위해서 GraphQL이 각광받게 되었다.</p>
<h2 id="rest-api-vs-graphql">REST API vs GraphQL</h2>
<p>REST API는 URL과 METHOD 등을 조합하여 사용하기 때문에 필연적으로 많은 Endpoint가 존재한다. 하지만 그래프큐엘은 하나의 Endpoint만 존재한다. 그렇기 때문에 REST API에서는 각 Endpoint마다 데이터베이스 SQL 쿼리가 달라지지만 그래프큐엘은 스키마의 타입마다 데이터베이스 SQL 쿼리가 달라진다.
<img src="https://images.velog.io/images/do_dadu/post/9a35546a-1b27-4a37-b206-36e30b3fe74d/image.png" alt="">
그러므로 위의 그림처럼 그래프큐엘은 네트워크를 여러번 호출(Underfatching)을 할 필요없이 쿼리 언어를 직접 작성하여 한번의 네트워크 호출로 원하는 데이터들을 받아올 수 있다.</p>
<h1 id="3-graphql의-구조">3. GraphQL의 구조</h1>
<h2 id="3-1-query-쿼리--mutation-뮤테이션">3-1. Query 쿼리 / Mutation 뮤테이션</h2>
<p>REST API의 경우에는 데이터 CRUD를 하기위해 get, post, put, delete를 사용하지만 GraphQL은 <code>Query</code>와 <code>Mutation</code> 두 가지만 사용한다. 데이터를 Read를 할 때는 Query를 사용하고 Create, Update, Delete를 하기 위해서 Mutation을 사용한다.
하지만 그래프큐엘 내부적으로 Query와 Mutation은 차이가 거의 없다. 그렇기 때문에 쿼리로 데이터를 수정할 수 있다. 그렇지만 변경을 발생시키는 작업은 명시적으로 뮤테이션를 이용해서 전송되어야 한다는 규칙을 지키는 것이 좋다.</p>
<h3 id="example">Example</h3>
<h4 id="query요청-쿼리--응답-데이터">Query(요청 쿼리 / 응답 데이터)</h4>
<p><img src="https://images.velog.io/images/do_dadu/post/1a4919e1-7f28-4831-adf5-7549f50c2dc8/AF413C53-E351-4DED-8E6D-2C73C39F1218.png" alt=""></p>
<p>위 그림과 같이 쿼리와 받은 데이터는 동일한 형태이다. 이렇게 명확하고 직관적인 응답 데이터를 받을 수 있는 이유는 그래프큐엘을 통해 서버가 클라이언트가 원하는 필드를 정확히 알 수 있기 때문이다.</p>
<h4 id="mutation요청-뮤테이션--응답-데이터">Mutation(요청 뮤테이션 / 응답 데이터)</h4>
<p><img src="https://images.velog.io/images/do_dadu/post/579712ae-c6af-4273-a86e-14a61e5c25f1/842138A6-C7D7-48C8-9DDF-BB03B6BA66B5.png" alt="">
<code>createReview</code> 필드가 새로 생성된 리뷰의 stars 와 commentary 필드를 반환한다. 이는 하나의 요청으로 필드의 새 값을 변경하고 쿼리할 수 있기 때문에 기존 데이터를 변경하는 경우 특히 유용하다.</p>
<h4 id="프래그먼트요청-쿼리">프래그먼트(요청 쿼리)</h4>
<p><img src="https://images.velog.io/images/do_dadu/post/99d8d03e-e545-43e9-a067-5f67f7a4c46f/B9A092FC-AE27-4624-8BA0-1D6A93B39A0B.png" alt="">
우리가 가져오려는 데이터의 형식이 너무 복잡하고 이 필드를 여러번 반복해서 사용해야한다면 우리는 프래그먼트를 사용할 수 있다. 프래그먼트 개념은 주로 복잡한 응용 프로그램의 데이터 요구사항을 작은 단위로 분할하는데 사용된다. 리액트에서 컴포넌트화하는 것을 연상하면 이해하기 쉬울 것이다.</p>
<h2 id="3-2-introspection-인트로스펙션">3-2. Introspection 인트로스펙션</h2>
<p>기존의 서버와 클라이언트의 협업 방식에서는 API 명세서를 주고 받는 절차가 필요하다.이러한 절차는 당연히 그에 맞는 리소스를 잡아먹게 된다. 만약 API 명세서를 제대로 업데이트하지 않는다면 그것은 큰 문제를 야기할 수 있다.
이러한 REST의 API 명세서 생성 자동화하는 것이 그래프큐엘의 인트로스펙션 기능이다. 그래프큐엘의 인트로스펙션은 서버에서 정의된 스키마의 정보를 생성 및 공유할 수 있게한다. 
<img src="https://images.velog.io/images/do_dadu/post/b9cf8735-999a-4e48-b6c1-646f59daf177/26570130-2DA8-4E8A-9F36-52A8CC042745.png" alt="">
위 사진은 그래프큐엘 라이브러리에서 제공하는 플레이그라운드라고 불리는 쿼리용 IDE이다. 플레이그라운드에서 직접 쿼리 및 뮤테이션, 필드 스키마를 확인 할 수 있다. 물론 상용환경에서는 이러한 스키마의 공개한다면 보안 이슈가 존재한다. 그러므로 해당 기능을 끄는 옵션을 사용하여 상용환경에서는 끄는 것을 권장한다.</p>
<h1 id="4-정리">4. 정리</h1>
<h2 id="장점">장점</h2>
<ul>
<li>그래프큐엘은 기존의 REST API가 필요한 데이터를 받기 위해서 여러번 요청을 보내야 했던 것(Underfetching)을 한번의 요청으로 받아올 수 있다.</li>
<li>필요하지 않은 데이터까지 포함된 데이터 덩어리를 받아오지(Overfetching) 않고 원하는 데이터만 선택적으로 받아올 수 있다.</li>
<li>가장 큰 장점은 인트로펙션 기능을 통해서 API 명세의 공유가 자동화 된다는 점이다. 이 부분은 개발의 생산성이 향상되어 백엔드 - 프론트엔드의 협업이 더 쉬워질 것이다.</li>
</ul>
<h2 id="단점">단점</h2>
<ul>
<li>File 전송 등 Text 만으로 하기 힘든 내용들을 처리하기 위해서는 외부 서비스에 의존하거나 복잡해진다.</li>
<li>고정된 요청과 응답만 필요할 때는 Query 요청의 크기가 REST API 보다 더 커진다.</li>
</ul>
<h2 id="마무리">마무리</h2>
<p>오늘 알아본 결과 그래프큐엘은 매우 훌륭하고 좋은 언어이자 서비스지만 만능은 아니다. 어떤 정보를 제공하느냐에 따라 REST API와 그래프큐엘의 효율성이 왔다갔다한다.</p>
<p>서로 다른 모양의 다양한 요청과 응답이 많다면 그래프큐엘이 효율적이지만 HTTP와 HTTPS에 의한 캐싱을 잘 활용하고 싶거나 요청의 구조가 정해지지 않았을 때는 REST API가 효율적일 가능성이 높다.</p>
<p>하지만 하나의 솔루션만을 선택할 필요없이 백엔드 서버에 두가지 솔루션을 모두 사용할 수 있다. 그러니 우리는 새로운 도구를 얻었고 기존의 도구와 잘 활용한다면 더 나은 서비스를 제공할 수 있을 것이다!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[다크 모드 구현하기(어서와 우리집 개발 기록)]]></title>
            <link>https://velog.io/@do_dadu/%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@do_dadu/%EB%8B%A4%ED%81%AC-%EB%AA%A8%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 21 Jan 2022 13:49:33 GMT</pubDate>
            <description><![CDATA[<h1 id="why">Why?</h1>
<p>다크 모드를 설정할 수 없는 웹사이트가 대부분이지만 많은 웹사이트들이 다크모드를 지원하고있다. 다크 모드를 구현하려면 어두운 테마에 맞는 디자인이 추가적으로 들어간다. 추가적인 리소스를 쏟으면서 다크 모드를 구현하는 이유가 무엇일까? 우리가 어두운 곳에서 핸드폰을 할 때 핸드폰 밝기를 낮춘 경험이 있을 것이다. 바로 주변이 어두운 경우 우리의 눈은 빛에 예민해진다. 예민한 눈에 계속해서 밝은 빛을 비추면 눈이 쉽게 피로해지며 눈 건강에도 좋지 않다. 하지만 어두운 곳이 아님에도 빛에 예민한 사용자들이 있다. 이러한 사용자들은 라이트 모드 밖에 없는 웹사이트를 이용할 때 우리가 어두운 곳에서 밝기가 고정된 핸드폰을 하는 느낌을 받게 될 것이다. 그렇기에 웹 접근성을 높이기 위해서 다크 모드를 설정 가능하도록 구현해야한다.</p>
<h1 id="개발-진행">개발 진행</h1>
<h2 id="개발-환경">개발 환경</h2>
<ul>
<li>Next.js</li>
<li>Typescript</li>
<li>Emotion</li>
</ul>
<h2 id="themeprovider">ThemeProvider</h2>
<p>이번 프로젝트에서 Emotion을 스타일 라이브러리로 사용하고 있다. 테마 스타일를 적용하기 위해 Emotion에서 제공하는 ThemeProvider 컴포넌트를 커스텀하여 사용했다. ThemeProvider에 Theme을 제공하게 되면 자식 요소에서 해당 Theme을 받아서 사용할 수 있되어 추후 Theme 수정시 일괄적으로 생상 변경이 가능하므로 유지보수에 편리하다.</p>
<h2 id="고려-상황들">고려 상황들</h2>
<h3 id="window-객체와-nextjs">window 객체와 Next.js</h3>
<ul>
<li>문제
사용자의 다크모드 설정을 localStorage에 저장하고 불러와서 쓰기위해서 window 객체가 필요했다. 하지만 Next.js에서는 window 객체를 React처럼 그냥 사용한다면 <code>ReferenceError: window is not defined</code> 라는 에러를 만나게된다. Next.js는 기본적으로 SSG를 지원하기 때문에 브라우저 환경에서 사용할 수 있는 window 객체 사용 불가능하다.</li>
<li>해결
그렇기 때문에 브라우저 환경에서 해당 코드가 실행되게 하기위해 useEffect를 활용해서 마운트 되었을 때 window를 사용해 주었다.<pre><code class="language-typescript">// 예시 코드
useEffect(() =&gt; {
  if (
    window.localStorage.getItem(&#39;welcoming-theme&#39;) === &#39;dark&#39;
  ) {
    setDark(true);
  }
}, []);</code></pre>
<h3 id="사용자-테마-감지">사용자 테마 감지</h3>
시스템을 다크 테마를 사용하는 사용자에게는 웹사에트 첫 방문시 다크 모드를 적용해서 보여준다면 더 나은 사용자 경험을 제공할 것이다. 
사용자의 시스템의 테마를 확인하기위해 <code>prefers-color-scheme</code>을 이용해 확인할 수 있다. </li>
</ul>
<h3 id="type-error">Type error</h3>
<p>Typescript와 ThemeProvider를 같이 사용할 때 테마의 타입을 제공하지 않으면 css props에서 theme의 값을 사용할 수 없다. <a href="https://emotion.sh/docs/typescript#define-a-theme">공식문서</a>를 확인해보면 <code>props.theme</code>을 의도적으로 비워뒀기 때문이다. type-safe를 위해서 비워뒀고 우리가 해당 타입을 정의해서 사용해야한다.</p>
<pre><code class="language-typescript">// 예시 코드
import &#39;@emotion/react&#39;

declare module &#39;@emotion/react&#39; {
  export interface Theme {
    color: {
      primary: string
      positive: string
      negative: string
    }
  }
}</code></pre>
<h2 id="code">Code</h2>
<h3 id="_apptsx-파일-설정">_app.tsx 파일 설정</h3>
<pre><code class="language-typescript">// _app.tsx
import type { AppProps } from &#39;next/app&#39;;
import client from &#39;../apollo&#39;;
import { CustomThemeProvider } from &#39;../styles/CustomThemeProvider&#39;;

function MyApp({ Component, pageProps }: AppProps) {
  return (
    &lt;CustomThemeProvider&gt;
      &lt;Component {...pageProps} /&gt;
    &lt;/CustomThemeProvider&gt;
  );
}

export default MyApp;</code></pre>
<h3 id="customthemeprovidertsx-구현">CustomThemeProvider.tsx 구현</h3>
<pre><code class="language-typescript">//CustomThemeProvider.tsx
import { Global, ThemeProvider } from &#39;@emotion/react&#39;;
import styled from &#39;@emotion/styled&#39;;
import { useCallback, useEffect, useState } from &#39;react&#39;;
import { GlobalStyles } from &#39;./globals&#39;;
import { mode } from &#39;./theme&#39;;

export const CustomThemeProvider: React.FC = ({ children }) =&gt; {
  const [mounted, setMounted] = useState(false);
  const [theme, setTheme] = useState(mode.light);
  const [dark, setDark] = useState(false);
  useEffect(() =&gt; {
    setMounted(true);
    if (
      window.localStorage.getItem(&#39;welcoming-theme&#39;) === &#39;dark&#39; ||
      (window.matchMedia(&#39;(prefers-color-scheme: dark)&#39;).matches &amp;&amp;
        !window.localStorage.getItem(&#39;welcoming-theme&#39;))
    ) {
      setDark(true);
    }
  }, []);
  useEffect(() =&gt; {
    window.localStorage.setItem(
      &#39;welcoming-theme&#39;,
      `${dark ? &#39;dark&#39; : &#39;light&#39;}`,
    );
    if (window.localStorage.getItem(&#39;welcoming-theme&#39;) === &#39;dark&#39;) {
      setTheme(mode.dark);
    } else if (window.localStorage.getItem(&#39;welcoming-theme&#39;) === &#39;light&#39;) {
      setTheme(mode.light);
    }
  }, [dark]);
  const toggleTheme = useCallback(() =&gt; {
    setDark((curr) =&gt; !curr);
  }, [dark]);

  const body = (
    &lt;ThemeProvider theme={theme}&gt;
      &lt;Global styles={GlobalStyles(theme)} /&gt;
      {children}
      &lt;DarkModeBtn type=&quot;button&quot; onClick={toggleTheme}&gt;
        {dark ? &#39;라이트 모드로 보기&#39; : &#39;다크 모드로 보기&#39;}
      &lt;/DarkModeBtn&gt;
    &lt;/ThemeProvider&gt;
  );

  if (!mounted) {
    return &lt;div style={{ visibility: &#39;hidden&#39; }}&gt;{body}&lt;/div&gt;;
  }
  return body;
};

const DarkModeBtn = styled.button`
  position: fixed;
  bottom: 30px;
  right: 30px;
  height: 40px;
  padding: 0 25px;
  border-radius: 20px;
  background: ${({ theme }) =&gt; theme.bg.darkBtn};
  color: ${({ theme }) =&gt; theme.text.darkBtn};
  font-weight: 600;
`;</code></pre>
<h3 id="themets-테마-파일-설정">theme.ts 테마 파일 설정</h3>
<pre><code class="language-typescript">// theme.ts
import { Theme, ThemeMode } from &#39;@emotion/react&#39;;

declare module &#39;@emotion/react&#39; {
  export interface ThemeMode {
    bg: {
      primary: string;
      bodyBg: string;
      darkBtn: string;
    };
    text: {
      primary: string;
      bodyText: string;
      darkBtn: string;
    };
  }
  export interface Theme extends ThemeMode {
    mediaQuery: {
      mobile: string;
      tablet: string;
      laptop: string;
      desktop: string;
    };
  }
}
interface ThemeGroup {
  light: Theme;
  dark: Theme;
}

const light: ThemeMode = {
  bg: {
    primary: &#39;#35c5f0&#39;,
    bodyBg: &#39;#ffffff&#39;,
    darkBtn: &#39;#eeeeee&#39;,
  },
  text: {
    primary: &#39;#35c5f0&#39;,
    bodyText: &#39;#000000&#39;,
    darkBtn: &#39;#000000&#39;,
  },
};
const dark: ThemeMode = {
  bg: {
    primary: &#39;#050505&#39;,
    bodyBg: &#39;#1e1f21&#39;,
    darkBtn: &#39;#757575&#39;,
  },
  text: {
    primary: &#39;#fbfbfc&#39;,
    bodyText: &#39;#d9d9d9&#39;,
    darkBtn: &#39;#ffffff&#39;,
  },
};
interface MEDIA {
  mobile: string;
  tablet: string;
  laptop: string;
  desktop: string;
}

export const mediaQuery: MEDIA = {
  mobile: &#39;375px&#39;,
  tablet: &#39;768px&#39;,
  laptop: &#39;1024px&#39;,
  desktop: &#39;1600px&#39;,
};

export const mode: ThemeGroup = {
  light: { ...light, mediaQuery },
  dark: { ...dark, mediaQuery },
};</code></pre>
<h3 id="global-style-설정">Global style 설정</h3>
<pre><code class="language-typescript">import { css, Theme } from &#39;@emotion/react&#39;;

export const GlobalStyles = (theme: Theme) =&gt; css`
  /* reset css 적용 */
  body {
    background: ${theme.bg.bodyBg};
    color: ${theme.text.bodyText};
  }
`;

export default GlobalStyles;
</code></pre>
<p>위의 코드처럼 css props를 구조분해 할당을 이용하여 theme 값을 받아와서 사용할 수 있다. </p>
<p><img src="https://images.velog.io/images/do_dadu/post/33a3cccc-5b4d-4920-aff2-9b926b51587d/ezgif.com-video-to-gif.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[어서와 우리집-기술 스택 선정 이유]]></title>
            <link>https://velog.io/@do_dadu/%EC%96%B4%EC%84%9C%EC%99%80-%EC%9A%B0%EB%A6%AC%EC%A7%91-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EB%B3%B8-%EC%84%B8%ED%8C%85</link>
            <guid>https://velog.io/@do_dadu/%EC%96%B4%EC%84%9C%EC%99%80-%EC%9A%B0%EB%A6%AC%EC%A7%91-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EA%B8%B0%EB%B3%B8-%EC%84%B8%ED%8C%85</guid>
            <pubDate>Thu, 20 Jan 2022 07:12:36 GMT</pubDate>
            <description><![CDATA[<h1 id="프로젝트-개요">프로젝트 개요</h1>
<ul>
<li>쇼핑몰의 주요 기능을 구현해보는 프로젝트입니다.</li>
<li><a href="https://github.com/Jetty2020/welcoming-frontend">Github 주소</a></li>
</ul>
<h1 id="기술-스택">기술 스택</h1>
<h2 id="프레임워크">프레임워크</h2>
<h3 id="nextjs">Next.js</h3>
<p>쇼핑몰 컨셉상 검색을 통한 유입 유저가 많다는 것을 생각하여 SSG 또는 SSR을 통해 SEO가 가능한 React 기반 프레임워크인 Next.js를 선정하였다. 추가적으로 Next.js는 자체적인 라우팅 시스템이 있어 기본 React로 구현하는 것보다 아키텍처가 제한된다. 이런 점은 새롭게 배워야한다는 불편함이 있지만 길게 봤을 때는 내가 Next.js를 사용했다는 점 하나만으로 타인이 내 프로젝트의 아키텍처를 볼 때 비교적으로 예상 가능하게 만들어준다는 장점이 있다. 타인 뿐만 아니라 3~4개월 후의 내가 보고 다시 유지보수하기에도 쉬워진다.</p>
<hr>
<h2 id="언어">언어</h2>
<h3 id="typescript">Typescript</h3>
<p>단순하게 보면 Javascript에 정적타입이라는 컨셉을 추가한 언어다. 하지만 정적타입이라는 컨셉이 추가되면서 개발자한테 정말로 많은 생상성 향상을 제공한다. 명시적인 정적타입을 지원하면서 코드의 가독성을 높이고 TS에서 JS로의 컴파일 단계에서 오류를 포착할 수 있게 되었다.</p>
<hr>
<h2 id="스타일-라이브러리">스타일 라이브러리</h2>
<h3 id="emotion">Emotion</h3>
<p>Emotion은 CSS in JS 컨셉의 style 라이브러리다. 우선 CSS in JS는 기존의 CSS 파일처럼 스타일시트를 문서 레벨로 관리하는 것이 아닌 컴포넌트 레벨로 추상화하여 관리를 하는 개념이다. 이 개념의 style 라이브러리를 React 프로젝트에서 사용할 때 가장 큰 장점은 React의 컴포넌트와 그에 맞는 style 코드를 같은 파일에서 관리가 가능하여 이후 코드 가독성과 유지보수가 편해진다.
그리고 개발이 어려워지는 이유 중에 하나는 수 많은 이름을 지어줘야한다는 점이다. 기존의 CSS를 사용하게 되면 스타일이 필요한 대부분의 요소에 클래스명을 중복되지 않게 지어줘야하며 가독성을 위해 명시적으로 지어줘야한다. 하지만 CSS in JS 개념의 라이브러리를 사용한다면 컴포넌트 내에서만 중복을 피하면 되기 때문에 클래스명 생성에서 오는 개발의 난이도가 낮아진다.
CSS in JS에 많이 사용하는 것으로는 Styled-component와 Emotion이 있다. 이중에 Emotion을 고른 이유는 styled component보다 번들 사이즈가 작고, <a href="https://www.youtube.com/watch?v=MN3RWhGudvw">퍼포먼스가 좋다</a>라는 이유로 Emotion을 사용하기로 했다.</p>
<hr>
<h2 id="상태관리">상태관리</h2>
<h3 id="apollo-client">Apollo Client</h3>
<p>Apollo를 이용하여 간단하게 상태관리 설정이 가능하다. 최근 리덕스 툴킷을 사용하려고 기본 설정을 한 적이 있었는데 리덕스 툴킷이 기존의 리덕스의 기본 설정이 너무 코드가 길고 러닝커브가 크다는 이유에서 등장했고 그에 맞게 러닝커브도 줄고 보일러 플레이트의 크기도 줄었다. 하지만 Apollo와 비교하니 리덕스는 아직도 너무 큰 러닝커브와 보일러 플레이트가 필요했다. </p>
<pre><code class="language-javascript">// 아폴로 세팅 예시
import { ApolloClient, InMemoryCache } from &#39;@apollo/client&#39;;

const client = new ApolloClient({
  uri: &#39;http://localhost:5050/graphql&#39;,
  cache: new InMemoryCache(),
});

export default client;
</code></pre>
<hr>
<h2 id="데이터-패칭">데이터 패칭</h2>
<h3 id="apollo--graphql">Apollo &amp; GraphQL</h3>
<p>장점 몇가지를 소개하자면 우선 PlayGround라는 GUI 인터페이스를 기본으로 제공한다. 제공하는 DOCS를 통해 쿼리들와 뮤테이션들의 정보를 확인할 수 있고 SCHEMA를 통해 테이블들의 타입 확인이 가능하다.</p>
<p><img src="https://images.velog.io/images/do_dadu/post/b1f2387e-985c-4d34-947e-d2299903260a/image.png" alt=""></p>
<p>그리고 GraphQL을 사용하여 필요한 데이터를 위한 쿼리문을 프론트엔드에서 작성해서 사용하므로 언더패칭, 오버패칭을 방지할 수 있다. 다음과 같이 원하는 데이터를 골라서 사용할 수 있다.</p>
<pre><code class="language-javascript">// GraphQL 사용 예시
const LOGIN_MUTATION = gql`
  mutation loginMutation($loginInput: LoginInput!) {
    login(input: $loginInput) {
      ok
      token
      error
    }
  }
`;</code></pre>
<p>gql 사용하여 쿼리문을 프론트엔드에서 작성하므로 프론트엔드 개발자가 데이터를 직접 다룰 수 있게 되어 자유도가 올라간다.</p>
<hr>
<h2 id="코드-스타일">코드 스타일</h2>
<h3 id="prettier--eslint">Prettier &amp; ESLint</h3>
<p>코드 스타일을 맞추지 않고 개발을 진행하다보면 에러가 생길 확률이 프로젝트가 진행하면 할 수록 커질 수 밖에 없어진다. 이 점을 방지하기 위해 코드 스타일을 컨벤션으로 정하고 프로젝트를 진행하게되는데 문서화된 컨벤션만으로는 강제성을 띄우기 힘들어진다. 강제성을 높이기 위해 ESLint와 Prettier를 이용하였다.
Prettier는 문법 오류는 아니지만 미관상의 이유로 가독성이 낮아지는 것을 방지하기 위해 코드를 자동 정렬하고 다듬어준다. ESLint는 문법적으로 오류가 생길 수 있는 부분들에 대해서 에러나 워닝을 띄우고 일부의 에러는 강제변환을 도와준다. ESLint의 코드 스타일은 airbnb 스타일 가이드를 기본으로 채택하여 사용하고 필요하지 않은 룰을 삭제해서 사용하고 있다.</p>
<h3 id="husky--lint-staged">husky &amp; lint-staged</h3>
<p>코드 스타일 컨벤션을 강제하기 위해 ESLint를 사용한다면 Husky와 Lint-staged는 Git을 통한 코드 공유를 할 때 ESLint 규정에 어긋나지 않은 코드만 배포할 수 있도록 강제성을 부여한다. husky와 lint-staged를 이용하여 git commit이 명령어가 실행되기 직전에 Staged된 파일들에 우리가 원하는 검사를 하게된다. 여기서 우리가 원하는 검사란 ESLint 체크다. 만약 ESLint 체크를 했을 때 에러를 띄우면 git commit이 실행되지 않고 멈추게 되고 통과를 해야지 git commit 명령어가 끝까지 완료하게 된다.</p>
<h1 id="폴더-구조">폴더 구조</h1>
<pre><code>.
├── node_modules
├── public
└── src
     ├── __generated__   // apollo codegen을 통해 생성된 graphQL 타입 파일들이 저장된 폴더
     ├── apollo          // apollo 설정 폴더
     ├── components      // 요소들을 정리한 폴더
     ├── constants       // 상수가 저장된 폴더
     ├── hooks           // 커스텀된 hook들이 저장된 폴더
     ├── pages           // page들이 저장된 폴더
     └── styles          // 스타일 관련 코드들이 저장된 폴더</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Enjoy your Futurama 프로젝트 회고]]></title>
            <link>https://velog.io/@do_dadu/Enjoy-your-Futurama-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@do_dadu/Enjoy-your-Futurama-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Thu, 30 Dec 2021 13:56:26 GMT</pubDate>
            <description><![CDATA[<h1 id="lets-use-nextjs">Let&#39;s use Next.js</h1>
<p><a href="https://futurama-fan-site.vercel.app/">프로젝트 사이트 둘러보기</a>
<a href="https://github.com/Jetty2020/futurama-fan.com">깃헙 보러가기</a></p>
<h2 id="다시-한번-next">다시 한번 Next</h2>
<p>이번에 실습 위주의 스터디를 열심히 참여를 해봤다. 주요 기술 스택은 <code>Next.js</code>와 <code>Typescript</code>, <code>useSWR</code>, <code>styled-component</code>였다. 이전에 Next js를 사용해 본적이 있지만 왜 사용해야 하는지를 생각하지 않고 사용을 했고 경험이 많이 부족했었기에 Next의 이점을 제대로 파악하지 못했다. 하지만 이번 기회에 조은님의 기깔나는 설명을 들으면서 Next와 좀 더 친해지는 기회가 됬었다.</p>
<h2 id="futurama-api">Futurama API</h2>
<p>첫 주차에 <a href="https://sampleapis.com/">sample apis 사이트</a>의 wines api를 사용하여 조은님이 강의를 진행하고 비슷한 beers api를 이용하여 복습 및 과제를 진행하였고 첫 주차 마무리 과제로 <a href="https://sampleapis.com/api-list/futurama">Futurama API</a>를 이용하여 개별 사이트를 만들어 보는 과제를 진행했다. 이전의 <a href="https://sampleapis.com/api-list/wines">wines</a>와 <a href="https://sampleapis.com/api-list/beers">beers</a> API와는 다르게 futurama api는 모든 엔드포인트가 다른 구조의 데이터를 뱉어서 페이지를 구성하는 재미가 있었다. 그 중 신경쓴 엔드포인트를 소개해보도록 하겠다.</p>
<h3 id="character">Character</h3>
<p><img src="https://images.velog.io/images/do_dadu/post/194aafe8-0297-488c-8122-1f4abd2314da/image.png" alt="">
character 엔드포인트 같은 경우에는 15가지의 캐릭터들과 해당 캐릭터들의 정보가 담겨 있었다. 문제는 명대사 데이터가 많은 캐릭터들이 있어 한 페이지에 보여주기에는 데이터가 너무 많았다. 다행히도 각 캐릭터들의 id값을 character endpoint 뒤에 넣어서 개별 캐릭터의 데이터만 받아올 수 있어 전체 캐릭터들의 페이지와 동적 라우팅을 이용한 캐릭터 디테일 페이지로 나눠서 구현해볼 수 있었다.</p>
<h3 id="episodes">Episodes</h3>
<p><img src="https://images.velog.io/images/do_dadu/post/50c09d22-1d17-445c-a680-b58e7592cfb6/C0C19A52-65CA-48CE-A9AF-CB27FD841D4F.png" alt="">
퓨처라마의 에피소드가 128개나 되었다. 이 데이터도 너무 많기 때문에 이 데이터를 한페이지에 넣는다면 UX가 너무 좋지 않을 것이라는 것은 당연했다. 하지만 이 데이터는 128개의 에피소드를 원하는 수만큼 가져올 수 없었기 때문에 한번에 받아온 데이터를 페이지네이션을 통해 나눠서 페이지에 보여주는 방법으로 진행하였다. 실제 프로젝트에서는 이런식으로 데이터 처리를 하지 않겠지만 그래도 나름대로 임기응변으로 UX를 끌어올린것 같아 매우 뿌듯했던 페이지였다.</p>
<h3 id="questions">Questions</h3>
<p><img src="https://images.velog.io/images/do_dadu/post/b48bc402-8da1-48f7-8343-f8b489eafc17/image.png" alt="">
이 데이터를 보자마자 <code>실제로 문제를 풀어볼 수 있게 진행하면 너무 재밌겠다!!</code>라는 생각이 들었다. 과제에 재미가 빠지면 섭하지 않나? 그래서 원하는 대로 구현을 했다. 문제 수는 28개의 문제였기에 7문제씩 4페이지로 나눴다. 누군가는 굳이?라고 생각할 수 있지만 최근에 친구의 부탁으로 32문항의 설문을 진행한적이 있는데 몇번을 그만두고 싶었는지 모른다. 문제는 32문항이라는 문항 수보다는 한페이지에 있다보니 그림하나 없는 논문을 읽는 느낌마저 들었다. 그래서 굳이 4페이지로 나눠서 페이지를 구현해보았다. 하지만 개별 문제들과 페이지네이션과 결과 페이지를 따로 컴포넌트로 나눴기때문에 따로 상태관리 라이브러리 없이 구현하는데 너무 힘들었다. 이전에 프로젝트를 할 때 당연하게 상태관리 라이브러리를 사용했었는데 이번 기회를 통해 필요성을 확실히 느끼는 프로젝트가 되었다.</p>
<h2 id="마무리">마무리</h2>
<p>이번 프로젝트를 통해 오랜만에 리액트기반 환경에서 개발을 해보았다. 한동안 바닐라 자바스크립트를 통해서 개발을 진행했는데 비교적으로 코드를 깔끔하게 짜기가 좋았고 아케텍처가 제한적이기 때문에 몇개월이 흘러서 봐도 코드를 이해하기 쉽겠다는 생각이 들었다. 그리고 기본 리액트보다 라우팅을 기본적으로 제공하기 때문에 한번 익숙해지면 새로운 프로젝트를 시작하는 허들이 낮아졌다고 생각한다. 거기다 SSG를 이용한 SEO에 강점도 갖고 있기때문에 현재 프론트엔드 개발을하는데 필수로 알아둬야하는 스택 중에 하나라고 생각된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[flex 컨테이너의 자식요소에 z-index 음수 값 사용 시 주의사항]]></title>
            <link>https://velog.io/@do_dadu/z-index%EC%97%90-%EC%9D%8C%EC%88%98-%EA%B0%92%EC%9D%84-%EC%A1%B0%EC%8B%AC%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@do_dadu/z-index%EC%97%90-%EC%9D%8C%EC%88%98-%EA%B0%92%EC%9D%84-%EC%A1%B0%EC%8B%AC%ED%95%98%EC%9E%90</guid>
            <pubDate>Mon, 06 Dec 2021 08:03:59 GMT</pubDate>
            <description><![CDATA[<h2 id="배경">배경</h2>
<p><a href="https://k-digital.likelion.net/frontend-school">프론트엔드스쿨</a> 동료와 네이버 채용 사이트를 클론 코딩을 하던 도중 처음 보는 상황을 맞닦드렸고 국내에 이에대한 글이 없는 듯하여 정리하고 넘어가려고 한다.
네이버 채용 사이트처럼 상단 내비게이션 바와 슬라이드를 겹치게 놓고싶었다.
<img src="https://images.velog.io/images/do_dadu/post/0487d77f-c2ba-40ba-9351-d841a23984b6/%E1%84%89%E1%85%A1%E1%86%BC%E1%84%83%E1%85%A1%E1%86%AB%E1%84%82%E1%85%A6%E1%84%87%E1%85%B5%E1%84%80%E1%85%A6%E1%84%8B%E1%85%B5%E1%84%89%E1%85%A7%E1%86%AB%20%E1%84%89%E1%85%AE%E1%84%8C%E1%85%A5%E1%86%BC.jpg" alt="네이버 채용사이트"></p>
<p>하지만 슬라이드를 상단 내비게이션 바의 영역을 침범하기위해 position: absolute를 주었더니 위치는 올라갔지만 내비게이션 바를 덮어버리면서 네비게이션바가 나오지 않게 되었다. 그래서 네비게이션 바 밑으로 보내기위해서 슬라이드에 z-index: -1;을 주니까 원하는데로 내비게이션바 아래로 내려갔지만 슬라이드 내부의 a태그가 눌리지 않게 되는 상황을 마주하게되었다. </p>
<p>내비게이션바에도 position을 설정하고 슬라이드와 내비게이션 바에 z-index를 주면서 원하는대로 구현을 했지만 z-index에 음수값을 주었을 때 a태그가 왜 작동을 하지 않는지에 대해서 의문이 생겨 찾아보게 되었다.</p>
<h2 id="추론">추론</h2>
<p><a href="https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context">MDN 문서</a>를 읽으면 flex 혹은 grid 컨테이너 요소의 자식 요소 중에 z-index가 auto가 아닐 경우 쌓임 맥락(stack context)가 생성된다고 한다. 쌓임 맥락 안의 자식 요소는 해당 규칙에 의해서 쌓이게 된다고 정의되어 있다. 하지만 부모요소의 아래로 쌓이게 되는 점에 대해서는 설명이 없다. 그래서 직접 실험해보고       알게된 점을 바탕으로 정리해보겠다.</p>
<h3 id="html-구성">HTML 구성</h3>
<pre><code class="language-html">&lt;!-- html --&gt;
&lt;div class=&quot;outer&quot;&gt;
  &lt;div class=&quot;inner&quot;&gt;
    &lt;a href=&quot;javascript:alert(&#39;동작&#39;);&quot; class=&quot;a1&quot;&gt;a01&lt;/a
    &gt;&lt;a href=&quot;javascript:alert(&#39;동작&#39;);&quot; class=&quot;a2&quot;&gt;a02&lt;/a&gt;
  &lt;/div&gt;
&lt;/div&gt;</code></pre>
<h3 id="상황-1">상황 1</h3>
<pre><code class="language-css">/* css */
.a1 {
  z-index: -1;
}
.a2 {
  z-index: 1;
}</code></pre>
<p>위처럼 .a1에 z-index: -1;만 준다면 .a2와 눈으로 볼 수 있는 차이는 없다
<img src="https://images.velog.io/images/do_dadu/post/2c4a9377-5ee9-4d22-a45a-bc05f0648f35/653EF112-1EA7-4DF5-BEFB-196B27A760F0.png" alt=""></p>
<p>a태그도 잘 클릭이 되며 alert도 잘 작동한다.</p>
<h3 id="상황-2">상황 2</h3>
<pre><code class="language-css">/* css */
.inner {
  display: flex; 
  /* 또는 display: grid; */
}
.a1 {
  z-index: -1;
}
.a2 {
  z-index: 1;
}</code></pre>
<p>하지만 부모 요소에 display: flex; 혹은 grid를 준다면 a태그가 눌리지 않으며 커서가 pointer로 변하지 않는다. 처음 이 상황을 봤을 때는 어리둥절했었다. 시각적으로는 상황1과 다를게 없기 때문이다. 하지만 부모 요소에 배경색을 넣으면 차이가 보인다.
<img src="https://images.velog.io/images/do_dadu/post/d8c36726-f911-4112-b7de-61efcb483d3f/E6EC67E6-A700-480A-AA80-A8F6E2996257.png" alt="">
.a1요소가 부모 밑에 깔려있기 때문에 부모에 가로막혀서 클릭이 되지않았던 것이다.</p>
<h3 id="상황-3">상황 3</h3>
<pre><code class="language-css">/* css */
.outer {
  background-color: aqua;
}
.inner {
  display: flex; 
  /* 또는 display: grid; */
}
.a1 {
  z-index: -1;
}
.a2 {
  z-index: 1;
}</code></pre>
<p>그렇다면 a는 부모요소의 밑으로만 간것인지 아니면 특정 층에 끼어있는 것인지 확인해보기 위해서 .inner의 배경색 pink을 없애고 .outer에 배경색 aqua를 주었다.
<img src="https://images.velog.io/images/do_dadu/post/562b5583-0f00-4afe-b329-25c9c53e5471/F912D0E4-61F6-483E-B3FD-2ED41A88622C.png" alt="">
.outer 요소의 display는 flex가 아닌데도 더 아래에 깔리게 되었다.</p>
<h3 id="상황-4">상황 4</h3>
<p>이번엔 부모 요소가 아닌 조상 요소에 display: flex;가 있을 때도 z-index: -1;인 요소가 아래로 깔리는지 확인해보겠다;</p>
<pre><code class="language-css">/* css */
.outer {
  display: flex; 
  /* 또는 display: grid; */
}
.inner {
  background-color: pink;
}
.a1 {
  z-index: -1;
}
.a2 {
  z-index: 1;
}</code></pre>
<p><img src="https://images.velog.io/images/do_dadu/post/9c25eb34-34ce-4628-bf26-e4fffc536d63/B0055D5E-2DEE-4A0B-B5C2-8EABAF436ED8.png" alt="">
잘 보이며 잘 동작한다. 부모 요소가 아닌 조상 요소의 display에는 영향을 받지않는 것으로 보인다.</p>
<h3 id="상황-5">상황 5</h3>
<p>이번엔 div의 배경색은 없애고 body에 배경색을 bisque로 줘서 어디까지 내려가는지 확인해보겠다.</p>
<pre><code class="language-css">/* css */
body {
  background-color: bisque;
}
.inner {
  display: flex; 
  /* 또는 display: grid; */
}
.a1 {
  z-index: -1;
}
.a2 {
  z-index: 1;
}</code></pre>
<p><img src="https://images.velog.io/images/do_dadu/post/5f1a1771-84c1-4324-8ea8-ada2914bdf0d/264F2904-003A-4737-B0F5-02A2B02E917E.png" alt="">
이미지를 보면 body 위에 있는 것처럼 보인다.</p>
<h3 id="상황-6">상황 6</h3>
<p>더 정확한 확인을 위하여 감싸던 div요소를 없애고 body에 flex를 줘서 확인해보겠다.</p>
<pre><code class="language-html">&lt;!-- html --&gt;
&lt;body&gt;
  &lt;a href=&quot;javascript:alert(&#39;동작&#39;);&quot; class=&quot;a1&quot;&gt;a01&lt;/a
  &gt;&lt;a href=&quot;javascript:alert(&#39;동작&#39;);&quot; class=&quot;a2&quot;&gt;a02&lt;/a&gt;
&lt;/body&gt;</code></pre>
<pre><code class="language-css">/* css */
body {
  display: flex; 
  /* 또는 display: grid; */
  background-color: bisque;
}
.a1 {
  z-index: -1;
}
.a2 {
  z-index: 1;
}</code></pre>
<p><img src="https://images.velog.io/images/do_dadu/post/521aa176-adfc-40f8-8274-ffc2c3c61d16/4091BFB1-FCCC-4225-BAE8-61F36D484CDA.png" alt="">
신기하게도 눈으로 보기엔 body 위에 있다고 보이지만 클릭이 되지않고 cursor가 pointer로 변하지 않는걸로 보아 body 아래에 있는 것처럼 느껴진다.</p>
<h2 id="결론">결론</h2>
<p>display: flex 또는 grid인 컨테이너의 자식 요소에 z-index: -1;를 주면 body 아래까지 내려가지만 시각적으로는 body위에 있는 것처럼 보인다.</p>
<h2 id="마무리">마무리</h2>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://developer.mozilla.org/ko/docs/Web/CSS/CSS_Positioning/Understanding_z_index/The_stacking_context">쌓임 맥락 MDN 문서</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[var의 특징 (왜 var는 따돌림 당하는가...)]]></title>
            <link>https://velog.io/@do_dadu/var%EC%9D%98-%EB%8B%A8%EC%A0%90-%EC%99%9C-var%EB%8A%94-%EB%94%B0%EB%8F%8C%EB%A6%BC-%EB%8B%B9%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@do_dadu/var%EC%9D%98-%EB%8B%A8%EC%A0%90-%EC%99%9C-var%EB%8A%94-%EB%94%B0%EB%8F%8C%EB%A6%BC-%EB%8B%B9%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Tue, 30 Nov 2021 08:05:49 GMT</pubDate>
            <description><![CDATA[<h2 id="변수-선언">변수 선언</h2>
<p>우리는 변수를 선할 때 <code>var</code>, <code>let</code>, <code>const</code> 키워드를 사용한다. ES6에서 let, const가 도입되기 전에는 var가 오랬동안 유일하게 변수 선을 위해 사용되어 왔다. 하지만 ES6이후에 대부분의 코드에서 var를 사용하지 않고 있다. 오늘은 왜 var를 꺼려하는지에 대해서 알아보겠다.</p>
<hr>
<h2 id="var의-특징">var의 특징</h2>
<h3 id="1-호이스팅-후-초기화">1. 호이스팅 후 초기화</h3>
<p>자바스크립트에서 호이스팅은 꽤나 중요한 개념이다. 호이스팅은 단순 번역으로 끌어올린다라는 의미다. 자바스크립트에선 인터프리터가 변수와 함수의 메모리 공간을 선언하기 전에 미리 할당하는 것을 의미한다. 그렇기 때문에 var, let, const 모두 호이스팅이 일어난다. 하지만 <strong>var</strong>의 경우 호이스팅 후 변수를 <strong>undefined</strong>로 값이 <strong>초기화</strong>되기 때문에 선언 전에 참조가 가능해진다.</p>
<pre><code class="language-javascript">console.log(a); // ReferenceError: Cannot access &#39;i&#39; before initialization
let a;</code></pre>
<pre><code class="language-javascript">console.log(b); // undefined
var b;</code></pre>
<p>한마디로 변수의 선언 위치가 자유로워지고 이 변수를 내가 undefined로 할당을 한건지 아니면 원하는 값으로의 할당이 아직 이루어지지 않았는지 확인이 힘들다.</p>
<h3 id="2-중복-선언">2. 중복 선언</h3>
<p>let과 const의 경우 선언한 변수를 다시 선언하는 경우 에러를 발생한다. 하지만 var의 경우  <strong>변수 이름이 중복</strong>되어도 에러가 발생하지 않는다.</p>
<pre><code class="language-javascript">let a;
let a; // SyntaxError</code></pre>
<pre><code class="language-javascript">var b;
var b; // No problem~!!</code></pre>
<p>이 특성 때문에 원치않게 이미 선언한 변수의 값에 영향을 미치게 되어도 실수를 알아차리기 힘들다.</p>
<h3 id="3-스코프">3. 스코프</h3>
<p>let, const의 경우는 <strong>블록 레벨 스코프</strong>를 갖고, var의 경우 <strong>함수 레벨 스코프</strong>를 갖게 된다. 이 부분은 예제를 먼저 보겠다.</p>
<pre><code class="language-javascript">for (let a = 0; a &lt; 3; a++) {
 ...
}
console.log(a); // ReferenceError</code></pre>
<pre><code class="language-javascript">for (var b = 0; a &lt; 3; b++) {
 ...
}
console.log(b); // 3</code></pre>
<p>예제처럼 let의 경우 for문 안의 블록 밖에서는 변수가 참조가 불가능하지만 var의 경우 변수가 함수 레벨 스코프이기에 블록 밖에서도 참조가 가능하다.
위의 예제에서는 동작에 문제가 없지만 setTimeout() 함수와 함께 비동기로 사용한다면 문제가 발생한다.</p>
<pre><code class="language-javascript">for (let a = 1; a &lt; 4; a++) {
  setTimeout(() =&gt; {
    console.log(a + &quot;번째&quot;);
  }, 1000);
} 
// 1 번째
// 2 번째
// 3 번째</code></pre>
<pre><code class="language-javascript">for (var b = 1; b &lt; 4; b++) {
  setTimeout(() =&gt; {
    console.log(b + &quot;번째&quot;);
  }, 1000);
}
// 4 번째
// 4 번째
// 4 번째</code></pre>
<p>for문이 실행되면서 a 또는 b 가 0인 스코프, 1인 스코프, 2인 스코프 ... 들을 생성해줍니다. let을 사용한 경우는 for문의 범위가 적용되기 때문에 이미 for문이 다 실행되었어도 이전의 스코프의 a 값을 참조하지만 var의 경우 함수 레벨 스코프이므로 for문이 실행되면서 생성한 스코프를 빠져나와서 최좀의 b를 참조하게 되어 예상한 결과와 다르게 나온다.</p>
<h2 id="정리">정리</h2>
<p>var의 경우 호이스팅 후 undefined로 초기화가 되어 해당 undefined가 내가 할당했는지 자동으로 초기화된 값인지 확인이 어렵고 var로 동일한 변수 이름을 중복하여 선언이 가능하므로 var로 변수 선언 시 이전의 변수 값에 영향을 미치는 건지 모호해진다. 그리고 블록 레벨 스코프가 아닌 함수 레벨 스코프이기 때문에 필요하지 않은 전역변수가 많아질 수 있고, 비동기처리시 원하지 않게 동작할 수 있다.</p>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="http://www.yes24.com/Product/Goods/92742567">모던 자바스크립트 DEEP DIVE(도서 이웅모 저)</a>
👉<a href="https://developer.mozilla.org/ko/docs/Glossary/Hoisting">https://developer.mozilla.org/ko/docs/Glossary/Hoisting</a>
👉<a href="https://velog.io/@yonyas/Javascript-for%EB%AC%B8%EA%B3%BC-setTimeout-%EB%8F%99%EC%8B%9C%EC%97%90-%EC%93%B8-%EB%95%8C-var%EC%99%80-let%EC%9D%98-%EC%B0%A8%EC%9D%B4">https://velog.io/@yonyas/Javascript-for문과-setTimeout-동시에-쓸-때-var와-let의-차이</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 알고리즘 9095번 1, 2, 3 더하기]]></title>
            <link>https://velog.io/@do_dadu/%EB%B0%B1%EC%A4%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-9095%EB%B2%88-1-2-3-%EB%8D%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@do_dadu/%EB%B0%B1%EC%A4%80-%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-9095%EB%B2%88-1-2-3-%EB%8D%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 28 Nov 2021 10:43:04 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-링크">문제 링크</h2>
<p><a href="https://www.acmicpc.net/problem/9095">https://www.acmicpc.net/problem/9095</a></p>
<hr>
<h2 id="문제-설명">문제 설명</h2>
<h3 id="문제">문제</h3>
<p>정수 4를 1, 2, 3의 합으로 나타내는 방법은 총 7가지가 있다. 합을 나타낼 때는 수를 1개 이상 사용해야 한다.</p>
<ul>
<li>1+1+1+1</li>
<li>1+1+2</li>
<li>1+2+1</li>
<li>2+1+1</li>
<li>2+2</li>
<li>1+3</li>
<li>3+1</li>
</ul>
<p>정수 n이 주어졌을 때, n을 1, 2, 3의 합으로 나타내는 방법의 수를 구하는 프로그램을 작성하시오.</p>
<h3 id="입력">입력</h3>
<p>첫째 줄에 테스트 케이스의 개수 T가 주어진다. 각 테스트 케이스는 한 줄로 이루어져 있고, 정수 n이 주어진다. n은 양수이며 11보다 작다.</p>
<h3 id="출력">출력</h3>
<p>각 테스트 케이스마다, n을 1, 2, 3의 합으로 나타내는 방법의 수를 출력한다.</p>
<hr>
<h2 id="문제-풀이">문제 풀이</h2>
<p>이 문제는 답이 나오는 규칙을 먼저 찾아야한다.
규칙을 보기 위해 1, 2, 3, 4의 답들을 구해 보겠다.</p>
<h3 id="1">1</h3>
<ul>
<li>1</li>
</ul>
<p>따라서 답은 1.</p>
<h3 id="2">2</h3>
<ul>
<li>1 + 1</li>
<li>2</li>
</ul>
<p>답은 2.</p>
<h3 id="3">3</h3>
<ul>
<li>1 + 1 + 1</li>
<li>1 + 2</li>
<li>2 + 1</li>
<li>3</li>
</ul>
<p>답은 4.</p>
<h3 id="4">4</h3>
<ul>
<li>1 + 1 + 1 + 1</li>
<li>1 + 1 + 2</li>
<li>1 + 2 + 1</li>
<li>2 + 1 + 1</li>
<li>1 + 3</li>
<li>3 + 1</li>
<li>2 + 2</li>
</ul>
<p>답은 7이다.</p>
<p>4의 답은 3, 2, 1의 답을 합친 것과 같다. 그 이유는 4의 답의 구성은 3의 답 구성에 1을 더한 것, 2의 답 구성에 2를 더한 것, 1의 답 구성에 3을 더한 것들로 구성되어 있기 때문이다.</p>
<p>다시 한번 4의 답 구성을 보자.</p>
<ul>
<li><span style='color: #f39c12'> 1 + ( 1 + 1 + 1 ) </span></li>
<li><span style='color: #f39c12'>1 + ( 1 + 2 )</span></li>
<li><span style='color: #f39c12'>1 + ( 2 + 1 )</span></li>
<li><span style='color: #f39c12'>1 + ( 3 )</span></li>
<li><span style='color: #27ae60'>2 + ( 1 + 1 )</span></li>
<li><span style='color: #27ae60'>2 + ( 2 )</span></li>
<li><span style='color: #9b59b6'>3 + ( 1 )</span></li>
</ul>
<p>그렇기 때문에 입력값 중 최대값까지의 답을 구하고서 순서에 맞게 출력값을 붙여주면 된다.</p>
<h2 id="코드">코드</h2>
<pre><code class="language-javascript">// 제출용 input
let input = require(&#39;fs&#39;)
  .readFileSync(&#39;/dev/stdin&#39;)
  .toString()
  .trim()
  .split(&#39;\n&#39;)
  .splice(1);

// 실행용
// const input = &#39;3\n4\n7\n10&#39;.split(&#39;\n&#39;).splice(1);
//ansArr[n] : 정수 n을 1, 2, 3의 합으로 나타내는 방법의 수
const ansArr = Array(10);
let result = &#39;&#39;;

//초기값 설정
ansArr[0] = 1;
ansArr[1] = 2;
ansArr[2] = 4;

for (let i = 3; i &lt; 10; i++) {
  //n이 10 이하이므로 10까지 방법의 수 저장
  ansArr[i] = ansArr[i - 1] + ansArr[i - 2] + ansArr[i - 3];
}
//입력에 맞는 방법의 수 메모에서 출력
input.forEach((value) =&gt; {
  result += `${ansArr[value * 1 - 1]}\n`;
});

console.log(result);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 Modal 구현하기(feat. createPortal, 스크롤 막기)]]></title>
            <link>https://velog.io/@do_dadu/React%EC%97%90%EC%84%9C-Modal-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0feat.-createPortal-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%89%EA%B8%B0</link>
            <guid>https://velog.io/@do_dadu/React%EC%97%90%EC%84%9C-Modal-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0feat.-createPortal-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%89%EA%B8%B0</guid>
            <pubDate>Sun, 29 Aug 2021 15:12:56 GMT</pubDate>
            <description><![CDATA[<h2 id="1-modal">1. Modal</h2>
<p>Modal(이하 모달)은 사용자의 이목을 끌기 위해 사용하는 화면전환 기법 중에 하나다. 이번 프로젝트에서는 사용자가 어떤 행동을 하였을 때 이 행동을 정말로 진행할 것인지 확인 하는 용도로 사용을 하기도 하였고, 로그인이 필요한 기능을 수행하려 할 때 로그인 창을 모달로 띄워서 페이지 이동 없이 로그인을 진행하도록 수정하려고한다.
모달은 모달 컴포넌트를 만든 후 필요한 컴포넌트에서 불러오면 된다. 하지만 그냥 불러온다면 모달 컴포넌트가 보이기로는 맨앞에 보이지만 컴포넌트 트리 속 어딘가에 위치하게 되서 직관적이지 않게된다.</p>
<p><img src="https://images.velog.io/images/do_dadu/post/f22d96c5-bb46-494a-a895-926f6480bde6/2F1C6209-B2CD-413F-9EAD-EE50ACD42050.png" alt=""></p>
<p>그래서 <code>react-dom</code>의 <code>createPortal</code>을 사용해서 기본의 컴포넌트 트리에서 벗어나게 직관적으로 모달을 구현해보려고 한다.</p>
<hr>
<h2 id="2-구현-코드">2. 구현 코드</h2>
<pre><code class="language-html">&lt;!--public/index.html--&gt;
&lt;body&gt;
    &lt;noscript&gt;You need to enable JavaScript to run this app.&lt;/noscript&gt;
    &lt;div id=&quot;root&quot;&gt;&lt;/div&gt;
    &lt;div id=&quot;modal&quot;&gt;&lt;/div&gt; // &lt;- 코드 추가
  &lt;/body&gt;</code></pre>
<p>우선 리액트 프로젝트 파일 내부의 <code>public/index.html</code>파일에 root 밑에 <code>&lt;div id=&quot;modal&quot;&gt;&lt;/div&gt;</code> 컨테이너를 추가한다.</p>
<pre><code class="language-javascript">//Modal.jsx
import React from &#39;react&#39;;
import { createPortal } from &#39;react-dom&#39;;
import styled from &#39;styled-components&#39;;

const ModalBg = styled.div`
  display: flex;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  align-items: center;
  justify-content: center;
  width: 100vw;
  height: 100vh;
  background-color: #ffffffe2;
`;

const ModalBox = styled.div`
  width: 25rem;
  background-color: white;
`;

const Modal = ({ setModalShow }) =&gt; {
  const handleOk = () =&gt; {
    console.log(&#39;댓글 삭제&#39;);
  };
  return createPortal(
    &lt;ModalBg&gt;
      &lt;ModalBox&gt;
        &lt;ModalTitle&gt;댓글 삭제&lt;/ModalTitle&gt;
        &lt;ModalContent&gt;댓글을 정말로 삭제하시겠습니까?&lt;/ModalContent&gt;
        &lt;ModalBtnBox&gt;
          &lt;ModalBtn cancel onClick={() =&gt; setModalShow(false)}&gt;
            취소
          &lt;/ModalBtn&gt;
          &lt;ModalBtn onClick={() =&gt; handleOk()}&gt;확인&lt;/ModalBtn&gt;
        &lt;/ModalBtnBox&gt;
      &lt;/ModalBox&gt;
    &lt;/ModalBg&gt;,
    document.getElementById(&#39;modal&#39;)
  );
};

export default Modal;
</code></pre>
<p>그리고 Modal 컴포넌트의 반환 값을<code>createPortal</code>로 감싸주고 원하는 element를 지정해주면 된다. 
(위 코드에서 모달생성에 필수적이지 않다고 생각한 css 코드는 삭제했습니다.)</p>
<p><img src="https://images.velog.io/images/do_dadu/post/4e2b7583-5cf9-4c9e-9e4d-a59d42e9e0f3/42957715-28B5-4C43-8AAB-540A91F0A6BD.png" alt=""></p>
<p>실행해보면 시각적으로는 똑같지만 html element를 보면 모달이 기존의 컴포넌트 트리에서 빠져나온 것을 확인할 수 있다.</p>
<hr>
<h2 id="3-modal-배경-스크롤-막기">3. Modal 배경 스크롤 막기</h2>
<p>  모달도 잘 만들었는데 뒤에 배경이 스크롤이 된다면 완성도가 많이 떨어져 보일 것이다. 그래서 모달의 글의 마지막은 스크롤을 막는 방법이다.</p>
<h3 id="3-1-구현-방법">3-1. 구현 방법</h3>
<p>  body 태그의 css를 <code>position</code>을 fixed로 변경하고, <code>top</code>을 현재 스크롤 위치로 하고 <code>overflow-y: scroll;</code> <code>width: 100%;</code>을 추가하면 스크롤 방지를 할 수 있다.</p>
<p>해당 css를 변경할 때는 useEffect를 사용할 것이다. 모달이 사라질 때에는 <code>useEffect의 return</code>을 사용해 <code>body의 cssText를 리셋</code>시킨 다음 <code>window.scrollTo</code>를 이용해 현재 스크롤 위치로 이동시키면 된다.</p>
<h3 id="3-2-구현-코드">3-2. 구현 코드</h3>
<pre><code class="language-javascript">  useEffect(() =&gt; {
    document.body.style.cssText = `
      position: fixed; 
      top: -${window.scrollY}px;
      overflow-y: scroll;
      width: 100%;`;
    return () =&gt; {
      const scrollY = document.body.style.top;
      document.body.style.cssText = &#39;&#39;;
      window.scrollTo(0, parseInt(scrollY || &#39;0&#39;, 10) * -1);
    };
  }, []);</code></pre>
<hr>
<h2 id="4-마무리">4. 마무리</h2>
<p>  프로그래밍을 잘 모르는 사람이 볼 때는 모달을 컴포넌트 트리에서 빼든 안 빼든 css적으로 동일하다면 상관없을 것이다. 하지만 이런 사소한 부분 하나 하나 모여 코드의 완성도와 깔끔함이 결정된다고 믿기에 이 방법이 좋은 프로그래밍이라고 생각한다.</p>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://codingbroker.tistory.com/126">https://codingbroker.tistory.com/126</a>
👉<a href="http://yoonbumtae.com/?p=3776">http://yoonbumtae.com/?p=3776</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[You don't know JS - 타입과 문법, 스코프와 클로저]]></title>
            <link>https://velog.io/@do_dadu/You-dont-know-JS-%ED%83%80%EC%9E%85%EA%B3%BC-%EB%AC%B8%EB%B2%95-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80-1</link>
            <guid>https://velog.io/@do_dadu/You-dont-know-JS-%ED%83%80%EC%9E%85%EA%B3%BC-%EB%AC%B8%EB%B2%95-%EC%8A%A4%EC%BD%94%ED%94%84%EC%99%80-%ED%81%B4%EB%A1%9C%EC%A0%80-1</guid>
            <pubDate>Sat, 14 Aug 2021 14:52:24 GMT</pubDate>
            <description><![CDATA[<h1 id="1-갑자기-책을-읽는-이유🧐">1. 갑자기 책을 읽는 이유🧐</h1>
<p>난 책을 좋아하지 않는다. 소설보다는 영화를, 설명서보다는 언박싱 브이로그를 보는 걸 더 좋아한다. 그래서 프로그래밍 공부를 할 때도 책보다는 인터넷 강의를 보면서 공부했다. 하지만 프로젝트들을 하면서 새로운 것을 익히고 써먹을 때마다 혹은 알고리즘 공부를 할 때마다 자바스립트에 대한 공부가 부족하다는 것이 느껴졌다. 그래서 이번에 자바스크립트 기초 공부를 하기 시작했다. 그래서 여러가지 자바스크립트 서적과 강의를 추천받아서 알아보다가 You don&#39;t know JS를 알게되었다. 지금 읽고 있는데 생각보다 재밌고 생각보다 유익해서 실제로 프로젝트에 바로 적용할 수 있을 법한 팁들도 많이 존재해서 단순히 읽고 끝내는 것이 아니라 정리가 필요하다가 느꼈다. 바로 시작한다.</p>
<h1 id="2-1장-타입과-문법📕">2. 1장 타입과 문법📕</h1>
<h2 id="1-1절-타입">1. 1절 타입</h2>
<h3 id="내장-타입">내장 타입</h3>
<p>자바스크립트에는 7가지 내장 타입이 있다.</p>
<ul>
<li>null</li>
<li>undefined</li>
<li>boolean</li>
<li>number</li>
<li>string</li>
<li>object</li>
<li>symbol(ES6부터 추가)</li>
</ul>
<p>값 타입은 &#39;typeof&#39; 연산자로 알 수 있다. 하지만 <code>null</code>의 경우 <code>object</code>로 반환된다.</p>
<pre><code class="language-javascript">typeof null === object // true</code></pre>
<h3 id="값이-없는-vs-선언되지-않은">값이 없는 vs 선언되지 않은</h3>
<pre><code class="language-javascript">var a;

a; // undefined
b; // ReferenceError

typeof a; // undefined
typeof b; // undefined</code></pre>
<p>위의 예제에서 보면 <code>b;</code>는 선언되지 않았기 때문에 에러를 던진다. 하지만 <code>typeof b;</code>의 경우에는 에러를 던지지 않고 <code>undefined</code>를 반환한다. 이를 이용하면 유용한 safe guard를 만들 수 있다.</p>
<pre><code class="language-javascript">// DEBUG를 선언하지 않은 경우
// 1
if(DEBUG) {
  console.log(&quot;디버깅을 시작합니다.&quot;);
}
// 2
if(typeof DEBUG !== undefined) {
  console.log(&quot;디버깅을 시작합니다.&quot;);
}</code></pre>
<p>1번의 경우 에러를 던지지만 2번의 경우 단순히 if문을 넘어서 프로그램이 진행된다.</p>
<hr>
<h2 id="2-2절-값✍️">2. 2절 값✍️</h2>
<h3 id="특수-값">특수 값</h3>
<p>null 타입의 값은 null 뿐이다. 마찬가지로 undefined 타입의 값은 undefined뿐이다.
수학 연산시 두 피연산자가 숫자가 아닐 경우 유효한 숫자가 나올 수 없으므로 그 결과는 NaN이다. 하지만 NaN은 그자 대로 <code>글자아님(Not a Number)</code>보다는 <code>유효하지 않은 숫자</code>라는 명칭이 더 올바르다.
왜냐하면 typeof NaN === number이기 때문이다. 또한 NaN의 특이한 점은 이뿐만이 아니라 null과 undefined와는 다르게 <code>NaN !== NaN</code>이다. 
자바스크립트에는 두가지의 0(영)이 존재한다. 바로 +0과 -0이다. </p>
<h3 id="값-vs-레퍼런스">값 vs 레퍼런스</h3>
<p>자바스크립트는 포인터라는 개념이 없고 참조하는 방법도 다른 언어들과 다르다. 우선 어떤 변수가 다른 변수를 참조하는 것은 불가능하다.
자바스크립트는 값 또는 레퍼런스의 할당 맟 전달을 제어하는 구문 암시가 전혀 없다. 대신, 값의 타입만으로 값 복사인지 레퍼런스 복사인지 결정된다.
null, undefined, string, number, boolean과 symbol같은 단순 값은 언제나 값 복사 방식으로 할당/전달된다.
개체나 함수 등 합성 값은 레퍼런스 복사로 할당/전달된다.</p>
<pre><code class="language-javascript">var a = 2;
var b = a;
b++;
a; // 2
b; // 3

var c = [1, 2, 3];
var d = c;
d.push(4);
c; // [1, 2, 3, 4];
d; // [1, 2, 3, 4];</code></pre>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://www.coupang.com/vp/products/31997012?itemId=120611115&amp;vendorItemId=3243914671&amp;q=you+don%E2%80%99t+know+js&amp;itemsCount=17&amp;searchId=c23db7c8ba574f9ca9138d6ad35e09a1&amp;rank=2&amp;isAddedCart=">YOU DON&#39;T KNOW JS(카일 심슨 지음)</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React 프로젝트에 Typescript 환경 설정하기(❌CRA)]]></title>
            <link>https://velog.io/@do_dadu/Webpack%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A0-React-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-Typescript-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0CRA</link>
            <guid>https://velog.io/@do_dadu/Webpack%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A0-React-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-Typescript-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0CRA</guid>
            <pubDate>Sat, 19 Jun 2021 17:05:44 GMT</pubDate>
            <description><![CDATA[<h2 id="1-이전-줄거리">1. 이전 줄거리</h2>
<p>&amp;nbsp이전에 우리는 <code>Webpack</code>을 사용해서 <code>CRA</code>를 사용하지 않고 리액트 프로젝트를 만들었다.
&amp;nbsp이번에는 해당 프로젝트에 <code>Typescript</code>를 사용할 수 있게 설정을 진행할 것이다.
&amp;nbsp이번에도 마찬가지로 원본 블로그의 흐름을 그대로 따라간다. 하지만 나는 <code>tslint</code>가 아닌 <code>eslint</code>를 사용할 것이여서 약간의 차이가 존재한다. 출처를 보시려면 <a href="https://dev-yakuza.posstree.com/ko/react/typescript/">여기</a>를 클릭해주세요.</p>
<p>이전 글을 아직 보지 않았다면 <a href="https://velog.io/@do_dadu/React%EB%A5%BC-Webpack%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0">Webpack으로 React 프로젝트 만들어보기(❌CRA)</a>를 먼저 보고와주세요!</p>
<hr>
<h2 id="2-타입스크립트-적용하기">2. 타입스크립트 적용하기</h2>
<h3 id="2-1-패키지-설치">2-1. 패키지 설치</h3>
<blockquote>
<p>npm i -D typescript @babel/preset-typescript ts-loader fork-ts-checker-webpack-plugin</p>
</blockquote>
<ul>
<li><code>typescript</code>: Typescript(타입스크립트) 필수 라이브러리</li>
<li><code>@babel/preset-typescript</code>: babel(바벨)에서 Typescript(타입스크립트)를 빌드하기 위해 필료한 라이브러리</li>
<li><code>ts-loader</code>: Webpack(웹팩)에서 Typescript(타입스크립트)를 컴파일 하기 위해 필요한 라이브러리</li>
<li><code>fork-ts-checker-webpack-plugin</code>: ts-loader의 성능을 향상시키기 위한 라이브러리</li>
</ul>
<hr>
<h3 id="2-2-webpack-설정하기">2-2. Webpack 설정하기</h3>
<p><code>webpack.config.js</code>를 열고 아래와 같이 설정합니다.</p>
<pre><code class="language-javascript">const path = require(&#39;path&#39;);

const HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;);

// Typescript(타입스크립트)를 빌드할 때 성능을 향상시키기 위한 플러그인를 불러오기
const ForkTsCheckerWebpackPlugin = require(&#39;fork-ts-checker-webpack-plugin&#39;); 

module.exports = {
  entry: {
    // 번들 파일(bundle)의 시작 파일(Entry)을 jsx에서 tsx로 변경
    &#39;js/app&#39;: [&#39;./src/App.tsx&#39;],
  },
  output: {
    path: path.resolve(__dirname, &#39;dist/&#39;),
    publicPath: &#39;/&#39;,
  },
  module: {
    rules: [
      // Webpack(웹팩)에서 Typescript(타입스크립트)를 사용하기 위해 js|jsx를 ts|tsx로 수정 후 ts-loader를 추가
      // ts-loader의 옵션은 성능 향상을 위해서
      {
        test: /\.(ts|tsx)$/,
        use: [
          &#39;babel-loader&#39;,
          {
            loader: &#39;ts-loader&#39;,
            options: {
              transpileOnly: true,
            },
          },
        ],
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: &#39;./src/index.html&#39;,
      filename: &#39;index.html&#39;,
    }),
    // Typescript(타입스크립트)의 컴파일 속도 향상을 위한 플러그인을 설정
    new ForkTsCheckerWebpackPlugin(),
  ],
};</code></pre>
<hr>
<h3 id="2-3-tsconfigjson-파일-설정하기">2-3. tsconfig.json 파일 설정하기</h3>
<p> &amp;nbsp<code>루트 디렉토리</code>에<code>tsconfig.json</code>을 만들고 아래와 같이 수정합니다.</p>
<pre><code class="language-javascript">{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es6&quot;,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;noResolve&quot;: false,
    &quot;noImplicitAny&quot;: false,
    &quot;removeComments&quot;: false,
    &quot;sourceMap&quot;: true,
    &quot;allowJs&quot;: true,
    &quot;jsx&quot;: &quot;react&quot;,
    &quot;allowSyntheticDefaultImports&quot;: true,
    &quot;keyofStringsOnly&quot;: true
  },
  &quot;typeRoots&quot;: [&quot;node_modules/@types&quot;, &quot;src/@type&quot;],
  &quot;exclude&quot;: [
    &quot;node_modules&quot;,
    &quot;build&quot;,
    &quot;scripts&quot;,
    &quot;acceptance-tests&quot;,
    &quot;webpack&quot;,
    &quot;jest&quot;,
    &quot;src/setupTests.ts&quot;,
    &quot;./node_modules/**/*&quot;
  ],
  &quot;include&quot;: [&quot;./src/**/*&quot;, &quot;@type&quot;]
}</code></pre>
<hr>
<h3 id="2-4-babel-설정하기">2-4. babel 설정하기</h3>
<p>&amp;nbsp <code>루트 디렉토리</code>의 <code>.babelrc</code>를 아래와 같이 수정합니다.</p>
<pre><code class="language-javascript">{
  &quot;presets&quot;: [
    [
      &quot;@babel/preset-env&quot;,
      { &quot;targets&quot;: { &quot;browsers&quot;: [&quot;last 2 versions&quot;, &quot;&gt;= 5% in KR&quot;] } }
    ],
    &quot;@babel/react&quot;,
    &quot;@babel/typescript&quot; // Typescript(타입스크립트)가 컴파일 가능하도록 추가
  ]
}</code></pre>
<hr>
<h3 id="2-5-typescript-스타일-코딩">2-5. Typescript 스타일 코딩</h3>
<p>&amp;nbsp<code>React</code>에서 <code>Typescript</code>를 사용하기 위해 <code>./src/App.jsx</code>를 <code>./src/App.tsx</code>로 이름을 변경하고 아래와 같이 수정합니다.</p>
<pre><code class="language-typescript">import * as React from &#39;react&#39;;
import * as ReactDOM from &#39;react-dom&#39;;

interface Props {}

const App = ({  }: Props) =&gt; {
  return &lt;h1&gt;Hello World!&lt;/h1&gt;;
};

ReactDOM.render(&lt;App /&gt;, document.getElementById(&#39;app&#39;));</code></pre>
<hr>
<h3 id="2-6-실행해보기">2-6. 실행해보기</h3>
<blockquote>
<p>npm start</p>
</blockquote>
<p>&amp;nbsp위의 명령어를 실행하고 인터넷 브라우저에서 <code>http://localhost:8080/</code>로 들어가면 <strong>Hello World!</strong>가 보이면 정상적으로 잘 작동하는 것이다.</p>
<blockquote>
<p>npm run build</p>
</blockquote>
<p>&amp;nbsp그리고 빌드를 했을 때 <code>dist</code> 폴더가 생성되는 지 확인해주면 됩니다.</p>
<hr>
<h2 id="3-마치며">3. 마치며</h2>
<p>&amp;nbsp이전 프로젝트에 바로 진행을 해서 비교적 간단하게 적용을 할 수 있었다.
하지만 찾아보면 더 많은 설정 옵션들이 있으니 다른 옵션들도 적용해봐야할 것 같다.
굿바이~~</p>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://dev-yakuza.posstree.com/ko/react/typescript/">https://dev-yakuza.posstree.com/ko/react/typescript/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Webpack으로 React 프로젝트  만들어보기(❌CRA)]]></title>
            <link>https://velog.io/@do_dadu/React%EB%A5%BC-Webpack%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@do_dadu/React%EB%A5%BC-Webpack%EC%9C%BC%EB%A1%9C-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 19 Jun 2021 13:14:59 GMT</pubDate>
            <description><![CDATA[<h2 id="1-react-cra로-만들면-되잖아🤔">1. React? CRA로 만들면 되잖아?🤔</h2>
<p>&amp;nbsp물론 CRA를 이용하면 간편하게 리액트 프로젝트를 만들 수 있다. 프로젝트 만드는 시간도 절약되고 다른 거를 찾아 볼 필요도 없다. 
&amp;nbsp하지만 CRA는 뭐랄까 종합선물세트같은 느낌이다. 너가 뭘 필요할지 몰라서 기본적인 거 다 준비해봤어. 같은 느낌이랄까? 그래서 나는 내가 필요한 기능만 적절하게 첨가해서 프로젝트를 생성하기 위해 Webpack을 배우기로 했다!🕺</p>
<hr>
<h2 id="2-webpack으로-프로젝트-생성하기💻">2. Webpack으로 프로젝트 생성하기💻</h2>
<p>&amp;nbsp우선 한가지 밝히자면 이번 글은 한 곳에서 너무나도 잘 설명이 되어 있기 때문에 많은 부분이 비슷하다. 하지만 내가 다시 한번 정리하는 차원 + 따라하다 나온 이슈 하나 해결 이 두가지 이유로 글을 적고있다. 원본은 이후 출처에 게시하겠지만 지금 바로 원본이 보고 싶으시다면 <a href="https://dev-yakuza.posstree.com/ko/react/start/">여기</a>로 가시면 됩니다.</p>
<h3 id="2-1-프로젝트-폴더-생성🗂">2-1. 프로젝트 폴더 생성🗂</h3>
<blockquote>
</blockquote>
<p>mkdir react_project
cd react_project
npm init -y</p>
<hr>
<h3 id="2-2-필요한-패키지-설치📚">2-2. 필요한 패키지 설치📚</h3>
<blockquote>
<p>npm i react react-dom
npm i -D webpack webpack-cli html-webpack-plugin webpack-dev-server babel-loader @babel/core @babel/preset-env @babel/preset-react rimraf</p>
</blockquote>
<p><strong>패키지 설명</strong></p>
<ul>
<li><code>react</code>, <code>react-dom</code>: React 개발에 필요한 필수 패키지</li>
<li><code>webpack</code>: Webpack(웹팩) 라이브러리</li>
<li><code>webpack-cli</code>: Webpack(웹팩)을 명령어로 조작하기 위한 라이브러리</li>
<li><code>html-webpack-plugin</code>: Webpack(웹팩)에서 HTML을 다루기 위한 플러그인</li>
<li><code>webpack-dev-server</code>: Webpack(웹팩)으로 로컬에서 개발하기 위한 테스트 서버</li>
<li><code>babel-loader</code>: Webpack(웹팩)에서 babel(바벨)을 다루기 위한 라이브러리</li>
<li><code>@babel/core</code>: babel(바벨)로 컴파일하기 위한 라이브러리</li>
<li><code>@babel/preset-env</code>: babel(바벨)로 컴파일시 어떤 타겟으로 지정할지 설정하는 라이브러리</li>
<li><code>@babel/preset-react</code>: React(리액트)를 babel(바벨)로 컴파일하기 위한 라이브러리</li>
<li><code>rimraf</code>: Mac과 윈도우즈에서 동일한 명령어로 폴더를 삭제하기 위한 라이브러리</li>
</ul>
<hr>
<h3 id="2-3-packagejson-설정📏">2-3. package.json 설정📏</h3>
<p>&amp;nbsppackage.json 파일에 Webpack을 동작시키기 위한 script를 추가합니다.</p>
<pre><code class="language-javascript">{
  &quot;name&quot;: &quot;react_project&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;&quot;,
  &quot;main&quot;: &quot;index.js&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;webpack serve --mode development&quot;,
    &quot;prebuild&quot;: &quot;rimraf dist&quot;,
    &quot;build&quot;: &quot;webpack --progress --mode production&quot;
  },
  &quot;keywords&quot;: [],
  &quot;author&quot;: &quot;&quot;,
  &quot;license&quot;: &quot;ISC&quot;,
  &quot;dependencies&quot;: {
    &quot;react&quot;: &quot;^17.0.2&quot;,
    &quot;react-dom&quot;: &quot;^17.0.2&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;@babel/core&quot;: &quot;^7.14.6&quot;,
    &quot;@babel/preset-env&quot;: &quot;^7.14.5&quot;,
    &quot;@babel/preset-react&quot;: &quot;^7.14.5&quot;,
    &quot;babel-loader&quot;: &quot;^8.2.2&quot;,
    &quot;html-webpack-plugin&quot;: &quot;^5.3.1&quot;,
    &quot;rimraf&quot;: &quot;^3.0.2&quot;,
    &quot;webpack&quot;: &quot;^5.39.1&quot;,
    &quot;webpack-cli&quot;: &quot;^4.7.2&quot;,
    &quot;webpack-dev-server&quot;: &quot;^3.11.2&quot;
  }
}</code></pre>
<p><strong>script 명령어 설명</strong></p>
<ul>
<li><code>&quot;start&quot;: &quot;webpack server --mode development&quot;</code>: npm start 또는 npm run start로 실행되는 스크립트입니다. Webpack(웹팩)의 개발 서버를 development 모드로 실행시킵니다. Webpack(웹팩)을 실행시킬 때는 항상 모드(<code>development</code> 또는 <code>production</code>)를 설정해야합니다.</li>
<li><code>&quot;prebuild&quot;: &quot;rimraf dist&quot;</code>: npm run build로 build 스크립트를 실행하면 build 스크립트전에 이 스크립트가 실행됩니다. pre와 post로 스크립트 실행전, 실행후 실행시키고 싶은 스크립트를 실행시킬 수 있습니다. 저는 빌드후 생성되는 폴더를 지우고 다시 만들기 위해 사용하고 있습니다.</li>
<li><code>&quot;build&quot;: &quot;webpack --progress --mode production&quot;</code>: npm run build로 실행되는 스크립트입니다. Webpack(웹팩)을 production 모드로 실행시켜 번들링(bundling)합니다. –progress은 빌드 진행 과정을 모니터링하기 위한 옵션입니다.</li>
</ul>
<hr>
<h3 id="2-4-webpack-설정하기">2-4. Webpack 설정하기</h3>
<p>&amp;nbsp루트 폴더에 <code>webpack.config.js</code> 파일을 생성하고 다음과 같이 설정합니다.</p>
<pre><code class="language-javascript">const path = require(&#39;path&#39;); //절대경로를 참조하기 위해 path를 불러오기

const HtmlWebpackPlugin = require(&#39;html-webpack-plugin&#39;); //웹팩에서 HTML을 다루기위한 플로그인을 불러오기

module.exports = {
  // 번들 파일로 만들기 위한 시작 파일(entry)을 설정
  //생성될 번들 파일은 js 폴더 하위에 app.js라는 이름으로 생성
  //이 파일은 ./src/App.jsx를 시작으로 번들링(하나로 합치기)합니다.
  entry: {
    &#39;js/app&#39;: [&#39;./src/App.jsx&#39;],
  },

  //생성된 번들 파일(bundle)은 ./dist/ 폴더에 생성
  //publicPath를 지정함으로써 HTML등 다른 파일에서 생성된 번들을 참조할 때, /을 기준으로 참조
  output: {
    path: path.resolve(__dirname, &#39;dist/&#39;),
    publicPath: &#39;/&#39;,
  },

  //React(리액트) 파일인 jsx와 js는 babel(바벨)을 이용하여 빌드
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: [&#39;babel-loader&#39;],
        exclude: /node_modules/,
      },
    ],
  },

  //./src/index.html 파일을 dist 경로에 index.html로 파일을 생성
  //파일을 생성할 때, Webpack(웹팩)이 만든 번들 파일(/js/app.js)를 HTML에 추가하여 생성
  plugins: [
    new HtmlWebpackPlugin({
      template: &#39;./src/index.html&#39;,
      filename: &#39;index.html&#39;,
    }),
  ],
};</code></pre>
<hr>
<h3 id="2-5-babel-설정하기⚙️">2-5. babel 설정하기⚙️</h3>
<p>&amp;nbsp루트 디렉토리에 <code>.babelrc</code>를 생성하고 아래와 같이 설정합니다.</p>
<pre><code class="language-javascript">{
  &quot;presets&quot;: [
    [
      &quot;@babel/preset-env&quot;,
      { &quot;targets&quot;: { &quot;browsers&quot;: [&quot;last 2 versions&quot;, &quot;&gt;= 5% in KR&quot;] } }
    ],
    &quot;@babel/react&quot;
  ]
}</code></pre>
<p>&amp;nbspbabel(바벨)로 컴파일 할 때, targets을 지정해줬어요. 브라우저의 상위 버전 두개(예: IE 11, 10)와 한국(KR)에서 5% 이상의 점유율을 가지고 있는 브라우저에 대응하여 컴파일되도록 설정했어요. 그리고 &quot;@babel/react&quot;을 통해 React(리액트)도 컴파일될 수 있도록 설정했어요.</p>
<hr>
<h3 id="2-6-html-만들기">2-6. HTML 만들기</h3>
<p>React(리액트)를 사용할 HTML 파일을 <code>./src/index.html</code>에 생성하고 다음처럼 수정합니다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot; /&gt;
    &lt;title&gt;Hello World&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;&lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<hr>
<h3 id="2-7react-파일-만들기📒">2-7.React 파일 만들기📒</h3>
<p>&amp;nbsp <code>./src/App.jsx</code>를 생성하고 아래와 같이 수정합니다.</p>
<pre><code class="language-javascript">import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;

const App = () =&gt; {
  return &lt;h1&gt;Hello World!&lt;/h1&gt;;
};
ReactDOM.render(&lt;App /&gt;, document.getElementById(&#39;app&#39;));</code></pre>
<hr>
<h3 id="2-8-실행해보기🖥">2-8. 실행해보기🖥</h3>
<blockquote>
<p>npm start</p>
</blockquote>
<p>&amp;nbsp터미널에서 위의 명령어를 실행하면 터미널에서 다음과 같은 화면이 보일 것이다.
<img src="https://images.velog.io/images/do_dadu/post/fe60aa87-68ce-4875-b392-664100390bd3/A96826AD-1A7C-4779-9774-9F41CD0C03BD.png" alt=""></p>
<p>그리고 인터넷 브라우저에서 <a href="http://localhost:8080/%EB%A1%9C">http://localhost:8080/로</a> 들어가면 Hello World!가 보이면 정상적으로 프로젝트를 시작한 것이다.
<img src="https://images.velog.io/images/do_dadu/post/b9d22987-eeeb-4307-a954-523534a9caf4/B0611401-716E-4D00-AFC4-062C63AD3F94.png" alt=""></p>
<p>&amp;nbsp<code>npm start</code>가 잘 작동한다면 <code>build</code>도 해보자.</p>
<blockquote>
<p>npm run build</p>
</blockquote>
<p>&amp;nbsp실행되면 ./dist/ 폴더와 하위에 <code>index.html</code>과 <code>/js/app.js</code>가 생성된 것을 확인할 수 있다. 또한 <code>index.html</code>을 열어보면 우리가 만든 <code>index.html</code>과는 다르게 <code>&lt;script type=&quot;text/javascript&quot; src=&quot;/js/app.js&quot;&gt;&lt;/script&gt;</code>이 추가된 것을 확인할 수 있다.</p>
<hr>
<h2 id="3-마치며🥳">3. 마치며🥳</h2>
<p>&amp;nbsp이제 웹팩으로 리액트 프로젝트를 만들 수 있다 정도인 것 같다. 웹팩을 더 공부해서 앞으로는 내가 원하는 프로젝트를 가볍게 시작할 수 있다면 좋을 것 같다.👍</p>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://dev-yakuza.posstree.com/ko/react/start/">https://dev-yakuza.posstree.com/ko/react/start/</a>
👉<a href="https://stackoverflow.com/questions/59611597/error-cannot-find-module-webpack-cli-bin-config-yargs">https://stackoverflow.com/questions/59611597/error-cannot-find-module-webpack-cli-bin-config-yargs</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[husky, lint-staged를 사용하자( sub : ESLint 자동화하기 )]]></title>
            <link>https://velog.io/@do_dadu/husky-lint-staged%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-sub-ESLint-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@do_dadu/husky-lint-staged%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90-sub-ESLint-%EC%9E%90%EB%8F%99%ED%99%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 15 Jun 2021 15:20:05 GMT</pubDate>
            <description><![CDATA[<h2 id="1-husky-lint-stage">1. husky? lint-stage?</h2>
<p>&amp;nbsp우리는 <strong>ESLint</strong>를 프로젝트에 적용시킬 때는 협업하는 모든 사람들이 같은 규칙 내에서 코딩을 하는 것을 예상한다. 하지만 가끔은 규칙을 지키지 않고 깃헙에 코드를 푸시할 때가 생긴다. 내 경우에도 가끔 오랜 코딩에 지쳐서 깜빡하고 ESLint 확인을 안하고 푸시할 때가 있었다. 뿐만 아니라 내가 직접 본 적은 없지만 어떤 팀에서는 전혀 규칙을 지키지 않고 코딩을 하는 사람도 있다고 한다.(그럼 왜 굳이 ESLint를 적용시켰을까...🥲)</p>
<p>&amp;nbsp그래서 우리는 git commit 또는 git push와 같은 <strong>git 이벤트가 일어나기 전</strong>에 우리가 원하는 <strong>스크립트를 실행</strong>하기 위해서 <strong>git 이벤트 사이에 갈고리(hook)를 걸어주는 것</strong>이다. 이것을 git hook 제어라고 한다. 우리는 이런 <code>git hook</code> 제어를 위해서 <code>husky</code> 라이브러리를 사용할 것이다. </p>
<p>&amp;nbsp그러면 <code>lint-staged</code>는 뭐냐? 우선 stage 상태를 이해해야한다. 파일들이 git add로 커밋 대상이 된 상태를 stage 상태라고 한다. stage 상태의 git 파일에 대해 lint와 우리가 설정해둔 명령어를 실행해주는 라이브러리다. </p>
<hr>
<h2 id="2-husky--lint-staged-사용하기">2. husky &amp; lint-staged 사용하기</h2>
<h3 id="2-1husky--lint-staged-설치하기💻">2-1.husky &amp; lint-staged 설치하기💻</h3>
<p>&amp;nbsp이제 실질적인 코드들을 살펴보자. 우선 <code>husky &amp; lint-staged</code>를 설치하자.</p>
<blockquote>
<p>npx mrm lint-staged</p>
</blockquote>
<p>&amp;nbsp여기서 <a href="https://mrm.js.org">mrm</a>이란 오픈소스 프로젝트의 환경 설정을 동기화 하기 위한 도구이다. <code>mrm</code>을 이용하면 lint-staged와 husky를 간편하게 설정해줄 수 있다.👍</p>
<p>&amp;nbsp위 명령어를 실행하면 <code>.husky</code>폴더가 생기고 <code>package.json</code> 파일에 다음과 같은 코드가 추가로 생길 것이다.</p>
<pre><code class="language-javascript">{
  &quot;scripts&quot;: {
    &quot;prepare&quot;: &quot;husky install&quot;
  },
  &quot;devDependencies&quot;: {
    &quot;husky&quot;: &quot;^6.0.0&quot;,
    &quot;lint-staged&quot;: &quot;^11.0.0&quot;,
  },
  &quot;lint-staged&quot;: {
    &quot;*.js&quot;: &quot;eslint --cache --fix&quot;
  }
}</code></pre>
<hr>
<h3 id="2-2-husky-설정하기">2-2. husky 설정하기</h3>
<p>&amp;nbsphusky는 <code>.husky</code>폴더에서 설정을 해주면 된다.
하지만 우리는 <code>mrm</code>이 기본 설정을 해준 덕에 따로 설정을 해줄 것이 없다ㅎㅎ
<code>.husky</code> 폴더를 보면 <strong>pre-commit</strong> 파일에 다음과 같은 명령어가 있을 것이다.</p>
<pre><code class="language-javascript">#!/bin/sh
. &quot;$(dirname &quot;$0&quot;)/_/husky.sh&quot;

npx lint-staged</code></pre>
<p>위의 설정대로 <code>git commit</code>을 하기전에 <code>npx lint-staged</code> 명령어를 실행할 것이다.</p>
<hr>
<h3 id="2-3-lint-staged-설정하기">2-3. lint-staged 설정하기</h3>
<p><code>lint-staged</code>는 약간 설정이 필요하다.</p>
<pre><code class="language-javascript">{
  &quot;lint-staged&quot;: {
    &quot;*.js&quot;: &quot;eslint --cache --fix&quot;
  }
}</code></pre>
<p>기본적으로는 <code>package.json</code> 파일에 위와 같이 설정이 된다. 만약 <code>.js</code>파일에 <code>eslint</code>만 사용한다면 위처럼 놓고 사용해도 되지만 나는 타입스크립트를 이용할 것이므로 <code>.ts</code> 파일과 <code>.tsx</code> 파일 둘 다 검사할 것 이고 <code>prettier</code>도 사용하므로 다음과 같이 변형해서 사용했다.</p>
<pre><code class="language-javascript">{
  &quot;lint-staged&quot;: {
    &quot;*.{ts,tsx}&quot;: [
      &quot;prettier --write&quot;,
      &quot;eslint --fix&quot;
    ]
  }
}</code></pre>
<hr>
<h3 id="2-4-test해보기🌻">2-4. Test해보기🌻</h3>
<p>&amp;nbsp이제 제대로 작동하는지 확인을 해보겠다.</p>
<blockquote>
<p>git add .
git commit -m &#39;Test husky &amp; lint-staged&#39;</p>
</blockquote>
<p>위의 명령어를 넣으면 다음처럼 나온다면 제대로 동작하는 것이다.
<img src="https://images.velog.io/images/do_dadu/post/14cd059a-629d-49ba-a26f-8feae674004d/9497A713-6FEE-4A5E-A2E1-F9609CF3ACCD.png" alt=""></p>
<p>&amp;nbsp위의 사진을 보면 우리가 설정한대로 git commit를 하기 전에 <code>stage 상태</code>에 있는 <code>.ts</code>와 <code>.tsx</code>파일들을 대상으로 <code>eslint --fix</code>와 <code>prettier --write</code> 명령어를 실행하고 있다. 그러다가 eslint 규칙상 에러가 난 부분이 있어 <code>git commit</code>을 못하게된 상황이다. <strong>한마디로 아주 잘 작동한다!!</strong>😎</p>
<hr>
<h2 id="3-마치며🥳">3. 마치며🥳</h2>
<p>&amp;nbsp이렇게 <code>husky</code>와 <code>lint-staged</code> 라이브러리를 이용하여 <code>eslint</code>를 강제로 할 수 밖에 없게 설정을 해보았다. 꼭 강제한다는 부분 말고도 내가 따로 eslint를 실행하지 않아도 자동으로 명령어를 실행해준다는 점도 매우 매력적이라고 생각된다.</p>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://go2zo.github.io/blog/2021/05/23/husky-v6/">https://go2zo.github.io/blog/2021/05/23/husky-v6/</a>
👉<a href="https://github.com/typicode/husky/issues/949">https://github.com/typicode/husky/issues/949</a>
👉<a href="https://taegon.kim/archives/10276">https://taegon.kim/archives/10276</a>
👉<a href="https://dev-syhy.tistory.com/57">https://dev-syhy.tistory.com/57</a>
👉<a href="https://shiningjean.tistory.com/86">https://shiningjean.tistory.com/86</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 ESLint를 사용해보자]]></title>
            <link>https://velog.io/@do_dadu/ESLint%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@do_dadu/ESLint%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 08 Jun 2021 11:57:32 GMT</pubDate>
            <description><![CDATA[<h2 id="1-eslint가-뭐야🧐">1. ESLint가 뭐야?🧐</h2>
<p>ESLint는 <code>문법적으로 문제가 있는 코드를 찾아주는 도구</code>다. </p>
<p>협업 프로젝트를 할 때는 여러사람이 자신만의 규칙으로 코드를 작성한다. 물론 다들 틀리게 코딩을 하는 것은 아니다. 하지만 대부분 다들 다르게 코딩을 하게된다. 본인에게 익숙한 방법으로 ... 
이게 바로 문제다.</p>
<blockquote>
<p>&quot;아니 코드 잘 돌아가고 빨리 작성하기만 하면 되는거 아니야?&quot;🤨</p>
</blockquote>
<p>라고 생각하는 분들도 있겠지만 주관적인 견해로는 옳지 않다.
서로가 일관성없는 코드를 작성하게 되면 타인의 코드의 가독성이 떨어지게 되는 것은 필연적이다. 그것은 이후의 코드 리뷰에서 큰 문제를 발생하게 된다. 내가 쓰는 코드와 생긴게 너무 다르면 코드를 읽는데 시간이 오래 걸리고 오래걸려서 읽는다고 해도 존재하는 오점을 잡아낼 가능성도 떨어지게 된다. 이런 문제를 해결하기 위해서 <code>코딩 컨벤션을 유지</code>시켜주며 추가적으로 <code>문법적인 오류</code>나 <code>안티 패턴</code>, <code>변수 범위</code> 등을 정적 분석을 통해 런타임 에러를 사전에 발견할 수 있게 도와준다.👍</p>
<hr>
<h2 id="2-eslint-사용하기feat-typescript">2. ESLint 사용하기(feat. typescript)</h2>
<h3 id="2-1-패키지-설치하기💻">2-1. 패키지 설치하기💻</h3>
<p>eslint는 정말로 좋은 협업 라이브러리다. 하지만 git정도로 배우는 것이 어렵다고 생각한다. 최대한 필요한 부분만 간단하게 설명하도록 할께요.</p>
<p>우선 eslint 관련 패키지들 설치하기!</p>
<blockquote>
<p>// 타입스크립트관련 ESLint패키지 설치
npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript </p>
</blockquote>
<blockquote>
<p>// ESLint 서드파티 플러그인 패키지 설치
npm i -D eslint-plugin-react eslint-plugin-react-hooks eslint-plugin-import eslint-plugin-jsx-a11y </p>
</blockquote>
<blockquote>
<p>// airbnb사 ESLint 환경설정 패키지
npm i -D eslint-config-airbnb </p>
</blockquote>
<blockquote>
<p>// prettier와 ESLint 충돌방지용 패키지
npm i -D eslint-config-prettier eslint-plugin-prettier</p>
</blockquote>
<h3 id="패키지-설명✍️">패키지 설명✍️</h3>
<p><strong>eslint</strong>: 코드의 문법을 검사하는 린팅과 코드의 스타일을 잡아주는 포맷팅 기능
<strong>@typescript-eslint/eslint-plugin</strong>: TypeScript관련 linting 규칙을 처리하는 플러그인
<strong>@typescript-eslint/parser</strong>: TypeScript용 ESLint 파서
<strong>eslint-import-resolver-typescript</strong>: 정확한 에러는 기억이 안나는데 해당 패키지를 설치하지 않으면 에러가 발생했었어요... 나중에 정확한 이유를 기입하겠습니다...ㅜ</p>
<p><strong>eslint-plugin-react</strong>: React 관련 린트설정을 지원해줘요
<strong>eslint-plugin-react-hooks</strong>: React Hooks의 규칙을 강제해주는 플러그인입니다
<strong>eslint-plugin-import</strong>: ES2015+의 import/export 구문을 지원해줘요
<strong>eslint-plugin-jsx-a11y</strong>: JSX 내의 접근성 문제에 대해 즉각적인 AST 린팅 피드백을 제공해줘요</p>
<p><strong>eslint-config-airbnb</strong>: airbnb사의 코딩규칙을 사용하게 해줘요</p>
<p><strong>eslint-config-prettier</strong>: prettier와 eslint의 충돌을 일으키는 ESLint 규칙들을 비활성화 시켜주는 config
<strong>eslint-plugin-prettier</strong>: prettier에서 인식하는 오류를 ESLint가 출력해주게 해줘요</p>
<blockquote>
<p>📌 혹시 <code>CRA</code>로 프로젝트를 생성했다면 <code>eslint-plugin-react</code>, <code>eslint-plugin-react-hooks</code>, <code>eslint-plugin-import</code>, <code>eslint-plugin-jsx-a11y</code> 는 CRA에서 지원해주므로 제외하고 설치해주면 되요!</p>
</blockquote>
<hr>
<h3 id="2-2-eslint-설정하기🤟">2-2. ESLint 설정하기🤟</h3>
<p> 모든 패키지를 설치했다면 이제 설정을 해줘야합니다. 여러가지 방법이 있지만 저는 <code>.eslintrc.json</code> 파일을 생성하는 것을 선호해서 해당 방법으로 진행해볼께요</p>
<p> 우선 <code>.eslintrc.json</code> 해당 파일을 프로젝트의 최상위 폴더에 생성해줍니다. 그 이후  아래의 텍스트를 붙여 넣어주시면 됩니다ㅎㅎ</p>
<pre><code class="language-javascript">{
  &quot;env&quot;: {
    &quot;browser&quot;: true,
    &quot;es6&quot;: true,
    &quot;node&quot;: true
  },
  &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
  &quot;plugins&quot;: [&quot;@typescript-eslint&quot;, &quot;import&quot;],
  &quot;extends&quot;: [
    &quot;airbnb&quot;,
    &quot;airbnb/hooks&quot;,
    &quot;plugin:@typescript-eslint/recommended&quot;,
    &quot;plugin:prettier/recommended&quot;,
    &quot;plugin:import/errors&quot;,
    &quot;plugin:import/warnings&quot;
  ],
  &quot;parserOptions&quot;: {
    &quot;ecmaVersion&quot;: 2020,
    &quot;sourceType&quot;: &quot;module&quot;,
    &quot;ecmaFeatures&quot;: {
      &quot;jsx&quot;: true
    }
  },
  &quot;rules&quot;: {
    &quot;linebreak-style&quot;: 0,
    &quot;import/no-dynamic-require&quot;: 0,
    &quot;import/no-unresolved&quot;: 0,
    &quot;import/prefer-default-export&quot;: 0,
    &quot;global-require&quot;: 0,
    &quot;import/no-extraneous-dependencies&quot;: 0,
    &quot;jsx-quotes&quot;: [&quot;error&quot;, &quot;prefer-single&quot;],
    &quot;react/jsx-props-no-spreading&quot;: 0,
    &quot;react/forbid-prop-types&quot;: 0,
    &quot;react/jsx-filename-extension&quot;: [
      2,
      { &quot;extensions&quot;: [&quot;.js&quot;, &quot;.jsx&quot;, &quot;.ts&quot;, &quot;.tsx&quot;] }
    ],
    &quot;import/extensions&quot;: 0,
    &quot;no-use-before-define&quot;: 0,
    &quot;@typescript-eslint/no-empty-interface&quot;: 0,
    &quot;@typescript-eslint/no-explicit-any&quot;: 0,
    &quot;@typescript-eslint/no-var-requires&quot;: 0,
    &quot;no-shadow&quot;: &quot;off&quot;,
    &quot;react/prop-types&quot;: 0,
    &quot;no-empty-pattern&quot;: 0,
    &quot;no-alert&quot;: 0,
    &quot;react-hooks/exhaustive-deps&quot;: 0
  },
  &quot;settings&quot;: {
    &quot;import/parsers&quot;: {
      &quot;@typescript-eslint/parser&quot;: [&quot;.ts&quot;, &quot;.tsx&quot;]
    },
    &quot;import/resolver&quot;: {
      &quot;typescript&quot;: &quot;./tsconfig.json&quot;
    }
  }
}
</code></pre>
<h3 id="eslint-설정-옵션-설명✍️">ESLint 설정 옵션 설명✍️</h3>
<p><strong>env</strong>:
사전 정의된 전역 변수 사용을 정의해줄 수 있어요. 사전 정의된 전역변수를 확인 할 수 있는 <a href="https://eslint.org/docs/user-guide/configuring/language-options#specifying-environments">Docs</a>를 링크해둘께요.</p>
<pre><code class="language-javascript">&quot;env&quot;: {
  &quot;browser&quot;: true,
  &quot;es6&quot;: true,
  &quot;node&quot;: true
}</code></pre>
<p>그리고 선언하지 않은 전역변수를 사용할 때 경고하지 않도록, <code>globals</code>를 이용하여 사용자 전역 변수를 추가할 수 있어요.</p>
<pre><code class="language-javascript">&quot;globals&quot;: {
 &quot;$&quot;: true
}</code></pre>
<p><strong>parser</strong>: 
ESLint는 구문 분석을 위해 보통 <code>Espree</code> 파서를 사용해요.
자주 사용되는 파서로는 <code>Babel</code>과 함께 사용되는 <code>babel-eslint</code>, <code>Typescript</code> 구문 분석을 위해 사용되는 <code>@typescript-eslint/parser</code>가 있어요.</p>
<pre><code class="language-javascript">&quot;parser&quot;: &quot;@typescript-eslint/parser&quot;</code></pre>
<p><strong>plugins</strong>: 
ESLint는 서드파티 플러그인을 지원해요.
플러그인 패키지들을 설치하고, 설치한 플러그인들을 <code>plugins</code>에 추가하여 사용할 수 있어요.</p>
<pre><code class="language-javascript">&quot;plugins&quot;: [&quot;@typescript-eslint&quot;, &quot;import&quot;]</code></pre>
<p><strong>extends</strong>: 
extends는 추가한 플러그인에서 사용할 규칙을 설정해줘야해요
플러그인은 일련의 규칙 집합이며, 플러그인을 추가하여도 규칙은 자동 적용되지 않아요.
규칙을 적용하기 위해서는 추가한 플러그인 중에 사용할 규칙을 직접 추가해줘야 적용되요.</p>
<pre><code class="language-javascript">&quot;plugins&quot;: [&quot;@typescript-eslint&quot;, &quot;import&quot;],
&quot;extends&quot;: [
  &quot;airbnb&quot;,
  &quot;airbnb/hooks&quot;,
  &quot;plugin:@typescript-eslint/recommended&quot;,
  &quot;plugin:prettier/recommended&quot;,
  &quot;plugin:import/errors&quot;,
  &quot;plugin:import/warnings&quot;
],</code></pre>
<p><code>eslint:all</code>과 <code>eslint:recommended</code>는 ESLint에 기본으로 제공해주고 있어요. 하지만 ESLint는 <code>eslint:all</code>을 프로덕션 용도로 사용하지 않는 것을 권장하고 있어요.</p>
<p><strong>parserOptions</strong>: 
ESLint 사용을 위해 지원하려는 Javascript 언어 옵션을 정의할 수 있어요.</p>
<ul>
<li><strong>ecmaVersion</strong>: 사용할 ECMAScript 버전을 설정</li>
<li><strong>sourceType</strong>: parser의 export 형태를 설정</li>
<li><strong>ecmaFeatures</strong>: ECMAScript의 언어 확장 기능을 설정<ul>
<li><strong>globalReturn</strong>: 전역 스코프의 사용 여부 (node, commonjs 환경에서 최상위 스코프는 module)</li>
<li><strong>impliedStric</strong>: strict mode 사용 여부</li>
<li><strong>jsx</strong>: ECMScript 규격의 JSX 사용 여부<pre><code class="language-javascript">{
&quot;parserOptions&quot;: {
&quot;ecmaVersion&quot;: 2020,
&quot;sourceType&quot;: &quot;module&quot;,
&quot;ecmaFeatures&quot;: {
  &quot;jsx&quot;: true
}
}
}</code></pre>
</li>
</ul>
</li>
</ul>
<p><strong>rules</strong>: 
ESLint에는 프로젝트에서 사용하는 규칙을 수정할 수 있어요. </p>
<ul>
<li><code>&quot;off&quot;</code> 또는 <code>0</code>: 규칙을 사용하지 않음</li>
<li><code>&quot;warn&quot;</code> 또는 <code>1</code>: 규칙을 경고로 사용</li>
<li><code>&quot;error&quot;</code> 또는 <code>2</code>: 규칙을 오류로 사용</li>
</ul>
<p><code>규칙에 추가 옵션이 있는 경우에는 배열 리터럴 구문</code>을 사용하여 지정할 수 있습니다.</p>
<pre><code class="language-javascript">&quot;rules&quot;: {
  &quot;eqeqeq&quot;: &quot;off&quot;,
  &quot;curly&quot;: &quot;error&quot;,
  &quot;quotes&quot;: [&quot;error&quot;, &quot;double&quot;]
  &quot;comma-style&quot;: [&quot;error&quot;, &quot;last&quot;],
}</code></pre>
<p><strong>settings</strong>: 
ESLint 구성 파일에 설정 개체를 추가할 수 있으며, 실행될 모든 규칙에 제공됩니다.</p>
<pre><code class="language-javascript">&quot;settings&quot;: {
  &quot;import/parsers&quot;: {
    &quot;@typescript-eslint/parser&quot;: [&quot;.ts&quot;, &quot;.tsx&quot;]
  },
  &quot;import/resolver&quot;: {
    &quot;typescript&quot;: &quot;./tsconfig.json&quot;
  }
}</code></pre>
<hr>
<h3 id="2-3-eslint-실행하기🚀">2-3. ESLint 실행하기🚀</h3>
<p>이렇게 설정까지 다 해냈다면 이제 잘 작동하는지 확인을 해볼 시간이네요.
터미널에 다음과 같은 명령어를 입력해봅시다.</p>
<blockquote>
<p>eslint {파일경로}
ex) eslint src/components/atoms/Button/index.ts</p>
</blockquote>
<p>만약 eslint 명령어를 사용할 수 없다면 eslint를 전역 설치해주시면 될거에요.</p>
<blockquote>
<p>npm i -g eslint</p>
</blockquote>
<p>다시 eslint를 실행해보면 에러가 있다면 다음과 같이 나올꺼에요.
<img src="https://images.velog.io/images/do_dadu/post/94c9ce42-b498-4b33-982e-fded6dcde9b7/200EEF8B-7147-4541-A658-A527837E7AF1.png" alt="">
해당 에러를 고치려면 직접 고치는 방법과 명령어를 통하여 자동으로 고치는 방법이 있어요.</p>
<blockquote>
<p>eslint --fix {파일경로}
ex) eslint --fix src/components/atoms/Button/index.ts</p>
</blockquote>
<p>eslint 명령어 뒤에 --fix 옵션을 붙이면 발생한 에러를 자동으로 고쳐줘요. 하지만 모든 에러를 고쳐주진 않아요...🥲</p>
<hr>
<h2 id="3-마치며">3. 마치며</h2>
<p>ESLint를 좀 더 자세히 알아보는 시간이 되었던 것 같다.
최재한 양질의 정보를 전달하려 했지만 그러지 못한 부분이 많은 것 같기도하다.
혹시 이 글을 끝까지 읽고 계신 분들한테는 밑에 출처도 한번씩 방문해서 더 좋은 글들을 읽어보시길 추천드립니다</p>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://velog.io/@kyusung/eslint-config-2#%ED%99%98%EA%B2%BDenv">https://velog.io/@kyusung/eslint-config-2#환경env</a>
👉<a href="https://velog.io/@xortm854/Typescript-React-Eslint-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95-2%ED%8E%B8-ESLint-Prettier-%EC%84%A4%EC%A0%95">https://velog.io/@xortm854/Typescript-React-Eslint-환경설정-2편-ESLint-Prettier-설정</a>
👉<a href="https://eslint.org/docs/user-guide/getting-started">https://eslint.org/docs/user-guide/getting-started</a>
👉<a href="https://velog.io/@kmlee95/React-Typescript-eslint-prettier%EC%84%A4%EC%A0%95">https://velog.io/@kmlee95/React-Typescript-eslint-prettier설정</a>
👉<a href="https://pravusid.kr/typescript/2020/07/19/typescript-eslint-prettier.html">https://pravusid.kr/typescript/2020/07/19/typescript-eslint-prettier.html</a>
👉<a href="https://overcome-the-limits.tistory.com/entry/%ED%98%91%EC%97%85-ESLint-Prettier-Airbnb-Style-Guide%EB%A1%9C-%EC%BD%94%EB%93%9C-%EC%BB%A8%EB%B2%A4%EC%85%98-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0">https://overcome-the-limits.tistory.com/entry/협업-ESLint-Prettier-Airbnb-Style-Guide로-코드-컨벤션-설정하기</a>
👉<a href="https://tech.kakao.com/2019/12/05/make-better-use-of-eslint/">https://tech.kakao.com/2019/12/05/make-better-use-of-eslint/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 Prettier 사용하기]]></title>
            <link>https://velog.io/@do_dadu/prettier-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@do_dadu/prettier-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 07 Jun 2021 14:38:10 GMT</pubDate>
            <description><![CDATA[<h2 id="1-prettier란😍">1. Prettier란😍?</h2>
<p>Prettier는 간단하게 <code>코드 스타일을 관리해주는 도구</code>다. </p>
<p>코딩을 하다보면 혼자서 코딩을 하더라도 코딩이 <code>일관성</code>이 없어진다.
어디서는 ;을 잘 붙어 놓고 어디선 안 붙이고 혹은 </p>
<pre><code class="language-javascript">for (let i = 0; i &lt; 10; i++)
for (let i=0;i&lt;10;i++)</code></pre>
<p>위 두줄은 같은 것을 의미하지만 육안으로 볼 때 확실히 달라보인다.
이렇게 코드가 일관성이 없다면 내 코드를 읽어도 짜증나는데 남의 코드를 읽거나 남이 이런 내코드를 읽으면 얼마나 화가 날까...🤬</p>
<p>이런 문제를 해결하기 위하여 태어난 것이 바로 Prettier이다. 나는 혼자 프로젝트를 할 때도 자주 사용하지만 다른 사람들과 협업을 할 때 더 빛을 발한다.
확실히 이름처럼 코드가 더 이뻐지는 것을 느낄 수 있을 것이다.🌻</p>
<hr>
<h2 id="2-prettier-사용법💻">2. Prettier 사용법💻</h2>
<p>우선 우리 프로젝트에 Prettier를 설치해야한다.</p>
<blockquote>
<p>npm i -D prettier</p>
</blockquote>
<p>(저는 npm 유저여서 npm 기준으로  설명을 진행할께요...😅)</p>
<p>그 후에 prettier를 설정하기 위해 최상위 디렉터리에 <code>.prettierrc</code>파일을 만들어주세요.
그럼 해당 파일에서 여러가지 설정이 가능하답니다.😁
그러면 제가 자주 사용하는 것들 위주로 설명을 드릴께요.</p>
<p><strong>singleQuote</strong>: 문자열 입력시 <code>&quot;</code> 를 쓸지 <code>&#39;</code> 를 사용할지 설정할 수 있어요. 저는 <code>&#39;</code> 를 사용하기에 <code>true</code>로 설정해 놓았습니다.</p>
<p><strong>semi</strong>: 세미콜론(;) 사용에 관한 설정이에요. 저는 사용하므로 <code>true</code> 로 놓고 사용해요.</p>
<p><strong>tabWidth</strong>: 들여쓰기의 크기를 정할 수 있어요. 저는 2칸으로 놓고 써요. 4칸으로 놓고 쓰면 들여쓰기가 중첩될 시에 코드 뒷부분이 안보이는 경우가 생기더라구요....ㅜ</p>
<p><strong>trailingComma</strong>: 객체 또는 배열이 여러줄로 구성되어 있으면 다음과 같이 맨 마지막 줄에 쉼표를 붙여줄지 설정할 수 있어요.</p>
<pre><code class="language-javascript">const object = {
  a: 1,👈
  b: 2,👈
};</code></pre>
<p><code>none</code> 이면 쉼표를 붙이지 않고, <code>es5</code> 이면 객체, 배열을 사용하게 될 떄 쉼표를 붙이고, <code>all</code> 이면 함수를 사용 할 때 인자를 전달 할 때도 쉼표를 붙입니다.</p>
<p><strong>printWidth</strong>: 줄 바꿈할 폭의 길이를 설정해줄 수 있어요.📏</p>
<p>저는 아래와 같이 설정해 놓고 사용합니다.</p>
<pre><code class="language-javascript">{
  &quot;singleQuote&quot;: true,
  &quot;semi&quot;: true,
  &quot;tabWidth&quot;: 2,
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;printWidth&quot;: 80
}
</code></pre>
<p>제가 주로 사용하는 옵션들 말고도 많이 있으니 <a href="https://prettier.io/docs/en/options.html">Docs</a>를 링크하도록 할께요.😉</p>
<h2 id="3-마치며">3. 마치며</h2>
<p>prettier는 정말로 코드의 질을 높여주는 것 중에 가장 가성비가 좋다. 이렇게 간단한데 코드는 정말로 43.1324배는 깔끔하고 이뻐진다. 정.말.로. 진심이다. 아직 사용하지 않는 분들이라면 꼭 한번 사용해보기를 추천한다.</p>
<blockquote>
<p>💡직접 읽어보면 뼈가 되고 살이 되는 출처
👉<a href="https://react.vlpt.us/basic/27-useful-tools.html">https://react.vlpt.us/basic/27-useful-tools.html</a>
👉<a href="https://velog.io/@kyusung/eslint-prettier-config">https://velog.io/@kyusung/eslint-prettier-config</a>
👉<a href="https://prettier.io/docs/en/options.html">https://prettier.io/docs/en/options.html</a></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>