<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>thumb_hyeok.log</title>
        <link>https://velog.io/</link>
        <description>우아한테크코스 4기 웹 프론트엔드</description>
        <lastBuildDate>Sat, 01 Jul 2023 08:21:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>thumb_hyeok.log</title>
            <url>https://velog.velcdn.com/images/thumb_hyeok/profile/f8778adc-dbf0-4c04-b0a5-78d3b7fd54e7/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. thumb_hyeok.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/thumb_hyeok" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[리액트에 불변성이 왜 필요할까?]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90-%EB%B6%88%EB%B3%80%EC%84%B1%EC%9D%B4-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90-%EB%B6%88%EB%B3%80%EC%84%B1%EC%9D%B4-%EC%99%9C-%ED%95%84%EC%9A%94%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sat, 01 Jul 2023 08:21:03 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🤯-들어가며">🤯 들어가며</h2>
<p>최근에 모 회사에서 기술면접을 봤는데 위와 같은 질문을 받았다. 좀 더 깊게 들어가서.. “SPA라는 걸 최초로 개발했고, 그걸 아무도 모른다고 했을 때, 불변성이라는 개념이 왜 중요할까?” 라는 질문이었는데 예전에 충분히 공부했다고 생각한 주제임에도 불구하고 만족스런 답변을 하지 못했다.. </p>
<p>그게 이 글을 쓰게 된 동기이다.</p>
<hr>
<h2 id="🪨-불변성이-뭔데">🪨 ”불변성”이 뭔데?</h2>
<p>불변성이 왜 필요한 지를 알아보기 전에 불변성이 무엇인지부터 알아보자.
먼저, 자바스크립트에는 <strong>“기본형”</strong> 값과 <strong>“참조형”</strong> 값이 있다. 아래 그림을 보자.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/4e3a4a41-a9d1-4130-8391-49feecbaa1f2/image.png" alt=""></p>
<p>기본형 값은 “<strong>불변성”</strong> 을 지니고, 참조형 값은 <strong>“가변성”</strong>을 지닌다. </p>
<p>기본형 값을 재사용하기 위해서, 변수에 저장한다고 하자. 그러기 위해서는 변수를 위한 메모리 공간이 할당이 될 것이고, 해당 메모리공간의 값은 기본형 값이 저장된 메모리 주소가 저장될 것이다.<br>그러면 그 기본형 값은 메모리가 해체되어 사라지기 전까지 다른 값으로 변화할 수 없다. 변수에 다른 값을 대입한다고 한들, 메모리의 값이 변화하지 않는다.</p>
<p>한 번 코드로 살펴보자.</p>
<pre><code class="language-jsx">let num = 10; // num의 메모리 주소와 값 : 0x00000001 , 0x00000002
num = 20; // num의 메모리 주소와 값 : 0x00000001 , 0x00000003

// 10의 메모리 주소 : 0x00000002
// 20의 메모리 주소 : 0x00000003</code></pre>
<p><code>num</code> 에 저장된 <code>10</code> 이라는 값을 <code>20</code> 이라는 값으로 바꿨지만,  <code>0x0000002</code> 에 저장된 <code>10</code> 이라는 값은 <strong>불변성</strong>을 지니기 때문에 <code>20</code> 으로 변하지 않고, 새로운 메모리 공간을 할당해 (<code>0x00000003</code>) 해당 주소에 <code>20</code> 이라는 값을 저장하고 <code>num</code> 의 가리키는 메모리 주소의 값을 <code>0x00000002</code> 에서 <code>0x00000003</code> 으로 바꿨다.</p>
<p>이러한 특성을 불변성이라고 한다.</p>
<p>만약 이해가 잘 안 되었거나, 좀 더 자세히 불변성과 가변성에 대해서 알고 싶다면, <a href="%5Bhttps://velog.io/@thumb_hyeok/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%B3%80%EC%88%98%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85%5D(https://velog.io/@thumb_hyeok/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%B3%80%EC%88%98%EC%99%80-%EB%8D%B0%EC%9D%B4%ED%84%B0-%ED%83%80%EC%9E%85)">이 글</a>을 참고하도록 하자.</p>
<hr>
<h2 id="🚀-그래서-리액트에서-이게-왜-중요해">🚀 그래서 리액트에서 이게 왜 중요해?</h2>
<p>그러면 리액트에서 불변성이 왜 중요한 걸까? 첫번째로는 그걸 알기 위해서는 <strong>“리액트가 상태를 업데이트 하는 방식”</strong> 에 대해서 알고 있을 필요가 있다. </p>
<p>리액트는 상태값을 업데이트할 때, 얕은 비교를 통해 상태를 업데이트한다. 즉, 객체나 배열의 프로퍼티를 하나 하나 다 비교하는 것이 아니라, 이전 참조값과 새로운 참조값이 동일한 지만 비교한다. 이를 통해 리액트는 계산 리소스를 줄여 상태를 효율적으로 업데이트를 할 수 있다.</p>
<p>추가적으로, 기본적으로 모든 컴포넌트는 상위 컴포넌트의 상태가 변경되면 자동으로 리렌더링된다. 이는 변경의 영향을 받지 않은 하위 컴포넌트도 마찬가지이다. 이 때, 성능상의 이유로 리렌더링의 영향을 받지 않은 트리 부분을 건너뛰고 싶을 수 있다. 불변성은 컴포넌트의 데이터가 변경되었는지 여부를 비교하는 데 저렴하다.</p>
<p>두번째로는 불변성은 복잡한 기능을 구현하기 훨씬 쉽게 만든다. “<strong>특정 작업을 실행 취소하고 다시 실행하는 기능”</strong>은 일반적인 요구사항이다. 직접적인 데이터 변형을 피하면 이전 버전의 데이터를 그대로 유지하고 나중에 재사용할 수 있다. <a href="https://react.dev/learn/tutorial-tic-tac-toe#why-immutability-is-important">이에 대한 좋은 예시</a>는 여기서 확인할 수 있다. </p>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li><p><a href="https://react.dev/learn/tutorial-tic-tac-toe#why-immutability-is-important">Tutorial: Tic-Tac-Toe – React</a></p>
</li>
<li><p><a href="https://react.dev/learn/updating-objects-in-state">Updating Objects in State – React</a></p>
</li>
<li><p><a href="https://react.dev/reference/react/memo#skipping-re-rendering-when-props-are-unchanged">memo – React</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔘 디자인시스템 버튼 컴포넌트 개발기]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%94%94%EC%9E%90%EC%9D%B8%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%B2%84%ED%8A%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%94%94%EC%9E%90%EC%9D%B8%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%B2%84%ED%8A%BC-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EA%B0%9C%EB%B0%9C%EA%B8%B0</guid>
            <pubDate>Thu, 18 May 2023 03:17:25 GMT</pubDate>
            <description><![CDATA[<h2 id="🏃🏻-들어가며">🏃🏻 들어가며</h2>
<p>어쩌다보니 <a href="%5Bhttps://edu.nextstep.camp/c/QoTvUh4y/%5D(https://edu.nextstep.camp/c/QoTvUh4y/)">NEXTSTEP 리액트 강의</a>에 있는 미션에서 디자인 시스템을 개발하는 사이드 프로젝트에 참여하게 되었다. 목적은 교육생들이 리액트 학습에만 집중할 수 있도록 컴포넌트를 가져다 써 UI 구현를 편리하게 하도록 해주기 위함이었다!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/d91d4f63-de2c-4489-8b45-ff0ea9c04634/image.png" alt=""></p>
<p>디자인시스템이 적용되는 미션은 <code>페이먼츠</code> , <code>장바구니</code> 두 가지로, 나는 <strong>장바구니</strong> 미션에서 개발팀에 지원, 장바구니 미션에 사용될 범용적인 <code>Button</code> 컴포넌트를 개발하게 되었다!</p>
<hr>
<h2 id="🤔-디자인시스템은-어떻게-만들어">🤔 디자인시스템은 어떻게 만들어?</h2>
<p>디자인시스템을 꼭 한 번 만들어보고, 적용해보고 싶은 로망은 갖고 있었지만, 막상 <strong>“모든 버튼”</strong>에서 사용할 수 있는 범용적인 공통 버튼 컴포넌트를 만들어야한다고 생각하니 좀 부담감이 있었다. 어찌 됐든 내가 제대로된 컴포넌트를 제공하지 못하면 돈내고 강의 듣는 분들이 불편을 겪게 되는 셈이니까(…)</p>
<p>그래서, 나는 나름 권위있는 애플의 <a href="%5Bhttps://developer.apple.com/design/human-interface-guidelines/buttons%5D(https://developer.apple.com/design/human-interface-guidelines/buttons)"><strong>Human Interface Guidelines</strong></a>(이하 <strong>HIG</strong>)과, 다른 디자인시스템들의 버튼 컴포넌트를 보며 천천히 틀을 잡아가기 시작했다.</p>
<p><strong>HIG</strong>에서는 이렇게 말한다.</p>
<blockquote>
<p>다재다능하고 고도로 사용자 정의할 수 있는 버튼은 사람들에게 앱에서 작업을 수행하는 간단하고 친숙한 방법을 제공합니다. <strong>일반적으로 버튼은 기능을 명확하게 전달하기 위해 세 가지 속성을 결합</strong>합니다.</p>
<ul>
<li><strong>스타일.</strong> 크기, 색상 및 모양을 기반으로 하는 시각적 스타일.</li>
<li><strong>콘텐츠.</strong> 목적 전달을 위해 버튼이 표시하는 기호(인터페이스 아이콘), 텍스트 레이블 또는 둘 다.</li>
<li><strong>역할.</strong> 버튼의 의미론적 의미를 식별하고 모양에 영향을 줄 수 있는 시스템 정의 역할.</li>
</ul>
</blockquote>
<p>이를 팀장인 콤피가 만들어온 <a href="%5Bhttps://www.figma.com/file/gzQzncX4OyveGZ5lKlwGC4/DRY-%EC%9E%A5%EB%B0%94%EA%B5%AC%EB%8B%88%5D(https://www.figma.com/file/gzQzncX4OyveGZ5lKlwGC4/DRY-%EC%9E%A5%EB%B0%94%EA%B5%AC%EB%8B%88)">figma</a>를 보며 만들어보기 시작했다. 아래에서 <strong>스타일, 콘텐츠, 역할</strong>을 결합해 버튼을 만들어가는 과정을 하나 하나 살펴보도록 하자!</p>
<hr>
<h2 id="🎨-스타일">🎨 스타일</h2>
<p><strong>HIG</strong>에서는 iOS, iPadOS에서 사용되는 버튼의 집합을 제시해준다. 4가지의 버튼 스타일과 3가지의 사이즈로 총 12가지의 버튼이 존재한다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/c0d2c1dc-73f7-418f-a7e0-2c9174814794/image.png" alt=""></p>
<p><strong>Filled</strong> 스타일은 <strong>“보기에서 가장 가능성이 높은 작업”</strong>을 표시하는 버튼에 사용된다. 말이 좀 어려워서 예시를 보여주자면 아래와 같은 상황을 주로 이야기 한다. 그래서 좋은 UX를 위해서 Filled 버튼을 남발하지 말라는 이야기도 함께 쓰여있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/12b0a891-8167-40ba-9573-0ccdbbad4439/image.png" alt=""></p>
<p>나는 <strong><code>variants</code></strong> props를 제공하여 Filled(<strong>Primary</strong>), Gray(<strong>Secondary</strong>), Plain 이렇게 세 가지 스타일을 지원하기로 했다. Tinted 스타일은 figma에도 없었고, 추후에도 이 소규모 디자인시스템에서 사용될 일은 없어 보였다.</p>
<blockquote>
<p><strong>크기가 아닌 스타일을 사용하여 여러 옵션 중에서 선호하는 선택을 시각적으로 구별하십시오.</strong></p>
</blockquote>
<p>를 만족하기에도 Secondary, Plain으로 충분할 것 같았다.</p>
<p>다음으로 사이즈로 넘어가서, <strong>width</strong>는 가득 채우도록 해 상위 컴포넌트의 사이즈에 영향을 받도록 조정했다. figma의 버튼들을 모두 살펴보고 <strong>height</strong>를 기준으로 <strong>Large</strong>(48px), <strong>Medium</strong>(38px), <strong>Small</strong>(28px) 세 가지 사이즈를 제공했다. (HIG에서는 버튼의 사이즈는 최소 44x44를 권장하지만… 이미 figma가 나온 상황이라 수정 요청을 드리기가 좀 그랬다.)</p>
<pre><code class="language-tsx">export declare type ButtonVariant = keyof typeof ButtonStyles.Type;
export declare type ButtonSize = keyof typeof ButtonPadding;

// ButtonVariant = &#39;Primary&#39; | &#39;Secondary&#39; | &#39;Plain&#39;
// ButtonSize = &#39;Large&#39; | &#39;Medium&#39; | &#39;Small&#39;</code></pre>
<hr>
<h2 id="💾-콘텐츠">💾 콘텐츠</h2>
<p>콘텐츠에서 HIG가 중요하게 생각하는 세 가지는 아래와 같았다.</p>
<ul>
<li><p><strong>사람들이 버튼의 기능을 즉시 이해할 수 있도록 도와주는 버튼 콘텐츠를 만듭니다.</strong></p>
</li>
<li><p><strong>유용한 세부 정보를 제공하는 경우에만 레이블 아래에 추가 텍스트를 포함합니다.</strong></p>
</li>
<li><p><strong>즉시 완료되지 않는 작업에 대한 피드백을 제공해야 할 때 활동 표시기를 표시하도록 버튼을 구성합니다.</strong></p>
</li>
</ul>
<p>나는 모든 걸 만족하고 싶어서 이를 위한 props 네 가지를 제공했다. <code>prefixIconURL</code> , <code>suffix</code> , <code>childrun</code> , <code>loading</code> 이 이 네 가지이다. 아래에서 하나 하나가 무슨 역할을 하는지 살펴보도록 하자.</p>
<h3 id="1️⃣-버튼의-기능-즉시-이해">1️⃣ 버튼의 기능 즉시 이해</h3>
<p>버튼의 기능을 사용자가 즉시 이해하도록 <code>childrun</code> 을 통해 버튼 텍스트를 상위 컴포넌트로부터 받는 것을 기본으로 하되, <code>prefixIconURL</code> 을 통해 버튼에 맞는 인터페이스 아이콘을 제공할 수 있도록 했다. 예를 들어 이런 버튼을 디자인시스템 이용자가 편리하게 제공할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/502724d9-a266-4b6e-ac83-287d1155025e/image.png" alt=""></p>
<p>장바구니 아이콘을 제공한다면, 사용자가 버튼이 장바구니 버튼이라는 것을 텍스트만 제공하는 것보다 더 빨리 이해하는데에 도움을 줄 수 있을 것이다!</p>
<hr>
<h3 id="2️⃣-유용한-세부-정보를-제공">2️⃣ 유용한 세부 정보를 제공</h3>
<p>장바구니 버튼 같은 경우, 장바구니의 상품이 몇 개나 들어있는 지에 대한 정보를 사용자에게 제공하는 것이 좀 더 유용할 수 있다. 그러한 값들은 <code>suffix</code> 로 받아서 버튼에서 보여주기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/7d45a69e-4142-4447-aa15-075fdf16a332/image.png" alt=""></p>
<p>장바구니에 상품이 담겼는지, 몇개나 담겨있는지 같은 간단한 정보는 장바구니 페이지까지 이동하지 않고도 상품목록 페이지 등에서 즉시 확인할 수 있다는 장점이 있다.</p>
<p>물론 원한다면 아래와 같이 <code>prefixIconURL</code> 과 함께 사용할 수도 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/2970b18d-2f5f-4589-acb2-4da02ecc7ccd/image.png" alt=""></p>
<hr>
<h3 id="3️⃣-즉시-완료되지-않는-작업">3️⃣ <strong>즉시 완료되지 않는 작업</strong></h3>
<p>아까 말했듯이 위에서 <code>loading</code> 이라는 props도 버튼 컴포넌트가 받도록 구현해줬는데 API 요청등 즉시 완료되지 않는 작업일 경우 <code>boolean</code> 값을 제공해 아래와 같이 버튼 상태를 변경할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/ee543925-1676-4c6d-b94f-aa968c35e9b2/image.png" alt=""></p>
<p>버튼의 <code>cursor</code> 값은 <code>not-allowed</code> 로 변경되고, <code>opacity</code> 값도 조정되어 약간 투명해진다. 인터페이스 아이콘과는 동시에 표현할 수 없고, 둘 다 제공하면 <code>loading</code> 상태가 끝나면 <code>LoadingSpinner</code> 가 인터페이스 아이콘으로 대체된다.</p>
<hr>
<h2 id="🎭-역할">🎭 역할</h2>
<p>HIG에서는 버튼을 네 가지로 역할을 구분한다. 인용하자면 이러하다.</p>
<ul>
<li><strong>정상.</strong> 특별한 의미가 없습니다.</li>
<li><strong>주요한.</strong> 버튼은 기본 버튼입니다. 사람들이 선택할 가능성이 가장 높은 버튼입니다.</li>
<li><strong>취소.</strong> 버튼은 현재 작업을 취소합니다.</li>
<li><strong>파괴적.</strong> 버튼은 데이터를 파괴할 수 있는 작업을 수행합니다.</li>
</ul>
<p>그런데 놀랍게도, 장바구니 미션에서 사용하는 버튼에는 <strong>“취소”,</strong> <strong>“파괴적”</strong> 역할의 버튼이 없었다. 그리고 모든 <strong>“Primary”</strong> 버튼은 <strong>“주요한”</strong> 역할의 버튼이었다. 하지만 이 글을 쓰면서 깨달은 점은, 장바구니 상품을 삭제할 때 모달의 버튼은 아마 <strong>“파괴적”</strong> 버튼과 <strong>“취소”</strong> 버튼으로 구성될 것 같았다.</p>
<p>1차 스프린트가 끝나고, 2차 스프린트로 들어갈 때, 부족한 <code>role</code> 을 조금 더 고민해서 추가해보는 것을 목표로 잡고 진행하기로 했다!</p>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li><a href="https://developer.apple.com/design/human-interface-guidelines/buttons">Buttons | Apple Developer Documentation</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Noise-Effector 개발기 -리팩터링-]]></title>
            <link>https://velog.io/@thumb_hyeok/Noise-Effector-%EA%B0%9C%EB%B0%9C%EA%B8%B0-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-</link>
            <guid>https://velog.io/@thumb_hyeok/Noise-Effector-%EA%B0%9C%EB%B0%9C%EA%B8%B0-%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-</guid>
            <pubDate>Mon, 24 Apr 2023 06:32:06 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/37644df3-7c68-4fcf-bfca-0719cf94453d/image.png" alt=""></p>
<h2 id="🤔-개요">🤔 개요</h2>
<p>최근 어떤 회사의 과제테스트를 진행하고 피드백을 받았는데, 컴포넌트의 추상화에 대한 피드백을 중점적으로 받았었다. 피드백을 듣고 보니 아쉬운 점들이 꽤 많이 보였고, 개인적으로 과제 코드를 리팩터링 해보다가 사이드 프로젝트에서 리팩터링을 진행하고 포스팅을 해보면 좋을 것 같다는 생각이 들었다.</p>
<p>그래서 최근에 진행한 사이드 프로젝트인 <code>Noise Effector</code> 의 코드를 한 번 보면서 진행해볼 예정이다!</p>
<hr>
<h2 id="✅-체크리스트-살펴보기">✅ 체크리스트 살펴보기</h2>
<p>컴포넌트 추상화에 대한 여러가지 글과 컨퍼런스 영상들을 보며 그냥 적당히 분리할 게 아니라, 명확한 기준을 가지고 컴포넌트를 분리하고 추상화하는 것이 필요하다는 생각이 들었다. 아래는 내가 정리해본 좋은 컴포넌트와 코드의 기준이고, <code>Noise Effector</code> 의 코드는 이를 얼마나 잘 지켰는지 살펴볼 예정이다.</p>
<ul>
<li><input disabled="" type="checkbox"> 사용처에서 편리하게 사용할 수 있는가? (인터페이스 고려)</li>
<li><input disabled="" type="checkbox"> 분리해서 복잡도가 낮아진 컴포넌트가 맞는가?</li>
<li><input disabled="" type="checkbox"> 재사용 가능한 컴포넌트인가?</li>
<li><input disabled="" type="checkbox"> 변경에 유연하게 대처할 수 있는 컴포넌트인가?</li>
<li><input disabled="" type="checkbox"> 한 가지 역할만 하는가?</li>
</ul>
<hr>
<h2 id="🎠-carousel">🎠 Carousel</h2>
<p>가장 먼저 보고 싶었던 건, <code>Carousel</code> 컴포넌트이다. 분명히 도메인과 철저히 분리되어서 만들어져야하는 컴포넌트라는 생각이 드는데 내가 그렇지 않게 만들었던 것 같기 때문이다.</p>
<pre><code class="language-tsx">export const Carousel = ({ thumbnailSource, carouselContainerRotateY }: CarouselProps) =&gt; {
  const noiseStrengths = [1, 2, 3, 4, 5, 6];

  return (
    &lt;S.Container rotateY={carouselContainerRotateY}&gt;
      {noiseStrengths.map((el, index) =&gt; (
        &lt;CarouselItem
          key={el}
          rotateY={(360 / noiseStrengths.length) * index}
          thumbnailSource={thumbnailSource}
          noiseStrength={noiseStrengths[index] - 1}
        /&gt;
      ))}
    &lt;/S.Container&gt;
  );
};</code></pre>
<p>역시나다. 애초에 이름부터 잘못됐다. 이 컴포넌트의 이름은 <code>3DCarousel</code> 이 되는게 맞을 것이다. 그리고 지금 사용하는 용도와 아주 끈끈하게 결합되어있어 절대 다른 용도로 재사용은 불가능해보인다. </p>
<p>그리고 아마 코드를 처음보는 사람은 <code>CarouselProps</code> 에 있는 것들이 왜 필요한지 어떻게 사용되는건지 짐작을 못할 거라고 생각한다. 설계단계부터 다시 해보자.</p>
<pre><code class="language-tsx">export const ThreeDimensionCarousel = ({ children }: ThreeDimensionCarouselProps) =&gt; {
  const { rotation, containerRef } = useCarouselSwipe(children.length);
  const [itemWidth, setItemWidth] = useState&lt;number&gt;(0);
  const itemRef = useRef&lt;HTMLDivElement | null&gt;(null);

  useEffect(() =&gt; {
    if (itemRef.current) {
      setItemWidth(itemRef.current.offsetWidth);
    }

    window.addEventListener(&quot;resize&quot;, () =&gt; {
      if (itemRef.current) {
        setItemWidth(itemRef.current.offsetWidth);
      }
    });
  }, []);

  return (
    &lt;S.Container ref={containerRef}&gt;
      &lt;S.Carousel rotateY={rotation}&gt;
        {children.map((item, i) =&gt; (
          &lt;S.Item
            key={i}
            itemsGap={itemWidth / 1.1}
            rotateY={(360 / children.length) * i}
            ref={i === 0 ? itemRef : null}
          &gt;
            {item}
          &lt;/S.Item&gt;
        ))}
      &lt;/S.Carousel&gt;
      {itemWidth === 0 &amp;&amp; &lt;Spinner /&gt;}
    &lt;/S.Container&gt;
  );
};

interface ThreeDimensionCarouselProps {
  children: JSX.Element[];
}</code></pre>
<p>위와 같이 코드를 고쳐봤다. </p>
<p>이제 확실하게 3D 캐러셀임을 알 수 있는 이름을 가지고 있다. props를 확인해보면, <code>children</code> 만을 받아서 <code>map</code> 을 통해 요소들을 그려주고 있다. 외부에서 반응형으로 item의 사이즈를 조절하면 거기에 맞춰 <code>translateZ</code> 를 조절해야하기 때문에 렌더링 이후 <code>width</code> 정보를 가져와야한다. 그래서 캐러셀이 그려지는 시간이 조금 늦춰지긴하지만 문제 없는 정보인 것 같다. </p>
<p>추가로 모바일과 데스크탑 환경을 모두 고려해 <code>MouseEvent</code> , <code>TouchEvent</code> 를 활용하여 스와이프를 통해 캐러셀을 회전할 수 있도록 컴포넌트 내부에서 커스텀훅을 불러오기 때문에, 외부에서 <code>rotateY</code> 같은 값을 주입해 직접 회전시키는 로직을 만들 필요도 없어졌다.</p>
<p>물론, 외부에서 버튼방식을 사용하거나 캐러셀 회전방식을 세세하게 수정하는 건 힘들게 되었지만 선택의 자유도가 높은 것만이 정답은 아니라고 생각한다. 지금은 한가지 방식을 강요하는 것이, 선택을 강요하는 것보다 이 컴포넌트를 사용하기 편하다고 판단했다.</p>
<p>어디 원래 코드와 바꿔도 문제 없이 동작하는지 살펴보자.</p>
<pre><code class="language-tsx">export const ThumbnailOptions = () =&gt; {
  const [searchParams] = useSearchParams();
  const [isClick, setIsClick] = useState(false);
  const rgba = useContext(RgbaContext);
  const navigate = useNavigate();
  const thumbnailSource = String(searchParams.get(&quot;thumbnail-source&quot;));
  const noiseStrengthArray = Array.from({ length: MAX_NOISE_LEVEL }, (_, i) =&gt; i);

  const handleClickButton = (noiseStrength: number) =&gt; {
    if (!isClick) return;

    navigate(`${ROUTE_PATH.THUMBNAIL_RESULT}?noise-strength=${noiseStrength}`, {
      state: { thumbnailSource },
    });
  };

  useEffect(() =&gt; {
    if (!rgba) {
      alert(&quot;이미지 정보가 사라졌습니다. 다시 시도해주세요.&quot;);
      navigate(ROUTE_PATH.HOME);
    }
  }, [rgba]);

  return (
    &lt;S.Container&gt;
      &lt;ThreeDimensionCarousel&gt;
        {noiseStrengthArray.map((noiseStrength) =&gt; {
          return (
            &lt;CarouselItem
              thumbnailSource={thumbnailSource}
              noiseStrength={noiseStrength}
              onClick={() =&gt; handleClickButton(noiseStrength)}
              onMouseDown={() =&gt; setIsClick(true)}
              onMouseMove={() =&gt; setIsClick(false)}
            /&gt;
          );
        })}
      &lt;/ThreeDimensionCarousel&gt;
    &lt;/S.Container&gt;
  );
};</code></pre>
<p>예상치 못한 문제가 발생하긴 했지만, 지금 당장 하고 있는 것과는 크게 연관이 없는 것 같아서 무시하기로 했다. 스와이프를 마우스 이벤트로 관리하다 보니, 스와이프 도중 클릭 이벤트 핸들러가 동작해버리는 문제가 있었다.</p>
<p>일단 <code>onMouseDown</code> , <code>onMouseMove</code> 를 통해 스와이프 도중인지, 진짜 클릭인지 구분하도록 변경해주었다. </p>
<p>이외에는 동작의 문제는 없는 것 같았다. 
<img src="https://velog.velcdn.com/images/thumb_hyeok/post/49e9b0db-a405-4cbc-bc6b-78ea398f00a9/image.gif" alt=""></p>
<hr>
<h2 id="✅-체크리스트-검토하기">✅ 체크리스트 검토하기</h2>
<p>이제 체크리스트를 검토하며 한 번 제대로 리팩터링이 되었는지 살펴보자.</p>
<ul>
<li><input disabled="" type="checkbox"> 사용처에서 편리하게 사용할 수 있는가? (인터페이스 고려)</li>
<li><input checked="" disabled="" type="checkbox"> 분리해서 복잡도가 낮아진 컴포넌트가 맞는가?</li>
<li><input checked="" disabled="" type="checkbox"> 재사용 가능한 컴포넌트인가?</li>
<li><input checked="" disabled="" type="checkbox"> 변경에 유연하게 대처할 수 있는 컴포넌트인가?</li>
<li><input checked="" disabled="" type="checkbox"> 한 가지 역할만 하는가?</li>
</ul>
<hr>
<h3 id="사용처에서-편리하게-사용할-수-있는가">사용처에서 편리하게 사용할 수 있는가?</h3>
<p>조금 아쉬운 부분이 있다. 원인은 오직 <strong>“스와이프”</strong> 때문인데, 스와이프 도중 클릭 이벤트 핸들러가 동작해버리는 문제 때문에 <code>children</code> 으로 전달하는 캐러셀 아이템에 <code>onMouseDown</code> , <code>onMouseMove</code> 에 이벤트 핸들러로 <code>isClick</code> 상태를 변경해주면서 <code>onClick</code> 이벤트 핸들러를 동작시켜야하기 때문이다.</p>
<p>스와이프를 유지하면서 이를 해결할 수 있는 방법은 당장은 모르겠다..</p>
<p>그래도 이 점만 제외하면 편리하게 사용할 수 있는 컴포넌트가 맞다고 생각한다.. <code>children</code> 만 전달해주면 요소들을 3D 캐러셀로 바로 그려줄 수 있기 때문이다!</p>
<hr>
<h3 id="분리해서-복잡도가-낮아진-컴포넌트가-맞는가">분리해서 복잡도가 낮아진 컴포넌트가 맞는가?</h3>
<p>사실 새로 따로 분리한 부분도 아닐 뿐더러 분리를 하지 않아야 하는 컴포넌트는 아니라고 생각한다.
짧게 다음으로 넘어가자.</p>
<hr>
<h3 id="재사용-가능한-컴포넌트인가">재사용 가능한 컴포넌트인가?</h3>
<p>이제 정말 재사용이 가능해졌다고 볼 수 있다. 재사용성이 떨어지는 부분을 내 눈으로는 찾아볼 수 없다.</p>
<hr>
<h3 id="변경에-유연하게-대처할-수-있는-컴포넌트인가">변경에 유연하게 대처할 수 있는 컴포넌트인가?</h3>
<p>크게 문제가 없다고 생각한다. 아래에 목차 인디게이터를 부착하거나, 좌우 버튼을 다는 정도의 변경을 예측하고 만든 컴포넌트인데, <code>useCarouselSwipe</code> 만 상위 컴포넌트로 옮기면 모든 상위 컴포넌트에서 캐러셀의 회전을 제어할 수 있으므로 상위컴포넌트에서 인디게이터나 버튼을 전달하거나 따로 사용할 수 있다.</p>
<hr>
<h3 id="한-가지-역할만-하는가">한 가지 역할만 하는가?</h3>
<p>그렇다.</p>
<hr>
<h2 id="📖-후기">📖 후기</h2>
<p>생각보다 컴포넌트를 리팩터링해보면서 고쳐야할 부분이 많다고 느꼈다. 캐러셀이 제일 심각한 것 같아서 캐러셀을 건드리긴 했지만, 여전히 문제가 있는 부분들은 많다는 생각이 든다. 시간이 나면 천천히 고쳐보면 좋을 것 같다.</p>
<p>ps. 혹시 스와이프에 대해서 좋은 방법을 알고 있다면 조언 부탁드립니다.</p>
]]></description>
        </item>
        <item>
            <link>https://velog.io/@thumb_hyeok/4vc0grph</link>
            <guid>https://velog.io/@thumb_hyeok/4vc0grph</guid>
            <pubDate>Mon, 10 Apr 2023 10:53:20 GMT</pubDate>
            <description><![CDATA[<h2 id="💡-개요">💡 개요</h2>
<p>최근 <a href="%5Bhttps://github.com/walking-sunset/selection-task%5D(https://github.com/walking-sunset/selection-task)">원티드 프리온보딩 사전과제</a>를 진행하면서 이런 요구사항을 만났다.</p>
<blockquote>
<p>배포된 사이트에서 기능이 정상동작 하지 않는다면 배포 가산점이 부여되지 않습니다</p>
<ul>
<li>기능이 정상 동작하지 않는 예시:<ul>
<li>새로고침하면 404 에러 페이지 표출</li>
<li>&quot;/&quot; URL이 아닌 &quot;/signup&quot;등의 경로로 바로 접속할 경우 404 에러 페이지 표출 등</li>
</ul>
</li>
</ul>
</blockquote>
<p>딱 보자마자 <code>gh-pages</code> 로 배포했을 때 저 두가지 문제가 생기는 건 알고 있었는데, 이유를 너무 어렴풋하게 알고있었고 해결법도 기억이 잘 나지 않아서 이번 기회에 한 번 공부해보기로 했다.</p>
<hr>
<h2 id="💣-문제가-생기는-이유는">💣 문제가 생기는 이유는?</h2>
<p>먼저 해결법을 알기 전에 문제상황과 해결법을 알고가자.</p>
<ul>
<li>새로고침하면 404 에러 페이지 표출</li>
<li>&quot;/&quot; URL이 아닌 &quot;/signup&quot;등의 경로로 바로 접속할 경우 404 에러 페이지 표출 등</li>
</ul>
<p>이러한 문제가 발생하는 이유는 배포한 프로젝트가 <strong>SPA(Single Page Application)</strong>이기 때문이다. SPA에서는 라우팅 처리 로직이 서버에 있지 않고, React 앱 내에 있다. 그렇기 때문에 모든 페이지 요청에 대해서 동일하게 <code>index.html</code> 을 반환한다. 그리고 나서 React 코드가 실행되면 URL에 따라 동적으로 페이지가 전환된다.</p>
<p>하지만 <strong>Github Page</strong>에서는 이를 지원하지 않는다. <strong>“/”</strong>가 아닌 <strong>“/signup”</strong> 등의 페이지로 진입하거나, <strong>“/”</strong>가 아닌 페이지에서 새로고침을 해보면 404 페이지가 뜬다. Github Page 측 서버에서 해당 경로에 대해 아는 것이 없기 때문이다.</p>
<p>결국, 결론은 아래와 같다.</p>
<blockquote>
<p>🤔 모든 페이지 요청에 대해 <strong>index.html</strong> 로 응답하도록 설정한다.</p>
</blockquote>
<hr>
<h2 id="🕸️-netlify">🕸️ Netlify</h2>
<p>사실 Netlify외에도 서버에서 모든 페이지 요청에 대해 index.html로 응답할 수 있는 다른 도구들을 사용해 해결할 수도 있겠지만, 나는 Netlify로 배포해봤다.</p>
<ul>
<li>회원가입 → 로그인 → 팀 생성 → 레포지토리를 통해 배포</li>
</ul>
<p>까지의 과정을 생략하도록 하겠다. 먼저 배포를 하고 나면 github page 때와 같은 문제가 발생한다.
이제 <code>public</code> 폴더에 <code>_redirects</code> 파일을 생성하고 아래와 같이 작성해준다.
<img src="https://velog.velcdn.com/images/thumb_hyeok/post/10aea1da-2dff-4775-9828-ca56ead723a7/image.png" alt=""></p>
<pre><code>/* /index.html 200</code></pre><p>이는 어떠한 경로로 진입하든 (<code>/*</code>) 
<code>index.html</code> 을 <code>200</code> 상태코드와 함께 반환하겠다는 의미이다.</p>
<p>이렇게 하면 문제가 해결된다.</p>
<hr>
<h2 id="🤔-굳이-github-page로-하고-싶다면">🤔 굳이 Github Page로 하고 싶다면,,</h2>
<p>Github Page로 위의 문제를 해결 할 수 있는 방법이 두 가지가 있다. 하지만 이 글에서 다루기엔 꼼수스러운 방법이라는 생각이 들어서 키워드만 남겨두겠다.</p>
<ol>
<li><code>react-router-dom</code> 의 <strong>HashRouter</strong> 사용하기</li>
<li><code>404.html</code> 를 반환하는 것을 이용하기</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[🪿 덕 타이핑과 구조 기반 타입]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%8D%95-%ED%83%80%EC%9D%B4%ED%95%91%EA%B3%BC-%EA%B5%AC%EC%A1%B0-%EA%B8%B0%EB%B0%98-%ED%83%80%EC%9E%85</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%8D%95-%ED%83%80%EC%9D%B4%ED%95%91%EA%B3%BC-%EA%B5%AC%EC%A1%B0-%EA%B8%B0%EB%B0%98-%ED%83%80%EC%9E%85</guid>
            <pubDate>Fri, 07 Apr 2023 17:59:55 GMT</pubDate>
            <description><![CDATA[<h2 id="💡-개요">💡 개요</h2>
<p>타입스크립트를 공부하다 보면, 타입스크립트의 타입 시스템은 <strong>“구조 기반 타입”</strong>이라는 글을 한 번쯤은 읽어보게된다. 이는 개발자가 타입이 무엇인지 신경쓰지 않아도 되도록 하는 자바스크립트의 <strong>“덕 타이핑”</strong>을 기반으로 모델링한 것이다. </p>
<p>고로, 타입스크립트의 타입시스템인 <strong>“구조 기반 타입”</strong>을 제대로 이해하기 위해서는 덕 타이핑의 이해가 물론 필요할 것이다. 덕 타이핑을 알아보면서, 자바스크립트와 타입스크립트의 타입체킹에 대해서 공부해보자.</p>
<hr>
<h2 id="🤔-덕-타이핑이-뭔데">🤔 덕 타이핑이 뭔데?</h2>
<blockquote>
</blockquote>
<p>🤔 <strong>간단히 이해하기에 이 사진만한 게 없는 것 같다.</strong></p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/5664c63f-743d-4aa9-b0bc-3bf7573d5bfa/image.jpg" alt=""></p>
<p>덕 타이핑이란, <strong>“내가 정의한 동작을 할 수 있다면 그 타입으로 인정해주는 것이다.”</strong> 어렵게 말하면 <strong>“객체의 변수, 메소드의 집합이 객체의 타입을 결정하는 것”</strong>을 말한다. </p>
<p>사진을 보자. 당장 저것의 이름이 콘센트인지 돼지코인지는 중요하지 않다. 내가 정의한 동작(코드를 꽂을 수 있다!)를 실행할 수 있다면 콘센트로 간주할 수 있다는 것이다. </p>
<p>아래에서 코드로 조금 더 자세히 살펴보자.</p>
<pre><code class="language-jsx">class Outlet {
    stick() {
        console.log(&#39;...&#39;);
  }

    charge() {
        console.log(&#39;전력 공급 중!&#39;);
    }
}

class PigNose {
    stick() {
        console.log(&#39;꿀!&#39;);
    }
}

function sticking(outlet) {
    outlet.stick();
}

function charging(outlet) {
    outlet.stick();
  outlet.charge();
}

const outlet = new Outlet();
const pigNose = new PigNose();

sticking(outlet);
sticking(pigNose);

charging(outlet);
charging(pigNose); // 런타임 에러 발생!</code></pre>
<p>여기서 <code>outlet</code> 과 <code>pigNose</code> 라는 인스턴스들을 클래스를 통해 생성했다. 그리고 각자 원하는 동작을 실행하는 함수인 <code>sticking</code> 과 <code>charging</code> 에서 호출했다. 
콘센트와 돼지코는 모두 꽂을 수 있기 때문에 <code>sticking</code> 에서는 콘센트와 돼지코 모두 콘센트로 간주되었지만, 돼지코는 전력 공급이 되지 않기 때문에 <code>charging</code> 에서는 런타임 에러가 발생한 것을 볼 수 있다.</p>
<p>이처럼 <strong>“동적이며 런타임 중에 액세스되는 유형 구조의 해당 부분만 기준으로 유형 호환성을 결정”</strong> 하는 방식을 <strong>덕 타이핑</strong>이라고 한다.</p>
<hr>
<h2 id="🎛️-덕-타이핑의-특징은">🎛️ 덕 타이핑의 특징은?</h2>
<p>위에서 봤듯이, <code>sticking</code> 이라는 함수는 <strong>“돼지코를 콘센트로 잘못 간주했다”</strong> 이러한 일이 실제로 개발환경에서 일어난다면 위험할 수도 있어보인다. 덕 타이핑의 특징은 뭐고, 다소 위험해 보이는 데도 불구하고 자바스크립트에서 덕 타이핑이 사용된 이유는 무엇일까?</p>
<h3 id="1️⃣-낮은-안정성">1️⃣ 낮은 안정성</h3>
<p>방금 위에서 말했듯이 덕 타이핑은 낮은 안정성이라는 특징을 가지고 있다. 당장 위 코드에서 살펴본 <code>sticking</code> 과 <code>charging</code> 이라는 함수는 모두 콘센트를 원할 것이다. 그러나, 돼지코도 파라미터로 넣을 수 있었고, 극단적으로는 객체가 아닌 string, number 등등 아무거나 넣을 수도 있을 것이다.</p>
<p>그렇게 넣더라도, 의도치 않은 동작을 실행하거나 (위의 예시에서는 돼지코에 stick 메서드를 실행했을 때 “꿀!”이 콘솔에 찍히는 것이라고 할 수 있겠다.) 런타임에서 에러가 발생할 뿐이다. (타입이 맞지 않아도 실행은 된다.)</p>
<h3 id="2️⃣-편리함-→-빠른-개발-속도">2️⃣ 편리함 → 빠른 개발 속도</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/fd275d8d-7e55-4740-b90c-22a83dc393bf/image.png" alt=""></p>
<p>덕 타이핑은 안정성을 포기하고, 편리함을 잡은 방식이다. 이로써 얻는 이득은 당장 빠르게 개발을 할 수 있다는 점과, 코드를 유연하게 작성할 수 있다는 것이다. 자바스크립트와 타입스크립트를 모두 경험해본 사람이라면 이게 무슨 의미인지 바로 와닿을 것이다.</p>
<p>타입에 대한 고민이 필요없으니 개발이 (당장은) 빠르게 진행이 될 것이다.</p>
<p>그리고 <code>number</code> , <code>string</code> 등등 함수 파라미터마다 오버로딩이나 새로운 함수를 만들 필요가 없어서 코드를 유연하게 작성할 수 있다는 장점이 있을 것이다.</p>
<hr>
<h2 id="🧱-구조-기반-타입">🧱 구조 기반 타입</h2>
<p>그럼 자바스크립트의 덕 타이핑의 모델링한 “<strong>구조 기반 타입”</strong>은 어떨까? 타입스크립트의 구조적 타입 시스템은 객체가 같은 프로퍼티를 모두 가지고, 동일한 형태를 가지면 같은 타입으로 판단한다. </p>
<p>아래 코드의 예시를 보자. </p>
<pre><code class="language-tsx">interface Crew {
  name: string;
    course: string;
}

interface Coach {
    name: string;
    course: string;
}

const printCrew = (crew: Crew) =&gt; {
    console.log(crew.name, crew.course);
}</code></pre>
<p><code>Coach</code> 타입의 객체를 생성해서 <code>printCrew</code> 에 대입한다고 해도, 타입스크립트는 아무런 에러를 발생시키지 않는다. <code>Crew</code> 타입과 <code>Coach</code> 타입은 동일한 형태를 가졌기 때문에 같은 타입으로 판단하기 때문이다. 이와는 다른 <strong>이름 기반 타입</strong>도 존재한다.</p>
<ul>
<li><strong>구조적 타입 시스템</strong>: 실제 구조와 정의에 의해 결정되는 타입 시스템의 한 종류<ul>
<li>객체가 같은 프로퍼티를 모두 가지고, 동일한 형태를 가지면 같은 타입으로 판단한다.</li>
</ul>
</li>
<li><strong>명목적 타입 시스템</strong>: 명시적 선언이나 이름을 기반<ul>
<li>이름이 같아야지만 같은 타입으로 판단한다.</li>
</ul>
</li>
</ul>
<p>덕 타이핑을 기반으로 한 구조 기반 타입은 이름 기반 타입과 다르게 집합 단위로 체크하기 때문에 중복되는 속성 혹은 중복되는 메서드를 획기적으로 줄일 수 있는 큰 장점이 존재한다. 그리고 이름 기반 타입에 비해 타입이 빡빡하지 않기 때문에 생산성을 높으면서 덕 타이핑과는 달리 안정성도 가지고 갈 수 있다.</p>
<p>자, 이제 맨 처음의 예시를 들고 와서 비교해 보자.</p>
<pre><code class="language-tsx">interface Outlet {
  stick: () =&gt; void;
    charge: () =&gt; void;
}

interface PigNose {
    stick: () =&gt; void;
}

const sticking = (outlet: Outlet) =&gt; {
    outlet.stick();
}</code></pre>
<p>이렇게 <code>Outlet</code> 과 <code>PigNose</code> 타입을 각자 작성해놓은 뒤에 변수를 생성해, <code>sticking</code> 함수에 파라미터로 준다면 <code>PigNose</code> 타입의 변수를 파라미터로 줄 때 에러가 발생한다.</p>
<p><code>charge</code> 메서드를 내부에서 호출하지 않더라도 말이다. 런타임에서 발생할 오류를 컴파일 타임에 미리 발견해 문제를 막을 수 있는 안정성이 생겼다!</p>
<hr>
<h2 id="🥊-덕-타이핑-vs-구조-기반-타입">🥊 덕 타이핑 vs 구조 기반 타입</h2>
<p>이제 두 가지를 모두 알아봤으니, 마지막으로 두 가지를 비교해보자.</p>
<ol>
<li>덕 타이핑은 런타임에 타입을 체크한다. (하지 않을 수도 있다)</li>
<li>구조적 타이핑에서는 컴파일 타임에 타입을 체크한다.</li>
<li>덕 타이핑은 동적 타이핑에서, 구조적 타이핑은 정적 타이핑에서 쓰인다.</li>
</ol>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li><p><a href="https://vallista.kr/%EB%8D%95-%ED%83%80%EC%9D%B4%ED%95%91%EA%B3%BC-%EA%B5%AC%EC%A1%B0%EC%A0%81-%ED%83%80%EC%9D%B4%ED%95%91/">덕 타이핑과 구조적 타이핑</a></p>
</li>
<li><p><a href="https://devopedia.org/duck-typing">Duck Typing</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Duck_typing">Duck typing</a></p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Structural_type_system">Structural type system</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🛡️ Vite 찍먹해보기]]></title>
            <link>https://velog.io/@thumb_hyeok/Vite-%EC%B0%8D%EB%A8%B9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@thumb_hyeok/Vite-%EC%B0%8D%EB%A8%B9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 14 Mar 2023 04:29:48 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-왜-vite인가">🤔 왜 Vite인가?</h2>
<p>최근 여러 가지 기업들의 과제를 하다가 느낀 점이 있다. <strong>webpack</strong>이라는 모듈 번들러는 초기 설정이 <strong>매우 번거롭다는 점</strong>이다. 물론 한 번 제대로 설정해두면 이후로 크게 변동이 생기거나 하는 일은 드물지만, 일주일만에 끝내야하는 과제테스트의 특성상 번거로운 설정을 하게 만드는 webpack은 핏에 맞지 않다고 느꼈다.</p>
<p>그렇다고 해서 내 마음대로 커스텀이 힘든 <strong>CRA</strong>를 사용하고 싶지는 않았기에, 새로운 모듈 번들러를 찾았다. 그게 바로 <strong>“Vite”</strong>였다. Vite는 webpack에 비해 초기 설정이 매우 간단하다고 한다. webpack과 비할 바가 아니라 오히려 <strong>CRA의 경쟁 상대</strong> 정도였다.</p>
<p><strong>snowpack</strong>, <strong>esbuild</strong>와 같은 다른 번들러들도 비교해봤지만, <strong>속도 문제와 SSR 문제</strong>를 건너 뛰고 초기 설정만 생각했을 때는 Vite를 여러 곳에 채택해봄직하다고 느꼈다. </p>
<hr>
<h2 id="🚀-더-빠른-빌드-타임">🚀 더 빠른 빌드 타임</h2>
<p>Vite은 다른 모듈 번들러들과 다르게 전체 애플리케이션을 크롤링하고 빌드해서 서비스를 시작하는 방식이 아니다. 먼저 <strong>“종속성”</strong>과 <strong>“코드”</strong> 두 가지 범주로 나누어 서버 시작 시간을 개선한다. </p>
<p><strong>종속성</strong>은 개발 도중 잘 바뀌지 않는 것으로, esbuild를 통해 사전 번들링이 된다. </p>
<p><strong>코드</strong>는 일반 JS가 아닌(JSX, CSS 등등) 것들이 포함되어 있는 경우가 많으며 자주 편집하게 된다. </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/c7623efc-aea5-4e38-b8bf-e13562cca04f/image.png" alt=""></p>
<p> <strong><code>Vite</code></strong> 은 아래 방식인 <strong>“Native ESM”</strong>을 통해 소스 코드를 제공한다. 다시말해, 브라우저가 곧 번들러라는 말이다. </p>
<p> Vite는 브라우저의 요청에 따라 필요에 따라 소스 코드를 변환하여 제공하기만 하면 된다. 간단히 말해 그냥 파일이 변경되면 변경된 파일을 브라우저 요청에 따라 보내주는 역할만 한다는 의미이다.</p>
<p>그러나, <strong>NoiseEffector</strong> 정도의 소규모의 프로젝트에서는 속도 차이가 미비한 수준 아닐까 생각한다.</p>
<p>추가적으로는 <strong>ESM 기반의 HMR, HTTP cache</strong> 등의 장점도 존재하나 사실 프로젝트가 워낙 작다보니 webpack과 HMR에서 큰 차이를 느낄 정도는 안 되는 것 같다. </p>
<p>정적파일 HTTP cache는 내가 따로 설정을 안해줘도 된다는 점에서 꽤 좋았다.</p>
<hr>
<h2 id="⚙️-간단한-초기-설정">⚙️ 간단한 초기 설정</h2>
<p>정말 초기 설정이 없는 수준이다. webpack으로 하루 종일 초기 설정을 하고 있던 걸 생각하면, CRA를 대체하고 vite를 사용하는 편이 낫지 않나 싶을 정도로 편하다.</p>
<pre><code> $ npm create vite@latest
</code></pre><p>놀랍게도 위 한 줄만 치면 아래의 프로젝트가 만들어진다!</p>
<p>(환경변수나 절대경로 등 기존과 다른 설정을 건드리려면 살짝 번거롭지만, 충분히 해봄직한 부분이다.)</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/9767968b-afee-46f9-bd14-1a39b85699e2/image.png" alt=""></p>
<hr>
<h2 id="🧐-후기">🧐 후기</h2>
<p>Vite는 이럴 때 쓰면 좋을 것 같다!</p>
<ul>
<li>초기 환경 설정이 복잡하지 않았으면 좋겠다 + 커스텀이 가능했으면 좋겠다</li>
<li>webpack + esbuild-loader 조합보다 빨랐으면 좋겠다</li>
</ul>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<p><a href="https://vitejs-kr.github.io/guide/why.html">Vite</a></p>
<p><a href="https://ui.toast.com/posts/ko_20220127">차세대 빌드 도구 비교</a></p>
<p><a href="https://engineering.ab180.co/stories/webpack-to-vite">Webpack → Vite: 번들러 마이그레이션 이야기</a></p>
<p><a href="https://junghan92.medium.com/%EB%B2%88%EC%97%AD-create-react-app-%EA%B6%8C%EC%9E%A5%EC%9D%84-vite%EB%A1%9C-%EB%8C%80%EC%B2%B4-pr-%EB%8C%80%ED%95%9C-dan-abramov%EC%9D%98-%EB%8B%B5%EB%B3%80-3050b5678ac8">(번역) &#39;Create React App 권장을 Vite로 대체&#39; PR 대한 Dan Abramov의 답변</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[📈 Noise-Effector 개발기 -성능 개선- ]]></title>
            <link>https://velog.io/@thumb_hyeok/Noise-Effector-%EA%B0%9C%EB%B0%9C%EA%B8%B0-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-</link>
            <guid>https://velog.io/@thumb_hyeok/Noise-Effector-%EA%B0%9C%EB%B0%9C%EA%B8%B0-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0-</guid>
            <pubDate>Tue, 14 Feb 2023 10:36:58 GMT</pubDate>
            <description><![CDATA[<h2 id="🏃🏻-들어가며">🏃🏻 들어가며</h2>
<p>혹시 <strong>&quot;노이즈 이펙터&quot;</strong>에 대해서 모른다면 <a href="https://velog.io/@thumb_hyeok/Noise-Effector-%EA%B0%9C%EB%B0%9C%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%86%8C%EA%B0%9C-">이전 포스팅</a>을 참고하는 것을 추천한다. 이번 글에서는 노이즈 이펙터를 개발하면서 꽤 애를 많이 먹었던 부분인 <strong>성능 개선</strong>에 대해 내가 겪은 <strong>시간 순으로 트러블 슈팅 과정</strong>을 적어보려한다. 그럼, 시작해보자!</p>
<hr>
<h2 id="🤔-아무리-프로토타입이라지만">🤔 아무리 프로토타입이라지만…</h2>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/92c2dbee-02b8-4eb1-842a-34e4aacd5802/image.png" alt=""></p>
<p>노이즈 이펙터의 핵심 기능인 사진에 노이즈를 적용하는 기능이 최초로 완성된 시점이다. <strong>성능 점수 20점..</strong> 사실 이것도 실제 성능보다 훨씬 높게 나온 것이다. <strong>왜냐하면 “LCP”가 된 시점이 실제 이미지가 완성되어 다운로드 받을 수 있는 시점과는 괴리가 꽤 컸</strong>기 때문이다. </p>
<p>무려** 네트워크 무제한, 개발모드가 아닌 배포된 환경, 데스크탑 기준**으로 측정한 점수이다. 심지어 개발모드에서는 너무 오래 걸려서 응답없음으로 크롬이 꺼지는 경우가 대부분이라 측정 자체도 불가능했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/30bb313a-e8bd-487b-bc55-3b14e1b43e63/image.png" alt=""></p>
<p>이렇게… 매우 자주 가버리곤 했다.</p>
<p>이렇게 성능이 처참한 이유는 <strong>“이렇게 성능이 많이 저하되는 작업일 거라 생각 못하고”</strong> 점묘화를 이루고 있는 삼각형 요소들을 <code>div</code> 를 이용해서 하나 하나 다 그렸기 때문이다. (아래 사진의 삼각형이 모두 div다..)</p>
<p>조금만 노이즈 강도를 약하게 해도 <code>div</code> 태그 수십, 수백만개가 생성되곤했다. 그리고 당연히 이렇게 동작하니 노이즈 정도에 따라 그리는 <code>div</code> 의 갯수 차이로 성능 차이가 어마어마했다. </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/840cfe12-71d1-46f6-8a1e-527c8c678752/image.png" alt=""></p>
<p>그래서 “<strong>Canvas API”</strong>를 이용해 div 대체해 점묘화를 그리는 것으로 했다. Canvas API는 GPU 가속을 사용하여, 브라우저가 그래픽 연산을 처리하는 과정에서 CPU와 GPU를 모두 사용한다. 이를 통해 빠른 그래픽 렌더링을 할 수 있다는 장점이 있다.</p>
<p>이 뿐만 아니라, Canvas API는 HTML 태그와 비교해서 상대적으로 가볍고 빠르게 로딩된다. 이것은 <strong>브라우저의 메모리를 적게</strong> 사용하고, <strong>렌더링 속도 또한 빠르게</strong> 만들어준다.</p>
<blockquote>
<p>💡 <strong>이렇게 장점이 많은 Canvas API를 이용하지 않을 이유가 없다!</strong></p>
</blockquote>
<hr>
<h2 id="🤔-정말-이게-맞는-방법이-맞을까">🤔 정말 이게 맞는 방법이 맞을까?</h2>
<p>위에서 말은 Canvas API가 좋다, 좋다했지만 다른 방법은 없었을까? 내가 원하는 점묘화 썸네일을 만들어 낼 수 있는 기능들은 무엇이 있는지 살펴보고 비교해보자. 그 이후에 가장 알맞은 기술을 사용하는 게 맞을 것이다!</p>
<h3 id="1️⃣-dom">1️⃣ DOM</h3>
<p>웹 프론트엔드 개발자라면 가장 친숙한 방법일 것이다. 그래서 내가 제일 먼저 시도해본 방법이기도 하다. 이벤트 핸들러를 이용하거나, 사용하기에 편한 점들이 있긴하지만, 메모리와 성능을 고려해봤을 때 지금은 적합만 방법이 아니라는 답이 나온다.</p>
<hr>
<h3 id="2️⃣-svg">2️⃣ SVG</h3>
<p>SVG는 세밀한 제어(Canvas API, WebGL)와 편의성(DOM)의 중간에 위치하는 방법이다. 나는 고퀄리티의 점묘화를 만들어야하니 벡터기반의 SVG는 나름 좋은 선택지일 수도 있지만 다만 SVG는 도형의 속성을 수정할 때마다 리렌더링이 발생하지만, Canvas API는 한 번에 모든 것을 그리기 때문에 일반적인 경우라면 SVG는 요소가 많아질 수록 성능이 감소한다.</p>
<hr>
<h3 id="3️⃣-canvas-api">3️⃣ Canvas API</h3>
<p>Canvas API는 위에서 설명했던 것처럼 다른 방식에서 생길 수 있는 문제점 <strong>“요소가 많아지면 성능이 감소한다”</strong> 가 발생하지 않는 조건에 있는 것은 물론, 하드웨어와 운영체제에 따라 다르지만 일반적인 최신 브라우저에서는 GPU 가속을 사용해 성능상의 장점을 가져갈 수 있다는 장점이 있다.</p>
<p>다만, DOM 문서 상에 <code>canvas</code> 안에 그린 것들은 인식되지 않아 텅 빈 <code>canvas</code> 태그 하나 뿐이라는 단점이 있다. 하지만 Canvas API 자체가 많은 요소들을 하나 하나 제어하기에 편해 큰 문제가 되지는 않았다. </p>
<hr>
<p>그 외 WebGL이나 다른 방법들도 존재할 수 있겠지만, 목적과 조금 동떨어졌다거나 진입장벽이 있어보이는 기술을 고려하지 않았다. </p>
<p>이렇게 결론은 <strong>“Canvan API”</strong>가 되었다.</p>
<hr>
<h2 id="🎨-canvas-api로-성능-개선하기">🎨 Canvas API로 성능 개선하기</h2>
<p>이게 Canvas API를 어떻게 사용하는지 알려주는 포스팅이 아니기 때문에, “어떻게 구현했냐” 하는 부분은 생략하도록 하겠다. 바로 넘어가서 div로 떡칠한 점묘화를 Canvas API로 대체하고 어떻게 성능이 향상되었는지 살펴보도록 하자! (잘 보면 이상한 점이 있을 것이다)</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/e83f8c5c-59b0-4ae2-b996-e062b545f3fc/image.png" alt=""></p>
<p>성능 점수상으로는 69점이지만, 잘보면 <strong>LCP가 0.3초인 것을 확인</strong>해볼 수 있다. 아무리 봐도 한 2초는 걸리는 것 같은데 0.3초라니.. 말이 안 되는 리포트였다. 그래서 퍼포먼스 탭을 확인해봤다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/c81f5fbe-7ccb-4af9-94c3-d8ab68d8aa7c/image.png" alt=""></p>
<p>모나리자 점묘화가 로딩이 되는 시점은 저 뒤 <strong>1998ms 지점에 있는데, LCP는 42.9ms로 찍혀있는 것을 확인</strong>할 수 있다. 이건 아마도 Canvas API의 내부 요소는 DOM에 찍히지 않기 때문에 Canvas가 로딩되면 바로 LCP로 기록이 되는 것으로 보인다. (하지만 정확하지는 않다)</p>
<p>측정환경은 아까와 동일하게 측정했다. 아마 이대로면 실제 LCP는 1.5s~1.9s 사이의 값일 것 같았다. 아주 만족스러운 성능은 아니지만 개발모드에서 웹사이트가 터진다거나, 하루 종일 기다려야 하는 사태가 일어나지는 않았다.</p>
<p>그러나 <strong>장치 종류나 브라우저에 따라 성능이 차이가 꽤 나는 것 같았다.</strong> 프로젝트를 테스트 해주던 내 친구의 컴퓨터에서는 거의 10초 가까이 기다려야 화면이 로딩이 완료된다고 했다.</p>
<p>그래도 일정상 개발해야하는 기능들이 많아 성능에 목숨걸지 않고 일단 넘어가기로 했다.</p>
<hr>
<h2 id="💣-끝판왕을-만나다">💣 끝판왕을 만나다</h2>
<p>저번 글을 보신 분이라면 노이즈 이펙터에는 노이즈 강도를 조절하도록 도와주는 <strong>“3D 캐러셀”</strong>이 존재한다는 사실을 알 것이다. 그렇다. 노이즈는 6단계로 이루어져 있고, 나는 캐러셀을 렌더링하기 위해서 <strong>저 오래걸리는 작업을 6번이나 해야함</strong>을 깨달았다.</p>
<h3 id="📈-다시-한-번-성능-분석">📈 다시 한 번 성능 분석</h3>
<p>잘 보면 캐러셀 요소마다 다른 노이즈 정도를 가지고 있기때문에, 하나 하나 다 따로 렌더링을 해야하는 상황이었다. 오… 이런 나는 UX가 뛰어난 3D캐러셀을 포기하고 싶지 않아서 결국 다시 성능 개선 문제로 되돌아갈 수 밖에 없었다. 먼저 구현이 끝나고 측정한 성능을 분석해보자</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/bb870324-bedb-4fe9-8421-6751dd76e22e/image.png" alt=""></p>
<p>이전처럼 LCP가 정상적으로 기록되지 않아서 퍼포먼스 탭을 참고해 LCP를 대충 계산해봤더니 약 <strong>“4s”</strong> 정도 될 것 같았다. 그 외 성능도 권장사항과는 매우 멀어보인다. 이정도면 다행이다라고 생각했을 정도였다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/edf9c0b5-ab9e-4aea-88e1-14b338fdeb3f/image.png" alt=""></p>
<p>여기서 나는 점묘화를 그리는 기능을 A-Z까지 담당하고 있는 <strong><code>usePointillism</code></strong> 훅을 중심으로 어떤 부분이 성능적으로 문제가 생기는 지 찾아봤다. 찾아보니 유독 두가지 함수가 오래 걸림을 확인했다.</p>
<p>자, 아래에서 해당 문제를 상세히 살펴보고 고쳐보도록 하자.</p>
<hr>
<h3 id="🤖-성능을-위한-코드-리팩터링">🤖 성능을 위한 코드 리팩터링</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/5d671977-e2b7-4320-999b-a206617efb31/image.png" alt=""></p>
<p>*<em><code>createImageData</code> *</em>함수는 source로 제공된 이미지를 캔버스에 그리고, 해당 이미지의 각 픽셀 마다의 RGBA값 정보를 배열을 반환하는 함수이다. 이건 보자마자 이름과 달리 역할이 두개라 분리해야겠다는 생각부터 들었다.  </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/ad1e28a8-a8dc-4625-8d43-248c94688e86/image.png" alt=""></p>
<p>추가로 <strong><code>createRgbaValues</code></strong> 함수는 그렇게 만들어진 RGBA 값 배열을 받아서 내가 사용하기 편한 형태로 바꿔서 이차원배열 형태로 반환해주는 함수이다. 나중에 알고 봤더니 이게 성능 문제의 주범이었다.</p>
<p>분석 결과, 점묘화를 그리는 게 주목적인 <strong><code>usePointillism</code></strong> 훅에서 굳이 이미지를 그리고, RGBA 값을 추출하는 로직까지 들고 있을 필요가 없어 보여 <strong><code>useRgba</code></strong> 라는 훅으로 따로 분리했다.</p>
<p>그리고 이미지의 RGBA 값은 한 번 이미지를 첨부하고 나면 바뀌지 않으니, 성능상의 문제가 없을 거라는 생각을 하고 <strong>“Context API”</strong>를 이용해서 컨텍스트에 저장해서 <strong><code>useRgba</code></strong> 에서 한 번 계산한 RGBA 값을 컨텍스트를 통해 <strong><code>usePointillism</code></strong> 에서 받아 쓰는 것으로 했다.</p>
<p>이렇게 하면 가장 오래걸리는 작업인 RGBA 값 계산을 점묘화를 그려야할 캔버스 갯수만큼 할 필요 없이 단 한번만 하면 된다는 아주 큰 이득을 얻을 수 있었다.</p>
<p>추가로 굳이 필요없는 작업인 RGBA 값을 이차원배열에 옮겨담는 작업을 하면서 작업 시간을 늘리고 있어 해당 함수를 제거 했다. </p>
<blockquote>
<p>💡 <strong>그럼 결과물을 살펴보자!</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/f7e610ac-1d86-4d6e-8b19-d9b8b1773d27/image.png" alt=""></p>
<p>이미지를 그리는 기능은 하나로 분리해서 <strong><code>drawImage</code></strong> 함수에서 역할을 주었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/f701c2a3-51f7-48a8-be86-7fb701cf738f/image.png" alt=""></p>
<p><strong><code>getPixelData</code></strong> 함수는 아까 전의 <strong><code>createImageData</code></strong> 함수와 이미지를 그리는 것만 빼고 완전히 동일한 동작을 하며, 컨텍스트에 RGBA 값을 업데이트 시켜주는 기능이 추가되었다.</p>
<p>이제 코드를 성공적으로 리팩터링 했으니 <strong>성능 개선이 잘 되었는지</strong> 살펴보러가자! </p>
<hr>
<h2 id="💯-성능-개선-결과">💯 성능 개선 결과!</h2>
<h3 id="1️⃣-page-1-이미지-업로드"><strong>1️⃣ Page 1: 이미지 업로드</strong></h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/85f5dfd6-c9f1-4a48-b18e-2af684f1cd51/image.png" alt=""></p>
<p><strong>99점…</strong> 좋다. 원래도 이 페이지는 빨라서 크게 달라진 점은 없다. 그러나, 수많은 이미지를 불러오느라 시간이 걸리는만큼 네트워크 환경이 좋지 못한 곳에서는 아쉬운 성능을 보여줄 수도 있다. </p>
<p><strong>캐시정책이나 SSR과 같은 방법을 통해 개선</strong>을 해볼 수도 있겠지만, 개선이 필요한 단계는 아닌 것 같다.</p>
<hr>
<h3 id="2️⃣-page-2-노이즈-강도-선택"><strong>2️⃣ Page 2: 노이즈 강도 선택</strong></h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/77bdfd0f-0656-430e-bd8e-103982a392be/image.png" alt=""></p>
<p><strong>100점…</strong> 엄청나게 빨라졌다. <strong>사용자가 이미지를 첨부하는 시점에 이미 컨텍스트에 RGBA 값이 저장</strong>이 되기 때문에 이 페이지가 로딩되는 속도는 그냥 모바일에서도 <strong>“즉시”</strong> 라고 볼 수 있을 정도이다. </p>
<p>위에 분석을 토대로 분석해보니 LCP는 TTI와 거의 같은 수준이었는데 지금은 제대로 된 LCP값이 맞다. </p>
<hr>
<h3 id="3️⃣-page-3-이미지-다운로드"><strong>3️⃣ Page 3: 이미지 다운로드</strong></h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/40c55c5e-8e55-4ad7-b3a6-07f55567b6c3/image.png" alt=""></p>
<p>이 페이지도 마찬가지로 <strong>이미 저장되어 있는 RGBA 값을 이용</strong>해 결과물을 그리기 때문에 (심지어 <strong>1개만 그리면 되기 때문</strong>에) 아까 페이지보다도 훨씬 빠른 성능을 보여준다.</p>
<hr>
<h3 id="4️⃣-전체-유저-플로우-timespan">4️⃣ 전체 유저 플로우: Timespan</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/aa603ba3-d3cb-4f6d-b29d-5ed40a3cba68/image.png" alt=""></p>
<p>마지막으로, <strong>“이미지 첨부부터”, “결과물 다운로드”</strong> 까지 모든 유저 사이클을 <strong>Lighthouse</strong>의 <strong>“TimeSpan”</strong>을 통해 성능 점수를 측정해봤다. 역시,,, 지금으로써는 개선이 필요없는 단계까지 왔다.</p>
<hr>
<h2 id="😎-마무리">😎 마무리</h2>
<p>이렇게 노이즈 이펙터 프로젝트의 <strong>&quot;성능 문제&quot;</strong>와 <strong>&quot;트러블 슈팅 과정&quot;</strong>을 살펴봤다. 다음 글에서는 내가 직접 코드 리뷰를 해보거나, 쓰인 기술에 대해 깊게 다뤄보는 시간을 가져보려한다.</p>
<p><strong>긴 글을 읽어줘서 감사하다!</strong></p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/ccb779c5-dbbb-4ceb-8eda-76cf1cdf1257/image.png" alt=""></p>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li><p><a href="https://joshondesign.com/p/books/canvasdeepdive/title.html">HTML Canvas Deep Dive</a></p>
</li>
<li><p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">Canvas API - Web APIs | MDN</a></p>
</li>
<li><p><a href="https://blog.logrocket.com/when-to-use-html5s-canvas-ce992b100ee8/">When to use HTML5’s canvas - LogRocket Blog</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[⚡ Noise-Effector 개발기 -프로젝트 소개-]]></title>
            <link>https://velog.io/@thumb_hyeok/Noise-Effector-%EA%B0%9C%EB%B0%9C%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%86%8C%EA%B0%9C-</link>
            <guid>https://velog.io/@thumb_hyeok/Noise-Effector-%EA%B0%9C%EB%B0%9C%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%86%8C%EA%B0%9C-</guid>
            <pubDate>Tue, 14 Feb 2023 04:48:24 GMT</pubDate>
            <description><![CDATA[<h2 id="💻-프로젝트-소개"><strong>💻 프로젝트 소개</strong></h2>
<p>이번에 개인 사이드 프로젝트로 개발을 진행한 <strong>“Noise Effector”</strong>는 사진에 여러 가지 효과를 적용해 사진을 바꿔주는 사이드 프로젝트이다. 단순한 필터수준의 효과는 아니다. </p>
<p>이전 글인 “글또를 시작하며”의 썸네일도 Noise Effector를 통해 만든 썸네일이다. 이제 Noise Effector가 어떤 효과를 더해주는 지는 이해할 수 있을 것 같다.</p>
<p>이 프로젝트를 시작하게된 이유도 블로그 썸네일이었다.</p>
<p>어느 순간부터 블로그 썸네일을 직접 제작하게 되었는데, 제법 깔끔하긴 하지만 단조롭고, 특색있다 할 정도는 되지 못했다. 그리고 일정하게 만들기가 번거롭다.</p>
<p>그래서 좀 특색있고, 일정한 퀄리티의 썸네일을 뽑아줄 수 있는 도구가 필요했고, 인터랙티브 디벨로퍼 채널에서 재밌는 영상을 하나 보고 영감을 얻어 시작하게 되었다.</p>
<hr>
<h2 id="🎞️-프로젝트-동작-영상"><strong>🎞️ 프로젝트 동작 영상</strong></h2>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/65a8396f-1be7-41ee-ae7e-d3c43825ce7b/image.gif" alt=""></p>
<p>노이즈 이펙터(앞으로는 편하게 노이즈 이펙터라고 칭하겠다)는 <strong>총 세 가지의 페이지</strong>로 구성되어있다. <strong>노이즈(효과)를 적용할 사진을 선택할</strong> 페이지, <strong>적용할 노이즈의 강도를 선택</strong>하는 페이지, 마지막으로 <strong>결과물을 확인하고 다운로드</strong> 받을 수 있는 페이지이다. </p>
<p>아래에서 개발과정에 대해 자세히 살펴보도록하자.</p>
<hr>
<h2 id="1️⃣-page-1-이미지-업로드">1️⃣ Page 1: 이미지 업로드</h2>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/5c6775b4-6bc1-40d4-b310-557078ca4171/image.png" alt=""></p>
<p>이 프로젝트의 초기 페이지부터 시작해보자!</p>
<p> 사진에 효과를 입혀주는 프로젝트라 컨셉을 <strong>“사진관”</strong>과 <strong>“폴라로이드”</strong> 로 잡았다!</p>
<p>사진관 느낌이 나도록<del>(사진관 안 가본 티내기)</del> <strong>노이즈가 적용된 사진들을 배경에 랜덤하게</strong> 흩뿌려주었다. 이는 사용자에게 어떤 결과물을 얻을 수 있는 지 미리 알려주는 역할도 하며, 단조로운 배경을 채워주는 역할도 한다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/d17be9eb-90d1-4acd-85ed-c3808e6b6d61/image.gif" alt=""></p>
<p>웹사이트인만큼 화면 사이즈가 도중에 바뀔 수 있는 것을 고려해, <strong>“resize” 이벤트가 발생</strong>되면 “debounce”를 적용하여 resize 이벤트가 끝나면 <strong>바뀐 화면 사이즈에 맞게 리렌더링</strong> 되도록 해주었다!</p>
<p>그래서 화면 사이즈가 줄었을 때, 화면 바깥으로 폴라로이드들이 나가지 않고, 화면 사이즈가 늘었을 때, <strong>화면에 빈 공간이 휑하게 있지 않게</strong> 되었다! 
<img src="https://velog.velcdn.com/images/thumb_hyeok/post/e81d0c1a-0296-42c6-8bae-0bbd80c7687d/image.png" alt=""></p>
<p>다음은 이 페이지의 핵심 기능인 <strong>“이미지 업로드”</strong>이다! </p>
<p>딱 봐도 파일 업로드하라고 생긴 폴라로이드에 사진을 <strong>“드래그 앤 드롭”</strong> 하거나, <strong>폴라로이드를 클릭해 파일을 선택</strong>할 수도 있다.  이 또한, 배경과 같은 폴라로이드 사진으로 만들어 배경의 사진들과 같은 결과물을 하나 더 만드는 듯한 느낌을 사용자에게 주고자 했다. </p>
<p>소개글인 만큼 기술적인 이야기는 다른 글에서 하도록 하겠다!</p>
<hr>
<h2 id="2️⃣-page-2-노이즈-강도-선택">2️⃣ Page 2: 노이즈 강도 선택</h2>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/35952c52-03ec-4bd9-b8cb-6fd2ca3fbdd9/image.gif" alt=""></p>
<p>다음은 노이즈 강도 선택 페이지다!</p>
<p>사실 이 페이지는 노이즈 효과를 적용하는 기능을 구현하고 나서야 만들 수 있던 페이지로, 개발기간 상으로는 마지막 페이지에 해당한다. 이 페이지를 만들면서 했던 UX적인 고민이 꽤 많았는데 대표적인 것은 아래와 같았다. </p>
<blockquote>
<p>🤔 어떻게 하면 “<strong>사용자가 선택한 노이즈 강도의 사진이 제일 잘 보이면서”</strong>
더 약한, 더 강한 “<strong>노이즈 효과를 한 눈에 비교”</strong>할 수 있을까?</p>
</blockquote>
<p>처음에 내가 생각한 방식은 카메라 필름처럼 노이즈 강도에 따른 사진들을 이어서 보여주는 방식이었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/cada60a8-06ed-4fc4-92c2-b21fb88786c0/image.jpg" alt=""></p>
<p>하지만 막상 프로토타입을 구현해보니, 지금 선택된 노이즈효과를 제일 강조하는 효과를 얻기 힘들었고, 무엇보다 사진들의 크기가 모두 동일하니, <strong>사용자가 선택한 사진을 크게 보기 어렵다는 문제점</strong>이 있었다.</p>
<blockquote>
<p>💡 이와 같은 문제를 어떻게 해결할까 생각하다가 <strong>“3D 캐러셀”</strong>이 떠올랐다!</p>
</blockquote>
<p>3D로 <strong>원통과 같이 사진들을 배치</strong>하고 회전시키면 3D 공간의 특성상 가운데에 있는 이미지는 튀어 나와 보이게 되고, 그 주변에 있는 이미지들은 굴곡이 발생해 회전된 각도에 따라 더 작게 보이게 된다!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/8e1472b2-8773-4166-82c4-37ff4cb3df40/image.png" alt=""></p>
<p>실제로 구현해서 결과물을 살펴보니, 아주 만족스러웠다! <strong>선택된 사진이 가장 잘 보이면서</strong>도 사이드에 보이는 사진들로 <strong>더 약한, 더 강한 노이즈와도 비교가 가능</strong>했다. 지금도 참 좋은 UX라고 생각한다! </p>
<hr>
<h2 id="3️⃣-page-3-이미지-다운로드">3️⃣ Page 3: 이미지 다운로드</h2>
<p>이렇게 노이즈 선택까지 완료하게 되면 이미지 다운로드 페이지로 오게 된다!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/e29bff9d-7ff4-4b92-99be-0a1bb42921e4/image.png" alt=""></p>
<p>사실 이 페이지는 별로 설명할 만한 것은 없다. <strong>노이즈를 적용해주는 주요 로직들이 이미 2번째 페이지에서도 보여지면서</strong> 결과물을 보여주는 이 페이지의 중요한 기능이 조금 뺏긴 느낌이다.</p>
<p>사실 이 페이지에서 할 이야기가 많은 건, <strong>기능구현을 어떻게 했느냐와 성능 개선에 관한 글에</strong>서이다. 나도 적고 싶은 이야기가 아주 많으니 다음 포스팅들에서 다뤄보도록 하겠다!</p>
<hr>
<h2 id="⚡마무리">⚡마무리</h2>
<p>혹시 이 프로젝트를 사용해보고 싶은 마음이 드는 사람이 있을 까봐 배포된 주소를 남겨두겠다! (사실 내가 많이 많이 사용해주고 피드백도 왕창 남겨줬으면 좋겠다 😃)</p>
<p>➡️ <a href="%5Bhttps://notpotter.github.io/noise-effector/%5D(https://notpotter.github.io/noise-effector/)">Noise Effector</a></p>
<p>다음 편에는 이 프로젝트를 하면서 가장 고생했던 <strong>“성능 개선”</strong>편으로 돌아오겠다.
긴 글을 읽어줘서 감사하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[글또를 시작하며]]></title>
            <link>https://velog.io/@thumb_hyeok/%EA%B8%80%EB%98%90%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0</link>
            <guid>https://velog.io/@thumb_hyeok/%EA%B8%80%EB%98%90%EB%A5%BC-%EC%8B%9C%EC%9E%91%ED%95%98%EB%A9%B0</guid>
            <pubDate>Fri, 10 Feb 2023 15:49:27 GMT</pubDate>
            <description><![CDATA[<h2 id="🌪️-또-새로운-해가-또-새로운-것을">🌪️ 또 새로운 해가, 또 새로운 것을.</h2>
<p>다사다난했던 2022년이 지나고, 2023년이 왔다. 우아한테크코스를 수료하고, 아직 취준생으로 남아있으면서 구직에 성공 하기 위한 여러 노력들을 하고 있다. 글또 또한 그 중 하나이다. 우아한테크코스와 2022년을 함께하면서 스스로의 다짐과 회고가 다소 부실하지 않았나 싶다. 결과적으로 열심히 성심껏 참여한 것은 맞지만 결실은 아직 맺지 못했다.</p>
<blockquote>
<p><strong>2023년에는 상반기 내에 좋은 결실을 맺기 위한 다짐을 글또를 시작하면서 해보려한다!</strong></p>
</blockquote>
<hr>
<h2 id="🏃♂️-글또-활동-동안-어떤-것을-구체적으로-할-것인가">🏃‍♂️ 글또 활동 동안 어떤 것을 구체적으로 할 것인가?</h2>
<h3 id="1️⃣-주-1회-포스팅">1️⃣ 주 1회 포스팅</h3>
<p>글또는 격주로 1회씩 포스팅을 하는 것이 원칙이다. 내가 속했던 “노포스팅유페이”는 매주 1회씩 포스팅을 했었는데, 바쁜 우아한테크코스 시절에도 해냈던 일을 지금 못한다는 것은 말이 안 된다. 나는 이 글을 쓰는 시점부터 <strong>“기술관련 주 1회 포스팅”</strong>을 글또가 끝날 때까지 유지할 예정이다.</p>
<h3 id="2️⃣-최소-3개-이상의-모임-또는-스터디">2️⃣ 최소 3개 이상의 모임 또는 스터디</h3>
<p>우아한테크코스 시절의 큰 아쉬운 점은, <strong>“함께 자라기</strong>”의 가치를 너무 늦게 깨닫고, 너무 늦게 이런 저런 스터디나 모임에 참여했다는 점이다. 글또에서는 내가 직접 모집을 하던, 맘에 드는 모임에 들어가던 의미있는 <strong>“함께 자라기”</strong>를 더 빨리 많이 경험하고 싶다.</p>
<h3 id="3️⃣-격주에-한-권씩-기술서적-읽기">3️⃣ 격주에 한 권씩 기술서적 읽기</h3>
<p>어느 순간부터 기술서적과 거리가 멀어지고 있다. 코드를 짜는 것도 중요하지만, 이론적인 기반을 쌓는 것도 중요하다고 생각한다. 이런 부분은 가능하면 스터디를 모집해서 함께 해보는 것도 좋을 것 같다. 서로가 같은 책을 읽더라도 놓치는 부분이 다르고, 잘 이해하는 부분과 그렇지 못한 부분도 다르다. 또한, 함께 이야기하는 과정에서 내용을 더 깊게 이해하게되고, 더 잘 기억하게된다. </p>
<hr>
<h2 id="6개월-후의-나는-무엇이-되어-있어야-하나">6개월 후의 나는 무엇이 되어 있어야 하나?</h2>
<p>당연히 <strong>“돈을 받는 프로 프론트엔드 개발자”</strong>가 되어 있어야하겠지만, 글또의 취지에 맞게 하자면 아무도 안 읽는 지금 내 블로그와는 달리, 유용한 정보로 가득 차, 남들이 나중에 다시 방문해서 읽고 싶어, 최소 10개씩은 하트를 눌러주는 기술 블로그를 운영하는 개발자가 되고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤔 우아한테크코스 돌아보기]]></title>
            <link>https://velog.io/@thumb_hyeok/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@thumb_hyeok/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BD%94%EC%8A%A4-%EB%8F%8C%EC%95%84%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 09 Jan 2023 09:01:24 GMT</pubDate>
            <description><![CDATA[<h2 id="🏃♂️-들어가며">🏃‍♂️ 들어가며!</h2>
<p>2021년 10월부터 시작된 우아한테크코스 지원부터, 2022년 11월말의 수료까지 우아한테크코스와 함께한 2022년 1년이 끝이 났다. 수료하고 미뤄뒀던 소감과 회고, <strong>“우아한테크코스 이전의 나와 지금의 나는 어떻게 달라졌을까?”</strong> 와 <strong>“앞으로는 무엇을 해나갈 것인가?”</strong> 를 중심으로 다뤄보려고 한다</p>
<hr>
<h2 id="🍼-우아한테크코스-이전의-나는">🍼 우아한테크코스 이전의 나는.</h2>
<p>사실 나는 우아한테크코스에 어떻게 붙었나 싶을 정도로 들어갈 때 우테코에서 거의 최약체였다고 생각한다. 우아한테크코스 프리코스 이전에는 클론코딩이 아니라 내 능력만으로 만들 수 있었던건 겨우 <strong>“투두리스트”</strong> 수준이었으니까 말이다. 어쩌면 기적이다.</p>
<p>우아한테크코스에 들어가서도 참 대단한 사람들이 많았다. 실제 현업에서 개발자로 일하다 온 크루, 명문대 컴공을 졸업한 크루, 많은 프로젝트 경험이 있는 크루에 비해 나는 <strong>“문과, 비전공자, 웹개발 경험 전무에 명문대도 아니었으니 말이다.”</strong></p>
<p>그래도 지금은 그들과 함께 이야기하며 지식을 나누고 함께 자라온 동료가 되어, 많이 성장했다고 생각한다. 그런 사람들에 섞여서 페어 프로그래밍을 진행하고, 팀 프로젝트를 하고, 서로 리뷰를 주고 받을 수준까지 되었으니 나는 내가 충분히 노력했고, 충분히 올라왔다고 생각한다.</p>
<p>물론, 결실을 맺을 때까지는 조금 더 달려야하겠지만 말이다!</p>
<hr>
<h2 id="💻-어떤-프론트엔드-개발자가-되고-싶나요">💻 어떤 프론트엔드 개발자가 되고 싶나요?</h2>
<p>이 질문은 우아한테크코스 레벨1 포코조의 오전에 하는 데일리 미팅에서 나온 주제였다. 사실 그 당시에는 여기에 대한 깊은 생각이 없어서 어떻게든 있어보이게만(?) 얘기했었던 것 같다.</p>
<p>심지어 레벨1 때 많은 미션에서 사용성을 잘 챙기지 못해 <strong>“프론트엔드”</strong> 개발자는 기능 개발만 하는 것뿐만 아니라 높아진 사용자들 수준에 맞게 웹페이지마다 적합한 사용성까지도 고려해야한다는 이야기도 코치분들, 리뷰어분들한테도 들었던 것 같다.</p>
<p>그랬던 내가 지금은 이력서 제목에 <strong>“UX와 DX개선을 좋아하는 개발자”</strong> 라고 당당히 써놓을 정도가 되었다.</p>
<p>무엇이 나를 이렇게 바꾸어 놓았을까? 나는 레벨3부터 4개월 가량 진행된 팀 프로젝트 <strong>“레벨로그”</strong> 라고 확신한다. 아무래도 나의 서비스인만큼 신경을 더 쓴 것도 있지만, 실제로 사용자가 들어오는 서비스를 사용자가 만족스럽게 느끼기 위해서 개선을 하는 것에 조금도 게을리하지 않았다고 자신한다.</p>
<p>웹 로딩 속도와 접근성을 개선하고, 불필요한 사용자의 클릭을 줄이고, 예상치 못한 불편을 직접 사용자들에게 들어가며 반영하고, 사용하기 불편한 기존의 UI를 개선하고, 사용성을 위해 기존 코드를 뜯어고치는 것도 마다하지 않았다! 사실 해보고 싶은 것들과 받은 인사이트들은 더 많았지만 4개월짜리 프로젝트는 너무 짧았다.</p>
<p>그리고 UX를 개선하는 과정에서 망가지는 코드들과 신경을 많이 쓰지 못한 불편한 개발환경들을 보면서 이런 것들을 개선하면 UX를 개선하는 것에도 큰 도움이 되겠다 싶어 DX 개선에도 관심을 가지게 되었다.</p>
<p>앞으로도 나는 <strong>“UX와 DX개선을 좋아하는 개발자”</strong>가 되기 위해 더더욱 노력할 것 같다.</p>
<hr>
<h2 id="🧗-2023년에는-무엇을">🧗 2023년에는 무엇을?</h2>
<p>당연히 취업을 해야겠지만, 막무가내로 <strong>“취업할 때가 되었으니까 취업을 하겠다”</strong>는 아니다. 4개월짜리 프로젝트를 하면서 겪은 짧은 데모판 시행착오와 성장이 너무 재미있었고, 이제 년단위로 지속되는 더 대규모의 큰 프로젝트를 하면서 시행착오를 겪어나가고 싶다는 생각이 들었다.</p>
<p>면접에서 지원동기를 물을 때, 단 한 번도 <strong>“돈 벌려고 지원하지 왜 지원하냐”</strong>라는 생각을 해본 적이 솔직히 한 번도 없다. 뭔가 납득되는 이유가 없다면 무언가를 하지 않는 편이다. 돈 벌려고 하는 거면 솔직히 개발자 말고 다른 거 해도 되지 않겠나 싶다. 왜 개발자이며, 왜 프론트엔드며, 왜 이 회사인지를 이제는 깊게 고민한다.</p>
<p>그런 의미에서 내가 하고 싶은 일과 경험, 그 곳으로 인도해줄 수 있는 회사에 입사할 수 있다면 좋을 것 같다. </p>
<p>여기까지 내가 성장하게 도움을 준 우아한테크코스 크루들, 코치분들, 포코조 사람들, 레벨로그 팀원들 모두 모두 감사합니다! 자, 이제는 글을 마무리 짓고 그럴 수 있기 위한 능력을 키우러 가보겠다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤔 프로토타입의 장점은 무엇일까?]]></title>
            <link>https://velog.io/@thumb_hyeok/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%98-%EC%9E%A5%EC%A0%90%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@thumb_hyeok/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%98-%EC%9E%A5%EC%A0%90%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Fri, 30 Dec 2022 11:47:31 GMT</pubDate>
            <description><![CDATA[<h2 id="🚀-들어가며">🚀 들어가며</h2>
<p>얼마 전, 우아한형제들 기술면접에서 <strong>“프로토타입의 장점은 무엇인가요?”</strong> 라는 질문을 받았다. 나의 답변이 부족한 부분이 많았던 것 같아고 아무래도 자바스크립트의 코어한 개념이기도 한만큼 다시 깊게 파보려고 이 글을 쓰게 되었다. </p>
<p>프로토타입을 장단점을 살펴보기 위해 총 세 가지 파트로 나누어서 진행하려고 한다.</p>
<ul>
<li>Prototype vs Class, on OOP</li>
<li>Prototype in JavaScript</li>
<li>Prototype, Merits and Demerits</li>
</ul>
<p>OOP에서의 두가지 컨셉의 개념과 지향하는 철학부터, 자바스크립트에서의 프로토타입을 가볍게 한 번 살펴보고 그리고 마지막으로 핵심인 <strong>“프로토타입의 장단점은 무엇인지”</strong>를 다시 대답해보도록 하겠다.</p>
<hr>
<h2 id="🥊-prototype-vs-class-on-oop">🥊 Prototype vs Class, on OOP</h2>
<p>자바스크립트의 프로토타입은 1987년에 등장한 최초의 프로토타입 객체 지향 언어인 <strong>“Self”</strong>에서 가져온 개념이다. <strong>“Self”</strong>를 통해 기존의 클래스 기반 프로그래밍과 비교되는 <strong>“프로토타입 기반 프로그래밍”</strong>이라는 패러다임에 대해서 알아보자.</p>
<h3 id="class-based-programing">Class-based Programing</h3>
<p>먼저, 클래스 기반 객체 지향 언어들의 문제를 살펴보자.</p>
<blockquote>
<p>클래스 기반 객체 지향 언어는 뿌리 깊은 이중성을 기반으로 한다.</p>
<ol>
<li><strong>클래스</strong>는 객체의 기본 “특징”과 “행동”을 정의한다.</li>
<li><strong>객체 인스턴스</strong>는 클래스의 특정 표현이다.</li>
</ol>
</blockquote>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/4d4d61c1-9ecc-430e-89f0-fa56947b0251/image.png" alt=""></p>
<p>위에 대한 예시를 보자. <strong><code>Vehicle</code></strong> 이라는 클래스가 있다. <strong><code>Vehicle</code></strong> 클래스는 <strong>“직장까지 운전”,  “건축 자재 배달”</strong> 과 같은 다양한 작업을 수행할 수 있는 기능이 있다.</p>
<p>그리고, 해리의 차를 위해 <strong><code>Vehicle</code> *<em>클래스의 *</em><code>ferrari</code></strong> 라는 이름을 가진 객체 인스턴스를 생성했다. 그런데 알고 보니 <strong><code>ferrari</code></strong> 는 스포츠카였다. <strong><code>Vehicle</code></strong> 클래스의 인스턴스인 <strong><code>ferrari</code> *<em>는 *</em>“건축 자재 배달”</strong>을 수행할 수 있어야 하지만, 실제론 할 수 없다.  </p>
<p>이 문제를 해결하기 위해서는 <strong><code>Vehicle</code></strong> 의 서브클래스를 사용해 전문화할 수 있다.</p>
<p> <strong><code>Vehicle</code></strong> 이 서브클래스로  <strong><code>SportsCar</code></strong> , <strong><code>Truck</code></strong> 를 갖도록 모델링한다. 그리고, <strong>“건축 자재 배달”</strong>은 <strong><code>Truck</code></strong> 으로 생성된 인스턴스가 할 수 있도록, <strong>“고속 주행”</strong>은 <strong><code>SportsCar</code></strong> 로 생성된 인스턴스가 할 수 있도록 만들어야한다.</p>
<p>그러나, 이러한 심층 모델은 클래스 설계 시 더 많은 통찰력이 필요하다. 먼 미래에 객체와 클래스가 어떤 특성을 갖게 될지 확실하게 예측할 수 없다면 클래스 계층 구조를 제대로 설계할 수 없다.</p>
<p>또한 클래스를 변경하면 클래스를 기반으로 하는 객체의 동작이 변경된다. 이러한 변경은 해당 클래스를 기반으로 하는 다른 객체에 예상치 못한 문제가 발생할 수 있기 때문에 매우 신중하게 실시해야한다. </p>
<p><strong>이러한 문제가 프로토타입을 만드는 동기 요인 중 하나이다.</strong></p>
<hr>
<h3 id="prototype-based-programing">Prototype-based Programing</h3>
<p>Self에서는 “클래스”를 기반으로 하는 객체의 “인스턴스”를 갖는 대신 <strong>기존 객체의 복사본을 만들고 변경</strong>한다. 따라서 위의 예시에서 <strong><code>Vehicle</code></strong> 객체의 복사본을 만든 다음 <strong>“고속 주행”</strong> 동작을 추가하여 <strong><code>ferrari</code></strong> 로써 모델링된다. 서브클래스를 만드는 과정이 필요없는 것이다.</p>
<p>이렇게 객체의 복사본을 만들 때의 기존 객체를 <strong>“프로토타입”</strong>이라 한다.</p>
<p>프로토타입을 사용하면, 기존 객체가 부적절한 모델인 것인 판명되었을 때, 올바른 동작을 가지는 변경된 객체를 작성해 그것을 대신 사용할 수 있다. 기존 객체를 사용하는 코드가 변경되는 문제도 발생하지 않는다. </p>
<hr>
<h2 id="🐕🐩-prototype-in-javascript">🐕🐩 Prototype in JavaScript</h2>
<p>위에서 <strong>“프로토타입 기반 프로그래밍”</strong>과 <strong>“클래스 기반 프로그래밍”</strong>을 비교해봤으니, 실제로 자바스크립트의 프로토타입은 어떤 것인지를 살펴보자.</p>
<blockquote>
<p><em>프로토타입 객체를 만든 다음 새 인스턴스를 만듭니다. 객체는 JavaScript에서 변경 가능하므로 새 인스턴스를 보강하여 새 필드와 메서드를 제공할 수 있습니다. 그런 다음 이들은 더 새로운 객체의 프로토타입 역할을 할 수 있습니다. 유사한 객체를 많이 만들기 위해 클래스가 필요하지 않습니다. 객체는 객체에서 상속됩니다. 그보다 더 객체지향적인 것이 있을까요?</em></p>
</blockquote>
<p>자바스크립트의 프로토타입 상속에 관한 더글라스 크락포드의 설명이다.
초기의 자바스크립트는 프로토타입을 채택하면서도, 자바처럼 보이기 위해서 “<strong>생성자 함수”</strong>와 <strong>“new 키워드”</strong>를 사용했지만 클래스 기반 OOP로 보이지만 사실상 프로토타입 기반이기 때문에 더 큰 혼란을 초래했다. </p>
<blockquote>
<p><em>JavaScript의 생성자 패턴은 고전적인 군중에게 호소력이 없었습니다. 또한 JavaScript의 진정한 프로토타입 특성을 모호하게 만들었습니다. 결과적으로 언어를 효과적으로 사용하는 방법을 아는 프로그래머는 거의 없습니다.</em></p>
</blockquote>
<p>위는 생성자 패턴에 대한 더글라스 크락포드의 의견이다. 이후 <code>Object.create()</code> 를 ECMAScript5 부터 지원하면서 기존의 문제점을 해결할 수 있게 되었다.</p>
<p>자바스크립트의 프로토타입을 통해 객체를 생성하고 프로토타입을 활용하는 것을 간단하게만 살펴보자.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/1a730770-34ad-4614-b02b-e0aab614cc52/image.png" alt=""></p>
<p>위 코드는 아까의 예시로 든 <strong><code>Vehicle</code></strong> 과 <strong><code>SportsCar</code></strong> 를 자바스크립트의 프로토타입을 통해 생성한 코드이다. <strong><code>ferrari</code></strong> 는 따로 서브클래스를 만들 필요 없이 <strong><code>Vehicle</code></strong> 를 프로토타입으로 지정하여 <strong><code>driveToCompony</code></strong> 메서드를 위임할 수 있고, 목적에 맞게 사용할 수 있도록 <strong><code>ferrari</code></strong> 객체에 따로 <strong><code>speedDrive</code></strong> 라는 메서드도 추가해주었다.</p>
<p>사실 여기서 <strong><code>Vehicle</code></strong> 직접 사용되는 객체라기보다 프로토타입으로써의 역할만 하는 것 같아서 좋은 방식이라는 생각이 들지는 않는다. 이건 아래에서 더 살펴보자. </p>
<p>이 글을 읽는 대상은 프로토타입에 대한 어느 정도의 이해가 있다고 생각하고 <code>__proto__</code> , <code>prototype</code> 등등 내부적인 부분이나 자세한 사항까지는 다루지 않겠다.</p>
<p><strong>이제 마지막으로 넘어가서 프로토타입의 장단점을 살펴보자!</strong></p>
<hr>
<h2 id="👍-merits">👍 Merits</h2>
<h3 id="1️⃣-점진적-학습">1️⃣ 점진적 학습</h3>
<p>클래스와 상속, 프로토타입과 위임은 “<strong>구체적인 상황에서 얻은 지식을 일반화하는 방식”</strong>의 두 가지 메커니즘이다. 하나의 예시를 통해 두 가지를 비교해보자.</p>
<p>위에 <strong><code>Vehicle</code></strong> 관련 예시를 가져와보겠다. 나에게 <strong><code>ferrari</code></strong> 라는 자동차가 있다. 이는 새로운 자동차인 <strong><code>volvo</code></strong> 을 만났을 때 <strong><code>ferrari</code></strong> 에 대한 지식을 활용할 수 있다. 둘은 모두 자동차이기 때문에 공통점을 가지고 있을 것이고 이를 일반화시킬 수 있을 것이다.</p>
<h4 id="클래스">클래스</h4>
<p>이를 어떻게 하는지, 먼저 클래스를 살펴보자.</p>
<p>클래스에서는 <strong><code>Vehicle</code></strong> 이라는 <strong>클래스(집합)</strong>을 생성할 수 있다. 이러한 <strong><code>Vehicle</code></strong> 클래스는 나에게 있는 <strong><code>ferrari</code></strong> 와 충분히 유사한 모든 자동차에 대해 사실이라고 믿는 것을 추상화한 집합이다. <strong><code>ferrari</code></strong> 는 <strong><code>Vehicle</code></strong> 의 인스턴스(구성원)으로 볼 수 있다.</p>
<p><strong>클래스(집합)</strong>는 <strong>모든 인스턴스(구성원)</strong>에 대해 사실을 나타내므로 이후에 자동차인 <strong><code>volvo</code></strong> 을 만나면 <strong><code>Vehicle</code></strong> 을 통해 <strong><code>volvo</code></strong> 를 설명할 수 있다. </p>
<p>그리고 각자가 다른 <strong><code>Vehicle</code></strong> 과 공유하지 않는 특성을 가지고 있다면 <strong><code>SportsCar</code></strong> , <strong><code>Truck</code></strong> 과 같은 서브클래스를 통해 구현할 수 있다.</p>
<hr>
<h4 id="프로토타입">프로토타입</h4>
<p>다음은, 프로토타입은 지식의 일반화다.</p>
<p>프로토타입에서는 <strong><code>ferrari</code></strong> 를 자동차의 프로토타입(원형)을 나타내는 것으로 간주할 수 있다. 나에게 가장 친숙한 자동차가 <strong><code>ferrari</code></strong> 라면, 자동차는 <strong><code>ferrari</code></strong> 자체의 이미지일 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/440ec738-0a9a-471a-83fe-61b633f2e356/image.png" alt=""></p>
<p><strong>“자동차의 바퀴가 몇 개인가?”</strong> 라는 질문에 달리 생각해야할 타당한 이유가 없는 한 질문에 대답하는 방식은 *<em><code>ferrari</code> *</em>의 바퀴가 몇 개인지와(4개 라고 가정) 답이 같다고 가정한다.</p>
<p>그리고 <strong><code>volvo</code></strong> 에 대해 설명하기 위해 프로토타입에서는 <strong><code>ferrari</code></strong> 를 <strong><code>volvo</code></strong> 의 프로토타입(원형)으로 지식을 활용할 수 있다.</p>
<p><strong>“volvo 의 바퀴가 몇 개인가?”</strong> 라는 질문에는 반대 증거가 없는 이상 <strong><code>ferrari</code></strong> 와 같다고 대답한다. 그러나, 이후 <strong><code>volvo</code></strong> 의 바퀴가 8개라는 사실을 알게 되면, 이는 <strong><code>volvo</code></strong> 에 대한 지식으로 저장되고, 프로토타입에 대한 참조를 확인하기 전에 검색된다.</p>
<p>또한 번거롭게 기존 수퍼클래스를 수정하거나, 서브클래스를 생성할 필요도 없다 .</p>
<p>아까 “<strong>Prototype in JavaScript”</strong> 파트에서 든 예시보다 이 예시가 프로토타입을 설명하기에, 사용하는 방식으로도 더 적합하다고 나는 생각한다.</p>
<hr>
<blockquote>
<p>💡** 그래서 점진적 학습이 뭔데?
**</p>
</blockquote>
<p>위에서 살펴본 바에 의하면, 프로토타입 접근 방식은 어떤 면에서 사람들이 구체적인 상황에서 지식을 습득하는 것처럼 보이는 방식에 더 가깝다. </p>
<p>사람들은 일반적인 추상 원리를 먼저 흡수하고 나중에 특정 사례에 적용하는 것보다 <strong>구체적인 예를 먼저 다룬 다음 그로부터 일반화하는 것</strong>을 훨씬 더 잘하는 것 같다.</p>
<p>그러나, 클래스(집합)에서는 개별 인스턴스(구성원)을 생성하기 전에 먼저 집합에 대한 추상화를 해야한다. 수학에서 집합은 구성원을 열거하거나, 집합의 구성원을 식별하는 통합 원칙을 설명해 정의한다.</p>
<p>*<em>그러나 우리는 모든 자동차를 열거할 수 없다. 10억번째까지 자동차의 바퀴는 4개였지만, 10억1번째 자동차의 바퀴는 8개일 수도 있다. *</em></p>
<p>하지만 클래스(집합)의 개념은 이와 위배된다. 이와 같이 프로토타입은 사람들이 더욱 편하게 생각하는 방식인 구체적인 예시들을 통해 개념에 대한 <strong>“점진적 학습”</strong>을 하는 데에 유리하다. </p>
<hr>
<h3 id="2️⃣-메모리">2️⃣ 메모리</h3>
<p>프로토타입은 클래스 방식에 비해 <strong>“메모리”</strong>를 아낄 수 있다는 장점이 있다. 클래스는 새로운 인스턴스를 만들 때 <strong>“복사”</strong>를 하지만, 프로토타입은 객체와 객체를 <strong>“연결”</strong>한다.</p>
<p>하나의 클래스에서 100개 인스턴스를 만든다면, 클래스의 메서드를 100개 복사한다. 그렇지만 프로토타입에서는 프로토타입 객체에 있는 메서드를 참조하고 있을 뿐임으로, 메서드 1개만 존재하게 된다.</p>
<p>그렇기 때문에 자바와 같은 클래스 기반 언어에서는 메모리를 아끼기 위해 하나의 클래스에서 하나의 인스턴스만 생성하는 싱글톤 패턴 등을 사용한다고 한다.</p>
<p>물론, 아래에서 다루겠지만 이로 인한 프로토타입의 단점도 존재한다. </p>
<hr>
<h2 id="👎-demerits">👎 Demerits</h2>
<h3 id="1️⃣-속도">1️⃣ 속도</h3>
<p>프로토타입은 위임을 통해서 객체를 생성할 때 상위 객체의 메서드를 <strong>“연결”</strong> 하므로 객체의 크기를 줄여 메모리를 아낄 수 있는 대신, 메서드를 검색하기 위해 상위 프로토타입 체인을 따라 검색해야한다. </p>
<p>이는** 속도 저하로 이어질 수 있다는 문제**가 있다.</p>
<p>다행히도, 검색 시간을 줄이는 방식으로 조회 결과를 <strong>“캐싱”</strong>하는 방법이 있다고 한다. 크롬의 V8 엔진은 <strong>“히든 클래스”</strong>라는 개념을 통해 이러한 속도 문제를 해결했다.</p>
<hr>
<h2 id="🧐-정리">🧐 정리</h2>
<p>오늘은 <strong>프로토타입 방식의 장단점</strong>을 알아봤다.</p>
<ul>
<li>장점: <strong>구체적 예시를 통한 개념의 점진적 학습, 메모리를 효율적으로 사용</strong></li>
<li>단점: <strong>속도가 느림</strong></li>
</ul>
<p>로 끝났지만 나열된 장점이 더 많다고 프로토타입이 무조건 클래스보다 좋다거나, 그 반대라거나 한 것은 아니다. 아직까지도 논의가 활발한 것으로 알고 있다. </p>
<p>어찌됐든 자바스크립트가 프로토타입 방식을 채택한만큼, 프로토타입을 잘 활용하기 위해 프로토타입과 그 배경에 대한 공부를 열심히 하는 것이 좋아보인다. </p>
<p>사실 이 글을 쓰면서 본 자료에서 더 많은 비교와 장단점을 봤지만 도저히 이해를 했다는 생각이 들지 않아서 이 글에는 포함을 시키지 못했다. (여러 소스의 동작을 결합하기 위해 더 유연, 점진적이고 인터랙티브한 개발에 유리 등등 ...) </p>
<p>추후 업데이트하거나 토끼굴에 빠질까봐 미룰 것 같다.</p>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li>Self <ul>
<li><a href="https://selflanguage.org/">selflanguage.org</a></li>
<li><a href="https://en.wikipedia.org/wiki/Self_(programming_language)">Self (programming language)</a></li>
<li><a href="https://handbook.selflanguage.org/4.5/morphic.html#morph-traits-and-prototypes">Self Handbook for Self 4.5.0 documentation</a></li>
</ul>
</li>
</ul>
<ul>
<li><p>prototype </p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Prototype-based_programming">Prototype-based programming</a></li>
<li><a href="http://crockford.com/javascript/prototypal.html">Prototypal Inheritance in JavaScript
</a></li>
<li><a href="https://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html">Using Prototypical Objects
to Implement Shared Behavior
in Object Oriented Systems</a></li>
<li><a href="https://alistapart.com/article/prototypal-object-oriented-programming-using-javascript/">Prototypal Object-Oriented Programming using JavaScript
</a></li>
</ul>
</li>
<li><p>JavaScript Performance</p>
<ul>
<li><a href="https://ui.toast.com/posts/ko_20210909">자바스크립트 성능의 비밀 (V8과 히든 클래스)</a></li>
<li><a href="https://blog.bitsrc.io/secret-behind-javascript-performance-v8-hidden-classes-ba4d0ebfb89d">Secret Behind JavaScript Performance: V8 &amp; Hidden Classes</a></li>
</ul>
</li>
</ul>
<ul>
<li>Singeleton <ul>
<li><a href="https://tecoble.techcourse.co.kr/post/2020-11-07-singleton/">싱글톤(Singleton) 패턴이란?</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[📉 Github Actions CI 프로세스 개선]]></title>
            <link>https://velog.io/@thumb_hyeok/Github-Actions-CI-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B0%9C%EC%84%A0</link>
            <guid>https://velog.io/@thumb_hyeok/Github-Actions-CI-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EA%B0%9C%EC%84%A0</guid>
            <pubDate>Mon, 31 Oct 2022 09:07:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/4903661f-4c87-419e-9abe-62553c36abc7/image.png" alt=""></p>
<h2 id="🤔-문제가-뭐야">🤔 문제가 뭐야?</h2>
<p>저번에 <a href="https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%A0%84%EB%9E%B5">레벨로그의 타입스크립트 -컴파일 전략-</a> 에서 dev와 prod의 빌드 속도를 개선하는 결과를 얻었다. Github Actions CI prod build 기준으로 약 <strong>&quot;32초</strong>&quot;에서 <strong>&quot;14초&quot;</strong> 정도로 개선을 했었다.</p>
<p>하지만 이건 레벨로그의 CI build 타임을 보면 아주 작은 부분이었기 때문에 다른 부분들의 개선이 필요하다고 생각했다. 아래 사진을 보자</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/3f8bfbce-232c-436e-8ee5-7ac9a133f709/image.png" alt=""></p>
<p>개선 후에도, CI build 타임을 보면 약 <strong>&quot;150s&quot;</strong>임을 알 수 있다. 어떤 문제점들이 있는지는 하나 하나 아래에서 불릿으로 살펴보자.</p>
<ul>
<li><p><strong>백엔드, 프론트엔드 구분 없이 <code>workflow</code> 가 모두 트리거되어 실행된다.</strong>
프론트엔드 관련 파일만 수정된 프론트엔드 PR에서 백엔드의 테스트와 소나큐브 빌드가 실행되고 그 반대의 경우에도 프론트엔드의 빌드가 진행되었다. (여기서 생기는 부수적인 문제들도 꽤 있었다.)</p>
</li>
<li><p><strong>모든 PR에 <code>Dev Build</code> 와 <code>Prod Build</code> 를 실행한다.</strong>
개발서버에 배포될 develop 브랜치와 운영서버에 배포될 main 브랜치에 PR에 각각의 build만 실행되지 않고, 무조건 두 가지 빌드를 다 했다.</p>
</li>
<li><p><strong>의존성을 다운로드하는 시간이 매우 길다</strong>
전체 &quot;150s&quot; 가량에서 &quot;100s&quot; 정도를 의존성을 다운로드하는데 사용한다.</p>
</li>
<li><p><strong>CI 과정에서 빌드만 한다</strong>
프론트엔드 CI 과정에서 &quot;빌드&quot;만 하고, 이미 프론트엔드에서 작성한 &quot;테스트&quot;들이 전혀 포함이 되어있지 않았다.</p>
</li>
<li><p><strong>적절하지 않은 <code>Required Status Checks</code></strong>
지금까지 브랜치 보호 규칙 중 <code>Required Status Checks</code> 에는 <strong>&quot;build&quot;</strong>, <strong>&quot;test&quot;</strong> 만 포함되어있었다. 지금까지 백엔드는 소나큐브 빌드가 실패해도 merge가 막혀있지 않아서 하려고 하면 할 수 있었다.</p>
</li>
</ul>
<blockquote>
<p><strong>아래에서 차례로 이런 문제들을 해결해보자!</strong></p>
</blockquote>
<hr>
<h2 id="🚀-프론트엔드-ci-개선하기">🚀 프론트엔드 CI 개선하기</h2>
<p>일단 프론트엔드 CI에 있는 문제들부터 해결해보기로 했다.</p>
<h3 id="dev-build-prod-build">Dev Build, Prod Build</h3>
<p>기존에는 프론트의 모든 <strong><code>workflow</code> *<em>가 *</em><code>frontBuild.yml</code></strong> 파일 하나에서 관리되고 있었다. 어떤 브랜치에 PR을 날렸냐에 따라 다른 <strong><code>workflow</code> *<em>를 트리거하도록 하기 위해서 *</em><code>front-dev.yml</code> *<em>, *</em><code>front-prod.yml</code></strong> 로 나눴다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/b8ae1266-1819-4a15-977b-f95359ac2262/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/6c2159b7-20d1-4432-937c-769ce20c7bdb/image.png" alt=""></p>
<p><strong>&quot;Github Actions&quot;</strong>의 동작이 각각의 브랜치에 PR을 보냈을 때 이뤄지도록 yml 파일도 수정해주었다. 테스트 결과, 원하는 동작이 수행되는 것을 확인할 수 있었다.</p>
<hr>
<h3 id="의존성-캐시">의존성 캐시</h3>
<p>다음은 시간이 가장 오래걸리는 의존성 다운로드 시간을 줄이기 위해 의존성을 캐시하고, <strong><code>yarn install</code></strong> 의 설정을 CI 환경에 맞게 바꿔주는 작업을 진행했다. </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/ca42631b-c77f-4127-ad6c-590387e0a032/image.png" alt=""></p>
<pre><code class="language-jsx">yarn install
yarn install --frozen-lockfile</code></pre>
<ul>
<li><strong><code>yarn install</code></strong> 의 기본 동작은 <code>yarn.lock</code> 이 있고 모든 종속성을 충족하기에 충분한 경우에 <code>package.json</code> 에 기록된 정확한 버전의 <code>yarn.lock</code> 이 설치되고, 변경되지 않는다.</li>
<li>그러나, <code>yarn.lock</code> 이 없거나, 모든 종속성을 충족하기에 충분하지 않은 경우, yarn은 <code>package.json</code> 의 제약 조건을 충족하는 사용 가능한 최신 버전을 찾고, 결과를 <code>yarn.lock</code> 파일에 기록한다.</li>
</ul>
<p><strong><code>yarn install --frozen-lockfile</code></strong> 은 두번째 상황과 같을 경우 <code>yarn.lock</code> 파일을 수정하지 않고, 업데이트가 필요할 경우 실패한다. <a href="%5Bhttps://github.com/yarnpkg/yarn/issues/5847%5D(https://github.com/yarnpkg/yarn/issues/5847)">관련된 이슈</a>에도 나와있지만 다른 패키지 매니저 “npm”의 <code>npm ci</code> 와 같은 역할을 한다.</p>
<blockquote>
<p>실제로 적용해본 결과 jenkins 및 알 수 없는 에러로 <strong><code>frosen lockfile</code></strong> 설정은 당장은 사용하기 힘든 것 같다! 일단 다시 되돌리고 문제를 찾아볼 예정이다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/ac3c51fa-c060-4865-b17e-522b0909c497/image.png" alt=""></p>
<p>테스트 결과, 캐시된 의존성을 사용해 <strong><code>Install Dependenies</code></strong> 과정을 생략해서 빌드 타임이 <strong>&quot;29s&quot;</strong> 까지 줄어든 것을 확인할 수 있었다!</p>
<hr>
<h3 id="테스트-자동화">테스트 자동화</h3>
<p>마지막으로, <code>jest</code> 로 작성한 유닛테스트를 CI 과정에 추가했다.
아래와 같이 <strong><code>front-unit-test.yml</code></strong> 파일을 생성해 모든 브랜치에서 PR이 발생하면 트리거되어 유닛테스트 결과가 성공해야만 merge할 수 있도록 했다!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/cc7613e9-b99e-43b2-a707-42ecdbd24806/image.png" alt=""></p>
<hr>
<h2 id="🏘️-백엔드-프론트엔드-ci-분리">🏘️ 백엔드, 프론트엔드 CI 분리</h2>
<p><strong><code>프론트엔드 workflow</code></strong> 가 완료되는 시간이 <strong>&quot;30s&quot;</strong> 가량으로 많이 줄었지만, 무조건 <strong><code>백엔드 workflow</code></strong> 가 같이 실행되기 때문에 일찍 끝나봤자 아무런 의미가 없었다. 그리고 애초에 프론트엔드 PR에서 <strong><code>백엔드 workflow</code></strong> 까지 실행할 이유가 없다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/c79f5ea1-5125-4937-a22d-4adf679165c0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/dab7ea47-cfc6-46ca-91ee-e70e74087d30/image.png" alt=""></p>
<p>레벨로그의 리포지토리에는 최상단에 <strong><code>frontend</code> , <code>backend</code></strong> 폴더가 있다.
어떤 폴더가 수정됐냐에 따라서 각자의 <strong><code>workflow</code></strong> 만 실행하도록 수정했다.</p>
<hr>
<h2 id="🔒-브랜치-보호-규칙-수정">🔒 브랜치 보호 규칙 수정</h2>
<p>백엔드, 프론트엔드의 CI를 분리하니까 생긴 문제는 레벨로그 리포지토리의 브랜치 보호 규칙이었다. 레벨로그의 <strong><code>Required Status Checks</code></strong> 에는 <strong><code>build</code></strong>, <strong><code>test</code></strong> 가 있었다</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/3622e674-254a-4d4d-ba04-aef7a900ed77/image.png" alt=""></p>
<p>하지만 <strong><code>백엔드 workflow</code></strong> 에는 <strong><code>sonarqube-build</code></strong> 와 <strong><code>test</code></strong> 라는 jobs만 있었기 때문에 있지도 않은 <strong><code>build</code></strong> 라는 job을 기다리느라 merge를 할 수 없는 문제가 발생했다. 이에 대한 해결책은 &quot;<a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks">Github Docs</a>&quot;에서 찾을 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/9eb04a56-28ef-406b-a31b-313933b3dfdb/image.png" alt=""></p>
<p>해답은 동일한 이름의 job을 추가하고, 무조건 성공하도록 하는 것이다. 
사실 뭔가 조금 찝찝하지만, 아직 다른 방법을 찾지는 못했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/45931665-0cdf-48a0-bda6-d7b2b20f9970/image.png" alt=""></p>
<p>백엔드는 <strong><code>sonarqube-build</code></strong> 도 성공해야하기 때문에, <strong><code>Required Status Checks</code></strong> 에 추가해주었다. <strong><code>프론트 workflow</code></strong> 에도 <strong><code>sonarqube-build</code></strong> 라는 job이 없기 때문에 위와 같은 작업을 동일하게 해줬다.</p>
<hr>
<h2 id="📉-결과">📉 결과</h2>
<p>자, 이제 프론트 PR이 올라왔을 때를 살펴보자!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/39cb6997-be25-4390-809e-0eb1b17881d4/image.png" alt=""></p>
<blockquote>
<p><strong>모든 작업이 끝났다. 위에서 가지고 있었던 문제들을 모두 해결했다!</strong></p>
</blockquote>
<ul>
<li>백엔드, 프론트엔드 구분 없이 workflow 가 모두 트리거되어 실행된다.</li>
<li>모든 PR에 Dev Build 와 Prod Build 를 실행한다.</li>
<li>의존성을 다운로드하는 시간이 매우 길다</li>
<li>CI 과정에서 테스트 없이 빌드만 한다</li>
<li>적절하지 않은 Required Status Checks</li>
</ul>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li><a href="https://blog.banksalad.com/tech/github-action-npm-cache/">뱅크샐러드 Web chapter에서 GitHub Action 기반의 CI 속도를 개선한 방법</a></li>
<li><a href="https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows">Caching dependencies to speed up workflows - GitHub Docs</a></li>
<li><a href="https://docs.github.com/en/actions/using-workflows/triggering-a-workflow">Triggering a workflow - GitHub Docs</a></li>
<li><a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/about-protected-branches#require-status-checks-before-merging">About protected branches - GitHub Docs</a></li>
<li><a href="https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks">Troubleshooting required status checks - GitHub Docs</a></li>
<li><a href="https://nts.strzibny.name/freezing-dependencies-yarn-lock-frozen-lockfile/">Freezing your Node.js dependencies with yarn.lock and -frozen-lockfile</a></li>
<li><a href="https://github.com/yarnpkg/yarn/issues/5847">https://github.com/yarnpkg/yarn/issues/5847</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎨 레벨로그의 UI/UX 개선기]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-UIUX-%EA%B0%9C%EC%84%A0%EA%B8%B0</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-UIUX-%EA%B0%9C%EC%84%A0%EA%B8%B0</guid>
            <pubDate>Wed, 26 Oct 2022 04:39:07 GMT</pubDate>
            <description><![CDATA[<h2 id="🎨-uiux-개선기">🎨 UI/UX 개선기</h2>
<p>나는 3차 데모가 끝나고,** 늘 우선순위에서 밀리고 밀려 버려져있던 UI/UX<strong>를 스프린트4 때 꼭 개선해서 방학이 끝나고 딱 레벨 인터뷰를 하게될 때 **우리의 서비스를 사용하도록 하기로 마음 먹었다</strong>.</p>
<p>지금까지 데모에서 쌓인 UI/UX에 대한 피드백, 기능추가보다 사용성 개선이 시급한 여러 이유를 통해 팀원들을 설득하는데에 성공하고, 4차 스프린트 2주차 때 UI/UX 개선 작업이 시작되었다.</p>
<hr>
<h2 id="🤢-기존의-불편한--uiux">🤢 기존의 불편한  UI/UX</h2>
<p>먼저 기존의 페이지들부터 살펴보자. 이 당시 존재했던 페이지는 아래와 같다.</p>
<ul>
<li><strong>팀</strong> - 팀 전체 조회, 팀 상세 조회, 팀 생성, 팀 수정 페이지</li>
<li><strong>레벨로그</strong> - 레벨로그 작성, 수정 페이지, 조회 모달</li>
<li><strong>피드백</strong> - 피드백 작성, 수정, 조회 페이지,</li>
<li><strong>사전질문</strong> - 사전질문 작성, 수정 페이지, 조회 모달</li>
</ul>
<p>수정페이지는 생성페이지와 같은 스타일을 사용함으로 생략하겠다!
추가로 두 모달은 동일한 스타일을 사용하기 때문에 사전질문에서만 살펴보도록 하겠다!</p>
<hr>
<h3 id="팀-관련-페이지">팀 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/cff8173a-5340-44e0-94c7-9e65d3f944d3/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/f0ec8b49-6692-45f2-afb6-06788de315f1/image.gif" alt=""></p>
<p>여기는 팀 전체 조회 페이지이다. </p>
<p><strong>반응형에 대한 상세한 고려도 없었</strong>고, 특정 해상도에 화면이 맞지않으면 <strong>우측에 애매한 공백</strong>이 생겼다.  그 외에도** 해상도를 높이면 한 줄에 무수히 많은 팀 카드가 배치<strong>되고, 해상도가 높은 화면에서 가독성이 나빠지는 문제도 있었다. **심지어는 footer도 마음대로 늘어난다</strong>..</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/92e4288c-1899-4e71-bbae-ec212f331e26/image.png" alt=""></p>
<p>위의 팀 카드를 보면, 사용하지 않는 <strong><code>dm 주소</code> , <code>2022-12-31T12:59:00</code> *<em>과 같은 불편한 날짜 표현, *</em>날짜나 장소가 길어지면 읽기 힘들게 두 줄이 된다</strong>는 문제도 있었다.
추가로 <strong>참가자가 늘어나면 맨 아래의 참가자 프로필이 2줄, 3줄, 무한정 늘어나는 문제</strong>들도 있었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/dcfe5700-8b66-4965-8a34-39a9f6d9014d/image.png" alt=""></p>
<p>다음 팀 생성페이지를 보면, 타이틀은 <strong><code>인터뷰 팀 생성하기</code></strong> 인데, 우측의 버튼은 <strong><code>만들기</code></strong> 라고 되어 있어 <strong>일관성이 없다</strong>. 그리고 팀을 생성하기 위해 모든 정보를 입력하고 <strong>다시 위로 올라와서 만들기 버튼을 눌러야하는 번거로움</strong>도 존재했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/83705fa4-b936-462e-8a28-c81eaea0ee04/image.gif" alt=""></p>
<p><strong><code>팀 상세 조회</code></strong> 페이지도 <strong><code>팀 전체 조회</code> **페이지와 같은 문제점을 가지고 있었다. 추가적인 부분이라면</strong> 버튼에 마우스가 호버되거나 클릭되어도 아무런 애니메이션이 없어** 텍스트만 보이는  </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/8b1a895d-0598-48b1-aba5-ca749b71dd89/image.png" alt=""></p>
<p>*<em><code>레벨로그 보기</code>, <code>사전 질문 작성</code> *</em>과 같은 버튼이 더더욱 버튼처럼 보이지 않는다는 점이 있었다. 대체적인 문제점을 다뤄봤으니 다음 페이지부터는 속도감 있게 살펴보자.</p>
<hr>
<h3 id="레벨로그-관련-페이지">레벨로그 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/bd601a1c-f214-4db2-bd0a-6ba00e5a12d0/image.png" alt=""></p>
<p>타이틀에서는 <strong><code>작성</code></strong>, 버튼은 <strong><code>제출하기</code></strong>, 타이틀에서는 <strong><code>레벨로그</code>,</strong> 밑에는 <strong><code>Level Log</code></strong> 로 <strong>한/영의 혼용</strong>, 버튼이 맨 위에 있어 다 작성 후에 다시 스크롤을 올려서 버튼을 눌러야했다. 
그리고 <strong>&quot;레벨로그 작성을 위한 공간&quot;</strong>을 위에 있는 타이틀부터 해서 <strong>쓸데 없는 요소에게 너무 많이 뺏긴다</strong>.</p>
<h3 id="피드백-관련-페이지">피드백 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/80e4a3a4-730e-402a-85f5-6c302d6d0c6a/image.png" alt=""></p>
<p>피드백 작성 페이지는 심지어 <strong><code>제출</code>, <code>작성</code></strong> 이 아니라 또 새로운 키워드인 <strong><code>등록하기</code></strong> 였다. 
*<em>심지어 이 페이지로 진입하기 위해서는 피드백 페이지에서 <code>추가하기</code> *</em>버튼을 클릭해야한다.</p>
<p>또한 좌측에는 <strong><code>레벨로그</code></strong> 라고 한글로 써있고, 우측에는 *<em><code>Feedback</code> *</em>이라고 영어로 써있다. 
그리고 좌측과 우측의 흰박스의 시작지점이 어긋나있다.</p>
<p>또한 꽤 자주 등장한 문제였는데, 타이틀이 <strong><code>해리의 레벨 인터뷰 피드백</code></strong> 이라 <strong>작성자</strong>가 해리인건지, <strong>피드백을 받는 사람</strong>이 해리인건지도 헷갈린다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/a650ae04-4afa-4dce-936f-1a22cad76d5c/image.png" alt=""></p>
<p>이 페이지도 반응형이 제대로 고려되지 않아, 위에서 봤던 *<em><code>팀 전체 조회</code>, <code>팀 상세 조회</code> *</em>페이지와 같은 문제점들을 가지고 있다. *<em>(피드백이 위치한 애매한 위치는 지금봐도 웃기다.) *</em></p>
<p>그리고 타이틀에 <strong><code>레벨 인터뷰 피드백</code></strong> 이라고만 적혀있어 누구에 대한 피드백인지 개발자인 나도 가끔 헷갈려서 다시 나가서 확인을 하고 들어와야 했다.</p>
<p>그리고 피드백에서 <strong>잘 보여야할 부분이 회색</strong>으로 칠해져 잘 보이지 않았고, 오히려 <strong>안 보여도 상관없는 테두리 부분이 흰색</strong>으로 더 눈에 띄었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/5411a78d-a834-4038-8ca7-41577244c5a3/image.png" alt=""></p>
<p>그리고 피드백이 없는 참가자의 피드백 페이지에 들어왔을 때는** 뭔가 잘못 들어온건가 싶은 페이지<strong>로 오게된다. (하지만 이 페이지로 오는게 맞다) 여전히 윗 공간이 비어있으니 **footer도 커진다.</strong></p>
<hr>
<h3 id="사전질문-관련-페이지">사전질문 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/ebc8d858-506a-4068-be03-d55de01756b0/image.png" alt=""></p>
<p>그나마 여기는 상황이 조금 낫다. 누구에 대한 사전질문인지가 헷갈라는 점과 <strong><code>작성하기</code></strong> 버튼이 상단에 있는 것 빼고는. 그래서 스낵바 없이 <strong><code>alert</code></strong> 만 사용하는 문제라도 들고 왔다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/e45866f7-adb8-4002-9d4b-8ef9504476b8/image.png" alt=""></p>
<p>여기 모달의 버튼도 텍스트만으로 이루어져 있고 딱히 애니메이션도 없다. </p>
<hr>
<h2 id="🎯-ux-개선-전략-짜기">🎯 UX 개선 전략 짜기.</h2>
<p>이제 대부분의 페이지에 있는 문제들을 살펴봤으니, 아래에서 내가 이를 어떻게 해결하려 했는지 살펴보자. 첫번째 단계에서는 불편한 요소이 무엇인지를 모두 파악하는 일이 었다. 데모에서 받은 피드백을 적극적으로 활용했다.</p>
<h3 id="문제-파악">문제 파악</h3>
<ul>
<li><p><input disabled="" type="checkbox">  상세한** 반응형이나 애니메이션, 요소의 포지션** 등이 고려되지 않았다. </p>
</li>
<li><p><input disabled="" type="checkbox">  <strong>제목, 날짜 등이 길어지는 경우를 고려하지 않아</strong> 2줄로 늘어나는 등의 문제가 있었다. </p>
</li>
<li><p><input disabled="" type="checkbox">  ** 사용자가 보기 힘든 표기법을 사용**한다. ex) 2022-12-31T12:59:00</p>
</li>
<li><p><input disabled="" type="checkbox">  <code>작성</code>, <code>제출</code>, <code>등록</code>, <code>만들기</code>, <code>생성</code> 등 <strong>같은걸 의미하는 다른 단어</strong>가 너무 많다</p>
</li>
<li><p><input disabled="" type="checkbox">  <strong>영어와 한글이 혼용</strong>되고 있다.</p>
</li>
<li><p><input disabled="" type="checkbox">  <strong><code>to</code>, <code>from</code> 구분</strong>이 어렵다.</p>
</li>
<li><p><input disabled="" type="checkbox">  에러메시지를 그대로** <code>alert</code>** 를 사용해 보여준다.</p>
</li>
<li><p><input disabled="" type="checkbox">  모든걸 작성하고 <code>작성하기</code> 와 같은 버튼을 클릭하려면** 스크롤을 최상단으로 올려야한다.**</p>
</li>
<li><p><input disabled="" type="checkbox">  쓸데없는 요소에게 메인공간이 너무 많이 뺏긴다.</p>
</li>
</ul>
<hr>
<h3 id="좀-더-상세한-피그마">좀 더 상세한 피그마.</h3>
<p>기존의 피그마는 반응형, 특수한 케이스, 애니메이션 등의 고려가 없었다. 이런게 없으면 <strong>개발자 간의 의견 차이</strong>나 <strong>구현의 차이</strong>가 발생하게한다.</p>
<p>의견 차이는 <strong>추가적인 커뮤니케이션이 자주 발생하도록 해 쓸데 없는 시간</strong>을 잡아먹도록 하고, 구현의 차이는 <strong>서로의 코드를 이해하는데 시간을 더 쓰게 만들고, 코드 유지보수에 어려움</strong>을 줄 수 있다. </p>
<p>새로운 피그마에서는 이를 <strong>UX에 관한 상세한 코멘트</strong> 등으로로 이를 개선했다.</p>
<hr>
<h3 id="최소-요구-사항-늘리기">최소 요구 사항 늘리기.</h3>
<p>아무래도 피그마를 기반으로 작업을 하는데, 기존 <strong>피그마에 UX 관련된 내용이 거의 없었기</strong> 때문에 <strong>“기능만 되면 된다”</strong>는 식으로 UX는 내팽겨쳐지는 경우가 많았다. </p>
<p><strong>새로운 피그마에는 UX 관련 요구사항을 많이 추가</strong>했고, 피그마를 기반으로 작업하게되는 한, 당연히 개선으로 이어지리라 생각하게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/5ce35039-d431-4252-b77c-22c1d9b1d098/image.png" alt=""></p>
<p>➡️ <a href="https://www.figma.com/file/FyuA7YhrtkXdCg2AvTBRUO/Levellog?node-id=696%3A5">레벨로그 피그마</a> (Page 2)</p>
<p>그 결과로 탄생한 피그마이다! 아래에서 이 피그마를 반영한 새로운 UI를 살펴보자.</p>
<hr>
<h2 id="✨-바뀐-uiux-와의-비교">✨ 바뀐 UI/UX 와의 비교</h2>
<p>다음 릴리즈에 몇 가지 기능이 추가되어서 약간 기존과 달라진 부분이 있지만, 신경쓰지 말고 문제점이 어떻게 개선되었는지를 중점으로 살펴보자!</p>
<h3 id="팀-관련-페이지-1">팀 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/21255b1b-7626-41fb-8656-21f6b2cf7fdd/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/af35bcea-a0f3-4bd7-8540-5f4eb0bb56e3/image.gif" alt=""></p>
<p>개선된 전체 팀 조회이다.
기존의 핵심 문제였던 <strong>반응형</strong>에 대한 문제가 해결되었다.
해상도가 아무리 높아져도 메인영역의 width에 제한을 둬 <strong>한 줄에 4개까지</strong>만 팀이 표시되도록했다.
그 외 <strong><code>팀 추가하기</code></strong> 버튼의 위치도 스크롤을 내려도 바로 클릭할 수 있도록 수정했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/9e5be1ec-ba51-4bf2-91c3-c37f18d3cead/image.png" alt=""></p>
<p>장소와 시간을** 각각의 열로 분리<strong>해 **2줄로 바뀌는 불상사</strong>가 일어나지 않도록 수정했다.
또한 참가자가 늘어나도 스크롤로 넘기도록해서 <strong>줄바꿈이 일어나는 일도 이제는 없다</strong>. </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/7198a648-50b2-4267-9acb-ef531085c1c7/image.gif" alt=""></p>
<p>이제 팀 생성 페이지에서 팀을 생성하기 위해 모든 정보를 입력하고 다시 위로 올라와서 만들기 버튼을 눌러야하는 번거로움도 <strong><code>생성하기</code></strong> 버튼을 최하단에 위치시켜 제거했다.
(당시에는 <strong>&quot;생성하기&quot;</strong> 였지만 지금은 팀 전체조회 페이지와 동일하도록 <strong>&quot;추가하기&quot;</strong>로 용어가 통일되었다!)</p>
<p>그리고 모든 버튼에** &quot;hover&quot;<strong>나 **&quot;click&quot;</strong>시, 최소한 상호작용을 하고 있다는 것을 알릴만한 변화를 주었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/2fecbef1-0c9e-4d6a-a78a-97b4e2915a42/image.gif" alt=""></p>
<p>마찬가지로 애매하게 공간이 남거나 하는 문제들이 동일하게 해결되었다.
다음은 레벨로그 관련 페이지로 넘어가보자!</p>
<hr>
<h3 id="레벨로그-관련-페이지-1">레벨로그 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/345e4098-dd30-4f97-b21c-6cba6b1fee2b/image.png" alt=""></p>
<p><strong>&quot;레벨로그 작성을 위한 공간&quot;</strong>을 최대한 확보했고, 용어도 <strong><code>작성하기</code></strong> 로 통일되었다. 버튼도 최하단에 고정되어있어 다시 스크롤을 최상단으로 올릴 필요도 없다. </p>
<hr>
<h3 id="피드백-관련-페이지-1">피드백 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/539ddefc-9cbd-4106-8375-f8c0ffc1390f/image.png" alt=""></p>
<p>&quot;인터뷰에서 받은 질문&quot; 칸이 새로 생겼지만 신경쓰지 말자.
<strong><code>From</code> , <code>to</code></strong> 가 헷갈렸던 문제를 <strong>&quot;~에 대한 레벨 인터뷰 피드백&quot;</strong>으로 고치고 프로필 사진까지 추가해 해결했다. 마찬가지로 <strong><code>작성하기</code></strong> 버튼은 하단 고정.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/84f0ebea-1d17-4b0d-ab3c-6cfc8b0e8f42/image.png" alt=""></p>
<p>피드백 페이지에서도 <strong><code>From</code> , <code>to</code></strong> 가 헷갈렸던 문제를 위와 동일한 방식으로 해결했고, 어떤 해상도에서도 하나의 피드백이 가운데에 위치하도록 하여 애매한 공백이나, 반응형 문제를 해결했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/cf193610-2689-4f39-b7a8-4923ab756133/image.png" alt=""></p>
<p>피드백이 존재하지 않을 때 보여줄 <strong>&quot;빈 피드백 페이지&quot;</strong>를 추가로 만들었다. 이 외에도 <strong>&quot;여러 빈 페이지&quot;와 &quot;에러페이지&quot;, &quot;로딩 UI&quot;</strong> 등을 함께 만들어 사용자 경험 개선을 이끌었다.</p>
<hr>
<h3 id="사전질문-관련-페이지-1">사전질문 관련 페이지</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/8602550a-fb3e-4f6b-9f20-51494f9ef692/image.png" alt=""></p>
<p>누구에 대한 사전질문을 보여줄지 말지를 고민했으나, 다른 곳은 작성까지 하나의 페이지를 추가로 거쳐 이동을 해야하지만, 여기는 참가자 카드에서 <strong><code>사전질문 작성</code></strong> 버튼을 클릭해 바로 이동하는 페이지라 따로 알려주지 않기로 했다.</p>
<p>그 외는 레벨로그 작성페이지와 동일하다!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/8e003cea-b527-4f69-9f67-4707ec035e7d/image.png" alt=""></p>
<p><strong>&quot;alert&quot;</strong>도 스낵바로 교체된 것을 확인할 수 있다!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/aac72b8c-a68b-430d-b4aa-c79361b5e871/image.png" alt=""></p>
<p>아주 흡족하진 않지만 모달도 피드백으로 받았던 양쪽의 &quot;padding&quot;을 제거하는 등의 변화를 겪었다. 이로써 모든 페이지의 개선을 살펴봤다. 대부분의 문제를 해결했음을 알 수 있다!</p>
<hr>
<h2 id="🧐-후기">🧐 후기</h2>
<p>사실 UI/UX를 개선한다는 마음으로 시작했지만, 지금와서 생각해보면 작업 프로세스를 개선하는 작업으로써도 매우 큰 역할을 해줬다는 생각이 들었다. </p>
<p>개선 작업 이전에는 보통 프론트 2명이 페어로 작업을 했고, 개인으로 작업을 하는 경우에는 *<em>“이걸 이렇게 하라는게 맞나?”, “엥 이게 아니라고?”, “이 케이스에 대한 고려는 피그마에 없는데?” *</em>하는 식으로 계속 커뮤니케이션을 해야하는 피로가 있었다.</p>
<p>생각해보면 개선 작업 이후에는 페어로 작업하는 일도 별로 없었고, 따로 작업하면서도 저런 식의** 무의미한 커뮤니케이션을 한 기억도 없다.**</p>
<p>잘한 일이라고 생각은 하지만 지금은 <a href="https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0">레벨로그 프로젝트 회고</a>에서 언급했던 것처럼 &quot;디자인 시스템&quot;을 도입했다면 어땠을까.. 하는 아쉬움이 있다.</p>
<p>여기까지는 가지 않더라도 좋은 UX를 위한 수많은 디자인 가이드를 활용했다면 어땠을까하는 아쉬움도 지울 수가 없는 것 같다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🎧 레벨로그 프로젝트 회고]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 23 Oct 2022 14:20:42 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/417ed582-db46-4448-a9ef-a0e107338dde/image.jpg" alt=""></p>
<h2 id="🏃-4개월-간의-달리기">🏃 4개월 간의 달리기</h2>
<p>레벨 3,4기간동안 우아한테크코스의 모의면접 레벨인터뷰를 편리하게 진행하기 위한 <strong>&quot;레벨로그&quot;</strong> 프로젝트 기간이 10/21을 마지막 끝이 났다. 사실 더 팀 프로젝트를 진행할 수는 있지만 대부분 크루들은 놓아주고 취업를 위한 준비를 하러 떠날 모양인 듯 싶었다.</p>
<p>나는 약간 욕심이 남아있어 며칠 정도는 더 투자할 듯 싶다. 레벨로그는 첫 공개 이후 우아한테크코스 크루의 거의 전체가 레벨 인터뷰에 사용한 서비스이기도 하고, 5기 때도 사용할 것 같다는 이야기를 굉장히 많이 들었기 때문에 그 기대를 실제로 만들고 싶은 욕심이다.</p>
<p>먼저 아래에서, 아쉬운 부분부터 이야기를 하고 싶다.</p>
<hr>
<h2 id="🤔-아-이랬다면-좋았을텐데">🤔 아.. 이랬다면 좋았을텐데</h2>
<p>지금 시점에서 가장 아쉬운 부분은 아래와 같다.
늘 <strong>&quot;사용자 경험 개선&quot;</strong> vs <strong>&quot;기능 추가&quot;</strong> 에서 열띤 토론을 하며 결국 한정된 리소스의 문제로 한 쪽을 (대부분의 경우 &quot;사용자 경험 개선&quot;이었다.) 어느 정도 포기했다는 것. </p>
<p>이제서야 깨닫게 된 것들이 있는데 남은 기간동안 이걸 도입한다면, 이 문제를 해결할 수 있을거라는 생각이 들었다. 결국 저 문제를 해결할 수 있는 방법은 하나뿐이라는 생각이 들었다.</p>
<p><strong>&quot;사용자 경험 개선&quot;에 드는 리소스를 줄인다!</strong></p>
<p>나는 여기에 대한 해결책을 <strong>&quot;디자인 시스템&quot;</strong> 에서 찾을 수 있을 것 같았다.</p>
<hr>
<h2 id="💀-기존의-방식">💀 기존의 방식</h2>
<p>레벨로그에서는 새로운 기능을 추가하고, 그에 맞는 화면을 만들기 위해서는 <strong>&quot;figma&quot;</strong>를 통해 아예 완전히 새로운 페이지를 그려야했다. </p>
<p>처음에는 나 혼자 figma를 완성해서 그나마 문제가 없었지만, 추가적인 기능인 &quot;인터뷰 질문 검색&quot;이 추가되며, 그에 필요한 화면을 시간이 남는 백엔드가 그리게 되었다.</p>
<p>새로운 화면은 다소 이질적이었으며, 기존의 컴포넌트를 재사용하기에도 애매해 대부분의 컴포넌트를 새로 작성하게 되었다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/8dee7cc9-1fd0-475d-a682-f25add86b36b/image.png" alt=""></p>
<p>이러한 프로세스는 총 세 가지 문제점을 가지고 있었다.</p>
<ol>
<li><p>사용자에게** 일관적인 경험을 제공하지 못할 수 있다**.</p>
</li>
<li><p>새로운 기능에 맞는 화면을 제작하는데 <strong>기존의 리소스를 재사용하기 힘들어 오랜 시간</strong>이 걸린다.</p>
</li>
<li><p>새로운 기능을 추가하는데 걸리는 시간이 당연히 길어지니, 또 위와 같은 토론(사용성 vs 기능 추가)을 하며 <strong>시간을 뺏기는 악순환</strong>에 들어가게된다.</p>
</li>
</ol>
<p>만약 제대로된 <strong>&quot;디자인 시스템&quot;</strong>이 존재했다면 어떻게 바뀌었을지를 아래에서 살펴보자!</p>
<hr>
<h2 id="🎨-디자인-시스템">🎨 디자인 시스템</h2>
<p>예시로 구글의 <strong>&quot;Material Design&quot;</strong>의 figma를 가져와 봤다.
여기에는 <code>Buttons</code> , <code>Icons</code> , <code>Lists</code> , <code>Menu</code> 등등 
<strong>구글의 디자인 원칙</strong>과 <strong>UX 전략</strong> 에 의해 결합된 <strong>UI Component</strong>들이 모여있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/61cbca0e-1926-4c1b-a9a8-2369010363be/image.png" alt=""></p>
<p>이 <strong>&quot;Material Design&quot;</strong>의 UI 컴포넌트들을 활용하면 개발자들은 이러한 컴포넌트를 다양한 조합으로 재사용하여 오랜 시간을 들이지 않고 단순히 레고 블록을 쌓듯이 화면을 그려낼 수 있다.</p>
<p>여기에는 <strong>UI Component</strong> 만 가지고 왔지만 디자인 시스템은 종종 스타일 가이드로 디자인 정보를 포함한다. UI Component 전체에서 사용되는 색상, 타이포그래피 및 스타일, 표준, 원칙 및 어떤 애니메이션이 어떻게 구현되고, 어디서 사용하는게 맞는지까지 아주 상세한 정보들을 포함할 수 있다.</p>
<p>이렇게 된다면 위에서 발견한 문제점 세 가지가 모두 해결될 것이라고 봤다.
새로운 화면을 그리는데 걸리는 리소스가 매우 작다면, 기능 추가를 하는 속도도 빨라질 것이며, UX를 개선하는데 쓰는 시간도 당연히 많아질 것이다. 
또한 일관된 UI Component를 재사용해서 화면을 그리게 되니, 일관성이 깨지는 문제도 많지 않을 것이다.</p>
<p>나는 그래서 남은 기간동안 레벨로그의 디자인 시스템을 만들기로 했다.
디자인 시스템을 통해서라면, 이전과는 비교도 할 수 없는 적은 시간만으로 새로운 기능을 개발하거나 프로젝트 유지보수를 할 수 있을 것이고, 곧 취업을 하게 되면 온전히 프로젝트에만 집중할 수 없는 우리에게 알맞다는 생각이 들었다. </p>
<p><del>아마 다음 주 동안 레벨로그에 맞는 디자인 시스템을 만들어 포스팅을 하지 않을까 싶다.</del></p>
<p><del><strong>아무래도 다음 주는 아주 재미있는 한 주가 될 것 같다!</strong></del></p>
<p>라고 했지만.. 팀원들과의 논의 끝에 도입하지 않기로 했다.. ㅠㅠ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔵 레벨로그의 타입스크립트 -1-]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%A0%84%EB%9E%B5</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EC%A0%84%EB%9E%B5</guid>
            <pubDate>Sun, 16 Oct 2022 12:47:45 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/689a0493-5bd4-4475-a271-c159af7e73e8/image.jpg" alt=""></p>
<p>저번에 말했던 “레벨로그의 타입스크립트”의 변천사를 다루어 볼텐데 오늘은 1편이다.</p>
<p>주제는 “타입스크립트 컴파일 전략” 이고, webpack과 함께 빌드 환경에 따라 타입스크립트를 어떻게 컴파일 하는지 살펴보도록 하자.</p>
<p>혹시 기존의 레벨로그의 타입스크립트 컴파일 전략을 모른다면 보고오는 것을 추천한다.</p>
<p>➡️ <a href="https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8">레벨로그의 타입스크립트</a></p>
<hr>
<h2 id="🚀-개발부터-배포까지의-빌드-상황">🚀 <strong>개발부터 배포까지의 빌드 상황</strong></h2>
<p>먼저 레벨로그에서 개발부터 배포까지 주로 마주치게 되는 빌드 상황을 나열해보았다.
local, dev, prod 환경에서 각각 자주 마주치는 빌드 상황에 맞게 컴파일 전략을 구성해볼 예정이다.</p>
<ul>
<li><strong>Dev Server Hot Module Replacement Time</strong>
  웹팩 개발 서버가 시작되고 난 후 HMR 기능이 동작하기 위해 리빌드되는 시간</li>
<li><strong>Build Time On Local Machine</strong>
   프로젝트 빌드 명령어를 통해 로컬 머신에서 웹팩 빌드 프로세스가 완료되는 시간</li>
<li><strong>Build Time On Github Actions CI</strong>
프로젝트 빌드 명령어를 통해 Github Actions CI 환경상에서 웹팩 빌드 프로세스가 완료되는 시간</li>
</ul>
<hr>
<h2 id="💻-local">💻 local</h2>
<p>로컬 빌드는 아무래도 아래의 두 개의 상황에서 자주 이루어진다.</p>
<ul>
<li><strong>Dev Server Hot Module Replacement Time</strong></li>
<li><strong>Build Time On Local Machine</strong></li>
</ul>
<p>기존 전략의 의도에 따르면 local에서는 타입체크를 포기하더라도 (VSCode의 ts-server에게 맡기긴한다.) 개발하는 동안 불편하지 않도록 빠른 빌드 + 타입 오류를 만나지 않음이 우선시 되었다. </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/c28f388c-4601-4c5d-87e4-98afa2d84c32/image.jpg" alt=""></p>
<p>위 그래프는 “<strong>Build Time On Local Machine”</strong> 에서의 비교인데, 대략 <code>esbuild-loader</code> 가 3배 정도 빠름을 알 수 있다. 조건이 맞지 않아 (타입체크를 포함함) 비교에는 포함되지 않았지만 <code>ts-loader + fork-ts-checker-webpack-plugin</code> 보다 <code>babal-loader</code> 는 조금 빠른 수준이었다.</p>
<p>추가로 “<strong>Dev Server Hot Module Replacement Time”</strong> 에서의 비교에서도 <code>react-refresh-webpack-plugin</code> 를 사용한 <code>babel-loader</code> 가 <code>esbuild-loader</code> 보다 느렸다. 그러다 보통 100ms도 안 되는 미비한 차이라 따로 그래프를 제작하지는 않았다.</p>
<p>그러다 초기 빌드를 자주 하지는 않고, <code>HMR</code> 상황에서는 크게 속도 차이가 나지 않는다. 
게다가 <code>babel-loader</code> 는 <code>react-refresh-webpack-plugin</code> 을 통해 코드 수정이 발생할 때 화면 깜빡임이 일어나는 없이 편안하게 작업할 수 있어 <strong><code>babel-loader</code></strong> 를 사용하기로 했다.</p>
<hr>
<h2 id="📡-dev-prod">📡 dev, prod</h2>
<p>아무래도 dev, prod는 개발 도중에 local Machine에서 빌드를 할 상황이 거의 없었던 것 같다.</p>
<ul>
<li><strong>Build Time On Github Actions CI</strong></li>
</ul>
<p>그래서 주로 <strong>Github Actions CI</strong> 에서의 상황을 살펴보는게 맞을 것이다.
그러나… 당장 여러번 PR을 보내는 것말고는 <strong>Github Actions CI</strong> 에서의 속도 차이를 비교하기는 힘들 것 같다.</p>
<p>그래서 어쩔수 없이 local Machine에서의 빌드로 대체했다.</p>
<p>dev, prod에서는 <code>ts-loader</code> 를 유지한 뒤, 타입 검사를 별도의 프로세스로 옮겨서 진행하게 해주는 <code>fork-ts-checker-webpack-plugin</code> 을 추가했다. 그리고 <code>esbuild-loader</code>에도 같은 플러그인을 적용해준 뒤 추가로 <code>ESBuildMinifyPlugin</code> 까지 적용해주었다. </p>
<blockquote>
<p>Webpack 빌드시 Esbuild의 속도를 통해 Transpilation과 <a href="https://www.cloudflare.com/ko-kr/learning/performance/why-minify-javascript-code/">Minification</a> 
단계에 대한 더 빠른 대안을 제공합니다.</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/807915fc-8344-4760-b449-e98165568803/image.png" alt=""></p>
<p>확실히 “<strong>Build Time On Local Machine”</strong> 에서의 비교만 봐도 기존에 비해 속도가 빨라진 걸 알 수 있다. dev 빌드는 따로 비교하지 않고 prod 빌드만 비교했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/aaa88465-5757-4bba-91f2-d6d9ed32bd7b/image.png" alt=""></p>
<p><strong>&quot;Github Actions CI&quot;</strong> 에서 빌드했을 때, dev빌드는 속도 차이가 거의 없었지만, prod빌드에서 속도가 유의미하게 줄어든 것을 볼 수 있다.</p>
<hr>
<h2 id="🧐-정리">🧐 정리</h2>
<p>여기까지 변화된 레벨로그의 타입스크립트 컴파일 전략을 살펴봤다. </p>
<p>결론은 아래와 같다</p>
<ul>
<li>local
   <strong><code>babel-loader</code></strong> (타입체크X + HMR 고려)</li>
<li>dev, prod
   <strong><code>esbuild-loader</code></strong> + <strong><code>fork-ts-checker-webpack-plugin</code></strong> + <strong><code>ESBuildMinifyPlugin</code></strong></li>
</ul>
<p>다음에는 상황을 보고 레벨로그의 타입스크립트 “코드 스타일” 편으로 돌아오겠다.</p>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li><a href="https://jeonghwan-kim.github.io/dev/2021/03/08/babel-typescript.html">babel-loader와 ts-loader의 빌드 결과가 다른 현상</a></li>
<li><a href="https://ui.toast.com/weekly-pick/ko_20181220">바벨과 타입스크립트의 아름다운 결혼</a></li>
<li><a href="https://fe-developers.kakaoent.com/2022/220707-webpack-esbuild-loader/">Webpack 빌드에 날개를 달아줄 Esbuild-Loader</a></li>
<li><a href="https://techblog.woowahan.com/6465/">제품 운영 잘하기 | 우아한형제들 기술블로그</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🥀 레벨로그의 리액트 쿼리 도입기]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EB%8F%84%EC%9E%85%EA%B8%B0</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%BF%BC%EB%A6%AC-%EB%8F%84%EC%9E%85%EA%B8%B0</guid>
            <pubDate>Sun, 09 Oct 2022 14:54:08 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/ae77860a-ca68-418f-9577-172dcc09fa2b/image.png" alt=""></p>
<h2 id="🤔-왜-리액트-쿼리">🤔 왜 리액트 쿼리?</h2>
<p>먼저 리액트 쿼리가 뭐하는 라이브러리인지를 가볍게 설명하자면, 데이터 Fetching, 캐싱, 동기화, 서버 쪽 데이터 업데이트 등을 쉽게 만들어 주는 라이브러리이다. 이전에 내가 다뤄봤던 Redux와는 다르게 클라이언트 데이터가 아니라 서버 데이터들을 관리하기 쉽게 도와준다. </p>
<p>고민 끝에 리액트 쿼리를 도입하게된 흐름을 아래에서 상세한 이유와 적용해보니 어땠는지 알아보도록 하자!</p>
<hr>
<h2 id="1️⃣-선언적인-코드-작성">1️⃣ 선언적인 코드 작성</h2>
<p>지난 5차 데모데이 때, 백엔드의 쿼리 개선으로 API Response의 변경이 있었다. 그래서 메인페이지에서 팀 상세 페이지로 들어갈 때, 팀 상세 정보를 새로 요청해야했기에 화면 깜빡임이 발생했다. </p>
<p>추가적으로 팀 필터링 기능이 추가되면서도 팀이 필터링이 되는 동안, 필터링 되기 전 팀 화면에서 가만히 기다리고 있어야하는 상태가 발생했다.</p>
<p>사용자가 느끼기에 답답한 점이 있을 것 같아 “스켈레톤 UI”를 활용하려고 마음을 먹었으나, 지금 코드는 꽤나 명령형이라 컴포넌트 렌더링 전에 분기문을 통해 처리를 해줘야했다.</p>
<pre><code class="language-jsx">const Components = () =&gt; {
    // ...

    if (isLoading) return &lt;Loading /&gt;
    if (isError) return &lt;Error /&gt;

    return (
        &lt;div&gt;data&lt;/div&gt;
    )
} </code></pre>
<p>간단히 작성해보면 이런식이다!</p>
<p>하지만 페이지에서 가져오는 상태가 많다면 <code>isLoading</code> , <code>isError</code> 값이 많아지고 일일히 분기 처리를 해주어야했다. 값이 늘어나면 누가 먼저 도착할 지 모르는 비동기의 경쟁 상태도 상당히 골치 아픈 문제가 되었다.</p>
<p>그렇다면 다른 방법으로 가져오는 상태를 하나로 묶어서 전체가 로딩되기 전까지 렌더링을 하지 않아야했다. 하지만 컴포넌트가 아닌 뒷면에서는 이를 처리하는 코드가 늘어나고, 무엇보다 위와 같은 분기처리가 컴포넌트에서 늘어나고 비즈니스 로직에서 에러 처리 코드가 반복되고 늘어나면서 <strong>컴포넌트와 함수가 하는 역할이 점점 가려지는 문제가 발생했다!</strong></p>
<p>이를 위해 리액트의 <code>Suspense</code> 와 <code>ErrorBoundary</code> 를 이용해 비동기로 가져오는 상태의 성공, 실패, 로딩에 관한 처리를 역할에 맞는 곳에 위임해 코드를 선언적으로 리팩터링하는 과정을 거치려했다.</p>
<hr>
<h2 id="2️⃣-waterfall식-상태관리의-문제점">2️⃣ “waterfall”식 상태관리의 문제점</h2>
<p>그러나, 레벨로그에서는 하위 컴포넌트를 멍청하게 만들기 위해 <code>Page</code> 단위의 최상위 컴포넌트에서만 데이터를 요청하고 하위 컴포넌트를 이를 받아서 화면을 그려주는 역할만 하도록 설계했다.</p>
<p><code>Suspense</code> 를 도입하려했지만, 이미 최상위 컴포넌트인 <code>Page</code> 에서 데이터를 모두 기다렸다가 화면을 그리기 때문에 하위 컴포넌트에서 <code>Suspense</code> 를 사용할 수가 없다.</p>
<p>이를 위해서는 상위 컴포넌트에서 모든 상태를 받아와 생겼던 의존성을 제거하고, 하위 컴포넌트가 자신이 필요한 상태값을 직접 요청하도록 하도록 해야했다.</p>
<p>즉 전체적인 상태관리의 방식이 변화해야했다. 이러한 문제점들을 리팩터링 과정에서 인지하고 도입할 수 있는 가장 적합한 라이브러리가 <strong>리액트 쿼리</strong>라는 결론이 내려졌다.</p>
<hr>
<h2 id="🥀-리액트-쿼리">🥀 리액트 쿼리</h2>
<p>아직까지 <strong><code>ErrorBoundary</code></strong> 는 도입을 하지 못했고, (아마 그대로 가져다 쓰기엔 좀 아쉬운 부분들이 조금 있어서 살짝 개조해서 쓰게 될 것 같다) <strong><code>Suspense</code></strong> 의 경우에는 대부분 도입했다. 생각보다 리액트 쿼리가 지원하는 기능들이 많아서 복잡했던 데이터 fetching과 커스텀 훅들이 간소화되는 효과를 배우 많이 봤다.</p>
<p>리액트 쿼리를 도입하면서 이전에 가지고 있었던 문제점들이 해결이 되었는지 페이지 컴포넌트 코드의 변화를 살펴보자! </p>
<hr>
<h3 id="리액트-쿼리-도입-이전">리액트 쿼리 도입 이전</h3>
<p>해당 사진의 코드는 <strong><code>InterviewTeams</code></strong> 페이지로, 전체 팀을 불러와 보여주는 페이지이다. 보다시피, 여러 문제점이 있었는데 하나씩 살펴보자.</p>
<ol>
<li><p><code>isActive</code> 는 status(전체 데이터 fetching이 성공했는지, 아직 response가 도착하지 않았는지, 실패했는지를 가지는 값이다.)를 통해 &quot;전체 팀&quot;의 상태를 알려주고, 이를 통해 컴포넌트에서는 알맞는 처리를 일일히 해주어야 했다.</p>
</li>
<li><p>전체 팀의 데이터와 어떤 유형의 팀(진행중, 종료, 나)을 불러오는지도 페이지 컴포넌트에서 처리를 완료해 아래로 내려주기 때문에 <code>Suspense</code> 나 <code>ErrorBoundary</code> 를 사용할 수 없다.  </p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/be35e79a-7fd4-4bf0-bc3e-ae7d3e3cfceb/image.png" alt=""></p>
<hr>
<h3 id="리액트-쿼리-도입-이후">리액트 쿼리 도입 이후</h3>
<p>이제는 위에 있던 문제점들을 어느정도 해결했음을 살펴볼 수 있다!</p>
<ol>
<li><p>더 이상 <code>isActive</code> 의 status 프로퍼티는 필요 없어졌다! 당연한 것이 해당 컴포넌트에서 전체 팀 데이터를 fetching을 하지 않고 하위 컴포넌트에서 역할을 넘겨 주었기 때문이다.</p>
</li>
<li><p>그렇기 때문에 Suspense를 사용해 하위 컴포넌트에서 필요한 상태를 아직 불러오지 못했다면 <code>&lt;Loading /&gt;</code> 컴포넌트를 보여주도록 깔끔하게 처리가 됐다! 이제 ErrorBoundary만 적용된다면 비즈니스 로직에 있는 복잡한 에러 처리 코드들도 걷어낼 수 있을 것이다.</p>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/d2099aac-fbda-4ec8-919a-d217dc18885f/image.png" alt=""></p>
<hr>
<h2 id="🧐-후기">🧐 후기</h2>
<p>아직 부족하거나, 모르는 부분들이 많아서 다음주에 마지막 데모 준비가 끝난다면 한 번 더 이 주제로 포스팅을 하고 싶다!  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔵 레벨로그의 타입스크립트 ]]></title>
            <link>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</link>
            <guid>https://velog.io/@thumb_hyeok/%EB%A0%88%EB%B2%A8%EB%A1%9C%EA%B7%B8%EC%9D%98-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8</guid>
            <pubDate>Sun, 02 Oct 2022 10:05:28 GMT</pubDate>
            <description><![CDATA[<h2 id="🚀-타입스크립트">🚀 타입스크립트</h2>
<p>얼마 전에, 프로젝트에서 사용하는 타입과 타입스크립트 코드의 이유와 기준에 대해서 정리하고 발표까지 하는 시간이 있었다. 이번 글은 그 때 정리된 레벨로그(나의 팀 프로젝트)의 타입스크립트 기준에 대해 적어보겠다.</p>
<p>이 글을 적은 이유는 곧 있을 6차 데모에서 타입스크립트의 여러 기준을 발표해야하기 때문에 지금과 그 때 새롭게 설정된 부분을 비교해보기 위함이다!</p>
<h2 id="🌝-component-typing-with-children">🌝 Component Typing With Children</h2>
<p>레벨로그에서는 <code>children</code> props를 <code>JSX.Element</code> 로 타입을 지정하고있다. 이렇게 지정한 이유는 <code>JSX.Element</code> , <code>ReactNode</code> , <code>ReactElement</code> 중에서 가장 좁은 타입이여서 사용했다.</p>
<ul>
<li><strong>ReactNode</strong>는 리액트가 렌더링할 수 있는 모든 것을 포함한다.</li>
<li><strong>ReactElement</strong> 및 <strong>JSX.Element</strong>는 모두 직접 JSX 트랜스파일링을 거친 결과이거나, React.createElement를 호출한 결과이다.</li>
</ul>
<p><strong>ReactElement</strong>는 <strong>type</strong>과 <strong>props</strong>가 있는 객체이고, <strong>JSX.Element</strong>는 type, props가 모두 any인 ReactElement이다. JSX에 대한 구현은 라이브러리 별로 세부적으로 다를 수 있다. 아래 사진을 통해 세 가지를 비교해보자.</p>
<h3 id="reactnode">ReactNode</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/3ca17610-a3fb-4255-b106-63381085bc73/image.png" alt=""></p>
<h3 id="reactelement">ReactElement</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/6e5d7733-3e78-4626-bc7f-55d5d880508d/image.png" alt=""></p>
<h3 id="jsxelement">JSX.Element</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/5a0a792a-74bf-4a0b-bd52-823b37ae3625/image.png" alt=""></p>
<hr>
<h2 id="🌚-component-typing-without-children">🌚 Component Typing Without Children</h2>
<h3 id="propswithchildren">PropsWithChildren</h3>
<p>위의 <code>Component Typing With Children</code> 에서 PropsWithChildren 타입을 활용하지 않았기 때문에, 여기도 <code>children</code> 만 props에서 제거된 동일한 형태의 타입을 각각의 컴포넌트의 매개변수 타입으로 <code>Interface</code> 를 통해 작성해 사용한다.</p>
<h3 id="type-alias-vs-interface">Type alias vs Interface</h3>
<p>모든 객체의 타입을 지정할 때 <code>Interface</code> 를 사용했고, 거의 모든 타입이 <code>Interface</code> 를 통해 작성되고 있지만, 가끔 함수나 변수의 타입을 지정할 때는 <code>Type</code> 별칭을 사용해 작성한다.</p>
<h2 id="event-type">Event Type</h2>
<p>레벨로그에서 사용되고 있는 이벤트 타입들은 아래와 같다.</p>
<pre><code class="language-tsx">React.SyntheticEvent&lt;EventTarget&gt;
React.FormEvent&lt;HTMLFormElement&gt;
React.MouseEvent&lt;HTMLElement&gt;
React.MouseEvent&lt;HTMLButtonElement&gt;
React.ChangeEvent&lt;HTMLInputElement&gt;
FocusEvent</code></pre>
<p>이러한 타입들은 일일이 찾아보고 작성하지는 않았고, <code>VSCode</code> 에서 제공하는 타입 설정에 도움을 받아 작성했습니다. 현재는 직접 이벤트 객체를 사용하는 핸들러에만 이벤트 타입을 달아줬다.</p>
<hr>
<h2 id="⚓-webpack-config-with-loader">⚓ Webpack Config with Loader</h2>
<p>빌드 환경에 따라서 <code>Webpack Config</code> 가 다르게 설정되어 있는데, 다른 요소들은 모두 배제하고 <code>Loader</code> 를 중심으로 차이점과 설정의 이유를 적어보겠다.</p>
<h3 id="local">local</h3>
<p><code>local</code> 빌드에서는 <code>babel-loader</code> 를 사용하였는데, 로컬에서는 변경점에 대한 화면변경이 빠른 것이 개발할 때 편하기 때문에 <code>ts-loader</code> 보다 빠른 <code>babel-loader</code> 를 사용했다. <code>babel-loader</code> 를 사용하면 typescript 사용이 불가하기에 <code>babel.config.js</code> 에서 추가 설정을 하였다.</p>
<p>하지만 지금은 설정에서는 바벨은 타입스크립트 코드를 제거하고 컴파일을 하기 때문에 타입 에러를 잡을 수가 없기 때문에 추가적인 <code>babel.config.js</code> 의 설정이 필요하다. </p>
<p>추가적으로 <code>ReactRefreshWebpackPlugin</code> 를 활용하여, 빌드 속도를 높였습니다.  </p>
<pre><code class="language-jsx">// webpack.js

module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: [
          {
            loader: &#39;babel-loader&#39;,
            options: {
              plugins: [require.resolve(&#39;react-refresh/babel&#39;)],
            },
          },
        ],
      },
    ],
  },

// babel.config.js

module.exports = {
  presets: [
    &#39;@babel/env&#39;,
    &#39;@babel/preset-typescript&#39;,
    [&#39;@babel/preset-react&#39;, { runtime: &#39;automatic&#39; }],
  ],
};</code></pre>
<h3 id="develop-production">develop, production</h3>
<p><code>develop</code> 과 <code>production</code> 두 가지 환경에서는 모두 조금 느리더라도 <code>ts-loader</code> 를 사용해 컴파일할 때 타입 검사를 진행하도록하여 더 안전한 빌드 파일을 서버에서 실행하도록 했다.</p>
<pre><code class="language-jsx">module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        use: &#39;ts-loader&#39;,
      },
    ],
  },</code></pre>
<hr>
<h2 id="🛡️-tsc--type-checker의-자세한-차이">🛡️ TSC &amp; Type Checker의 자세한 차이</h2>
<h3 id="tsc">TSC</h3>
<p><code>TSC</code> 는 <strong>TypeScript Compiler</strong> 이다. 큰 그림에서 보면, 두 가지 역할을 수행한다.</p>
<ul>
<li>최신 타입스크립트/자바스크립트를 브라우저에서 동작할 수 있도록 </li>
<li><em>구버전의 자바스크립트로 트랜스파일(transpile)*</em>한다.</li>
<li>코드의 <strong>타입 오류를 체크</strong>한다.</li>
</ul>
<p>여기서 놀라운 점은 이 두 가지가 서로 완벽히 독립적이라는 것이다. 타입스크립트로 작성한 코드를 자바스크립트로 컴파일할 때 작성한 타입스크립트 코드가 유효한 자바스크립트라면 타입오류가 있더라도, TSC는 코드를 컴파일에 성공한다.</p>
<p>타입스크립트가 자바스크립트로 변환될 때 코드 내의 타입에는 영향을 주지 않고, 또한 그 자바스크립트의 실행 시점에도 타입은 영향을 미치지 않는다. </p>
<p>타입스크립트의 오류는 C나 자바 같은 언어들의 경고(Warning)와 비슷하다. 문제가 될 만한 부분을 알려 주지만, 빌드를 멈추지는 않는다.</p>
<h3 id="type-checker">Type Checker</h3>
<p><code>TSC</code> 가 타입스크립트를 타입스크립트 AST로 변환한 다음, TSC의 <code>Type Checker</code> 가 AST를 확인해 타입을 확인해 프로그램이 개발자의 기대대로 실행될 수 있게 해준다. </p>
<p>위에서도 언급했지만 타입스크립트 코드가 자바스크립트 코드로 컴파일할 때는 개발자가 사용한 타입을 확인하지 않으며, 개발자가 기입한 타입 정보는 최종적으로 만들어지는 프로그램에 아무런 영향을 주지 않으며 단지 타입을 확인하는데만 쓰인다.</p>
<p>타입스크립트는 점진적 컴파일을 지원하는 정적 언어이기 때문에, 실제 개발자가 코딩을 시작하면 코드 편집기가 타입 관련 에러를 모두 검출한다.</p>
<hr>
<h2 id="🤔-의문">🤔 의문</h2>
<p>타입스크립트를 설치하게 되면 <code>tsc</code> 뿐만 아니라 <code>tsserver</code> 에서 제공하는 언어 서비스도 코드 편집기에서 사용하게 된다. 타입스크립트가 코드를 작성 중에 변수나 함수의 타입을 무엇으로 추론하는지 알 수 있는 좋은 도구인데, 이렇게 편집기에서 타입을 즉석으로 확인할 수 있다는 것은 <code>tsserver</code> 에도 <code>tsc</code> 에 있는 것과 동일한 <code>Type Checker</code> 가 있는 걸까? 아직 공부가 더 필요할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🗺️ JavaScript와 ECMAScript의 탄생]]></title>
            <link>https://velog.io/@thumb_hyeok/JavaScript%EC%99%80-ECMAScript%EC%9D%98-%ED%83%84%EC%83%9D</link>
            <guid>https://velog.io/@thumb_hyeok/JavaScript%EC%99%80-ECMAScript%EC%9D%98-%ED%83%84%EC%83%9D</guid>
            <pubDate>Sun, 18 Sep 2022 12:36:02 GMT</pubDate>
            <description><![CDATA[<h2 id="🤔-ecmascript란-무엇일까">🤔 ECMAScript란 무엇일까?</h2>
<p>자바스크립트 개발자라면 <strong><code>ECMAScript</code></strong>에 대해서는 모두 들어봤을 것이다. 자세히 알아보는 것은 아래의 목차에서 살펴보고, 이 곳에서는 <strong><code>Javascript</code></strong>와 <strong><code>ECMAScript</code></strong>의 차이만 가볍게 짚고 넘어가자.
 <strong><code>Javascript</code></strong>는 프로그래밍 언어의 이름이고, <strong><code>ECMAScript</code></strong>는 언어 명세에서 사용하는 이름으로, 자바스크립트를 표준화하기 위해 만들어졌다.</p>
<hr>
<h2 id="🍼-javascript와-ecmascript는-왜-탄생했을까">🍼 JavaScript와 ECMAScript는 왜 탄생했을까?</h2>
<h3 id="1️⃣-javascript의-탄생">1️⃣ JavaScript의 탄생</h3>
<p>이를 알기위해서는 <strong>1993년,</strong> 널리 인기를 얻는 첫번째 웹 브라우저의 NSCA의 Mosaic가 탄생한 시점으로 거슬러 올라가야한다. Mosaic의 개발자 중 한 명인 Marc Andreesen은 <strong><code>Mosaic Communications Corporation</code></strong> 을 공동 설립하고 <strong><code>Mosaic Netscape</code></strong> 라는 새로운 웹 브라우저를 만들었다.</p>
<p>그리고 이후에, NCSA와 관련된 법적 문제를 해결하기 위해 회사 이름은 <strong><code>Netscape Communications Corporation</code></strong> 으로, 브라우저는 <strong><code>Netscape Navigator</code></strong>  로 변경했다. </p>
<p><strong>Netscape</strong>는 웹이 더 동적이어야 함을 빨리 깨달았다. 당시에는 사용자가 단순히 폼에 정확한 값을 입력했는지 체크하기만 할 때도 서버에 데이터를 보내고 피드백을 받아야했다.</p>
<p>그래서 Netscape는 <strong>1995년</strong>  Brianden Eich를 고용하고, 이런저런 열띤 토론 끝에 Brianden Eich는 단 10일 만에, 자바와 문법이 비슷해보이는 스크립트 언어 시제품 <strong><code>Mocha</code></strong> 를 만들었다. 이후 <strong><code>Mocha</code></strong> 는 <strong><code>LiveScript</code></strong> 라는 이름을 거쳐 최종적으로 <strong><code>JavaScript</code></strong> 라는 이름으로 현재까지 사용되고 있다.</p>
<h3 id="2️⃣-ecmascript의-탄생">2️⃣ ECMAScript의 탄생</h3>
<p><strong>1996년 3월</strong>, Netscape는 <strong><code>Netscape Navigator 2.0</code></strong>을 출시하면서, JavaScript를 지원하기 시작했다. 웹 페이지의 동작을 향상시키는 언어로서 JavaScript의 성공은 마이크로소프트가 이와 적당히 호환되는 <strong><code>JScript</code></strong>를 개발하는 계기가 되었고, 이는 <strong>1996년 8월</strong>에 <strong><code>Internet Explorer 3.0</code></strong>에 포함되어 출시되었다. </p>
<p>마이크로소프트를 견제하려는 의도를 어느 정도 포함해서, Netscape는 표준 기관인 <strong><code>Ecma International</code></strong> 에 표준화를 주관해달라고 요청했고, <strong><code>ECMA-262</code></strong> 명세는 <strong>1996년 11월</strong>에 시작되어, <strong>1997년 6월</strong>, ECMA 일반 회의에서 ECMA-262의 초판이 채택됐다. </p>
<p>썬(지금의 오라클)에서 Java라는 이름으로 상표를 소유하고 있었으므로 표준 언어의 공식 이름을 JavaScript라고 부를 수는 없었으므로, JavaScript와 ECMA를 합쳐, <strong><code>ECMAScript</code></strong> 라는 이름을 붙였다.</p>
<p><strong><code>ECMAScript</code></strong> 는 ECMA-262에 의해 표준화된 언어의 이름이다. JavaScript와 JScript는 모두 <strong><code>ECMAScript</code></strong> 와의 호환을 목표로 하면서, ECMA 규격에 포함되지 않는 확장 기능을 제공한다. 오늘날 JavaScript는 ECMA-262을 만족하는 구현체를 가리킨다. </p>
<hr>
<h2 id="👨💻-ecmascript와-tc39">👨‍💻 ECMAScript와 TC39</h2>
<p><strong>ECMAScript</strong>는 <strong>Ecma International</strong>의 여러 기술 의원회(Technial Comittee, 이하 TC) 중 <strong><code>TC39</code></strong> 라는 의원회가 이 명세를 관리한다. 1997년 6월 <strong><code>ECMAScript 1</code></strong>이 등장한 이후로 2022년 6월까지 <strong><code>ECMAScript 13</code></strong> 까지 작성되었다. ECMAScript가 어떻게 관리되는지 궁금하지 않은가?
이번 장에서는 TC39와 ECMA-262 표준에 새로운 명세를 추가하기 위한 과정에 대해 알아보자.</p>
<h3 id="1️⃣-technical-comittee-39">1️⃣ Technical Comittee 39</h3>
<p><strong>TC39</strong>에는 마이크로소프트, 모질라, 구글 같은 회사가 들어갔고, 이 회사에서 일하던 사람들, 즉 브렌던 아이크, 앨런 워프스-브록(ECMA-262 편집자), 데이드 허먼 등이 위원회 작업에 참여했다. 
<strong>TC39</strong>는 ECMAScript의 디자인을 발전시키기 위해 <a href="https://esdiscuss.org">es-discuss</a>를 만들었고, 정기적으로 회합을 열었다. 이 회합에는 TC39 멤버와 초대된 전문가가 참여했고, 회의록은 웹상에 모두 공개된다.</p>
<h3 id="2️⃣-the-tc39-process">2️⃣ The TC39 Process</h3>
<p>ECMA-262 표준에 새로운 명세를 추가하기 위한 과정은 <a href="https://tc39.es/process-document">The TC39 Process</a>에서 확인할 수 있다. 그렇기 때문에 나는 여기에 있는 내용들을 아주 간략하게 요약하여 포스팅할 예정이다.
<strong><code>TC39 Process</code></strong> 는 누구나(라이센스 관련 조항에 동의하고 TC39의 컨트리뷰터로 등록한) <strong>제안</strong>을 등록할 수 있는 허수아비 단계인 0단계와 나머지 4단계까지 5단계로 나뉘어져있으며, 각 단계로의 승급을 위한 명시적인 조건들이 존재한다. 
해당 조건을 만족한 이후 위원회의 동의를 얻은 <strong>제안</strong>만이 다음 단계로 넘어간다.</p>
<p>그렇게 <strong>제안</strong>이 수많은 단계들을 거쳐 당해 3월 전까지 4단계를 달성하면, <strong><code>March TC39 Meeting</code></strong> 에서 4단계 제안들이 통합되고 최종 승인되면 새 사양 버전이 마스터에서 분기된다. </p>
<p>이 시점부터는 편집상의 변경만 허용되고, 이후 4~6월 동안 검토 기간을 가진 후에 7월에 ECMA 총회에서 새로운 표준을 승인해 <strong>제안</strong>이 <strong><code>ECMAScript</code></strong>의 새로운 버전에 추가된다.</p>
<hr>
<h2 id="🧐-정리">🧐 정리</h2>
<p>이렇게 <strong><code>JavaScript</code></strong> 와 <strong><code>ECMAScript</code></strong> 의 탄생과 의미, ECMAScript에 새로운 명세가 추가되는 과정까지 알아봤다. 글이 너무 무거워지는 것을 막기 위해서 간략히 적은 부분이 많으니 관심이 있는 사람이라면 아래에 적어놓은 참고자료를 통해 추가적으로 공부해보길 바란다!</p>
<hr>
<h2 id="📖-참고-자료">📖 참고 자료</h2>
<ul>
<li>자바스크립트를 말하다<ul>
<li>자바스크립트는 어떻게 만들어졌는가? (p97~)</li>
<li>ECMAScript 표준화 (p99~)</li>
</ul>
</li>
<li><a href="https://en.wikipedia.org/wiki/JavaScript#History">JavaScript</a></li>
<li><a href="https://en.wikipedia.org/wiki/Ecma_International">Ecma International</a></li>
<li><a href="https://en.wikipedia.org/wiki/ECMAScript">ECMAScript</a></li>
<li><a href="https://en.wikipedia.org/wiki/Browser_wars">Browser wars</a></li>
<li><a href="https://tc39.es/process-document/">The TC39 Process</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🚀 프론트엔드 성능 개선하기 -로딩-]]></title>
            <link>https://velog.io/@thumb_hyeok/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@thumb_hyeok/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%84%B1%EB%8A%A5-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 06 Sep 2022 10:10:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/c9933706-c440-4d88-9ddb-ccbaa681159a/image.png" alt=""></p>
<h2 id="🤔-성능-개선을-어떻게-할-수-있을까">🤔 성능 개선을 어떻게 할 수 있을까?</h2>
<p>내가 사용자라도 느려터진 웹사이트에는 다시 접속하고 싶지 않다. 만약 웹사이트가 사용자가 불편함을 느낄 정도로 퍼포먼스에 문제가 있다면 웹사이트의 성능개선이 필요할 것이다.
아래에서 어떻게 웹사이트의 성능개선을 할 수 있을지 알아보자.</p>
<hr>
<h2 id="🧾-측정-분석">🧾 측정, 분석</h2>
<p>먼저 웹사이트가 느리다면 왜 느린지 문제를 먼저 파악하고, 그 문제에 맞는 해결책을 찾아야하기때문에 <strong>&quot;측정&quot;</strong>을 하는 과정이 중요한다. chrome 브라우저의 <strong>Lighthouse</strong>나 <strong><a href="https://www.webpagetest.org/">WebPageTest</a></strong>를 통해 웹사이트의 속도가 느린 이유를 찾아볼 수 있다. 예시가 있으면 좋으니, Lighthouse를 통해 팀 프로젝트의 성능 분석 리포트를 만들어 보도록 하겠다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/cf7aa1c5-f872-4f3f-b9eb-9b4e12ff0125/image.png" alt=""></p>
<p>이런식으로 성능 분석 리포트를 통해 웹사이트의 속도가 느린 이유를 찾을 수 있고, 이를 토대로 어떻게 웹사이트의 성능을 개선할 수 있다. 내가 파악한 문제점들은 아래와 같았다.</p>
<ul>
<li><input disabled="" type="checkbox"> 요소 크기에 비해 너무 큰 이미지를 사용중이다.</li>
<li><input disabled="" type="checkbox"> 용량이 작은 차세대 파일 형식 (webp 등등)을 사용하지 않았다.</li>
<li><input disabled="" type="checkbox"> 번들파일의 크기가 너무 크다.</li>
<li><input disabled="" type="checkbox"> 사용하지 않는 코드가 번들파일에 포함되었다.</li>
<li><input disabled="" type="checkbox"> 캐시 정책이 효율적이지 않다.</li>
<li><input disabled="" type="checkbox"> 네트워크 페이로드가 너무 크다. (요청 수도 많고, 요청 크기도 크다)</li>
</ul>
<p>이러한 문제들을 어떻게 해결할 수 있는지 그리고 더 상세한 분석은, 이번 글 <strong>프론트엔드 성능 개선하기 (로딩)</strong>과 다음 글인 <strong>렌더링</strong>편으로 나누어서 살펴보도록 하겠다.</p>
<hr>
<h2 id="🖼️-이미지-크기-줄이기">🖼️ 이미지 크기 줄이기</h2>
<p>가장 먼저 손봐야할 부분은 <strong>&quot;이미지&quot;</strong>이다. 아래 사진은 웹사이트에 접속하면 가져오는 리소스인데, 이미지가 무러 <strong>6MB</strong>를 넘는 수준으로 압도적인 용량을 차지하고 있다. 파일형식의 변경과 압축이 사용해볼 수 있는 방법이다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/b977304c-853d-487a-826e-ec1a70cea7c9/image.png" alt=""></p>
<h3 id="format-compresstion">format, compresstion</h3>
<p>가장 먼저 <code>png</code>, <code>gif</code> 처럼 용량이 큰 파일형식을 <code>webp</code>,<code>mp4</code> 등으로 변환할 수 있다. <code>webpack</code> 에서 플러그인을 활용해서 번들 시에 여러 설정을 통해 용량을 줄이거나, 형식을 바꿔줄 수도 있고, 직접 <strong>sqoose</strong> 같은 여러 사이트를 이용할 수도 있다. 시도할 수 있는 방법이 정말 많고 하나의 정답보다는 상황에 맞게 하는게 맞는 것 같아 간단히 소개만 하겠다!</p>
<ul>
<li><p><a href="https://webpack.js.org/plugins/image-minimizer-webpack-plugin/#getting-started">ImageMinimizerWebpackPlugin</a></p>
</li>
<li><p><a href="https://www.npmjs.com/package/image-webpack-loader">image-webpack-loader</a></p>
</li>
<li><p><a href="https://squoosh.app">sqoosh</a></p>
</li>
</ul>
<hr>
<h2 id="📦-소스코드-크기-줄이기">📦 소스코드 크기 줄이기</h2>
<p>다음에 살펴볼 내용은 <strong>소스코드</strong>이다. 여기서 웹팩이 번들링한 <code>main.xxx.js</code> 파일의 사이즈가 무려 <strong>1.7MB</strong> 수준으로 이 번들파일이 응답이 완료되기 전까지 웹사이트는 흰 배경만 보일 뿐이다. 이는 사용자 경험에 당연히 좋지 않을 수 밖에 없으므로 번들링된 파일의 크기를 줄여보자!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/7cde8aa6-7f93-4c4b-9fb7-bd2c906a54d9/image.png" alt=""></p>
<h3 id="css-js-minify-uglify">CSS, JS minify, uglify</h3>
<p><strong>minify(압축)</strong>은 코드의 불필요한 줄바꿈, 공백 밑 들여쓰기, 짧게 쓸 수 있는 긴 구문 등등을 제거하거나 수정하여 소스 코드의 파일 용량을 줄일 수 있다. 
그<strong>uglify(난독화)</strong>는 코드를 읽기 힘들게 만드는 과정이다. 변수명, 함수명들이 짧아져 용량을 줄일 수 있으나, 난독화 수준이 높아질수록 코드를 해석하고 실행하는 시간이 오래걸릴 수 있다.</p>
<p>bundle.js 파일은 webpack5 기준으로 minify, uglify가 <code>TerserPlugin</code> 이 내장 되어  production 모드에서는 기본으로 최적화가 진행된다. webpack5로 프로젝트를 진행하고 있기에 이 과정은 생략했다. </p>
<p>또한 CSS-in-JS는 babel 트랜스파일링 과정에서 최적화되기 때문에 CSS-in-CSS인 경우에만 압축화 난독화를 적용해주면 된다. 프로젝트에서 <strong>styled-component</strong>를 사용하기 때문에 이 또한 생략했다.!</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/e79981c8-b830-4454-ba70-94bc401b5175/image.png" alt=""></p>
<p>압축을 진행하면, 왼쪽의 번들파일이 오른쪽과 같이 변한다. (난독화는 왼쪽도 진행된 상태이다.)
<img src="https://velog.velcdn.com/images/thumb_hyeok/post/26632d1c-172a-4cb5-b134-38e10f849e52/image.jpg" alt=""></p>
<p>오직 압축 하나만으로 이정도의 파일 용량을 줄일 수 있으니, 꼭 압축과 난독화를 적용해보자!</p>
<hr>
<h3 id="gzip">gzip</h3>
<p><strong><code>gzip</code></strong> 은 파일 압축에 쓰이는 소프트웨어이며, 텍스트 파일을 압축하는데에 뛰어난 성능을 가지고 있다. 대부분의 브라우저는 gzip으로 압축된 gz 확장자의 파일을 압축해제할 수 있는 기능, 압축프로그램을 이미 내장하고있다. 그렇다면, 우리는 번들파일을 gzip을 통해 압축해서 소스코드의 용량을 줄일 수 있다!</p>
<p>먼저 gzip으로 압축된 번들파일이 필요하다! <a href="https://webpack.js.org/plugins/compression-webpack-plugin">CompressionWebpackPlugin</a>을 통해 빌드할 때 번들파일을 압축한 gz 확장자의 파일을 생성할 수 있다.</p>
<pre><code class="language-javascript">// webpack.config.js

module.exports = {
  // ...
  plugins: [
    new CompressionPlugin({
      algorithm: &#39;gzip&#39;,
      exclude: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif|webp|txt|map|ico)$/i
    }),
  ],
  // ...
};</code></pre>
<p>아래와 같이 설정을 해주면, 제외된 파일 형식 이외의 번들파일들이 gz파일과 함께 생성된다. 아래 사진을 보면 gzip압축으로 번들파일의 용량을 대폭 줄인 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/13ea0b41-7fa5-44cd-9a44-ccd492c9dc22/image.png" alt=""></p>
<p>하지만 아직 끝난 것이 아니다. 서버와 브라우저간의 압축된 파일을 보내도 괜찮다는 설정을 해주어야 gzip파일을 브라우저에서 받아서 사용할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/74590e65-e218-4a2f-828f-24e71048e866/image.png" alt=""></p>
<ol>
<li><p><strong>브라우저</strong>가 압축된 콘텐츠를 수락한다는 <code>Accept-Encoding: gzip</code> 헤더를 추가한 요청을 서버에 보낸다.</p>
</li>
<li><p><strong>서버</strong>가 콘텐츠를 요청하는 브라우저의 요청을 받은 후 파일을 찾고, 압축된 파일이 존재할 경우, <code>Content-Encoding: gzip</code>를 헤더를 추가하여 응답을 보낸다.</p>
</li>
<li><p><strong>브라우저</strong>가 압축된 파일의 압축을 해체하고, 페이지를 그린다.</p>
</li>
</ol>
<p>위와 같은 과정을 통해 압축된 파일을 생성하고, 브라우저에서 사용할 수 있다!</p>
<hr>
<h2 id="🗃️-필요한-것만-요청하기">🗃️ 필요한 것만 요청하기</h2>
<p>이렇게 이미지의 크기도, 소스코드의 크기도 줄여봤지만, 지금 웹팩은 번들파일을 단 하나로 만들기 때문에 필요 없는 코드들이 번들파일에 들어가는 문제가 발생한다. 
이러한 이유로 A페이지에서 필요한 코드만으로 번들파일 하나, B페이지에서 필요한 코드만으로 번들파일 하나를 만들어서 각각을 필요할 때 요청하면 더 응답의 크기를 줄일 수 있을 것이다.
그리고 모듈의 일부만 사용함에도 모듈 전체를 번들파일에 포함하는 문제도 발생할 수 있다.
아래에서 이를 해결해보자!</p>
<h3 id="code-splitting">Code Splitting</h3>
<p><strong>Code Splitting</strong>이란 웹팩을 통해 번들링된 파일이 하나일 경우 페이지에서 사용하지 않는 모듈들까지 들어가 파일이 커질 수 있기 때문에, 번들링된 파일이 여러개가 될 수 있도록 나누는 것을 의미한다.</p>
<p>먼저 <a href="https://www.npmjs.com/package/webpack-bundle-analyzer">webpack-bundle-analyzer</a>를 통해 현재의 번들파일을 분석해보자. 
하나의 번들파일에 모든 파일이 들어가 있기 때문에 당장 해당 페이지에서 불필요한 라이브러리나 코드가 포함되어 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/24b836eb-3fa3-420a-a9ba-b8d079a80702/image.jpg" alt=""></p>
<p><a href="https://webpack.kr/guides/code-splitting/">Code Splitting</a> 해당 링크에서 보면 수많은 코드 스플리팅 방법이 존재함을 알 수 있다. 그러나, 해당 프로젝트는 react를 사용했기 때문에, react에서 제공하는 <strong><code>Suspense</code></strong>, <strong><code>lazy</code></strong>를 사용해서 코드 스플리팅을 적용했다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/214a67f4-a7a2-480a-b79f-cb7b1fac5aec/image.png" alt=""></p>
<pre><code class="language-javascript">// webpack.config.js

module.exports = {
  // ...
  output: {
    filename: &#39;[name].[contenthash].bundle.js&#39;,
    path: path.join(__dirname, &#39;/dist&#39;),
    clean: true
  },
  // ...
};</code></pre>
<p>위의 webpack 설정까지 완료하고 다시 webpack-bundle-analyzer를 통해 번들 파일을 확인해보면, 아래와 같이 각각의 라우팅에서 필요한 코드들이 분리되어 번들파일들을 생성했음을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/61ace18d-1699-468f-9a7b-e90813781b46/image.png" alt=""></p>
<hr>
<h3 id="tree-shaking">Tree Shaking</h3>
<p><strong>Tree Shaking</strong> 사용하지 않는 코드들을 제거해주는 것을 의미한다. <strong><code>ESM</code></strong> 의 <code>import</code>, <code>export</code> 문을 사용해야함으로, <code>babel</code> 이나 다른 플러그인들이 <strong><code>CommonJS</code></strong> 같은 다른 모듈로 코드를 변환하지 않도록 설정해주어야한다. </p>
<p>특정 라이브러리를 사용할 경우, 라이브러리에서 라이브러리 코드는 자동으로 Tree Shaking을 해주는 경우도 있다고 하니, 해당 라이브러리의 공식문서를 읽어보는 것이 좋다.</p>
<p>Tree Shaking을 위해서는 먼저 프로젝트에서 사용하는 babel이 모듈을 ESM으로 유지하도록 만들어줘야했다. </p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/2eab58c4-2030-4ca5-a7d4-164a0df9feda/image.png" alt=""></p>
<p>해당 설정이 끝나고는 프로젝트의 어떤 파일이 &quot;순수&quot;한지 나타내며, 만약 사용하지 않는다면 제거해도 괜찮은지를 알려주기 위해 package.json에 <strong>&quot;sideEffect&quot;</strong> 속성을 작성한다.
이를 통해 이곳에 포함된 코드는 사이드 이펙트를 포함함을 알리고, 이외의 파일들을 사용하지 않는 export는 제거해도 괜찮다는 것을 webpack에 알릴 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/a53f9a0b-4620-46d0-89da-a10f39f22060/image.png" alt=""></p>
<p>이를 통해 이제 페이지를 로드할 때, 필요없는 코드를 가져오는 일까지 사라진다!</p>
<hr>
<h2 id="♻️-같은-건-매번-새로-요청하지-않기">♻️ 같은 건 매번 새로 요청하지 않기</h2>
<p><strong>캐싱(caching)</strong>은 주어진 리소스의 복사본을 저장하고 있다가 요청 시에 그것을 제공하는 기술이다. 웹 캐시가 자신의 저장소 내에 요청된 리소스를 가지고 있다면, 요청을 가로채 원래의 서버로부터 리소스를 다시 다운로드하는 대신 리소스의 복사본을 반환한다. </p>
<p>매번 같은 페이지를 들락날락하게되는데, 그 때마다 계속 요청을 보내 새로운 파일을 받아오는 것보다는 캐시저장소에 리소스를 저장해두고, 서버에 요청을 보내지 않고 사본을 사용하는 것이 나을 것 같다.
그 전에, 캐시에 대해서 간단히 좀 알아보자.</p>
<p>캐시에는 몇 가지 종류가 있는데 크게 <code>사설(private)</code>, <code>공유(shared)</code> 캐시 두 가지로 분류된다. 
<strong>공유(shared)</strong> 캐시는 한 명 이상의 사용자가 재사용할 수 있도록 응답을 저장하는 캐시이고, <strong>사설(private)</strong> 캐시는 한 명의 사용자만 사용하는 캐시이다.</p>
<p>캐시는 <code>브라우저 캐시</code>, <code>프록시 캐시</code>, <code>게이트웨이 캐시</code> , <code>CDN</code> , <code>로드 밸런서</code> 등이 많지만, 이번에는 직접 사용해본 <strong>“사설 브라우저 캐시”</strong>와 <strong>“CDN 캐시”</strong> 이 둘을 중심으로 이야기 해보도록 하겠다.</p>
<h3 id="캐싱-제어">캐싱 제어</h3>
<p><code>Cache-control</code> HTTP/1.1 기본 헤더 필드를 사용해 클라이언트에 어떻게 캐싱할 지를 지정할 수 있다.</p>
<ul>
<li><p><code>Cache-Control: no-store</code>
캐시는 클라이언트 요청, 서버 응답에 관해 어떤 것도 저장해서는 안 된다. 요청은 서버 측으로 전송되고 전체 응답은 매번 다운로드된다.</p>
</li>
<li><p><code>Cache-Control: no-cache</code>
캐시된 복사본을 사용자에게 릴리즈 하기 전에, 유효성 확인을 위해 원 서버로 요청을 보낸다.</p>
</li>
<li><p><code>Cache-Control: private</code>
응답이 단일 사용자만을 위한 것이며 공유 캐시에 의해 저장되어서는 안된다는 것을 가리킨다. 사설 브라우저 캐시는 이런 경우에 응답을 저장할 수 있다. 즉, public 캐시인 CDN 등에서 응답을 캐시할 수 없다.</p>
</li>
<li><p><code>Cache-Control: public</code>
응답이 어떤 캐시에 의해서든 캐시되어도 좋다는 것을 가리킨다.</p>
</li>
<li><p><code>Cache-Control: max-age=31536000</code>
리소스가 유효하다고 판단되는 최대 시간을 말한다. <strong>Expires</strong>가 설정되어 있어도 그보다 우선하며, 변경되지 않을 파일에 대해 긴 시간을 캐싱할 수 있다.</p>
</li>
<li><p><code>Cache-Control: must-revalidate</code>
캐시는 오래된 리소스를 사용하기 전에 그 상태를 확인하고 만료된 리소스는 사용하지 말아야한다.</p>
</li>
</ul>
<h3 id="캐시-유효성">캐시 유효성</h3>
<ul>
<li><code>Cache-control: max-age=N</code></li>
<li><code>Expires</code></li>
<li>유효성 검사 휴리스틱<ul>
<li><code>Last-Modified</code></li>
</ul>
</li>
</ul>
<h3 id="캐시-검증">캐시 검증</h3>
<ul>
<li><code>Cache-control: must-revalidate</code></li>
<li><code>Advanced-&gt;Cache</code> 환경설정 패널 내에 캐시 검증 환경 설정</li>
<li>강한 검증<ul>
<li><code>ETags</code></li>
</ul>
</li>
<li>약한 검증<ul>
<li><code>Last-Modified</code></li>
</ul>
</li>
</ul>
<h3 id="캐시-무효화">캐시 무효화</h3>
<ul>
<li>CDN 캐시 무효화는 해당 플랫폼에서 제공</li>
<li>브라우저 캐시 무효화는 불가능<ul>
<li>캐시 버스팅(Cache Busting)<ul>
<li>content / chunk hash</li>
<li>version</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr>
<h2 id="🧐-정리와-개선결과-보기">🧐 정리와 개선결과 보기</h2>
<p>여러가지 방법을 통해 분석과정에서 나온 문제점들을 해결해보았다! 성능을 개선하는 방법은 이 방법들 이외에도 더 있으나, 다음 글인 렌더링편에서 다루거나, 혹은 다루지 않는 방법들이 있을 수 있다! 그런 방법들은 참고용으로 아래에 키워드만 가볍게 적어두도록 하겠다.</p>
<p><strong>이미지 크기 줄이기:</strong> srcset, web font, 
<strong>필요할 때 필요한 것 요청하기:</strong> HTTP/1.1 vs HTTP/2, preload, prefetch, preconnect, defer, async</p>
<p>레벨로그에서는 위의 방법의 일부와 더 필요한 다른 방법을 활용해 아래와 같은 개선결과를 얻었다.
성능 분석은 &quot;FAST 3G&quot; 환경에서 진행했다.</p>
<h3 id="네트워크-페이로드">네트워크 페이로드</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/914bf189-f96f-4de0-bf85-d7bc35e53beb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/eacca598-f017-43a8-9a88-60e2f30d8639/image.png" alt=""></p>
<hr>
<h3 id="lighthouse">Lighthouse</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/d816aee0-d5b7-43ea-b3e3-b673b482a655/image.png" alt=""></p>
<hr>
<h3 id="webpagetest">WebPageTest</h3>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/8cce4f81-9d14-40e2-ba84-b803fd6591eb/image.png" alt=""></p>
<hr>
<h2 id="📖-참고자료">📖 참고자료</h2>
<ul>
<li>Webpack Guides<ul>
<li><a href="https://webpack.js.org/plugins/image-minimizer-webpack-plugin/">ImageMinimizerWebpackPlugin</a></li>
<li><a href="https://webpack.kr/guides/code-splitting/">Code Splitting</a></li>
<li><a href="https://webpack.kr/guides/tree-shaking/">Tree Shaking</a></li>
</ul>
</li>
<li>React<ul>
<li><a href="https://ko.reactjs.org/docs/code-splitting.html">코드 분할</a></li>
</ul>
</li>
<li>MDN<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/HTTP/Caching">HTTP caching</a></li>
</ul>
</li>
<li>Better Explained<ul>
<li><a href="https://betterexplained.com/articles/how-to-optimize-your-site-with-gzip-compression/">How To Optimize Your Site With GZIP Compression</a></li>
<li><a href="https://betterexplained.com/articles/how-to-optimize-your-site-with-http-caching/">How To Optimize Your Site With HTTP Caching</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[💣 "git revert"가 되지 않았던 이유]]></title>
            <link>https://velog.io/@thumb_hyeok/git-revert%EA%B0%80-%EB%90%98%EC%A7%80-%EC%95%8A%EC%95%98%EB%8D%98-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@thumb_hyeok/git-revert%EA%B0%80-%EB%90%98%EC%A7%80-%EC%95%8A%EC%95%98%EB%8D%98-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Wed, 10 Aug 2022 16:54:14 GMT</pubDate>
            <description><![CDATA[<h2 id="🤬-문제-상황">🤬 문제 상황</h2>
<p>오늘 팀 프로젝트를 진행하다가 PR을 보내려고 내가 작업하는동안 변경된 사항들을 반영하기 위해 develop 브랜치에서 <code>pull</code>을 받아와서 작업하던 브랜치에 <code>merge</code>를 하고 있던 중에 무슨 실수를 한 것인지 먼 과거의 커밋 하나가 마지막 머지 커밋으로 붙어 버렸다.
<img src="https://velog.velcdn.com/images/thumb_hyeok/post/bc0ed188-e571-4585-9dff-fe770e27dd31/image.jpg" alt=""></p>
<p>코드를 보니 프론트 변동사항은 모두 반영이 되어있었지만, 백엔드 코드는 이틀 전 API 명세 변경 이전 코드임을 확인하고 <code>git reset</code> 이나 <code>git revert</code> 를 사용하고자 했다.  아무래도 팀 프로젝트이기도 하고, 마지막 커밋에 중요한 사항들이 많아 안전한 방향으로 가기 위해 <code>git revert</code> 를 써야겠다는 생각이 들었다. </p>
<hr>
<h2 id="📌-git-revert">📌 git revert</h2>
<p>VSCode를 켜고 되돌릴 커밋번호를 복사해 당당하게 아래처럼 입력했지만!</p>
<pre><code>git revert d658020

error: d658020 is a merge but no -m option was given. 
fatal: revert failed</code></pre><p>에러가 발생했다.
위와 같은 에러가 발생하는 이유는 병합 커밋을 되돌릴 대상을 정확히 지정을 해주지 않아서였다!</p>
<hr>
<h2 id="💡-해결법">💡 해결법</h2>
<pre><code>git cat-file -p d658020</code></pre><p>리포지토리 개체에 대한 콘텐츠 또는 유형 및 크기 정보 제공을 해주는 <code>cat-file</code> 명령어를 통해 해당 병합 커밋에 대한 정보를 살펴보면 아래와 같이 두 개의 <strong>parent</strong>가 존재하는 것을 확인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/thumb_hyeok/post/7aee4fa5-0199-43b2-8bf5-9dba8727b3b7/image.png" alt=""></p>
<p>부모의 커밋번호를 보니 첫번째 부모가 내가 되돌리려고 한 위치의 커밋임을 확인할 수 있었다.</p>
<pre><code>git revert d658020 -m 1</code></pre><p>이를 통해 다시 커밋을 되돌려 과거로 돌아갈 수 있었다!
<img src="https://velog.velcdn.com/images/thumb_hyeok/post/fe2beaf0-7217-4e87-92fe-2feb372381a2/image.jpg" alt=""></p>
<hr>
<h2 id="🔖-참고-사항">🔖 참고 사항</h2>
<ul>
<li><a href="https://git-scm.com/docs/git-revert">Git - git-revert Documentation</a></li>
<li><a href="https://git-scm.com/docs/git-cat-file">Git - git-cat-file</a>  </li>
</ul>
]]></description>
        </item>
    </channel>
</rss>