<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>내가 볼log😁</title>
        <link>https://velog.io/</link>
        <description>유능한 프론트엔드 개발자가 되고픈 사람😀</description>
        <lastBuildDate>Thu, 09 Feb 2023 10:26:02 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>내가 볼log😁</title>
            <url>https://images.velog.io/images/apro_xo/profile/af7139d7-4e85-4287-91a4-3c4c0d394792/소개사진.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 내가 볼log😁. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/apro_xo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[JS] javascript this 이해해보기(FEAT. arrow function, method)]]></title>
            <link>https://velog.io/@apro_xo/JS-this-%EB%9A%9C%EA%B9%8C%ED%8C%A8%EA%B8%B0</link>
            <guid>https://velog.io/@apro_xo/JS-this-%EB%9A%9C%EA%B9%8C%ED%8C%A8%EA%B8%B0</guid>
            <pubDate>Thu, 09 Feb 2023 10:26:02 GMT</pubDate>
            <description><![CDATA[<h1 id="헷갈린다this">헷갈린다..this</h1>
<p>this에 대해서 어느정도는 알고 있었지만 시간이 지나면 자꾸 까먹어서 외우기보단 이해를 해보자! 라고 큰 맘 먹고 공부하고 기록하기로 했다.</p>
<p>📌 약간의 뇌피셜을 섞어서 제 방식대로 이해한 내용입니다! 틀린 개념이 있을 수 있다는 것을 미리 알려드립니다. 댓글로 알려주시면 정말 감사합니다</p>
<h1 id="this">this?</h1>
<blockquote>
<p>자신을 호출한 객체를 가리킨다.</p>
</blockquote>
<p>위의 정의에 따라 자바스크립트의 this는 동적으로 결정이 된다.
this는 자바스크립트에서</p>
<ul>
<li>default는 전역 객체를 가리킨다.<code>(브라우저:window, node:global)</code></li>
<li>메소드가 아닌 일반적인 함수 실행에서의 this는 전역 객체를 가리킨다.</li>
<li>객체 안의 메소드는 해당 객체를 가리킨다.</li>
</ul>
<p>아래의 코드를 보면서 알아보자.</p>
<pre><code class="language-js">function tempfunc() {
  console.log(this);
}


const tempobj = {
  name:&#39;temp&#39;,
  showThis: function() {
    console.log(this);
  }
}
tempfunc();
tempobj.showThis();</code></pre>
<p>위의 코드를 실행하면 아래와 같은 결과가 나온다.
<img src="https://velog.velcdn.com/images/apro_xo/post/56f54908-f63b-440e-8abb-e96d73557cfa/image.png" alt="">
정말로 일반적인 함수 tempfunc의 this는 전역 객체를 가리키고, 메서드는 해당 객체를 가리키는 것을 알 수 있다.</p>
<p><strong>왜 이런 결과가 나오는걸까?????🤔</strong></p>
<h1 id="함수-vs-메서드-그리고-this">함수 vs 메서드, 그리고 this</h1>
<p>함수와 메서드는 다르다. 함수는 우리가 일반적으로 선언해서 사용하는 것이 함수이고 메서드는 객체의 프로퍼티 값으로 전달된 함수를 뜻한다.</p>
<blockquote>
<ul>
<li>메서드를 수행하기 위해서는 객체를 통해서 메서드를 수행해야한다.</li>
</ul>
</blockquote>
<ul>
<li>반면에 함수는 함수 자체가 그 동작을 정의한 함수 객체이기 때문에 자기 자신을 수행한다. </li>
</ul>
<p>그래서 나는 메서드는 객체를 통해 수행되니까 객체가 메서드를 호출하는 것으로 이해했다.</p>
<p>함수는 함수 자기 자신이 하나의 객체이자 하나의 실행 컨텍스트 그 자체이니까? 얘를 호출하려면 브라우저에서는 window가, node 환경에서는 global이 호출 해야하지 않을까? 라고 생각했다.</p>
<p><strong>그럼 this와 연결시켜보면, 메서드는 객체가 호출하기 때문에 메서드의 this는 당연히 해당 객체를 가리키고, 함수는 전역 객체가 실행시키기 때문에 전역객체를 가리킨다.</strong></p>
<p>라고 생각하니 이해가 잘 되었다.</p>
<p>📌 하지만 틀릴 수 있따..... 100% 맹신하지 마세요!!! 그냥 끄적끄적해봤습니다. 제가 적은게 틀린 개념이거나 다른 생각 가지신 분은 댓글로 알려주세요!! 환영합니다!!!</p>
<pre><code class="language-js">const tempobj = {
  name:&#39;temp&#39;,
  showThis: function() {
    console.log(this);
    function temp() {
        console.log(this);
    }
    temp();
  }
}
tempobj.showThis();</code></pre>
<p>그럼 위의 코드를 실행하면 어떤 결과가 나올까?
<img src="https://velog.velcdn.com/images/apro_xo/post/aae450cb-4fd3-4ada-81fa-223264465f1b/image.png" alt=""></p>
<p>showThis 메서드 안에 정의된 temp()는 객체가 호출하는 것이 아니기 때문에 전역 객체를 가리키는 것을 알 수 있다.</p>
<h1 id="arrow-function">arrow function</h1>
<p>ES6부터 도입된 화살표함수.... 나는 정말 많이 사용하는 것 같다.
<strong>화살표 함수와 this의 관계는 위의 개념이 적용되지 않는다❌</strong>
알아볼게요</p>
<blockquote>
<p>화살표 함수의 this는 함수가 <strong>선언될 때</strong>를 기준으로 상위 스코프에 있는 객체를 가리킨다. </p>
</blockquote>
<p>이 글의 첫 번째 코드를 화살표 함수를 사용하는 예제로 바꿔서 그대로 써보자</p>
<pre><code class="language-js">const tempfunc = () =&gt; {
  console.log(this);
}


const tempobj = {
  name:&#39;temp&#39;,
  showThis: () =&gt; {
    console.log(this);
  }
}
tempfunc();
tempobj.showThis();</code></pre>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/1844adc2-a449-49d4-9575-71d2dca9c9f5/image.png" alt=""></p>
<p>둘 다 전역 객체를 가리키는 것을 알 수 있다.
<strong>왜 그럴까❓</strong></p>
<p>tempfunc()이 선언될 때 기준 자신보다 상위 스코프는 전역 객체가 된다.</p>
<p>tempobj 안의 메서드 showThis는 선언 기준 tempobj 스코프 안에 있는데, 그것의 상위 스코프는 전역 객체가 된다.</p>
<p>📌 제가 틀린 개념을 적었을 수도 있으니, 혹시 제가 틀렸다면 댓글로 알려주시면 정말 감사하겠습니다!!</p>
<p>자 그럼 아래의 코드를 실행해보자.</p>
<pre><code class="language-js">const tempobj = {
  name:&#39;temp&#39;,
  showThis: () =&gt; {
    console.log(this);
    function temp() {
        console.log(this);
    }
    temp();
  }
}
tempobj.showThis();</code></pre>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/1a9ed3d3-c698-47ba-985c-2d5c0a8889da/image.png" alt=""></p>
<p>showThis 메서드는 선언 기준으로 tempobj 스코프에 포함되어 있고, 선언 기준 상위 스코프가 전역 객체이다. showThis 메서드 내부에 선언된 일반 함수 temp()는 일반 함수이기에 무조건 전역 객체를 가리키게 된다.</p>
<p><strong>그럼 아래의 코드를 실행하면 어떻게 될까?</strong></p>
<pre><code class="language-js">const tempobj = {
  name:&#39;temp&#39;,
  showThis: function() {
    console.log(this);
    const temp = () =&gt; {
        console.log(this);
    }
    temp();
  }
}
tempobj.showThis();</code></pre>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/e35220df-0f3d-4c9b-baca-b3a326eea6bb/image.png" alt=""></p>
<p>showThis 메서드는 일반 함수로 정의 되었지만, 객체의 메소드이기 때문에 객체가 호출하므로 this가 tempobj 객체를 가리키는 것을 알 수 있다.</p>
<p>showThis 메서드 내부에 정의된 화살표함수 temp()는 선언 기준 showThis 메서드의 스코프에 포함되어있다.
따라서 상위 스코프인 tempobj를 this가 가리키게 되는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React Query] useQuery select 옵션]]></title>
            <link>https://velog.io/@apro_xo/ReactQuery-useQuery-select-%EC%98%B5%EC%85%98</link>
            <guid>https://velog.io/@apro_xo/ReactQuery-useQuery-select-%EC%98%B5%EC%85%98</guid>
            <pubDate>Tue, 07 Feb 2023 12:09:08 GMT</pubDate>
            <description><![CDATA[<h1 id="get-요청에서-resdata를-반환했었다">GET 요청에서 res.data를 반환했었다</h1>
<p>useQuery를 사용하여 데이터를 fetching 할 때, axios API 요청 함수에서 return하는 것은 항상 GET 요청 후 받은 응답의 data 값이었다.</p>
<p>왜냐하면 실제로 useQuery를 통해 받아온 응답에서 필요한 것은 response.data 값이기 때문이다. 따라서 아래와 같이 항상 코드를 작성해왔다.</p>
<p>아래의 코드는 jsonplaceholder와 mock API를 통해 실험한 코드이다.</p>
<pre><code class="language-ts">// api.ts
export const functionAPI = {
  fetchDefault: async () =&gt; {
    const res = await axios.get(&quot;https://jsonplaceholder.typicode.com/todos/1&quot;);
    return res.data;
  },
};</code></pre>
<p>📌 useQuery는 커스텀 훅으로 분리하여 모듈화하여 사용하는 예시로 작성하겠다.</p>
<pre><code class="language-tsx">// useDefaultFetch.tsx
import React from &quot;react&quot;;
import { functionAPI } from &quot;../apis/apifunction&quot;;
import { useQuery } from &quot;@tanstack/react-query&quot;;

interface Props {
  onSuccess: (data:any) =&gt; void;
  onError?: () =&gt; void;
}

const useDefaultFetch = ({ onSuccess, onError }: Props) =&gt; {
  return useQuery([&quot;fetch_default&quot;], functionAPI.fetchDefault, {
    onSuccess,
    onError
  });
};

export default useDefaultFetch;</code></pre>
<pre><code class="language-tsx">// FetchComponent.tsx
import React from &quot;react&quot;;
import styled from &quot;styled-components&quot;;
import useDefaultFetch from &quot;../hooks/useDefaultFetch&quot;;
import { TypeJsonPlaceHolder } from &quot;../typings&quot;;

const FetchComponent = () =&gt; {
  const onSuccess = (data: TypeJsonPlaceHolder) =&gt; {
    console.log(&quot;onSuccess: &quot;, data.userId);
  };

  const { data } = useDefaultFetch({ onSuccess });

  return &lt;FetchWrapper&gt;{data?.userId}&lt;/FetchWrapper&gt;;
};

export default FetchComponent;

const FetchWrapper = styled.div``;
</code></pre>
<p>FetchComponent.tsx에서 커스텀 훅 useDefaultFetch를 사용하고 있고, onSuccess를 통해 GET 요청을 통해 받아온 응답의 data 값의 userId를 console에 찍어주고 있다.</p>
<p>실행 결과는 아래와 같다.
<img src="https://velog.velcdn.com/images/apro_xo/post/79e134a1-5a9e-4047-86f8-6d14b8d236a9/image.png" alt=""></p>
<h1 id="나-혼자-조금씩-거슬렸던-점">나 혼자 조금씩 거슬렸던 점</h1>
<p>apis.ts 파일을 생성하고 axios를 통해 API 요청을 담당하는 함수를 apis.ts에 모두 모으고 분리하여 모듈화 시켜서 사용하는데, 
ReactQuery의 useMutate에 사용되는 mutateFn은 항상 Promise를 return 하는 함수여야 한다.</p>
<p>따라서 axios 함수 그 자체를 반환하며 사용했었다.</p>
<pre><code class="language-ts">// apis.ts
import axios from &quot;axios&quot;;
import { TypeMock } from &quot;../typings&quot;;

export const functionAPI = {
  fetchDefault: async () =&gt; {
    const res = await axios.get(&quot;https://jsonplaceholder.typicode.com/todos/1&quot;);
    return res.data;
  },
  postMock: async (data: TypeMock) =&gt; {
    await axios.post(
      &quot;https://62f223b8b1098f15080c163f.mockapi.io/apimainpostings&quot;,
      data
    );
  },
};</code></pre>
<p>POST 요청 함수는 Promise를 반환하고 GET 요청 함수는 응답의 data 값을 반환하고,,, 뭔가 통일이 되지 않아 거슬렸다.</p>
<p>물론 GET 함수에서 Promise를 반환하고 onSuccess 같은 곳에서 사용할 때 <code>res.data</code> 와 같은 형태로 사용해도 되긴 하지만 그렇게 하기는 싫었다.
뭔가 조금 있어보이는 방법이 필요했다.</p>
<h1 id="select-옵션">select 옵션</h1>
<blockquote>
<p>queryFn을 통해 반환 받은 값을 가공, 정제할 수 있는 옵션</p>
</blockquote>
<p>select 옵션을 사용해서 조금 멋있게 해결해보자.......!!</p>
<pre><code class="language-ts">// apis.ts
import axios from &quot;axios&quot;;

export const functionAPI = {
  fetchDefault: async () =&gt; {
    return await axios.get(&quot;https://jsonplaceholder.typicode.com/todos/1&quot;);
  },</code></pre>
<p>GET 요청 그 자체를 return한다. 즉 Promise를 반환한다..!</p>
<pre><code class="language-tsx">import React from &quot;react&quot;;
import { functionAPI } from &quot;../apis/apifunction&quot;;
import { useQuery } from &quot;@tanstack/react-query&quot;;

interface Props {
  onSuccess: (data:any) =&gt; void;
  onError?: () =&gt; void;
}

const useDefaultFetch = ({ onSuccess, onError }: Props) =&gt; {
  return useQuery([&quot;fetch_default&quot;], functionAPI.fetchDefault, {
    onSuccess,
    onError,
    select:data =&gt; data.data
  });
};

export default useDefaultFetch;</code></pre>
<p>위의 커스텀 훅에서 select 옵션이 추가 되었다.
<strong>queryFn을 통해 반환 받은 값의 data 속성에 접근하여 마치 그 값을 반환 받은 것 처럼 처리해준다.</strong></p>
<p>따라서 사용 시에 번거롭게 data 속성 값에 접근하여 사용하지 않아도 된다.
즉, 데이터를 정제, 가공해준다.</p>
<p>실행 결과는 역시 성공이다.
<img src="https://velog.velcdn.com/images/apro_xo/post/eb3df225-ddfa-4dc1-81ed-f7043b7d5177/image.png" alt=""></p>
<p>지금은 비교적 간단한 예시지만, 상황에 따라 데이터를 가공해야할 때, 사용하면 매우 좋을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[리팩토링 프로젝트를 진행하며]]></title>
            <link>https://velog.io/@apro_xo/%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%EC%A7%84%ED%96%89%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@apro_xo/%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EB%A5%BC-%EC%A7%84%ED%96%89%ED%95%98%EB%A9%B0</guid>
            <pubDate>Fri, 27 Jan 2023 08:00:29 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/apro_xo/post/e99feb5a-68c5-4d21-b14a-6f6ebc76f638/image.png" alt=""></p>
<h1 id="글의-목적">글의 목적</h1>
<p>이전에 진행했었던 프로젝트의 코드 리팩토링 작업을 1차적으로 끝마쳤다.
리팩토링을 하면서 발생했던 문제들과 해결 방법들을 간단하게 적어보고, 느낀점도 간단하게 적어보려 글을 작성한다.</p>
<p>어떻게 보면 회고라고 할 수도 있겠다.</p>
<p><strong>❌ 다만, 깃허브 Issue, Pull Request를 통해 문서화가 되어 있는 부분들은 이 블로그 포스팅에서 다루지 않을 것이다.</strong></p>
<p><a href="https://github.com/nggoong/iting-refactoring">💁‍♂️깃허브는 여기에요!</a></p>
<h1 id="📌-발생한-문제와-해결-방법">📌 발생한 문제와 해결 방법</h1>
<h2 id="1-react-router-dom-제네릭-인식-문제">1. react-router-dom 제네릭 인식 문제</h2>
<p>결론부터 얘기하면 react-router-dom v6부터는 제네릭을 지원하지 않는다.
따라서 타입스크립트를 사용하며 타입 정의를 위해 <strong><em>제네릭 대신에 as 문법을 사용해야 한다.</em></strong></p>
<p>useNavigate를 통해 페이지를 이동하면서 넘겨주는 state가 있는데,
이 state의 타입을 아래와 같이 정의했었다.</p>
<pre><code class="language-tsx">export interface TypeChatRoomNavigateState {
  state: {
    title?: string;
    isHost: boolean;
  };
}</code></pre>
<p>이 타입을 제네릭으로 사용하려니 아무리해도 되지 않았다.
as를 통해 해결할 수 있었다.</p>
<pre><code class="language-tsx">//const { state } = useLocation&lt;TypeChatRoomNavigateState&gt;()
const { state } = useLocation() as TypeChatRoomNavigateState;</code></pre>
<h2 id="2-배포-시-발생한-문제">2. 배포 시 발생한 문제</h2>
<blockquote>
<p>uncaught syntaxerror: unexpected token &#39;&lt;&#39; </p>
</blockquote>
<p>로컬에서 실행시킬 때는 괜찮았는데, 배포 후 배포 도메인으로 들어가보니 위와 같은 에러와 함께 빈 화면만 떴다.</p>
<p>알아보니, 빌드 시 웹팩이 chunkFile을 JS가 아닌 HTML 구문으로 인식해서 발생하는 오류이며, &lt;DOCTYPE 으로 시작하는 HTML의 &lt;를 인식하지 못한다는 문법 에러라고 한다.</p>
<p><a href="https://yoon-dumbo.tistory.com/entry/Error-%EC%A0%95%EB%A6%AC-React-Uncaught-SyntaxError-Unexpected-token">https://yoon-dumbo.tistory.com/entry/Error-%EC%A0%95%EB%A6%AC-React-Uncaught-SyntaxError-Unexpected-token</a></p>
<p>해결한 방법은,</p>
<h4 id="🎇-packagejson에-homepage-추가">🎇 package.json에 &quot;homepage&quot;:&quot;.&quot; 추가</h4>
<pre><code class="language-json">&quot;version&quot;: &quot;0.1.0&quot;,
&quot;private&quot;: true,
&quot;dependencies&quot;:
(...생략)</code></pre>
<h4 id="🎇-indexhtml의-베이스-경로-수정">🎇 index.html의 베이스 경로 수정</h4>
<pre><code class="language-html">&lt;base href=&quot;/&quot; /&gt;</code></pre>
<p>위의 태그를 추가하여 현재 문서의 모든 상대 링크에 사용될 기본 URL을 지정한다.</p>
<p>위의 수정사항을 적용하여 재빌드 후 배포하였지만 똑같은 오류와 함께 빈 화면이 떴다.</p>
<p>개발자 도구를 켜서 index.html을 확인해보니 예전의 index.html이었다.
따라서 CloudFront의 캐시를 삭제했다.</p>
<h4 id="🎇-cloudfront-캐시-삭제">🎇 CloudFront 캐시 삭제</h4>
<p>CloudFront의 캐시는 기본적으로 24시간이며, 나는 24시간을 기다리기 싫었다.😂 빨리 확인도 해봐야하고 하니까..!</p>
<p><strong>1. 무효화할 프로젝트 선택</strong>
CloudFront 대시보드에 들어가서 무효화 할 프로젝트를 선택한다.
<img src="https://velog.velcdn.com/images/apro_xo/post/a10fe8eb-682a-449c-8fad-819e46127dd2/image.png" alt="">
<strong>2. 무효화 생성</strong>
위의 무효화 탭을 클릭하여 들어간 다음, 무효화 생성 버튼을 눌러 무효화를 생성한다. 생성할 때, 모든 전체 파일에 대해 무효화를 선언했다. <code>/*</code>
그 후 1분 내에 상태가 완료됨으로 바뀐다.
<img src="https://velog.velcdn.com/images/apro_xo/post/1a61a8fb-c294-48cd-83c4-3d5dfba97fee/image.png" alt="">
<strong>3. 무효화 세부 정보 확인</strong>
<img src="https://velog.velcdn.com/images/apro_xo/post/5b41916a-a1a5-4f2c-b12d-a975efa0e621/image.png" alt=""></p>
<p>위의 방법으로 CloudFront의 캐시를 무효화하고, 도메인을 입력하여 다시 들어가보니 제대로 작동했다.</p>
<h2 id="추가">추가</h2>
<p>package.json에 &quot;homepage&quot;:&quot;.&quot; 추가, index.html의 base 태그를 통해 베이스 경로를 수정했었는데, 처음 수정 했을 때는 잘 됐는데, 계속적인 수정을 거치고 다시 배포를 해보니 또 안 됐다.</p>
<p>진짜 식겁했다.</p>
<p>이번에 해결한 방법은 build 디렉토리에 들어가서 index.html을 확인 해봤다.</p>
<p><code>&lt;script defer=&quot;defer&quot; src=&quot;./static/js/main.8f931722.js&quot;&gt;&lt;/script&gt;</code></p>
<p>src 속성에서 <strong>.</strong>을 빼줬다.</p>
<p><code>&lt;script defer=&quot;defer&quot; src=&quot;/static/js/main.8f931722.js&quot;&gt;&lt;/script&gt;</code></p>
<p>그랬더니 해결이 되었다.</p>
<h1 id="📌-느낀점">📌 느낀점</h1>
<p>타입스크립트로 마이그레이션을 하면서, 많은 에러를 겪었다. 마이그레이션 도전기를 따로 블로그에 포스팅 할 예정이다.</p>
<p>CRA template을 타입스크립트로 지정하여 처음부터 개발할 때는 몰랐으나 기존 자바스크립트 프로젝트를 마이그레이션하면서 나는 정말 편하게 개발하고 있었구나 느꼈다😂</p>
<p>그리고 자바스크립트가 정말정말 느슨했다는 것을 느꼈다.
당연히 되어야한다고 생각했던 코드가 에러가 나서 조금 당황했다.
지금 당장 생각나는 부분을 적어보면, <strong>Date 타입의 -연산인데,</strong></p>
<pre><code class="language-js">let start = new Date();
let result = new Date() - start;
console.log(result);</code></pre>
<p>자바스크립트에서는 위의 코드가 정상적으로 동작하지만 타입스크립트에서는 그렇지 않았다. Date 타입은 - 연산을 할 수 없으며, number로 타입 변환이 필요했다. 이렇듯, 자바스크립트는 정말 느슨했다는 것을 느낄 수 있었다.</p>
<p>타입을 명시적으로 지정해주면서 데이터 타입이 대충 어떻게 넘어온다는 것을 알 수 있었고, 코드의 목적을 더 명확하게 알 수 있었다. 협업 시 타입스크립트로 프로젝트를 진행한다면 타입이 명시되어 있기 때문에 타입으로 인해 일어날 수 있는 에러를 미리 방지할 수 있고, 팀원 간 의사소통에도 도움이 될 것 같다. </p>
<p>왜 사람들이 타입스크립트 한 번 맛보면 자바스크립트 안간다고 하는지 알겠다 ㅋㅋㅋㅋ</p>
<p>이번 프로젝트를 통해 정말 절실하게 느낀 점🔥은 리팩토링은 날 잡고 한 번에 하는게 아니라 주기적으로, 습관적으로, 프로젝트 진행 중에도 하는 것이 좋을 것 같다고 느꼈다.</p>
<p>기존의 코드는 정말 뒤죽박죽이었다.
분리가 된 부분도 있고 안 된 부분도 있고 혼란스러웠고, 간단하게 처리할 수 있는 방법이 있는데 굳이 어렵게 구현한 부분도 눈에 보였다. 또한 컨벤션과 포맷팅도 맞춰지지 않았었다.</p>
<p>코드의 의미를 분석하느라 꽤 오랜시간이 걸렸다.</p>
<p>그래서 개발을 하면서도 팀원과 주기적인 코드 리뷰를 통해 서로의 코드를 이해하고, 습관적으로 리팩토링을 진행해야 정신 건강에 좋을 것 같았다.</p>
<p>디버깅에 용이하고 유지보수성을 높이고, 이해하기 쉬운 코드를 작성하는 것이 좋은 코드에 대한 궁극적인 목표가 아닐까? 하는 생각도 들었다. 물론 지금은 취준생이라서 개발 경력이 부족해서 내가 생각하는 이것이 전부가 아니라는 생각이 든다. 근데 지금은 이렇게 생각한다.</p>
<p>Typescript를 사용하는 이유도 코드의 목적을 분명히하여 코드의 이해를 돕고, 디버깅을 쉽게하는 것, 결국은 좋은 코드를 위한 궁극적인 목표이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] SEO를 위한 react-helmet-async, react-snap]]></title>
            <link>https://velog.io/@apro_xo/react-helmet-async-react-snap</link>
            <guid>https://velog.io/@apro_xo/react-helmet-async-react-snap</guid>
            <pubDate>Thu, 22 Dec 2022 07:03:11 GMT</pubDate>
            <description><![CDATA[<h1 id="csr의-문제점">CSR의 문제점</h1>
<p>리액트는 대표적인 CSR이며, SPA이다.
따라서 리액트는 하나의 index.html을 두고, 자바스크립트를 활용하여 데이터만 변경한다.
사용자가 필요한 부분의 데이터만 변경하여 웹을 구성한다.</p>
<p>여러가지 장점이 있지만 단점도 존재한다.
대표적으로 꼽으면, 초기 로딩 속도가 느리다는 것과 SEO가 어렵다는 것이다.</p>
<p>초기 로딩 속도는 SPA의 특성상 초기 로딩 시 모든 자바스크립트 파일을 로드해야하기 때문에 사용자가 특정 페이지를 들어가지 않더라도 모든 파일을 다운 받아야한다. 따라서 로딩 속도가 SSR에 비해 느리다.</p>
<p>또 하나는 SEO인데, React는 하나의 index.html을 두고 있기 때문에 페이지 마다 페이지를 설명하는 메타태그를 설정하기 어렵다.</p>
<p>따라서 브라우저 크롤러가 페이지를 크롤링 할 때 모든 페이지의 정보를 가져올 수 없어 검색엔진최적화에는 좋지 않다.</p>
<p>이 외에도 다른 단점들이 존재하지만 이번에는 React를 사용한 프로젝트에서 SEO에 조금이나마 도움이 될 수 있도록 <code>react-helmet-async</code>, <code>react-snap</code>을 사용하는 방법을 기록한다.</p>
<h1 id="react-helmet-async">react-helmet-async</h1>
<blockquote>
<p>npm install react-helmet-async</p>
</blockquote>
<p>npm 또는 yarn으로 react-helmet-async를 설치한다.</p>
<p>이전에 프로젝트를 하면서 react-helmet을 사용했었는데, 이번에 다른 프로젝트를 하면서 다시 사용하려고 보니 react-helmet-async라는 것이 있었다. 우선 이 둘의 차이점을 보자.</p>
<h2 id="1-react-helmet과의-차이점">1. react-helmet과의 차이점</h2>
<h3 id="1-1-react-side-effect">1-1. react-side-effect</h3>
<p>아래의 <a href="https://www.npmjs.com/package/react-helmet-async">react-helmet-async npm</a>에서 확인 할 수 있었다.
<img src="https://velog.velcdn.com/images/apro_xo/post/b0e8fde8-c645-4b95-b345-fef3e62914c5/image.png" alt=""></p>
<blockquote>
<p>해석해보면, 기존의 react-helmet은 thread-safe하지 않은 react-side-effect에 의존했다.</p>
</blockquote>
<p>📌 <strong>thread-safe란?</strong>
여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에 문제가 없음을 뜻한다.</p>
<p>react-side-effect라는 패키지가 thread-safe하지 않는 것 같다.</p>
<p>실제로 <a href="https://github.com/nfl/react-helmet/blob/master/package.json">react-helmet의 깃허브 코드의 package.json</a>에 들어가서 코드를 보았다.
<img src="https://velog.velcdn.com/images/apro_xo/post/e8b69b0e-f37b-446a-9353-773018292e1c/image.png" alt=""></p>
<p>위와 같이 react-side-effect가 디펜던시로 추가 되어 있음을 확인할 수 있었다.</p>
<p>react-helmet-async의 package.json에는 react-side-effect가 없는 것을 확인할 수 있었다. 궁금한 사람은 직접 가서 보기!</p>
<p>실제로 react-helmet 같은 경우는 마지막 업데이트가 현재 기준으로 3년 전이다.
업데이트가 이루어지지 않고 있다.</p>
<p>그래서 이번 프로젝트에서는 react-helmet 대신에 react-helmet-async를 사용하기로 했다.</p>
<h3 id="1-2-사용법">1-2. 사용법</h3>
<p>기본적인 사용 방법은 기존의 react-helmet과 비슷하다.
하지만 react-helmet-async를 사용하기 위해서는 HelmetProvider를 사용하여 App.js를 감싸주어야한다.</p>
<pre><code class="language-jsx">// index.js
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom/client&#39;;
import &#39;./index.css&#39;;
import App from &#39;./App&#39;;
import reportWebVitals from &#39;./reportWebVitals&#39;;
import { BrowserRouter } from &#39;react-router-dom&#39;;
import { HelmetProvider } from &#39;react-helmet-async&#39;;

const root = ReactDOM.createRoot(document.getElementById(&#39;root&#39;));
root.render(
  &lt;HelmetProvider&gt;
    &lt;BrowserRouter&gt;
    &lt;App /&gt;
    &lt;/BrowserRouter&gt;
    &lt;/HelmetProvider&gt;
);
reportWebVitals();
</code></pre>
<pre><code class="language-jsx">// App.jsx
import React, { useState } from &#39;react&#39;
import &#39;./App.css&#39;;
import { Helmet } from &#39;react-helmet&#39;;
import TempComponent from &#39;./pages/TempConponent&#39;;


function App() {
  const [isShow, setIsShow] = useState(false);

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;Helmet&gt;
        &lt;title&gt;app title&lt;/title&gt;
      &lt;/Helmet&gt;
      {isShow&amp;&amp;&lt;TempComponent /&gt;}
      &lt;button onClick={()=&gt; setIsShow(!isShow)}&gt;{isShow?&quot;close&quot;:&quot;show&quot;}&lt;/button&gt;

    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>위와 같이 사용하면 된다.
index.js에서 HelmetProvider를 사용하였고, App.jsx에서는 Helmet을 사용하여 페이지의 title을 변경할 수 있었다.</p>
<h1 id="react-snap">react-snap</h1>
<p>react-helmet-async를 통하여 각 페이지별 메타태그를 설정하는 방법을 알아보았다. 하지만 여전히 문제는 남아있다.</p>
<p>react-helmet-async를 사용한다해도 페이지별로 html 파일이 생성되는 것이 아니기 때문에 브라우저의 크롤러는 여전히 하나의 index.html을 바라보고있다.</p>
<blockquote>
<p>그렇기 때문에 url을 공유하였을 때 동적으로 변한 meta 태그가 보이지 않는 등의 문제가 발생한다.</p>
</blockquote>
<p>이 문제를 react-snap을 사용하여 해결할 수 있다.</p>
<p>react-snap을 사용하면 페이지별로 index.html 파일이 생성된다.</p>
<p>React를 사용하여 프로젝트를 한다면, react-snap과 react-helmet-async를 통해 SEO에 불리하다는 단점을 어느 정도는 극복할 수 있을 것이다.</p>
<p><strong>또한 react-snap은 pre-rendering을 지원한다.</strong></p>
<h2 id="📌-pre-rendering이란">📌 pre-rendering이란?</h2>
<blockquote>
<p>pre-rendering은 웹 크롤러가 볼 수 있도록 페이지의 모든 요소를 사전로드하는 프로세스를 의미한다.</p>
</blockquote>
<p>pre-rendering은 페이지 요청을 낚아채어 사용자가 크롤러인지 여부를 확인하여 크롤러인 경우 캐시 된 버전의 페이지를 전달하여 준다. 반대로 크롤러가 아니라면 일반적인 페이지를 전달해준다.</p>
<p>즉, 크롤러를 위한 최적화 작업을 하는 것이 pre-rendering의 핵심이라고 할 수 있다.</p>
<p><a href="https://ujeon.medium.com/react-react-snap%EC%9C%BC%EB%A1%9C-pre-rendered-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-70fa56816d75">https://ujeon.medium.com/react-react-snap%EC%9C%BC%EB%A1%9C-pre-rendered-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-70fa56816d75</a></p>
<p>하지만, pre-rendering은 server-side-rendering(SSR)과는 명백히 다르다.</p>
<p>react-snap을 이용한 pre-rendering은 단순히 빌드 시점에 렌더링된 화면을 마치 스크린샷을 찍듯이 크롤링한 것 뿐이기 때문에 어떠한 동작도 하지 않는다.</p>
<p>검색엔진이 봤을 때, &#39;이 사이트에는 이런 컨텐츠가 있구나&#39; 정도를 알려준다고 생각하면 된다.</p>
<p>SSR은 그 시점에 실제 유저에게 표시되는 정보를 서버에서 제공하는 것이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[웹 바이탈] LCP, FID, CLS]]></title>
            <link>https://velog.io/@apro_xo/%EC%9B%B9-%EB%B0%94%EC%9D%B4%ED%83%88-LCP-FID-CLS</link>
            <guid>https://velog.io/@apro_xo/%EC%9B%B9-%EB%B0%94%EC%9D%B4%ED%83%88-LCP-FID-CLS</guid>
            <pubDate>Tue, 13 Dec 2022 02:59:14 GMT</pubDate>
            <description><![CDATA[<h1 id="lcplargest-contentful-paint">LCP(Largest Contentful Paint)</h1>
<blockquote>
<p>LCP는 웹페이지의 로딩 속도에 대한 지표로 <code>&lt;main&gt;</code>이나 <code>&lt;section&gt;</code> 뿐만 아니라 제목, div, 양식 요소 등 텍스트 요소가 포함된 모든 HTML 요소들이 브라우저 화면에 렌더링 완료되는데까지 걸리는 시간의 길이이다.</p>
</blockquote>
<p>단순한 로딩 속도와는 조금 다르며, 스크롤 없이 볼 수 있는 부분을 로딩하는데 걸리는 시간을 의미한다.</p>
<p>대부분의 중요한 정보들은 스크롤 없이 볼 수 있는 부분에 담겨있기 때문이다.
따라서 스크롤 페이지 레이아웃을 개선함으로써 LCP를 향상시킬 수 있다.</p>
<p>📌LCP를 개선하기 위해서는 레이아웃을 조정하여 중요한 컨텐츠를 상단에 배치하는 것으로 해결할 수 있다. 동영상이나 고해상도 이미지 등은 다운로드 링크 형식으로 배치하는 것도 좋은 방법이나, 상황이 그렇지 못하다면 압축 등을 통해 용량을 낮추는 것도 방법이다.</p>
<h1 id="fidfirst-input-delay">FID(First Input Delay)</h1>
<blockquote>
<p>사용자가 페이지의 링크를 클릭하거나 버튼을 클릭하는 등의 상호작용을  처음했을 때 부터 브라우저가 이에 반응할 때 까지의 시간을 의미</p>
</blockquote>
<p>따라서 블로그와 같이 사용자와의 상호작용이 불필요한 웹의 경우 FID는 무의미하다.</p>
<p>모든 웹 사이트가 상호작용 요소를 넣지 않기 때문에 FID를 볼 때 95~99번째 백분위를 보는 것을 추천한다고 한다.</p>
<p>📌FID를 개선하기 위해서는 코드를 최소화하는 것이 중요하다.
타사코드의 영향을 줄이고, 불필요한 코드는 제거하는 것이 바람직하다.</p>
<p>다운로드 파일의 경우 사이즈를 최소하하는 것이 효과적이다.</p>
<h1 id="clscumulative-layout-shift">CLS(Cumulative Layout Shift)</h1>
<blockquote>
<p>CLS는 어떤 콘텐츠를 읽다가 링크를 클릭하려고 한 순간, 갑자기 광고나 다른 요소가 나오며 원하지 않은 요소를 클릭하는 실수를 방지하기 위한 척도</p>
</blockquote>
<p>📌CLS를 개선하기 위해서는 LCP와 비슷하게 시각적으로 안정적인 페이지 로딩이 될 수 있도록 레이아웃을 적절하게 배치하는 것이 바람직하다.</p>
<p>과도한 애니메이션은 레이아웃을 변경시키는 요소이기 때문에 변환 애니메이션을 최대한 제한하는 것이 좋다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 폰트와 웹 폰트 최적화]]></title>
            <link>https://velog.io/@apro_xo/%EC%9B%B9-%ED%8F%B0%ED%8A%B8%EC%99%80-%EC%9B%B9-%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@apro_xo/%EC%9B%B9-%ED%8F%B0%ED%8A%B8%EC%99%80-%EC%9B%B9-%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Thu, 08 Dec 2022 06:10:49 GMT</pubDate>
            <description><![CDATA[<h1 id="웹-폰트란">웹 폰트란?</h1>
<p>방문자의 로컬 컴퓨터의 폰트 설치 여부와 상관없이 온라인의 특정 서버에 위치한 폰트 파일을 다운로드하여 화면에 표시해주는 웹 전용 폰트이다.</p>
<h1 id="웹-폰트의-장단점">웹 폰트의 장/단점</h1>
<p>웹 폰트는 위에서 언급했듯이 방문자의 로컬 컴퓨터의 폰트 설치 여부와 상관없이 화면에 폰트를 보여줄 수 있다는 것이다.</p>
<p>그렇다면 단점은 뭘까❓</p>
<p>웹 폰트는 용량이 매우 무거우며 웹에서 폰트를 불러와서 사용하는 것이기 때문에 웹의 성능 저하를 불러온다. 또한 사용자의 네트워크 환경에 따라 시스템 폰트에서 웹 폰트로 변화는 과정이 사용자에게 보이는 현상이 발생할 수 있다. 이 경우, 좋은 사용자 경험을 제공할 수 없다.</p>
<p>따라서 우리는 <strong>웹 폰트 최적화</strong>를 통해 위의 속도 문제로 발생하는 단점들을 해결해야한다.</p>
<h1 id="웹-폰트로-인한-문제-사례">웹 폰트로 인한 문제 사례</h1>
<p>웹 폰트 최적화에 대해 검색하고 공부하면서 찾아본 레퍼런스에서 거의 대부분 다루던 사례이다.</p>
<p>아래는 2016년에 미국의 상원 의원인 Mitt Romney에 관한 기사인데, 웹 폰트가 로딩되기 전과 후의 화면을 비교한 그림이다.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/1610c4bb-a21b-489a-951b-b98868c984f1/image.png" alt=""></p>
<p><strong>Not</strong>을 강조하기 위해 Not에만 웹 폰트를 적용하였는데, 로딩이 늦어 정반대의 뜻이 되어버린 것이다.
Not이 없이 기사가 노출된 시간은 불과 몇 초이지만, 전혀 다른 내용이 사용자들에게 전달 되었다.
이 사건으로 인해 미국에서 웹 폰트가 이슈가 되었다고 한다.</p>
<h1 id="사례의-문제-원인">사례의 문제 원인</h1>
<p>위 사례의 원인을 파악하기 위해서는 브라우저의 렌더링 과정을 살펴보아야한다.
브라우저 렌더링 과정에 대해 전체적으로 다루지는 않을 것이다.</p>
<p>저번에 브라우저의 렌더링 과정에 대해 포스팅 했었는데 참고❗
<a href="https://velog.io/@apro_xo/WIL-7.17.7-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0">https://velog.io/@apro_xo/WIL-7.17.7-3%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</a></p>
<blockquote>
<p>📌 <strong>브라우저 파싱 단계에서 CSSOM을 생성할 때 웹 폰트 로딩을 시작한다.</strong>
📌 <strong>페인트 단계에서 웹 폰트 파일처럼 외부 링크로 연결된 파일의 다운로드가 완료되지 않았다면 해당 리소스를 사용하는 콘텐츠의 렌더링을 차단한다.</strong></p>
</blockquote>
<p>따라서 사용자의 네트워크 환경 및 속도, 웹 폰트의 용량, 웹 폰트가 적용된 텍스트가 보이지 않는 현상 때문에 발생한 문제라고 정의할 수 있다.</p>
<p>사용자의 네트워크는 제어가 불가능하므로 웹 폰트의 용량과 웹 폰트가 적용된 텍스트가 보이지 않는 현상을 최적화를 통해 완화하여 문제를 해결해보자.</p>
<h1 id="웹-폰트-최적화-방법">웹 폰트 최적화 방법</h1>
<h2 id="1-폰트-파일의-용량-줄이기">1. 폰트 파일의 용량 줄이기</h2>
<p>웹 폰트는 네트워크를 통해 웹에서 다운로드하는 리소스이기 때문에 폰트 파일의 크기가 클수록 다운로드 및 적용이 오래 걸린다. 따라서 폰트 파일의 용량을 최적화하여 완화할 수 있다.</p>
<h3 id="1-1-모던한-폰트-형식-사용">1-1. 모던한 폰트 형식 사용</h3>
<p>폰트의 형식은 매우 다양하다. 폰트의 형식마다 용량이 다르고, 브라우저가 지원하는 형식도 다 다르다는 것이 중요하다.</p>
<p>주로 4가지의 형식이 쓰이는데, 아래와 같다.</p>
<blockquote>
<p>👉 EOT: IE8 이하일 경우
👉 TTF: 구형 안드로이드 버전(4.4)에서 필요.
👉 WOFF: 대부분의 모던 브라우저에서 지원
👉 WOFF2: WOFF보다 압축률이 30% 정도 더 좋음</p>
</blockquote>
<p>아래는 브라우저별 폰트 지원 현황이다.
<img src="https://velog.velcdn.com/images/apro_xo/post/e4dc82d9-3964-4d99-9996-5e5c701c8283/image.png" alt=""></p>
<p>WOFF2의 경우, IE에서 아예 지원하지 않았는데, IE가 없어져서 편-안..👍</p>
<p>WOFF2는 압축률이 가장 좋아 용량이 제일 작다. 따라서 WOFF2를 사용하는 것이 가장 좋지만, 브라우저 버전에 따라 지원하지 않을 수도 있기 때문에 <code>Fallback font</code>를 적용하는 것이 바람직하다.</p>
<h4 id="📌-fallback-font-적용방법">📌 Fallback font 적용방법</h4>
<pre><code class="language-css">@font-face {
  font-family: &#39;NanumGothic&#39;;
  src: url(/fonts/NanumGothic-Regular.woff2) format(&quot;woff2&quot;), 
       url(/fonts/NanumGothic-Regular.woff) format(&quot;woff&quot;),
       url(/fonts/NanumGothic-Regular.ttf) format(&quot;truetype&quot;);
}</code></pre>
<p>사용자의 브라우저가 WOFF2 형식을 지원한다면 WOFF2를 사용하고, 지원하지 않는다면 WOFF를 사용, 또 지원하지 않는다면 TTF 형식을 사용한다. 이를 <strong>Fallback font</strong>라고 한다.</p>
<p>format()을 통해 파일 형식을 꼭 명시해주어야한다.</p>
<h3 id="1-2-서브셋-폰트">1-2. 서브셋 폰트</h3>
<blockquote>
<p>폰트 파일에서 불필요한 글자를 제거하고 사용할 글자만 남겨둔 폰트</p>
</blockquote>
<p>영어는 26개 알파벳으로 이루어져 있다. 영문 폰트는 대소문자 포함 총 72자가 필요하다.
하지만 한글은 자음, 모음의 조합으로 만들 수 있는 글자가 11,172자이다.
따라서 한글 폰트 파일은 영어 폰트 파일보다 용량이 크다.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/f0236fca-f1eb-4f8c-b58e-a1a53313ec04/image.png" alt=""></p>
<p>위 그림은 <a href="https://d2.naver.com/helloworld/4969726">네이버 기술 블로그</a>에서 가져온 그림인데, 11,172 글자를 사용하는 나눔바른고딕 폰트의 일부분이다.</p>
<p>노란색으로 표시된 글자들은 실생활에서 잘 사용하지 않는다.
이런 불필요한 글자를 제거한 것이 <strong>서브셋 폰트</strong>이다. 따라서 서브셋 폰트는 용량이 작다.</p>
<p>실제로 나눔바른고딕 폰트의 서브셋 폰트는 2,350자로, 기존 용량 2.4MB에서 586KB로 줄어들었다고 한다.</p>
<p>최근에 출시되는 한글 웹 폰트의 대부분이 2,350자의 서브셋 폰트이긴 하나 만약 아니라면 서브셋 폰트를 만들어서 사용할 수 있다.
서브셋 폰트는 <a href="https://opentype.jp/subsetfontmk.htm">サブセットフォントメーカー(이하 서브셋 폰트 메이커)</a>나 <a href="https://github.com/fonttools/fonttools">fontTools 라이브러리</a>를 사용해 만들 수 있다.</p>
<h2 id="2-불필요한-다운로드-막기">2. 불필요한 다운로드 막기</h2>
<h3 id="2-1-unicode-range">2-1. unicode-range</h3>
<pre><code class="language-css">@font-face {
  font-family: NanumFont;
  src: url(NanumPenWeb.woff2) format(&#39;woff2&#39;);
  unicode-range: U+BC14, U+CC28;
}</code></pre>
<p><code>U+BC14</code>는 &#39;바&#39;, <code>U+CC28</code>는 &#39;차&#39;에 해당하는 유니코드다. 따라서 전체 텍스트에서 &#39;바&#39;, &#39;차&#39;에만 폰트가 적용이 된다.</p>
<p>unicode-range 속성을 사용하면 필요한 텍스트에만 폰트를 적용할 수 있고, 텍스트가 없으면 웹에 다운로드를 요청하지 않는다. 불필요한 다운로드를 막을 수 있다.</p>
<h3 id="2-2-local">2-2. local</h3>
<p>CSS에 폰트를 선언하면 시스템에 폰트 유무와 관계없이 무조건 폰트를 다운로드하게 된다.</p>
<p>local을 사용하면 시스템에 폰트가 설치되어 있다면 불필요하게 다운로드를 하지 않는다.</p>
<pre><code class="language-css">src: local(&#39;Nanum-Gothic&#39;), 
     url(/fonts/NanumGothic-Regular.woff2) format(&quot;woff2&quot;), 
     url(/fonts/NanumGothic-Regular.woff) format(&quot;woff&quot;);</code></pre>
<h2 id="3-텍스트가-항상-보이게-하기">3. 텍스트가 항상 보이게 하기</h2>
<h3 id="3-1-브라우저의-렌더링-차단-처리-방식foit-vs-fout">3-1. 브라우저의 렌더링 차단 처리 방식(FOIT vs FOUT)</h3>
<p>우선 IE 계열 브라우저는 FOUT(Flash Of Unstyled Text) 방식으로 렌더링 차단을 처리하고, 그 외의 브라우저는 FOIT(Flash Of Invisible Text) 방식으로 렌더링 차단을 처리한다.</p>
<p>웹 폰트가 적용될 때는 텍스트의 번쩍임(flash of text)이 일어난다.</p>
<blockquote>
<p>웹 폰트가 적용되지 않은 폴백 폰트 상태에서 폰트가 바뀌면서 텍스트 번쩍임이 일어나는 것이 <strong>FOUT</strong>이고, 웹 폰트가 적용되지 않은 텍스트가 보이지 않는 상태에서 폰트가 바뀌면서 텍스트 번쩍임이 일어나는 것이 <strong>FOIT</strong>이다.</p>
</blockquote>
<p><strong><em>앞서 다뤘던 사례에서는 FOIT 방식으로 인해 웹 폰트가 적용되기 전의 텍스트가 렌더링 되지 않았던 것이다.</em></strong></p>
<h4 id="📌-fout-방식의-문제점은-없을까">📌 FOUT 방식의 문제점은 없을까?</h4>
<p>FOUT 방식은 fallback font나 system font를 먼저 보여주고, web font가 다운로드가 완료되면 웹 폰트를 적용시켜 보여준다.
이 과정에서 system font와 web font는 자간, 높이 등이 다르기 때문에 레이아웃이 깨질 가능성이 높다.</p>
<p><strong>따라서 웹 폰트가 다운로드가 완료되고 적용되면 브라우저는 reflow를 통해 레이아웃을 다시 계산해야한다.</strong></p>
<p><strong>또한 좋은 사용자 경험을 제공할 수 없다.</strong></p>
<p>따라서 font-size-adjust 속성이나 letter-spacing, line-height 등을 조정하여 최대한 적게 변하도록 수정해야한다.</p>
<h3 id="3-2-fout-방식을-사용한-최적화">3-2. FOUT 방식을 사용한 최적화</h3>
<p>IE 계열의 브라우저를 제외하고 Chorme이나 safari 같은 경우에는 FOIT 방식을 채택하고 있기 때문에 웹 폰트가 적용되지 않으면 텍스트가 보이지 않는다.
따라서 FOUT 방식으로 동작하여 텍스트가 노출되도록 최적화를 진행하는 방법이다.</p>
<h4 id="📌-preload-옵션으로-먼저-로딩">📌 preload 옵션으로 먼저 로딩</h4>
<p><code>&lt;link&gt;</code>태그의 rel 속성에 preload 옵션을 사용하면 해당 리소스를 다른 리소스보다 빨리 로딩한다.
주로 폰트파일, 이미지 파일, 스크립트 파일, 비디오 파일 등 페이지에서 중요도가 높은 자원을 의도적으로 먼저 로딩할 때 사용하는 옵션이다.</p>
<pre><code class="language-html">&lt;link rel=&quot;preload&quot; href=&quot;./nanumGothic.woff2&quot; as=&quot;font&quot; type=&quot;font/woff2&quot; 
      crossorigin=&quot;anonymous&quot;&gt;</code></pre>
<p>웹 폰트 파일에 preload를 사용하면 CSS파일보다 먼저 웹 폰트 파일 다운로드를 시작한다.
<img src="https://velog.velcdn.com/images/apro_xo/post/5fc7b3c9-8ac2-404d-96e1-e305ea3c4d2e/image.png" alt=""></p>
<p>preload가 적용되는 브라우저들을 잘 확인하고 사용해야 한다.</p>
<h4 id="📌-font-display">📌 font-display</h4>
<p>@font-face에 font-display 속성을 추가한다.</p>
<p>속성 값에는 auto, block, swap, fallback, optional의 5가지 옵션이 있다.</p>
<p><strong>👉 block</strong>
block은 FOIT와 동일하게 작동하는 옵션이다. 웹 폰트가 로딩되지 않았을 때는 텍스트를 렌더링하지 않는다. 로딩이 완료되면 적용한다.</p>
<p><strong>👉 swap</strong>
swap은 FOUT과 동일하게 작동하는 옵션이다. 우선 fallback, system font로 텍스트를 렌더링하고 폰트 로딩이 완료되면 적용한다. 항상 텍스트가 보이게 된다.</p>
<p><strong>👉 fallback</strong>
fallback은 우선 100ms 동안 텍스트가 보이지 않고, 그 후 fallback font로 렌더링한다.
약 2초의 전환 시간이 있는데, 이 시간 안에 로딩이 완료되면 웹 폰트로 전환한다.
하지만 이 시간이 지나면 웹 폰트 다운로드가 완료되어도 웹 폰트로 전환하지 않고 fallback font를 유지한다.</p>
<p><strong>👉 optional</strong>
optional은 우선 100ms동안 텍스트가 보이지 않고, 그 후 fallback font로 전환한다.
웹 폰트를 다운로드 하지만, 브라우저가 네트워크 상태를 파악해 웹 폰트 전환 여부를 결정한다.</p>
<p>예를 들어 네트워크 연결 상태가 안 좋으면 웹 폰트가 다운로드가 완료되어도 캐시에 저장만 하고 전환은 하지 않는다.</p>
<h1 id="lighthouse">lighthouse</h1>
<p>웹 폰트 최적화를 하지 않은 프로젝트를 lighthouse로 성능 측정을 해보았다.
<img src="https://velog.velcdn.com/images/apro_xo/post/91698544-cbf3-4638-a47f-ccafe6e8127b/image.png" alt=""></p>
<p>위와 같은 문구를 볼 수 있다.
웹 폰트가 로드 되는 동안 텍스트가 보이도록 하라는 것인데, 이렇게 lighthouse에서도 웹 폰트 최적화를 하라고 알려준다.</p>
<pre><code class="language-ts">import { createGlobalStyle } from &#39;styled-components&#39;;

const GlobalStyle = createGlobalStyle`
    * {
        margin:0;
        padding:0;
        font-family: Cafe24ClassicType-Regular;
    }
    @font-face {
        font-family: &#39;Cafe24ClassicType-Regular&#39;;
        src: url(&#39;https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_2210-2@1.0/Cafe24ClassicType-Regular.woff2&#39;) format(&#39;woff2&#39;);
        font-weight: normal;
        font-style: normal;
        font-display:swap;
    }
`;

export default GlobalStyle;
</code></pre>
<p>위와 같이 font-display에 swap 속성 값을 주었더니 lighthouse에서 더 이상 폰트 최적화에 관한 알림이 뜨지 않았으며, 퍼포먼스 점수 또한 올라갔다.</p>
<p><strong>참고 :</strong> 
<a href="https://d2.naver.com/helloworld/4969726">https://d2.naver.com/helloworld/4969726</a>
<a href="https://whales.tistory.com/66">https://whales.tistory.com/66</a>
<a href="https://velog.io/@vnthf/%EC%9B%B9%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%98%EA%B8%B0">https://velog.io/@vnthf/%EC%9B%B9%ED%8F%B0%ED%8A%B8-%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%98%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] Error Boundary(에러 바운더리)]]></title>
            <link>https://velog.io/@apro_xo/React-Error-Boundary</link>
            <guid>https://velog.io/@apro_xo/React-Error-Boundary</guid>
            <pubDate>Tue, 06 Dec 2022 01:06:44 GMT</pubDate>
            <description><![CDATA[<h1 id="error-boundary">Error Boundary?</h1>
<p>에러 바운더리(에러 경계)는 리액트를 사용하여 구현할 시 사용되는 컴포넌트 에러를 핸들링하는 방법이다.</p>
<p>리액트 컴포넌트 함수에서 반환되는 jsx, 또는 render() 함수에서 반환되는 jsx를 렌더링 하는 도중 에러를 만나면 컴포넌트 렌더링을 멈춰버리며, 사용자는 빈 화면을 보게 된다.</p>
<p>이를 리액트 공식문서에서는 컴포넌트가 깨진다고 표현을 한다.</p>
<p>에러로 인해 컴포넌트가 깨지는 경우 대체 컴포넌트(fallback component)를 보여주도록 하는 것이 Error Boundary이다.</p>
<h1 id="error-boundary-사용법">Error Boundary 사용법</h1>
<blockquote>
<p>Error Boundary는 클래스형 컴포넌트에서만 사용 가능하다.
함수형 컴포넌트에서 사용하기 위해서는 react-error-boundary 패키지를 설치하여 사용한다.</p>
</blockquote>
<h2 id="1-클래스형-컴포넌트">1. 클래스형 컴포넌트</h2>
<p>기본적으로 error boundary는 생명주기 함수인 getDerivedStateFromError()와 componentDidCatch()를 정의한 클래스형 컴포넌트를 만들어야 한다.
둘 중 하나를 정의해서 만든 클래스형 컴포넌트가 에러 경계가 되어 동작한다.</p>
<p>getDerivedStateFromError()를 사용해서 fallback component를 보여주고, componentDidCatch()를 사용해서 에러 내용을 기록하는 형태로 사용하면 된다.</p>
<h2 id="2-함수형-컴포넌트🔥">2. 함수형 컴포넌트🔥</h2>
<h3 id="2-1-패키지-설치">2-1. 패키지 설치</h3>
<blockquote>
<p>npm install react-error-boundary</p>
</blockquote>
<h3 id="2-2-react-error-boundary-사용하기">2-2. react-error-boundary 사용하기</h3>
<pre><code class="language-jsx">// Person.jsx
const Person = () =&gt; {
  const person_name = &quot;철수&quot;;

  if (person_name !== &quot;영희&quot;) {
    const new_error = new Error(&quot;영희가 아니에요!&quot;);
    new_error.name = &quot;이름이 달라요&quot;;
    new_error.person_name = person_name;

    throw new_error;
  }

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

export default Person;
</code></pre>
<pre><code class="language-jsx">// app.jsx
import Person from &#39;./Person&#39;;
import {ErrorBoundary} from &#39;react-error-boundary&#39;;

const ErrorFallback = () =&gt; {
  return(&lt;div&gt;에러났어요@@&lt;/div&gt;)
}

function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;ErrorBoundary FallbackComponent={ErrorFallback}&gt;
      &lt;Person /&gt;
      &lt;/ErrorBoundary&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>person.jsx라는 임의의 컴포넌트를 만들었다.
커스텀 에러를 사용하여 고의적으로 에러를 발생시켜 보았다.</p>
<p>app.jsx에서는 컴포넌트가 깨졌을 때 대신 보여줄 fallback component인 ErrorFallback 컴포넌트를 만들고 App 컴포넌트에서 사용하였다.</p>
<p>FallbackComponent 속성에 만들었던 fallback component를 넣어주어 사용하며 정확한 코드 사용법은 위 코드를 참고하자.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/d985e847-e887-4a50-abc2-35fad18cca6d/image.png" alt=""></p>
<p>코드를 실행하면 정상적으로 fallback component가 보여지는 것을 알 수 있다.</p>
<h3 id="2-3-에러-로깅하기">2-3. 에러 로깅하기</h3>
<p>클래스형 컴포넌트에서 에러를 로깅하기 위해서는 생명 주기 함수인 <code>componentDidCatch()</code>를 사용해야 하는데,
react-error-boundary 패키지를 사용할 때는 어떻게 로깅해야할까?</p>
<p>만들었던 fallback component에서 에러를 로깅할 수 있다. 아래와 같이 사용한다.</p>
<pre><code class="language-jsx">import Person from &#39;./Person&#39;;
import {ErrorBoundary} from &#39;react-error-boundary&#39;;


// 에러 로깅 가능
const ErrorFallback = (err) =&gt; {

  console.log(err.error.name);
  console.log(err.error.person_name);
  return(&lt;div&gt;에러났어요@@&lt;/div&gt;)
}

function App() {

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;ErrorBoundary FallbackComponent={ErrorFallback}&gt;
      &lt;Person /&gt;
      &lt;/ErrorBoundary&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<p>fallback component에서 error 객체를 받고 내부의 error 속성으로 에러 데이터에 접근할 수 있다.</p>
<p>person.jsx에서 커스텀 에러를 만들었는데, name과 person_name 속성을 직접 커스텀하였었다.</p>
<pre><code class="language-jsx">// person.jsx
if (person_name !== &quot;영희&quot;) {
    const new_error = new Error(&quot;영희가 아니에요!&quot;);
    new_error.name = &quot;이름이 달라요&quot;;
    new_error.person_name = person_name;

    throw new_error;
  }</code></pre>
<p>위 코드를 실행한 후 개발자 도구를 열어보면 정상적으로 에러에 포함된 데이터가 출력됨을 알 수 있다.
<img src="https://velog.velcdn.com/images/apro_xo/post/67fe1181-af2f-4ff0-aafe-5ed621ed6542/image.png" alt=""></p>
<h1 id="error-boundary가-잡지-못하는-에러">Error Boundary가 잡지 못하는 에러</h1>
<p>공식 문서에서는 아래와 같이 명시했다.
<a href="https://reactjs.org/docs/error-boundaries.htmlhttps://reactjs.org/docs/error-boundaries.html">https://reactjs.org/docs/error-boundaries.htmlhttps://reactjs.org/docs/error-boundaries.html</a>
<img src="https://velog.velcdn.com/images/apro_xo/post/ce5157c7-44a5-42e7-8c8e-085fd9984fad/image.png" alt=""></p>
<p>이벤트 핸들러에서 에러 처리를 할 때는 try-catch문을 사용하여 에러를 처리하는 것이 좋다고 문서에 명시 되어있다.</p>
<p>그렇다.</p>
<h1 id="useerrorhandler">useErrorHandler()</h1>
<p>위에 명시된 경우와 같이 에러 바운더리가 감지하지 못하는 에러는 try/catch 문 등을 사용하여 직접 핸들링해야 하는데,
react-error-boundary에서는 이를 쉽게 해주는 <strong>useErrorHandler</strong> 기능을 제공한다.</p>
<p>나는 fetch()를 이용한 비동기 코드로 테스트해봤다.</p>
<p>우선, data 디렉토리를 만들고 그 안에 data.json을 만들어 임의의 데이터를 생성하였다.</p>
<pre><code class="language-json">{
    &quot;person&quot;:[
    {
        &quot;name&quot;:&quot;철수&quot;,
        &quot;age&quot;:56,
        &quot;gender&quot;:&quot;male&quot;
    },
    {
        &quot;name&quot;:&quot;영희&quot;,
        &quot;age&quot;:56,
        &quot;gender&quot;:&quot;male&quot;
    }
  ]
}</code></pre>
<p>그리고 이 데이터를 fetch()를 통해 받아오는 코드를 Person.jsx의 useEffect에 작성하였는데,
에러를 고의로 발생시키기 위해 경로에 오타를 내보았다.</p>
<pre><code class="language-jsx">// App.js
import Person from &#39;./Person&#39;;
import {ErrorBoundary} from &#39;react-error-boundary&#39;;

const ErrorFallback = (err) =&gt; {

  console.log(err.error.name);
  console.log(err.error.person_name);
  return(&lt;div&gt;에러났어요@@&lt;/div&gt;)
}

function App() {

  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;ErrorBoundary FallbackComponent={ErrorFallback}&gt;
      &lt;Person /&gt;
      &lt;/ErrorBoundary&gt;
    &lt;/div&gt;
  );
}

export default App;</code></pre>
<pre><code class="language-jsx">// Person.jsx
import React, { useEffect, useState } from &quot;react&quot;;
import { useErrorHandler } from &#39;react-error-boundary&#39;;

const Person = () =&gt; {
  const person_name = &quot;영희&quot;;
  const handleError = useErrorHandler();

  useEffect(() =&gt; {
    const getPersonInfo = async () =&gt; {
      try {
        const res = await fetch(&quot;pata.json&quot;, {
          headers: {
            Accept: &quot;application/json&quot;,
          },
          method: &quot;GET&quot;,
        });
        const data = await res.json()
        console.log(data);
      }
      catch(err) {
        handleError(err);
      }
    };

    getPersonInfo();
  }, []);

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

export default Person;</code></pre>
<p>위 코드를 실행해보면 에러 바운더리가 비동기 코드임에도 불구하고 에러를 잘 잡아내는 것을 확인 할 수 있다. fallback UI도 잘 뜬다.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/fdd184de-6c0c-4c7e-a3b0-04ff1300ef26/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Babel] 바벨 속성으로 이해하기]]></title>
            <link>https://velog.io/@apro_xo/Babel-%EB%B0%94%EB%B2%A8-%EC%86%8D%EC%84%B1%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@apro_xo/Babel-%EB%B0%94%EB%B2%A8-%EC%86%8D%EC%84%B1%EC%9C%BC%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 05 Dec 2022 02:44:26 GMT</pubDate>
            <description><![CDATA[<h1 id="바벨이-궁금했다">바벨이 궁금했다..</h1>
<p>이전 포스팅에서 리액트 프로젝트에 RTL, Jest를 적용시켜 보면서 바벨 설정을 하였는데, 공식문서와 블로그 등 여러가지 레퍼런스들을 참고하여 설정을 하였다.</p>
<p>.babelrc 또는 babel.config.js를 설정하면서도 왜 이렇게 설정하는지 모르니까 잘 와닿지 않았고 바벨 설정에 대해서 궁금해졌다.</p>
<p>그래서 속성으로 공부해보고 알게 된 것들을 기록한다.</p>
<h1 id="바벨이란">바벨이란?</h1>
<p>바벨은 프론트엔드 개발자라면 한 번씩은 꼭 들어봤을 것이다.</p>
<blockquote>
<p>바벨은 크로스 브라우징을 위한 트랜스파일러다.</p>
</blockquote>
<p>즉, ES6 이상의 문법을 지원하지 않는 브라우저에서도 ES6 문법으로 만들어진 코드를 실행할 수 있도록 바벨이 코드를 이전 버전(ES5)의 코드로 바꿔준다는 것이다.</p>
<h1 id="프로젝트-설정">프로젝트 설정</h1>
<blockquote>
<p>npm install --save-dev @babel/core @babel/cli</p>
</blockquote>
<p>위의 npm 명령어를 실행하여 필요한 디펜던시를 추가한다.</p>
<p>@babel/core는 바벨을 사용하기 위해서는 필수로 설치해야하는 패키지이며
@babel/cli는 바벨 명령어를 터미널에서 실행하기 위해 필요하다.</p>
<h1 id="babel-무지성-실행하기">Babel 무지성 실행하기</h1>
<pre><code class="language-js">// index.js
const hello = (a, b) =&gt; a + b;</code></pre>
<p>index.js를 생성하고 위와 같은 화살표 함수를 작성한다.</p>
<p>화살표 함수는 대표적인 ES6 문법이다. 이것을 ES5 문법, 즉 function을 사용한 코드로 변경해보자.</p>
<blockquote>
<p>npx babel ./index.js --out-dir dist</p>
</blockquote>
<p>위 명령어를 실행하면 dist 디렉토리가 생성되고 그 안에 Babel로 인해 변경된 index.js가 생성된다.</p>
<p><strong>결과를 보면 아래와 같이 변화가 없다.</strong>😂
<img src="https://velog.velcdn.com/images/apro_xo/post/022cf0b4-5342-4446-951a-ec938105852b/image.png" alt=""></p>
<h1 id="babel-plugin">Babel Plugin</h1>
<p>위에서 babel을 무지성 실행해봤지만 아무 일도 일어나지 않았다.
Plugin을 추가해주어야 Babel이 동작한다.</p>
<blockquote>
<p>Plugin이 없다면 Babel은 아무것도 할 수 없다.</p>
</blockquote>
<p>플러그인을 설치해보자.</p>
<blockquote>
<p>npm install --save-dev @babel/plugin-transform-arrow-functions</p>
</blockquote>
<p>화살표 함수를 변경해주는 플러그인이라는 것을 한 번에 알 수 있는 이름이다.</p>
<h1 id="babelconfigjson">babel.config.json</h1>
<pre><code class="language-json">{
    &quot;plugins&quot;: [&quot;@babel/plugin-transform-arrow-functions&quot;]
}</code></pre>
<p>위와 같이 설치했던 <code>@babel/plugin-transform-arrow-functions</code>를 사용하겠다고 babel.config.json에 추가한다.</p>
<p>그리고 <code>npx babel ./index.js --out-dir dist</code>를 실행해보면 index.js에서 화살표 함수로 작성되었던 hello 함수가 function 키워드가 사용된 함수로 변경됨을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/92ae806e-bfa1-4822-86ef-3cedbace7370/image.png" alt=""></p>
<p>이렇게 필요한 플러그인들을 설치하고 babel.config.json에 포함시켜 사용하면 된다.</p>
<p>플러그인 리스트를 보고싶다면 Babel 공식문서에 들어가서 볼 수 있다.
<a href="https://babeljs.io/docs/en/plugins-list">https://babeljs.io/docs/en/plugins-list</a></p>
<h1 id="불편한-점">불편한 점</h1>
<p>ES6 문법에는 화살표함수만 있는 것이 아니다.
class도 사용할 수 있고, export/import 등 이전 문법에는 없는 것들이 많다.</p>
<p>그럼 이것을 다 사용하고 싶으면 일일이 다 추가하고 설정해주어야 할까?
굉장히 번거로울 것 같다.</p>
<h1 id="preset">preset</h1>
<p>위의 불편한 점을 개선하기 위해 preset이라는 것을 사용한다.</p>
<blockquote>
<p>preset이란 필요한 Plugin들을 번들링하여 모아둔 집합체라고 보면 되겠다.</p>
</blockquote>
<p>예를 들어 preset 하나만 설치하면 ES6 문법을 일일이 다 plugin으로 설치/설정 하지 않아도 된다는 것이다.</p>
<p>대표적인 preset인 preset-env를 설치해보자.</p>
<blockquote>
<p>npm install --save-dev @babel/preset-env</p>
</blockquote>
<p>그리고 preset을 babel.config.json에 포함시켜보자.
기존의 plugin 설정은 삭제한다.</p>
<pre><code class="language-json">{
    &quot;presets&quot;: [&quot;@babel/preset-env&quot;]
}</code></pre>
<p>그리고 바벨을 실행해보면 정상적으로 문법이 변경 되었음을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/b8fc06fe-3f9a-417a-82a5-9dfa7d8c5c0a/image.png" alt=""></p>
<p>이렇게 plugin을 일일이 설치하지 않고도 preset을 이용하여 간편하게 babel을 이용할 수 있다는 것을 알 수 있다.</p>
<p>공식 preset은 아래와 같다.</p>
<pre><code>@babel/preset-env
@babel/preset-react
@babel/preset-typescript
@babel/preset-flow</code></pre><h1 id="preset의-단점은-없을까">preset의 단점은 없을까?</h1>
<p>필요하지 않을 수 있는 plugin 까지 preset이 다 포함할 수 있기 때문에 이 경우에는 불필요하게 번들 파일이 커진다는 단점이 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[RTL] React Testing Library 도입기(CRA + typescript) FEAT. Enzyme ]]></title>
            <link>https://velog.io/@apro_xo/RTL-React-Testing-Library-%EB%8F%84%EC%9E%85%EA%B8%B0CRA-typescript-Enzyme</link>
            <guid>https://velog.io/@apro_xo/RTL-React-Testing-Library-%EB%8F%84%EC%9E%85%EA%B8%B0CRA-typescript-Enzyme</guid>
            <pubDate>Sun, 04 Dec 2022 08:56:24 GMT</pubDate>
            <description><![CDATA[<h1 id="enzyme-vs-react-testing-library">Enzyme vs React Testing Library</h1>
<p><code>React Testing Library</code>는 Jest를 기반으로 만들어진 테스팅 라이브러리이다.
CRA를 통해 프로젝트를 만든다면, 별도의 설치 없이 바로 사용할 수 있다.
Jest 기반이기 때문에 Jest 또한 설치할 필요가 없다.</p>
<p>React Testing Library가 각광 받기 이전에는 <code>Enzyme</code>을 주로 사용했다고 한다. Enzyme과 RTL의 차이가 뭘까?</p>
<h2 id="1-구현-주도-vs-행위-주도🔥">1. 구현 주도 vs 행위 주도🔥</h2>
<pre><code class="language-jsx">import React from &#39;react&#39;;

const Temp = () =&gt; {
  return(
    &lt;h1&gt;안녕하세요&lt;/h1&gt;
  )
}</code></pre>
<p>위의 컴포넌트를 테스트 한다고 가정하자.</p>
<p><code>Enzyme</code>은 렌더링 된 <code>&lt;h1&gt;안녕하세요&lt;/h1&gt;</code>가 h1 태그로 이루어진게 맞는지를 테스트 한다. 개발자 관점에서 테스트를 진행한다.</p>
<p>그리고 이를 <strong>구현 주도 테스트(Implementation Driven Test)</strong>라고 한다.</p>
<p>반면, <code>RTL</code>은 h1태그로 이루어진게 맞는지를 보는 것이 아니라 안녕하세요 라는 텍스트가 화면에 제대로 출력이 되었는지를 테스트 한다. 사용자 관점에서 테스트를 진행한다고 보면 되겠다.</p>
<p>이를 <strong>행위 주도 테스트(Behavior Driven Test)</strong>라고 한다.</p>
<p>사용자는 안녕하세요라는 텍스트가 div 태그로 이루어졌는지, h1 태그로 이루어졌는지 알 필요가 있을까?  없다❌</p>
<p>안녕하세요라는 텍스트가 사용자의 화면에 정상적으로 출력이 되었는지의 여부가 더 중요하다. RTL은 사용자 친화적인 테스트를 진행한다.</p>
<h1 id="프로젝트에-rtl-도입">프로젝트에 RTL 도입</h1>
<p>도입하면서 겪었던 에러, 해결한 방법 등의 기록이다.</p>
<h2 id="1-typescript-설정">1. typescript 설정</h2>
<h3 id="1-1-babel-설정">1-1. babel 설정</h3>
<p>참고: <a href="https://jestjs.io/docs/getting-started">https://jestjs.io/docs/getting-started</a></p>
<blockquote>
<p>npm install --save-dev babel-jest @babel/core @babel/preset-env @babel/preset-react</p>
</blockquote>
<p>npm으로 위의 목록들을 설치한다.</p>
<h3 id="1-2-babelconfigjson">1-2. babel.config.json</h3>
<pre><code class="language-json">{
    &quot;presets&quot;: [[&quot;@babel/preset-env&quot;],
                &quot;@babel/preset-react&quot;]
}</code></pre>
<p>babel.config.json에 위의 코드를 추가한다.</p>
<h3 id="1-3-typescript-babel-설정">1-3. typescript babel 설정</h3>
<blockquote>
<p>npm install --save-dev @babel/preset-typescript</p>
</blockquote>
<p>위의 명령어를 입력하여 디펜던시에 추가한 후 babel.config.json을 아래와 같이 수정한다.</p>
<pre><code class="language-json">{
    &quot;presets&quot;: [[&quot;@babel/preset-env&quot;],
                &quot;@babel/preset-react&quot;,
                &quot;@babel/preset-typescript&quot;]
}
</code></pre>
<h2 id="2-memoryrouter">2. MemoryRouter</h2>
<pre><code class="language-js">import { render } from &#39;@testing-library/react&#39;;
import App from &#39;../App&#39;;
describe(&#39;테스트코드 테스트&#39;, () =&gt; {
    test(&#39;렌더링 테스트&#39;, () =&gt; {
        render(
          &lt;App /&gt;
        ).debug();
    });
});</code></pre>
<pre><code class="language-js">// app.tsx
(...생략)
return (
  (...생략)
  &lt;Routes&gt;
    &lt;Route path=&quot;/&quot; element={&lt;Navigate to=&quot;/viewer/topheadline&quot; /&gt;} /&gt;
      &lt;Route path=&quot;/viewer/topheadline&quot; element={&lt;Viewer /&gt;} /&gt;
    &lt;Route path=&quot;/viewer/:category&quot; element={&lt;Viewer /&gt;} /&gt;
    &lt;Route path=&quot;/favorite/viewer&quot; element={&lt;FavoriteViewer /&gt;} /&gt;
   &lt;/Routes&gt;
    );
(...생략)
};
(...생략)</code></pre>
<p>app.tsx가 렌더링이 잘 되는지 간단하게 테스트를 해보기 위해 테스트 코드를 작성하였다. app.tsx는 위의 코드를 참고하면 된다.</p>
<p>app.tsx를 보면, react-router-dom을 사용하여 페이지별로 라우팅을 하고 있다.</p>
<p>테스트를 돌려보면 엄청나게 긴 에러가 발생한다.</p>
<p>우리가 react-router-dom을 사용하기 위해 index.tsx에서 BrowserRouter로 App 컴포넌트를 감싼 후 사용하는데, App 컴포넌트 렌더링을 <strong>테스트</strong>할 때는 브라우저 환경을 사용하여 테스트를 하는 것이 아니기 때문에 react-router-dom에서 제공하는 <strong>MemoryRouter</strong>를 사용하여 감싸주어야한다.</p>
<p>아래와 같이 테스트 코드를 수정하면 된다.</p>
<pre><code class="language-tsx">import { render } from &#39;@testing-library/react&#39;;
import { MemoryRouter } from &#39;react-router-dom&#39;;
describe(&#39;테스트코드 테스트&#39;, () =&gt; {
    test(&#39;렌더링 테스트&#39;, () =&gt; {
        render(
            &lt;MemoryRouter&gt;
              &lt;App /&gt;
            &lt;/MemoryRouter&gt;
        ).debug();
    });
});</code></pre>
<h2 id="3-redux-provider">3. redux Provider</h2>
<p>RTL을 적용시킨 프로젝트는 redux toolkit을 사용하고 있는데, 테스트를 진행하니 Provider로 감싸주어야한다는 에러가 발생했다.
<img src="https://velog.velcdn.com/images/apro_xo/post/fcfefa0e-1590-40f5-bb55-1927ab3efc9f/image.png" alt=""></p>
<p>그래서 테스트 코드에서도 index.tsx와 동일하게 Provider를 사용하여 감싸주었더니 해결되었다.</p>
<pre><code class="language-jsx">import { render } from &#39;@testing-library/react&#39;;
import { MemoryRouter } from &#39;react-router-dom&#39;;
import { Provider } from &#39;react-redux&#39;;
import store from &#39;../redux/configStore&#39;;

describe(&#39;테스트코드 테스트&#39;, () =&gt; {
    test(&#39;렌더링 테스트&#39;, () =&gt; {
        render(
            &lt;MemoryRouter&gt;
                &lt;Provider store={store}&gt;
                    &lt;App /&gt;
                &lt;/Provider&gt;
            &lt;/MemoryRouter&gt;
        ).debug();
    });
});</code></pre>
<h2 id="4-axios-import-설정">4. axios import 설정</h2>
<p>App 컴포넌트를 테스트 코드에서 렌더링하는 과정에서 아래와 같은 에러가 발생했다.
<img src="https://velog.velcdn.com/images/apro_xo/post/a7eed3be-8482-4c02-a734-73eecbb08b34/image.png" alt=""></p>
<p>axios를 import하는 과정에서 ES6 문법인 import를 사용할 수 없다는 것이다.</p>
<p>기본적으로 Jest는 import/export를 사용할 수 없고 require를 통해 모듈을 import해야한다. ES6모듈을 지원하지 않기 때문이다.</p>
<p>그래서 Jest에서 import/export를 사용하기 위해 알아보고 수정하였지만 해결하지 못하였다.</p>
<p>이 과정에서 이상한 점을 발견했다. 다른 모듈을 import/export를 사용하는 것은 에러 없이 테스트가 통과되는데, axios를 import 하는 것에서만 저런 에러가 발생하였다.</p>
<p>방향을 바꿔 알아본 결과 스택오버플로우를 통해 해결할 수 있었다.</p>
<pre><code class="language-js">// package.json
(...생략)
&quot;jest&quot;: {
  &quot;moduleNameMapper&quot;: {
    &quot;axios&quot;: &quot;axios/dist/node/axios.cjs&quot;
  }
}</code></pre>
<p>위 코드를 package.json에 추가하여 사용하니 axios에 대한 import 오류가 더 이상 발생하지 않았다.</p>
<p>stackoverflow: <a href="https://stackoverflow.com/questions/73958968/cannot-use-import-statement-outside-a-module-with-axios">https://stackoverflow.com/questions/73958968/cannot-use-import-statement-outside-a-module-with-axios</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Jest] 비동기 코드 테스트하기]]></title>
            <link>https://velog.io/@apro_xo/Jest-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%BD%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@apro_xo/Jest-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%BD%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 29 Nov 2022 00:36:40 GMT</pubDate>
            <description><![CDATA[<h1 id="비동기-코드-테스트하는-방법">비동기 코드 테스트하는 방법</h1>
<h2 id="1-callback">1. callback</h2>
<h3 id="1-1-잘못된-사용">1-1. 잘못된 사용</h3>
<pre><code class="language-js">// fn.js
const fn = {
    getName:(callback) =&gt; {
        const name = &quot;mike&quot;;
        setTimeout(()=&gt; {
            callback(name);
        }, 3000)
    }
}

module.exports = fn;</code></pre>
<pre><code class="language-js">// fn.test.js
test(&quot;3초 후에 받아온 이름은 Mike&quot;, ()=&gt; {
    function callback(name) {
        expect(name).toBe(&quot;mike&quot;);
    }
    fn.getName(callback);
})</code></pre>
<p>위의 코드를 실행해보면 테스트를 통과한다. 겉으로 보기에는 문제가 없어 보이지만, 분명 setTimeout()을 실행하여 3초 후 어떠한 동작을 하는 기능을 하는 비동기 함수를 테스트하는데, 테스트 통과까지 걸리는 시간은 3초를 넘기지 않는다. 비동기 동작이 제대로 수행되지 않는다는 것이다.</p>
<blockquote>
<p>Jest는 실행하다가 테스트 코드의 끝에 도달하면 그대로 종료한다.</p>
</blockquote>
<p>callback이 실행되지 않고 그대로 끝나버려서 3초가 걸리지 않은 것이다.</p>
<p><strong>이러한 현상을 해결하기 위해 done을 사용한다.</strong></p>
<h3 id="1-2-done-사용">1-2. done 사용</h3>
<pre><code class="language-js">// fn.js
const fn = {
    getName:(callback) =&gt; {
        const name = &quot;mike&quot;;
        setTimeout(()=&gt; {
            callback(name);
        }, 3000)
    }
}

module.exports = fn;</code></pre>
<pre><code class="language-js">// fn.test.js
const fn = require(&#39;./fn&#39;);
test(&quot;3초 후에 받아온 이름은 Mike&quot;, (done)=&gt; {
    function callback(name) {
        expect(name).toBe(&quot;mike&quot;);
        done();
    }
    fn.getName(callback);
})</code></pre>
<p>위 코드를 실행하면 마찬가지로 테스트는 통과하게 되고, 테스트 시간도 3초 이상이 걸린다. 비동기 코드를 제대로 테스트 한 것이다.</p>
<p>Jest는 <code>done()</code>이 호출되기 전 까지 코드를 종료하지 않고 기다리기 때문에 callback을 실행할 수 있다.</p>
<h2 id="2-promise">2. Promise</h2>
<h3 id="2-1-then">2-1. then</h3>
<pre><code class="language-js">// fn.js
const fn = {
  getAge:()=&gt; {
          const age = 30;
          return new Promise((res, rej) =&gt; {
              setTimeout(()=&gt; {
                  res(age);
              }, 3000);
          })
      }
}</code></pre>
<pre><code class="language-js">// fn.test.js
const fn = require(&#39;./fn&#39;);
test(&quot;3초 후에 받아온 나이는 30&quot;, ()=&gt; {
    return fn.getAge().then(age =&gt; {
        expect(age).toBe(30);
    })
});</code></pre>
<p>위 코드를 실행해보면 정확하게 테스트가 진행됨을 알 수 있다.</p>
<h3 id="2-2-resolves">2-2. resolves()</h3>
<pre><code class="language-js">// fn.test.js
const fn = require(&#39;./fn&#39;);
test(&quot;3초 후에 받아온 나이는 30&quot;, ()=&gt; {
    return expect(fn.getAge()).resolves.toBe(30);
});</code></pre>
<p><code>resolves()</code>를 통해서도 Promise를 사용하는 비동기 코드를 테스트 할 수 있다. <strong>에러 상황을 테스트하기 위해서는 rejects()를 사용하면 된다.</strong></p>
<h3 id="2-3-asyncawait">2-3. async/await</h3>
<pre><code class="language-js">// fn.test.js
const fn = require(&#39;./fn&#39;);
test(&quot;3초 후에 받아온 나이는 30&quot;, async ()=&gt; {
    const age = await fn.getAge();
    expect(age).toBe(30);
});</code></pre>
<p>자바스크립트에서 async/await을 사용하는 방법과 동일하게 사용하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Jest] Jest 간단 사용법 및 유용한 matcher]]></title>
            <link>https://velog.io/@apro_xo/Jest-Jest-%EA%B0%84%EB%8B%A8-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B0%8F-%EC%9C%A0%EC%9A%A9%ED%95%9C-matcher</link>
            <guid>https://velog.io/@apro_xo/Jest-Jest-%EA%B0%84%EB%8B%A8-%EC%82%AC%EC%9A%A9%EB%B2%95-%EB%B0%8F-%EC%9C%A0%EC%9A%A9%ED%95%9C-matcher</guid>
            <pubDate>Fri, 25 Nov 2022 14:50:23 GMT</pubDate>
            <description><![CDATA[<h1 id="jest">Jest?</h1>
<p>Jest는 페이스북에서 만들었으며, React와 더불어 많은 자바스크립트 개발자들이 사용하는 테스팅 라이브러리이다.
실제로 Mocha, Chai 등 자바스크립트를 테스트하는 라이브러리는 많다.</p>
<h1 id="all-in-one-테스팅-라이브러리">All-in-one 테스팅 라이브러리</h1>
<p>페이스북에서는 Jest를 단순히 테스팅 라이브러리가 아닌 <strong>테스팅 프레임워크</strong> 라고 부르는 만큼 기존 자바스크립트 테스팅 라이브러리와는 차이가 있다.</p>
<p>Jest 이전에는 자바스크립트를 테스트하기 위해 여러 라이브러리를 조합하여 사용해야했다.</p>
<p>Mocha나 Jasmin을 Test Runner로 사용하고, Chai나 Expect와 같은 Test Matcher를 사용했으며, 또한 Sinon과 Testdouble 같은 Test Mock 라이브러리도 필요했었다고 한다.</p>
<p>하지만 Jest는 위의 모든 기능을 다 가지고 있고, Test Mock 프레임워크까지 제공해주어 굉장히 편리하다.</p>
<h1 id="사용법">사용법</h1>
<h2 id="1-테스트-파일-생성">1. 테스트 파일 생성</h2>
<blockquote>
<p>[파일명].test.js</p>
</blockquote>
<p>위와 같이 파일 이름을 설정하면 테스트 파일이 생성된다. ex)fn.test.js</p>
<p>또한 테스트 파일을 모아놓은 디렉토리를 생성하고 싶다면 _<em>test__를 생성하여 넣으면 된다.
_<em>test</em></em> 폴더에 있는 파일들은 모두 테스트 파일로 인식하기 때문이다.</p>
<h2 id="2-설치-및-설정">2. 설치 및 설정</h2>
<blockquote>
<p>npm init -y</p>
</blockquote>
<blockquote>
<p>npm install jest --save-dev</p>
</blockquote>
<p>npm으로 jest를 설치한 다음 package.json의 script를 변경해야한다.</p>
<pre><code class="language-json">&quot;scripts&quot;: {
    &quot;test&quot;: &quot;jest&quot;
  },</code></pre>
<blockquote>
<p>📌 CRA로 만든 프로젝트는 React Testing Library가 설치되어 있고 이는 Jest 기반이기 때문에 npm으로 Jest를 따로 설치할 필요가 없다.</p>
</blockquote>
<h2 id="3-test-it">3. test(), it()</h2>
<p>test()와 it()은 하나의 테스트를 만드는 기능을 수행한다. 기본적으로 아래와 같이 사용하면 된다.</p>
<pre><code class="language-js">// fn.test.js
test(&#39;1은 1이야&#39;, ()=&gt; {
    expect(1).toBe(1);
})

it(&#39;1은 1이야&#39;, () =&gt; {
    expect(1).toBe(1);
})
</code></pre>
<p>test()와 it()은 개발자의 취향에 따라 사용하면 되며 둘의 기능은 완벽하게 일치한다.</p>
<p><strong>위의 코드에서 expect()에는 검증 대상이 들어가고 toBe()에는 기대 결과가 들어간다.</strong>
그리고 toBe()와 같은 함수를 <strong><em>Test Matcher</em></strong>라고 한다.</p>
<p>matcher는 정말 다양한데, 필요에 따라 Jest 공식 문서로 가서 읽어보면서 사용하면 된다.</p>
<h2 id="4-유용한-matcher">4. 유용한 matcher</h2>
<h3 id="4-1-toequal">4-1. toEqual()</h3>
<pre><code class="language-js">// fn.js
const fn = {
    add : (num1, num2) =&gt; (num1 + num2),
}

module.exports = fn;</code></pre>
<pre><code class="language-js">// fn.test.js
const fn = require(&#39;./fn&#39;);

test(&#39;2+3은 5&#39;, ()=&gt; {
    expect(add(2, 3)).toBe(5);
});

test(&#39;2+3은 5&#39;, ()=&gt; {
    expect(add(2, 3)).toEqual(5);
})</code></pre>
<p>위의 두 테스트를 실행해보면 둘 다 통과하게 된다. 그럼 toBe()와 toEqual()은 같은 기능을 하는 함수일까❓
<strong>아니다</strong>❌</p>
<p>아래의 경우에는 다르다.</p>
<pre><code class="language-js">// fn.js
const fn = {
    makeUser:(name, age) =&gt; ({name, age})
}

module.exports = fn;</code></pre>
<pre><code class="language-js">// fn.test.js
test(&#39;이름과 나이를 전달받아서 객체를 반환해줘zz&#39;, () =&gt; {
    expect(fn.makeUser(&quot;mike&quot;, 30)).toBe({
        name:&quot;mike&quot;,
        age:30
    })
})

test(&#39;이름과 나이를 전달받아서 객체를 반환해줘ss&#39;, () =&gt; {
    expect(fn.makeUser(&quot;mike&quot;, 30)).toEqual({
        name:&quot;mike&quot;,
        age:30
    })
})</code></pre>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/82594f03-c3e1-4293-b7ba-a12092ae302a/image.png" alt=""></p>
<p>위에서 알 수 있듯이 toBe()에서는 테스트를 통과하지 못한다. 왜그럴까?</p>
<p>Jest는 객체나 배열을 재귀적으로 돌면서 값을 확인하기 때문에 toBe()가 먹히지 않는다고 한다.
따라서 객체나 배열을 테스트할 때는 toBe()를 사용하지 않아야 한다.</p>
<p><strong>그럼 toEqual()은 문제가 없을까?</strong></p>
<p>아래의 경우를 보자.</p>
<pre><code class="language-js">// fn.js

const fn = {
  makeUser:(name, age) =&gt; ({name, age, gender:undefined})
}

module.exports = fn;</code></pre>
<pre><code class="language-js">// fn.test.js
test(&#39;이름과 나이를 전달받아서 객체를 반환해줘ss&#39;, () =&gt; {
    expect(fn.makeUser(&quot;mike&quot;, 30)).toEqual({
        name:&quot;mike&quot;,
        age:30
    })
})</code></pre>
<p>위의 테스트를 실행했을 때, 대부분의 개발자들은 테스트를 통과하지 않아야한다고 생각할 것이다. 하지만 위의 경우 테스트를 통과하게 된다.</p>
<p>toEqual()은 깊은 비교를 하지 않기 때문에 정확한 테스트를 할 수 없다.</p>
<h3 id="4-2-tostrictequal">4-2. toStrictEqual()</h3>
<p>toEqual()의 문제를 위에서 확인했다.
깊은 비교를 통해 해결할 수 있는 방법이 toStrictEqual()을 사용하는 것이다.</p>
<p>아래의 코드를 보자.</p>
<pre><code class="language-js">// fn.test.js
test(&#39;이름과 나이를 전달받아서 객체를 반환해줘dd&#39;, () =&gt; {
    expect(fn.makeUser(&quot;mike&quot;, 30)).toStrictEqual({
        name:&quot;mike&quot;,
        age:30
    })
})</code></pre>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/ce0d64bd-039a-4ab1-8b44-26b5c855bdaa/image.png" alt=""></p>
<p>테스트를 통과하지 못하는 것을 알 수 있다. 정상적으로 동작한다.</p>
<h3 id="4-3-tobefalsy-tobetruthy">4-3. toBeFalsy(), toBeTruthy()</h3>
<p>boolean 값을 판단하는 matcher다.</p>
<pre><code class="language-js">// fn.test.js
test(&#39;0은 false입니다.&#39;, () =&gt; {
    expect(fn.add(1, -1)).toBeFalsy();
    // expect(fn.add(1, -1)).toBeTruthy(); &gt;&gt; test 통과 X
})</code></pre>
<h3 id="4-4-tobeclose">4-4. toBeClose()</h3>
<p>아래의 코드를 먼저 실행해보면,</p>
<pre><code class="language-js">// fn.test.js
test(&quot;0.1 더하기 0.2는 0.3입니다.&quot;, () =&gt; {
    expect(fn.add(0.1, 0.2)).toBe(0.3);
})</code></pre>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/e4fbd10e-adb4-4a1f-8cdb-7787c8dc276e/image.png" alt=""></p>
<p><strong>테스트를 통과하지 못한다.</strong> 위의 실행결과를 자세히 보면 Received가 0.30000000...4로 되어있음을 알 수 있다. 이것 때문에 테스트를 통과하지 못했다. 왜 이런 현상이 발생할까?</p>
<p>컴퓨터는 이진법을 사용한다. 소수를 이진법으로 바꿨을 때, 몇몇 소수들은 무한 소수가 되어버려서 발생하는 현상이다. 따라서 소수를 테스트할 때는 toBe()를 사용할 수 없다.</p>
<p>따라서 아래와 같이 작성하고 실행할 수 있다.</p>
<pre><code class="language-js">// fn.test.js
test(&quot;0.1 더하기 0.2는 0.3입니다.&quot;, () =&gt; {
    expect(fn.add(0.1, 0.2)).toBeCloseTo(0.3);
})</code></pre>
<p>테스트를 통과하는 것을 알 수 있다.</p>
<h3 id="4-5-tomatch">4-5. toMatch()</h3>
<p>toMatch()는 정규표현식을 사용하여 테스트를 할 때 사용한다.</p>
<pre><code class="language-js">// fn.test.js
test(&quot;hello world에 a라는 글자가 있나?&quot;, ()=&gt; {
    expect(&quot;hello world&quot;).toMatch(/a/);
}) // 테스트 통과 X

test(&quot;hello world에 H라는 글자가 있나?&quot;, ()=&gt; {
    expect(&quot;hello world&quot;).toMatch(/a/);
}) // 테스트 통과 X</code></pre>
<p>위의 두 테스트 모두 통과하지 못하며, 두 번째 테스트를 통해서 대소문자 구분을 한다는 것을 알 수 있다.</p>
<h3 id="4-6-tocontain">4-6. toContain()</h3>
<p>배열에 특정 요소가 존재하는지 테스트를 할 때 사용하는 matcher이다.</p>
<p>아래와 같이 사용한다.</p>
<pre><code class="language-js">// fn.test.js

test(&quot;유저리스트에 mike가 있나?&quot;, () =&gt; {
    const userName = &quot;mike&quot;
    const userList = [&quot;Tom&quot;, &quot;mike&quot;, &quot;alice&quot;];
    expect(userList).toContain(userName);
})</code></pre>
<h3 id="4-7-tothrow">4-7. toThrow()</h3>
<p>어떠한 동작을 했을 때 특정 에러가 발생하는지 테스트를 하기 위한 matcher이다.</p>
<pre><code class="language-js">// fn.js
const fn = {
  throwErr:() =&gt; {
    throw new Error(&quot;xx&quot;);
  }
}

module.exports = fn;</code></pre>
<pre><code class="language-js">// fn.test.js

test(&quot;이거 에러 나나요?&quot;, () =&gt; {
    expect(() =&gt; fn.throwErr()).toThrow(&quot;oo&quot;) // &gt;&gt; 테스트 통과 X
    // expect(() =&gt; fn.throwErr()).toThrow(&quot;xx&quot;);
})</code></pre>
<h1 id="마치며">마치며</h1>
<p>이외에도 정말 많은 matcher들이 있다. 필요할 때 마다 Jest 공식 문서를 참고하면 될 것 같다.
🖐🖐🖐🖐🖐</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] React.FC의 단점과 해결 방법]]></title>
            <link>https://velog.io/@apro_xo/React-React.FC%EC%9D%98-%EB%8B%A8%EC%A0%90%EA%B3%BC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@apro_xo/React-React.FC%EC%9D%98-%EB%8B%A8%EC%A0%90%EA%B3%BC-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 25 Nov 2022 03:12:09 GMT</pubDate>
            <description><![CDATA[<h1 id="functional-component">Functional Component</h1>
<p>리액트를 구현하는 방식은 크게 두 가지가 있다.
<code>클래스 컴포넌트</code>, <code>함수 컴포넌트</code> 이렇게 두 가지가 있는데,
타입스크립트를 사용하여 함수 컴포넌트 방식으로 구현할 때 사용하는 <code>FC</code>라는 제네릭 타입이 존재한다.
React.FC의 FC는 함수 컴포넌트를 의미한다.
아래와 같이 코드를 작성할 수 있다.</p>
<pre><code class="language-tsx">import React from &#39;react&#39;;
import { ModalPagePropsType } from &#39;../../model/props.model&#39;;

const AuthModal: React.FC&lt;ModalPagePropsType&gt; = ({ authModal, setIsOpenModal }) =&gt; {
    return (
      // (...생략)
    )
}</code></pre>
<p>나도 여태 위와 같이 FC를 사용하여 코드를 작성해왔는데, FC를 사용하는 방법에 단점이 있다는 것을 알게 되어 기록한다.</p>
<p>단점에는 어떤 것이 있을까?</p>
<h1 id="reactfc의-단점">React.FC의 단점</h1>
<h2 id="1-optional-children">1. optional children</h2>
<p>말 그대로 children을 옵셔널하게 가지고 있다는 것이다.</p>
<pre><code class="language-tsx">export const Hello: React.FC&lt;HelloProps&gt; = ({ name }) =&gt; {
  return &lt;h1&gt;Hello olleh! my name is {name}&lt;/h1&gt;
}

const App = () =&gt; (
  &lt;&gt;
    &lt;Hello name=&quot;suzi&quot;&gt;
      &lt;span&gt;{&quot;I have children&quot;}&lt;/span&gt;
    &lt;/Hello&gt;
  &lt;/&gt;
)</code></pre>
<p>위 코드를 보면 Hello 컴포넌트는 children을 렌더링하는 부분이 없음에도 불구하고 App 컴포넌트에서 오류 없이 정상적으로 작동한다.</p>
<p>이는 타입스크립트를 사용하면서 충분히 혼란을 줄 수 있는 여지가 있다.</p>
<p>컴포넌트에 children의 존재가 가능한지 여부를 확인하는 방법이 좋다.
만약 컴포넌트에 children이 존재할 수 도 있다는 것을 알려주기 위해서는
<strong>PropsWithChildren</strong>을 사용하는 것이 좋다. 아래와 같이 사용하면 된다.</p>
<pre><code class="language-tsx">function Card({ title, children }: PropsWithChildren&lt;{ title: string }&gt;) {
  return (
    &lt;&gt;
      &lt;h1&gt;{title}&lt;/h1&gt;
      {children}
    &lt;/&gt;
  )
}</code></pre>
<h2 id="2-defaultprops">2. defaultProps</h2>
<p>React.FC 에서는 <strong><code>defaultProps</code>를 사용하지 못하게 한다.</strong>
defaultProps란 props의 default 값을 설정해주는 방법이다.</p>
<p>언어에서 전달 받는 매개변수의 default 값을 설정해주는 것과 비슷한 맥락이라고 생각하면 되겠다.</p>
<p>함수형 컴포넌트에서는 자바스크립트의 기본적인 기능을 활용하여 props의 기본값을 제공할 수 있었는데, 아래와 같이 사용하였다.</p>
<pre><code class="language-tsx">export const Hello: FC&lt;HelloProps&gt; = ({ name = &quot;minsu&quot; }) =&gt; {
  return &lt;h1&gt;Hello olleh! my name is {name}&lt;/h1&gt;
}
</code></pre>
<p>하지만 타입스크립트 3.1 버전 이후로 <code>defaultProps</code>를 이해하는 메커니즘이 추가가 되었다고 한다. <strong>하지만 React.FC는 defaultProps를 이해하지 못한다.</strong></p>
<pre><code class="language-tsx">export const Hello: React.FC&lt;HelloProps&gt; = ({ name }) =&gt; {
  return &lt;h1&gt;Hello olleh! my name is {name}&lt;/h1&gt;
}

Hello.defaultProps = {
  name:&quot;minsu&quot;
}

const App = () =&gt; (
  &lt;&gt;
    &lt;Hello /&gt;
  &lt;/&gt;
)</code></pre>
<p><strong>위의 코드를 실행하면 name에 minsu가 들어오지 않는다.</strong>😂</p>
<h1 id="해결-방법">해결 방법</h1>
<p>해결 방법은 생각보다 간단하다.
아래와 같이 React.FC를 사용하지 않고 일반적인 함수 방식을 사용하면 된다.</p>
<pre><code class="language-tsx">export const Hello: = ({ name }: HelloProps) =&gt; {
  return &lt;h1&gt;Hello olleh! my name is {name}&lt;/h1&gt;
}

Hello.defaultProps = {
  name:&quot;minsu&quot;
}

const App = () =&gt; (
  &lt;&gt;
    &lt;Hello /&gt;
  &lt;/&gt;
)</code></pre>
<p>위 코드를 실행하면 defaultProps가 제대로 동작하게 된다.
또한, 아래의 코드를 실행했을 때, children으로 인해 정상적으로 오류가 발생한다.</p>
<pre><code class="language-tsx">export const Hello = ({ name }: HelloProps) =&gt; {
  return &lt;h1&gt;Hello olleh! my name is {name}&lt;/h1&gt;
}

const App = () =&gt; (
  &lt;&gt;
    &lt;Hello name=&quot;suzi&quot;&gt;
      &lt;span&gt;{&quot;I have children&quot;}&lt;/span&gt; // Error
    &lt;/Hello&gt;
  &lt;/&gt;
)</code></pre>
<h1 id="마치며">마치며</h1>
<p><code>React.FC</code>를 사용하는 것이 나쁜 것은 아니다. 이 글을 포스팅하기 위해 공부를 해보면서 위에서 React.FC의 단점이라고 언급했던 optional children을 React.FC의 장점이자 단점으로 생각하는 사람도 있었다. 상황에 따라 React.FC를 사용하는 것이 더 좋은 경우도 있을 수 있다는 것이다.</p>
<p>그러나 일반적인 함수 처럼 props에 직접 타입을 적용하는 것이 타입스크립트 또는 자바스크립트의 느낌과 비슷하고 다양한 경우의 수로 부터 비교적 좀 더 안전해 질 수는 있다.</p>
<p>참고: <a href="https://yceffort.kr/2022/03/dont-use-react-fc#reactfc%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80">https://yceffort.kr/2022/03/dont-use-react-fc#reactfc%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] 자바스크립트가 비동기 작업을 처리하는 방법(FEAT. 이벤트 루프, 태스크 큐)]]></title>
            <link>https://velog.io/@apro_xo/JS-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EA%B0%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@apro_xo/JS-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EA%B0%80-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9E%91%EC%97%85%EC%9D%84-%EC%B2%98%EB%A6%AC%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Sat, 19 Nov 2022 07:15:47 GMT</pubDate>
            <description><![CDATA[<h1 id="1-자바스크립트는-싱글-스레드">1. 자바스크립트는 싱글 스레드</h1>
<p>자바스크립트는 제목에서도 알 수 있는 것 처럼 <strong><em>싱글 스레드</em></strong>이다.
싱글 스레드라는 것은 쉽게 얘기하면 프로세스로 부터 할당 받은 일을 처리할 수 있는 녀석(?)이 하나 밖에 없다는 것이다. 상식적으로는 하나의 스레드가 여러 일을 처리하려면 동기적으로 일을 처리할 수 밖에 없다. 한 번에 하나의 일만 하는 것이다<strong>(run to completion)</strong>.</p>
<p>하지만 우리는 자바스크립트를 통해 비동기 동작을 구현할 수 있다.
알기 쉬운 예로 setTimeout()을 사용하여 비동기 동작을 구현 할 수 있다.</p>
<p>📌 <strong>싱글 스레드인 자바스크립트가 비동기 동작을 어떻게 처리할 수 있을까?</strong></p>
<h1 id="2-실행-환경에게-helpfeat-web-api">2. 실행 환경에게 help!(FEAT. web api)</h1>
<blockquote>
<p>자바스크립트의 실행 환경은 host 즉, 웹 브라우저다.</p>
</blockquote>
<p>크롬 브라우저에는 자바스크립트를 실행하기 위한 <strong>V8 자바스크립트 엔진</strong>이 존재한다.</p>
<p>V8 엔진에는 실행 컨텍스트를 담는 콜 스택(호출 스택), 크기가 동적으로 변하는 값들의 참조 값을 갖고 있는 힙(heap)이 존재한다.</p>
<pre><code class="language-js">setTimeout(()=&gt; {
  alert(&quot;time out!&quot;);
}, 1000);

console.log(&quot;hello&quot;);</code></pre>
<p>위와 같은 코드를 V8 엔진에서 파싱할 때, setTimeout()에 의한 실행 컨텍스트를 콜스택에 저장, console.log()에 의한 실행 컨텍스트를 콜스택에 저장한다.</p>
<p>이 상황에서 브라우저가 자바스크립트가 실행이 될 때 비동기 작업을 해야하는 타이밍 즉, setTimeout()을 실행해야 할 때가 되면 브라우저는 <strong>web api에게 이 작업을 넘긴다.</strong>
당연하게 setTimeout() 뿐만 아니라, API 요청과 같은 비동기 동작 또한 마찬가지다.</p>
<p>이렇게 해서 web api가 네트워킹도 대신 처리해주고, setTimeout과 같은 메서드도 대신 처리해준다.</p>
<h1 id="3-콜-스택과-태스크-큐feat-이벤트-루프">3. 콜 스택과 태스크 큐(FEAT. 이벤트 루프)</h1>
<pre><code class="language-js">setTimeout(()=&gt; {
  alert(&quot;time out!&quot;);
}, 1000);</code></pre>
<p>위 코드가 실행된다고 가정하면, <strong>1초를 세어 주는 것은 web api가 대신 해주고, alert()를 띄워주는 것은 콜 스택이 담당해야한다.</strong></p>
<p>하지만 콜 스택도 놀고만 있지는 않을 것이다. 비동기 작업을 web api가 처리하는 동안 콜 스택 또한 실행 컨텍스트로 쌓인 어떠한 일을 계속 수행하고 있었을 것이다.
갑자기 web api에서 완료된 비동기 작업을 콜 스택이 수행하기에는 무리가 있을 것이다.</p>
<p>따라서 <strong>web api는 비동기 처리가 끝난 작업을 태스크 큐에 임시 저장한다.</strong>🔥</p>
<p>태스크 큐에 들어간 작업은 콜 스택이 모든 작업을 끝마칠 때 까지 대기하다가 콜 스택이 비면(empty) 콜 스택으로 넘어가 작업이 수행된다.</p>
<h2 id="3-1-이벤트-루프">3-1. 이벤트 루프</h2>
<blockquote>
<p>콜 스택이 비었는지 관찰하는 관찰자</p>
</blockquote>
<p>콜 스택이 비었는지 확인하고 비었다면 태스크 큐에 알려주는 역할이 필요한데,
이를 <strong>이벤트 루프</strong>가 수행한다.</p>
<p>태스크 큐에 있는 일을 콜 스택으로 넘길 타이밍을 관찰하다가 콜 스택이 비면 이벤트 루프가 태스크 큐에 있는 일을 콜 스택으로 넘긴다.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/95004886-d76c-4bec-a8e3-644af530a3bb/image.png" alt=""></p>
<p>자바스크립트가 브라우저에서 이런 방식으로 동작한다.</p>
<h1 id="4-마이크로태스크큐">4. 마이크로태스크큐</h1>
<p>위의 예시에서 setTimeout이 태스크 큐로 이동한다고 했다. 그럼 <strong>마이크로태스크큐</strong>는 무엇인가?</p>
<p>2개의 큐 모두 비동기 처리를 담당하는 것은 같지만 어떤 것을 실행하느냐에 따라 어디로 들어가는지가 달라진다.</p>
<p>📌 <strong>태스크 큐(매크로태스크큐)에 들어가는 아이들</strong></p>
<blockquote>
<p>setTimeout, setInterval, setImmediate, UI렌더링 ....</p>
</blockquote>
<p>📌 <strong>마이크로태스크 큐에 들어가는 아이들</strong></p>
<blockquote>
<p>process.nextTick, Promise, Object.observe ... </p>
</blockquote>
<h3 id="4-1-먼저-실행되는건-누구">4-1. 먼저 실행되는건 누구?</h3>
<p><strong><em>마이크로태스크 큐에 있는 아이가 먼저 실행된다.</em></strong>
이벤트 루프는 콜 스택이 비면 먼저 마이크로태스크 큐에서 대기하고 있는 작업을 가져와 실행한다. 이후 마이크로태스크 큐가 비면 태스크 큐에 대기하고 있던 작업들을 가져와 실행한다.</p>
<p>아래의 코드를 실행해보면</p>
<pre><code class="language-js">console.log(&#39;콜 스택!&#39;);
setTimeout(() =&gt; console.log(&#39;태스크 큐!&#39;), 0);
Promise.resolve().then(() =&gt; console.log(&#39;마이크로태스크 큐!&#39;));</code></pre>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/339d5740-0f9e-45b6-a993-7a21b1658d7e/image.png" alt=""></p>
<p>마이크로태스크 큐의 작업이 먼저 실행된다. Promise가 먼저 실행됨을 알 수 있다.</p>
<h1 id="5-동시성">5. 동시성</h1>
<blockquote>
<p>자바스크립트는 기본적으로 한 번에 하나의 일을 처리하지만, 실행 환경의 도움을 받아 여러 작업이 한 번에 처리되는 것 처럼 보이는데, 이를 <strong>동시성</strong>이라고 한다.</p>
</blockquote>
<p>병렬과는 다르다. 자바스크립트가 동시성을 가진다고 해서 실제로 동시에 모든 작업이 실행되는 것은 아니다. 이를 병렬이라고 부른다.</p>
<p>하지만 자바스크립트는 그것은 아니기 때문에 병렬이라고 볼 수 없고, <strong>동시성</strong>을 가진다고 볼 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[axios] axios interceptors를 사용하여 JWT RefreshToken 로직 구현하기]]></title>
            <link>https://velog.io/@apro_xo/axios-axios-interceptors%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JWT-RefreshToken-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@apro_xo/axios-axios-interceptors%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-JWT-RefreshToken-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 17 Nov 2022 11:50:20 GMT</pubDate>
            <description><![CDATA[<h1 id="👉-jwt-refreshtoken-with-interceptors">👉 JWT RefreshToken with interceptors</h1>
<p>interceptors란 axios 요청 또는 응답을 가로채어 추가적인 동작을 진행할 수 있는 기능이다.</p>
<p>이것을 통해 JWT의 RefreshToken을 적용하고 구현해봤다.
아래는 예전에 프로젝트를 진행하며 작성했던 코드다.</p>
<h1 id="코드">코드</h1>
<pre><code class="language-js">instance.interceptors.response.use((res) =&gt; {
    return res;
}, (err) =&gt; {
    if(err.response &amp;&amp; err.response.status === 401) {
        const refreshToken = sessionStorage.getItem(&quot;Refresh__Token&quot;);
        const prevAccessToken = sessionStorage.getItem(&quot;Authorization&quot;);
        const originRequest = err.config; // 원래의 요청

        return axios.put(`${process.env.REACT_APP_API_URL}/api/refreshToken`,{}, {
            headers: {
                Authorization:prevAccessToken,
                RefreshToken:refreshToken
            }
        }).then((res) =&gt; {
            const newAccessToken = res.headers.authorization;
            sessionStorage.setItem(&quot;Authorization&quot;, newAccessToken);
            originRequest.headers.Authorization= newAccessToken;

            return instance.request(originRequest).catch((e) =&gt; {
                alert(&quot;세션이 만료되었습니다.&quot;);
                console.log(e);
                window.location.href=&quot;/login&quot;;
            }); // 원래의 요청으로 다시 요청
        }).catch((err) =&gt; {
            alert(&quot;세션이 만료되었습니다.&quot;)
            sessionStorage.removeItem(&quot;Authorization&quot;);
            window.location.href=&quot;/login&quot;;
        })
    }
  }
    return Promise.reject(err.config);
)</code></pre>
<h1 id="설명">설명</h1>
<p>기본적으로 JWT를 이용하여 토큰 기반 인증 방식을 사용하기 위해서는 인증이 필요한 모든 API 요청에 <code>AccessToken</code>을 header에 포함시켜야한다.</p>
<p>클라이언트에서 사용자의 AccessToken을 가지고 있고, 이를 요청의 headers에 포함하여 정상적으로 요청을 했을 때,
만약 요청에 포함되었던 토큰이(클라이언트가 가지고 있었던 토큰이) 만료되었다면 <strong>요청은 당연히 거부되어야 할 것이다.</strong> 그리고 거부 된 것을 <strong>응답</strong>을 통해 알려주게 된다.</p>
<p>그렇기 때문에 interceptors를 사용하여 <strong>응답</strong>을 가로채고, 에러가 발생했다면, RefreshToken을 사용하여 새로운 AccessToken을 받아오는 API 요청을 하여 AccessToken을 갱신하고, 기존에 토큰이 만료되어 거부되었던 요청을 새로운 AccessToken을 사용하여 다시 요청하는 방식이다.</p>
<p>다시 요청하는 이유는 개발 의도에 따라 다르지만, 나는 사용자가 자신의 로그인 상태가 만료되었는지 모르고 자연스럽게 사용할 수 있게 하는 것이 개발하고 있던 웹 어플리케이션에 더 잘 맞다고 생각했고, 사용자 경험 측면에서도 더 좋을 것이라고 판단하였다.</p>
<p>코드에서는 <code>const originRequest = err.config</code>을 통해 거부되었던 원래의 요청을 따로 저장하고, AccessToken을 갱신하는 과정을 거친 후, 기존의 요청을 재요청하는 것을 알 수 있다.</p>
<pre><code class="language-js">return instance.request(originRequest).catch((e) =&gt; {
                alert(&quot;세션이 만료되었습니다.&quot;);
                console.log(e);
                window.location.href=&quot;/login&quot;;
            }); // 원래의 요청으로 다시 요청</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[eslint + prettier] CRA + typescript 설정하기 + husky, git hook]]></title>
            <link>https://velog.io/@apro_xo/eslint-prettier-CRA-typescript-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-husky-git-hook</link>
            <guid>https://velog.io/@apro_xo/eslint-prettier-CRA-typescript-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-husky-git-hook</guid>
            <pubDate>Mon, 14 Nov 2022 04:03:46 GMT</pubDate>
            <description><![CDATA[<h1 id="1-eslint와-prettier">1. eslint와 prettier</h1>
<p><code>eslint</code>는 일부의 포맷팅과 오류가 날 법한 부분을 알려주는 기능을 제공하고, <code>prettier</code>는 코드 스타일을 교정하는 포맷터이다.</p>
<p>오류 위험을 미리 알려주고, 코드를 포맷팅 하는 것은 다른 개발자와 팀 프로젝트를 진행할 때 굉장히 필요하고 유용하다.</p>
<p>코드를 작성하는데, 코드 작성 스타일이 다르다면 개발자 간에 코드를 공유, 이해하기가 힘들어진다.</p>
<h1 id="2-eslint-적용법cra">2. eslint 적용법(CRA)</h1>
<p>create-react-app에서는 eslint가 이미 설치되어 있어 npm으로 eslint를 설치하지 않아도 된다.</p>
<blockquote>
<p>npx eslint --init</p>
</blockquote>
<p>eslint 설정을 하기 위해 위의 명령어를 실행한다.
그러면 터미널에서 아래와 같이 설정을 위한 질문을 한다.
자신의 프로젝트 환경에 따라 설정을 마쳐주면 된다.
<img src="https://velog.velcdn.com/images/apro_xo/post/bb92c6a4-a58c-4b59-bfa4-818ef2a99fb4/image.png" alt=""></p>
<p>npx eslint --init을 통해 설치되는 항목은 아래와 같다.</p>
<ol>
<li>eslint-plugin-react@latest</li>
<li>@typescript-eslint/eslint-plugin@latest</li>
<li>@typescript-eslint/parser@latest</li>
<li>eslint@latest</li>
</ol>
<p>--init을 통해 설치하지 않고 설치하고 싶은 것들을 npm이나 yarn을 사용하여 직접 설치해도 무방하다.</p>
<h2 id="eslint---cache">eslint --cache</h2>
<p><code>eslint --cache .</code>는 모든 파일을 eslint로 검사하는데,
<strong><em>이 전에 검사했던 파일이나 항목을 cache에 저장하여 변경사항이 없다면 그 파일은 검사하지 않는 기능</em></strong>이다.
따라서 eslint로 매번 모든 파일을 검사하는 것 보다 실행 속도가 더 빠르다.</p>
<p>이를 쉽게 사용하기 위해 package.json에 등록한다.</p>
<h2 id="packagejson-등록">package.json 등록</h2>
<pre><code class="language-js">{
  &quot;script&quot;:{
    &quot;lint&quot;:&quot;eslint --cache .&quot;
  }
}</code></pre>
<p>따라서 <code>npm run lint</code> 를 통해 쉽게 eslint를 실행할 수 있다.</p>
<h2 id="eslintrcjs-설정">.eslintrc.js 설정</h2>
<pre><code class="language-js">module.exports = {
  env: {
    browser: true,
    es2021: true,
  },
  extends: [
    &quot;eslint:recommended&quot;,
    &quot;plugin:react/recommended&quot;,
    &quot;plugin:@typescript-eslint/recommended&quot;,
    &quot;plugin:prettier/recommended&quot;,
    &quot;prettier&quot;,
  ],
  overrides: [],
  parser: &quot;@typescript-eslint/parser&quot;,
  parserOptions: {
    ecmaVersion: &quot;latest&quot;,
    sourceType: &quot;module&quot;,
  },
  plugins: [&quot;react&quot;, &quot;@typescript-eslint&quot;],
  rules: {
    &quot;no-var&quot;: &quot;error&quot;,
  },
};</code></pre>
<p>eslint --init을 실행하면 자동으로 eslintrc 파일이 설치되는데, 여기서 eslint의 설정을 변경하거나 추가/삭제 할 수 있다.</p>
<p>이후에 prettier와 eslint를 결합하여 사용할 것이고, 포맷팅은 온전히 prettier가 담당할 것이기 때문에 extends에 &quot;prettier&quot;를 추가한다.</p>
<h2 id="typescript-eslintparser">@typescript-eslint/parser</h2>
<p>eslint는 javascript 코드만 파싱할 수 있다. 타입스크립트의 코드는 파싱하지 못한다.</p>
<p>따라서 설치 목록 중 @typescript-eslint/parser를 parser에 등록하여 eslint가 타입스크립트 코드를 파싱할 수 있도록 설정을 해준다.
<strong>(eslint --init으로 이미 설정이 되어 있다.)</strong></p>
<h1 id="3-prettier-적용법">3. prettier 적용법</h1>
<blockquote>
<p>npm install --save-dev prettier</p>
</blockquote>
<p>위의 명령어를 통해 설치한다. prettier는 개발하는 동안에만 사용하기 때문에 dev dependency에 추가한다. eslint도 마찬가지이다.</p>
<h2 id="eslint-prettier-포맷터-충돌-방지">eslint, prettier 포맷터 충돌 방지</h2>
<blockquote>
<p>npm install eslint-config-prettier --save-dev</p>
</blockquote>
<p>eslint는 linting 기능, prettier는 포맷팅 기능을 담당하는 것이 제일 좋다.</p>
<p>하지만 eslint는 일부 포맷팅 관련된 rule도 포함이 되어 있다.
이 rule 때문에 eslint와 prettier는 포맷팅 관련하여 충돌하는 경우가 있다. 
따라서 위의 명령어를 통해 <code>eslint-config-prettier</code>를 설치하면 eslint에서 포맷팅 관련 rule을 모두 해제할 수 있다.</p>
<h2 id="prettier---cache---write-">prettier --cache --write .</h2>
<p>prettier로 모든 파일을 포맷팅 하는 것 보다 위의 eslint 처럼 이전에 포맷팅을 진행한 파일을 cache에 보관하고, 별도의 변경사항이 없다면 포맷팅을 진행하지 않는 것이 더 좋다.</p>
<p>따라서 --cache 속성을 추가하여 속도를 향상시킨다.</p>
<h2 id="packagejson-등록-1">package.json 등록</h2>
<pre><code class="language-js">{
  &quot;script&quot;:{
    &quot;lint&quot;:&quot;eslint --cache .&quot;,
    &quot;format&quot;:&quot;prettier --cache --write .&quot;
  }
}</code></pre>
<p>위와 같이 prettier 실행 명령어를 추가한다.</p>
<h2 id="prettierrcjson">.prettierrc.json</h2>
<p>포맷팅 설정을 할 수 있는 prettierrc 파일을 만든다. 어떤 설정이 있는지는 prettier 공식 문서를 보면 잘 나와 있다.</p>
<pre><code class="language-js">{
  &quot;singleQuote&quot;: true,
  &quot;semi&quot;: true,
  &quot;useTabs&quot;: true,
  &quot;tabWidth&quot;: 2,
  &quot;trailingComma&quot;: &quot;all&quot;,
  &quot;printWidth&quot;: 120,
  &quot;arrowParens&quot;: &quot;always&quot;
}</code></pre>
<h1 id="4-git-hook">4. git hook</h1>
<p>위의 eslint 설정과 prettier 설정을 끝마쳤다고 가정하자.
팀원이나 내가 개발 간에 eslint와 prettier를 사용하지 않으면 무용지물이다.
또한 개인이 매번확인해서 실행하면 실수가 발생할 여지가 있다. 
따라서 이를 자동화하고 강제(?) 할 필요가 있다.</p>
<p><code>git hook</code>를 통해 설정할 수 있다.</p>
<h2 id="git-hook이란">git hook이란?</h2>
<blockquote>
<p>git에서 특정 이벤트가 발생하기 전, 후로 특정 hook 동작을 실행할 수 있게 하는 것</p>
</blockquote>
<p>commit과 push 등의 명령어 실행을 예로 들 수 있다.</p>
<h2 id="단점">단점</h2>
<p>설정이 까다롭고, 모든 팀원들이 사전에 레포지토리를 클론받고, 동일한 사전 과정을 수행해야만 git hook이 실행된다. 따라서 조금 번거롭다.</p>
<h1 id="5-husky">5. husky</h1>
<p>husky란, git hook 설정을 도와주는 npm package이다.</p>
<h2 id="장점">장점</h2>
<p>번거로운 git hook 설정이 편하고, 모든 팀원이 git hook 실행이 되도록 하기가 편함.</p>
<blockquote>
<p>npm install husky --save-dev</p>
</blockquote>
<p>위의 명령어를 통해 husky를 설치할 수 있다.</p>
<h2 id="npx-husky-install">npx husky install</h2>
<p>처음 husky를 설치하고 설정하는 사람만 <code>npx husky install</code>을 실행하면 된다.
husky에 등록된 hook을 실제 .git에 적용시킨다.</p>
<h2 id="postinstall-설정">postinstall 설정</h2>
<p>package.json에 postinstall을 설정할 수 있다.
이는 레포지토리를 클론한 팀원들이 husky를 따로 설치하지 않아도 자동으로 설치가 되게끔 하는 설정이다.</p>
<pre><code class="language-js">{
  &quot;script&quot;:{
    &quot;postinstall&quot;:&quot;husky install&quot;,
    &quot;lint&quot;:&quot;eslint --cache .&quot;,
    &quot;format&quot;:&quot;prettier --cache --write .&quot;
  }
}</code></pre>
<h2 id="pre-commit-pre-push-등록">pre-commit, pre-push 등록</h2>
<blockquote>
<p>npx husky add .husky/pre-commit &quot;npm run format &amp;&amp; git add -A .&quot;</p>
</blockquote>
<blockquote>
<p>npx husky add .husky/pre-push &quot;npm run lint&quot;</p>
</blockquote>
<p>위의 명령어를 입력하여 commit 전에 포맷팅을 하고, push 전에 lint를 실행하였는지 유무에 따라 push를 막는 기능을 자동화 할 수 있다.</p>
<p>따라서 모든 팀원이 포맷팅과 lint를 실행할 수 있도록 설정할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Redux] 리덕스가 값을 비교하는 방법]]></title>
            <link>https://velog.io/@apro_xo/Redux-%EB%A6%AC%EB%8D%95%EC%8A%A4%EA%B0%80-%EA%B0%92%EC%9D%84-%EB%B9%84%EA%B5%90%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@apro_xo/Redux-%EB%A6%AC%EB%8D%95%EC%8A%A4%EA%B0%80-%EA%B0%92%EC%9D%84-%EB%B9%84%EA%B5%90%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 17 Oct 2022 03:10:42 GMT</pubDate>
            <description><![CDATA[<h1 id="리덕스는-얕은-복사-깊은-복사">리덕스는 얕은 복사? 깊은 복사?</h1>
<p>리덕스를 사용하면서 가장 중요한 부분은 불변성을 지켜야한다는 것이라고 생각한다.</p>
<p>우선 불변성이란, 기존의 데이터를 수정하는 것이 아니라, 기존의 데이터는 그대로 놔두고 새로운 데이터를 기존의 데이터에 대입하는 것이다.</p>
<pre><code class="language-js">const reducer = (state = initialState, action) =&gt; {
    switch(action.type) {
        case ADD:
            return {...state,
                    list:[...state.list, {id:action.payload.id, text:action.payload.text}]};

        case LOAD:
            return {...state, load: !state.load};

        default:
            return state;
    }
}</code></pre>
<p>위의 코드는 리덕스의 리듀서 함수인데, 기존의 state를 변경하지 않기 위해 spread 연산자로 state를 복사하여 기존의 state를 완전히 갈아끼운다.</p>
<h1 id="리덕스가-값을-비교하는-방법">리덕스가 값을 비교하는 방법</h1>
<p><strong><em>우선 리덕스는 주소값을 비교하여 값을 비교한다.</em></strong></p>
<p>리덕스가 변경을 알아차리기 위해 새로운 데이터의 속성값을 하나하나 비교한다면 성능과 복잡성의 문제를 일으킬 수 있다.</p>
<p>그래서 주소값을 비교하는 것인데, spread 연산자를 사용하면 객체의 depth가 1이라면 깊은 복사를 수행할 수 있고, depth가 1 이상이면 얕은 복사를 수행할 수 있다.</p>
<p>얕은 복사를 수행하면 주소값을 그대로 복사하기 때문에 리덕스에서 값의 변경을 알아차릴 수 없다. 깊은 복사를 사용해야 주소값이 다른 새로운 객체를 생성할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JS] try-catch-finally 에러 핸들링]]></title>
            <link>https://velog.io/@apro_xo/JS-try-catch-finally-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</link>
            <guid>https://velog.io/@apro_xo/JS-try-catch-finally-%EC%97%90%EB%9F%AC-%ED%95%B8%EB%93%A4%EB%A7%81</guid>
            <pubDate>Mon, 17 Oct 2022 02:03:55 GMT</pubDate>
            <description><![CDATA[<h1 id="try-catch에러핸들링가-필요한-이유">try-catch(에러핸들링)가 필요한 이유</h1>
<pre><code class="language-jsx">const App = () =&gt; {
  const a = 2;
  a = 4;

  return(
    //...(생략)
  )
}

export default App;</code></pre>
<p>npm run start를 통해 앱을 구동시켜 보면, 타입 에러가 발생하는 것을 알 수 있다.</p>
<p>const(상수)로 정의한 a라는 변수를 선언 후 변경을 시도했기 때문이다.</p>
<p>하지만 여기서 문제점은 에러가 발생하면 에러가 난 부분의 이후 로직은 실행이 되지 않는다는 것이다.</p>
<p>위의 경우는 에러를 핸들링하는 이유들 중 하나라고 볼 수 있다.</p>
<h1 id="try-catch-사용">try-catch 사용</h1>
<pre><code class="language-jsx">const App = () =&gt; {
  const a = 2;
  try{
      a = 1;
  }catch(err) {
      console.log(err);
  }
  a = 4;

  return(
    //...(생략)
  )
}

export default App; </code></pre>
<p>위와 같이 try-catch를 사용하면 에러가 발생했을 시 error를 콘솔에 찍어주고, 아래의 로직을 실행할 수 있다.</p>
<p>try 안의 로직을 실행하다 에러가 발생하는 구문을 만나면 바로 catch로 이동하여 catch의 로직을 실행한다.</p>
<p>catch 블록으로 제어흐름이 넘어간 것이다.</p>
<h1 id="try-catch는-모든-에러를-잡아주는가">try-catch는 모든 에러를 잡아주는가?</h1>
<p>아니다. try-catch는 모든 에러를 잡아주지는 못한다.
(), {}가 덜 닫혔다던지 하는 등의 parse-time error는 잡지 못한다.
자바스크립트 엔진(V8)은 코드를 실행 시 먼저 코드를 위에서 아래로 한 번 훑어보는데, parse-time error로 인해 코드를 실행 할 수 없다면 try-catch가 무용지물이 되는 것이다.</p>
<p><strong><em>따라서 try-catch는 런타임 에러만 잡아낼 수 있다.</em></strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] react-router-dom을 사용하여 로그인 유무에 따라 사용자 접근 막기]]></title>
            <link>https://velog.io/@apro_xo/React-react-router-dom%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9C%A0%EB%AC%B4%EC%97%90-%EB%94%B0%EB%9D%BC-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%91%EA%B7%BC-%EB%A7%89%EA%B8%B0</link>
            <guid>https://velog.io/@apro_xo/React-react-router-dom%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%98%EC%97%AC-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%9C%A0%EB%AC%B4%EC%97%90-%EB%94%B0%EB%9D%BC-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%A0%91%EA%B7%BC-%EB%A7%89%EA%B8%B0</guid>
            <pubDate>Mon, 17 Oct 2022 01:37:56 GMT</pubDate>
            <description><![CDATA[<h1 id="주소창을-통한-페이지-이동-막기">주소창을 통한 페이지 이동 막기</h1>
<p>웹 어플리케이션을 개발하다 보면 사용자의 권한에 따라 페이지의 접근을 막아야하는 경우가 자주 발생한다.</p>
<p>예를 들면, 로그인을 하지 않은 사용자가 방문하면 안 되는 페이지가 있을 수 있다. 페이지를 이동하는 네비게이션 바의 버튼을 클릭 시 항상 로그인 유무를 파악하여 접근을 막을 수 있다.</p>
<p>하지만 이는 사용자가 주소창을 통해 개발자의 의도와 다르게 비정상적으로 접근하는 경우를 막을 수 없다.</p>
<p>이를 해결하기 위해 페이지 컴포넌트에서 useEffect를 사용하여 로그인 유무를 판단하고, 로그인이 되지 않았다면 페이지를 나가도록 구현하는 등을 생각할 수 있지만 이 방법의 단점은 <strong><em>페이지를 일단 무조건 한 번 들어간다는 것이다.</em></strong></p>
<p>예를 들어, 페이지 이동이 완료 된 후 사용자의 로그인 유무를 판단하여 로그인 페이지로 이동하게 하거나, alert를 띄워 사용자에게 로그인을 권유한다는 것이다.</p>
<p>이는 사용자 경험을 저해할 수 있고, 각 컴포넌트마다 useEffect에 로그인을 판단하는 로직을 구현해야한다. 매우 비효율적이고 별로 좋지 않은 방법이라는 것을 알 수 있다.</p>
<p>따라서 react-router-dom을 사용하여 이를 막는 방법을 사용해보았다.</p>
<h1 id="privateroute-생성-및-사용">PrivateRoute 생성 및 사용</h1>
<pre><code class="language-jsx">// app.js
function App() {

const context = useContext(userContext);
  const location = useLocation();
  const access = context.state.userInfo.username;
  const { setUserInfo } = context.actions;
  const token = sessionStorage.getItem(&quot;Authorization&quot;);

  ...(생략)

  return (
    &lt;ThemeProvider theme={theme}&gt;
    &lt;div className=&quot;App&quot;&gt;
        &lt;GlobalStyle/&gt;
        &lt;Helmet&gt;
          ...(생략)
        &lt;/Helmet&gt;
        &lt;Content&gt;
        &lt;AnimatePresence exitBeforeEnter&gt;
          ...(생략)
            &lt;Route path=&quot;/viewer/room&quot; element={&lt;PrivateRoute component={&lt;RoomViewer/&gt;} authenticated={token}/&gt;}/&gt;
            &lt;Route path=&quot;/viewer/room/search/:hashtag&quot; element={&lt;PrivateRoute component={&lt;RoomViewer/&gt;} authenticated={token}/&gt;}/&gt;
            &lt;Route path=&quot;/detail/posting/:postingId&quot; element={&lt;PrivateRoute component={&lt;Detail /&gt;} authenticated={token}/&gt;} /&gt;
            &lt;Route path=&quot;/signup&quot; element={&lt;Signup /&gt;}&gt;&lt;/Route&gt;
          ...(생략)</code></pre>
<pre><code class="language-jsx">// PrivateRoute.jsx
import React from &#39;react&#39;;
import { Navigate } from &#39;react-router-dom&#39;;

const PrivateRoute = ({ authenticated, component:Component}) =&gt; {

    return(
        authenticated?Component:&lt;Navigate to=&quot;/login&quot; {...alert(&quot;로그인이 필요합니다.&quot;)}&gt;&lt;/Navigate&gt;
    )
}

export default PrivateRoute;</code></pre>
<p>위의 코드들은 실제로 내가 프로젝트간 사용했던 코드다.</p>
<p>먼저 PrivateRoute.jsx를 설명하면, props로 사용자의 로그인 유무를 파악할 수 있는 authenticated 와 로그인이 되어있는 상태라면 렌더링 할 컴포넌트 component를 받아온다.</p>
<p>그리고 authenticated가 참이라면 Component를 return하고, 거짓이라면 react-router-dom의 Navigate를 사용하여 로그인 페이지로 리다이렉트 하고, alert를 통해 사용자에게 로그인이 필요하다는 경고창을 띄워준다.</p>
<p>app.js를 보면,</p>
<pre><code class="language-js">&lt;Route path=&quot;/viewer/room&quot; element={&lt;PrivateRoute component={&lt;RoomViewer/&gt;} authenticated={token}/&gt;}/&gt;</code></pre>
<p>위와 같이 PrivateRoute를 사용하는데, /viewer/room으로 라우팅이 되었을 때 PrivateRoute 컴포넌트로 먼저 들어가고, 사용자의 로그인 유무를 파악할 수 있는 값인 token을 props로 넘겨주어 로그인 유무를 파악하고, 이에 따라 컴포넌트를 렌더링 하도록 만들 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[내 로컬 서버를 공유하자! localtunnel]]></title>
            <link>https://velog.io/@apro_xo/%EB%82%B4-%EB%A1%9C%EC%BB%AC-%EC%84%9C%EB%B2%84%EB%A5%BC-%EA%B3%B5%EC%9C%A0%ED%95%98%EC%9E%90-localtunnel</link>
            <guid>https://velog.io/@apro_xo/%EB%82%B4-%EB%A1%9C%EC%BB%AC-%EC%84%9C%EB%B2%84%EB%A5%BC-%EA%B3%B5%EC%9C%A0%ED%95%98%EC%9E%90-localtunnel</guid>
            <pubDate>Fri, 26 Aug 2022 13:19:50 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/apro_xo/post/84a88d74-d749-4a30-aba0-6c8529a648d4/image.png" alt=""></p>
<p>팀원들과 프로젝트를 진행하던 중 기능 테스트를 위해 급하게 팀원들에게 구현된 결과를 보여줘야 하는데, 배포가 구축이 안 되어 있을 경우 사용하면 좋은 것들이다.</p>
<p>내 로컬 서버를 다른 팀원들에게 공유하여 팀원들이 내 로컬 서버를 통해서 테스트 할 수 있게 된다.</p>
<h2 id="localtunnel">localtunnel</h2>
<h3 id="install">install</h3>
<blockquote>
<p>npm install -g localtunnel</p>
</blockquote>
<h3 id="실행">실행</h3>
<blockquote>
<p>lt --port 3000</p>
</blockquote>
<p>나는 리액트를 통해 개발하고 있는데, 리액트 서버는 보통 포트번호가 3000이다.
따라서 npm run start를 통해 로컬 서버를 연 다음, 실행 명령어를 입력하면 내 로컬 서버를 공유할 수 있다.</p>
<h2 id="mixed-content">mixed-content</h2>
<p>아래와 같은 오류가 발생했다.</p>
<p><img src="https://velog.velcdn.com/images/apro_xo/post/e268b6f5-f0ee-4116-8b65-027ca0f18b8b/image.png" alt="">
공유된 프론트 서버는 https로 임시 배포 된다. 하지만 백엔드 API 서버가 https 설정이 되어 있지 않아 나타나는 오류다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[WIL] 8.12~8.18 9주차 회고]]></title>
            <link>https://velog.io/@apro_xo/WIL-8.128.18-9%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@apro_xo/WIL-8.128.18-9%EC%A3%BC%EC%B0%A8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 21 Aug 2022 14:26:56 GMT</pubDate>
            <description><![CDATA[<p>요즘 프로젝트를 하느라 정말 정신이 없다. 시간이 정말 빨리간다.
바쁘게 사는 것 같아 뿌듯하다.</p>
<p>이번 프로젝트에서 새롭게 도전해보는 기술에는 react-query와 소켓 통신을 위한 sockJS, stompJS가 있는데, 아직 실시간 채팅 기능은 구현하기 전이라 공부만 살짝 했는데 어느 정도 감이 잡혔다. 근데 백엔드와 같이 함께 구현해야하는데 나도 잘 몰라서 조금 걱정이긴 하다.😂</p>
<p>react-query를 공부하면서 알게 된 지식들은 따로 블로그에 정리를 하고 있는데, react-query의 장점, 왜 쓰는지에 대해서는 블로그에 정리한 적이 없어서 이번에 정리해보려 한다.</p>
<h1 id="server-state와-client-state">server state와 client state</h1>
<p><strong>server state</strong>는 서버로 부터 불러와 사용되는 데이터를 말한다. API 요청을 통해 서버에서 전달 받는 형식이라 클라이언트에서 제어가 불가능하다. </p>
<p><strong>client state</strong>는 클라이언트에서 제어가 가능한 데이터를 말한다. 예를 들면 사용자로부터 입력 받는 input 데이터, 야간모드 주간모드를 위한 데이터 등이 있다. 서버를 전혀 거치지 않는다.</p>
<h1 id="react-query를-사용하는-이유">react-query를 사용하는 이유</h1>
<p>✔ <strong>우선 server state를 쉽게 관리하기 위해 사용된다.</strong>
redux, recoil, mobx 등의 전역 상태 관리 라이브러리들이 존재하지만, 언급한 라이브러리들은 모두 client state관리에 적합한 라이브러리로서 server state를 제어하고 관리하기에는 조금 어려움이 있다.</p>
<p>예를 들어, redux-thunk를 사용하여 비동기 API 통신을 구현해보면, 보일러 플레이트도 많지만 이건 둘째 치고, 서버의 데이터를 변경하고 나서 그 데이터를 가져오는 액션을 dispatch 해야 최신의 데이터를 가져올 수 있었다.
나는 이 부분이 너무 불편했다.</p>
<p>react-query에서는 개발자가 하나하나 dispatch 해주는 것이 아니라 react가 알아서 최신의 데이터를 가져올 수 있도록 하게 해준다. 이 부분이 정말 좋았다.</p>
<p>그리고 react-query의 장점을 알아보자.</p>
<h2 id="1-캐싱">1. 캐싱</h2>
<p>서버에서 가져온 데이터를 캐시에 저장하여 캐시에 데이터가 남아있으면 그 데이터를 가져와서 사용할 수 있어 불필요한 반복적인 API 요청을 막을 수 있다.</p>
<h2 id="2-최신-server-state">2. 최신 server state</h2>
<p>최대한 빨리 최신의 server state로 업데이트가 가능하다.
react-query는 가지고 있는 데이터가 stale한 상태라고 판단이 되면 다시 데이터를 가져올 수 있다.</p>
<h2 id="3-동일한-데이터-요청-제거">3. 동일한 데이터 요청 제거</h2>
<p>동일한 데이터가 여러 번 요청되면 한 번만 요청한다. 옵션으로 중복 호출 가능 시간을 조절할 수 있다.</p>
<h2 id="4-에러핸들링-로딩">4. 에러핸들링, 로딩</h2>
<p>에러핸들링과 로딩을 정말 쉽게 처리할 수 있다. isError, isLoading, isFetching 등을 react-query에서 제공하기 때문에 이를 위한 loading, error state를 따로 선언하여 사용하지 않아도 된다. </p>
<h2 id="5-가비지-컬렉션">5. 가비지 컬렉션</h2>
<p>캐시에 남아있는 데이터가 오래 사용되지 않으면(cacheTime이 지나면) 가비지 컬렉션이 작동하여 데이터를 삭제한다. react-query가 알아서 메모리를 관리해준다.</p>
]]></description>
        </item>
    </channel>
</rss>