<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>chun_gil.log</title>
        <link>https://velog.io/</link>
        <description>일지를 꾸준히 작성하자.</description>
        <lastBuildDate>Sat, 22 Jun 2024 06:55:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>chun_gil.log</title>
            <url>https://velog.velcdn.com/images/chun_gil/profile/0021647e-6e10-4694-8ef7-c351b9c59274/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. chun_gil.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/chun_gil" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[💻 20240622 SOLID 원칙으로 리액트 훅 작성하기]]></title>
            <link>https://velog.io/@chun_gil/20240622-SOLID-%EC%9B%90%EC%B9%99%EC%9C%BC%EB%A1%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@chun_gil/20240622-SOLID-%EC%9B%90%EC%B9%99%EC%9C%BC%EB%A1%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 22 Jun 2024 06:55:26 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-참조">📌 참조</h2>
<p><a href="https://ykss.netlify.app/translation/write_solid_react_hooks/?utm_source=substack&amp;utm_medium=email">(번역) SOLID 원칙으로 리액트 훅 작성하기</a></p>
<hr>
<h2 id="📌-개요">📌 개요</h2>
<p>어떻게 하면 최적화 된 컴포넌트 설계를 할 수 있을까, 이 부분에 대해서 매일이 배움의 과정이고 항상 궁금하다. 관련해서, SOLID 객체지향 원칙을 활용하여 리액트에 최적화 된 리액트 훅을 작성할 수 있는 정보를 전달 받아 정리 차원으로 작성하게 되었다.</p>
<hr>
<h2 id="📌-단일-책임-원칙">📌 단일 책임 원칙</h2>
<blockquote>
<p>SRP - Single Responsibility Principle
모듈은 단 하나의 액터만 담당해야 합니다.</p>
</blockquote>
<h3 id="설명">설명</h3>
<ul>
<li><strong>UI(프레젠테이션)를 표시해야 하는 컴포넌트인가요? 
아니면 데이터(로직)를 처리해야 하는 컴포넌트인가요?</strong></li>
<li>이 훅은 어떤 단일 유형의 데이터를 처리해야 하나요?</li>
<li>이 훅이나 컴포넌트는 어떤 레이어에 속하나요?
데이터 저장소를 처리하는 건가요? 아니면 UI의 일부일까요?</li>
</ul>
<h3 id="코드">코드</h3>
<pre><code class="language-typescript">import { useState } from &#39;react&#39;;
import { getUser, getTodoTasks } from &#39;somewhere&#39;;

// useUser 훅은 더 이상 todo task를 가져오는 작업을 담당하지 않습니다.
const useUser = () =&gt; {
  const [user, setUser] = useState();

  useEffect(() =&gt; {
    const userInfo = getUser();
    setUser(userInfo);
  }, []);

  return { user };
};

// Todo task를 가져오는 작업은 그 자신의 고유한 훅이 수행합니다.
// 이 훅은 실제로 별도 파일에 있어야 합니다. 하나의 파일에는 하나의 훅만 두세요!
const useTodoTasks = () =&gt; {
  const [todoTasks, setTodoTasks] = useState();

  useEffect(() =&gt; {
    const tasks = getTodoTasks();
    setTodoTasks(tasks);
  }, []);

  return { todoTasks };
};</code></pre>
<hr>
<h2 id="📌-개방--폐쇄-원칙">📌 개방 / 폐쇄 원칙</h2>
<blockquote>
<p>OCP - Open / Closed Principle
다시 건들지 않을 수 있도록 훅과 컴포넌트를 작성하고,
다른 훅 / 컴포넌트에서는 재사용만 하세요.</p>
</blockquote>
<h3 id="설명-1">설명</h3>
<ol>
<li>훅의 확장성이 높아져 더 자주 재사용할 수 있습니다.</li>
<li>테스트를 다시 작성하는 횟수가 줄어들어 훅이 더 견고해집니다.</li>
<li>이전 코드를 건드리지 않으면 버그를 만들지 않는다는 것입니다.</li>
</ol>
<h3 id="코드-사례">코드 (사례)</h3>
<pre><code class="language-typescript">import { useState } from &#39;react&#39;;
import { getUser, updateUser } from &#39;somewhere&#39;;

const useUser = ({ userType }) =&gt; {
  const [user, setUser] = useState();

  useEffect(() =&gt; {
    const userInfo = getUser();
    setUser(userInfo);
  }, []);

  const updateEmail = newEmail =&gt; {
    if (user &amp;&amp; userType === &#39;admin&#39;) {
      updateUser({ ...user, email: newEmail });
    } else {
      console.error(&#39;Cannot update email&#39;);
    }
  };

  return { user, updateEmail };
};</code></pre>
<h3 id="코드-해결">코드 (해결)</h3>
<pre><code class="language-typescript">import { useState } from &#39;react&#39;;
import { getUser, updateUser } from &#39;somewhere&#39;;

// useUser 훅은 이제 이메일 업데이트 기능 없이
// 사용자만 반환합니다.
const useUser = () =&gt; {
  const [user, setUser] = useState();

  useEffect(() =&gt; {
    const userInfo = getUser();
    setUser(userInfo);
  }, []);

  return { user };
};

// 새로운 훅인 useAdmin은 useUser 훅을 확장하며,
// 이메일을 업데이트하는 추가 기능을 제공합니다.
const useAdmin = () =&gt; {
  const { user } = useUser();

  const updateEmail = newEmail =&gt; {
    if (user) {
      updateUser({ ...user, email: newEmail });
    } else {
      console.error(&#39;Cannot update email&#39;);
    }
  };

  return { user, updateEmail };
};</code></pre>
<hr>
<h2 id="📌-리스코프-치환의-원칙">📌 리스코프 치환의 원칙</h2>
<blockquote>
<p>LSP - Liskov Substitution Principle
훅 / 컴포넌트를 확장하는 모든 훅과 컴포넌트는 props를 받아들여야 합니다.
반환값도 마찬가지입니다.</p>
</blockquote>
<h3 id="설명-2">설명</h3>
<ol>
<li>훅 / 컴포넌트를 확장할 경우, props와 반환값을 유지해야한다.</li>
<li>리액트는 상속이 없기 때문에, 확장시에 같은 패키지에 props,
반환값 인터페이스를 상속 받아 확장하여 활용하자</li>
</ol>
<h3 id="코드-1">코드</h3>
<pre><code class="language-typescript">import { useState } from &#39;react&#39;;
import {
  getFromLocalStorage,
  saveToLocalStorage,
  getFromRemoteStorage,
} from &#39;somewhere&#39;;

// 리스코프는 훅의 인터페이스(변수 이름)와 일치시키기 위해서
// 데이터 상태 변수의 이름을 localData로 변경했습니다.
const useLocalStorage = ({ onDataSaved }) =&gt; {
  const [localData, setLocalData] = useState();

  useEffect(() =&gt; {
    const storageData = getFromLocalStorage();
    setLocalData(storageData);
  }, []);

  const saveToStorage = newData =&gt; {
    saveToLocalStorage(newData);
    onDataSaved(newData);
  };

  // 이 훅은 이제 &quot;data&quot; 대신 &quot;localData&quot;를 반환합니다.
  return { localData, saveToStorage };
};

// 리스코프는 useLocalStorage의 프롭 인터페이스와 일치시키기 위해
// 이 훅에 onDataSaved 콜백을 추가했습니다,
const useLocalAndRemoteStorage = ({ onDataSaved }) =&gt; {
  const [localData, setLocalData] = useState();
  const [remoteData, setRemoteData] = useState();

  useEffect(() =&gt; {
    const storageData = getFromLocalStorage();
    setLocalData(storageData);
  }, []);

  useEffect(() =&gt; {
    const storageData = getFromRemoteStorage();
    setRemoteData(storageData);
  }, []);

  const saveToStorage = newData =&gt; {
    saveToLocalStorage(newData);
    onDataSaved(newData);
  };

  return { localData, remoteData, saveToStorage };
};</code></pre>
<hr>
<h2 id="📌-인터페이스-분리-원칙">📌 인터페이스 분리 원칙</h2>
<blockquote>
<p>ISP - Interface Segregation Principle
사용하지 않는 메서드에 의존하도록 코드를 강제해서는 안 됩니다.</p>
</blockquote>
<h3 id="설명-3">설명</h3>
<ol>
<li>함수와 클래스는 명시적으로 사용하는 인터페이스만 구현해야 한다.</li>
<li>필요한 코드의 존재, 즉 혼란을 피하는 것</li>
<li>잊지 말아야 할 것은 테스트 가능성</li>
</ol>
<h3 id="코드사례">코드(사례)</h3>
<pre><code class="language-typescript">// 대안 1

// 이 인터페이스에는 다음과 같은 문제가 있습니다.
// 속성이 인터페이스의 일부 소비자에게 필수임에도 불구하고
// 선택적 속성이 포함되어 있습니다.
interface Website {
  companyName?: string;
  ownerFirstname?: string;
  ownerFamilyName?: number;
  domain: string;
  type: string;
}

// 대안 2

// 이것은 원래 웹사이트 인터페이스가 사람에 맞게 이름이 바뀐 것입니다.
// 즉, 이전 코드와 테스트를 업데이트해야 했고
// 잠재적으로 몇 가지 버그가 발생할 수 있습니다.
interface PersonWebsite {
  ownerFirstname: string;
  ownerFamilyName: number;
  domain: string;
  type: string;
}

// 회사에서 사용할 수 있는 새로운 인터페이스입니다.
interface CompanyOwnedWebsite {
  companyName: string;
  domain: string;
  type: string;
}</code></pre>
<h3 id="코드해결">코드(해결)</h3>
<pre><code class="language-typescript">interface Person {
  firstname: string;
  familyName: string;
  age: number;
}

interface Company {
  companyName: string;
}

interface Website {
  domain: string;
  type: string;
}</code></pre>
<hr>
<h2 id="📌-의존성-역전-원칙">📌 의존성 역전 원칙</h2>
<blockquote>
<p>Dependency Inversion Principle, DIP
고수준 모듈은 저수준 모듈로부터 아무것도 가져오지 말아야 하며,
둘 다 추상화에 의존해야 합니다.</p>
</blockquote>
<h3 id="설명-4">설명</h3>
<ol>
<li>HOC(Higher Order Component) 활용</li>
</ol>
<h3 id="코드-2">코드</h3>
<pre><code class="language-typescript">const withAuth = (Component) =&gt; {
  return (props) =&gt; {
    const { user } = useContext(AuthContext)

    if (!user) {
      return &lt;LoginComponent&gt;
    }

    return &lt;Component {...props} user={user} /&gt;
  }
}

const Profile = () =&gt; { // Profile 컴포넌트... }

// withAuth HOC를 사용하여 Profile 컴포넌트에 user를 삽입합니다.
const ProfileWithAuth = withAuth(Profile)</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[💻 20240527 실무에서 바로 쓰는 Frontend Clean Code]]></title>
            <link>https://velog.io/@chun_gil/20240527-%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EB%B0%94%EB%A1%9C-%EC%93%B0%EB%8A%94-Frontend-Clean-Code</link>
            <guid>https://velog.io/@chun_gil/20240527-%EC%8B%A4%EB%AC%B4%EC%97%90%EC%84%9C-%EB%B0%94%EB%A1%9C-%EC%93%B0%EB%8A%94-Frontend-Clean-Code</guid>
            <pubDate>Mon, 27 May 2024 05:42:12 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-참조">📌 참조</h2>
<p><a href="https://product.kyobobook.co.kr/detail/S000001032980">로버트 C. 마틴 클린코드</a>
<a href="https://github.com/lcgyung/clean-code-typescript">타입스크립트를 위한 클린코드</a>
<a href="https://youtu.be/edWbHp_k_9Y">실무에서 바로 쓰는 프론트엔드 클린코드</a></p>
<hr>
<h2 id="📌-개요">📌 개요</h2>
<p>React라는 자유로움 안에서 Typescript, Webpack, ESLint등의 조합으로 규율을 만들어서 사용하고 있었다. 추가로, 클린코드, 리팩토링 관련 지식들로 가독성이 좋은 코드를 작성하기 위해 노력했다.</p>
<p>자유함은 논쟁으로 이어졌다. 모든 조직을 겪어 보지는 않았으나, 클린코드, 리팩토링 관련해서 유독 이견들이 많았고, 이는 건전하고 발전적인 대화가 아닌 탁상공론으로 빠졌던 기억들이 떠올랐다.</p>
<p>사람이 모이는 곳에는 정답은 없지만, 언제나 개선은 존재한다고 생각한다. 이번 계기로 클린코드, 리팩토링에 대한 본질적인 지식들을 체득하고 논의함으로서 다음 조직에서 같은 문제가 나타 났을때 개선에 기여하고 싶다는 생각이 들었다.</p>
<hr>
<h2 id="📌-what-is-clean-code">📌 What is clean code?</h2>
<p>개발 조직에서 가장 중요한 포인트는 <strong>&#39;시간은 곧 돈이다&#39;</strong> 코드 파악이 어려운 경우 시간이 할애되고 비용이 빠져 나간다고 생각하면 정말 클린 코드는 중요하다.</p>
<p>조금 서술해서 작성하자면 개발자들에게 병목을 일으키고, 유지보수 시간이 증대되며, 기능 추가가 어렵고, 대게 이런 코드들이 성능도 좋지 못하다.</p>
<p>과거의 나 또는 동료가 짠 코드를 빠르게 이해할 수 있다면, 위 문제들이 해소가 되고 더욱 효율적인 개발 조직이 될 수 있다.</p>
<hr>
<h2 id="📌-언제부터-코드가-지저분해질까">📌 언제부터 코드가 지저분해질까?</h2>
<p>개발자가 조직에 들어가서 일을 하는 경우, 기존 코드에서 기능 추가 하는 일을 맡을 것이다. 이 부분에서 긴장을 놓치면 클린하지 못한 코드를 작성으로 이어지며, 이 순간들이 스노우볼이 되어 병목을 일으킬 수 있다.</p>
<p><strong>클린코드는 짧은 코드가 아니다.
클린코드는 원하는 로직을 빨리 찾을 수 있는 코드이다.</strong></p>
<p>아래 예시는 기존 코드에서 신규 기능을 추가할 때 주의사항들이다.</p>
<blockquote>
</blockquote>
<ol>
<li>하나의 목적을 가진 코드가 흩뿌려져 있을 경우</li>
<li>함수가 여러 가지 일을 하고 있는 경우</li>
<li>함수의 세부구현 단계가 제각각인 경우<blockquote>
</blockquote>
</li>
</ol>
<hr>
<h2 id="📌-로직을-빠르게-찾을-수-있는-코드">📌 로직을 빠르게 찾을 수 있는 코드</h2>
<p>개발자 작업 병목 현상을 일으키는 요인들을 살펴 보았다.
그렇다면, 어떻게 작성해야 원하는 로직을 빨리 찾을 수 있는 코드를 작성할 수 있을까?</p>
<h3 id="응집도">응집도</h3>
<ol>
<li>정의 : 같은 목적의 코드는 뭉쳐두자, &#39;선언적 프로그래밍&#39;</li>
<li>절차<ol>
<li>커스텀 훅 활용 : 응집도는 높혔으나, 핵심 기능 파악이 어려움<ul>
<li>뭉치면 쾌적 : 당장 몰라도 되는 디테일</li>
<li>뭉치면 답답 : 현재 코드 파악에 필수적인 핵심 정보</li>
</ul>
</li>
<li>응집도 높히는 방법<ul>
<li>남겨야 할 핵심 데이터 : 팝업 클릭 시 액션, 제목, 내용</li>
<li>숨겨도 될 세부 구현 나누기 : 팝업 열고 닫는 상태, 컴포넌트 세부 마크업, 버튼 클릭 시 특정 함수를 호출해야 한다는 바인딩</li>
</ul>
</li>
</ol>
</li>
</ol>
<h3 id="단일책임">단일책임</h3>
<ol>
<li>정의 : 하나의 일을 하는 뚜렷한 이름의 함수를 만들자</li>
<li>절차<ol>
<li>한 가지 일만 하는 함수명</li>
<li>예) 버튼 핸들러에서 로그 전송, API콜 함께 하는 경우<ul>
<li>로그 전송 컴포넌트를 만들어 클릭시, 자동으로 전송할 수 있도록</li>
</ul>
</li>
</ol>
</li>
</ol>
<h3 id="추상화">추상화</h3>
<ol>
<li>정의 : 핵심 개념을 뽑아내자, 눈으로 보았을 때, 바로 알 수 있게</li>
<li>절차<ol>
<li>JSX 컴포넌트를, 컴포넌트, 동작으로 추상화
(onConfirm, onSuccess등)</li>
<li>라벨을 만드는 헬퍼 변수들을 헬퍼 함수로 추상화</li>
<li>경고 1 : 추상화 수준이 섞여 있으면 코드 파악이 어려움</li>
<li>경고 2 : 클린 코드가 메인이지 추상화가 메인이 아님을 경계 !!!</li>
</ol>
</li>
</ol>
<hr>
<h2 id="📌-액션-아이템">📌 액션 아이템</h2>
<ol>
<li>담대하게 기존 코드 수정하기</li>
<li>큰 그림 보는 연습하기</li>
<li>팀과 함께 공감대 형성</li>
<li>문서로 적어보기</li>
</ol>
<hr>
<h2 id="📌-결론">📌 결론</h2>
<h3 id="팀-안에서의-클린-코드">팀 안에서의 클린 코드</h3>
<ul>
<li>3, 4번 액션 아이템에 대한 고찰이다.</li>
</ul>
<p><strong>[공감대 형성]</strong>
해당 부분에 대한 공동체 의식이 결여된 상태에서 클린 코드를 독단적으로 도입한다면, 
클린 코드가 클린하지 못한 코드로 이어질 수 있다는 생각이 들었다.</p>
<p><strong>[안정적인 코드 검증]</strong>
1순위는 안정적인 제품 릴리즈이다. 그렇기 위해서 돌아가는 코드를 만들어야 하고 
검증이 테스트 코드를 통해 남아 있어야 한다.</p>
<p><strong>[리팩토링에 대한 논의]</strong>
검증이 선행된 상태에서, 현재 우리는 어떤 수준까지 리팩토링 전략을 가져가야 할 지,
논의를 통해 나아가야 하며, 논의를 통해 맞춰진 수준 가운데 PR을 통해 클린 코드에 대한 코멘트를 남기면 좋을 것 같다는 생각이 들었다.</p>
<p><strong>[문서를 통한 리팩토링 수준 버전업]</strong>
추가로, 문서 릴리즈를 통해 응집도, 단일책임, 추상화에 대한 적절한 깊이와 수준을 맞춰 나가며 나아간다면, 여러명이 작성 했지만 한명이 작성한 것 같은 순간이 언젠가 찾아오지 않을까 싶다.</p>
<h3 id="개인이-갖춰야-할-클린-코드">개인이 갖춰야 할 클린 코드</h3>
<ul>
<li>팀 안에서의 클린 코드가 선행이 되어야 한다.</li>
<li>1, 2번 액션 아이템에 대한 고찰이다.</li>
</ul>
<p><strong>[리팩토링은 필요하다]</strong>
Task 단위에서 리팩토링 단위가 존재할 만큼, 리팩토링은 안정적이고 리소스 효율성을 높히는데에 꼭 필요한 작업이다. 단, 위에서 설명한바와 같이 팀 안에서의 클린 코드에 대한 협의가 이뤄진 상태에서 그 수준에 맞게 진행하고, PR을 통해 수정 사항을 적용함으로서 함께 점진적으로 나아 가는것이 중요해 보인다.</p>
<p><strong>[큰 그림 보는 연습하기]</strong>
코드짜기 전에 펜을 들고자 노력한다. 현재 내가 작성하는 컴포넌트는 어떤 컴포넌트이고 이 컴포넌트에 목적은 무엇이며, 어떻게 하면 이 컴포넌트가 순수함을 유지할 수 있는지, 불가피하게 그렇지 못하다면 추 후, 어떤 방향으로 이 코드를 수정할 지 다양한 고민들을 작성하고 확신이 들었을 때, 코드를 작성 하는것이 좋을 것 같다는 생각이 들었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[💻 20240514 번들러 Vite에 대해 알아보자]]></title>
            <link>https://velog.io/@chun_gil/20240514-Vite%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@chun_gil/20240514-Vite%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Tue, 14 May 2024 04:56:13 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-참조">📌 참조</h2>
<p><a href="https://ko.vitejs.dev/guide/why">Vite 공식문서 - Vite를 사용해야 하는 이유</a>
<a href="https://bepyan.github.io/blog/2023/bundlers">차세대 번들러 비교</a></p>
<hr>
<h2 id="📌-개요">📌 개요</h2>
<ul>
<li>기존에 만든 커스텀 보일러 플레이트에서 Webpack을 통해 번들링 하고 있었다.</li>
<li>당시, 번들 사이즈 축소를 통한 성능 향상에 힘을 썼으나 큰 변화는 없었다.</li>
<li>근래, Vite라는 번들러가 핫하다는 이야기를 들었고 맛보고자 Docs를 훑어 봤다.</li>
<li>Rollup의 빠른 번들 속도와 Webpack에 거대한 생태계, 중용점을 지향하는 느낌</li>
<li>이것이 요즘 Vite가 핫한 이유이지 않을까라는 개인적인 생각이 들었다.</li>
</ul>
<blockquote>
<p>이런 개요는 충분히 흥미로 이어졌고 Vite를 조금 더 파보기로 결심했다.</p>
</blockquote>
<hr>
<h2 id="📌-vite-파헤치기">📌 Vite 파헤치기</h2>
<h3 id="📌-개발-서버-구동-향상">📌 개발 서버 구동 향상</h3>
<h4 id="기존-도구의-문제점">기존 도구의 문제점</h4>
<ul>
<li>콜드 스타트 방식으로 개발 서버 구동</li>
<li>애플리케이션 내 모든 소스 코드에 대해 크롤링 및 빌드 작업 진행</li>
<li>작업 완료 이 후, 실제 페이지 제공<del>텍스트</del><h4 id="vite-개선-포인트-1---카테고리-분리">Vite 개선 포인트 (1) - 카테고리 분리</h4>
</li>
<li>Dependencies, Source Code 카테고리 분리</li>
<li>카테고리별 최적화 된 소스코드 빌드 제공</li>
<li>이를 통해 개발 서버 구동 향상<h4 id="vite-개선-포인트-1-1---dependencies">Vite 개선 포인트 (1-1) - Dependencies</h4>
</li>
<li>사전 번들링 : 변하지 않는 종속성 코드들을 사전에 번들링함</li>
<li>Esbuild : Go로 작성된 고성능 번들러, 10-100배 개선<h4 id="vite-개선-포인트-1-2---sourcecode">Vite 개선 포인트 (1-2) - Sourcecode</h4>
</li>
<li>브라우저의 발달 : ESM 표준으로 사용</li>
<li>Vite 번들링 코드 ESM으로 제공, 브라우저가 번들러의 작업 일부 차지</li>
</ul>
<h3 id="📌-번들링-차별화">📌 번들링 차별화</h3>
<h4 id="esm-기반-번들링">ESM 기반 번들링</h4>
<ul>
<li>브라우저 친화적인 ESM 기반 번들링</li>
<li>ESM 기반 번들링 문제점 파악, 자체적인 최적화 된 개발 / 운영 빌드 제공<h4 id="rollup-기반-빌드">Rollup 기반 빌드</h4>
</li>
<li>성능과 유연성 비교 했을때, 현재는 Esbuild보다 Rollup이 낫다고 판단</li>
<li>개선을 위해 지속적인 노력중</li>
</ul>
<blockquote>
</blockquote>
<p>대폭적인 개발 서버 구동 향상이 되었다는 개념은 인지했다.
하지만, 아직, 빌드 최적화에 대한 부분은 물음표이다.
Rollup vs Webpack 비교를 통해 Vite를 어떤 프로젝트 일 때
도입하면 좋을지 결론이 나올 것 같다.</p>
<hr>
<h2 id="📌-webpack-vs-rollup">📌 Webpack vs Rollup</h2>
<h3 id="📌-webpack">📌 Webpack</h3>
<ul>
<li>CommonJS 기반의 빌드 (Webpack v5부터 ESM 지원)</li>
<li>거대한 생태계</li>
<li>간편하고 직관적인 설정</li>
<li>풍부한 plugins과 loaders</li>
<li>목표 : 통합<h3 id="📌-rollup">📌 Rollup</h3>
</li>
<li>ESM 기반의 빌드</li>
<li>간편하고 직관적인 설정</li>
<li>목표 : 확장</li>
</ul>
<hr>
<h2 id="📌-결론">📌 결론</h2>
<ul>
<li>무언가 만들때, 비전, 목표가 그 제품을 형성한다.</li>
<li>Webpack은 CommonJS 기반의 통합 환경을 목표로 잡았다.</li>
<li>Webpack은 통합을 이루었으니, 이 후, 성능 개선에 집중하고 있다.</li>
<li>Rollup은 ESM 기반의 확장을 목표로 잡았다.</li>
<li>Rollup은 라이브러리 만드는데 친화적이며, 그렇기에 성능적으로 유리하다.</li>
<li>Webpack과 Rollup의 중용점을 지향하는 번들러가 Vite라고 생각한다.</li>
<li>Webpack의 개선이냐, Vite의 통합 목표로 인한 혁신이냐, 결론이 궁금하다.</li>
</ul>
<blockquote>
</blockquote>
<p>내용을 정리하자면 아래와 같을 것 같다.</p>
<ol>
<li>대형 애플리케이션 : Webpack</li>
<li>소규모 애플리케이션 : Vite</li>
<li>라이브러리 : Vite or Rollup</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[💻 20240424 우아콘 TanStack Query & Zustand ]]></title>
            <link>https://velog.io/@chun_gil/2024.04.23-%EC%9A%B0%EC%95%84%EC%BD%98-TanStack-Query-Zustand</link>
            <guid>https://velog.io/@chun_gil/2024.04.23-%EC%9A%B0%EC%95%84%EC%BD%98-TanStack-Query-Zustand</guid>
            <pubDate>Wed, 24 Apr 2024 16:45:08 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-참조">📌 참조</h2>
<p><a href="https://youtu.be/nkXIpGjVxWU?si=gNeh-irXBAJFFcK-">프론트엔드 상태관리 실전편 with React Query &amp; Zustand 우아콘2023</a></p>
<hr>
<h2 id="📌-개요">📌 개요</h2>
<p>기존 조직에서 프론트엔드 상태 관리를 TanStack Query(FKA React Query) 그리고 Zustand를 도입해 표준으로 사용하고 있었다. 이것을 도입하는데 거의 예송논쟁급 탁상공론이 있었던 것으로 기억이 난다. 결과론적으로 너무 만족하면서 쓰고 있다. 그런데, 최근 동향이 이렇게 두 기술이 Client State 관리 하는데 국룰처럼 쓰이고 있는것을 확인했고, 리마인드가 필요하다고 느꼈다. 학습 자료를 찾던 중 우연히, 우아콘 2023에서 발표한 내용을 확인할 수 있었다.</p>
<hr>
<h2 id="📌-우리가-도입한-이유">📌 우리가 도입한 이유</h2>
<h3 id="tanstack-query">TanStack Query</h3>
<p>토스에서 React-Query를 주요 스택으로 사용하길래 개인 프로젝트에서 도입을 해봤었다. 결과는 대만족, Key를 통해 변화된 값만 조회할 수 있고, API 상태를 Status형태로 관리하고, Success, Error, 그리고 캐싱까지, axios를 사용할 때 일일이 만들어야 했던 불안정한 모듈들을 일괄적으로 제공하는 것이었다. 이것을 도입을 안할 이유가 없었다.</p>
<h3 id="zustand">Zustand</h3>
<p>TanStack Query가 만능은 아니다, 전역 상태 관리는 필요하다. 한창, Recoil이 페이스북에서 개발하고 Atom이라는 개념이 흥미로웠다. 하지만, v1.0이 오랜 기간동안 나오지 않는것이 불안 포인트였으며, 빠른 도입을 위해 Redux 기반에 익숙한 Zustand를 도입하기로 결정했다.</p>
<hr>
<h2 id="📌-배민이-도입한-이유">📌 배민이 도입한 이유</h2>
<h3 id="고민---상태-관리에-비대함">고민 - 상태 관리에 비대함</h3>
<ol>
<li>궁극적인 상태 관리보다 API 호출 로직이 많음</li>
<li>상태 관리를 위한 상태 관리 코드가 비대함</li>
</ol>
<h3 id="zustand-global-state">Zustand (Global State)</h3>
<ol>
<li>컴포넌트 밖에서도 상태 변경이 가능</li>
<li>사용성이 단순해 러닝커브가 낮음</li>
<li>상태관리에 필요한 코드도 적음</li>
<li>Redux Devtools 확장 프로그램 활용 가능</li>
</ol>
<h3 id="tanstack-query-api-store">TanStack-Query (API Store)</h3>
<ol>
<li>API 호출 코드로 비대해진 Store를 목적에 맞게 분리</li>
<li>리액트 훅과 비슷한 직관적인 사용성</li>
<li>여러 인터페이스 &amp; 옵션을 제공해 적은 코드로 강력한 동작</li>
<li>자체 개발도구 제공</li>
</ol>
<hr>
<h2 id="📌-기술의-도입으로-적용된-아키텍처">📌 기술의 도입으로 적용된 아키텍처</h2>
<h3 id="배민-코어-프론트엔드-아키텍처">배민 코어 프론트엔드 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/chun_gil/post/0c21c846-5b59-4534-8a46-a4220c7d8030/image.png" alt=""></p>
<h3 id="아키텍처-흐름">아키텍처 흐름</h3>
<p><img src="https://velog.velcdn.com/images/chun_gil/post/a2096624-b242-4cfe-ac36-ecabca1d900a/image.png" alt=""></p>
<h3 id="설명">설명</h3>
<ol>
<li>컴포넌트 로직 최적화 - 관심사 분리를 통해 컴포넌트 로직 최적화</li>
<li>State 활용 - Local State, Global State(Zustand), API State(TanStack Query)</li>
</ol>
<hr>
<h2 id="📌-결론">📌 결론</h2>
<ol>
<li>고민 또한 리소스라는 말이 되게 와 닿았다.</li>
<li>고민하고 고민을 해결해 줄 최적의 수를 찾는 것이 필요하다.</li>
<li>현재 스택이 정답이자 끝이 아니다. 고민은 계속된다.</li>
<li>제품이라는 관점에서 어떤 고민과 해결이 제품 그리고 조직에게 있어서 가장 효율적이고 안전한 방법인지 고민하고 나아가야 할 것 같다는 생각이 들었다.</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[💻 20240419 대용량 트래픽으로 인한 인스턴스 서버 포트 포화, 이로 인한 502 에러]]></title>
            <link>https://velog.io/@chun_gil/2024.04.19-%EB%8C%80%EC%9A%A9%EB%9F%89-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%9C-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%84%9C%EB%B2%84-%ED%8F%AC%ED%8A%B8-%ED%8F%AC%ED%99%94-%EC%9D%B4%EB%A1%9C-%EC%9D%B8%ED%95%9C-502-%EC%97%90%EB%9F%AC</link>
            <guid>https://velog.io/@chun_gil/2024.04.19-%EB%8C%80%EC%9A%A9%EB%9F%89-%ED%8A%B8%EB%9E%98%ED%94%BD%EC%9C%BC%EB%A1%9C-%EC%9D%B8%ED%95%9C-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%84%9C%EB%B2%84-%ED%8F%AC%ED%8A%B8-%ED%8F%AC%ED%99%94-%EC%9D%B4%EB%A1%9C-%EC%9D%B8%ED%95%9C-502-%EC%97%90%EB%9F%AC</guid>
            <pubDate>Fri, 19 Apr 2024 08:01:17 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-개요">📌 개요</h2>
<p>대용량 트래픽을 대응하는 서비스를 운영 중이다. N년간 정상적으로 동작하고 있었고, 이상이 없었다. 그런데 지난주에 우연히 502 에러가 뜨면서 API 호출이 정상적으로 되지 않는 이슈를 발견했다. 서버 스케일 아웃으로 처리했고, 문제가 해결됐다고 생각했으나, 금일, 새벽 6시경 몰리는 트래픽으로 같은 이슈가 발생했고, 근본적인 문제 해결이 필요할 것 같다는 진단을 내렸다.</p>
<h2 id="📌-원인-파악-1---nginx-설정-체크">📌 원인 파악 (1) - Nginx 설정 체크</h2>
<p>네이버 클라우드 플랫폼을 사용하고 있었고, 로드 밸런서(Auto Scaling), Nginx, PM2 서버로 이어지는 프로세스를 통해 백엔드를 구축했다. 간헐적으로 발생하는 502 에러와 nginx, error.log에 찍히는 99, 110, 111 에러를 확인했고, 혹시, nginx 설정에 이슈가 있을까를 의심하며 해당 에러가 뜨지 않도록 조치를 취하고자 노력했다. 반대로, nginx 설정이 이슈가 있었다면, 애초에 동작하지 않거나 이렇게 오랫동안 유지가 될 일이 없을 것 같다는 의문은 동시에 품고 있었다. 역시나, 기존 설정은 크게 문제가 없었고, 테스트를 위한 설정 변화에도 여전히 같은 이슈가 발생했다.</p>
<h2 id="📌-원인-파악-2---502-에러에-대한-고찰">📌 원인 파악 (2) - 502 에러에 대한 고찰</h2>
<p>어쨌든, 502에러는 부하로 발생한 것이라는 맥락을 잃지 않으며 다음 수를 고민했다. 결론적으로, 트래픽 증대로 인한 LB에서 Nginx로 가는 길 또는, Nginx에서 프록시 패스를 통해 실서버로 가는 길 이 두 길 중에 이슈가 있는 건 분명하다. 그래서, LB를 교체해 보았으나 실패, 그래서, 99번 에러를 보던 중, 우연히 어떤 한 포스팅을 발견했다. <a href="https://jojoldu.tistory.com/319">TIME_WAIT으로 인한 Local Port 고갈 문제</a>를 다룬 해당 글을 토대로, 트래킹을 시작했고, 같은 이슈가 있음을 확인했다.</p>
<h2 id="📌-해결---인스턴스-포트-유연성-부여">📌 해결 - 인스턴스 포트 유연성 부여</h2>
<ol>
<li>포트 범위 증대 - AS_IS : 32768 ~ 60999 / TO_BE : 10240 ~ 65535</li>
<li>TIME_WAIT 포트 즉각 재사용<pre><code>$ echo &quot;10240 65535&quot; &gt; /proc/sys/net/ipv4/ip_local_port_range
$ sudo sysctl -w &quot;net.ipv4.tcp_timestamps=1&quot;
$ sudo sysctl -w &quot;net.ipv4.tcp_tw_reuse=1&quot;</code></pre><h2 id="📌-결론---모니터링과-로그를-확실히">📌 결론 - 모니터링과 로그를 확실히</h2>
</li>
<li>완전 무결성인 프로그램은 없다. 그래서, 모니터링, 로그를 확실히</li>
<li>모니터링
2-1) 매트릭들을 대시보드, 알람 설정을 통해 실시간으로 확인할 수 있도록 설정
2-2) 모니터링 레벨을 정교하게 설정하여 닥쳐올 상황들을 인지하고 있기</li>
<li>로그
3-1) 로그 레벨별로 정밀하게 로그 쌓기
3-2) 모니터링 시점과 연결하여 확인할 수 있도록 셋팅</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[🚴‍♀ 20240418 블로그를 다시 시작하며 개발자 블로그에 대한 생각]]></title>
            <link>https://velog.io/@chun_gil/2024.04.18-%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EB%8B%A4%EC%8B%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@chun_gil/2024.04.18-%EB%B8%94%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EB%8B%A4%EC%8B%9C-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0</guid>
            <pubDate>Thu, 18 Apr 2024 04:46:38 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-intro">📌 Intro</h2>
<p>블로그를 다시 시작해야 하는 이유는 알겠는데, 나의 전례들을 돌아보았을 때 동기 부여가 되지 않았다. 네이버 블로그, 티스토리, 그리고 벨로그까지 개발자 블로그를 다양한 플랫폼에서 수차례 만들고 지우기를 반복했다. 그 이유는 이상하게도 시간이 지나 포스팅 된 글을 보면 내용이 형편없거나 내가 이런 글을 왜 적었지? 라는 생각이 들었기 때문이다. 이번에도 역시, 돌아와 지난 포스팅들을 보니 같은 생각이었고, 본질적인 원인을 탐구하기보다 블로그 플랫폼을 비난의 대상으로 돌리는 나의 모습을 볼 수 있었다. 이건 뭔가 잘못된 생각이라는 판단에 고민을 했고, 좋은 결론이 나온 것 같아 공유 및 다짐의 측면에서 해당 포스팅을 작성하고자 한다.</p>
<h2 id="📌-문제점---백과사전을-만들려고-했었다">📌 문제점 - 백과사전을 만들려고 했었다.</h2>
<p>구현하다 보면 직면하는 문제들이 많다. 그렇기에, 기존에는 모든 문제에서 정량적으로 포스팅하고자 했었다. 예를 들자면, 단순 구현 난이도가 있었던 문제, 아니면, 일정하게 오피셜하게 사용되는 문제, 이것을 내 블로그에 작성한다면, 같은 문제가 있었을 때, 사전을 펼쳐서 읽듯이 블로그를 사용하여 효율성 증대를 이끌 수 있지 않겠느냐는 생각을 했었다. 하지만 시간이 지나, 같은 문제를 직면 했을때, 나는 나의 블로그를 와서 해결책을 찾기보다, 회사 내에 작성한 공용 레퍼런스 또는 구글링, 심지어, 요즘은 ChatGPT를 통해 해결책을 찾았다. 결과적으로, 나의 포스팅보다 더욱 양질의 최신화 된 자료를 외부를 통해 확인할 수 있었기에 나의 블로그 포스팅은 돌아와서 마주 했을때 거부감으로 다가왔다.</p>
<h2 id="📌-개선점---일기를-작성하자">📌 개선점 - 일기를 작성하자.</h2>
<p>Jira Task에서도 Story, Task, Bugfix와 같은 업무별 목적성이 분명하다. 이처럼, 여러 문제에서도 수렴적으로 파고들어야 해결할 수 있는 문제들이 있고 이 문제들이 하나의 나만의 스토리가 된다고 생각한다. 이것을 히스토리로 작성하는 게 블로그 포스팅의 목적이라고 생각한다. 부연 설명으로, 일반적인 문제(A에 정답은 B이다)들은 이미 너무나도 좋은 자료가 많다. 하지만 스토리가 있는 나만의 문제들은 어찌 보면 나의 기질적인 약점이며, 이때 접근 방향성 &gt; 문제 탐구 &gt; 문제 해결 &gt; 결론과 같은 일련의 과정들을 기록함으로써 개발적인 정답을 넘어 개발자로서 나만의 문제 해결 능력과 방법도 함께 볼 수 있다. 이것이 내가 보기에는 나의 해결 프로세스를 바라보며 더 나은 개선된 나를 찾을 수 있을 것 같고 남이 보기에는 나라는 개발자가 어떤 개발자인지 확인할 수 있는 블로그 포스팅이지 않나 싶다. 왜냐하면, 차가운 결과만 작성 하는 것이 아닌 그 안에 우여곡절들이 모두 들어가 있고, 치열한 문제 해결의 반복 끝에 나온 결론이기 때문이다.</p>
<h2 id="📌-결론">📌 결론</h2>
<p>삶 속에서 매일 다른 문제들이 우리를 흔들듯이 개발자의 삶 또한 여러 터지는 문제들을 어떻게 더욱 지혜롭게 해결하느냐에 연속인 것 같다. 일련의 상황들을 기록함으로써 지속해서 복기할 수 있는 일기를 작성 하는 것 이것이 내가 내린 블로그 포스팅에 대한 결론이며, 앞으로 나의 포스팅은 이와 같은 나를 여실히 보여줄 수 있는 포스팅 위주로 작성될 예정이다. 새로운 출발이 기대되며, 함께 이어지는 앞으로의 개발자 커리어 또한 기대된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Query, staleTime, cacheTime 정리]]></title>
            <link>https://velog.io/@chun_gil/React-Query-staleTime-cacheTime-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@chun_gil/React-Query-staleTime-cacheTime-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Sun, 06 Mar 2022 06:41:04 GMT</pubDate>
            <description><![CDATA[<h2 id="출처">출처</h2>
<blockquote>
<p><a href="https://velog.io/@yrnana/React-Query%EC%97%90%EC%84%9C-staleTime%EA%B3%BC-cacheTime%EC%9D%98-%EC%B0%A8%EC%9D%B4">https://velog.io/@yrnana/React-Query%EC%97%90%EC%84%9C-staleTime%EA%B3%BC-cacheTime%EC%9D%98-%EC%B0%A8%EC%9D%B4</a></p>
</blockquote>
<ul>
<li>기록 목적으로 퍼왔습니다.</li>
<li>문제가 될 시, 바로 삭제하겠습니다.</li>
</ul>
<hr>
<h2 id="react-query의-라이프-사이클">React Query의 라이프 사이클</h2>
<ul>
<li>A 쿼리 인스턴스가 mount 됨</li>
<li>네트워크에서 데이터 fetch 하고 A라는 query key로 캐싱함</li>
<li>이 데이터는 fresh 상태에서 staleTime(기본값 0) 이후 stale 상태로 변경됨</li>
<li>A 쿼리 인스턴스가 unmount 됨</li>
<li>캐시는 cacheTime(기본값 5min) 만큼 유지되다가 가비지 콜렉터로 수집됨</li>
<li>만일 cacheTime이 지나기 전에 A 쿼리 인스턴스가 새롭게 mount되면, fetch가 행되고 fresh한 값을 가져오는 동안 캐시 데이터를 보여줌</li>
</ul>
<h2 id="staletime">staleTime</h2>
<ul>
<li>데이터가 fresh -&gt; stale 상태로 변경되는데 걸리는 시간</li>
<li>fresh 상태일때는 쿼리 인스턴스가 새롭게 mount 되어도 네트워크 fetch가 일어나지 않는다.</li>
<li>데이터가 한번 fetch 되고 나서 staleTime이 지나지 않았다면 unmount 후 mount 되어도 fetch가 일어나지 않는다.
cacheTime</li>
<li>데이터가 inactive 상태일 때 캐싱된 상태로 남아있는 시간
쿼리 인스턴스가 unmount 되면 데이터는 inactive 상태로 변경되며, 캐시는 cacheTime만큼 유지된다.</li>
<li>cacheTime이 지나면 가비지 콜렉터로 수집된다.</li>
<li>cacheTime이 지나기 전에 쿼리 인스턴스가 다시 마운트 되면, 데이터를 fetch하는 동안 캐시 데이터를 보여준다.</li>
<li>cacheTime은 staleTime과 관계없이, 무조건 inactive 된 시점을 기준으로 캐시 데이터 삭제를 결정한다.</li>
</ul>
<h2 id="그-외">그 외</h2>
<ul>
<li>isFetching : 데이터가 fetch될 때 true, 캐싱 데이터가 있어서 백그라운드에서 fetch되더라도 true</li>
<li>isLoading : 캐싱된 데이터가 없을때 fetch 중에 true</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 배포 스크립트 만들기]]></title>
            <link>https://velog.io/@chun_gil/React-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@chun_gil/React-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 20 Feb 2022 08:47:18 GMT</pubDate>
            <description><![CDATA[<h2 id="선행">선행</h2>
<blockquote>
<p>SSH 통신 사전적 정의</p>
</blockquote>
<hr>
<h2 id="목표">목표</h2>
<blockquote>
</blockquote>
<ol>
<li>CI / CD를 구축하고 싶지만 여건이 안될 때</li>
<li>CI / CD 구축이 헤비 스펙일 때</li>
<li>라이트하면서 간단하게 프로젝트를 배포하고 싶을때</li>
</ol>
<hr>
<h2 id="1-ssh-키-생성">1. SSH 키 생성</h2>
<ul>
<li>SSH 키 연동을 통해 패스워드 없이 실서버에 접속하기 위함</li>
<li>커스텀 경로 지정을 하고자 한다면, 경로를 입력한다.</li>
<li>패스워드는 불편할 수도 있으니 패스 !!!</li>
</ul>
<pre><code class="language-bash">$ ssh-keygen -t rsa -b 4096 -C &quot;Email_Address&quot;</code></pre>
<br />

<h2 id="2-ssh-키-서버-저장">2. SSH 키 서버 저장</h2>
<h3 id="2-1-직접-서버에-등록">2-1) 직접 서버에 등록</h3>
<ol>
<li>클라이언트에서 id_rsa.pub 내용을 복사한다.</li>
<li>서버로 이동하여 .ssh/authorized_keys 내에 붙여넣기 후 저장한다.</li>
</ol>
<h3 id="2-2-클라이언트-유틸리티-활용">2-2) 클라이언트 유틸리티 활용</h3>
<pre><code class="language-bash">$ ssh-copy-id id_rsa.pub 서버계정@서버id // 기본 포트 일 경우
$ ssh-copy-id id_rsa.pub &#39;-p 포트 서버계정@서버id&#39; // 기본 포트가 아닐 경우</code></pre>
<br />

<h2 id="3-접속-테스트">3. 접속 테스트</h2>
<ul>
<li>위 과정을 정상적으로 수행했다면, 별도 패스워드 없이 접속이 가능하다.<pre><code class="language-bash">$ ssh [host_name]@[server_ip]</code></pre>
</li>
</ul>
<br />

<h2 id="4-스크립트-작성">4. 스크립트 작성</h2>
<ul>
<li>서버에 프로젝트가 Clone이 되었다는 가정</li>
<li>최초 배포 이 후, 배포할 때 사용할 수 있다.</li>
</ul>
<pre><code class="language-shell">HOST=[server_ip]
USER=[server_host_name]
DEST_REPO=[project_repository]
BRANCH=[project_branch]

ssh $USER@$HOST &quot;cd $DEST_REPO &amp;&amp; git checkout $BRANCH &amp;&amp; git reset --hard HEAD~1 &amp;&amp; git pull &amp;&amp; yarn &amp;&amp; yarn build &amp;&amp; service nginx restart&quot;</code></pre>
<blockquote>
</blockquote>
<h3 id="스크립트-설명">스크립트 설명</h3>
<ol>
<li>프로젝트로 이동</li>
<li>배포하고자 하는 브랜치로 checkout</li>
<li>충돌 방지를 위해 현재 commit에서 한칸 뒤로 이동</li>
<li>pull을 통해 최신 리모트 반영</li>
<li>build 수행</li>
<li>nginx 재실행 (저는 SPA라 nginx로 배포했습니다)</li>
</ol>
<br />

<h2 id="5-주의사항">5. 주의사항</h2>
<h3 id="5-1-ssh-통신이-가능한-상태인지-확인">5-1) SSH 통신이 가능한 상태인지 확인</h3>
<ul>
<li>커넥션 성공 여부 파악</li>
<li>커넥션에 실패한다면 두 가지 작업이 필요하다
  1) Server outbound 규칙 port 22 추가
  2) Server 방화벽 port 22 해제</li>
</ul>
<pre><code class="language-bash">$ telnet bitbucket.org 22</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zoom SDK 연동 - 3) Zoom SDK 사용하기]]></title>
            <link>https://velog.io/@chun_gil/Zoom-SDK-%EC%97%B0%EB%8F%99-3-Zoom-SDK-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@chun_gil/Zoom-SDK-%EC%97%B0%EB%8F%99-3-Zoom-SDK-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 22 Jan 2022 12:56:01 GMT</pubDate>
            <description><![CDATA[<h2 id="1-sample-예제">1. Sample 예제</h2>
<blockquote>
<p><a href="https://github.com/zoom/meetingsdk-sample-react">https://github.com/zoom/meetingsdk-sample-react</a></p>
</blockquote>
<hr>
<h2 id="2-zoom-sdk-초기화">2. Zoom SDK 초기화</h2>
<h3 id="2-1-setting-zoom-sdk">2-1) Setting Zoom SDK</h3>
<pre><code class="language-javascript">import { ZoomMtg } from &#39;@zoomus/websdk&#39;;

ZoomMtg.setZoomJSLib(&#39;https://source.zoom.us/2.1.1/lib&#39;, &#39;/av&#39;);
ZoomMtg.preLoadWasm();
ZoomMtg.prepareWebSDK();
ZoomMtg.i18n.load(&#39;ko-KO&#39;);
ZoomMtg.i18n.reload(&#39;ko-KO&#39;);</code></pre>
<ul>
<li>setZoomJSLib 메소드 호출 시, CDN 링크 이외에 오동작 하는 이슈가 있어서, 
CDN 링크를 사용했습니다.</li>
<li>위 코드 실행 시, 개발자가 작성한 React App 컴포넌트 외부에 Zoom 컴포넌트가 생성됩니다.</li>
</ul>
<h3 id="2-2-zoom-컴포넌트-제어">2-2) Zoom 컴포넌트 제어</h3>
<pre><code class="language-javascript">const zoomMeetingSDK = document.getElementById(&quot;zmmtg-root&quot;)

// To hide
zoomMeetingSDK.style.display = &#39;none&#39;;

// To show
zoomMeetingSDK.style.display = &#39;block&#39;;</code></pre>
<pre><code class="language-css">/* To hide */
#zmmtg-root {
    display: none;
}

/* To show */
#zmmtg-root {
    display: block;
}</code></pre>
<ul>
<li>생성된 Zoom 컴포넌트는 위와 같은 방식으로 숨깁니다.</li>
<li>실제 SDK를 통해 Zoom Client를 띄울 때, display 속성을 &#39;block&#39;으로 변경</li>
</ul>
<hr>
<h2 id="3-zoom-클라이언트-띄우기">3. Zoom 클라이언트 띄우기</h2>
<blockquote>
<p>Meeting : <a href="https://marketplace.zoom.us/docs/sdk/native-sdks/web/client-view/meetings">https://marketplace.zoom.us/docs/sdk/native-sdks/web/client-view/meetings</a>
Webinar : <a href="https://marketplace.zoom.us/docs/sdk/native-sdks/web/client-view/webinars">https://marketplace.zoom.us/docs/sdk/native-sdks/web/client-view/webinars</a></p>
</blockquote>
<h3 id="3-1-generatesignature">3-1) generateSignature</h3>
<pre><code class="language-javascript">const signature = ZoomMtg.generateSignature({ apiKey, apiSecret, meetingNumber, role });</code></pre>
<h3 id="3-2-zoomsdkinit">3-2) ZoomSDK.init</h3>
<blockquote>
<p>Reference : <a href="https://marketplace.zoom.us/docs/sdk/native-sdks/web/client-view/reference">https://marketplace.zoom.us/docs/sdk/native-sdks/web/client-view/reference</a></p>
</blockquote>
<pre><code class="language-javascript">ZoomMtg.init({
  leaveUrl: leaveUrl,
  success: (success) =&gt; {
    console.log(success)
    ZoomMtg.join({
      ...
    })
  },
  error: (error) =&gt; {
    console.log(error)
  }
})</code></pre>
<ul>
<li>leaveUrl(required) : Zoom Client 종료 시, 이동할 페이지 지정</li>
<li>상단 레퍼런스 링크를 참고, 파라미터를 통해 Zoom Client 초기화 가능</li>
</ul>
<h3 id="3-3-zoomsdkjoin">3-3) ZoomSDK.join</h3>
<pre><code class="language-javascript">ZoomMtg.join({
    signature: signature, // required
    apiKey: apiKey, // required
    meetingNumber: meetingNumber, // required
    userName: userName, // required
    userEmail: userEmail, // required
    passWord: passWord, // optional
    success: (success) =&gt; {
        console.log(success)
    },
    error: (error) =&gt; {
        console.log(error)
    }
})</code></pre>
<ul>
<li>Signature : 사용자에 대한 서명, 검증된 사용자 체크</li>
<li>apiKey : Zoom App Credentials</li>
<li>meetingNumber : 실제 참여하고자 하는 미팅룸 번호</li>
<li>userName : 참가자 이름</li>
<li>userEmail : 참가자 이메일</li>
<li>passWord : 미팅룸 패스워드 (패스워드 지정 시)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zoom SDK 연동 - 2) Zoom SDK 살펴보기]]></title>
            <link>https://velog.io/@chun_gil/Zoom-SDK-%EC%97%B0%EB%8F%99-2-Zoom-SDK-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@chun_gil/Zoom-SDK-%EC%97%B0%EB%8F%99-2-Zoom-SDK-%EC%82%B4%ED%8E%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 22 Jan 2022 12:40:48 GMT</pubDate>
            <description><![CDATA[<h2 id="1-zoom-web-sdk">1. Zoom Web SDK</h2>
<p><img src="https://images.velog.io/images/chun_gil/post/21eb3e94-b21d-4ab2-b610-46cac7a1f800/image.png" alt=""></p>
<ul>
<li>크게, 기본 사용 방법, Component view, Client View로 나뉘어져 있다.</li>
<li>Component View는 릴리즈 된지 얼마 되지 않아 안정적이지 못했다</li>
<li>그래서, 이번 포스팅은 Use Client View 카테고리를 이용해 작성하겠다.</li>
</ul>
<hr>
<h2 id="2-zoom-web-sdk-설치">2. Zoom Web SDK 설치</h2>
<h3 id="2-1-npm">2-1) npm</h3>
<pre><code class="language-bash">$ npm install @zoomus/websdk --save</code></pre>
<h3 id="2-2-cdn">2-2) CDN</h3>
<blockquote>
<p><a href="https://marketplace.zoom.us/docs/changelog#labels/meeting-sdk-web">Zoom SDK Version 정보 페이지로 이동</a></p>
</blockquote>
<pre><code class="language-html">&lt;!-- CSS --&gt;
&lt;head&gt;
    &lt;!-- For Web Client View: import Web Meeting SDK CSS --&gt;
    &lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;https://source.zoom.us/{VERSION_NUMBER}/css/bootstrap.css&quot; /&gt;
    &lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;https://source.zoom.us/{VERSION_NUMBER}/css/react-select.css&quot; /&gt;
&lt;/head&gt;

&lt;!-- JS --&gt;
&lt;body&gt;
    &lt;!-- For either view: import Web Meeting SDK JS dependencies --&gt;
    &lt;script src=&quot;https://source.zoom.us/{VERSION_NUMBER}/lib/vendor/react.min.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://source.zoom.us/{VERSION_NUMBER}/lib/vendor/react-dom.min.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://source.zoom.us/{VERSION_NUMBER}/lib/vendor/redux.min.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://source.zoom.us/{VERSION_NUMBER}/lib/vendor/redux-thunk.min.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;https://source.zoom.us/{VERSION_NUMBER}/lib/vendor/lodash.min.js&quot;&gt;&lt;/script&gt;

    &lt;!-- For Component View --&gt;
    &lt;script src=&quot;https://source.zoom.us/{VERSION_NUMBER}/zoom-meeting-embedded-{VERSION_NUMBER}.min.js&quot;&gt;&lt;/script&gt;

    &lt;!-- For Client View --&gt;
    &lt;script src=&quot;https://source.zoom.us/zoom-meeting-{VERSION_NUMBER}.min.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
</code></pre>
<hr>
<h2 id="3-signature-생성">3. Signature 생성</h2>
<h3 id="3-1-signature-">3-1) Signature (?)</h3>
<ul>
<li>Zoom SDK는 파라미터 조합으로 Signature를 만들어 사용자를 식별합니다.</li>
</ul>
<p><img src="https://images.velog.io/images/chun_gil/post/339d37f5-892d-44ae-9c32-fbdee7b70f98/image.png" alt=""></p>
<h3 id="3-2-signature-생성">3-2) Signature 생성</h3>
<blockquote>
<p>Zoom Web SDK에서 내장 메소드로 제공하기 때문에 사용하시면 됩니다.</p>
</blockquote>
<pre><code class="language-javascript">import { ZoomMtg } from &#39;@zoomus/websdk&#39;;
ZoomMtg.generateSignature({ apiKey, apiSecret, meetingNumber, role });</code></pre>
<ul>
<li>apiKey : JWT App Credential API_KEY</li>
<li>apiSecret : JWT App Credential API_SECRET</li>
<li>meetingNumber : 입장하고자 하는 미팅룸 번호</li>
<li>role : 0 (참가자), 1 (호스트)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Zoom SDK 연동 - 1) App Marketplace]]></title>
            <link>https://velog.io/@chun_gil/Zoom-SDK-%EC%97%B0%EB%8F%99-1-App-Marketplace</link>
            <guid>https://velog.io/@chun_gil/Zoom-SDK-%EC%97%B0%EB%8F%99-1-App-Marketplace</guid>
            <pubDate>Sat, 22 Jan 2022 12:21:59 GMT</pubDate>
            <description><![CDATA[<h2 id="1-zoom-계정-생성">1. Zoom 계정 생성</h2>
<h2 id="2-사용하고자-하는-서비스-선택">2. 사용하고자 하는 서비스 선택</h2>
<ul>
<li><a href="https://zoom.us/pricing">Zoom Price 페이지로 이동</a></li>
<li>Meeting : 소규모, 양방향 화상 회의</li>
<li>Webinar : 대규모, 양방향 화상 회의, 참석자 기능(Only Viewer)</li>
<li>서비스 선택 후, 자신에게 맞는 인원에 맞게 추가 결제가 필요합니다.</li>
</ul>
<hr>
<h2 id="3-app-marketplace">3. App Marketplace</h2>
<ul>
<li><a href="https://marketplace.zoom.us/">Zoom App Marketplace 페이지로 이동</a></li>
</ul>
<ol>
<li>Zoom 계정으로 로그인</li>
<li>상단 우측 메뉴바 &gt; Develop Select &gt; Build App 이동
<img src="https://images.velog.io/images/chun_gil/post/1013885c-f689-48c6-9f3c-19b8f11af01c/image.png" alt=""></li>
</ol>
<hr>
<h2 id="4-zoom-jwt-token-발행">4. Zoom JWT Token 발행</h2>
<ul>
<li>저는 이미 발행을 해서 View More만 가능합니다.</li>
<li>Zoom JWT Token은 SDK 사용에 있어서 필수입니다.
<img src="https://images.velog.io/images/chun_gil/post/67e580f7-8d86-42d6-8d77-37abd5aef63b/image.png" alt=""></li>
</ul>
<hr>
<h2 id="5-zoom-jwt-token-확인">5. Zoom JWT Token 확인</h2>
<ul>
<li>API Key, API Secret 요소는 SDK 사용에 있어서 필수값입니다.</li>
<li>JWT Token은 Zoom API를 사용함에 있어서 Authrization 속성으로 사용됩니다.</li>
</ul>
<p><img src="https://images.velog.io/images/chun_gil/post/6fea4d38-5ed8-47bd-928e-1a28c5ffabd3/tempsnip.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSH 키를 통한 서버 SSH 접속]]></title>
            <link>https://velog.io/@chun_gil/SSH-%ED%82%A4%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%84%9C%EB%B2%84-SSH-%EC%A0%91%EC%86%8D</link>
            <guid>https://velog.io/@chun_gil/SSH-%ED%82%A4%EB%A5%BC-%ED%86%B5%ED%95%9C-%EC%84%9C%EB%B2%84-SSH-%EC%A0%91%EC%86%8D</guid>
            <pubDate>Thu, 13 Jan 2022 13:28:40 GMT</pubDate>
            <description><![CDATA[<h2 id="1-서론">1. 서론</h2>
<p>기본적으로 제공하는 ID / PW 방식에 인증 방법이 있다.
하지만, 매번 입력을 해야 한다는 번거로움과 동시에
스크립트를 통해 접근할 때 비밀번호 안정성에 대한 꺼림직한 부분이 있다.</p>
<p>그래서, 다른 방법인 <strong>공개키, 개인키 기반에 SSH 방식</strong>을 통해
비밀번호를 노출 시키지 않고 안전하게 서버에 접속하고자 한다.</p>
<hr>
<h2 id="2-동작-방식">2. 동작 방식</h2>
<ol>
<li>클라이언트에서 공개키와 개인키를 생성</li>
<li>생성한 공개키를 서버에 전달</li>
<li>서버 접속 시 클라이언트의 개인키로 로그인</li>
</ol>
<hr>
<h2 id="3-동작-원리">3. 동작 원리</h2>
<ol>
<li>클라이언트에서 개인키로 메시지를 암호화</li>
<li>서버에서 전달 받은 공개키로 메시지를 복호화</li>
<li>인증 확인</li>
</ol>
<hr>
<h2 id="4-실습">4. 실습</h2>
<h3 id="4-1-클라이언트-키-생성">4-1) 클라이언트 키 생성</h3>
<ul>
<li>출력되는 요소들은 Enter로 패스</li>
<li>커스텀한 설정을 하고자 한다면 해당 내용을 읽고 입력</li>
</ul>
<pre><code class="language-bash">$ ssh-keygen -t rsa</code></pre>
<h3 id="4-2-클라이언트-키-생성-확인">4-2) 클라이언트 키 생성 확인</h3>
<pre><code class="language-bash">$ cd ~/.ssh/
$ ls -al

// id_rsa : 개인키
// id_rsa.pub : 공개키</code></pre>
<h3 id="4-3-클라이언트-공개키-서버로-전송">4-3) 클라이언트 공개키 서버로 전송</h3>
<ol>
<li>해당 명령어를 통해 서버로 공개키를 전송한다.</li>
<li>서버 계정에 대한 패스워드를 알아야 전송이 가능하다.</li>
</ol>
<pre><code class="language-bash">$ ssh-copy-id -i ~/.ssh/id_rsa.pub username@[ip_address]
$ password :</code></pre>
<h3 id="4-4-ssh-접속">4-4) SSH 접속</h3>
<pre><code class="language-bash">$ ssh username@[ip_address]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[CentOS 7 React SPA 프로젝트 띄우기 - (2) Repository 연동]]></title>
            <link>https://velog.io/@chun_gil/CentOS-7-React-SPA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%9D%84%EC%9A%B0%EA%B8%B0-2-Repository-%EC%97%B0%EB%8F%99</link>
            <guid>https://velog.io/@chun_gil/CentOS-7-React-SPA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%9D%84%EC%9A%B0%EA%B8%B0-2-Repository-%EC%97%B0%EB%8F%99</guid>
            <pubDate>Wed, 12 Jan 2022 03:04:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>본 예제는 Bitbucket을 사용했습니다.</p>
</blockquote>
<h2 id="1-access-key-등록">1. Access key 등록</h2>
<ul>
<li>Git 명령어를 쓸 때, 매번 패스워드를 입력해야 하는 번거로움 제거</li>
<li>SSH 형식으로 Git clone 후, 사용하기 위한 목적</li>
</ul>
<h3 id="1-1-ssh-key-생성">1-1) ssh key 생성</h3>
<ul>
<li>출력되는 키를 복사한다.<pre><code class="language-bash">$ ssh-keygen -t rsa
$ cat /root/.ssh/id_rsa.pub</code></pre>
</li>
</ul>
<h3 id="1-2-access-keys--add-key">1-2) Access keys &gt; Add key</h3>
<p><img src="https://images.velog.io/images/chun_gil/post/9b195ee1-787e-41a6-bf3a-9ecac2509d9c/image.png" alt=""></p>
<h3 id="1-3-access-keys-등록-확인">1-3) Access keys 등록 확인</h3>
<p><img src="https://images.velog.io/images/chun_gil/post/4949590e-9a2c-4ffc-bc54-9d21bc9aa50f/tempsnip.png" alt=""></p>
<hr>
<h2 id="2-repository-가져오기">2. Repository 가져오기</h2>
<ul>
<li>Repository를 SSH 형식으로 clone</li>
<li>해당 서버에 원하는 위치에 clone</li>
<li>build script 실행
<img src="https://images.velog.io/images/chun_gil/post/f7d40dd9-1d98-4e21-84b2-0624a84112e1/tempsnip2.png" alt=""></li>
</ul>
<hr>
<h2 id="3-nginx-수정">3. nginx 수정</h2>
<ul>
<li>/etc/nginx/conf.d/default.conf, 원본 유지를 위해 복사 본 백업</li>
<li>/etc/nginx/conf.d/default.conf 수정</li>
<li>프로젝트 권한에 따라, 403 Forbidden</li>
<li>chmod 755를 통해 프로젝트에 대한 적절한 권한을 부여할 것</li>
</ul>
<pre><code class="language-bash">...
location / {
 root [path_project];
 ...
}
...</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[CentOS 7 React SPA 프로젝트 띄우기 - (1) Nginx 구축]]></title>
            <link>https://velog.io/@chun_gil/CentOS-7-React-SPA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%9D%84%EC%9A%B0%EA%B8%B0-1-Nginx-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@chun_gil/CentOS-7-React-SPA-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%9D%84%EC%9A%B0%EA%B8%B0-1-Nginx-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Wed, 12 Jan 2022 02:51:15 GMT</pubDate>
            <description><![CDATA[<h2 id="1-nginx-설치">1. Nginx 설치</h2>
<h3 id="1-1-nginxrepo-추가">1-1) nginx.repo 추가</h3>
<ul>
<li>centOS는 yum을 사용하기 때문에 nginx.repo가 없다.</li>
<li>별도로 추가해줘야 하는 사항</li>
</ul>
<h3 id="repo-추가-경로">repo 추가 경로</h3>
<pre><code class="language-bash">$ cd /etc/yum.repos.d
$ vi nginx.repo</code></pre>
<h3 id="repo-추가">repo 추가</h3>
<pre><code class="language-bash">[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/7/$basearch/
gpgcheck=0
enabled=1</code></pre>
<h3 id="1-2-nginx-설치">1-2) nginx 설치</h3>
<pre><code class="language-bash">$ yum install -y nginx</code></pre>
<hr>
<h2 id="2-방화벽-포트-개방">2. 방화벽 포트 개방</h2>
<ul>
<li>외부에서 접근 가능하게할 포트 개방</li>
</ul>
<pre><code class="language-bash">$ systemctl start firewalld
$ firewall-cmd --permanent --zone=pubilc --add-port=80/tcp
$ firewall-cmd --reload
$ firewall-cmd --list-ports</code></pre>
<hr>
<h2 id="3-nginx-테스트">3. nginx 테스트</h2>
<h3 id="3-1-nginx-명령어">3-1) nginx 명령어</h3>
<pre><code class="language-bash">$ service nginx start // 시작
$ service nginx stop // 정지
$ service nginx restart // 재시작
$ service nginx reload // 설정파일을 재로드
$ service nginx status // 현재 상태</code></pre>
<h3 id="3-2-실행-확인">3-2) 실행 확인</h3>
<ul>
<li>nginx는 기본적으로 제공해주는 페이지가 있다.</li>
<li>nginx가 동작하고 있다면 외부로부터 접근이 가능하다.</li>
<li>간단하게, localhost 접속 후, 확인해보자</li>
</ul>
<p><img src="https://images.velog.io/images/chun_gil/post/e1e48c93-5c15-426e-9f2f-664b2256a561/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Router, 3가지 사용 방법]]></title>
            <link>https://velog.io/@chun_gil/React-Router-3%EA%B0%80%EC%A7%80-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@chun_gil/React-Router-3%EA%B0%80%EC%A7%80-%EC%82%AC%EC%9A%A9-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Mon, 03 Jan 2022 01:55:40 GMT</pubDate>
            <description><![CDATA[<h2 id="1-route-자식-요소에-component-추가">1. Route 자식 요소에 Component 추가</h2>
<pre><code class="language-javascript"> &lt;Route&gt;
   &lt;APage /&gt;
 &lt;/Route&gt;

const APage = () =&gt; &lt;div&gt;Hello World !!!&lt;/div&gt;;</code></pre>
<ul>
<li>APage를 라우터에 적용한다</li>
</ul>
<hr>
<h2 id="2-route-component-속성-사용">2. Route component 속성 사용</h2>
<pre><code class="language-javascript"> &lt;Route component={APage} /&gt;

const APage = ({history, location, match}) =&gt; &lt;div&gt;Hello World !!!&lt;/div&gt;;</code></pre>
<ul>
<li>APage를 라우터에 적용한다</li>
<li>Props 속성으로 history, location, match 전달 받는다</li>
</ul>
<hr>
<h2 id="3route-render-속성-사용">3.Route render 속성 사용</h2>
<pre><code class="language-javascript"> &lt;Route render={APage} isActive={false} /&gt;

const APage = (props: any) =&gt; &lt;div&gt;Hello World !!!&lt;/div&gt;;</code></pre>
<ul>
<li>APage를 라우터에 적용한다</li>
<li>render 적용 시, 2번 항목과 동일하게 router 정보를 제공 받는다.</li>
<li>Route를 감싸고 있는 컴포넌트에서 이외에 속성을 props로 넘겨줄 수 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Router v5 예제 모음]]></title>
            <link>https://velog.io/@chun_gil/React-Router-v5-%EC%98%88%EC%A0%9C-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@chun_gil/React-Router-v5-%EC%98%88%EC%A0%9C-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Fri, 31 Dec 2021 13:29:56 GMT</pubDate>
            <description><![CDATA[<h2 id="1-예제를-위한-cra-설치">1. 예제를 위한 CRA 설치</h2>
<pre><code class="language-javascript">npx create-react-app my-app --template typescript
# or
yarn create react-app my-app --template typescript</code></pre>
<h2 id="2-react-router-설치">2. React Router 설치</h2>
<pre><code class="language-javascript">yarn add react-router-dom
yarn add @types/react-router-dom</code></pre>
<h2 id="3-1-기본-예제---non-exact">3-1. 기본 예제 - Non exact</h2>
<ul>
<li>exact 속성이 없는 경우, path depth를 기준으로 상속 관계를 가진다.</li>
<li>많은 depth를 가진 라우터부터 규칙을 지키며 작성해야 한다.<pre><code class="language-typescript">import { Route, Switch } from &#39;react-router-dom&#39;;
import { FirstPage } from &#39;./pages/FirstPage&#39;;
import { SecondPage } from &#39;./pages/SecondPage&#39;;
import { ThirdPage } from &#39;./pages/ThirdPage&#39;;
import React from &#39;react&#39;;
</code></pre>
</li>
</ul>
<p>export const Routes = () =&gt; (
    <Switch>
        <Route path="/third">
            <ThirdPage />
        </Route>
        <Route path="/second">
            <SecondPage />
        </Route>
        <Route path="/">
            <FirstPage />
        </Route>
    </Switch>
);</p>
<pre><code>
## 3-2. 기본 예제 - exact
```typescript
export const Routes = () =&gt; (
    &lt;Switch&gt;
        &lt;Route path=&quot;/&quot; exact={true}&gt;
            &lt;FirstPage /&gt;
        &lt;/Route&gt;
        &lt;Route path=&quot;/second&quot; exact={true}&gt;
            &lt;SecondPage /&gt;
        &lt;/Route&gt;
        &lt;Route path=&quot;/third&quot; exact={true}&gt;
            &lt;ThirdPage /&gt;
        &lt;/Route&gt;
    &lt;/Switch&gt;
);</code></pre><h2 id="4-중첩된-라우팅">4. 중첩된 라우팅</h2>
<pre><code class="language-typescript">import React from &#39;react&#39;;
import { Link, Switch, Route, useRouteMatch, useParams } from &#39;react-router-dom&#39;;

function SubPage() {
    let { anyIdNameYouLike } = useParams();

    return (
        &lt;div&gt;
            &lt;h1&gt;{anyIdNameYouLike}&lt;/h1&gt;
        &lt;/div&gt;
    );
}

export const ThirdPage = () =&gt; {
    let { path, url } = useRouteMatch();

    return (
        &lt;div&gt;
            &lt;h1&gt;Third Page&lt;/h1&gt;
            &lt;nav&gt;
                &lt;ul&gt;
                    &lt;li&gt;
                        &lt;Link to={`${url}/3-1`}&gt;Sub-page-1&lt;/Link&gt;
                    &lt;/li&gt;
                    &lt;li&gt;
                        &lt;Link to={`${url}/3-2`}&gt;Sub-page-2&lt;/Link&gt;
                    &lt;/li&gt;
                    &lt;li&gt;
                        &lt;Link to={`${url}/3-3`}&gt;Sub-page-3&lt;/Link&gt;
                    &lt;/li&gt;
                &lt;/ul&gt;
            &lt;/nav&gt;
            &lt;hr /&gt;
            &lt;Switch&gt;
                &lt;Route exact={true} path={path}&gt;
                    &lt;h3&gt;Please select a sub-page.&lt;/h3&gt;
                &lt;/Route&gt;
                &lt;Route path={`${path}/:anyIdNameYouLike`}&gt;
                    &lt;SubPage /&gt;
                &lt;/Route&gt;
            &lt;/Switch&gt;
        &lt;/div&gt;
    );
};</code></pre>
<h2 id="5-404-page">5. 404 Page</h2>
<ul>
<li>Route로 지정 되지 않은 페이지는 404 페이지를 렌더링한다.<pre><code class="language-typescript">import { Route, Switch } from &#39;react-router-dom&#39;;
import { FirstPage } from &#39;./pages/FirstPage&#39;;
import { SecondPage } from &#39;./pages/SecondPage&#39;;
import { ThirdPage } from &#39;./pages/ThirdPage&#39;;
import React from &#39;react&#39;;
import { PageNotFound } from &#39;./pages/PageNotFound&#39;;
</code></pre>
</li>
</ul>
<p>export const Routes = () =&gt; (
    <Switch>
        <Route path="/" exact={true}>
            <FirstPage />
        </Route>
        <Route path="/second" exact={true}>
            <SecondPage />
        </Route>
        <Route path="/third">
            <ThirdPage />
        </Route>
        <Route path="*">
            <PageNotFound />
        </Route>
    </Switch>
);</p>
<p>```</p>
<h2 id="6-이외에-정보-및-출처">6. 이외에 정보 및 출처</h2>
<p><a href="https://medium.com/litslink/react-js-router-in-examples-93c778a37888">medium_Artem Diashkin</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 불변성을 적용해야 하는 이유]]></title>
            <link>https://velog.io/@chun_gil/React%EC%97%90%EC%84%9C-%EB%B6%88%EB%B3%80%EC%84%B1%EC%9D%84-%EC%A0%81%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@chun_gil/React%EC%97%90%EC%84%9C-%EB%B6%88%EB%B3%80%EC%84%B1%EC%9D%84-%EC%A0%81%EC%9A%A9%ED%95%B4%EC%95%BC-%ED%95%98%EB%8A%94-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Sun, 21 Nov 2021 07:58:39 GMT</pubDate>
            <description><![CDATA[<h2 id="서론">서론</h2>
<ol>
<li>React는 얕은 비교(Shallow Compare)을 통해 데이터 변경을 감지한다.</li>
<li>그래서, Object.assign() / Array.concat() 과 같은 내장 메소드를 통해 처리한다.</li>
<li>그런데, 2번 항목 요소들은 얕은 복사(Shallow Copy)를 수행하기 때문에
중첩된 요소에 대한 참조 값 변경은 불가능하다.</li>
<li>Immer, Immutable과 같이 불변성을 위한 써드 파티를 사용해서 
안정성 보장 및 일관된 코드를 작성할 수 있다.</li>
</ol>
<hr>
<h2 id="목차">목차</h2>
<ul>
<li>불변성 (Immutable)에 도움이 되는 써드 파티 라이브러리 확인</li>
</ul>
<hr>
<h2 id="1-immer">1. Immer</h2>
<p><img src="https://images.velog.io/images/chun_gil/post/0bfd066c-5ff5-4583-bb21-4f18b77a3a32/image.png" alt=""></p>
<ul>
<li>가볍고 단순한 사용 방법</li>
<li>produce 메소드를 통해 처리</li>
<li>React Hooks 관련 기능 제공</li>
<li>링크 : <a href="https://immerjs.github.io/immer/">https://immerjs.github.io/immer/</a></li>
</ul>
<hr>
<h2 id="2-immutable">2. Immutable</h2>
<p><img src="https://images.velog.io/images/chun_gil/post/97032740-fbc0-40d3-9f2c-f0c04d5d6f81/image.png" alt=""></p>
<ul>
<li>Immutable을 위한 다양한 Collection과 문법을 제공</li>
<li>정형화 된 형식과 다양한 기능을 제공</li>
<li>라이브러리 무게가 있고, 러닝 커브가 조금 있음</li>
<li>링크 : <a href="https://immutable-js.com/docs/v4.0.0">https://immutable-js.com/docs/v4.0.0</a></li>
</ul>
<hr>
<h2 id="3-선택">3. 선택</h2>
<ul>
<li>취향 차이로 보일 수 있다.</li>
<li>필자는 가볍고 자유로운 것을 선호하기 때문에 Immer를 우선적으로 사용</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Axios 인스턴스 구성 예제]]></title>
            <link>https://velog.io/@chun_gil/Axios-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B5%AC%EC%84%B1-%EC%98%88%EC%A0%9C</link>
            <guid>https://velog.io/@chun_gil/Axios-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EA%B5%AC%EC%84%B1-%EC%98%88%EC%A0%9C</guid>
            <pubDate>Sat, 20 Nov 2021 17:05:04 GMT</pubDate>
            <description><![CDATA[<h2 id="목표">목표</h2>
<ul>
<li>AxiosInstance 구성 보일러 플레이트 설명</li>
</ul>
<hr>
<h2 id="1-axios-인스턴스">1. Axios 인스턴스</h2>
<h3 id="1-1-axios-추상-클래스-생성">1-1) Axios 추상 클래스 생성</h3>
<ul>
<li>axios 인스턴스 생성<pre><code class="language-typescript">import axios, { AxiosInstance } from &#39;axios&#39;;
</code></pre>
</li>
</ul>
<p>abstract class HttpClient {
  protected readonly instance: AxiosInstance;</p>
<p>  public constructor(baseURL: string) {
    this.instance = axios.create({
      baseURL,
    });
  }
}</p>
<pre><code>
### 1-2) Response Interceptor 구현
- AxiosResponse 재정의
- Response Intercepor 정의

``` typescript
import axios, { AxiosInstance, AxiosResponse } from &#39;axios&#39;;

declare module &#39;axios&#39; {
  interface AxiosResponse&lt;T = any&gt; extends Promise&lt;T&gt; {}
}

abstract class HttpClient {
  protected readonly instance: AxiosInstance;

  public constructor(baseURL: string) {
    this.instance = axios.create({
      baseURL,
    });

    this._initializeResponseInterceptor();
  }

  private _initializeResponseInterceptor = () =&gt; {
    this.instance.interceptors.response.use(
      this._handleResponse,
      this._handleError,
    );
  };

  private _handleResponse = ({ data }: AxiosResponse) =&gt; data;
  protected _handleError = (error: any) =&gt; Promise.reject(error);
}</code></pre><hr>
<h2 id="2-사용-사례">2. 사용 사례</h2>
<h3 id="2-1-authorization이-없는-경우">2-1) Authorization이 없는 경우</h3>
<pre><code class="language-typescript">import HttpClient from &#39;./http-client&#39;;
import { User } from &#39;./types&#39;;

const URI_USER:string = &#39;/users&#39;;
const URI_USER_ID:string = `${URI_USER}/${id}`

class MainApi extends HttpClient {
  public constructor() {
    super(process.env.REACT_APP_API_DOMAIN);
  }

  public getUsers = () =&gt; this.instance.get&lt;User[]&gt;(URI_USER);
  public getUser = (id: string) =&gt; this.instance.get&lt;User&gt;(URI_USER_ID);
}</code></pre>
<h3 id="2-2-authorization이-있는-경우">2-2) Authorization이 있는 경우</h3>
<ul>
<li>추상화를 위해 두 사례로 나뉘어서 분리</li>
</ul>
<pre><code class="language-typescript">import { AxiosRequestConfig } from &#39;axios&#39;;
import { CreateUserBody } from &#39;./types&#39;;

const URI_USER:string = &#39;/users&#39;;

class MainApiProtected extends HttpClient {
  public constructor() {
    super(process.env.REACT_APP_API_DOMAIN);
    this._initializeRequestInterceptor();
  }

  private _initializeRequestInterceptor = () =&gt; {
    this.instance.interceptors.request.use(
      this._handleRequest,
      this._handleError,
    );
  };

  private _handleRequest = (config: AxiosRequestConfig) =&gt; {
    config.headers[&#39;Authorization&#39;] = &#39;Bearer ...&#39;;
    return config;
  };

  public createUser = (body: CreateUserBody) =&gt; this.instance.post(URI_USER, body);
}</code></pre>
<hr>
<h2 id="출처">출처</h2>
<p><a href="https://ichi.pro/ko/axios-mich-typescriptlo-http-yocheong-hyangsang-269562556460174">Axios Best Practice</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Front-End Books 정리 (지속적 업데이트)]]></title>
            <link>https://velog.io/@chun_gil/Front-End-Books-%EC%A0%95%EB%A6%AC-%EC%A7%80%EC%86%8D%EC%A0%81-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@chun_gil/Front-End-Books-%EC%A0%95%EB%A6%AC-%EC%A7%80%EC%86%8D%EC%A0%81-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Fri, 19 Nov 2021 07:04:29 GMT</pubDate>
            <description><![CDATA[<ul>
<li>제가 즐겨보고 공부하는 사이트들 기록 목적으로 작성합니다.</li>
<li>이외에, 추가적으로 좋은 사이트 발견 시, 지속적으로 업데이트 할 예정입니다.</li>
</ul>
<h1 id="📌-front-end-지식--인터뷰">📌 Front-End 지식 &amp; 인터뷰</h1>
<blockquote>
<p>프론트엔드 개발을 위한 전반적인 넓은 지식 제공
<a href="https://hangem-study.readthedocs.io/en/latest/">Front-Develop Study</a></p>
</blockquote>
<blockquote>
<p>프론트엔드 코드 설계 &amp; 작성, 형상 관리와 배포
<a href="https://peter-cho.gitbook.io/book/">실용주의 프론트엔드 개발</a></p>
</blockquote>
<blockquote>
<p>프론트엔드 관련 지식 포스팅 모음
<a href="https://github.com/Integerous/goQuality-dev-contents/tree/master/4.%20%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C#%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%9D%BC%EB%B0%98">고퀄리티 개발 컨텐츠 - 프론트엔드</a></p>
</blockquote>
<blockquote>
<p>취준생이 반드시 알아야 할 프론트엔드 지식들
<a href="https://github.com/baeharam/Must-Know-About-Frontend">baeharam님 Github</a></p>
</blockquote>
<blockquote>
<p>Tech Interview
<a href="https://github.com/JaeYeopHan/Interview_Question_for_Beginner">jaeyeopHan님 Github</a></p>
</blockquote>
<blockquote>
<p>프론트엔드 개발 지식 및 인터뷰 모음
<a href="https://github.com/Febase/FeBase">FeBase - Frontend Base</a></p>
</blockquote>
<blockquote>
<p>우아한형제들 기술 블로그
<a href="https://techblog.woowahan.com/">https://techblog.woowahan.com/</a></p>
</blockquote>
<blockquote>
<p>토스 테크
<a href="https://toss.tech/">https://toss.tech/</a></p>
</blockquote>
<hr>
<h1 id="📌-개발자가-갖춰야-할-기본-기술">📌 개발자가 갖춰야 할 기본 기술</h1>
<blockquote>
<p>리팩토링 &amp; 디자인 패턴
<a href="https://refactoring.guru/design-patterns/typescript">Refactoring GURU</a></p>
</blockquote>
<blockquote>
<p>클린 코드 타입스크립트
<a href="https://github.com/lcgyung/clean-code-typescript">clean-code-typescript</a></p>
</blockquote>
<hr>
<h1 id="📌-front-end-개발-기술">📌 Front-End 개발 기술</h1>
<blockquote>
<p>HTML &amp; CSS &amp; JavaScript 튜토리얼 - 웹 프로그래밍 입문
<a href="https://github.com/Integerous/goQuality-dev-contents/tree/master/4.%20%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C#%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%9D%BC%EB%B0%98">웹 프로그래밍 튜토리얼</a></p>
</blockquote>
<blockquote>
<p>구글 Web.dev - 더 나은 웹 프로그래밍을 위한 가이드, 한번쯤 꼭 봐야할 지식
<a href="https://web.dev/">https://web.dev/</a></p>
</blockquote>
<blockquote>
<p>캡틴 판교 웹팩 핸드북 - Webpack 입문
<a href="https://joshua1988.github.io/webpack-guide/">웹팩 핸드북</a></p>
</blockquote>
<blockquote>
<p>TypeScript 핸드북 한국어 버전 - TypeScript 입문
<a href="https://typescript-kr.github.io/">TypeScript-kr</a></p>
</blockquote>
<blockquote>
<p>TSConfig Reference
<a href="https://www.typescriptlang.org/tsconfig">tsconfig</a></p>
</blockquote>
<blockquote>
<p>React에서 TypeScript 적용하는 방법 - React + JavaScript 개발 경험자
<a href="https://react-typescript-cheatsheet.netlify.app/">React TypeScript Cheatsheets</a></p>
</blockquote>
<blockquote>
<p>자주 사용하는 Custom Hooks
<a href="https://usehooks-ts.com/react-hook/use-boolean">usehooks-ts</a></p>
</blockquote>
<blockquote>
<p>Axios 러닝 가이드
<a href="https://yamoo9.github.io/axios/">Axios</a></p>
</blockquote>
<blockquote>
<p>Redux 한글 가이드
<a href="https://lunit.gitbook.io/redux-in-korean">Redux</a></p>
</blockquote>
<blockquote>
<p>React-Query 한글 가이드
<a href="https://velog.io/@familyman80/React-Query-%ED%95%9C%EA%B8%80-%EB%A9%94%EB%89%B4%EC%96%BC">velog.io/@familyman80</a></p>
</blockquote>
<hr>
<h1 id="📌-front-end-개발-도우미">📌 Front-End 개발 도우미</h1>
<blockquote>
<p>프로젝트 번들 크기 분석
<a href="https://bundlephobia.com/">라이브러리 번들 크기 분석</a> / <a href="https://bundlephobia.com/scan">package.json 번들 크기 분석</a></p>
</blockquote>
<blockquote>
<p>써드 파티 라이브러리 CDN
<a href="https://cdnjs.com/">https://cdnjs.com/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[How to fetch data with React Hooks
정리]]></title>
            <link>https://velog.io/@chun_gil/How-to-fetch-data-with-React-Hooks%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@chun_gil/How-to-fetch-data-with-React-Hooks%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 18 Nov 2021 17:26:43 GMT</pubDate>
            <description><![CDATA[<h2 id="목표">목표</h2>
<ul>
<li>React Hooks에서 fetch data 사용 방법 확인</li>
</ul>
<hr>
<h2 id="1-react-hooks로-데이터-가져오기">1. React Hooks로 데이터 가져오기</h2>
<ul>
<li><a href="https://github.com/axios/axios">axios</a></li>
<li>useEffect Hook</li>
</ul>
<pre><code class="language-jsx">import React, { useState, useEffect } from &#39;react&#39;;
import axios from &#39;axios&#39;;

function App() {
  const [data, setData] = useState({ hits: [] });

  useEffect(() =&gt; {
    const fetchData = async () =&gt; { // (1)
      const result = await axios(
        &#39;https://hn.algolia.com/api/v1/search?query=redux&#39;,
      );

      setData(result.data);
    };

    fetchData();
  }, []); // (2)

  return (
    &lt;ul&gt;
      {data.hits.map(item =&gt; (
        &lt;li key={item.objectID}&gt;
          &lt;a href={item.url}&gt;{item.title}&lt;/a&gt;
        &lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}

export default App;</code></pre>
<ol>
<li><p>async / await 문법은 암시적으로 반환 값이 있음을 의미한다. 결과적으로, useEffect Callback 함수 대신에 내부적으로 fetch용 async / await 함수를 선언해야 한다.</p>
</li>
<li><p>해당 useEffect는 Element가 그려진 직 후, 호출된다.</p>
</li>
</ol>
<hr>
<h2 id="2-useeffect-트리거">2. useEffect 트리거</h2>
<ul>
<li>props, state 데이터와의 결합으로 트리거를 형성할 수 있다.</li>
</ul>
<pre><code class="language-jsx">function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState(&#39;redux&#39;);
  const [url, setUrl] = useState(
    &#39;https://hn.algolia.com/api/v1/search?query=redux&#39;,
  );

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      const result = await axios(url); // (3)

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    &lt;Fragment&gt;
      &lt;input
        type=&quot;text&quot;
        value={query}
        onChange={event =&gt; setQuery(event.target.value)}
      /&gt; // (1)
      &lt;button
        type=&quot;button&quot;
        onClick={() =&gt;
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      &gt; // (2)
        Search
      &lt;/button&gt;

      &lt;ul&gt;
        {data.hits.map(item =&gt; (
          &lt;li key={item.objectID}&gt;
            &lt;a href={item.url}&gt;{item.title}&lt;/a&gt;
          &lt;/li&gt;
        ))}
      &lt;/ul&gt;
    &lt;/Fragment&gt;
  );
}</code></pre>
<ol>
<li>사용자를 통해 Query Data를 입력 받는다.</li>
<li>Search 버튼 클릭 시, 입력 받은 query state를 이용하여 검색 url을 갱신한다.</li>
<li>url 변경이 감지되면 useEffect가 동작하고 데이터 패칭을 수행한다.</li>
</ol>
<hr>
<h2 id="3-커스텀-hook을-통한-data-fetching">3. 커스텀 Hook을 통한 Data fetching</h2>
<ul>
<li>API 호출 전용 Hook을 통해 Data fetching 수행</li>
</ul>
<pre><code class="language-jsx">import React, { Fragment, useState, useEffect } from &#39;react&#39;;
import axios from &#39;axios&#39;;

const useDataApi = (initialUrl, initialData) =&gt; {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() =&gt; {
    const fetchData = async () =&gt; {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return [{ data, isLoading, isError }, setUrl];
};</code></pre>
<hr>
<h2 id="4-useeffect-data-fetching-중단">4. useEffect data fetching 중단</h2>
<ul>
<li>해당 컴포넌트가 마운트 해제가 되었음에도 상태가 설정되는 이슈</li>
<li>마운트 해제된 컴포넌트 상태 설정 방지</li>
<li>내부 분기 처리 로직을 통해 해결</li>
</ul>
<pre><code class="language-jsx">const useDataApi = (initialUrl, initialData) =&gt; {
  const [url, setUrl] = useState(initialUrl);
  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() =&gt; {
    let didCancel = false;
    const fetchData = async () =&gt; {
      dispatch({ type: &#39;FETCH_INIT&#39; });
      try {
        const result = await axios(url);
        if (!didCancel) {
          dispatch({ type: &#39;FETCH_SUCCESS&#39;, payload: result.data });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: &#39;FETCH_FAILURE&#39; });
        }
      }
    };
    fetchData();
    return () =&gt; {
      didCancel = true;
    };
  }, [url]);

  return [state, setUrl];
};</code></pre>
<hr>
<h2 id="출처">출처</h2>
<p><a href="https://www.robinwieruch.de/react-hooks-fetch-data/">How to fetch data with React Hooks</a></p>
]]></description>
        </item>
    </channel>
</rss>