<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>FE.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 30 Dec 2024 10:46:54 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>FE.log</title>
            <url>https://images.velog.io/images/seeh_h/profile/6b7bfde5-b67c-4665-a2e1-a308e8de2059/tt.PNG</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. FE.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/seeh_h" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[2024년 회고]]></title>
            <link>https://velog.io/@seeh_h/2024%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@seeh_h/2024%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 30 Dec 2024 10:46:54 GMT</pubDate>
            <description><![CDATA[<p>프론트에드 엔지니어로 업무를 시작한지 벌써 만 3년이 되었네요. 올해도 나름 많은 일이 있었는데, 한 해를 마무리하며 가볍게 돌아보려 합니다.</p>
<h3 id="배움">배움</h3>
<p>메스프레소에서의 1년은 참 특별했습니다. 메스프레소는 콴다라는 글로벌 교육 서비스를 운영하는 스타트업인데요. 글로벌 환경에서 서비스를 운영하며 다양한 경험을 많이 해볼 수 있었습니다.</p>
<p>가장 큰 수확이라고 생각하는 부분은 불안정한 환경에서 안정적인 서비스를 운영하기 위해 다양한 고민과 시도를 해본 점 입니다. 글로벌 환경의 네트워크 품질은 대체로 열악하고 디바이스 사양 역시 좋지 않은 경우가 많습니다. 엔지니어로서 성능 개선과 최적화는 항상 중요한 요소라고 생각했지만 실제로 필요성을 인지할 기회는 많지 않았는데, 동남아 유저들의 웹 바이탈 지표를 보고 개선해 나가며 이론적으로만 알았던 내용들을 다양하게 시도하고 적용해 볼 수 있었습니다. </p>
<p>제품적으로도 좋은 경험을 많이 해볼 수 있었던 것 같습니다. 국가 별 소득 수준을 고려한 구독 비용 산정이나, 쿼터 산정 등 비즈니스적으로 고민 해야할 부분이 많았습니다. 신규 피쳐들의 국가간의 지표가 크게 차이나는 것을 보며 글로벌 니즈에 부합하는 서비스를 만들기는 참 쉽지 않다는 것을 느끼기도 했습니다. 여러가지 가설을 세우고, 실험하고, 결론을 내리는 과정에 직간접적으로 참여하며 제품과 비즈니스적인 사고에 대해 많이 배울 수 있었습니다.</p>
<p>동료들로부터 배운 부분도 많습니다. 문제를 정의하고 필요한 부분은 과감히 도움을 요청하며 일을 해결해 나가는 모습을 보면서 일 하는 방식에 대해서도 다시금 고민하고 배워 나갈 수 있었습니다. 비슷한 맥락으로 테스트 코드나, 클린 코드에 대한 고민, 모노레포 활용 방식, 인프라 구성, 실험 플랫폼 등 동료들이 과거에 고민해 온 흔적들을 쫒아가는 과정에서도 많은 영감을 얻을 수 있었습니다.</p>
<h3 id="생산성">생산성</h3>
<p>조직 개편으로 팀 규모가 축소되며 소수의 인원이 많은 프로젝트를 관리해야 하는 환경에서, 요구사항에 빠르게 대응하기 위해 팀 차원의 생산성을 높이려 많은 시도를 한 해이기도 합니다.</p>
<p>특히 모노레포에서 팀 차원의 개발 생산성을 올리기 위해 시도를 했던 것 같습니다. 기존의 모노레포는 사실상 멀티 레포로 운영 되고 있었는데요. 모노레포로 마이그레이션은 진행했지만 이후 조직 개편 과정에서 담당하는 팀들이 크게 변경되면서 이후의 작업들이 진행되지 못했습니다. </p>
<p>API 콜, Util 함수, 로깅, 실험, 디자인 시스템 컴포넌트 등 대부분의 프로젝트에서 사용되는 요소들을 공통화하여 재사용 할 수 있도록 구조를 개선했습니다. 이 외에도 개발 경험을 개선하기 위해 태그 방식의 배포를 만들어주거나, 피그마로부터 아이콘을 땡겨오는 작업 같이 수고스럽지는 않지만 귀찮은 작업을 자동화 했습니다. </p>
<p>나름 이런 저런 일들을 많이 진행 했는데, 아직도 백로그에 쌓인 작업이 산더미처럼 남아있는건 좀 신기한 부분이네요 ^ㅁ^</p>
<h3 id="ai">AI</h3>
<p>올해는 정말 다양한 AI 제품들이 나왔습니다. GPT외에 Claude, Cursor, Windsurf등 새로운 게 나올때마다 점점 더 좋아진 성능에 감탄하면서도 좀 무섭기도 했습니다. &quot;이 정도면 나보다 잘하는데?&quot;라는 생각이 꽤 자주 들었거든요. </p>
<p>그래서인지 요즘은 ‘대체 불가능한 개발자’라는 주제에 대한 고민을 종종 하고 있습니다. AI의 발전과 함께 이 개념 또한 크게 변화하고 있다고 느낍니다. 기존에는 개발자의 다양한 경험이 개발자로서 가질 수 있는 큰 재산이라고 생각했는데, AI를 통해 정보를 빠르게 얻을 수 있게 된 지금은 더 이상 개인의 경험이 큰 차별화 요소가 아니게 된 것 같기도 합니다.</p>
<p>오히려 프론트엔드부터 인프라까지 애플리케이션의 전체적인 구조와 동작 원리를 폭 넓게 이해하는게 더 중요해졌다고 느낍니다. 전체적인 그림을 그리고 구현은 AI한테 시킬 수 있게 되었으니까요. 워낙 빠르게 바뀌고 있는 요즘이라 이 생각도 언제 바뀔지는 모르겠습니다.</p>
<h3 id="hola">Hola</h3>
<p>어느덧 3년째 운영중인 <a href="https://holaworld.io">Hola</a>는 올해도 나름 변화가 많았는데요. </p>
<p>상반기에는 IT 관련 행사를 모아 볼 수 있는 Hola-It를 출시했고, 회원가입 플로우, 그리고 모바일 뷰를 일부 개선했습니다. 아, 알림과 글 등록 플로우도 좀 개선했네요.</p>
<p>하반기에는 기술 부채를 상환하는 시간을 보냈습니다. Hola는 제가 React를 공부하며 만든 사이드 프로젝트인데요. 그러다보니 deprecated되는 라이브러리들도 꽤 있었고, 코드베이스도 불필요하게 서로 의존적인 부분이 많아 점점 더 작업하기가 힘들어지고 있었습니다.</p>
<p>올해는 어드민도 만들고, dev 환경도 만들고, 타입스크립트도 적용하고, 패키지 버전도 올리고, 코드도 뜯어고치고, 테스트 코드도 넣고, CI/CD 환경을 개선하고..달리는 수레의 바퀴를 고치는 업무를 주로 했습니다. </p>
<p>사실 생각보다 시간이 너무 오래 걸려서 중간에 한번(<del>여러번</del>) 포기할 뻔 했지만, 나름 애정을 가지고 있는 서비스다보니 어찌저찌 잘 끝낼 수 있었습니다. </p>
<p>내년에도 준비중인 많은 기능들이 있으니 많은 관심 부탁드립니다ㅎㅎ</p>
<h3 id="내년에는">내년에는?</h3>
<p>지금까지 웹 전반에 대한 많이 공부를 해왔습니다. 지식들이 작은 여러개의 조각들로 파편화되어 머리속에 존재 했었는데, 올해는 그 개념들이 연결되고 합쳐지는 느낌을 종종 받았습니다. 개발자로 일한 기간 중 기술적으로 가장 많이 성장한 한 해 였다고 느낍니다.</p>
<p>내년에는 조금 더 다양한 관점에서 사고해볼 수 있는 시야를 갖는것이 목표입니다. 
두 가지를 시도해보고 싶은데요. 리액트로만 대부분의 프로젝트를 개발하다보니, 리액트 외부의 세계에 대해서는 많이 무지하단것을 느낍니다. 다른 관점에서 디자인된 프레임워크를 써보면 시야를 좀 더 넓힐 수 있지 않을까 싶습니다. remix나 astro 같은 친구들에 좀 흥미가 있고, 요즘 좀 주춤하는 것 같지만 svelte도 한번 써봐야겠다 싶구요. 기회가 된다면 모바일이나 서버쪽도 다뤄보고 싶습니다.</p>
<p>비슷한 맥락에서 오픈소스를 많이 읽어보는 것도 목표입니다. 사실 잘 만들어지고 오래 운영되는 오픈 소스만큼 좋은 레퍼런스도 없다고 생각하는 편입니다(<del>생각만 2년째 하는 중</del>). 설계를 좀 뜯어보고 운영 중 마주친 주요 이슈들을 살펴보려 합니다.</p>
<p>별거 아닌 회고인데 여기까지 읽어주신 모든 분들 감사합니다. 내년에도 행복한 일만 가득한 한해가 되시길 기원합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React Server Component & Streaming SSR에 대한 고찰]]></title>
            <link>https://velog.io/@seeh_h/React-Server-Component-Streaming-SSR%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@seeh_h/React-Server-Component-Streaming-SSR%EC%97%90-%EB%8C%80%ED%95%9C-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Tue, 10 Sep 2024 02:16:31 GMT</pubDate>
            <description><![CDATA[<p>React 18 버전의 릴리즈는 기존의 웹 개발 패러다임에 다시 한번 큰 전환점을 제시했습니다. Concurrent Feature, Batching update, React Server Component(이후 RSC), 그리고 Streaming SSR 등 새로운 기능들이 많이 도입되었습니다.</p>
<p>이 중에서도 RSC와 Streaming SSR은 기존의 서버 사이드 렌더링을 획기적으로 개선했는데요.</p>
<p>오늘은 이 두 가지 기능에 초점을 맞추어, 기존 SSR 모델의 한계점과 새로운 기능들이 이 문제를 어떻게 해결하는지 살펴보겠습니다.</p>
<h3 id="기존의-ssr">기존의 SSR</h3>
<p>우선 기존 서버 사이드 렌더링(이후 SSR)의 렌더링 방식에 대해 살펴봅시다.</p>
<p>유저가 페이지를 요청하면 서버는 초기 렌더링에 필요한 HTML 파일을 만들어 응답하는 간단한 요청-응답 모델을 가지고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/597150c3-9e57-49f1-becc-80b2b1e702c6/image.png" alt=""></p>
<p>이미지 출처 : <a href="https://medium.com/geekculture/server-side-rendering-simplified-fd708d5520ba">https://medium.com/geekculture/server-side-rendering-simplified-fd708d5520ba</a></p>
<p>이 과정을 좀 더 상세히 살펴보면 아래와 같습니다.</p>
<ol>
<li>사용자가 페이지 진입 시 서버에 HTML 파일을 요청합니다.</li>
<li>서버에서는 데이터 패칭 등 필요한 작업들을 수행한 후 초기 HTML을 완성합니다.</li>
<li>완성된 HTML을 브라우저에 전송합니다.</li>
<li>브라우저는 받은 HTML을 렌더링합니다.</li>
<li>이후 리액트 컴포넌트를 실행하여 페이지를 상호작용 가능한 상태로 만듭니다.</li>
</ol>
<h3 id="기존-ssr-모델의-한계">기존 SSR 모델의 한계</h3>
<p>이러한 SSR 모델은 어떤 문제점을 가지고 있었을까요?</p>
<p>첫번째로는 <strong>초기 로딩 시간이 길며, HTML이 반환되기 전에는 유저가 아무것도 볼 수 없다는 점입니다.</strong> 전체 페이지에 필요한 데이터를 한번에 내려받고, 완성된 HTML을 한번에 로드하기 때문입니다. 서버에서 HTML을 완성하는 데 걸리는 시간은 페이지의 복잡도에 비례해서 길어집니다. </p>
<p>두번째로는, <strong>전체 렌더 트리에 대해 하이드레이션</strong>을 <strong>한번에</strong> 진행해야 한다는 점입니다. 초기 HTML을 받아서 화면을 그려낸다 하더라도, 모든 하이드레이션 과정이 종료되기 전에는 유저는 페이지를 정상적으로 사용할 수 없습니다. </p>
<p>리액트 코어 개발자인 Dan이 작성한 <a href="https://github.com/reactwg/react-18/discussions/37">New Suspense SSR Architecture in React 18</a>에도 이러한 SSR 모델의 문제점을 잘 설명하고 있습니다.</p>
<p>해당 글에서 Dan은 기존 SSR의 문제점을 아래 세가지로 요약합니다.</p>
<blockquote>
<ol>
<li>모든 것을 가져와야 무언가를 보여줄 수 있다. (You have to fetch everything before you can show anything)</li>
<li>모든 것을 로드해야 무언가를 하이드레이션할 수 있다. (You have to load everything before you can hydrate anything)</li>
<li>모든 것을 하이드레이션해야 무언가와 상호작용할 수 있다. (You have to hydrate everything before you can interact with anything)</li>
</ol>
</blockquote>
<p>세가지 문제점을 살펴보면 공통점이 있습니다. 바로 이전 단계가 모두 완료되기 전까지 다음 단계는 진행할 수 없다는 점인데요. </p>
<p>유저가 웹사이트를 정상적으로 이용하기 위해 데이터 패칭(서버) → HTML 생성(서버) → HTML 로드(클라이언트) → 하이드레이션(클라이언트)의 단계를 거쳐야 하는데, 각 단계는 이전 단계가 완료 된 후에 순차적으로 진행되어야 합니다. </p>
<p>즉, <strong>기존의 SSR 모델은 waterfall을 가지고 있습니다.</strong> 리액트 팀은 기존 모델의 waterfall을 개선하고자 Streaming SSR과 Selective Hydration을 해결책으로 내놓습니다.</p>
<h3 id="streaming--selective-hydration">Streaming &amp; Selective Hydration</h3>
<p>기존의 SSR은 &quot;All or Nothing&quot; 방식이라고도 할 수 있습니다. 각 단계가 진행이 완료되거나, 안되거나 둘 중 하나이기 때문입니다. 유저 입장에서 중간 단계는 없습니다.</p>
<p>만약 각 과정이 나누어져서 진행될 수 있다면 어떨까요? 완성된 부분의 HTML을 먼저 수신하고, 도착한 부분부터 클라이언트에서 하이드레이션을 진행할 수 있다면요? 유저는 페이지를 더 빨리 보고, 더 빨리 사용할 수 있게 됩니다.</p>
<p>이게 Streaming SSR &amp; Selective Hydration의 컨셉 입니다. 전 단계를 모두 완료한 후 다음 단계를 진행하는게 아니라, 각 단계를 쪼개서 진행하고 먼저 완료된 부분만 우선해서 보여주는 것이죠. </p>
<p>AS-IS와 TO-BE 모델을 그림으로 한번 살펴보며 비교해보겠습니다.</p>
<h4 id="as-is">AS-IS</h4>
<p>먼저, 기존 방식입니다. 아래와 같이 전체 HTML을 서버로부터 수신합니다. 이 단계가 완료되면 유저는 초기 화면을 볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/f84e21a6-8a74-4116-9a04-319eef818422/image.png" alt=""></p>
<p>이후 전체 컴포넌트에 대해 하이드레이션을 진행합니다. 이 단계까지 완료되면 유저는 웹 사이트를 정상적으로 사용 가능합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/3aac6fd9-c63a-409c-a1dc-fcf17250ca90/image.png" alt=""></p>
<h4 id="to-be">TO-BE</h4>
<p>변경된 방식입니다. 서버는 완료된 HTML의 일부를 먼저 클라이언트로 내려줍니다. 유저는 전체 HTML 생성이 완료되지 않아도 화면을 볼 수 있습니다.
<img src="https://velog.velcdn.com/images/seeh_h/post/87703f40-53c5-4fa7-88a9-22125efb8846/image.png" alt=""></p>
<p>하이드레이션 역시 받은 부분부터 먼저 진행해줍니다. 화면의 특정 부분이 아직 보이지 않더라도, 유저는 페이지의 보이는 부분에선 상호작용을 할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/43069cfe-2895-4abd-a26c-4d24041bee07/image.png" alt=""></p>
<p>그림으로 보니 차이가 더 느껴지시나요? 이렇게 리액트는 한 단계 더 진화한 SSR 모델을 장착하게 되었습니다.</p>
<p>Streaming은 Suspense 경계로 동작하기 때문에 적용 또한 간단합니다. 아래와 같이 코드를 작성하면, Comments는 컴포넌트는 Streaming의 경계가 되고 Streaming 이전엔 Fallback UI를 노출합니다.</p>
<pre><code class="language-jsx">&lt;Layout&gt;
  &lt;NavBar /&gt;
  &lt;Sidebar /&gt;
  &lt;RightPane&gt;
    &lt;Post /&gt;
    &lt;Suspense fallback={&lt;Spinner /&gt;}&gt;
      &lt;Comments /&gt;
    &lt;/Suspense&gt;
  &lt;/RightPane&gt;
&lt;/Layout&gt;</code></pre>
<h3 id="rsc">RSC</h3>
<p>다음은 RSC입니다. RSC는 또 무엇이며, 왜 나왔을까요? 앞서 Streaming SSR로도 해결하지 못하는 문제가 있었던 걸까요? RSC에 대한 <a href="https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md">RFC</a> 문서를 보면, 해당 질문에 대한 답을 얻을 수 있습니다.</p>
<p>문제의 핵심은 <strong>리액트는 여전히 클라이언트를 중심으로 동작하며, 서버를 충분히 활용하지 못하고 있다는 점</strong>입니다.</p>
<p>Streaming 방식을 사용한다고 해도 결국 서버는 초기 HTML을 만드는 과정에만 관여합니다. 초기 렌더링 과정 이후 라우팅을 비롯한 모든 작업은 클라이언트에서 사이드에서 진행되기 때문에 서버 자원을 최대로 활용한다고 보기 어렵습니다. </p>
<p>그래서 리액트 팀은 리액트를 서버를 조금 더 활용하는 방향으로 발전시키고자 했는데요. 그렇게 해서 등장하게 된 것이 바로 RSC 입니다. </p>
<p>RSC는 말 그대로 서버 환경에서 실행되는 컴포넌트를 의미합니다. 조금 더 강조하자면 <strong>서버에서&#39;만&#39;</strong> 실행되는 컴포넌트입니다. 클라이언트 사이드에서는 실행되지 않기 때문에 컴포넌트 내부에서 서버 자원에 접근하는 등 서버의 이점을 더 누릴 수 있습니다.</p>
<h4 id="new-mental-model">New Mental Model</h4>
<p>서버 컴포넌트는 기존에는 없었던 개념이기 때문에 이해를 위해 약간의 배경 지식이 필요한데요. 이번에도 Dan이 작성한 <a href="https://github.com/reactwg/server-components/discussions/4">&quot;Why do Client Components get SSR&#39;d to HTML?&quot;</a> 글의 일부를 살펴보며 서버 컴포넌트 동작 방식에 대해 더 자세히 살펴보도록 하겠습니다.</p>
<p>해당 글은 &quot;왜 클라이언트 컴포넌트도 SSR이 되나요?&quot;라는 질문에 대한 답변을 담은 글이지만, 새로운 리액트의 멘탈 모델에 대한 이해도를 높이기에도 아주 좋은 예제입니다.</p>
<p>우선, 기존의 리액트의 멘탈 모델에서부터 출발해 보겠습니다. 서버 컴포넌트가 없고 클라이언트 컴포넌트들만 존재하던 시절입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/1464766a-0a07-450e-8f22-43a828e8aec3/image.png" alt=""></p>
<p>이제, 여기에 서버 컴포넌트로 구성된 서버 트리가 추가됩니다. 서버 트리는 서버 컴포넌트로 구성됩니다. 이 트리는 리액트 트리보다 먼저 실행되며 실행한 결과값을 리액트 트리로 넘겨줍니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/dbb5b882-3d4d-41a4-9087-f3c73510474c/image.png" alt=""></p>
<p>서버 트리는 클라이언트 사이드에서 실행되지 않기 때문에 파일시스템이나 데이터베이스 등 각종 서버 자원에 자유롭게 접근할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/fb4c25ee-3fce-4ad2-ad36-632fcfaa06ea/image.png" alt=""></p>
<p>마지막으로 기존 리액트 트리에 서버 트리와 구분짓기 위해 클라이언트 트리라는 이름을 붙여줍니다. 클라이언트 트리라는 새로운 이름만 부여받았을 뿐 기존 리액트 모델에는 변화가 없습니다.</p>
<p>정리하면 기존의 리액트 모델은 유지되고, 새롭게 활용할 수 있는 서버 트리 레이어가 추가된 셈입니다. 이에 따라 개발자는 필요에 따라 렌더링 전략을 더 세밀하게 제어할 수 있게 되었습니다.</p>
<h4 id="benefits-of-rsc">Benefits of RSC</h4>
<p>그럼 이런 특성을 가진 서버 컴포넌트는 어떤 장점이 있을까요? 위에서 살짝 언급하긴 했지만, 서버 컴포넌트에서는 기존에 클라이언트 사이드에서 접근할 수 없었던 파일 시스템이나 데이터 베이스 등에 직접 접근할 수 있게 됩니다. </p>
<pre><code class="language-jsx">import fs from &#39;fs&#39;;

async function Note({id}) {
  const note = JSON.parse(await fs.readFile(`${id}.json`));
  return &lt;NoteWithMarkdown note={note} /&gt;;
}</code></pre>
<p>물론,  컴포넌트 내에서 DB 접근도 가능합니다.</p>
<pre><code class="language-jsx">import db from &#39;db&#39;;

async function Note({id}) {
  const note = await db.notes.get(id);
  return &lt;NoteWithMarkdown note={note} /&gt;;
}</code></pre>
<p>또한 서버 컴포넌트는 HTML의 형태로 클라이언트로 서빙되기 때문에 컴포넌트에서 내부에서 사용된 라이브러리를 번들 사이즈에 포함할 필요가 없습니다. 따라서 번들 사이즈도 획기적으로 줄일 수 있습니다.</p>
<blockquote>
<p>서버 컴포넌트는 정확히는 RSC Payload라는 특별한 포맷으로 클라이언트로 전달되지만, 이 글에서는 이해를 돕기 위해 HTML로 설명합니다. 더 궁금하신 분들은 <a href="https://hrtyy.dev/web/rsc_payload/">해당 글</a>을 읽어보시길 권장드립니다.</p>
</blockquote>
<p>이것도 코드로 한번 살펴봅시다. 아래는 우리가 기존에 클라이언트 컴포넌트를 활용할 때의 예시입니다. 브라우저에서도 해당 코드가 실행되어야 하기 때문에, 번들에 해당 라이브러리 코드를 포함해야 합니다.</p>
<pre><code class="language-jsx">// NOTE: *before* Server Components

import marked from &#39;marked&#39;; // 35.9K (11.2K gzipped)
import sanitizeHtml from &#39;sanitize-html&#39;; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}</code></pre>
<p>만약 위 컴포넌트가 서버 컴포넌트로 전환된다면 어떨까요?</p>
<pre><code class="language-jsx">// Server Component === zero bundle size

import marked from &#39;marked&#39;; // zero bundle size
import sanitizeHtml from &#39;sanitize-html&#39;; // zero bundle size

function NoteWithMarkdown({text}) {
  // same as before
}</code></pre>
<p>앞서 말씀드렸듯 서버 컴포넌트의 실행 결과물은 HTML이기 때문에 번들 사이즈에 기여하지 않습니다. 위의 예제의 경우 약 70KB의 번들을 줄일 수 있겠네요. </p>
<p>이렇게 서버 컴포넌트를 사용하면, 서버 리소스를 더 자유롭고 폭넓게 활용할 수 있게 됩니다.</p>
<h4 id="why-rsc">Why RSC?</h4>
<p>지금까지 RSC가 나온 기술적인 배경과, 간단한 예시를 살펴보았습니다. 그런데 사실 위에서 살펴본 예시들은 꼭 RSC를 사용하지 않고도 기존 서버 사이드에서 작업할 수 있는 것들입니다. </p>
<p>NextJS로 예를 들어보면 getServersideProps에서도 가능한 작업입니다. 그럼 RSC를 사용하는 것은 어떤 차이점을 가져다줄까요?</p>
<p>가장 크고 중요한 차이점은 <strong>해당 로직을 페이지 레벨이 아닌 컴포넌트 레벨로 내렸다는 것입니다.</strong> 그리고 이런 서버 컴포넌트는 전체 컴포넌트 트리 어디에서나 삽입될 수 있습니다. 아래 그림과 같이 말이죠.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/9444d13b-d7b2-43d7-b414-48ba674b957d/image.png" alt=""></p>
<p>이렇게 컴포넌트 레벨에서 서버 접근이 가능하게 하고, 이를 Streaming ssr과 결합함으로써 기존 모델에 비해 훨씬 강력하고 유연한 모델을 갖게 됩니다.</p>
<h3 id="마치며">마치며</h3>
<p>RSC를 처음으로 구현한 App router가 출시된지 어느덧 1년이 훌쩍 넘었습니다. 그러나 레딧과 같은 해외 커뮤니티를 보면 App router는 여전히 뜨거운 감자 같은 녀석입니다. </p>
<p>출시 초기와 같이 &quot;이건 PHP시대로의 회귀다!!&quot;, &quot;이건 그냥 복잡한 똥덩어리다(<del>이런 얘기는 없었을 수도 있습니다</del>)&quot;와 같은 강한 반감을 표하는 글은 많이 줄어든 듯 하지만, 기존 Page Router의 간결함을 좋아하던 사람들에게는 아직 큰 지지를 얻지 못하고 있는 듯 합니다.</p>
<p>심지어 Nextjs github discussion을 보면 <a href="https://github.com/vercel/next.js/discussions/59373">page router vs app router</a>와 같은 vote도 올라와 있더라구요. page router가 압도적으로 이기고 있는데, 제가 vercel 개발자라면 꽤나 슬플 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/9070675b-e773-4af0-bbbe-e7acd50cafc6/image.png" alt=""></p>
<p>RSC 반대파의 의견을 살펴보면, DX는 낮아지고 복잡도는 높아진게 불호의 주된 이유입니다. 어느정도 동의하는 부분입니다. </p>
<p>저 또한 1년 정도 App router를 사용해오면서 개발 경험이 좋지 않다는걸 많이 느꼈거든요. 특히 Next 14 초반에는 App router 자체 버그가 꽤나 많기도 했구요. 지금은 많이 개선되었지만 여전히 갈 길이 먼 부분이라 느껴집니다(<del>쌓여있는 이슈들 처리좀 해라...</del>).</p>
<p>또 서버 컴포넌트의 사용은 전체적인 어플리케이션 복잡도를 확실히 높이는 것 같습니다. 기본적으로 클라이언트 컴포넌트인지 서버 컴포넌트인지 계속 신경쓰면서 개발해야 하고, 생각보다 서버 컴포넌트의 제약사항이 꽤 있기 때문에 잘 활용하려면 많은 고민이 필요하기도 하구요.</p>
<p>최근 리액트의 행보에는 이런 저런 의견이 많지만 개인적으로는 리액트가 더 똑똑한 아키텍쳐를 가지게 되었다고 생각합니다. 더 잘 깎아서 좋은 성능의 어플리케이션을 개발할 수 있도록 만들어 주었기 때문이죠.</p>
<p>사실 한국에서 서비스를 하는 경우에 대부분의 경우 성능은 큰 문제가 되지 않는 경우가 많습니다. 대다수의 유저가 최신 기기를 사용하고 네트워크 인프라도 훌륭하기에, 어플리케이션의 동작에만 집중해서 만들어도 준수한 서비스를 제공하기에 무리가 없거든요.</p>
<p>하지만 글로벌 서비스를 운영하면서, 디지털 격차는 여전히 크다는걸 알게 되었습니다. 아직도 10년 전 기기를 쓰는 유저가 꽤 많다는것도, 세상에는 정말 다양한 모바일 기기가 있다는 것도, 갤럭시 A15 시리즈는 여전히 현역이라는 것도, 크롬 네트워크 탭에 3G 쓰로틀링이 옵션이 괜히 있다는게 아니라는 것도요..😂</p>
<p>이렇게 원활하지 않은 네트워크 환경에서 저사양 기기로 접속하는 유저가 대부분인 환경이라면 단순 몇 KB의 번들 사이즈 개선도 유저 지표에 유의미한 영향을 미칩니다. 이러한 맥락에서 리액트 18버전은 큰 의미가 있다고 생각하고, 앞으로도 기대해 볼 여지가 많은 것 같습니다. 개발자 입장에서 이런 변화를 잘 활용해 통해 더 많은 사용자에게 준수한 성능을 제공할 수 있다면, 이 또한 넓은 의미에서의 접근성 준수가 아닐까요?ㅎㅎ</p>
<p>점점 주저리 주저리가 되는 것 같아 후기는 여기까지 하도록 하겠습니다. 그럼 긴 글 읽어주셔서 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프론트엔드 엔지니어를 위한 쿠버네티스 기초 개념 정리]]></title>
            <link>https://velog.io/@seeh_h/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4</link>
            <guid>https://velog.io/@seeh_h/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4</guid>
            <pubDate>Sun, 21 Jul 2024 04:07:48 GMT</pubDate>
            <description><![CDATA[<p>리액트 18 버전에서 서버 컴포넌트를 출시하고 뒤이어 NextJS가 프레임워크 차원에서 이를 지원하기 시작하면서 프론트엔드 개발 방식이 크게 변화하고 있습니다. </p>
<p>서버에서 리액트 컴포넌트를 실행하고, 서버 액션을 통해 데이터 뮤테이션을 진행하는 등 클라이언트에서 처리하던 많은 작업들이 서버로 이동할 수 있게 되었습니다.</p>
<p>이렇게 프론트엔드 애플리케이션의 서버 의존도가 높아지면서, 서버 인프라 관리 및 모니터링의 중요성도 커지고 있는데요. 프론트엔드 개발자에게도 어느정도의 인프라 지식이 요구되고 있습니다.</p>
<p>이번 포스팅은 이러한 배경을 바탕으로 현대 인프라의 핵심인 쿠버네티스의 주요 개념과 자주 쓰이는 용어에 대해 살펴보고 정리해보고자 합니다.</p>
<h1 id="쿠버네티스란-무엇이고-왜-사용할까요">쿠버네티스란 무엇이고 왜 사용할까요?</h1>
<p>클라우드 환경에서 서비스되는 애플리케이션은 대부분 <strong>이미지</strong>의 형태로 빌드되며, 컨테이너에서 이 이미지를 실행하는 구조를 갖습니다. 쿠버네티스란 이렇게 <strong>컨테이너화</strong>된 애플리케이션을 자동으로 배포하고 확장 관리하는데 필요한 여러가지 요소들을 자동화해주는 오픈소스 플랫폼입니다.</p>
<p>서비스가 안정적인 품질로 운영되기 위해서는 트래픽에 따른 서버 자원의 유연한 스케일링, 주기적인 컨테이너 상태 체크, 서비스 불가 컨테이너 종료 및 재시작, 무중단 배포, 필요시 빠른 롤백 등 다양한 작업이 적기에 이뤄져야 합니다. 수백, 수천개 규모의 컨테이너가 운용되는 대규모의 서비스일수록 이런 모든 작업을 인프라 관리자가 수동으로 처리하기는 쉽지 않습니다. </p>
<p>이런 작업을 똑똑한 시스템이 알아서 처리해준다면 더 쉽고 편하게 안정적인 서비스 운영 환경을 유지할 수 있지 않을까요? 이것이 바로 쿠버네티스를 사용하는 이유입니다. </p>
<p>쿠버네티스는 컨테이너 환경에서 서비스를 탄력적이고 안정적으로 운영하기 위한 다양한 기능들을 제공합니다. 배포, 스케일링, 로드 밸런싱과 같은 기능을 제공하며 사용자는 로깅, 모니터링 및 알림 솔루션을 자유롭게 통합할 수 있습니다. </p>
<h1 id="쿠버네티스의-동작-원리와-핵심-컴포넌트">쿠버네티스의 동작 원리와 핵심 컴포넌트</h1>
<p>쿠버네티스의 동작 원리와 쿠버네티스를 구성하는 핵심 컴포넌트들에 대해 살펴보겠습니다.</p>
<h2 id="쿠버네티스-동작-원리">쿠버네티스 동작 원리</h2>
<p>쿠버네티스를 배포하게되면 클러스터가 생성됩니다. 클러스터는 컨트롤 플레인과 노드 플레인으로 구분됩니다. </p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/61567d43-0b0e-47ae-a7ee-0619517c3ff1/image.png" alt=""></p>
<h3 id="컨트롤-플레인">컨트롤 플레인</h3>
<p>컨트롤 플레인은 클러스터의 관리자 역할을 수행합니다. 클러스터 전체의 상태를 관리하며, 각 워커 노드의 상태를 모니터링하고 작업 스케줄링, 리소스 관리 등 클러스터의 모든 동작을 제어합니다.</p>
<h3 id="노드-플레인">노드 플레인</h3>
<p>노드 플레인은 애플리케이션이 실행되는 환경을 제공하는 워커 노드들이 존재하는 공간입니다. 컨테이너화된 애플리케이션은 <strong>워커 노드</strong>의 파드(Pod) 형태로 존재하게 됩니다. 사용자가 애플리케이션에 접속하여 서비스를 이용할 때 워커 노드에서 실행 중인 파드를 통해 통신이 이루어집니다.</p>
<p>정리하면 컨트롤 플레인은 워커 노드들을 관리하는 사령관 역할이며, 워커 노드는 애플리케이션 서비스를 유저에게 제공하는 역할을 담당합니다.</p>
<h2 id="쿠버네티스를-이루는-핵심-컴포넌트">쿠버네티스를 이루는 핵심 컴포넌트</h2>
<p>이어서 쿠버네티스를 이루는 핵심 컴포넌트들에 대해서 살펴보겠습니다.</p>
<h3 id="파드pod">파드(POD)</h3>
<p>파드는 쿠버네티스에서 가장 작은 배포 단위로, 컨테이너가 실행되는 공간입니다.</p>
<p>쿠버네티스는 이미지를 컨테이너 내부에서 실행시키는데요. 이 컨테이너는 단독으로 실행되는 것이 아니라 파드 내에서 실행됩니다. 일반적으로 하나의 파드는 하나의 컨테이너로 구성되지만, 필요에 따라 여러개의 컨테이너로 구성되기도 합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/d04d0a32-384d-49ab-b351-4ce47b2a2c3e/image.png" alt=""></p>
<p>파드들은 앞에서 살펴 본 워커 노드에 의해 관리됩니다. 동일한 역할을 하는 파드라도 환경에 따라 다른 노드에 배치될 수 있으며, 특정 노드가 죽으면 해당 노드의 파드들이 건강한 상태의 다른 노드로 옮겨지기도 합니다.</p>
<p>파드는 스케일링 과정에서 자주 생성되고 사라지며, 워커 노드의 상태에 따라 이 노드에서 저 노드로 옮겨지기도 합니다.</p>
<h3 id="서비스service">서비스(Service)</h3>
<p>컨테이너화된 애플리케이션은 클러스터 내에서 여러 개의 파드(Pod)로 운영됩니다. 파드는 생성과 종료가 빈번하게 이루어지는 동적인 특성을 가지기 때문에, 동일한 역할을 하는 파드들을 하나의 네트워크 엔드포인트로 묶어 제공해야 합니다.</p>
<p>이를 담당하는 쿠버네티스 오브젝트가 바로 서비스(Service)입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/e36089f0-669f-47f6-a3f8-6d36bfda3bfd/image.png" alt=""></p>
<p>서비스는 내부적으로 <strong>실행 중인 파드에 안정적으로 접근할 수 있는 방법</strong>을 제공합니다. 동적으로 변하는 파드의 IP 주소 대신, 서비스는 고정된 네트워크 엔드포인트를 노출하여 클러스터 내부 또는 외부에서 파드에 접근할 수 있도록 해줍니다. </p>
<p>서비스 타입에는 Cluster IP, NodePort, LoadBalencer, ExternalName등 네가지가 있으나, 각 타입에 대한 설명은 이 글에 범위에 벗어나므로 다루지는 않습니다. 자세한 내용이 궁금하신 분들은 <a href="https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types">여기</a>를 참조하세요.</p>
<h3 id="인그레스ingress">인그레스(Ingress)</h3>
<p>쿠버네티스의 네트워크 트래픽은 외부에서부터 들어오는 인그레스 트래픽과, 내부에서 외부로 나가는 이그레스 트래픽으로 분류됩니다. 인그레스는 클러스터 외부 트래픽을 클러스터 내부의 서비스로 라우팅해주는 역할을 하는 오브젝트입니다.</p>
<p>쉽게 말하면 인그레스는 클러스터 외부에서 내부로 접근하는 요청들을 어떻게 처리할 지 정의해둔 규칙들의 모음, 라우팅 테이블이라 볼 수 있습니다.</p>
<p>애플리케이션에 접속하려는 유저는 인그레스를 거쳐 적절한 파드로 라우팅됩니다.</p>
<p>지금까지 파드, 서비스, 인그레스에 대해 살펴보았는데요. 이를 조합하면 유저가 애플리케이션을 사용하기 위해 거쳐야하는 경로를 완성할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/87ed265a-fe84-4482-8d1d-45d3cc111cfc/image.png" alt=""></p>
<p><strong>인그레스</strong>를 통해 들어온 트래픽은 <strong>서비스</strong>를 거쳐 내부 <strong>파드</strong>로 연결됩니다. 각 파드는 트래픽을 받아 유저에게 애플리케이션 서비스를 제공합니다.</p>
<h3 id="디플로이먼트deployment">디플로이먼트(Deployment)</h3>
<p>실제 운영 환경에서는 트래픽 부하를 분산하고, 서비스의 안정성을 높이기 위해서 하나의 애플리케이션을 여러 개의 파드로 운영합니다. 각 파드를 하나 하나 직접 생성할수도 있지만, 이렇게 여러 개의 파드를 수동으로 생성하고 관리하는 것은 공수도 많이 들고 실수하기도 쉽습니다.</p>
<p>디플로이먼트는 파드 생성 및 관리를 쉽게 진행할 수 있게 도와주는 오브젝트입니다. 개발자는 애플리케이션의 desired state(원하는 상태)를 선언적으로 정의할 수 있습니다. </p>
<p>예를 들어 &quot;이 애플리케이션은 항상 3개의 파드로 운영되어야 하며, 각 파드는 이 특정 버전의 이미지를 사용해야 한다&quot;라고 정의하면, 쿠버네티스는 자동으로 이 상태를 유지하기 위해 필요한 작업들을 수행합니다.</p>
<p>디플로이먼트를 통해 정의된 상태를 바탕으로 쿠버네티스는 자동으로 필요한 작업(파드 생성, 업데이트, 복구 등)을 수행하여 선언된 상태를 유지합니다.</p>
<p>디플로이먼트는 주로 <a href="https://raw.githubusercontent.com/kubernetes/website/main/content/en/examples/controllers/nginx-deployment.yaml">yaml 파일</a>로 설정합니다. 개발자는 YAML 파일에 원하는 상태를 정의한 후, kubectl 명령어를 사용해 컨트롤 플레인에 전달합니다. 컨트롤 플레인은 이를 실행하여 정의된 상태를 유지하도록 관리합니다.</p>
<h2 id="마치며">마치며</h2>
<p>이번 포스팅에서는 쿠버네티스와 쿠버네티스를 구성하는 주요 컴포넌트에 대해 간단하게 살펴보았습니다. 오늘 포스팅에서 다룬 개념들 외에도 네임스페이스, 레플리카셋, 시크릿, 컨피그맵 등 다루지 못한 개념들이 많습니다.</p>
<p>쿠버네티스는 기능이 많고 각 개념 또한 방대하기 때문에 모든 내용을 한 번에 이해하기는 어렵습니다. 사실 저 역시 이 글을 작성하는 현재 시점에서 모르는 부분이 많습니다.(<del>갑자기 고해성사?</del>)</p>
<p>사실 실무에서는 개념적인 요소들 보다는 파드의 상태, 서버 리소스(메모리, CPU 등)를 모니터링 해야하는 경우가 많은데요. 해당 내용을 다루지 못한것이 좀 아쉬워서, 다음 포스팅에는 실제로 클라우드 환경에서 자원들을 만들어보고 모니터링 환경을 구축해보는 과정을 다뤄볼까 합니다. 언제가 될진 모르겠지만요ㅎㅎ;; </p>
<p>그럼 오늘도 긴 글 읽어주셔서 감사합니다.</p>
<h2 id="출처">출처</h2>
<blockquote>
</blockquote>
<p><a href="https://kubernetes.io/ko/docs/home/">https://kubernetes.io/ko/docs/home/</a>
<a href="https://kubetm.github.io/k8s/">https://kubetm.github.io/k8s/</a>
<a href="https://seongjin.me/kubernetes-pods/">https://seongjin.me/kubernetes-pods/</a>
<a href="https://www.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS5675948724">https://www.hanbit.co.kr/channel/category/category_view.html?cms_code=CMS5675948724</a>
<a href="https://bumday.tistory.com/165">https://bumday.tistory.com/165</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Web worker로 성능 향상시키기]]></title>
            <link>https://velog.io/@seeh_h/Web-worker%EB%A1%9C-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81%EC%8B%9C%ED%82%A4%EA%B8%B0</link>
            <guid>https://velog.io/@seeh_h/Web-worker%EB%A1%9C-%EC%84%B1%EB%8A%A5-%ED%96%A5%EC%83%81%EC%8B%9C%ED%82%A4%EA%B8%B0</guid>
            <pubDate>Sat, 30 Mar 2024 11:35:52 GMT</pubDate>
            <description><![CDATA[<p>현대의 웹 브라우저는 멀티 스레드 방식으로 동작합니다. 네트워크 요청, 파일 I/O, 화면 렌더링 등은 각각 다른 스레드에서 동작하며 브라우저는 각각의 테스크를 병렬로 처리합니다. </p>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">웹 워커(Web Workers)</a>란 무엇일까요? 웹 워커는 Web 표준 API로, 브라우저의 멀티 스레딩 환경을 활용하는 기술입니다. 웹 워커를 사용하면 메인 스레드와 분리된 백그라운드 스레드에서 스크립트를 실행할 수 있습니다.</p>
<p>자바스크립트는 싱글 스레드로 동작하기 때문에 한번에 하나의 스크립트만 실행할 수 있는데요. 웹 워커를 사용하면 별도의 스레드에서 다른 작업을 동시에 실행시킬 수 있게 됩니다.</p>
<p>따라서 CPU 리소스를 많이 소모하는 작업을 메인 스레드에서 분리하여 별도의 스레드로 실행시킨다면, 메인 스레드가 UI 업데이트 및 사용자 액션 처리에 집중할 수 있도록 하여 어플리케이션의 성능 향상을 기대할 수 있습니다.</p>
<p>이번 포스팅에서는 실제로 복잡한 연산을 별도의 스레드로 분리해서 처리해보며 메인 스레드의 성능에 어떤 영향을 미치는지 살펴보겠습니다.</p>
<h2 id="web-worker-사용법">web worker 사용법</h2>
<p>Web Worker를 사용하기 위해서는 먼저 Worker 객체를 생성해야 합니다.</p>
<pre><code class="language-jsx">// 메인 스레드에서 Worker 생성
const myWorker = new Worker(&#39;worker.js&#39;);</code></pre>
<p>postMessage 메서드와 onmessage 이벤트 핸들러를 메인 스레드와 Worker 간의 통신을 진행할 수 있습니다.</p>
<pre><code class="language-jsx">// 메인 스레드에서 Worker에 데이터 전송
myWorker.postMessage({type: &#39;start&#39;, payload: &#39;Hello Worker!&#39;});

// Worker로부터 메시지 수신
myWorker.onmessage = function(event) {
  console.log(&#39;Received message from worker:&#39;, event.data);
};</code></pre>
<p>메인 스레드는 postMessage()를 사용하여 Worker에 데이터를 전송하고, Worker는 onmessage를 사용하여 메시지를 수신합니다.</p>
<pre><code class="language-jsx">// worker.js에서 메시지 수신 및 응답
self.onmessage = function(event) {
  console.log(&#39;Message received from main thread:&#39;, event.data);
  postMessage(&#39;Hi from Worker!&#39;);
};</code></pre>
<h2 id="web-worker-테스트">web worker 테스트</h2>
<p>이제 실제로 두 환경에서 동일한 연산을 실행해보며 차이를 살펴보겠습니다. </p>
<h3 id="without-webworker">without webworker</h3>
<p>성능 테스트를 위해 의도적으로 메인 스레드를 바쁘게 만들도록 하겠습니다. 버튼을 누를때마다 복잡한 피보나치 수열을 계산하도록 하고, 이를 통해 UI 업데이트 지연을 유도합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/7e1fad66-71b2-4036-bb9f-88c559c22d55/image.png" alt=""></p>
<p>버튼이 클릭될때마다 fibonacci 수열이 40번 계산됩니다.</p>
<h3 id="with-webworker">with webworker</h3>
<p>모든 로직은 동일하며 수열 계산 부분을 worker로 옮겨줍니다. </p>
<p>worker 생성 코드입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/9c5463ba-c51f-4ac8-9f5b-c979375999c9/image.png" alt=""></p>
<p>메인 스레드에서 worker 쓰레드와 통신할 수 있도록 event listener를 추가합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/29b086f7-da17-4726-9529-b7ba6a56ea63/image.png" alt=""></p>
<blockquote>
<p>설명의 편의를 위해 일부 코드가 생략되었습니다. 전체 코드는 <a href="git@github.com:Siihyun/web-worker-example.git">여기</a>서 확인하실 수 있습니다.</p>
</blockquote>
<h2 id="web-worker-테스트-결과">web worker 테스트 결과</h2>
<h3 id="without-web-worker">without web worker</h3>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/7be4edce-4ad6-4294-afed-2940414da69d/image.gif" alt=""></p>
<p>계산 로직이 메인 스레드를 블락해서 UI 업데이트가 지연되는 모습입니다. 사용자의 액션(클릭)은 무시됩니다.</p>
<h3 id="with-web-worker">with web worker</h3>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/62f65088-1c13-455d-9dfe-5acc4de87e12/image.gif" alt=""></p>
<p>계산은 동일하게 오랜 시간이 소요되지만 메인 스레드에는 영향이 없는것을 확인할 수 있습니다.</p>
<h2 id="마치며">마치며</h2>
<p>이번 포스팅은 &quot;Worker thread를 통해 작업을 백그라운드에서 실행할 수 있을까? 그리고 이를 통해 실제로 성능적인 이점을 얻을 수 있을까?&quot;라는 의문에서 출발했습니다. </p>
<p>테스트 후 성능적인 궁금증은 해소 되었으나, 실제 사용 사례가 있을지 다시금 궁금해져 조금 더 찾아봤는데요. 아쉽게도 유의미하게 worker가 활용되는 사례를 발견하지는 못했습니다. <a href="https://web.dev/learn/performance/web-worker-demo?hl=ko">web dev</a>에서는 이미지 업로드 시 메타 정보를 제거에 활용하는 사례를 소개하고 있습니다. 서버 리소스를 활용할 수 없는 경우라면 꽤 유용할지도 모르겠습니다. </p>
<p>혹시 web worker를 유용하게 사용하신 경험이 있다면 들려주시면 감사하겠습니다🙇‍♂️</p>
<h2 id="reference">reference</h2>
<blockquote>
<p><a href="https://web.dev/learn/performance/web-worker-demo?hl=ko">https://web.dev/learn/performance/web-worker-demo?hl=ko</a>
<a href="https://inpa.tistory.com/entry/NODE-%F0%9F%93%9A-workerthreads-%EB%AA%A8%EB%93%88">https://inpa.tistory.com/entry/NODE-%F0%9F%93%9A-workerthreads-%EB%AA%A8%EB%93%88</a>
<a href="https://velog.io/@whow1101/Web-Worker">https://velog.io/@whow1101/Web-Worker</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[2023 회고]]></title>
            <link>https://velog.io/@seeh_h/2023-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@seeh_h/2023-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Tue, 02 Jan 2024 14:22:25 GMT</pubDate>
            <description><![CDATA[<p>어느덧 2023년도 마무리가 되었네요.<br>업무, 성장, 팀, 행복을 주제로 지난 1년을 돌아보고자 합니다.</p>
<h2 id="업무">업무</h2>
<h3 id="웹-리뉴얼">웹 리뉴얼</h3>
<p>웹뷰를 주로 하다보니 &#39;웹 작업을 하고싶다!&#39;라는 생각을 종종 했었는데, 운이 좋게도 기존 웹 제품을 리뉴얼 할 기회가 있었습니다. 기존의 웹은 앱에서 불편한 기능을 보조하고 앱으로의 랜딩을 유도하는 도구로써 활용되었는데요. 리뉴얼 버전은 독자적인 프로덕트로 더 다양한 기능을 제공하고자 했습니다.</p>
<p>이를 위해 필요한 핵심 기능중 하나는 주식 차트였고, 멀티차트와 각종 보조지표까지 지원하는 것으로 스펙이 결정되었습니다.</p>
<p>바닥부터 개발하기에는 일정, 구현 난이도 모두 쉽지 않다고 느껴 리서치를 통해 사용 가능한 라이브러리를 찾아보았습니다. 여러 차트 라이브러리들의 코드를 뜯어보며 동작 방식을 분석해보고 현재 스펙을 기준으로 사용 가능한지 검토하는 과정을 진행했습니다.</p>
<p>결론적으로는 더 이상 유지보수 되지 않는 라이브러리를 가져와 베이스코드로 사용하고, 동작하지 않는 부분을 고치고 스펙에 맞지 않는 부분들을 커스텀해서 요구사항을 맞출 수 있었습니다.</p>
<p>커스텀 과정 중 여러가지 어려움이 있었지만 보조지표 중 매물대 차트를 만드는 과정에서 고생했던게 기억이 남습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/c41fed79-b6f6-47b5-9228-64415ac73e4e/image.png" alt=""></p>
<p>차트를 만들며 canvas를 기존에 알던 것보다 더 깊게 다뤄볼 수 있었습니다. 또, 라이브러리를 가져와서 패치, 배포하며 라이브러리 버저닝, 번들링 등 다양한 부분에서 고민을 할 수 있었던 것 같습니다. </p>
<p>처음 다뤄보는 도메인이라 여러가지 우여곡절이 많았지만 어찌저찌 잘 마무리 할 수 있었고, 나중에는 웹뷰 내에도 차트를 넣게 되어 굉장히 뿌듯했던 성과 중 하나였습니다.</p>
<h3 id="오퍼월-광고">오퍼월 광고</h3>
<p>하반기는 주로 앱 내의 광고 상품들을 만드는 작업을 진행했습니다. 릴스 형식의 숏폼이나 전환형 광고, 클릭형 광고 등 다양한 상품을 만들었습니다. </p>
<p>숏폼의 비디오 플레이어를 만들며 평소 잘 사용해보지 않았던 video 태그를 많이 다뤄볼 수 있었습니다. 그리고 전환형 광고 상품을 만들며 모바일의 세계에선 어떤 방식으로 유저의 행동을 추적하는지, 앱스플라이어의 포스트백은 어떻게 동작하는지 등 평소 궁금했던 부분들에 대해서 배울 수 있었습니다.</p>
<p>앱 내 광고 세팅과 전환 추적을 위해 디테일한 어드민 기능이 요구되었는데요. 이 과정에서 어드민의 기능 복잡도가 꽤 높아졌습니다.</p>
<p>더 이상 복잡도가 올라가면 기능 추가나 유지보수에 상당한 어려움을 느낄 것 같다는 판단 하에, 사용성은 유지하면서 기능 복잡도를 최소한으로 가져갈 수 있도록 사업팀 그리고 pm분들과 많은 대화를 나누며 스펙을 정리하고 재정의했습니다.</p>
<p>결국 의도한대로 업무를 마무리 할 수 있었고 소통하는 과정에서 커뮤니케이션의 중요성을 다시금 느꼈습니다. 개발 외적으로도 많은 것을 배울 수 있었던 소중한 경험이었습니다.</p>
<h3 id="이벤트">이벤트</h3>
<p>하반기에는 퍼포먼스 마케팅 비용을 일부 삭감했기 때문에, 이에 대비하여 유저 유입을 늘릴 수 있는 다양한 이벤트 피쳐들을 출시했습니다. </p>
<p>이벤트는 주식 리워드, 5분 예측, 행운 빙고, 친구 초대 등 게임성 피쳐가 많았습니다. 피쳐 특성상 특정 시기에 트래픽이 몰리는 경우가 많아 ssr 노드서버가 죽기도 하고, API 호출수가 튀어 서버가 힘들어 하기도 하는 다소 살떨리는 경험들을 많이 했습니다. 해결 과정에서 네트워크 성능 최적화에 대해 많은 고민을 해볼 수 있었습니다.</p>
<p>또한 그로스 목적의 피쳐인만큼 유저 지표가 중요했는데, PM팀과 함께 세션 시간, 평균 세션 수, 탭 체류시간, 체리 피커 비율 등 각종 지표들을 살펴보며 중요하게 살펴보아야 하는 지표가 무엇이며 어떤 의미가 있는지도 배울 수 있었습니다.</p>
<h2 id="성장">성장</h2>
<p>작년에 작성한 회고 글에 이런 내용이 있습니다.</p>
<blockquote>
<p>올해는 너무 많은 것을 배우려고 하다 보니 정작 하고있는 일들에 집중하지 못했던 순간들이 많었다. 내년에는 하나를 배울 때 더 몰두하고 디깅하는 습관을 들이고 싶다.</p>
</blockquote>
<p>그래서 올해는 많은 것을 동시에 공부하기 보다는 부족한 부분을 찾아서 채워가는것을 목표로 세웠습니다. 퇴근 후에는 업무를 진행하다 일정에 치여 우선 지나간 찜찜한 것들을 살펴보거나, 평소 궁금했던 라이브러리들의 내부 코드를 살펴보았습니다.</p>
<p>NextAuth 라이브러리를 이용해 소셜 로그인 연동 작업을 진행하다 막상 <a href="https://velog.io/@seeh_h/OAuth-%EC%9B%90%EB%A6%AC-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0">OAuth</a>를 잘 모른다는 생각이 들어 공부해보기도 하고, 어드민에서 텍스트파일 추출 기능을 개발하다 <a href="https://velog.io/@seeh_h/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%9D%98-%EC%9D%B8%EC%BD%94%EB%94%A9-%EB%94%94%EC%BD%94%EB%94%A9">인코딩/디코딩</a>에 막혀 살펴보기도 하고, </p>
<p>광고 배너를 위한 이미지 생성기를 만들다 s3에서 이미지를 가져오는 과정에서 CORS를 마주하여 고생하기도 하고, <a href="https://velog.io/@seeh_h/react-reanger-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0">ranger 라이브러리</a> 내부 구현이 궁금해 들여다보기도 하고,</p>
<p>배포한 아이콘 패키지의 트리쉐이킹이 제대로 되지 않아 해결 과정에서 esm과 cjs 그리고 웹팩에 대해 공부하기도 했습니다. 띄엄띄엄 알고있던 부분들이 조금 더 촘촘하게 채워진 것 같아 만족스럽지만, 여전히 부족함을 많이 느낍니다. </p>
<p>올해는 아래 주제들에 대해 고민해보고, 필요한 부분을 찾아 학습하려 합니다.</p>
<ul>
<li>Nodejs</li>
<li>Docker와 k8s</li>
<li>ssr과 server component</li>
<li>번들러와 모듈</li>
<li>브라우저 성능 최적화</li>
<li>모노레포 관리</li>
<li>지속 가능한 테스트코드</li>
<li>type-safe한 코드</li>
</ul>
<p>올해는 특별한 액션 플랜을 정해봤는데요. 각 주제에 대해 더 깊게 공부할 수 있도록 한 달에 하나씩 주제를 정하고 집중적으로 다뤄보려 합니다. 올해 또한 만족할만한 성과를 얻었으면 하는 바람입니다.</p>
<h2 id="팀">팀</h2>
<p>지금의 팀에서 업무를 하며 참 많은것을 배웠습니다. 시니어 비율이 높은 팀이고, 큰 테크기업에서 오신 분들도 많아 업무를 대하는 태도와 업무 진행 방식, 커뮤니케이션 스킬 등 많은 것을 보고 배울 수 있었습니다. </p>
<p>감사하게도 &#39;나만 잘하면 되겠다&#39;라는 생각을 참 많이 했습니다. 동료분들의 존중과 배려속에서 업무를 진행하며, 항상 겸손한 자세로 업무를 해야겠다는 생각도 많이 했던 것 같습니다. &#39;뛰어난 동료가 최고의 복지다!&#39;라는 말을 실감하며 행복하게 일했던 일년이었습니다.</p>
<p>아쉬웠던 점이 있다면 기획부터 배포까지의 작업 일정이 빠듯하여 종종 코드에 대해 구조적으로 고민할 시간이  부족하다고 느껴진 점입니다. 하지만 가장 중요한 것은 비즈니스적인 요소이고, 이러한 상황에서 최선의 아웃풋을 내는것도 결국 개인의 역량이라 생각했기에 반복되는 작업들을 최대한 빠르게 마무리하여 고민할 시간을 확보하려고 노력했습니다.</p>
<p>반복되는 업무를 줄이려다 보니 이는 자연스레 팀 차원의 생산성에 대해 고민으로 이어져, 공통 로직을 처리하는 컴포넌트나 클래스를 만들어보기도 하고, 다른 회사의 공개된 디자인 시스템 라이브러리를 살펴보며 어떤 것들을 공통화하는지 들여다보기도 했습니다. </p>
<p>사내에서도 클라이언트 팀이 스웨거 문서를 통해 API 통신 코드를 자동으로 생성하고 <a href="https://www.jetbrains.com/kotlin-multiplatform/">KMM</a>을 통해 모바일(IOS/AND) 공통 모듈을 만들어 나가는 과정을 보며 개발 조직의 생산성이 회사의 전체적인 속도에도 영향을 미칠 수 있는 중요한 영역이라는 생각이 들었습니다.</p>
<p>아쉽게도 현재는 여러가지 사정으로 인해 팀과 작별을 앞두고 있습니다. 정들었던 팀과 제품을 떠나기 아쉬운 마음이 크지만, 또 다른 성장을 위해 새로운 도전을 준비하려고 합니다.</p>
<h2 id="행복">행복</h2>
<p>올해는 유난히 &#39;행복&#39;에 대한 고민을 많이 했습니다. 인생은 결국 행복하기위해 사는 것이라고 생각을 하는데, 정작 행복이 뭔지에 대해 갈피를 잡기가 어려웠습니다.</p>
<p>고민의 정도가 심했을 때는 &#39;나는 언제 행복한가?&#39;, &#39;다른 사람들은 언제 행복감을 느낄까?, &#39;이런 고민이 드는 나는 행복하지 않은건가?&#39;등 많은 생각이 꼬리의 꼬리를 물고 이어질 때도 있어서, 주말에 코딩하러 카페에 갔다가 온종일 이런 고민만 하다 나온적도 있습니다🤣</p>
<p>몰입해서 업무를 하거나, 사랑하는 사람들을 만나거나, 취미 생활을 할 때 소소한 행복감을 느끼긴 하지만 &#39;이 정도로 인생이 행복하다고 말할 수 있을까?&#39;라는 의문에 확신을 가지고 답하기 어려웠습니다.</p>
<p>혼자 깊게 고민 해보기도 하고 주변 지인들의 생각을 물어보기도 했으나 아직 결론을 내리지는 못했습니다. </p>
<p>올해도 계속 될 것 같은 고민이지만, 어쩌면 작은 일들에 소소한 행복감을 느낄 수 있는 지금이 행복한 상태가 아닐까?하고 조금 편하게 생각해보기로 했습니다.</p>
<p>2024년, 모두 행복한 한 해를 보내셨으면 좋겠습니다. 긴 글 읽어주셔서 감사합니다🙇‍♂️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[react-ranger 뜯어보기(feat. Headless UI)]]></title>
            <link>https://velog.io/@seeh_h/react-reanger-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@seeh_h/react-reanger-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Sat, 18 Nov 2023 08:27:41 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seeh_h/post/5d752839-0f30-4a7a-a687-d2950cb6ccfd/image.gif" alt=""></p>
<p>Range Slider는 사용자가 고정 옵션 집합에서 값 또는 범위를 선택할 수 있는 UI입니다. 볼륨이나 화면의 밝기, 필터에서 많이 사용됩니다.</p>
<p>이번 포스팅에서는 tanstack팀에서 제공하는 <a href="https://github.com/TanStack/ranger">react-ranger</a>라는 라이브러리를 소개할 예정인데요. 이 라이브러리는 Range Slider를 Headless UI의 형태로 제공합니다. </p>
<p>Range Slider를 실제로 사용해보고, 이후 내부 코드를 살펴보며 Headless 형태의 라이브러리가 사용자에게 어떤 장점을 제공하는지, 그리고 이러한 사용성을 제공하기 위한 interface는 어떻게 설계 되었는지 살펴보겠습니다.</p>
<h2 id="headless-ui">Headless UI</h2>
<p>본격적으로 살펴보기 전에, 우선 Headless UI의 정의에 대해 알아보겠습니다. Headless UI란 무엇일까요? </p>
<p>Headless UI란 컴포넌트의 별도 UI를 제공하지 않고, &#39;동작&#39;에 대한 기능만 제공해주는 라이브러리를 말합니다. 쉽게 말하자면 스타일 없이 기능만 제공해주는 라이브러리라고도 할 수 있습니다. 대표적으로 <a href="https://www.radix-ui.com/primitives">Radix-UI</a>, <a href="https://headlessui.com/">Headless-UI</a>, <a href="https://tanstack.com/table/v8">tanstack/table</a>등이 있습니다.</p>
<p>프론트엔드 개발을 하다 보면 기능 요구사항만큼 중요한 것이 디자인 요구사항인데요. 같은 기능을 제공 하더라도 UX/UI에 따라 사용자가 느끼는 제품의 퀄리티가 많이 달라지기 때문에, 디자인 구현에 쏟는 시간이 적지 않은 것 같습니다.</p>
<p>개발을 하다 보면 일정, 편의, 안정성 등 다양한 이유로 라이브러리를 적용하는 경우가 많습니다. 이 때 스타일이 포함되어 제공되는 라이브러리를 사용하는 경우에는 css 오버라이드를 통해 스타일을 재정의하여 디자인 요구사항을 맞추게 됩니다.</p>
<p>문제는 이 작업이 생각보다 번거롭고 쉽지 않다는 점인데요. 가끔은 이미 정의된 레이아웃을 뒤엎고 원하는 UI에 완전히 맞추는 것이 불가능할 때도 있습니다.</p>
<p>이런 느낌이랄까요? 😞
<img src="https://velog.velcdn.com/images/seeh_h/post/a5c84e55-3492-4521-897f-18ac93554746/image.png" alt=""></p>
<p>Headless 컴포넌트 패턴은 이러한 문제에 대해 로직(동작)과 UI(표현)를 분리하는 것으로 해결책을 제시합니다. 라이브러리는 로직에 대한 기능만 제공하고, UI는 사용자가 알아서 정의하는 것이죠. </p>
<p>이는 곧 두가지 장점으로 이어집니다. 첫번째로는 관심사의 분리입니다. Headless UI는 로직과 UI를 명확하게 분리합니다. 이로 인해 코드베이스가 더 관리하기 쉽고 이해하기 쉬워지며 기능 추가 또한 용이해집니다.</p>
<p>두번째로는 유연성입니다. Headless UI는 표현(UI)에 구애받지 않기 때문에 디자인 유연성을 높일 수 있습니다. 기본 로직에 영향을 받지 않고 사용자가 원하는대로 UI를 정의할 수 있습니다.</p>
<p>그럼 실제로 라이브러리 예시 코드를 보면서 headless 컨셉에 대해 조금 더 자세히 살펴보도록 하겠습니다.</p>
<h2 id="react-ranger-뜯어보기">React-Ranger 뜯어보기</h2>
<p>본격적으로 시작하기 전 Range Slider의 UI 구성 요소에 대해 먼저 살펴보겠습니다.  Slider는 Track, Handle, Segment, Tick 네가지 영역으로 구분됩니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/ea302766-2d28-4c1d-9492-b46fa49b1ef7/image.png" alt=""></p>
<p>각 구성 요소가 의미하는 바는 아래와 같습니다.</p>
<blockquote>
<ul>
<li>Track: 전체 bar 영역을 의미합니다.</li>
</ul>
</blockquote>
<ul>
<li>Handle: range를 조절하는 버튼을 의미합니다. </li>
<li>Segment: Handle(버튼)들에 의해 나눠지는 영역으로, 위 이미지에서 빨간색 박스로 감싸진 영역입니다. </li>
<li>Tick: 하단 눈금(0~100)을 나타내는 영역입니다.</li>
</ul>
<h2 id="react-ranger-적용-코드">React-Ranger 적용 코드</h2>
<p>이제 예제 코드를 살펴볼텐데요. 코드는 크게 hook을 적용하는 부분과 hook에서 받은 rangerInstance를 이용해서 렌더하는 부분으로 나눌 수 있습니다.</p>
<h3 id="useranger">useRanger</h3>
<p>먼저 useRanger hook입니다.</p>
<p>react-ranger는 useRanger hook 내부에서 모든 로직을 관리합니다. 주요 로직들은 hook에서 반환하는 rangerInstance안에 대부분 추상화되어 있습니다. 필요한 상태 및 함수는 인스턴스를 통해 최소한으로 노출합니다.</p>
<p>사용자는 rangerInstance를 통해 range 내부의 상태와 상호작용이 가능합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/eeb21c30-676e-4d09-affc-73d2bd3b4450/image.png" alt=""></p>
<p>hook을 호출할 때 option을 전달해야 주어야 하는데요. 넘겨주는 option 각 요소의 의미는 다음과 같습니다.</p>
<blockquote>
<ul>
<li>getRangerElement: track에 해당하는 html element 입니다. 내부에서 handle과 segment의 위치를 구하는데에 사용됩니다.</li>
</ul>
</blockquote>
<ul>
<li>values: 현재 range slider의 handle의 위치입니다. state로 선언해서 넘겨주어야 합니다.</li>
<li>min, max: track에서 handle이 가질 수 있는 최소, 최대값 입니다.</li>
<li>stepSize: 한 번의 drag당 handle의 값이 변화하는 크기입니다.</li>
<li>onChange: handle 값의 변화마다 트리거되는 이벤트 핸들러입니다</li>
</ul>
<h3 id="렌더부">렌더부</h3>
<p>다음은 useRanger hook에서 반환한 rangeInstance를 이용해서 range slider UI를 렌더하는 부분입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/189fe58a-7c33-4664-8f18-41112fc71467/image.png" alt=""></p>
<blockquote>
<p>설명을 위해 최소한의 코드만 남겨두었으며, 전체 코드는 <a href="https://github.com/TanStack/ranger/blob/main/examples/react/basic/src/main.tsx">여기</a>서 확인 가능합니다. </p>
</blockquote>
<p>rangeInstance는 UI 요소들(tick, segment, handle)에 대한 정보를 담은 getTicks, getSteps, handles와 같은 함수를 제공하는데요. 위 코드를 보면 해당 함수들을 이용하여 UI를 그려내는것을 볼 수 있습니다.</p>
<h4 id="getticks-라인48">getTicks (라인4~8)</h4>
<p>Track 하단에 노출되는 Tick(눈금)을 그리기 위해 사용되는 함수입니다. getTicks 함수는 tick의 value, percentage 값을 담은 배열을 반환합니다. tick은 option으로 전달한 step size로 자동 생성되며, percentage 또한 min, max와 step size 값을 통해 자동으로 계산됩니다.</p>
<h4 id="getsteps-라인-911">getSteps (라인 9~11)</h4>
<p>Track 내부에서 handle들로 인해 나눠지는 영역인 Segment들을 그리기 위한 함수입니다. getSteps 함수는 각 segment의 시작점(left)과 너비(width)의 값을 담은 배열을 반환합니다. 각 segment는 handle의 값을 참조하여 자동으로 계산됩니다.</p>
<h4 id="handles-라인-1234">handles (라인 12~34)</h4>
<p>Track 위 영역을 조절하는 버튼인 handle을 그리는 함수입니다. 실질적으로 핵심 기능을 수행하는 친구입니다. </p>
<p>앞 선 두 함수와 다르게 내려주는 인자가 좀 많은데요. 유저가 handle을 drag 했을때, 이를 감지할 수 있어야 하기 때문에 필요한 handler(keyDown, mouseDown, touchStart)를 함께 내려주어야 하기 때문입니다.</p>
<p>이를 받아서 handle로 렌더하려는 element에 심어주면 됩니다. (라인 26~30)</p>
<p>handles 함수는 이러한 eventHandler 외에도 handle의 value, 그리고 현재 해당 handle이 drag되고 있는지를 판단할 수 있는 isActive등의 필드를 내려줍니다. </p>
<p>이렇게 코드를 작성하면 아래와 같은 슬라이더가 멋지게 그려집니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/5d752839-0f30-4a7a-a687-d2950cb6ccfd/image.gif" alt=""></p>
<h2 id="headless-ui-1">Headless UI!</h2>
<p>간단하게 라이브러리를 이용해보았습니다. 이제 앞서 정의한 Headless UI 정의를 다시한번 살펴보겠습니다.</p>
<blockquote>
<p>Headless UI란 컴포넌트의 별도 UI를 제공하지 않고, &#39;동작&#39;에 대한 기능만 제공해주는 라이브러리를 말합니다.</p>
</blockquote>
<p>의미가 조금 더 와닿으셨나요? 앞서 살펴본 예시에서 react-range는 UI 요소인 tick, handle, segment에 대해 <strong>그릴 수 있는 정보</strong>만 제공할 뿐, 렌더(UI) 로직에는 일절 관여하지 않습니다.</p>
<p>반면에 tick의 값이나 유저의 drag 액션에 의해 변화하는 handle 및 segment 의 값을 처리하는 로직은 rangeInstance 내부에서 알아서 처리해줍니다.</p>
<p>만약 새로운 기능이 추가되어야 하는 경우, rangeInstance 내부에 로직을 작성하고, hook을 통해 노출해주기만 하면 됩니다.</p>
<p>또한 사용자는 이러한 내부 값들이 어떻게 관리되는지 알 필요 없이 정보를 받아서 UI만 그려낼 수 있습니다. </p>
<p><a href="https://mui.com/material-ui/react-slider/">MUI-slider</a>나 <a href="https://rsuitejs.com/components/slider/">react-suite-slider</a>와 같이 디자인 컴포넌트 형식으로 제공해주는 컴포넌트와 비교하면 작성해야할 코드가 조금 더 많긴 하지만, UI와 비즈니스 로직의 의존성을 분리하여 유연하게 대응이 가능한 점이 Headless UI의 장점입니다.</p>
<h2 id="react-ranger-라이브러리의-내부-구조">React Ranger 라이브러리의 내부 구조</h2>
<p>지금까지 라이브러리를 사용하는 시점에서의 코드를 살펴보았습니다. 이제 라이브러리 내부 코드를 살펴보겠습니다. </p>
<p>React Ranger는 크게 두 패키지로 나눌 수 있는데요. 핵심 기능을 처리하는 ranger 패키지와 이것을 리액트 프로젝트에서 사용할 수 있도록 adapter의 역할을 하는 react-ranger 패키지가 존재합니다.</p>
<h3 id="useranger-1">useRanger</h3>
<p>먼저 react-ranger 패키지 내부에 있는 useRanger hook을 살펴보겠습니다. </p>
<p>앞서 언급했듯이 코어 로직은 ranger 패키지에 담겨있기 때문에, useRanger는 Ranger Class와 react를 연결하는 단순 adapter의 역할만을 담당합니다.</p>
<p>아래는 useRanger hook의 코드입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/43afc2b6-19b8-49f9-bd71-c6c9fd4b479f/image.png" alt=""></p>
<p>hook의 전체 코드를 가지고 온 건데요. 굉장히 짧습니다. 앞서 말했듯 hook은 value의 변화에 따라 컴포넌트를 다시 그리는 역할만을 담당하며, 이를 위해 useReducer hook을 사용합니다. </p>
<p>모든 액션에 대해 새로운 객체를 반환함으로써 force update가 가능한 reducer를 선언해줍니다. (라인 12)</p>
<p>이후 유저가 전달한 옵션과 합쳐 22번째 라인에서 Ranger Instance를 만들어주면, onChange시마다 rerender가 되는 rangeInstance가 완성됩니다.(라인 13~22) </p>
<blockquote>
<p>react-ranger도 tantack의 다른 라이브러리들처럼 코어 로직을 바닐라js로 이관하며 다른 라이브러리도 지원하기 위한 준비를 하고 있습니다. 레거시 프로젝트는 <a href="https://github.com/uiforks/react-ranger">여기</a>서 확인하실 수 있습니다.</p>
</blockquote>
<h3 id="ranger-class">Ranger Class</h3>
<p>이제 코어 로직이 담겨있는 ranger class를 살펴보겠습니다. </p>
<h4 id="constructor">constructor</h4>
<p>우선, 생성자입니다. 앞서 useRanger hook에서 class를 생성해서 state에 담았었는데요. 생성자 내부에서 사용자가 전달한 option으로 rangerInstance를 생성합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/dc9adae6-cbd9-42bc-97fa-91f4d83de226/image.png" alt=""></p>
<p>이 때 사용자가 전달한 min, max, stepSize, values, onChange, onDrag등을 내부 변수로 관리됩니다.</p>
<h4 id="getticks">getTicks</h4>
<p>tick을 그리기위해 사용했던 getTicks 함수입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/d678f8f4-dcd0-4808-8a61-686b60ff2589/image.png" alt=""></p>
<p>getTicks 함수는 생각보다 간단합니다. step을 참조해서 tick을 배열의 형태로 생성해주는 역할을 합니다. (라인 9~16)</p>
<p>예를들어, min = 0, max = 100, step = 10의 값을 넘기면 [0,10,20,...100]까지 tick을 만들어서 내려줍니다.</p>
<p>반환값에서 사용되는 getPercentageForValue 함수도 살펴볼까요?</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/0eac860e-959a-4e84-b36b-52aee9947f80/image.png" alt=""></p>
<p>이 함수는 현재 value가 전체 길이에서 얼만큼의 비율에 위치해야하는지를 백분위로 내려줍니다. 
계산식은 (시작부터 현재위치까지의 길이 / 전체 길이) * 100이네요.</p>
<p>이 백분위 값은 tick의 상대적 위치를 잡는데에 사용됩니다.</p>
<h4 id="getsteps">getSteps</h4>
<p>두번째로 살펴볼 함수는 Segment의 정보를 반환해주는 getSteps입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/d19fb6a9-562f-4717-9864-b7a771974eb2/image.png" alt=""></p>
<p>이 함수는 현재 segment의 left와 width값이 담긴 객체의 배열을 반환합니다. Segment는 결국 handle의 위치에 따라 결정되기 때문에, handle의 값에 의존적인 값인데요.</p>
<p>만약 handle의 값이 [v1,v2,v3]이라고 가정해보면, segment는 총 4개로 나눠지며 구간은 s1: 0<del>v1, s2: v1</del>v2, s3: v3<del>v4, s4: v4</del>max가 됩니다.</p>
<p>이를 구하기 위해 handle의 위치 변수값인 values에 max를 추가한 배열([...values, options.map])을 돌며 각각의 left와 width를 계산해서 반환합니다.</p>
<h4 id="handle">handle</h4>
<p>마지막은 handle 함수입니다. handle 함수는 range slider 위의 handle(range button)들을 렌더하기 위해 사용됩니다.</p>
<p>handle은 값이 변할때마다 track을 리렌더시켜야합니다. 이 때 상태의 변화를 감지하고, 새로운 상태(위치)를 계산하기 위해서는 handle element에 적절한 핸들러가 부착되어 있어야합니다.</p>
<p>react-ranger는 데스크탑 환경과 모바일 환경, 키보드를 지원하기 때문에 drag시 handle의 위치를 추적할 수 있도록 onKeyDown, onMouseDown, onTouchStart 세개의 handler를 제공합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/c3fbacb7-f9df-4b37-a5d2-265eb54c4569/image.png" alt=""></p>
<p>values 배열을 돌며 현재 handle의 value와, 미리 정의된 핸들러들을  배열의 형태로 반환해줍니다. 
그럼 마지막으로 handlePress의 코드를 살펴보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/c091bba0-954b-4908-98e1-c57d258f0e3e/image.png" alt=""></p>
<p>handlePress는 onMouseDown 이벤트 핸들러로 사용되므로, 마우스가 내려간 시점에 실행됩니다. </p>
<p>마우스가 내려갔을때 mousemove 이벤트 핸들러를 달아주면 마우스가 움직이는 동안 handle 값을 추적할 수 있게 되는데요. 이 역할은 handleDrag 함수가 담당합니다(라인 24~25).</p>
<p>handleDrag 함수 내부에는 handle의 위치와 가장 가까운 step으로 값을 업데이트 해주는 로직이 들어가 있습니다.</p>
<p>이렇게 값을 계속 추적하다가 드래그가 종료되는 시점, 즉 mouseup event가 발생했을때 등록한 모든 이벤트를 해지하고 최종 값으로 업데이트를 해줍니다.(라인 21)</p>
<p>이를 통해 handle이 range slider 내부에서 좌우로 움직일때마다 계속 value를 갱신하며 가지고 있을 수 있게 됩니다.</p>
<h3 id="정리하며">정리하며</h3>
<p>Headless로 라이브러리를 제공하기 위해서는 설계 단계부터 다양한 측면을 고려해야 합니다. 기능은 추상화해서 제공하면서도 디자인 요소는 자유롭게 커스터마이징 가능하도록 설계되어야 하기 때문입니다.</p>
<p>그래서 기능과 UI가 많이 얽혀 있는 Ranger Slider의 경우 headless로 제공하기 어려울 것으로 생각했는데 UI를 작은 요소(segment, tick, handle등)들로 분리하고, UI의 관리 로직은 rangerInstance를 통해 추상화하여 제공하는 방식으로 해결한 점이 인상깊었습니다.</p>
<p>그리고 Headless 라이브러리들도 레이아웃 구조상 강제되는 스타일 코드들은 내부에 포함하는 경우를 많이 보았는데, 이를 라이브러리 내부에 추가하지 않고 가이드 문서를 통해 제공한 방식도 흥미로웠습니다. 막연하게 디자인 라이브러리는 import해서 바로 쓰면 사용할 수 있어야 한다는 생각을 가지고 있었던 것 같기도 합니다.</p>
<p>최근에 컴포넌트를 만들때 인터페이스 설계에 대해 고민이 깊어지고 있었는데 큰 인사이트를 얻어 기쁩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 문자열의 인코딩 & 디코딩]]></title>
            <link>https://velog.io/@seeh_h/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%9D%98-%EC%9D%B8%EC%BD%94%EB%94%A9-%EB%94%94%EC%BD%94%EB%94%A9</link>
            <guid>https://velog.io/@seeh_h/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AC%B8%EC%9E%90%EC%97%B4%EC%9D%98-%EC%9D%B8%EC%BD%94%EB%94%A9-%EB%94%94%EC%BD%94%EB%94%A9</guid>
            <pubDate>Tue, 23 May 2023 11:48:19 GMT</pubDate>
            <description><![CDATA[<p>자바스크립트 문자열은 다양한 나라의 언어를 표현할 수 있습니다. 자바스크립트는 수 많은 국가의 언어들을 어떻게 구분하고 저장할 수 있을까요?</p>
<p>이 의문을 해결하려면 자바스크립트가 내부적으로 문자열을 어떻게 표현하는지, 문자열의 인코딩과 디코딩 과정에 대한 이해가 필요합니다. </p>
<p>자바스크립트는 고수준 언어로 대부분의 경우 인코딩 &amp; 디코딩 과정을 내부적으로 처리해주고, 필요한 경우에도 간단한 메서드를 통해 처리가 가능하기 때문에 개발하며 신경 쓸 일이 많이 없는데요. </p>
<p>그래서 인코딩과 디코딩은 자주 사용하는 용어임에도 불구하고 막상 설명을 해보려 하면 쉽지 않은 주제인 것 같습니다. 이번 글에서는 인코딩 &amp; 디코딩이 무엇인지 관련 개념들과 함께 살펴보겠습니다.</p>
<h3 id="bit--byte">Bit &amp; Byte</h3>
<p>근원이 되는 bit와 byte부터 이야기하며 시작 해보겠습니다. 컴퓨터의 CPU(중앙 처리 장치), 메모리(Memory), 저장 장치(Storage) 등은 모두 bit를 사용하여 데이터를 처리합니다. </p>
<p>bit는 0과 1로 이루어져 있고, 8개의 bit 모여 1byte를 이루게 됩니다. 아래 그림과 같은 형태입니다.
<img src="https://velog.velcdn.com/images/seeh_h/post/115727c2-6a78-4b04-b479-f68de0dbf198/image.png" alt=""></p>
<p>즉 메모리에 올라가는 모든 데이터는 <strong>0과 1로 변환되어 저장</strong>되게 됩니다. 
아래와 같이 str이라는 변수에 문자열을 할당했다고 가정해봅시다.</p>
<p><code>const str = &#39;Hello world&#39;</code></p>
<p>&#39;Hello world&#39;라는 문자열을 저장해야하는데, 저장 되기 위해선 모든 알파벳도 0과 1로 변환이 되어야 할텐데요. </p>
<p>이 때 각 알파벳은 어떤 숫자로 변환되어야 할까요? <strong>알파벳 &lt;-&gt; 숫자를 상호간에 변환해주기 위해서는 &#39;기준&#39;이 필요합니다.</strong></p>
<h3 id="아스키코드">아스키코드</h3>
<p>이를 위해 아스키코드가 등장하게 됩니다. 아스키 코드는 1960년대에 제정되어서 초기 컴퓨터 시스템에서 문자를 표현하기 위해 사용되었습니다. </p>
<p>아스키코드는 7비트로 표현되어 128개의 문자를 표현할 수 있고, 알파벳과 일부 문자들로 구성됩니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/e4a0e931-517f-4d6d-8126-dc46f66e7d9d/image.png" alt=""></p>
<p>위 표를 보면 &#39;H&#39;는 72로 맵핑되어 있는것을 확인할 수 있습니다. 그러면 &#39;H&#39;는 이진수로 1001000로 변환되어 저장될 수 있겠네요. &#39;Hello world&#39;에 있는 다른 문자들도 각각 값으로 변환되어 저장될 수 있습니다.</p>
<p>컴퓨터가 처음 나왔을때는 주로 영어권 국가에서만 사용되었기 때문에, 아스키 코드를 통해 영어 알파벳을 포함한 몇가지 문자들만 표현 가능해도 사용하는데 무리가 없었습니다.</p>
<p>그러나 시간이 흘러 다른 언어를 사용하는 국가들도 컴퓨터를 사용하게 되면서 다양한 언어들이 표현될 필요성이 생기게 되었습니다. 처음에는 국가 별로 아스키 코드에 남는 128개의 공간에 자국의 언어를 넣어서 사용하거나, 아스키코드표와 같은 국가의 표준을 직접 정의하여 사용하기도 했습니다.</p>
<p>이 방식은 인터넷이 나오기 전까지는 꽤 유효하게 동작했지만 인터넷의 발전으로 다른 언어를 사용하는 페이지도 방문할 필요성이 생김에 따라 결국 국제적인 표준이 필요하게 되었습니다.</p>
<h3 id="유니코드">유니코드</h3>
<p>그래서 <a href="https://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C">유니코드</a>가 등장합니다. 유니코드란 모든 문자를 컴퓨터에서 일관되게 표현하고 다룰 수 있도록 설계된 산업 표준을 말합니다. 쉽게 말하면 이 세상에 존재하는 모든 나라의 언어를 특정 숫자로 맵핑시켜놓은 표라고 생각할 수 있습니다.</p>
<p>유니코드는 0000부터 10FFFF까지의 범위를 가지며, 약 114만개의 문자를 표현할 수 있습니다. 현재 웹에서 사용되는 대부분의 언어를 표현하고도 빈 공간이 많이 남아있다고 합니다. </p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/c179d560-5239-40e2-8067-02e153695256/image.png" alt=""></p>
<p>이렇듯 표현 가능한 문자들이 굉장히 많기 때문에 유니코드는 전체 공간을 17개로 나누어 용도별로 관리하고 있는데요. 각 공간을 &#39;평면&#39;이라고 부르고 있습니다.</p>
<p>한글을 포함한 기본 문자들은 대부분 0번 평면인 기본 다국어 평면(Basic Multilingual Plane)에 속해 있습니다. 한글은 0xAC00 ~ 0xD7A3 까지 차지하고 있습니다. 여기서 앞에 붙는 0x는 16진수임을 알려주는 prefix입니다.</p>
<p>아래는 유니코드 표를 보면 한글 &#39;안&#39;은 유니코드로 C548에 맵핑되어 있는것을 확인할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/c59d1ab1-5ddf-4ebb-a504-38427d703b75/image.png" alt=""></p>
<h3 id="인코딩--디코딩">인코딩 &amp; 디코딩</h3>
<p>지금까지 아스키코드와 유니코드의 등장 배경에 대해서 알아보았습니다. 그리고 컴퓨터는 내부적으로 0과 1로 데이터를 표현하기 때문에 문자열 또한 컴퓨터가 이해 가능한 이진수로 바꾸어서 저장해야 한다는 사실도 알게 되었습니다. </p>
<p>이렇게 <strong>문자열을 컴퓨터가 이해가능한 이진수로 바꾸는 과정을 인코딩, 반대로 이진수를 문자열로 바꾸는 과정을 디코딩</strong>이라고 합니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/0bcbfc1a-f392-46b8-961c-bb0bb4a8576c/image.png" alt=""></p>
<p>그러면 자바스크립트에서 모든 문자열들은 유니코드에 있는 값으로 변환되는걸까요? 아쉽게도 그렇지는 않습니다. </p>
<p>자바스크립트는 내부적으로 문자열을 유니코드를 이용한 인코딩 방식인 UTF-16 형식으로 인코딩하여 저장합니다.</p>
<blockquote>
<p><a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#utf-16_characters_unicode_code_points_and_grapheme_clusters">MDN</a> : <strong>Strings are represented fundamentally as sequences of UTF-16 code units.</strong> UTF-16 characters, Unicode code points, and grapheme clusters
Strings are represented fundamentally as sequences of UTF-16 code units. In UTF-16 encoding, every code unit is exact 16 bits long. This means there are a maximum of 216, or 65536 possible characters representable as single UTF-16 code units... (중략) </p>
</blockquote>
<blockquote>
<p><a href="https://tc39.es/ecma262/#sec-ecmascript-language-types-string-type">EcmaScript</a> : 
A String value is a member of the String type. <strong>Each integer value in the sequence usually represents a single 16-bit unit of UTF-16 text.</strong> However, ECMAScript does not place any restrictions or requirements on the values except that they must be 16-bit unsigned integers.</p>
</blockquote>
<p>UTF-16은 또 무엇일까요? 유니코드는 엄밀히 말하면 인코딩 방식은 아닙니다. 유니코드를 기반으로 변환하는 UTF-8, UTF-16과 같은 인코딩 방식이 존재합니다. </p>
<p>즉 유니코드의 코드 포인트 그대로 메모리에 저장하지는 않는다는 소리인데요. 이 코드 포인트 값을 기반으로 한번 더 변환해서 저장하여 사용합니다.</p>
<p>왜 유니코드의 값을 그대로 사용하지 않고 다른 값으로 한번 더 변환하여 사용할까요? 가장 큰 이유는 효율성 때문입니다. 앞서 유니코드는 문자를 0000부터 10FFFF 사이의 코드포인트에 할당한다고 말씀드렸는데요. </p>
<p>10FFFF를 10진수로 변환하면 약 114만개이고, 이를 이진수로 표현하기 위해서는 대략 2^21 &gt; 114만이므로 최소 21비트가 필요합니다.</p>
<p>따라서 유니코드 문자를 그대로 이용해서 처리하려면 24비트, 바이트로는 3바이트가 필요합니다. 하지만 컴퓨터의 구조상 홀수 바이트인 3바이트로 끊어 처리하는 것이 효율적이지 않기 때문에 빈 바이트를 0으로 채워 4바이트로 늘려서 처리하게 되는데요. </p>
<p>그럼 가장 사용 비중이 높은 영어의 입장에서는 기존의 ASCII를 사용했을때에 비해 저장 용량이 4배가 되어 굉장히 비효율적인 저장방식이 됩니다.</p>
<p>유니코드 초기에는 이러한 저장 방식(UCS-4)을 사용했지만, 문자열을 조금 효율적으로 표현하고 저장하기 위해 <strong>유니코드를 기반한 특정한 규칙으로 다시 변환하는</strong> 인코딩 방식인 UTF-8과, UTF-16이 등장했고 보다 더 보편적으로 사용되고 있습니다.</p>
<p>정리하면, <strong>유니코드는 각 글자에 숫자를 배당하는 방식이고 인코딩은 유니코드 숫자를 표현하고 저장하는 방식</strong>이라고 볼 수 있겠습니다.</p>
<p>각 인코딩이 어떤 규칙을 통해 문자를 byte로 변환하는지는 잘 설명해놓은 포스팅이 많기도 하고, 포스팅의 길이가 너무 길어질 것 같아 다루지 않겠습니다. 궁금하신 분들은 <a href="https://hongl.tistory.com/50">이 글</a>을 읽어보시길 추천드립니다.</p>
<h3 id="textencoder">TextEncoder</h3>
<p>인코딩 방식에 대해 알아보고 난 뒤 &#39;자바스크립트에서 인코딩과 디코딩을 직접 해볼 수 있을까?&#39;라는 궁금증이 들어 조금 더 찾아보았는데요. </p>
<p>자바스크립트에서도 문자열 인코딩과 디코딩을 도와주는 네이티브 객체인 <a href="https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder">TextEncoder</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder">TextDecoder</a> 객체가 존재합니다.</p>
<p>TextEncoder는 문자열을 바이트로 변환합니다. TextEncoder를 이용하여 &#39;안녕&#39;이란 문자열을 utf-8 인코딩된 바이트로 변환해보겠습니다. </p>
<p>UTF-8 인코딩 방식에서 한글은 글자당 3바이트를 차지합니다. &#39;안&#39;은 &#39;0xEC9588&#39;로, &#39;녕&#39;은 &#39;0xEB8595&#39;로 표현됩니다.</p>
<p>실제로 TextEncoder를 통해 변환해보며 제대로 된 값을 뱉어주는지 확인해보겠습니다.</p>
<pre><code class="language-javascript">const encoder = new TextEncoder(); // TextEncoder 객체 생성

const uint8Array = encoder.encode(&quot;안녕&quot;); // &#39;안녕&#39;이란 문자열을 utf-8 바이트로 변환
console.log(uint8Array); // [236, 149, 136, 235, 133, 149]</code></pre>
<p>얻은 uint8Array를 16진수로 변환해보면 0xEC, 0x95, 0x88, 0xEB, 0x85, 0x95이고, 3바이트씩 조합하면 &#39;0xEC9588&#39;, &#39;0xEB8595&#39;네요. 제대로 변환해 주는것을 확인했습니다.</p>
<p>아쉽게도 TextEncoder는 생성자에 특정 인코딩 방식을 전달할 수 없고, 오로지 utf-8의 byteArray로의 변환만을 지원합니다.</p>
<h3 id="textdecoder">TextDecoder</h3>
<p>이제 반대로 TextDecoder를 사용해서 바이트 정보를 문자열로 변환해보겠습니다. TextDecoder는 TextEncoder와는 다르게 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings">다양한 방식의 인코딩 데이터 input</a>을 지원하는데요. 예전에 자주 쓰였던 방식 중 하나인 EUC-KR 인코딩 방식을 이용해 바이트 데이터를 문자열로 바꿔보겠습니다. </p>
<p>EUC-KR 인코딩 방식에서 &#39;안&#39;은 &#39;0xBEC8&#39;, &#39;녕&#39;은 &#39;0xB3E7&#39;로 표현되는데요. 이를 byteArray로 표현하면 [190, 200, 179, 231]입니다.</p>
<pre><code class="language-javascript">
const uint8Array = new Uint8Array([190, 200, 179, 231]);

const decoder = new TextDecoder(&#39;euc-kr&#39;);
const decodedString = decoder.decode(uint8Array);

console.log(decodedString); // 안녕</code></pre>
<p>역시 제대로 된 결과값을 뱉어주는것을 확인할 수 있습니다.</p>
<h3 id="iconv-lite">iconv-lite</h3>
<p>iconv-lite는 인코딩 &amp; 디코딩 관련 기능을 제공해주는 라이브러리입니다. 앞서 말씀드렸듯이 TextEncoder는 utf-8로의 인코딩만 지원하는데요. 다른 방식으로 문자열을 인코딩 해야할 때 iconv-lite를 유용하게 사용할 수 있습니다.</p>
<p>사용법은 간단합니다.</p>
<pre><code class="language-javascript">import iconv from &#39;iconv-lite&#39;;

const string = &#39;안녕&#39;;

const encodedText = iconv.encode(string, &#39;euc-kr&#39;);
console.log(encodedText); // [190, 200, 179, 231]</code></pre>
<p>단점으로는 인코딩 변환 테이블을 라이브러리 내부에서 다 들고있기 때문에 번들 크기가 조금 커질 수 있습니다. (약 167.1k 정도네요 😅)</p>
<h3 id="마치며">마치며</h3>
<p>지금까지 자바스크립트의 인코딩과 디코딩에 과정에 대해 알아보았습니다. </p>
<p>사실 이 포스팅은 EUC-KR 형식으로 인코딩된 txt파일을 export하는 기능을 만들며 수많은 눈물을 흘린 후 작성되었는데요. 처음에는 브라우저에서의 변환이 불가능한줄 알았는데, 되긴 되더라구요. 사실 저도 엄청 많이 궁금하진 않았습니다.</p>
<p>혹시 틀린 내용이 있다면 댓글을 통해 말씀해주시면 감사하겠습니다🙇‍♂️</p>
<h3 id="reference">reference</h3>
<blockquote>
<p><a href="https://d2.naver.com/helloworld/19187">https://d2.naver.com/helloworld/19187</a>
<a href="https://brunch.co.kr/@font/179">https://brunch.co.kr/@font/179</a>
<a href="https://velog.io/@goggling/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C%EC%99%80-UTF-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0">https://velog.io/@goggling/%EC%9C%A0%EB%8B%88%EC%BD%94%EB%93%9C%EC%99%80-UTF-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[OAuth  원리 뜯어보기]]></title>
            <link>https://velog.io/@seeh_h/OAuth-%EC%9B%90%EB%A6%AC-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@seeh_h/OAuth-%EC%9B%90%EB%A6%AC-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Mon, 03 Apr 2023 12:00:02 GMT</pubDate>
            <description><![CDATA[<p>서비스 개발을 하다 보면 소셜 로그인 기능을 한 번쯤 구현해 보게 되는데요. 요즘에는 대부분의 서비스가 제공하는 것 같습니다.</p>
<p>이번 포스팅에서는 소셜 로그인의 근간이 되는 기술인 OAuth의 스펙에 대해 살펴보고, 직접 구현해 보며 OAuth에 대해 조금 더 이해해 보고자 합니다.</p>
<h3 id="oauth">OAuth</h3>
<p>OAuth란 무엇일까요? OAuth는 <a href="https://datatracker.ietf.org/doc/html/rfc6749">RFC 6749</a>에 정의된 표준으로 인터넷 사용자들이 다른 웹사이트를 접속할 때 비밀번호를 제공하지 않고 자신들의 정보에 대해 웹사이트에게 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는 개방형 표준 프로토콜입니다.</p>
<p>말이 좀 어려운데요. 쉽게 말하면 이용하려는 사이트에 로그인하기 위해 직접 비밀번호를 제공하는 대신, 특정 플랫폼의 계정(ex: 구글, 페이스북, 카카오)을 가지고 해당 서비스에 로그인하는 것을 말합니다. 요즘은 대부분의 서비스에서 제공하기 때문에 아래와 같은 소셜 로그인 화면을 쉽게 접할 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/476f925c-d350-437d-bf2b-e8d666fca23e/image.png" alt=""></p>
<p>그럼 소셜로그인은 어떤 과정을 통해 이루어질까요? </p>
<h3 id="oauth를-이용한-로그인-플로우">OAuth를 이용한 로그인 플로우</h3>
<p>OAuth에 대한 RFC 문서를 살펴보면 OAuth의 진행 플로우를 볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/3b708ba1-1470-47e7-8452-646961dae991/image.png" alt=""></p>
<p>(A)~(F)까지의 과정을 거쳐 유저는 로그인하게 되는데요. 각각의 과정에 대해 살펴보기 전에, 로그인 과정에서의 참여자와 그 역할에 대해 먼저 간단히 살펴보겠습니다. </p>
<p>문서에 따르면 OAuth 로그인 플로우 동작에 관여하는 참여자는 크게 네 가지로 구분됩니다.</p>
<blockquote>
<ul>
<li>Client : 카카오 인증 서버에 요청을 보내는 주체입니다. </li>
</ul>
</blockquote>
<ul>
<li>Resource Owner: 정보의 주인을 의미합니다.</li>
<li>Authorization Server: 인증을 관리하는 서버입니다. 이 곳에서 access token을 발급합니다.</li>
<li>Resource Server : 실제 유저 정보를 가지고 있는 서버입니다.</li>
</ul>
<p>실제 서버 구성 환경에 따라 인증 서버와 자원 서버는 분리되어 있지 않은 경우도 있기 때문에 Auth 서버와 Resource 서버를 하나로 합쳐서 설명하기도 합니다.</p>
<p>그럼 실제 로그인 상황에서 각 참여자는 누가 될 수 있을까요? 이해를 돕기 위해 유저가 Velog 서비스에서 카카오 로그인을 하는 상황이라고 가정하고 대입해보겠습니다.</p>
<blockquote>
<ul>
<li>Client : Velog 서비스</li>
</ul>
</blockquote>
<ul>
<li>Resource Owner : 로그인하려는 유저</li>
<li>Authorization Server : 카카오 인증 서버</li>
<li>Resource Server : 카카오 api 서버</li>
</ul>
<p>좀 더 이해가 되셨나요? </p>
<p>그럼 바로 이어서 OAuth flow의 (A)부터 (F)까지의 과정을 동일하게 카카오 로그인으로 대입해보겠습니다. </p>
<blockquote>
<ul>
<li>(A) : 클라이언트가 유저에게 로그인 요청(로그인 버튼 클릭과 같은 액션시 발생)</li>
</ul>
</blockquote>
<ul>
<li>(B) : 유저가 카카오 로그인 진행</li>
<li>(C)~(D) : 카카오 서버가 Access Token 발급</li>
<li>(E) : 카카오 서버에게 추가 유저 정보 요청(닉네임, 프로필 이미지 등)</li>
<li>(F) : 카카오 서버가 유저의 정보 응답</li>
</ul>
<p>이런 과정을 거쳐 유저는 로그인하게 됩니다. </p>
<h3 id="kakao-소셜-로그인-붙여보기">Kakao 소셜 로그인 붙여보기</h3>
<p>지금까지 OAuth를 이용한 로그인 플로우를 살펴보았습니다. 이제 카카오 로그인을 직접 구현해보며 OAuth 플로우와 비교해보겠습니다. </p>
<p><a href="https://developers.kakao.com/docs/latest/ko/kakaologin/common">Kakao Developers</a>를 보면 로그인을 할 수 있는 절차에 대해 3가지 Step으로 나누어 자세하게 설명하고 있습니다. </p>
<h4 id="step-1">Step 1</h4>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/272d0dd9-e2bc-443a-8c49-f7243db81ccf/image.png" alt=""></p>
<p>첫번째 단계는 유저가 로그인을 하고, Access Token을 받기 위한 인가 코드를 가져오는 과정입니다. 위의 OAuth flow에서는 (A)~(B) 단계가 됩니다.</p>
<p>위 이미지를 보면 앞서 OAuth Flow에서는 볼 수 없었던 Service Client와 Service Server가 등장하는데요. </p>
<p>이들은 쉽게 말해 서비스의 프론트와 백엔드라고 이해하시면 됩니다. 앞서 살펴본 OAuth flow에서는 Client로 통합되어 표현되어 있는 부분입니다.</p>
<p>구현에는 Service Client로 브라우저, Service Server로 Nextjs 13의 <a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers">Route Handler</a>(기존의 API route)를 이용해서 진행 하겠습니다.</p>
<p>우선 간단하게 로그인 페이지를 할 수 있는 페이지를 만들어 볼텐데요. 버튼을 누르면 카카오 로그인 페이지로 이동하는 간단한 페이지입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/71761f73-2ff4-43cf-b63a-3c273f333360/image.png" alt=""></p>
<p>실제 이동 경로는 브라우저 -&gt; next server -&gt; 카카오 로그인 페이지로 이동하게 됩니다. 브라우저에서 인가 코드를 요청하지 않고 웹 서버를 거치는 이유는 인가 코드를 브라우저에 노출하지 않기 위함입니다.</p>
<p>아래는 홈 컴포넌트의 코드입니다.
<img src="https://velog.velcdn.com/images/seeh_h/post/ce143000-b0ec-48d0-8a27-81eaec868d65/image.png" alt=""></p>
<p>로그인 버튼 클릭시 백엔드의 <code>/api/oauth/kakao</code> 경로로 로그인을 요청하고, 이동한 경로에서는 카카오 로그인 페이지로 redirect시켜줍니다. (라인 7~9)</p>
<p>이때 인가 code를 받을 수 있는 route 경로는 <code>/oauth/code</code>로 해줍니다. 유저가 카카오 로그인을 정상적으로 마치면 해당 경로로 access token을 얻을 수 있는 인가 코드를 넣어줍니다.</p>
<p>아래는 <code>/api/oauth/kakao</code> route handler 코드입니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/1725b049-578f-4d21-b717-54bbe9f1852e/image.png" alt=""></p>
<p>redirect후 유저는 로그인 페이지로 이동하게 되고, 아래와 같은 로그인 화면을 보게 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/69a8a6cf-f396-4148-9c7f-dcfacce981da/image.png" alt=""></p>
<p>약관 동의 페이지를 거쳐 가입을 하게되면, 앞서 입력한 Redirect_URI(<code>/oauth/code</code>)로 카카오에서 redirect를 시켜줍니다. </p>
<p>아래는 <code>/oauth/code</code> route handler의 일부입니다.
<img src="https://velog.velcdn.com/images/seeh_h/post/58423f9b-6b6d-406d-9838-e1e854299838/image.png" alt=""></p>
<p>url에서 인가 code를 받아줍니다. 이렇게 Step 1의 단계를 마쳤습니다.</p>
<h4 id="step-2">Step 2</h4>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/fd6f7466-5dbd-446c-bc0a-391f20373889/image.png" alt=""></p>
<p>두번째 단계는 code를 통해 access token을 얻어오는 단계인데요. OAuth flow에서는 (C)~(D) 과정입니다.</p>
<p>Step1에서  <code>/oauth/code</code> 핸들러에서 code를 받은 뒤부터 이어서 진행하겠습니다. 인가코드를 받아 왔으니 카카오 인증 서버에 접근해서 access token을 받아와줍시다. (라인5~6)</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/fc9af3d6-4976-44f1-aff9-950edbbb0253/image.png" alt=""></p>
<p>getKakaoToken 함수 구현부를 살펴봅시다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/f49793aa-be75-422f-9e99-7efaf33840d6/image.png" alt=""></p>
<p>특별한 건 없는데요. 인가 코드를 카카오 서버에서 정해준 스펙대로 보내주면 access token을 받아올 수 있습니다.</p>
<p>API 통신시 body를 Json.Stringify로 말아서 보내면 400에러를 던져줘서 찾아보니 이런 이슈가 있더라구요. (라인 12~24) <a href="https://devtalk.kakao.com/t/react-invalid-client-koe010/114139">여기</a>를 참고해서 해결했습니다.</p>
<h4 id="step-3">Step 3</h4>
<p>마지막 단계는 사용자 정보를 가져와서 로그인 처리를 완료하는 단계입니다. 앞 단계에서 카카오 인증 서버로부터 access token을 받아 왔는데요. 이 토큰을 이용해서 유저의 닉네임과 같은 개인 정보를 가져올 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/ca519568-394e-4d26-8266-066df2b4e12e/image.png" alt=""></p>
<p>유저 정보를 가져오기 위해 아래와 같이 kakaoAccountInfo 함수를 호출해줍시다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/c3f43d0e-1477-4df1-acb1-3ecad812a325/image.png" alt=""></p>
<p>getUserInfo 함수의 본문은 아래와 같은데요. <code>/v2/users/me</code>로 access token을 던지면 유저정보를 받아올 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/9f4e600d-3bee-4d43-8dd3-225dc33c5d97/image.png" alt=""></p>
<p>이렇게 유저 정보를 가져온 후 보통은 자사 인증 서버에서 자사 서비스에서 사용할 토큰으로 교환하는 과정을 거치게 되는데요. 이 과정을 진행하고 로그인 처리를 마무리 하겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/7693f3e7-e7de-4829-96b8-1e9fed53f439/image.png" alt=""></p>
<p>간단하게 토큰 교환 과정을 거친 후(라인 1~3), 쿠키로 accessToken, refreshToken을 심어주고 브라우저로 redirect 시켜줍니다(라인 6)</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/726aee69-fac4-43cf-a91e-a263c5825322/image.png" alt=""></p>
<p>이후 브라우저에서 쿠키를 확인해보면 정상적으로 토큰이 심어졌음을 확인할 수 있습니다.</p>
<blockquote>
<p>작성된 모든 코드는 <a href="https://github.com/Siihyun/oauth-practice">여기</a>서 확인하실 수 있습니다.</p>
</blockquote>
<h3 id="마치며">마치며</h3>
<p>소셜 로그인 기능은 각 플랫폼에서 sdk를 제공해주는 경우도 많고 사용할 수 있는 라이브러리의 폭도 넓은 편이라 바닥부터 직접 구현해 볼 일이 많이 없는 것 같습니다. </p>
<p>저도 웹에서 로그인을 구현할 때에는 Next-Auth라는 라이브러리를 주로 사용하곤 하는데요. 최근 로그인 관련 이슈를 해결하다 문득 라이브러리에 너무 의존하다 보니 OAuth에 대해 잘 알고있지 못하다는 생각이 들어 조사하며 포스팅을 작성하게 되었습니다. </p>
<p>이어지는 다음 포스팅에서는 Next-Auth 라이브러리를 뜯어보며 소셜 로그인 기능을 어떻게 추상화해서 제공해주는지 한번 살펴보고자 합니다.</p>
<p>긴 글 읽어주셔서 감사합니다 🙇‍♂️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[22년 회고]]></title>
            <link>https://velog.io/@seeh_h/22%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@seeh_h/22%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sat, 31 Dec 2022 17:05:09 GMT</pubDate>
            <description><![CDATA[<p>돌이켜보면 올해도 많은 일들이 있었다.</p>
<h1 id="bye-2022-👋">Bye, 2022 👋</h1>
<p>많은 배움을 얻은 회사를 떠나고, 새로운 회사에서의 도전을 시작했다. 많은 사람들을 만나고 다양한 경험을 했다. 진행한 사이드 프로젝트에서 기업과의 협업도 진행해보았다. 바쁜 시간을 쪼개서 운동도 시작했다. 맞이하게 된 많은 기회들과 그에 따른 배움에 감사할 수 있었던 한 해였다.</p>
<p>22년이 시작할때 크게 두가지의 목표를 세웠다. 하나는 개발자로서 더 성장하는 것, 두번째로는 개발 외적으로도 건강하게 몰입할 수 있는 취미를 찾는것이었다. </p>
<p>첫번째 목표를 위해 쿼터별 계획을 세워 공부하기, 블로그에 포스팅하기, 사이드 프로젝트 진행하기 등 목표를 세웠다. </p>
<p>두번째 목표를 위해서는 운동 시작하기, 취미 찾기의 목표를 세웠다. 목표했던 모든 것을 다 달성하지 못해서 아쉬움도 있지만 충분히 만족할만 한 한해를 보냈다.</p>
<h2 id="이직">이직</h2>
<p>전 회사에서는 기존 제품의 신규 버전을 개발했다. 운영중인 서비스의 코드를 분석하고 신규 스택으로 마이그레이션하는 일을 주 업무로 진행했다. 기존 코드가 엄청난 레거시 스택으로 구성되어 있어서 파악이 쉽지는 않았지만 좋은 동료들과 합을 맞춰가며 열정적으로 업무를 했던 것 같다. </p>
<p>아쉬운 점은 오롯이 개발에만 집중할 수 있는 환경은 아니었다는 것이다. 회사의 자금 상황과 팀 간 이해관계에 의해 기획이 전면 수정되고 일정이 지속적으로 딜레이가 되는 현상이 계속되었다. 지난주에 개발해놓은 코드가 이번주에 사라지는 경우가 많았고, 제품 방향성이 제대로 서지 않다 보니 점점 의미를 알 수 없는 회의도 많아져갔다. 이런 상황 속에서 점차 스스로 의욕이 떨어져가는것을 느꼈다.</p>
<p>이 때 즈음 새로운 회사에서 제안을 받았다. 기존에 관심있던 도메인은 아니었지만 대표님과 커피챗 끝에 지원을 결정했다. 커피챗 중 가장 마음에 들었던 부분은 대표님이 제시하는 제품에 대한 비전이었다. 저번 회사에서 가장 부족하다고 느꼈던 부분이라, 다음 회사를 갈때 가장 중요하게 보려고 했던 부분이었다.</p>
<p>일하는 방식도 마음에 들었다. 완벽한 것을 만드려고 하기 보다 빠르게 만들고 보완해 나가는 것을 선호하는데, 대표님과 이야기를 나눴을 때 업무 하는 방식이 나와 핏이 맞는 팀일 것 같다는 느낌을 받았다.</p>
<p>마지막으로 가서 하게 될 업무와 앞으로의 계획에 대해 들으니 기술적으로 경험하고 도전해 볼 수 있는 영역이 많아 보였다.</p>
<p>결과적으로 굉장히 만족한다. 확실히 지금 팀은 조금 더 빠르고, 유기적으로 움직인다는 느낌을 받는다. 그리고 &#39;나만 잘하면 되겠다&#39;라는 생각이 종종들만큼 실력있는 팀원들이 많다. </p>
<h2 id="긍정적인-영향-주기">긍정적인 영향 주기</h2>
<h3 id="포스팅">포스팅</h3>
<p>목표는 12개의 포스트를 작성하려고 했는데, 7개의 포스팅밖에 작성하지 못했다. </p>
<p>일을 하다보니 생각보다 한달이라는 주기가 생각보다 빠르게 찾아오기도 하고, 단순 학습 내용을 정리하는 것보다는 보는 사람들에게 어느정도의 인사이트를 제공할 수 있는 포스팅을 하고 싶었는데 그런 주제를 찾기가 쉽지가 않았다. 많이 부족함을 느꼈다.</p>
<p>올해는 포스팅 횟수에 초점을 맞추기 보단 조금 더 유의미한 글을 작성하는데에 집중해 보려고 한다.</p>
<h3 id="멘토링">멘토링</h3>
<p>올해에는 다양한 부트캠프에서 멘토로 활동했다. 기업 별로 미세한 차이는 있었지만, 기본적으로 멘토가 하는 역할은 크게 다르지 않았다. 부트캠프에서 처음 개발을 배우는 분들에게 프로젝트 진행 가이드를 드리는 것이다.</p>
<p>현업과 멘토링 활동을 병행하는 것이 쉽지 않을 때도 있지만 그래도 재미있다. 내가 가진 능력으로 타인에게 선한 영향력을 행사할 수 있다는게 보람차고 일상생활의 긍정적인 에너지로 이어지는 것 같다.</p>
<p>또, 멘토링을 하다보면 반강제적으로 많은 분들의 코드를 보게 되는데 이것도 은근 도움이 된다. 코드를 보며 어떤 의도로 이런 코드를 작성했을까 고민해보기도 하고, 어쩔때는 생각하지 못했던 구현 방식을 보며 관점을 넓힐 수 있는 것 같다.</p>
<p>질문에 답하기 위해 부족한 부분을 학습하며 스스로에게도 많은 도움이 되었다. (<del>그리고 조금의 부수입..</del>) 
기회가 된다면, 내년에도 계속 이어서 하고싶다.</p>
<h2 id="사이드-프로젝트">사이드 프로젝트</h2>
<p>운영중인 서비스 <a href="https://holaworld.io">Hola</a>에서 Udemy Korea와 협업을 진행했다. Hola 플랫폼을 이용해서 스터디를 구하시는 분들께 Udemy에서 제공한 무료 강좌 쿠폰을 제공하는 <a href="https://sturdy-dugout-e49.notion.site/Hola-Study-with-Me-5299159dd78e424181c8cdbf7d5be46c">프로모션</a>이었다. </p>
<p>총 2000명 이상의 유저가 프로모션에 참여했고 무료 강의를 얻었다. 내가 만든 플랫폼으로 경제가치를 만들어 냈다는 사실이 많이 뿌듯하고 기뻤다.</p>
<h2 id="운동">운동</h2>
<p>하반기부터 꾸준히 운동을 했다. 아직 헬린이이긴 해도 신체적으로 뿐만 아니라 정신적으로도 건강해지고 있음을 느낀다. 올해말의 회고에서 꾸준히 운동을 한 나를 칭찬할 수 있으면 좋겠다. 사실 가장 자신없는 목표다😅</p>
<h1 id="hi-2023👋">Hi, 2023👋</h1>
<p>하루 종일 책상에 앉아 내년의 목표에 대해 깊게 생각해보았다. 결국 고민 끝에 올해는 정량적인 목표는 정하지 않기로 했다. </p>
<p>작년에는 구체적인 목표를 세웠었는데, 원리에 대한 깊은 고민보다는 목표량을 달성하기 위한 공부를 하고 있다는 생각이 종종 들곤 했다. 아무래도 건강한 성장 방향이 아닌 것 같아서, 다른 방법으로 진행해보려고 한다.</p>
<p>그래서 올해는 부족한 부분을 찾아서 채워가는 공부를 할 예정이다. 업무를 진행하다 일정에 치여 우선 지나간 찜찜한 것들을 살펴보거나, 평소 자주 사용하는 라이브러리들의 내부 코드를 살펴보며 성장을 도모해 볼 예정이다.</p>
<p>올해가 마무리 되는 시점에는 어떤 글을 쓸 수 있을지, 어떤 사람이 되었을지 궁금하다. </p>
<blockquote>
<p>긴 회고 읽어주셔서 감사합니다 :) 모두들 행복한 2023년 되시길 기원합니다 🎉 </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹뷰 아이폰 상단 노치 대응하기(feat. safe area)]]></title>
            <link>https://velog.io/@seeh_h/%EC%9B%B9%EB%B7%B0-%EC%95%84%EC%9D%B4%ED%8F%B0-%EC%83%81%EB%8B%A8-%EB%85%B8%EC%B9%98-%EB%8C%80%EC%9D%91%ED%95%98%EA%B8%B0feat.-safe-area</link>
            <guid>https://velog.io/@seeh_h/%EC%9B%B9%EB%B7%B0-%EC%95%84%EC%9D%B4%ED%8F%B0-%EC%83%81%EB%8B%A8-%EB%85%B8%EC%B9%98-%EB%8C%80%EC%9D%91%ED%95%98%EA%B8%B0feat.-safe-area</guid>
            <pubDate>Sun, 23 Oct 2022 09:29:22 GMT</pubDate>
            <description><![CDATA[<p>최근 작업해 본 웹 뷰에 ScrollSpy UI를 적용해 보았다. 작업 후 아이폰 특정 기기에서 스크롤 높이가 정확하게 계산되지 않았는데, 상단 노치영역 높이를 고려하지 않은게 원인이었다.</p>
<p>해결 과정에서 아이폰은 Safe Area가 존재한다는 사실을 알게 되었다. 혹시나 나와 같이 원인을 모르고 헤매고 있을 누군가를 위하여 작성하는 글이다.</p>
<h2 id="scrollspy-ui">Scrollspy UI</h2>
<p>노치 영역에 대해 살펴보기 전에, ScrollSpy UI에 대해 먼저 알아보자.</p>
<p>ScrollSpy는 앱과 웹에서 모두 자주 사용되는 UI인데, 주로 페이지 상단에 위치해서 유저에게 현재 스크롤 위치에 대한 정보를 제공하는 역할을 한다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/24d803ba-c2d5-4734-b55f-f4ec4dc033a6/image.gif" alt=""></p>
<p>위 이미지에서 가장 위 네비게이션 바 영역에 주목하자.</p>
<p>보면 스크롤되는 영역을 감지해서 민트색 영역(Home 영역)과 하늘색 영역(About 영역)이 교차하는 순간 About으로 탑바 하이라이팅이 옮겨간다.</p>
<p>또한 각 탭들(Home, About, Work, Contact)을 클릭하면 해당 탭이 active되며 그 위치로 스크롤 포인터가 옮겨간다.</p>
<p>정리하면 ScrollSpy UI는 두 가지의 기능을 한다.</p>
<blockquote>
<ol>
<li>스크롤 되는 높이를 계산해서 위치에 해당하는 탭 하이라이팅하기</li>
<li>각 탭 클릭시 해당 위치로 이동시키기</li>
</ol>
</blockquote>
<p>해당 UI가 익숙치 않으신 분들을 위해 구체적인 작동 방식을 파악해 보실 수 있는 <a href="https://codepen.io/acarlie/pen/LKmORw">링크</a> 첨부합니다. 😀</p>
<h2 id="notch-영역이란">Notch 영역이란?</h2>
<p>다음으로는 노치영역에 대해서 알아보자.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/feadc279-a315-461d-b8c2-db8698069e70/image.png" alt=""></p>
<p>노치 영역은 아이폰 X부터 생겨난 화면 상하단의 영역으로, 위 사진에서 오른쪽 기기의 상하단 초록색 부분이다. IOS에서 별도로 해당 영역을 제외하지 않고 웹뷰 영역을 잡게되면 노치 영역 또한 웹뷰 영역으로 잡히게 된다.</p>
<p>따라서 웹뷰에서는 문제없이 노출되던 영역들도 노치 영역 때문에 가려지거나 짤려 보이는 현상이 발생할 수 있다. </p>
<h2 id="문제-상황--해결">문제 상황 &amp; 해결</h2>
<p>아래 이미지는 위에서 소개한 ScrollSpy UI중 탑바 부분만 가져온 것이다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/4d8d4710-0b6d-41e8-83c8-0a66b41095c5/image.png" alt=""></p>
<p>Home 영역을 보고 있는 상황에서 상단 탑바의 Work탭을 클릭하게 되면 해당 역역으로 이동해야만 한다. 해당 액션은 dom에서 해당 element를 잡아서 scrollIntoView 혹은 scrollTo 메서드를 사용하면 쉽게 구현할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/11e000c7-115b-45c7-aa20-2a4f02eb0f8b/image.png" alt=""></p>
<p>하지만 노치영역이 존재하는 IOS기기에서는 노치 영역도 웹뷰 영역으로 잡히게 되기 때문에, 해당 영역의 높이를 고려하지 않으면 정확한 위치로 이동되지 않는다. <strong>위 이미지 처럼 노치 높이만큼 화면을 위로 더 끌어올려줘야 한다.</strong> 동일한 이유로 스크롤 영역을 판별해서 액티브된 탭을 바꿔주는 위치 역시 정확하게 계산되지 않았다. </p>
<p>다행히도 노치 영역의 높이를 구해서 해결할 수 있었다. ios 11.2버전 이상에서는 safe area를 구할 수 있는 <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/env">env()</a> css 함수를 제공한다.</p>
<blockquote>
</blockquote>
<p>env(safe-area-inset-top) 
env(safe-area-inset-right)
env(safe-area-inset-bottom)
env(safe-area-inset-left)</p>
<p>노치 영역을 위한 padding이나 margin 영역을 줘야할 경우, 위와 같이  css에 env 속성을 이용해서 적용할 수 있다. 노치 영역이 없는 경우 0을 반환한다.</p>
<p>하지만 위의 경우는 특정 탭 클릭 시 ScrollTop을 통해 높이를 직접 설정해줘야했기 때문에 노치 영역의 px값을 javascript로 받아와야만 했다.</p>
<p>이런 경우에는 safe-area 영역값을 css 전역변수로 정의하면 getComputedStyle을 통해 받아올 수 있다.</p>
<blockquote>
<p>💡 <a href="https://benfrain.com/how-to-get-the-value-of-phone-notches-environment-variables-env-in-javascript-from-css/">해당 글</a>의 코드를 참조하여 구현하였습니다.</p>
</blockquote>
<p>우선 global style 변수를 아래와 같이 선언해준 후,</p>
<pre><code class="language-css">:root {
    --sat: env(safe-area-inset-top);
    --sar: env(safe-area-inset-right);
    --sab: env(safe-area-inset-bottom);
    --sal: env(safe-area-inset-left);
  }</code></pre>
<p>필요한 곳에서 해당 값을 이용해 변수로 받아온다.</p>
<pre><code class="language-jsx">const notchHeight = getComputedStyle(
    document.documentElement,
  ).getPropertyValue(&quot;--sat&quot;);</code></pre>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/9e388a77-0e92-4622-88cd-42a1b8ee458a/image.png" alt=""></p>
<p>ios simulator를 통해 웹뷰를 띄우고 콘솔을 띄워보면 47px이 출력된 것을 확인할 수 있다.  </p>
<p>마지막으로 프로젝트에 적용된 전체 코드를 살펴보자.</p>
<pre><code class="language-jsx">const notchHeight = getComputedStyle(
    document.documentElement,
  ).getPropertyValue(&quot;--sat&quot;);

  const offset = 100 + Number(notchHeight.substring(0, notchHeight.length - 2));

  const handleTabClick = (tab: string) =&gt; {
    window.scrollTo({
      top:
        refs.current[tabList.findIndex(element =&gt; element === tab)].offsetTop -
        offset,
      behavior: &quot;smooth&quot;,
    });
  };</code></pre>
<p>각 탭 클릭시 노치 높이 + 탑바 높이 만큼 화면을 더 올려줘서 정확한 위치를 잡아줄 수 있었다.</p>
<h2 id="마치며">마치며</h2>
<p>웹뷰 작업을 하다보니 아무래도 스크롤 이벤트를 자주 다루게 된다. 특히 최근에 작업한 뷰에서는 특정 element 위치와 현재 스크롤 위치를 비교하며 처리해야 하는 로직이 많았다. </p>
<p>스크롤 이벤트와 ref의 조합으로 처리했는데 코드가 좀 지저분해 지는 것 같다. 더 개선할 수 있을지 한번 고민해봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Suspense의 동작 원리]]></title>
            <link>https://velog.io/@seeh_h/suspense%EC%9D%98-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</link>
            <guid>https://velog.io/@seeh_h/suspense%EC%9D%98-%EB%8F%99%EC%9E%91%EC%9B%90%EB%A6%AC</guid>
            <pubDate>Thu, 29 Sep 2022 14:11:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/seeh_h/post/eabee579-2bff-484c-8380-2e5d36cc12fa/image.png" alt=""></p>
<p>이번 포스팅에서는 React v18에서 정식 기능으로 릴리즈 된 Suspense에 대해 알아보자.</p>
<p>React v18이 3월 말에 릴리즈가 되었으니 포스팅이 조금 늦은 감이 있지만.. 안하는것 보다는 낫지 뭐 ^0^</p>
<h3 id="기존의-suspense">기존의 Suspense</h3>
<p>Suspense는 React v16에 처음 등장해서 v18 정식 릴리즈 전까지 실험적인(experimental) 기능으로서 존재했다.</p>
<p>18버전 이전의 Suspense는 서브 트리의 일부 구성 요소가 아직 렌더링할 준비가 되지 않은 경우 fallback UI를 지정할 수 있었다. 이러한 특성 때문에 <a href="https://reactjs.org/docs/code-splitting.html">code spliting</a>에 많이 적용되었다. </p>
<pre><code class="language-jsx">
const OtherComponent = React.lazy(() =&gt; import(&#39;./OtherComponent&#39;));

function MyComponent() {
  return (
    // 해당 번들 파일이 다운로드 되어 로딩이 완료될때까지 Spinner 노출
    &lt;React.Suspense fallback={&lt;Spinner /&gt;}&gt;
      &lt;div&gt;
        &lt;OtherComponent /&gt;
      &lt;/div&gt;
    &lt;/React.Suspense&gt;
  );
}</code></pre>
<p>위와 같이 import에 React.lazy와 Suspense를 함께 적용하면, 번들 파일을 페이지 단위로 나누어 필요한 코드만 내려받을 수 있다. </p>
<p>기존의 Suspense는 <strong>코드를 Lazy Loading 하는데에</strong> 목적이 있었다.</p>
<h3 id="신규-suspense">신규 Suspense</h3>
<p>18버전 부터는 이러한 기능이 data fetching에도 확대 적용되었다. 이제는 코드 스플리팅 뿐만 아니라 API의 결과값을 기다리는데도 사용할 수 있다. </p>
<p>코드를 통해 바로 살펴보자.</p>
<pre><code class="language-jsx">const resource = fetchProfileData();

function ProfilePage() {
  return (
    &lt;Suspense fallback={&lt;h1&gt;Loading profile...&lt;/h1&gt;}&gt;
      &lt;ProfileDetails /&gt;
      &lt;Suspense fallback={&lt;h1&gt;Loading posts...&lt;/h1&gt;}&gt;
        &lt;ProfileTimeline /&gt;
      &lt;/Suspense&gt;
    &lt;/Suspense&gt;
  );
}

function ProfileDetails() {
  // Try to read user info, although it might not have loaded yet
  const user = resource.user.read();
  return &lt;h1&gt;{user.name}&lt;/h1&gt;;
}

function ProfileTimeline() {
  // Try to read posts, although they might not have loaded yet
  const posts = resource.posts.read();
  return (
    &lt;ul&gt;
      {posts.map(post =&gt; (
        &lt;li key={post.id}&gt;{post.text}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  );
}</code></pre>
<blockquote>
<p>출처 : <a href="https://17.reactjs.org/docs/concurrent-mode-suspense.html">react 공식 문서</a></p>
</blockquote>
<p>Suspense로 비동기 통신이 있는 컴포넌트로 감싸주고, data fetching이 완료되기 전에 보여줄 fallback UI를 전달한다.</p>
<p>children 컴포넌트인 ProfileDetails, ProfileTimeLine는 항상 데이터가 준비된 시점에 렌더되기 때문에 컴포넌트 내부에서 비동기 통신의 완료 여부를 체크하지 않아도 된다.</p>
<p>이렇게 <strong>비동기 처리에 대한 책임을 Suspense에 위임</strong>함으로써 컴포넌트 내부에서는 본 로직에만 집중할 수 있다. 로딩 상태를 분기하는 코드가 빠진만큼 코드도 더 짧아지고, 코드를 더 <strong>선언적</strong>으로 작성할 수 있다는 장점도 있다.</p>
<h3 id="suspense-원리와-직접-구현해보기">Suspense 원리와 직접 구현해보기</h3>
<p>지금까지 Suspense의 추가된 기능과 적용하는 방법에 대해 살펴보았으니, 이제부터는 내부 동작 Suspense의 원리에 대해 더 파헤쳐보자.</p>
<p>앞서 이야기했듯 Suspense에서는 비동기 상태에 따라 fallback UI 혹은 children 컴포넌트를 띄워준다.</p>
<p>이는 곧 Suspense는 하위 children 컴포넌트들의 비동기 상태를 감지할 수 있다는 소리인데, 어떻게 알 수 있는걸까? </p>
<p>핵심은 하위 컴포넌트에서 <strong>Promise를 throw 해주는 것</strong>이다. </p>
<p>아래 코드는 React 코어 팀에서 Suspense로 비동기를 감지하는 과정 설명을 위해 작성한 컨셉 코드이다. </p>
<p>컨셉 코드이므로 실제의 구현에서 바로 쓰기에는 무리가 있지만, 아래의 wrapPromise 함수를 보면 children 컴포넌트에서 어떻게 parent 컴포넌트인 Suspense와 소통하는지에 대한 힌트를 얻을 수 있다.</p>
<pre><code class="language-jsx">function wrapPromise(promise) {
  let status = &quot;pending&quot;;
  let response;

  const suspender = promise.then(
    (res) =&gt; {
      status = &quot;success&quot;;
      response = res;
    },
    (err) =&gt; {
      status = &quot;error&quot;;
      response = err;
    }
  );

  const read = () =&gt; {
    switch (status) {
      case &quot;pending&quot;:
        throw suspender;
      case &quot;error&quot;:
        throw response;
      default:
        return response;
    }
  };

  return { read };
}

export default wrapPromise;
</code></pre>
<p>wrapPromise는 promise를 한번 감싸서 promise가 &quot;pending&quot; 혹은 &quot;error&quot; 상태이면 상위로 throw하고, 데이터가 준비된 시점에는 response를 return한다.</p>
<p>이렇게 promise 상태에 따라 상위로 throw함으로써 상위에 존재하는 Suspense, ErrorBoundary 컴포넌트와 커뮤니케이션 할 수 있는 것이다. </p>
<p>promise를 throw하다니 정말 엄청난 발상의 전환인 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/137896c6-3398-44a8-ba59-c30094b64787/image.png" alt=""></p>
<p>그러면 실제 비동기 통신 코드에 wrapPromise 함수를 적용해보자.</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;
import wrapPromise from &quot;./wrapPromise&quot;;

const fetchData = (url) =&gt; {
  const promise = fetch(url)
    .then((res) =&gt; res.json())
    .then((res) =&gt; res.data);

  return wrapPromise(promise);
}

const resource = fetchData(
  &quot;https://run.mocky.io/v3/d6ac91ac-6dab-4ff0-a08e-9348d7deed51&quot;
);

const UserWelcome = () =&gt; {
  const userDetails = resource.read();

  return (
    &lt;div&gt;
      &lt;div&gt;Fetch completed.&lt;/div&gt;
      &lt;div&gt;
        Welcome &lt;span className=&quot;user-name&quot;&gt;{userDetails.name}&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

export default UserWelcome;
</code></pre>
<p>UserWelcome 컴포넌트는 매번 렌더될때 마다 read 함수를 통해 결과값을 읽으려고 시도한다. </p>
<p>그럼 wrapPromise 함수에서는 &quot;pending&quot; 혹은 &quot;error&quot; 상태인 경우 상위의 Suspense 혹은 ErrorBoundary로 해당 promise를 throw하고, 만약 정상 종료된 &quot;fulfilled&quot; 상태인 경우 정상 UI를 보여준다. </p>
<p>이제 하위 컴포넌트와 상위 컴포넌트가 어떻게 의사소통을 하는지는 알아냈다. 마지막으로 Suspense는 어떻게 throw 상태에 따라 UI를 결정해서 내려줄 수 있는지 살펴보면 모든 비밀이 풀릴 것 같다.</p>
<p>Suspense 코드를 살펴보자. 구현을 위해서는 throw된 promise를 Suspense 내에서 catch해야 하기 때문에 class 컴포넌트를 사용했다.</p>
<pre><code class="language-jsx">import React from &quot;react&quot;;

export interface SuspenseProps {
  fallback: React.ReactNode;
}

interface SuspenseState {
  pending: boolean;
  error?: any;
}

function isPromise(i: any): i is Promise&lt;any&gt; {
  return i &amp;&amp; typeof i.then === &quot;function&quot;;
}

export default class Suspense extends React.Component&lt;
  SuspenseProps,
  SuspenseState
&gt; {
  public state: SuspenseState = {
    pending: false
  };

  public componentDidCatch(catchedPromise: any) {
    if (isPromise(catchedPromise)) {
      this.setState({ pending: true });

      catchedPromise
        .then(() =&gt; {
          this.setState({ pending: false });
        })
        .catch((err) =&gt; {
          this.setState({ error: err || new Error(&quot;Suspense Error&quot;) });
        });
    } else {
      throw catchedPromise;
    }
  }

  public componentDidUpdate() {
    if (this.state.pending &amp;&amp; this.state.error) {
      throw this.state.error;
    }
  }

  public render() {
    return this.state.pending ? this.props.fallback : this.props.children;
  }
}

</code></pre>
<p>Suspense 컴포넌트는 내부적으로 pending 여부를 판단하는 state를 갖는다. </p>
<p>이후 componentDidCatch 메서드를 통해 throw된 promise가 error 상태면 상위 ErrorBoundary 컴포넌트로 다시 throw하고, pending의 경우 fallback UI를 렌더하고, fulfilled 상태가 되면 children 컴포넌트를 렌더한다.</p>
<blockquote>
<p>전체 코드는 <a href="https://codesandbox.io/s/react-custom-suspense-s6m8in">여기</a>서 확인하실 수 있습니다 :)</p>
</blockquote>
<h3 id="후기">후기</h3>
<p>이렇게 Suspense의 비밀에 대해서 알아보았다. 항상(<del>사실 항상은 아님</del>) Suspense를 사용하며 어떻게 내부 컴포넌트들의 promise 상태를 감지하는건지 궁금했었는데 이번 포스팅을 계기로 또 조금 더 알게 되었다.</p>
<p>요즘 프레임워크나 라이브러리들이 제공해주는 magical한 기능 뒤편에 숨어있는 로직들을 살펴보는데 흥미가 있다. 추상화되어 마법처럼 제공되는 멋진 기능들의 뒷 단에 짜여진 복잡한 로직들을 살펴보다보면 오히려 더 마법처럼 느껴진다. 결국 자바스크립트를 더 잘해야겠다는 생각을 많이 하게 된다.</p>
<p>언젠가 이런 멋진 로직들을 직접 구현하는 사람이 될 수 있을까? 잠시 생각해 봤는데 안될 것 같아서 적어도 원리를 알고 쓰는 사람은 되어야겠다 생각하며 포스팅을 마친다. 끝!</p>
<h3 id="레퍼런스">레퍼런스</h3>
<p>이 포스팅은 해당 글을 참고하여 작성하였습니다.</p>
<blockquote>
<p><a href="https://blog.logrocket.com/react-suspense-data-fetching/">https://blog.logrocket.com/react-suspense-data-fetching/</a>
<a href="https://tech.kakaopay.com/post/react-query-2/">https://tech.kakaopay.com/post/react-query-2/</a>
<a href="https://maxkim-j.github.io/posts/suspense-argibraic-effect/">https://maxkim-j.github.io/posts/suspense-argibraic-effect/</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[BFF(BackEnd-For-FrontEnd)란?]]></title>
            <link>https://velog.io/@seeh_h/BFF%EB%9E%80</link>
            <guid>https://velog.io/@seeh_h/BFF%EB%9E%80</guid>
            <pubDate>Thu, 30 Jun 2022 07:26:18 GMT</pubDate>
            <description><![CDATA[<p>올해 초 즈음부터 여러 기업의 테크 블로그에서 BFF(Backend-For-Frontend)에 관련된 글을 많이 읽었다. BFF 개념과 용도에 대해 알게 되었지만, MSA 환경에서 개발 경험이 없다 보니 필요성에 대한 공감은 크게 하지 못하고 있었다.</p>
<p>최근 회사에서 MSA 구조를 채택하여 신규 제품을 개발하고 있다. 개발을 진행하면서 BFF의 필요성을 느끼고 도입을 하게 되었는데 작업하며 느낀 점을 공유하고자 포스팅을 작성하게 되었다.</p>
<br>

<h3 id="msamicroservice-architecture">MSA(MicroService Architecture)</h3>
<p>본격적으로 BFF 이야기를 하기 전에 MSA로 이야기를 시작해야 할 것 같다. MSA는 Microservice Architecture의 약자로 독립적인 배포가 가능한 서비스들로 구성된 아키텍처라는 의미를 갖는다. </p>
<p>기존에는 모든 서비스가 한 곳에 모인 Monolithic한 구조로 개발을 진행하는 경우가 많았지만, 서비스의 규모가 커지면서 Monolithic한 구조로는 해결이 어려운 문제들에 직면하게 되고 이를 해결하기 위해 MSA로 서비스를 전환하거나 전환을 시도하는 곳이 늘어나고 있다. </p>
<p>최근에는 설계 시점부터 MSA를 채택하는 곳도 많은 것 같다. (<del>큰 X vs 작은 X..?</del>)</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/326e88b2-a490-4601-852f-6a615756b1e1/image.png" alt=""></p>
<p>MSA에서는 각 서비스를 도메인별로 분리시킨다. 서비스는 작은 서비스의 집합으로 구성되며 각 서비스는 독립적이고 단일 비즈니스 기능에 대한 책임만을 가진다. </p>
<p>이를 프론트엔드 관점에서 살펴보자. 프론트엔드의 궁극적인 업무는 화면을 그리는 일이다. MSA 구조에서는 각 서비스가 기능별로 흩어져 있으므로 화면을 완성하기 위해 호출해야 하는 서비스도 늘어나게 된다. </p>
<p>따라서 화면을 그리기 위해서는 다수의 서비스에 연동을 해야하며, <strong>여러 서비스에 분산되어 있는 데이터를 가져와서 적절히 합쳐야 하는 경우</strong>도 발생하게 된다.</p>
<br>

<h3 id="bffbackend-for-frontend❓">BFF(BackEnd For FrontEnd)❓</h3>
<p>이를 돕기 위해 BFF가 등장하게 되었다. BFF란 BackEnd For FrontEnd의 약자인데, 말 그대로 프론트엔드를 위한 백엔드 서버를 의미한다. 프론트엔드를 요구사항에 맞게 구현하기 위한 도움을 주는 보조 서버 정도로 정의할 수 있을 것 같다.</p>
<p>BFF의 필요성을 살펴보기 위해 MSA 구조에서 이커머스 서비스를 만든다고 가정해보자.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/bb2db81a-a3b5-46ee-9676-bc54713709f5/image.png" alt=""></p>
<p>MSA답게 고객, 주문, 물건, 장바구니 등 커머스 도메인 별로 나뉘어 서비스가 구현되어 있다. 각 서비스는 프론트엔드에서 사용하는 API를 제공한다.</p>
<p>얼핏 보면 기능별로 나뉘어져 서비스가 잘 분리되어 있어 문제가 될만한 점이 보이지 않을 수 있으나, 실제로 개발을 진행하다 보면 여러 고민들에 봉착하게 된다. </p>
<h4 id="하나의-view기능를-완성하기-위해-여러-도메인의-api-응답값을-조작해야-하는-경우">하나의 view(기능)를 완성하기 위해 여러 도메인의 API 응답값을 조작해야 하는 경우</h4>
<p>이를테면, 고객의 등급을 조회하는 페이지에서 등급 업그레이드까지 남은 주문 금액을 디스플레이 하기 위해 주문 데이터를 살펴보아야 할 수도 있다. 혹은 특정 주문 데이터의 결제 현황을 조회하기 위해 결제 서비스를 호출해야 하는 경우도 있을 수 있다. 즉 원하는 응답값을 얻기 위해 여러 서비스를 호출해야 하는 상황이다.</p>
<h4 id="데이터를-전송하는-과정에서-불필요한-데이터를-숨겨야-하는-경우">데이터를 전송하는 과정에서 불필요한 데이터를 숨겨야 하는 경우</h4>
<p>결제 서비스를 이용하기 위한 유저별 Secret Key와 같은 비공개성 정보들이 존재할 수 있다. 브라우저를 통한 네트워크 통신은 항상 위험에 노출되어 있기 때문에, 해당 정보를 보관하고 결제 서비스 요청 시 심어주기 위한 인터페이스 서버가 필요한 상황이다.</p>
<h4 id="프론트엔드에서-많은-양의-연산을-요구하는-작업을-진행해야-하는-경우">프론트엔드에서 많은 양의 연산을 요구하는 작업을 진행해야 하는 경우</h4>
<p>주문 정보 엑셀 다운로드와 같이 브라우저의 자원을 많이 소모하거나 고객 정보 일괄 수정 등 다수의 네트워크 통신을 유발하는 작업을 진행해야 하는 경우가 있을 수 있다. 브라우저에서 복잡한 연산을 수행하는 경우 렌더링 성능에 악영향을 끼칠 수 있기 때문에 역시 개선이 필요한 상황이다.</p>
<br>

<h3 id="bff-너❓">BFF, 너...❓</h3>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/2a2b0824-f22a-4386-8750-18ab305505a0/image.png" alt=""></p>
<p>이런 경우 BFF가 좋은 해결책이 될 수 있다. BFF를 두는 목적은 프론트엔드의 로직을 중간 레이어를 두어 BFF에 위임하는 것이다. 이로써 프론트엔드는 구체적인 구현을 내용을 감추고 추상화된 API를 사용할 수 있게된다. 결과적으로 프론트엔드에는 더 적은 로직이 남게 된다. </p>
<p>그럼 지금까지 나온 내용을 토대로 BFF의 장점을 나열해보자.</p>
<blockquote>
<ul>
<li>프론트 엔드에서 복잡한 로직을 감추고 추상화된 인터페이스를 사용 가능하다.</li>
</ul>
</blockquote>
<ul>
<li>연동하는 백엔드 API의 인터페이스가 변경되는 경우 BFF에서 수정사항을 반영하면 되므로 프론트엔드는 관심사를 분리하여 본연의 비즈니스 로직에 집중할 수 있다.</li>
<li>데이터를 전송하는 과정에서 민감하거나 불필요한 데이터는 숨길 수 있다.</li>
</ul>
<h3 id="마치며">마치며</h3>
<p>위에서 언급한 이슈들은 실제로 개발하며 겪었던 사례들을 약간 각색하여 적어놓은 것이다. 처음에는 클라이언트 단에서 모든 로직이 구현되어 있었지만 BFF 도입 후 해당 고민들을 상당수 해결할 수 있었다.</p>
<p>그냥 마치기는 아쉬워서 어떤 경우에 BFF 도입을 고민하면 좋을지 나름대로 적어보았다.</p>
<blockquote>
<p>💡 BFF 도입을 고려해봐도 좋은 경우</p>
</blockquote>
<ul>
<li>하나의 view(기능)를 완성하기 위해 여러 도메인의 API 응답값을 조작해야 하는 경우</li>
<li>데이터를 전송하는 과정에서 불필요한 데이터를 숨겨야 하는 경우</li>
<li>프론트엔드에서 많은 양의 연산을 요구하는 작업을 진행해야 하는 경우</li>
<li>Open API 연동시 제공하는 API의 조합으로 특정 기능을 완성해야 하는경우 </li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[📘 기록 남기기 - NextJS 배포 POC]]></title>
            <link>https://velog.io/@seeh_h/NextJsDeploy</link>
            <guid>https://velog.io/@seeh_h/NextJsDeploy</guid>
            <pubDate>Mon, 16 May 2022 11:28:31 GMT</pubDate>
            <description><![CDATA[<p>최근 회사에서 디자이너 분들과의 원활한 협업을 목적으로 스테이징 배포 환경을 임시로 구성하게 되었다. 배포 플랫폼을 선택하는 과정에서 여러 플랫폼을 사용해보며 POC를 진행했는데, 기록 차원에서 포스팅으로 남겨두기로 했다!</p>
<h2 id="nextjs-배포-환경">NextJS 배포 환경</h2>
<p>NextJS는 SSR을 지원하기 때문에 배포시 서버가 필요하다. 서버 환경을 구성하는 방법은 다양하지만 결국 서버 환경에 따라 아래와 같은 두가지로 나눌 수 있다.</p>
<blockquote>
</blockquote>
<ul>
<li>클라우드(AWS, Azure등) 환경에서 직접 서버 환경 구성</li>
<li>Vercel, Netlify, Amplify, Cloudflare, Netlify, Serverless등과 같은 zero configration 배포 플렛폼 사용</li>
</ul>
<p>이번 포스팅에서는 배포 환경을 구성하며 검토했던 기술들과 도입시 고려사항에 대해 간단히 정리해보려 한다.</p>
<h2 id="플랫폼-별-살펴보기">플랫폼 별 살펴보기</h2>
<h3 id="vercel">Vercel</h3>
<p>가장 먼저 살펴본 선택지는 Vercel이다. Vercel의 가장 큰 장점은 역시 NextJS에서 제공하는 모든 기능 + 앞으로 추가될 기능을 가장 먼저, 가장 완벽하게 지원한다는 것이다. </p>
<p>zero configuration을 지향하는 Vercel답게 클릭 몇번으로 간단히 배포를 할 수 있다는 것도 큰 장점이다.</p>
<p>또 별도의 구성 없이 모든 push에 대한 배포가 진행되는 <a href="https://vercel.com/docs/concepts/deployments/environments#preview">preview</a> 기능을 제공하는데, 이게 상당히 편리하다. 
Vercel에 Github Repository를 연결 하고 해당 원격 저장소에 push를 하면 배포된 화면을 확인할 수 있는 preview url이 발급된다. 이런 방식으로 매 커밋에 대한 배포 결과물을 확인해볼 수 있다. </p>
<p>더 놀라운 점은 해당 기능은 무료 요금제인 Hobby 플랜에서도 지원한다는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/ff936937-6679-45f5-a499-e2b36fa9f6f9/image.png" alt=""></p>
<p>개인 프로젝트에서는 Vercel이 최고인 것 같다. 하지만 아쉽게도 Github Team 계정 레포의 경우 Pro 플랜을 이용해야 하기 때문에, Vercel에 연결하기 위해 1인당 20달러가 필요하다. </p>
<h3 id="amplify">Amplify</h3>
<p>Amplify는 AWS에서 제공하는 서비스로 AWS내의 리소스들을 사용하여 손쉽게 프론트엔드, 백엔드, 호스팅, 배포까지 풀스택으로 애플리케이션을 개발하도록 도와주는 완전 관리형 서비스이다. Vercel과 비슷한 컨셉에 AWS 서비스라고 볼 수 있을 것 같다. </p>
<p>현 시점에서(22년 7월) Vercel을 제외하고 NextJS의 가장 많은 기능을 사용할 수 있다. NextJS 12버전의 middleware, i18n 관련 피처를 제외한 모든 기능을 지원한다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/665cc407-d44c-40cd-84b9-5f7e42a944d6/image.png" alt=""></p>
<p>단점으로는 배포 과정이 Vercel에 비해 까다롭다. IAM 설정부터 Amplify CLI 설정까지 추가적으로 설정해 주어야 할게 꽤 많지만, 비용 측면에서는 Vercel보다 더 저렴하다.</p>
<p>궁금한 분들은 잘 정리해 놓은 <a href="https://velog.io/@mimi0905/AWS-Amplify%EB%A1%9C-next-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0SSR#amplify-%ED%98%B8%EC%8A%A4%ED%8C%85-%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0%ED%94%84%EB%A1%A0%ED%8A%B8-%EB%B0%B0%ED%8F%AC">블로그</a>가 있으니 참조해보시길!</p>
<h3 id="aws-elastic-beanstalk">AWS Elastic BeanStalk</h3>
<p>AWS의 Elastic BeanStalk(이하 EB) 서비스를 이용하여 서버 환경을 직접 구성할수도 있다. 물론 EC2에서 직접 서버 환경을 구성할 수도 있으나, EB의 장점은 로드 밸런싱 및 Auto Scaling부터 시작하여 애플리케이션 상태 모니터링, 무중단 배포 등 다양한 기능을 더 지원해준다는 점이다.</p>
<p>또한 이런 자동으로 처리하는 것에 대한 추가 비용 없이 사용한 AWS 리소스에 대해서만 요금을 지불할 수 있다는 점도 장점이다. </p>
<p>배포 환경 구성이 크게 어렵지 않고(클릭 몇번이면 가능) Github Action/Jenkins와 같은 CI/CD도 쉽게 붙일 수 있다. 하지만 아무래도 다른 Zero Config 플랫폼들에 비해서는 조금 더 설정해 주어야 하는 부분들이 많다.</p>
<h3 id="serverless">Serverless</h3>
<p><a href="https://www.serverless.com/plugins/serverless-nextjs-plugin">Serverless Framework</a>는, AWS 람다와 Azure 함수 서버리스와 같은 Serverless 서비스를 이용해 코드를 쉽게 배포해 주는 프레임워크이다. </p>
<p>Serverless Framework는 다양한 서비스들을 serverless 방식으로 배포할 수 있도록 플러그인들을 제공하며, 사용자들은 원하는 플러그인을 설치하여 배포를 진행할 수 있다.</p>
<p>Serverless-NextJS-plugin은 AWS 서비스를 이용해서 배포를 진행하며 아키텍쳐는 아래와 같이 구성된다.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/83ca2bd3-f9a3-4173-a0dd-c7b909413dcc/image.png" alt=""></p>
<p>기본적으로 모든 배포 환경은 Cloudfront에 구성되게 된다. 필요한 정적 파일은 S3 버킷을 통해 생성하고, SSR과 API 백엔드에 필요한 함수는 lambda@edge에 구성되어 배포된다. </p>
<p>필요한 모든 자원은 Cloudformation을 통해 자동으로 생성되기 때문에 사용자는 배포 설정을 담은 YAML파일만 잘 만들어주면 된다.</p>
<p>추가로 Serverless도 아직은 NextJS 12를 지원하지 않지만, 곧 지원 예정이라고 한다!</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/12a8c03e-4f98-493f-9243-46bfd4973bfc/image.png" alt=""></p>
<h2 id="마치며">마치며</h2>
<p>사내에서 배포 POC를 진행하며 회사 블로그에 글을 작성하게 되었다. AWS의 Elastic Beanstalk와 Serverless Framework의 배포 과정에 대해 궁금하신 분들은 참고하셔도 좋을 것 같다.</p>
<blockquote>
<p>식스샵 기술 블로그 : <a href="https://medium.com/sixshop/next-js-%EB%B0%B0%ED%8F%AC-%EC%96%B4%EB%96%BB%EA%B2%8C-%ED%95%98%EC%8B%9C%EB%82%98%EC%9A%94-a7a632480d93">NextJS 배포 어떻게 하시나요?</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[현실에선 주니어인 내가 이 세계에서는 코치님..?!]]></title>
            <link>https://velog.io/@seeh_h/%ED%98%84%EC%8B%A4%EC%97%90%EC%84%A0-%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%B8-%EB%82%B4%EA%B0%80-%EC%9D%B4-%EC%84%B8%EA%B3%84%EC%97%90%EC%84%9C%EB%8A%94-%EC%BD%94%EC%B9%98%EB%8B%98</link>
            <guid>https://velog.io/@seeh_h/%ED%98%84%EC%8B%A4%EC%97%90%EC%84%A0-%EC%A3%BC%EB%8B%88%EC%96%B4%EC%9D%B8-%EB%82%B4%EA%B0%80-%EC%9D%B4-%EC%84%B8%EA%B3%84%EC%97%90%EC%84%9C%EB%8A%94-%EC%BD%94%EC%B9%98%EB%8B%98</guid>
            <pubDate>Wed, 30 Mar 2022 17:05:53 GMT</pubDate>
            <description><![CDATA[<h3 id="리뷰어-제안">리뷰어 제안!</h3>
<p>이사 시즌과 코로나가 겹쳐 여러모로 정신이 없던 3월 초, 링크드인을 통해 메세지를 받았다. 부트캠프 엘리스의 교육생들이 진행하는 첫 프로젝트의 코칭 참여 제안이었다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/37286d69-1da2-4eaf-93b6-a3f2946476f9/image.png" alt=""></p>
<p>코치의 역할은 크게 두가지였는데, 주중에 진행되는 오피스아워를 통해 교육생들의 질문을 받아주는 일종의 사수같은 역할과, PR을 리뷰해주는 코드 리뷰어의 역할이었다.</p>
<p>멘토링은 언젠가 꼭 한번 해보고 싶었던 활동이었지만 막상 제안을 받고 나니 고민이 됐다. 사실 나도 아직 1년차인데다가, 나 또한 &#39;이게.. 왜 안돼?&#39; 혹은 &#39;이게 돼?&#39;하며 괴로워하는 코더이기 때문에 리뷰어의 역할을 잘 수행할 수 있을지 걱정이 앞섰다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/e8c76a7c-d04f-4aa8-b26c-fab958325b1c/image.png" alt=""></p>
<p>그렇게 계속 &#39;내가 잘 할 수 있을까?&#39;라는 고민을 많이 했는데, 조금 먼저 고민해 본 입장에서 새로 배우는 분들의 어려움에 더 공감하고, 눈높이에 맞는 답변을 드릴 수도 있을것 같았다. 그리고 &#39;부족하면 면접에서 떨어지겠지 뭐!&#39;라는 생각을 하며 면접에 임하기로 결심했다.</p>
<h3 id="면접-과정">면접 과정</h3>
<p>면접은 화상으로 한시간 정도 진행했다. 면접에서는 간단한 자기소개 후 코칭 시연을 요청 받았는데, 시연은 과거 교육생들이 실제로 했던 질문을 면접관께서 주시면 듣고 답변하는 롤플레잉 방식으로 진행되었다.</p>
<p>실제로 개발을 배운 기간이 오래지 않은 분들이 주신 질문이기 때문에, 질문의 난이도는 크게 어렵지 않았던 것으로 기억한다. 단지 맥락 파악이 어려운 질문들이 간혹 있었는데, 이러한 질문의 경우 질문이 잘 이해가 가지 않아 교육생분들과 조금 더 이야기를 나눠 볼 것 같다고 솔직하게 말씀드렸다.</p>
<p>그리고, 면접 후 오래지 않아 긍정적인 결과를 회신받았다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/9bc5049d-79af-4b60-8179-5082afb22e73/image.png" alt=""></p>
<h3 id="코칭-과정">코칭 과정</h3>
<p>첫번째 프로젝트는 한정된 2주라는 짧은 시간과 수강생들의 경험을 고려하여 엘리스에서 프로젝트의 주제와 추가해야할 기능, 그리고 기초가 되는 프로젝트의 뼈대 코드를 제공하고 교육생분들이 그 위에 추가 기능을 개발하는 형식으로 진행되었다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/6c18ab31-f13d-4f8d-b6b8-d012557a1a7f/image.png" alt=""></p>
<p>코치는 해당 프로젝트가 잘 진행되도록 도와주는 역할인데, 앞서 말했듯 평일에는 오피스아워를 통해 수강생들의 질문을 받고 주말 동안은 올라온 PR을 리뷰해주게 된다.</p>
<p>예상을 하긴 했지만 코칭을 하는 과정은 정말 어려웠다. 프로젝트 난이도와 무관하게 개발을 처음 배우는 사람을 코칭하고 리뷰한다는 것은 꽤 큰 책임감과 부담감이 느껴지는 일이었다.</p>
<p>교육생분들께 도움이 되는 방향으로 가이드를 드리기 위해 엘리스 교육의 방향성과 진행할 프로젝트의 요구사항들을 최대한 이해하려고 노력했다. 다행스럽게도 엘리스의 커리큘럼은 잘 짜여져 있었고, 프로젝트도 교육생분들이 진행하기에 벽을 느낄 정도로 어렵진 않으면서도 진행 하며 배울 수 있는 것들이 많도록 잘 설계되어 있었다.</p>
<p>코칭의 방향성에 대해서도 고민을 많이 했다. 개발을 하다보면 어차피 모르는 부분이 계속 생기기 때문에 질문에 대한 해답만 던져주는 방향은 장기적으로 큰 도움이 되지 않을 것이라고 생각했다. 기억에 오래 남지도 않고.</p>
<p><img src="https://velog.velcdn.com/images/seeh_h/post/14556193-7828-492a-81ed-bb0d22a9b3de/image.png" alt=""></p>
<p>그래서 교육생분들이 서칭을 통해 해결할 수 있는 문제라고 생각되는 경우는 레퍼런스를 찾을 수 있도록 키워드만 전달 드리기도 하고, 해결책을 제시할 때는 커리큘럼 상에 있는 연관 개념을 끌어들여 설명하려고 노력했다. </p>
<p>또 질의응답이 일찍 종료된 날은 사내에서 POC하며 직접 작성했던 문서나 코드리뷰가 진행되는 과정을 조금씩 보여드려 어느정도 현업에 대한 정보를 제공해 드리려 했다. 이것 저것 나름대로 꽤 노력을 했는데, 잘 전달이 됐는지는 모르겠다.</p>
<h3 id="후기">후기</h3>
<p>어느새 2주가 훅 흘러가서 엘리스의 4기 첫번째 프로젝트 과정도 마무리가 되었다. 아쉬움이 조금 남지만, 어찌 저찌 잘 끝난 것 같다! 코칭을 진행하며 열심히 하는 교육생분들을 보면서 많은 자극을 얻을 수 있었다. </p>
<p><img src="https://images.velog.io/images/seeh_h/post/2b84e9c5-c000-4727-a931-6031d8d3aec9/image.png" alt=""></p>
<p>교육생 분들의 최종 발표 중 뜬금 없지만 사랑 고백도 받았다🤣 많이 부족한 코칭을 잘 따라와주신 교육생분들께 감사한 마음이다. </p>
<br>

<p>또, 예상 못했던 좋은 기회가 생겼다!</p>
<p><img src="https://images.velog.io/images/seeh_h/post/d66db7f8-58e8-4f86-9023-d43c17bbecdc/image.png" alt=""></p>
<p>감사하게도 엘리스에서 3주 뒤 이어지는 두번째 프로젝트의 코칭 제안을 또 주셨다. 사실 생각보다 시간이 꽤 들고 신경써야할 부분이 많아 다음 활동을 할 수 있을지는 아직은 모르겠다. </p>
<p>이번 기회에 멘토링을 진행하며 누구를 가르쳐 준다는 것은 내가 부족한 부분에 대해서도 많이 깨달을 수 있는 기회라는 것을 알게되었다. 부족한 부분을 알게 된 만큼 더 빠르게 채워가야지 👊</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS Lambda + API Gateway로 Serverless API 환경 구성하기]]></title>
            <link>https://velog.io/@seeh_h/AWS-Lambda-API-Gateway%EB%A1%9C-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@seeh_h/AWS-Lambda-API-Gateway%EB%A1%9C-API-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sun, 20 Feb 2022 08:11:01 GMT</pubDate>
            <description><![CDATA[<p>최근 채용 준비 과정에서 과제전형을 위한 간단한 api가 필요했다. 안그래도 운영중인 서비스의 서버 비용이 점점 늘어나 서버리스 아키텍쳐 도입을 검토하고 있었는데, 공부도 할 겸 AWS로 가벼운 api를 호출할 수 있는 환경을 구성해보았다.</p>
<p>AWS Lambda와 API Gateway를 이용해서 간단한 Serverless API 환경을 구축했는데, 글로도 남겨 놓으면 좋을 것 같아 오랜만에 포스팅을 작성하게 되었다😁</p>
<p><img src="https://images.velog.io/images/seeh_h/post/45621e6f-1de5-49e2-9494-3471464089c9/image.png" alt=""></p>
<blockquote>
<p>이미지 출처 : <a href="https://aws.amazon.com/ko/blogs/korea/using-aws-lambda-within-your-game/">AWS 한국 블로그</a></p>
</blockquote>
<h2 id="aws-lambda란">AWS Lambda란?</h2>
<p>우선 AWS Lambda에 대해 알아보자. Lambda는 주로 서버리스(serverless)라는 용어와 함께 사용된다. 서버리스란 &#39;서버가 없음&#39;을 의미하지만 사실 서버가 아예 없는것은 아니고 서버의 관리 주체가 aws로 넘어감을 의미한다. </p>
<p>람다는 백엔드를 작은 함수 단위로 쪼개어 aws 내부의 서버에 업로드 하는 방식이다. 그러면 aws는 해당 함수를 내부 서버에 업로드 하고 요청 발생시 요청에 맞는 람다함수를 실행시켜준다.</p>
<p>따라서 람다를 이용하면 별도의 서버 구성 없이 클라이언트의 요청에 따라 원하는 함수를 실행시킬 수 있다. 그리고 람다는 요청 수 기반으로 비용이 측정되기 때문에 24시간 켜놓아야 하는 EC2같은 서비스 대비 저렴하게 이용할 수 있다. </p>
<p>또한 AWS의 다른 서비스와도 연계하여 사용할 수 있는 등 다양한 장점이 있다.</p>
<h2 id="api-gateway란">API Gateway란?</h2>
<p>API Gateway는 API의 소비자, 즉 클라이언트가 백엔드 서비스의 데이터 비즈니스 로직 또는 기능에 액세스할 수 있게 해주는 게이트 역할을 한다. 쉽게 말하면 백엔드의 HTTP 엔드포인트 역할을 제공하는 서비스라고 볼 수 있다.</p>
<p>앞서 말한 람다는 &#39;함수&#39;의 역할을 수행하기 때문에, 실제로 해당 람다에 접근할 수 있도록 하기 위해서는 API gateway를 연결하여 엔드포인트를 만들어 주어야 한다.</p>
<p>그러면 클라이언트는 API Gateway를 통해서 Lambda 함수를 호출할 수 있다. 예를 들면 만약 클라이언트가 GET /products HTTP 요청을 보내면, API Gateway가 이것을 받아서 해당 path에 매칭되는 람다 함수를 실행시켜 주는 방식으로 동작한다.</p>
<h2 id="실습">실습</h2>
<p>이제 이론적으로 살펴 보았으니, 실제로 동작하는 API를 만들어 보자. 간단한 쇼핑몰을 만든다 생각하고, 쇼핑몰에 필요한 두가지 API를 만들어 볼 예정이다. 만들 API 목록은 아래와 같다.</p>
<blockquote>
<ol>
<li>전체 상품 리스트 받아오기</li>
<li>특정 상품 상세 정보 받아오기</li>
</ol>
</blockquote>
<p>통신 경로는 아래 그림과 같이 API 소비자(클라이언트) &lt;-&gt; API 게이트웨이 &lt;-&gt; AWS 람다의 구조로 진행된다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/2092bf4a-734d-4175-b7f0-c1cf6c67ea8c/image.png" alt=""></p>
<br>
<br>

<h3 id="1-api-gateway-생성">1. API Gateway 생성</h3>
<p>우선 API Gateway를 먼저 생성해주자. AWS 콘솔에서 API Gateway에 접근하면 아래와 같이 API 유형을 선택할 수 있다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/7009646e-a57b-48c7-9233-50da559da3fb/image.png" alt=""></p>
<p>우리는 GET 메서드를 이용한 간단한 api만 생성할 것이므로 <strong>HTTP API</strong>를 골라주자. REST API는 HTTP API에 비해 조금 더 다양한 기능을 제공하는데, 자세한 내용이 궁금하신 분들은 <a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html">여기</a>를 참고하면 좋을 것 같다.</p>
<br>

<p><img src="https://images.velog.io/images/seeh_h/post/1fb2b6f1-4798-432b-be8f-35cf9c37d5cd/image.png" alt=""></p>
<p>HTTP API를 선택하면 위와 같은 API 생성 페이지를 마주하게 된다. 이미지 중간을 보면 <strong>통합 추가</strong>라는 버튼이 있는데, 통합은 API Gateway가 연결할 서비스를 의미한다. 우리는 람다 함수를 만들어서 추후에 연결시켜 줄 것이므로 API 이름만 입력하고 다음으로 넘어가자.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/55cf079f-43a9-4084-9155-c2481ec7a3f7/image.png" alt=""></p>
<p>다음으로는 스테이지를 정의해야 한다. dev, prod등 원하는 스테이지를 정의하고 각각 배포할 수 있다. 설정한 스테이지 이름이 해당 API 엔드포인트 url의 일부로 포함되게 되니, 이를 고려해서 원하는 스테이지 이름을 설정해주자.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/b30fb19a-96ef-46d7-9845-09a3a0df062e/image.png" alt=""></p>
<p>스테이지까지 정의하면 API Gateway가 만들어진다. 이어지는 화면에서 API Gateway 세부정보를 볼 수 있는데, 생성한 API Gateway의 기본 정보(스테이지 이름, 엔드포인트 등)가 나타나있다.</p>
<p>앞서 스테이지 이름을 dev로 설정해서 실제 URL에도 dev가 포함되어 있는 모습을 확인할 수 있다.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/b063db39-99c4-4e4c-b711-1d825fa25eda/image.png" alt=""></p>
<p>다음으로는 API Gateway가 요청을 받았을때 적절히 요청을 배분할 수 있도록 path를 만들어주자. 좌측 탭에서 <strong>경로</strong> 탭에 들어가서 create 클릭한다.</p>
<br>



<p><img src="https://images.velog.io/images/seeh_h/post/c0a7f95e-b7d7-45a3-afd3-0271a70acf99/image.png" alt=""></p>
<p>path를 만들기 위해서는 경로와 사용할 메서드를 설정해야 한다. 처음으로 만들 API는 상품 리스트 조회 api이므로 경로는 /products, 메서드는 GET으로 설정해주었다.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/0babd179-a39f-40c7-8e96-2ed1dc2b6ae6/image.png" alt=""></p>
<p>설정한 /products 경로로 path가 설정이 된 모습이다. 이어서 이번에는 상품 상세 정보 api를 연결하기 위해 path를 하나 더 만들어 보자.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/c997485b-3333-4375-ac41-8d94790dde44/image.png" alt=""></p>
<p>리소스 경로에 {}를 포함하면 있으면 경로 파라미터를 나타낼 수 있다. 뒤의 상품 id는 동적으로 받아 올 거기 때문에 {productId} 형식으로 넣어줬다. </p>
<br>

<p><img src="https://images.velog.io/images/seeh_h/post/fee2e16b-bf77-44da-b69d-d49114d241cc/image.png" alt=""></p>
<p>products를 포함한 경로를 입력해주면 위와 같이 알아서 계층화를 시켜준다. 이로서 path를 완성했다! 이제 마지막으로 CORS 설정을 진행하자.</p>
<br>

<p><img src="https://images.velog.io/images/seeh_h/post/fd918126-82b6-4155-a941-1d1d74457d1d/image.png" alt=""></p>
<p>API Gateway의 디폴트 설정은 모든 값이 <strong>허용되지 않음</strong>으로 설정되어있다. 따라서 브라우저에서 해당 api 호출을 할 수 있도록 적절한 CORS 설정을 해줘야한다. 좌측의 CORS탭에서 CORS 관련 설정을 진행할 수 있다. </p>
<p>원하는대로 설정을 진행하면 되는데, 모든 브라우저에서 호출할 수 있도록 하려면 Access-Control-Allow-Origin에 &#39;*&#39;을 넣어주면 된다.</p>
<p>이제 API Gateway 관련 설정은 끝났다! 이제 람다 함수를 만들자.</p>
<br>
<br>

<h3 id="2-lambda-생성products">2. Lambda 생성(/products)</h3>
<p><img src="https://images.velog.io/images/seeh_h/post/89fbd154-4280-4b81-9e15-095119196b67/image.png" alt=""></p>
<p>람다 함수를 만들기 위해 다시 aws 콘솔에서 람다로 들어가 함수 생성을 클릭해주자.</p>
<br>



<p><img src="https://images.velog.io/images/seeh_h/post/ca8f7ddc-022a-4f7c-87a5-5d563652210f/image.png" alt=""></p>
<p>함수 생성 탭에서는 함수 이름과 사용할 언어 등 람다 함수와 관련된 여러가지 설정들을 진행할 수 있다. 언어는 Node, python, go, java등 다양한 언어를 지원하며, 버전은 함수 생성 시점 해당 언어의 LTS 버전을 사용할 수 있다.</p>
<p>언어 선택 후 함수 이름을 입력하고 함수 생성을 클릭하면 람다함수가 만들어진다.</p>
<br>



<p><img src="https://images.velog.io/images/seeh_h/post/96edc4cc-9473-4c8b-a8a9-dac270e95a2f/image.png" alt=""></p>
<p>함수를 만든 뒤 함수 개요 탭에서는 람다의 트리거를 추가할 수 있는 버튼과 함수 코드 작성을 위한 코드 에디터가 노출되어 있다. 람다 함수에서 <strong>트리거</strong>는 핵심적인 개념인데, 트리거 이벤트를 설정하여 이벤트 발생 시 해당 람다 함수를 실행시킬 수 있다. </p>
<p>트리거는 추후에 API Gateway에서 설정해 줄 것이므로 코드만 수정하자.</p>
<br>

<p><img src="https://images.velog.io/images/seeh_h/post/280e5cfc-e903-45d6-a11f-9ee91dff29c5/image.png" alt=""></p>
<p>모든 products를 반환해주는 함수이므로, products를 선언하고 해당 정보만 내려주었다.</p>
<h3 id="3-lambda-생성productsproductid">3. Lambda 생성(/products/{productId})</h3>
<p>함수 생성 부분은 앞과 동일하므로 코드 부분만 정리해보자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/26e82280-c1fa-46cd-b3fa-31477b7e5f87/image.png" alt=""></p>
<p>상품 상세 api에서는 product 뒤에 동적으로 productId가 오게된다. 동적 parameter의 경우 람다 함수의 기본 parameter인 event의 pathParameters를 통해서 받아올 수 있다. </p>
<p>현재는 product 수가 많지 않으므로 전체 product option 배열에서 검색해서 내려주는 간단한 로직으로 구현하였다.</p>
<br>
<br>

<h3 id="4-api-gateway-연결">4. API Gateway 연결</h3>
<p>이제 람다함수 까지 생성을 마쳤으니, 마지막 스텝이다! 처음에 생성한 path에 람다 함수를 연결시켜 주는 작업을 진행하자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/ffbb4904-0dfd-4705-8ce0-2666cc81787b/image.png" alt=""></p>
<p>다시 콘솔에서 API Gateway -&gt; 경로 탭을 들어간다. 첫번째 함수부터 연결을 진행해보자.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/67954f0e-667e-4bab-b00e-ad7bef41da7f/image.png" alt=""></p>
<p>/products 밑에 GET을 누르고 <strong>통합 연결</strong>을 클릭하자. 통합 연결은 해당 path로 요청을 수신했을때 호출하는 백엔드 리소스이다. 여기에 좀 전에 만든 람다 함수를 연결해 줄 것이다.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/c9b699e9-16cc-4a65-baa2-1f226720108b/image.png" alt=""></p>
<p>통합 대상에서 람다함수를 선택하고, 통합 세부 정보에서 연결할 람다 함수, 여기서는 productsAll을 누르고 통합 연결을 클릭해주자.</p>
<br>

<p><img src="https://images.velog.io/images/seeh_h/post/ca28c916-416d-4b68-a983-84bbaa8fb036/image.png" alt=""></p>
<p>정상적으로 API Gateway의 path와 람다 함수가 연결이 되면 위 이미지와 같이 path 우측으로 AWS Lambda 뱃지가 노출된다.</p>
<br>


<p><img src="https://images.velog.io/images/seeh_h/post/462c3253-d4a0-434f-8713-2b2994562802/image.png" alt=""></p>
<p>이제 나머지 하나의 함수도 똑같이 연결해주자. 과정은 동일하므로 생략하겠다. 최종적으로 위와 같은 화면이 나오면 성공이다! API 게이트웨이에 두개의 람다 함수를 연결 완료했다.</p>
<h3 id="5-정상-동작-확인">5. 정상 동작 확인</h3>
<p>이제 모든 준비가 끝났으니 호출이 잘 되는지 직접 테스트해보자. 우리가 만든 API들은 모두 Get 메소드이므로 경우 간단히 브라우저에서 호출해보자. 다른 요청의 경우 postman 같은 API platform을 이용하여 진행하면 된다.</p>
<h4 id="전체-상품-리스트-받아오기">전체 상품 리스트 받아오기</h4>
<p><img src="https://images.velog.io/images/seeh_h/post/1aa6db75-e3eb-403c-bb11-bd78cc1af44a/image.png" alt=""></p>
<h4 id="상품-상세-정보-받아오기">상품 상세 정보 받아오기</h4>
<p><img src="https://images.velog.io/images/seeh_h/post/82c145f4-839a-4d4e-91db-e5ef0123dca5/image.png" alt=""></p>
<p>정상 동작까지 확인 완료! 끗!</p>
<h2 id="마치며">마치며</h2>
<p>채용 과제를 계기로 람다와 API 게이트웨이 등 새로운 것들을 사용해 볼 수 있어서 좋았다. 다음에는 Authorizer나 DB등 AWS내 다른 서비스를 같이 이용해서 서버리스 환경을 구축해보고 싶다.</p>
<h3 id="reference">reference</h3>
<p>본 포스팅은 아래의 글들을 참고하여 작성되었습니다.</p>
<blockquote>
<p><a href="https://grip.news/archives/1397">https://grip.news/archives/1397</a>
<a href="http://labs.brandi.co.kr/2018/07/31/kwakjs.html">http://labs.brandi.co.kr/2018/07/31/kwakjs.html</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[독서 - 리팩터링 2판 리뷰]]></title>
            <link>https://velog.io/@seeh_h/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EB%A6%AC%EB%B7%B0</link>
            <guid>https://velog.io/@seeh_h/%EB%A6%AC%ED%8C%A9%ED%84%B0%EB%A7%81-2%ED%8C%90-%EB%A6%AC%EB%B7%B0</guid>
            <pubDate>Mon, 31 Jan 2022 03:26:20 GMT</pubDate>
            <description><![CDATA[<p>평소 즐겨 보던 개발 유튜버들의 영상과 다른 여러 블로그를 통해 추천받아온 리팩토링 2판! 궁금증이 생겨 작년 6월 즈음 책을 구매했다. 그리고 약 5개월간 방치했다..</p>
<p>어느날 문득 시선을 돌리다 발견한 책에 쌓인 먼지를 보고 심한 죄책감이 들어, 급하게 사내에서 스터디를 구성했다. 급조한 스터디는 약 두달간의 여정 끝에 성황리에 종료되었다! 🙌 그리하여 작성하게 된 후기 글.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/e4dde997-5aa3-425c-82b0-27815bf1a1b0/image.png" alt=""></p>
<p>첫주에 대강 타임라인을 담은 계획표를 작성하였는데, 다행스럽게도 큰 딜레이 없이 마무리 되었다. 적고 보니 5장이 없다. 5장은 어디로 갔을까? 마틴 파울러 아저씨의 카탈로그 편지 분명 읽었는데... </p>
<p>여튼, 스터디는 매주 진행자를 선정하여 진행자가 미리 내용을 정리하고, 정리한 내용을 같이 보며 리뷰하는 식으로 진행했다. 종종 잡담이 많이 섞여 한시간 짜리 토론이 두시간이 되기도 했지만 꽤 괜찮은 진행 방식 이었던 것 같다.</p>
<p>스터디 진행 방식은 만족스러웠지만, 솔직히 책 내용은 조금 아쉬웠다. 저자인 마틴 파울러는 리팩토링 외에도 클린 코드, 클린 아키텍처 등 많은 명서를 쓰신 분이다. 다른 책들을 읽어 본 적은 없지만 이 분이 쓴 모든 책이 명서로 유명하기에 이 책도 내심 기대를 많이 하고 읽었는데, 기대한만큼 좋지는 않았다.</p>
<p>아쉬웠던 부분을 꼽아보자면, 저자가 책에서 소개한 리팩토링 기법 중 대부분이 평소에 클린 코드나 리팩토링에 대해 관심이 있어서 관련 아티클이나 컨텐츠들을 봐왔던 사람들은 익숙할 법한 내용이라고 느껴졌다. 내가 미리 접해왔던 정보들의 소스가 이 책이었을 수도 있겠지만.. 무튼 그랬다. </p>
<p>갑자기 글을 적다 생각난건데, 리팩토링이라는 당연한 행위에서 특별한 내용을 기대하는것 자체가 역설적인 것이었나? 싶기도 하다.</p>
<p>두번째는 예시로 사용된 코드들이 설명을 위해 작성된 느낌이 드는 경우가 더러 있었다. 책은 저자가 리팩토링 방식을 제시하고 이를 실제 코드를 통해 설명하는 방식으로 진행된다. 이렇게 예시로 작성된 코드 중 해당 상황에서만 적용할 수 있는 다소 억지스러운 코드들이 꽤 많다고 느꼈다.</p>
<p>그래도 아쉬웠던 부분만 있었던 건 아니고, 좋았던 부분도 있다. 책에서는 다양한 주제의 리팩터링을 다루었지만 전반적으로 데이터의 소유 주체와 책임에 관련된 이야기가 많았던 것 같다.</p>
<p>예를 들면 다양한 객체 혹은 클래스에서 공통적으로 사용되는 필드는 어떻게 관리돼야 하고, 접근의 범위를 어떻게 제한할 수 있는지, 그렇게 해야 하는 이유는 무엇인지와 같은 정보의 소유 범위와 관리 주체에 대한 내용이 많았다. 이는 평소에도 개발하며 많이 고민했던 내용이라 재미있게 읽을 수 있었다.</p>
<p>아직 경험이 부족하고 내공이 부족해서 많은 것을 느끼지 못한지도 모르겠다. 시간이 조금 흐른 뒤 기회가 되면 다시 한번 읽어 봐도 좋을 듯!</p>
<p>최근에는 이펙티브 타입스크립트라는 책을 같이 읽고 있다. 다음 독서 포스팅은 이 책의 후기가 될 것 같다!</p>
<p>끝!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[21년 회고]]></title>
            <link>https://velog.io/@seeh_h/21%EB%85%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@seeh_h/21%EB%85%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Fri, 31 Dec 2021 14:47:59 GMT</pubDate>
            <description><![CDATA[<h1 id="2021">2021</h1>
<p>올해는 개인적으로 의미 있는 일들이 참 많았다.</p>
<h2 id="bye-2021👋">Bye, 2021👋</h2>
<blockquote>
<p>✅ 퇴사 - LG 👋
✅ 창업 - New Challenge❗
✅ 입사 - 식스샵 🎉
✅ Hola - 첫 서비스 런칭 ✨
✅ 인연 - (ᵕ ᵕ⁎) </p>
</blockquote>
<h3 id="퇴사">퇴사</h3>
<p>역시 가장 크고 의미 있었던 일은 프론트엔드 개발자로의 커리어 전환에 성공한 것이다🎉</p>
<p>전 직장에서는 1년 9개월 정도 인프라 엔지니어로 근무했다. 인프라 조직은 각 고객사의 시스템들이 안정적으로 동작하는지 모니터링 하는 업무를 주로 수행하는데, 업무 특성상 루틴한 업무들이 많았다.</p>
<p>업무도 익숙해 지면 빠르게 처리할 수 있고, 워라벨도 꽤 좋은 편에 속했기 때문에 직무에 만족했던 동기들도 많이 있었다. 하지만 아쉽게도 나의 경우는 아니었다. 매일 같은 업무가 반복되는 삶은 지루했고 권태로웠다.</p>
<p>개발이 하고 싶었다. 취업 후의 삶에 대해 깊게 생각해 본 적은 없었지만, 졸업 이후에는 응당 개발자가 되어 있을거라고 생각해왔다.</p>
<p>물론 퇴사도 고려했지만 쉽게 실천하지는 못했다. 잘 다니고 있던 직장을 직무가 맞지 않는다는 이유로 그만두는 것은 꽤 큰 용기가 필요한 일이라는 사실을 알게되었다.</p>
<p>그래서 처음에는 시도때도 없이 밀려오는 퇴사 욕구를 잠재우기 위해 노력했다. 어쩔때는 &#39;개발은 퇴근 후 취미로 해도 괜찮지 않을까? 하고 싶은 일을 하면서 사는 사람이 몇명이나 있을까?(<del>너 내년에 서른이야 정신차려</del>)&#39;라고 하며 스스로를 다그쳐 보기도 하고, 때로는 &#39;그래도 안에서 나름 잘하고 있으니 적응하면 괜찮아 질거야.&#39;하고 억지로 생각하며 마음을 달래보기도 했다.</p>
<p>하지만 여러가지 개발 관련 활동을 병행하면서 내 삶 속에서 일의 가치는 결코 작지 않다는 것을 알게 되었고, 하고 싶은 일을 하지 않고서는 행복해 질 수 없다고 결론 내렸다.</p>
<p>그래서 퇴사했다! 당시 느꼈던 여러 복잡한 감정을 <a href="https://velog.io/@seeh_h/%ED%87%B4%EC%82%AC-zvv7kfeg">포스팅</a>으로도 작성 했었는데, 오랜만에 다시 읽으니 개발자로 스스로를 소개할 수 있는 지금이 꽤 행복하다는 생각이 든다. </p>
<h3 id="창업">창업</h3>
<p>올해에는 생각지도 못했던 창업을 경험했다. 아는 동기에게 좋은 제안을 받아 완전 초창기 스타트업 팀에 합류하게 된게 계기가 되었다.</p>
<p>당시만 해도 극초기 스타트업에는 큰 관심이 없었기 때문에 합류 전 고민을 꽤 오래 했다. 결과적으로는 굉장히 만족스러운 경험을 할 수 있었다. </p>
<p>초기 스타트업의 팀 빌딩은 어떻게 진행되며, 어떤 식으로 협업을 하는지, 회사는 어떻게 성장해 나가고, 투자는 어떤 방식으로 진행 되는지 등 많은 부분을 경험할 수 있었다. 특히 여러 벤처 회사 대표님들을 만나며 사업 방향성에 대해 조언을 구할 수 있었는데, 이 역시도 값진 경험이었다.</p>
<p>여러 시행착오와 문제들을 해결해 나가면서 개발 외적으로도 많은 것을 배웠던 것 같다. 사실 전에는 큰 기업 아니면 안된다는 이상한 생각에 사로잡혀 있었는데, 초기 스타트업을 경험하며 생각이 많이 바뀌었다. </p>
<p>팀 합류부터 MVP 런칭까지의 과정을 담은 <a href="https://velog.io/@seeh_h/%EC%98%88%EB%B9%84-%EC%B0%BD%EC%97%85-%ED%8C%A8%ED%82%A4%EC%A7%80aka-%EC%98%88%EC%B0%BD%ED%8C%A8-%ED%95%A9%EA%B2%A9-%EA%B0%84%EB%8B%A8-%ED%9B%84%EA%B8%B0">포스팅</a>을 남겨 놓았으니, 궁금하신 분들은 한번 읽어보셔도 좋을 것 같다. :)</p>
<h3 id="입사">입사</h3>
<p>식스샵에 입사했다. 이직할 회사를 결정할때 중요하게 고려했던 3가지 항목이 있었다.</p>
<h4 id="1-프론트엔드가-중요한-서비스인가">1. 프론트엔드가 중요한 서비스인가?</h4>
<p>다양한 경험을 하기 위해 Notion이나 Jira 같이 프론트엔드가 프로덕트에서 큰 비중을 차치하는, 유저 경험이 중요시 되는 서비스였으면 했다.</p>
<h4 id="2-서비스가-몰입해서-개발할-수-있을만큼-매력이-있는가">2. 서비스가 몰입해서 개발할 수 있을만큼 매력이 있는가?</h4>
<p>전 회사에서 일하며 좋아하지 않는 일에는 몰입할 수 없다는 사실을 알게 되었다. 그래서 프로덕트 자체가 매력적으로 느껴지는 곳에 가고 싶었다.</p>
<p>누군가에게 필요한 서비스를 만드는 데에 흥미를 느끼기 때문에, 프로덕트가 소비자에게 어떤 가치를 주는 서비스인지를 판단 기준으로 삼았다.</p>
<h4 id="3-개발-문화를-중요하게-생각하는가">3. 개발 문화를 중요하게 생각하는가?</h4>
<p>개발자로서 성장할 수 있는 개발문화(코드리뷰, 문서화 등)가 잘 갖춰진 곳으로 가고 싶었다.</p>
<h4 id="합격-🥳--그-이후">합격 🥳 , 그 이후</h4>
<p>이렇게 정한 기준에 맞는 곳을 찾아서 하나씩 지원했었는데, 식스샵에 최종 합격하게 되어 한달 정도 쉬는 기간을 가진 뒤 입사하게 되었다.</p>
<p>확실히 팀 속에서 업무를 진행하다보니 배우는게 더 많은 것 같다. 특히 최근에는 디자인 시스템 구축 업무를 진행 했는데, 많은 것을 배웠고 개인적으로 큰 의미가 있었다. 이는 조금 더 정리가 되면 별도의 포스팅으로 따로 정리할 예정이다.</p>
<h3 id="hola">HOLA</h3>
<p>사이드 프로젝트로 시작했던 <a href="https://holaworld.io">HOLA</a>를 공식 서비스로 런칭했다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/32c5505f-5455-42fa-8faf-6a568a750c75/image.png" alt=""></p>
<p>오픈 후 이직 시기와 겹쳐 아직 이렇다 할 업데이트를 하지 못하고 있는데 감사하게도 많은 분들이 이용해 주시고 있다. </p>
<p>HOLA는 처음 진행한 사이드 프로젝트인만큼 개인적으로 많이 애정하는 서비스이다. 이제 이직도 끝났고, 좀 여유가 생겨서 22년에는 밀린 기능들 추가를 빠르게 진행할 예정이다. 많관부!</p>
<h3 id="인연">인연</h3>
<p>올해도 많은 인연을 새로 만났다. 여러 스터디를 통해 알게 된 개발자 분들과, 몇개월동안 고생을 같이 한 HOLA 팀원들, 식스샵의 좋은 동료들..</p>
<p>고마운 사람들이 스쳐가는 인연이 되지 않았음에 감사하고, 내년에도 좋은 인연을 많이 맺을 수 있기를 소망한다🙏</p>
<h1 id="2022">2022</h1>
<p>올해만큼만 열심히 할 수 있는 한 해가 되었으면 좋겠다 :)</p>
<h2 id="hi-2022👋">Hi, 2022👋</h2>
<blockquote>
<p>✅ 개발 - 더 멋진⭐️ 개발자로
✅ 블로그 - 꾸준히 포스팅하기
✅ HOLA - 서비스 개선
✅ 앱 - IOS 맛보기
✅ 운동 - 험난한 헬창의 길 💪
✅ 휴식 - 취미 찾기</p>
</blockquote>
<h3 id="개발">개발</h3>
<p>올해는 너무 많은 것을 배우려고 하다 보니 정작 하고있는 일들에 집중하지 못했던 순간들이 많었다. 내년에는 하나를 배울 때 더 몰두하고 디깅하는 습관을 들이고 싶다.</p>
<p>여전히 읽고 싶은 개발 서적도 많고 듣고 싶은 강의도 많다(<del>사 놓고 안 보고, 안 듣고 있는건 더 많다</del>). 공부할 시간은 앞으로도 많으니 여유를 가지고 천천히 하나씩 살펴봐야겠다.</p>
<h3 id="블로그">블로그</h3>
<p>한달에 하나씩 포스팅을 하는게 목표이다. 올해 블로그를 운영 하며 느낀건, 혼자 정리하는 것 보다 남에게 지식을 전달 할 목적으로 글을 쓸때 머리속에 더 잘 정리가 되고, 주제에 대한 전반적인 이해도도 훨씬 높아진다는 것이다.</p>
<p>그리고 내가 작성한 글들을 보고 도움을 받았다는 분들을 보며 굉장히 뿌듯했다. 내년에는 조금 더 양질의 포스팅을 작성할 수 있도록 노력해야겠다💪</p>
<h3 id="hola-1">HOLA</h3>
<p>올해 하반기에 배포 후 거의 진행하지 못했는데, 내년에는 HOLA에 시간을 더 많이 쏟을 예정이다. 예비/주니어 개발자에게 도움을 줄 수 있는 서비스로 만들어 나가고 싶다.</p>
<h3 id="앱">앱</h3>
<p>최근에 여러가지 애플 제품들을 사용하면서 IOS에 관심이 생겼다. 마침 만들어 보고 싶은 앱도 있고 해서 이 참에 배워보고 싶다. 상반기 내에 시작 할 수 있으면 좋을 것 같다.</p>
<h3 id="운동">운동</h3>
<p>이거는 항상 새해 다짐 리스트에 있는데.. 지금 까지는 있기만 했었다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/06d53978-5659-45ae-96d9-033c19eeb6be/image.png" alt=""></p>
<p>하지만 올해는 뭔가 성공할 것 같은 느낌이 든다. 기분탓일까?</p>
<h3 id="휴식">휴식</h3>
<p>1년이 생각보다 금방 지나갔다. 올해는 항상 바쁘게 지냈는데, 이렇게 계속 지내다 보면 어느순간 번아웃이 오는건 아닐까 하는 걱정이 있다. 내년에는 가볍게 즐길 수 있는 취미 생활을 하나 찾고 싶다. </p>
<h1 id="마무리하며">마무리하며</h1>
<p>이직 외에 크게 한 게 없다고 생각했는데 돌아보니 나름 한것도 많고 배운것도 많은 만족스러운 한 해였다. 올해는 방향성을 잡고 기초를 다지는데 집중 했다면, 내년에는 실력이 부각되는 성장이 따르면 더 기쁠 것 같다.</p>
<blockquote>
<p>긴 회고 읽어주셔서 감사합니다 :) 모두들 행복한 2022년 되시길 기원합니다 🎉 </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[라이브러리 뽀개기 - Formik]]></title>
            <link>https://velog.io/@seeh_h/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%BD%80%EA%B0%9C%EA%B8%B0-Formik</link>
            <guid>https://velog.io/@seeh_h/%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%BD%80%EA%B0%9C%EA%B8%B0-Formik</guid>
            <pubDate>Sun, 19 Dec 2021 07:56:56 GMT</pubDate>
            <description><![CDATA[<p>이번 포스팅에서는 react에서 form의 상태관리를 도와주는 formik에 대하여 정리해본다.</p>
<h1 id="form">Form</h1>
<p>웹 어플리케이션을 개발하다 보면 input, select, checkbox 등 다양한 입력으로 이뤄진 form을 자주 다루게 된다. 예를 들어 아래와 같은 room reservation form을 만들어야 한다고 가정해보자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/0e69c199-c721-44cf-b4dc-4509c7589fd1/image.png" alt=""></p>
<p>위 form에는 Name, Email, Room Type등 다수의 input field가 존재한다. 만약 순수 React만을 사용한다면, 위 form 내부의 모든 input에 state 변화를 직접 처리해주어야 한다.</p>
<p>사용자에게 받아야 하는 정보가 다양해 질수록(input field의 개수가 늘어 날수록) form 내부 state의 관리는 점점 더 어려워지고 복잡해진다.</p>
<h1 id="form-library">Form library</h1>
<p>다행히도 이런 form 내부 상태관리를 도와주는 여러 라이브러리들이 존재한다. 대중적으로 널리 사용되는 라이브러리에는 react-hook-form, formik, final-form 정도가 있다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/62c2dd95-0379-4a78-83b2-a30ebea89460/image.png" alt=""></p>
<p>오늘은 이 중 가장 많이 사용되는 formik에 대해 살펴보자.</p>
<h1 id="formik">Formik</h1>
<p>formik <a href="https://formik.org/">공식 docs</a>에 들어가 보면 아래와 같은 문구로 해당 라이브러리를 소개하고 있다.</p>
<blockquote>
<p>Build forms in React, <strong>without the tears</strong> 🤣</p>
</blockquote>
<p>formik을 이용하면 눈물 없이 form을 빌드할 수 있다고 한다. (<del>사실 포믹 공부하며 더 많이 흘림..</del>) 
공식 문서에 따르면 formik은 폼에서 가장 성가신 3가지 부분을 쉽게 처리하게끔 도와준다.</p>
<blockquote>
</blockquote>
<ul>
<li>form state 값 가져오기</li>
<li>validation을 및 에러 메시지 출력</li>
<li>form submit 핸들링</li>
</ul>
<p>포믹 공식 문서의 튜토리얼을 따라 진행해보며 formik의 위력을 느껴보자.</p>
<h1 id="실습">실습</h1>
<h2 id="튜토리얼-시작">튜토리얼 시작</h2>
<p>튜토리얼에서는 아래와 같은 간단한 form을 만들어 볼 것이다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/17211711-ceb5-4708-9ed4-543c78d0de1e/image.png" alt=""></p>
<p>우선 간단한 하나의 input으로 시작해보자. </p>
<p>formik의 가장 기본적인 사용 형태는 아래와 같이 <strong>useFormik</strong>이라는 커스텀 훅을 이용하는 방식이다. </p>
<blockquote>
<p>💡 <strong>useFormik</strong>이란?
formik의 가장 기본적인 이용 방식으로, hook 호출을 통해 form과 input의 상태관리에 필요한 여러가지 도구들을 담은 formik 객체를 리턴받아 form 내부 input에 대한 상태 관리를 진행할 수 있다.</p>
</blockquote>
<p><img src="https://images.velog.io/images/seeh_h/post/94874d88-8266-4751-81ca-b88f100fe6c1/image.png" alt=""></p>
<p><strong>useFormik</strong> 훅을 이용할 때는 <strong>initialValue</strong>와 <strong>onSubmit</strong> 함수를 전달하여야 한다. <strong>initialValue</strong>는 input의 초기 value이며, <strong>onSubmit</strong>은 form 제출 시 실행 되는 콜백 함수이다. </p>
<p>해당 함수들을 전달하여 <strong>useFormik</strong> 훅을 호출하면 formik 객체를 반환 받을 수 있다. formik 객체 안에는 form을 제어할 수 있는 여러가지 도구들이 담겨 있는데, 공식 문서에서는 이를 <strong>goodie bag(좋은 가방)</strong>이라고 표현하고 있다.</p>
<p>좋은 가방 내부를 조금 더 들여다보면, input state로 사용할 수 있는 value와 여러가지 handler (handleChange, handleBlur등)들이 담겨 있다. 따라서 이를 이용하면 별도의 추가 state 선언 없이 form 내부 상태 관리를 할 수 있다. </p>
<p>위 코드를 예시로 보면, formik 객체 안의 <strong>formik.handleChange</strong>와 <strong>formik.values.email</strong>을 이용해 email input field를 관리하고, <strong>formik.handleSubmit</strong>을 통해 form의 submit event를 관리하고 있다.</p>
<br>

<p>그런데 사실 위 예시 같이 input field가 하나인 경우, formik을 적용하는게 큰 이점을 가져다 준다고 보긴 어렵다. 따라서 이제 조금 더 일반적인 form의 상황을 가정해보자. </p>
<p><img src="https://images.velog.io/images/seeh_h/post/7ad34add-9f13-4f49-a01a-c9962e0c5811/image.png" alt=""></p>
<p>firstName, LastName, EmailAddress 3가지의 필드를 입력받는 form으로 바뀌었다. 위 코드를 보면, 하나의 formik 객체로 3개의 input을 모두 관리하고 있다.</p>
<p>formik은 input의 name property를 통해 어떤 input에서 변화가 일어났는지를 감지할 수 있다. 실제로 formik.values를 console로 찍어보면 우리가 전달한 name property가 state에 추가된 객체 형태로 출력된다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/947a17c5-d0ad-4968-9d75-4426c26fbe95/image.png" alt=""></p>
<p>따라서 <strong>formik 하나만으로 여러개의 input 상태에 대한 제어</strong>가 가능하다. 이렇게 여러개의 input이 추가되더라도 formik 하나로 form 전체를 관리할 수 있는 것이 formik의 장점이다.</p>
<br>

<p>formik을 사용하지 않고 실제로 handleChange 함수를 구현하기 위해서는 아래와 같은 코드를 작성해야만 한다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/6af12e7c-147b-487b-aab4-016d18c39ed7/image.png" alt=""></p>
<p>사실 위 코드를 작성하는 일은 다소 귀찮지만 그렇게 어려운 일은 아니므로, formik을 도입할만한 이유까지 되진 않는 것 같다. </p>
<p>맞다! formik은 상태 관리 뿐만 아니라 form 관리에 필요한 다양한 기능을 함께 제공하고, 이 기능들을 조합해서 사용했을때 더 강력하다. 조금 더 살펴보자.</p>
<h2 id="validation">validation</h2>
<p>formik은 state 관리 외에 input의 validation 기능도 함께 제공한다.</p>
<p>validation은 2가지 방식으로 진행할 수 있다. 첫번째 방식은 <strong>useFormik</strong> hook에 validate property에 객체를 직접 정의하는 방식이고, 두번째 방식은 <a href="https://www.npmjs.com/package/yup">yup</a>을 이용하여 진행하는 방식이다.</p>
<p>두가지 방식 중 Formik 공식문서에서는 yup을 이용한 방식을 권장하고 있다. 적용 방식은 굉장히 간단한데, <strong>useFormik</strong>에 값을 넘길때 Yup의 object method를 이용해서 정의한 validation object를 함께 넘겨주기만 하면된다.</p>
<p>위에서 작성한 코드에 validation을 추가로 적용해보자. </p>
<p><img src="https://images.velog.io/images/seeh_h/post/eec0f67b-4940-48c1-92e8-333d41859825/image.png" alt=""></p>
<p>각 필드에 대한 validation 로직을 적용한 Yup object를 함께 넘겨주었다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/c061d43e-ee76-4fa5-9eca-9713b89f1af9/image.png" alt=""></p>
<p>그러면 formik이 자동으로 input에 대한 validation을 진행해준다. LastName과 Email Address 필드에 유효하지 않은 값을 입력했을 경우, 에러 메세지를 띄워주는 것을 확인할 수 있다.</p>
<br>

<h2 id="reducing-boilerplate">Reducing boilerplate</h2>
<p>이제 formik에 대한 기본 내용은 다 살펴 보았다. 추가적으로 formik에서 제공하는 여러가지 API들을 이용하여 코드를 조금 더 개선해보자.</p>
<h3 id="getfieldprops">getFieldProps</h3>
<p>지금 까지의 코드를 보면 모든 input에 onChange, value, onBlur 값이 동일하게 전달되고 있다. </p>
<p><img src="https://images.velog.io/images/seeh_h/post/38be9d83-e4c3-43fe-891d-1c4a3cd83268/image.png" alt=""></p>
<p>formik은 이런 중복을 제거할 수 있도록 <strong>getFieldProps</strong> 메서드를 제공한다. </p>
<p><strong>getFieldProps</strong> 메서드는 input, select, textarea 태그에 적용할 수 있으며 주어진 필드에 맞춰 onChange, onBlur, value, checked를 리턴 한다. 위 코드에 <strong>getFieldProps</strong>를 적용하면 아래과 같이 개선이 가능하다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/d2ad9f58-7e7f-4ec9-b122-553b7827d32d/image.png" alt=""></p>
<p>훨씬 깔끔하다! <strong>getFieldProps</strong>를 전체 코드에 적용해 보자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/a72628bc-c89d-4a28-90ba-0586ae27ed31/image.png" alt=""></p>
<br>

<h3 id="contextapi">ContextAPI</h3>
<p><strong>getFieldProps</strong>를 이용하여 많은 중복을 제거할 수 있었지만, 결국 input 태그 별로 해당 props를 수동으로 넘겨줘야 하는 것은 변하지 않았다.</p>
<p>Formik은 사용자들이 코드를 조금 더 효율적으로 작성할 수 있도록 <a href="https://reactjs.org/docs/context.html">context API</a> 기반의 추상화된 컴포넌트들을 제공한다. </p>
<p>해당 컴포넌트들은 contextAPI를 통해 컴포넌트 내부에서 필요한 props(onChange, onBlur 등)에 바로 접근할 수 있기 때문에 getFieldProps를 전달하는 과정 또한 생략할 수 있다.</p>
<p>추상화된 컴포넌트로는 <code>&lt;Formik\&gt;</code>, <code>&lt;Field\&gt;</code>, <code>&lt;Form\&gt;</code>, <code>&lt;ErrorMessage\&gt;</code> 컴포넌트 등이 있다. </p>
<h4 id="formik-1">Formik</h4>
<p><code>&lt;Formik\&gt;</code> 컴포넌트를 사용하면 <strong>useformik hook을 대체</strong> 할 수 있다.</p>
<p><code>&lt;Formik\&gt;</code> 컴포넌트는 <strong>내부적으로 useFormik을 호출</strong>하고 이를 children 컴포넌트에게 formik 객체를 전달한다. 따라서 해당 컴포넌트로 우리의 컴포넌트를 감싸기만 하면 내부에 있는 컴포넌트들은 모두 formik 객체를 사용 가능하다.</p>
<p>formik 공식 문서에서 제공하는 Formik 컴포넌트의 의사 코드(pseudocode)는 아래와 같다. 코드를 보면 <code>&lt;Formik\&gt;</code> 컴포넌트 내부에서 <strong>useFormik</strong> 훅을 호출하여 contextAPI를 이용해 내부로 전달하고 있다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/d6f71d8e-db01-41a2-b551-788f49edc4e9/image.png" alt=""></p>
<br>

<p>궁금해서 실제로 formik <a href="https://github.com/jaredpalmer/formik/blob/master/packages/formik/src/Formik.tsx#L992">레포지터리에</a> 들어가 보았는데, 아래 이미지와 같이 Formik 컴포넌트에 내부적으로 useFormik을 호출해서 사용하는 것을 확인할 수 있었다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/f200d6f6-b984-4b00-b6e1-523fcd023ac1/image.png" alt=""></p>
<br>

<h4 id="field-form-errormessage">Field, Form, ErrorMessage</h4>
<p>이 외에도 input을 대체하는 <code>Field</code>, form을 대체하는 <code>Form</code>, error를 대체하는 <code>ErrorMessage</code> 컴포넌트도 제공한다.</p>
<p><code>Field</code> 컴포넌트만 살펴보자. <code>Field</code>컴포넌트는 는 디폴트로 input 태그로 render 되지만, <code>as</code> prop을 이용하여 textarea 혹은 select로도 사용할 수 있다. styled-component의 문법과 비슷하다.</p>
<p><code>Field</code> 컴포넌트에 대한 자세한 API 스펙은 <a href="https://formik.org/docs/api/field">여기</a>서 확인할 수 있다.</p>
<p>해당 컴포넌트들을 적용하기 전에, 현재까지의 코드를 다시 살펴보자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/a72628bc-c89d-4a28-90ba-0586ae27ed31/image.png" alt=""></p>
<p>이제 위 코드에 최종적으로 Form, Field, Error 컴포넌트를 적용해보자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/d8ab965b-7c37-4439-a806-46239c20ea05/image.png" alt=""></p>
<p>폼의 state와 input의 validation을 하나하나 관리하는 초기 코드에 비해, 처음에 비해 코드가 훨씬 간결해 진 것을 확인할 수 있다.</p>
<p>이렇게 튜토리얼을 따라 일반적인 폼에서부터 formik을 적용한 폼으로 변경해 보았다! 상태 관리부터 필드 유효성 체크, submit 관리까지 알아서 해주니 작성해야할 코드를 꽤 많이 줄일 수 있다는 생각이 든다.</p>
<h3 id="with-custom-component">with custom component</h3>
<p>이대로 끝내기는 아쉬우니 마지막으로 한가지만 더 살펴보자. 결과적으로는 Formik 소개글이 되었지만 사실 처음에는 해당 고민을 해결하는 과정을 공유하고 싶어 이 글을 쓰게 되었다.</p>
<p>만약 네이티브 input 태그가 아닌, 커스텀 된 컴포넌트 혹은 기타 UI 라이브러리(MUI, Ant Design등)를 이용하여 formik을 이용하고 싶은 경우는 어떻게 해야 할까?</p>
<p>예를 들어 아래 이미지의 <code>&lt;CustomInput&gt;</code> 컴포넌트에 formik을 적용하고 싶다고 가정해보자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/5f64a0dd-3206-4418-b462-07a70e0e2f21/image.png" alt=""></p>
<br>

<p>우선 일반 formik을 적용할 때와 같이Field에서 props로 넘어오는 onChange 함수를 <code>&lt;CustomInput&gt;</code> 컴포넌트에 전달해주자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/2074171f-fdce-449c-b363-798f8eec77d6/image.png" alt=""></p>
<br>

<p>그리고 커스텀 컴포넌트에서 onChange시 특정한 로직을 진행해야 하는 경우에는, 전달 받은 <strong>field.onChange</strong> 함수를 먼저 호출해 준 뒤에 추가 로직을 수행하는 방식으로 진행할 수 있다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/57e39965-cf96-496e-b42a-c45c70a8afe6/image.png" alt=""></p>
<h1 id="마치며">마치며</h1>
<p>form 관리에 사용하기 위해 formik에 대해서 알아보았는데, 생각보다 이해하는데 시간이 조금 걸렸다. 간단하지는 않지만 잘 익혀두면 강력한 도구가 될 것 같다. </p>
<p>깃 레포 이슈들을 살펴보니 관리해야하는 폼 내부 element들이 많아지면 성능 이슈가 조금 있는 듯 하다🤔
시간 날때 조금 더 살펴봐야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[React에서 활용 가능한 DatePicker Library 정리]]></title>
            <link>https://velog.io/@seeh_h/React%EC%97%90%EC%84%9C-%ED%99%9C%EC%9A%A9-%EA%B0%80%EB%8A%A5%ED%95%9C-DatePicker-Library</link>
            <guid>https://velog.io/@seeh_h/React%EC%97%90%EC%84%9C-%ED%99%9C%EC%9A%A9-%EA%B0%80%EB%8A%A5%ED%95%9C-DatePicker-Library</guid>
            <pubDate>Sat, 11 Dec 2021 16:45:36 GMT</pubDate>
            <description><![CDATA[<p>최근에 회사에서 프로덕트 신규 버전을 위한 디자인 시스템 제작중에 있다. 재사용 가능한 작은 단위의 컴포넌트들 부터 하나씩 만들어 나가고 있는데, 최근에 Datepicker를 붙이는 작업을 진행하였다.</p>
<p>주요 라이브러리 몇가지를 사용해 보며 장단점을 조사해보고,  <a href="https://reactdatepicker.com/">react-datepicker</a>를 사용을 결정하게 되었는데 해당 라이브러리를 선택하게 된 이유와 과정에 대해서 남겨둘까 한다😆</p>
<blockquote>
<p>💡 실제 라이브러리 사용 예제 코드는 <a href="https://codesandbox.io/s/vigorous-sun-cll90?file=/src/App.tsx">여기</a>에서 확인 하실 수 있습니다 :)</p>
</blockquote>
<h2 id="고려사항">고려사항</h2>
<p>만들고자 하는 컴포넌트는 아래와 같이 input 영역을 클릭하면 datepicker가 열리는 UI를 가지고 있다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/96bf54ef-592f-45dc-a052-023abb03d973/image.png" alt=""></p>
<p>사용성을 제외하고, 라이브러리 선택을 위해 고려해야 하는 몇가지 사항들이 있었다. 우선적으로 기술 스택으로 react + typescript를 사용하고 있었기 때문에 typescript를 지원해야 했다.(직접 타입 정의하는거 넘모..고통...)</p>
<p>두번째로 네이티브 input tag가 아닌 커스텀 input 컴포넌트를 따로 만들어서 사용하고 있었기 때문에, 커스텀 된 input 컴포넌트를 사용할 수 있어야 했다.</p>
<p>추가적으로 라이센스와 안정성, 지속적인 업데이트 여부도 고려했다. 선정 기준을 대충 정리해보면 아래와 같다. </p>
<blockquote>
</blockquote>
<ul>
<li>custom input 지원 여부</li>
<li>typescript 지원 여부</li>
<li>라이센스</li>
<li>범용성(weekly download)</li>
<li>지속적 관리(last publish)</li>
<li>datepicker style custom</li>
</ul>
<h2 id="시작">시작</h2>
<p>위에서 언급한 내용을 중심으로 총 5개 정도를 후보군으로 놓고 조사해보았다!</p>
<blockquote>
</blockquote>
<ol>
<li>react-datepicker</li>
<li>react-calendar</li>
<li>react-day-picker</li>
<li>Material-UI date and time pickers</li>
<li>Ant Design date-picker</li>
</ol>
<h3 id="1-react-datepicker"><a href="https://reactdatepicker.com/">1. react-datepicker</a></h3>
<p><img src="https://images.velog.io/images/seeh_h/post/0e6b2270-77a2-4cdf-96e1-2e5ddd969360/image.png" alt=""></p>
<ul>
<li>license: MIT</li>
<li>weekly download : 956,868</li>
<li>last publish: 3 days ago</li>
<li>장점<ul>
<li>가장 범용적으로 사용돼서 레퍼런스 많음</li>
<li>기본 제공 props가 다양함</li>
<li>스타일 커스텀 가능<a href="https://blog.naver.com/PostList.naver?blogId=marsdo">(참고 블로그)</a></li>
</ul>
</li>
<li>단점<ul>
<li>패키지 사이즈 526kb로 상대적으로 큼</li>
</ul>
</li>
</ul>
<p>살펴본 라이브러리 중에서는 react-datepicker가 사용성이 가장 좋았다. 제공하는 props들도 가장 다양했고 커스텀 스타일 적용도 가능했다. 또 기본적으로 키보드 입력을 지원한다는 점이 좋았다.</p>
<p>문서화도 친절하게 잘 되어 있어서, <a href="https://reactdatepicker.com/">여기</a>를 확인해보면 각 props와 해당 props가 적용된 예제 코드를 쉽게 확인할 수 있다.</p>
<p>따라서 react-datepicker 도입을 결정하게 되었다.</p>
<h3 id="2-react-calendar"><a href="https://www.npmjs.com/package/react-calendar">2. react-calendar</a></h3>
<p><img src="https://images.velog.io/images/seeh_h/post/cd02a9f9-490b-43a0-987a-649f050634e5/image.png" alt=""></p>
<ul>
<li><p>license: MIT</p>
</li>
<li><p>weekly download : 213,478</p>
</li>
<li><p>last publish: 1 month ago</p>
</li>
<li><p>장점</p>
<ul>
<li>다양한 props 제공</li>
<li>default 달력 스타일이 예쁨</li>
</ul>
</li>
<li><p>단점</p>
<ul>
<li>input을 props를 제공하지 않음</li>
</ul>
</li>
</ul>
<p>react-calendar 같은 경우 기본적으로 달력이 독립적으로 사용된다. 따라서 input tag와 함께 두고 해당 영역 클릭시에만 달력을 보여주고자 할 때에는 state를 통해 제어해주어야 한다는 불편함이 있었다.</p>
<p>또한 기존 DOM에 영향을 주지 않기 위해 absolute로 한번 감싸야하고..무튼 귀찮았다.</p>
<h3 id="3-react-day-picker"><a href="https://www.npmjs.com/package/react-day-picker">3. react-day-picker</a></h3>
<p><img src="https://images.velog.io/images/seeh_h/post/15bb8d53-2a30-498a-86ad-9c4bdb140200/image.png" alt=""></p>
<ul>
<li>license: MIT</li>
<li>weekly download : 419,106</li>
<li>last publish: 3 days ago</li>
<li>장점<ul>
<li>문서화가 잘 되어 있음</li>
<li>범용적으로 사용됨</li>
<li>input을 함께 사용할 경우와 그렇지 않을 경우 둘 다 제공</li>
<li>커스텀 기능 다양하게 제공</li>
</ul>
</li>
<li>단점<ul>
<li>잘 모르겠음</li>
</ul>
</li>
</ul>
<p>react-day-picker도 <a href="http://react-day-picker.js.org/api/DayPickerInput">문서화</a>가 잘 되어 있고 많이 적용하는 라이브러리 인듯하다. </p>
<p>특이점으로는 DayPicker와 DayPickerInput 두가지 컴포넌트를 제공하여 달력만 사용할 경우와, Input tag과 함께 사용할 경우 두 컴포넌트를 나누어 제공한다는 것이다.</p>
<p>react-datepicker와 고민하다가, 추후 확장성을 고려했을때 reacte-datepicker가 조금 더 다양한 custom 기능을 제공하고 있어 유리하다고 판단했다.</p>
<h3 id="4-material-ui-date-and-time-pickers"><a href="https://mui.com/components/date-picker/">4. Material-UI date and time pickers</a></h3>
<p><img src="https://images.velog.io/images/seeh_h/post/9a7b40d9-76c8-401c-992f-3c522394c1eb/image.png" alt=""></p>
<ul>
<li>license: MIT</li>
<li>장점<ul>
<li>다양한 props 제공</li>
<li>MUI팀에서 만들었기 때문에 신뢰하고 쓸 수 있다(?)</li>
</ul>
</li>
<li>단점<ul>
<li>input만 전달 가능(custom icon 사용 불가)</li>
</ul>
</li>
</ul>
<p>custom calendar icon을 사용할 수 없었기 때문에 사용할 수 없었다. 기존에 mui를 사용하고 있었다면 충분히 고려해볼 만한 옵션이라고 생각한다.</p>
<h3 id="5-ant-date-picker"><a href="https://ant.design/components/date-picker/#header">5. ANT Date-picker</a></h3>
<p><img src="https://images.velog.io/images/seeh_h/post/54dae842-26cd-4f3c-9724-7b80be56195a/image.png" alt=""></p>
<ul>
<li>license: MIT</li>
</ul>
<p>커스텀 인풋과 함께 사용 불가하여 패스하였다.</p>
<h2 id="reference">reference</h2>
<blockquote>
<p><a href="https://blog.bitsrc.io/13-react-time-and-date-pickers-for-2020-d52d88d1ca0b">13 React DatePickers and TimePickers for 2020</a></p>
</blockquote>
<h2 id="마치며">마치며</h2>
<p>date picker 라이브러리를 직접 사용해보기도 하고, 내부 구현이 어떻게 되어있는지 들여다 보기도 하면서 재미있게 작업했던 것 같다.</p>
<p>사용해보여 작성한 코드를 <a href="https://codesandbox.io/s/vigorous-sun-cll90?file=/src/App.tsx">codeSandbox</a>에 공유해놓았으니, 필요한 분들은 참고하셔도 좋을 듯 하다 :)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Webpack 기초 간단 정리✨ - 응용편]]></title>
            <link>https://velog.io/@seeh_h/Webpack-%EA%B8%B0%EC%B4%88-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-%EC%9D%91%EC%9A%A9%ED%8E%B8</link>
            <guid>https://velog.io/@seeh_h/Webpack-%EA%B8%B0%EC%B4%88-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-%EC%9D%91%EC%9A%A9%ED%8E%B8</guid>
            <pubDate>Wed, 24 Nov 2021 14:38:42 GMT</pubDate>
            <description><![CDATA[<p>이번 편은 웹팩 시리즈의 세번째 포스팅인 응용편이다! 지난 두번째 포스팅에서는 간단한 웹팩 설정을 진행해보며 번들링 과정을 직접 경험해보았다. </p>
<p>지난 포스팅에서 바닐라 자바스크립트로 코드를 작성했었는데, 이번엔 해당 코드를 리액트 기반으로 바꾸는 작업을 진행할 예정이다!  </p>
<p>그럼 바로 시작해보자 🔥</p>
<blockquote>
<p>💡 지난 포스팅에서 작성한 코드에 이어서 진행 예정이므로, <a href="https://velog.io/@seeh_h/Webpack-%EA%B8%B0%EC%B4%88-%EA%B0%84%EB%8B%A8-%EC%A0%95%EB%A6%AC-%EC%8B%A4%EC%A0%84%ED%8E%B8">2편</a> 포스팅을 먼저 읽고 오시는 것을 추천드립니다 :)</p>
</blockquote>
<h2 id="바벨babel-적용하기">바벨(Babel) 적용하기</h2>
<p>이번 포스팅의 핵심은 바벨이이므로, 우선적으로 바벨을 설치해주어야 한다.</p>
<h3 id="바벨은-왜-설치해야-하나요-🙋♂️-">바벨은 왜 설치해야 하나요 🙋‍♂️ ?</h3>
<p>바벨은 자바스크립트 트랜스파일러(Transpiler)이다. &#39;트랜스파일링&#39;이란 어떤 특정 언어로 작성된 소스 코드를 다른 소스 코드로 변환하는 것을 말한다. 즉 바벨을 이용하여 기존 작성한 코드를 자바스크립트 코드로 변환할 수 있다.</p>
<p>리액트로 코드를 작성하기 위해 바벨이 필요한 이유는 브라우저가 이해하는건 오직 &#39;자바스크립트&#39;이기 때문이다. 브라우저는 자바스크립트 외에 코드, 즉 리액트로 작성된 코드(jsx)나 타입스크립트로 작성된 코드(ts)를 이해하지 못한다. </p>
<p>따라서 <strong>바벨을 이용해 해당 코드를 자바스크립트로 트랜스파일링</strong> 해줘야 한다. </p>
<p>또한 바벨은 구형 브라우저 지원을 위해서도 사용된다. 특정 브라우저(IE라던가.. IE라던가.. IE같은)는 ES6의 코드를 이해하지 못한다. ES6 이상의 최신 자바스크립트 문법으로 개발된 코드를 구형 브라우저에서 정상 동작시키기 위해서는 바벨을 통해 ES5 코드로 변환해 주어야 한다.</p>
<h3 id="바벨의-동작-방식">바벨의 동작 방식</h3>
<p>다음은 바벨의 동작 방식에 대해서 알아보자! 바벨은 플러그인(plugin)과 프리셋(preset) 기반으로 동작한다. </p>
<p>따라서 아무런 설정을 해주지 않고 바벨을 실행시키면 바벨은 아무런 일도 하지 않는다. 사용자는 목적에 따라 사용할 플러그인과 프리셋을 적절하게 설정해 주어야 한다. 바벨 플러그인과 바벨 프리셋에 대해서 조금 더 알아보자.</p>
<h4 id="바벨-플러그인">바벨 플러그인</h4>
<p>바벨 플러그인은 바벨이 어떤 코드를 어떻게 변환할 지에 대한 규칙을 나타낸다. </p>
<p>예를 들어 ES6의 화살표 함수를 일반 함수로 변환해주는 플러그인은 <a href="https://babeljs.io/docs/en/babel-plugin-transform-arrow-functions">@babel/plugin-transform-arrow-functions</a>이며, ES6의 블록 스코프를 지원하도록 변환해주는 플러그인은 <a href="https://babeljs.io/docs/en/babel-plugin-transform-block-scoping">@babel/plugin-transform-block-scoping</a>이다.</p>
<p>바벨이 제공하는 수많은 플러그인들이 있으며 우리는 필요한 플러그인만 설치해서 이용하면 된다.😆</p>
<h4 id="바벨-프리셋">바벨 프리셋</h4>
<p>바벨 프리셋은 플러그인의 모음집이라고 할 수 있다. 개발을 하다보면 수많은 플러그인을 사용해야 하는데, 사용하는 플러그인이 늘어날 때마다 바벨의 설정파일에 플러그인들을 추가해 주어야 한다.</p>
<p>이는 매우 번거롭고 귀찮은 일이다. 따라서 바벨 개발팀은 특정 스펙으로 개발시 필요한 플러그인을 모아 놓은 프리셋을 만들어 두었다.</p>
<p>현재 바벨에서 공식적으로 지원하는 <a href="https://babeljs.io/docs/en/presets/">official preset</a>은 4개이다.</p>
<blockquote>
<ul>
<li>@babel/preset-env</li>
</ul>
</blockquote>
<ul>
<li>@babel/preset-flow</li>
<li>@babel/preset-react</li>
<li>@babel/preset-typescript</li>
</ul>
<p>프리셋은 결국 플러그인들의 집합이므로, 바벨 설정은 곧 바벨 플러그인 설정이라고 볼 수 있다.</p>
<h3 id="바벨-설정-컨피그">바벨 설정 컨피그</h3>
<p>바벨이 무엇인지 알아 보았으니, 이제 바벨 설정을 진행해보자! 
우선 바벨 설정에 필요한 패키지들을 설치하자. </p>
<pre><code class="language-jsx">yarn add -D @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript babel-loader</code></pre>
<p>각 패키지들의 역할은 아래와 같다.</p>
<blockquote>
<ul>
<li>@babel/core : babel의 핵심 기능(코어) 모듈</li>
</ul>
</blockquote>
<ul>
<li>@babel/preset-env : ECMAScript2015+를 변환</li>
<li>@babel/preset-typescript : typescript 문법을 javascript로 변환</li>
<li>@babel/preset-react : JSX로 작성된 코드들을 createElement 함수를 이용한 코드로 변환</li>
<li>babel-loader : 웹팩에서 바벨을 사용하기 위한 로더 제공</li>
</ul>
<p>이제 바벨 설정 파일을 작성해야 한다. 바벨 설정 파일을 작성하는 방법은 두가지가 있는데, <strong>babel.config.js</strong>를 이용하는 방법과 <strong>.babelrc</strong>을 이용하는 방법이다.</p>
<h4 id="babelconfigjson">babel.config.json</h4>
<p>프로젝트 전 범위에 동일한 바벨 옵션을 적용할때 채택하는 방법이다. 루트 디렉토리에 babel.config.json 파일을 만들면 바벨이 해당 파일을 자동으로 찾아서 프로젝트 전 범위에 동일한 옵션을 적용한다. </p>
<p>여러 패키지 디렉토리를 가진 프로젝트에서 하나의 바벨 설정을 가져갈 때 유용하다.</p>
<h4 id="babelrc">.babelrc</h4>
<p>특정 서브셋 디렉토리 또는 파일을 지정해 트랜스파일링 할때 사용하는 방법이다. 예를 들어, 서드파티 라이브러리가 바벨에 의해 트랜스파일링되기 원하지 않는 경우 이용할 수 있다.</p>
<p>이 경우 babel-loader의 configFile 옵션을 이용하면 바벨은 babel.config.json 파일을 자동으로 찾지 않고, 입력한 경로에서 설정 파일을 참조하여 적용하게 된다.</p>
<h4 id="컨피그-시작">컨피그 시작!</h4>
<p>우리는 이번 설정에서 두번째 방법을 적용하여 진행 해 볼 것이다. 프로젝트 root directory에 .babelrc 파일을 생성후 이용할 preset들을 적어주자.</p>
<p>우리는 styled-component와 함께 react 코드를 작성해 볼 것이다.</p>
<pre><code class="language-jsx">// .babelrc
{
  &quot;presets&quot;: [&quot;@babel/preset-react&quot;],
  &quot;plugins&quot;: [&quot;babel-plugin-styled-components&quot;]
}
</code></pre>
<p>또, 웹팩에서 빌드시 바벨을 사용하도록 기존 작성한 웹팩의 로더 부분에 babel-loader를 추가해주자.</p>
<pre><code class="language-jsx">// webpack.common.js
{ // 1
  test: /\.(js|jsx)$/,
  exclude: /node_modules/,
  use: {
  loader: &#39;babel-loader&#39;,
  },
},</code></pre>
<p>이후 웹팩은 빌드시 js나 jsx 파일을 만나게 되면 babel-loader를 실행시키고, 바벨은 .babelrc를 참조하여 코드를 트랜스파일링 하게 된다.</p>
<br>


<h2 id="react-적용하기">React 적용하기</h2>
<h3 id="패키지-설치">패키지 설치</h3>
<p>리액트를 사용하기 위해 필요한 패키지들을 설치해주자.</p>
<pre><code class="language-jsx">yarn add react react-dom styled-components</code></pre>
<p>각 패키지들의 역할은 아래와 같다.</p>
<blockquote>
<p>react : react 핵심 기능(코어)
react-dom : 브라우저, 돔, 웹앱 관리 
styled-components : css-in-js 스타일링 패키지</p>
</blockquote>
<h3 id="기본-코드-작성">기본 코드 작성</h3>
<p>이제 기본적인 리액트 코드를 작성해보자! src 안에 기본이 되는 파일인 App.js와 index.js를 생성하고 아래와 같이 작성하자.</p>
<pre><code class="language-jsx">// App.js
import React from &#39;react&#39;;

const App = () =&gt; {
  return &lt;div&gt;Hello, React!&lt;/div&gt;;
};

export default App;</code></pre>
<pre><code class="language-jsx">// index.js
import React from &#39;react&#39;;
import ReactDOM from &#39;react-dom&#39;;
import App from &#39;./App&#39;;

ReactDOM.render(&lt;App /&gt;, document.getElementById(&#39;root&#39;));
</code></pre>
<p>이제 기존에 작성한 webpack.common.js에서 entry를 새로 작성한 index.js로 수정해주자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/ce8e6022-7c8c-4738-a381-692534f9142b/image.png" alt=""></p>
<p>이제 모든 준비가 완료되었다! yarn start를 통해 webpack dev server를 구동하여 코드가 정상 작동하는지 확인해보면 정상 작동 하는 것을 확인할 수 있다👍</p>
<p><img src="https://images.velog.io/images/seeh_h/post/1b698355-4135-449c-9bec-d4733193b7bd/image.png" alt=""></p>
<br>




<h2 id="기존-코드-적용하기">기존 코드 적용하기</h2>
<p>리액트 코드를 정상적으로 트랜스파일링 하는 것을 확인했으니, 이제 기존 바닐라js로 작성한 코드를 리액트 코드로 변환해보자. </p>
<p>이제 우리는 컴포넌트를 만들 수 있다! 지난번 utils.js파일에 제작해 놓았던 makeAvatar 함수를 Avatar 컴포넌트로 변환하자. </p>
<h3 id="utiljs">util.js</h3>
<p>지난번 작성했던 util.js 코드이다</p>
<p><img src="https://images.velog.io/images/seeh_h/post/c9a81521-5902-42c0-a625-18b21b074292/image.png" alt=""></p>
<br>

<h3 id="avatarjsx">avatar.jsx</h3>
<p>이제 src 밑에 avatar 폴더를 만들고, 컴포넌트 형식으로 코드를 변경하자!</p>
<p><img src="https://images.velog.io/images/seeh_h/post/4e81ffd2-8bab-42c3-b79e-5fcf89354ea2/image.png" alt=""></p>
<h3 id="appjs">App.js</h3>
<p>마지막으로 App.js에서 Avatar component를 불러오자.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/e47d3a85-58e9-496c-8f3e-b7c1ccbacfa7/image.png" alt=""></p>
<h3 id="완성">완성</h3>
<p>이제 다시 컴파일을 해주면 정상 작동 하는것을 확인할 수 있다.</p>
<p><img src="https://images.velog.io/images/seeh_h/post/0fd8d79c-787b-4136-8c77-80135f07d38a/image.png" alt=""></p>
<p>끝!</p>
<h2 id="후기">후기</h2>
<p>기존에 작성한 바닐라JS 기반의 코드에 바벨을 이용하여 리액트 코드까지 적용해 보았다! 전체 코드는 <a href="https://github.com/Siihyun/webpack-boilerplate">Github</a>에서 확인 가능하다 :)</p>
<h3 id="reference">Reference</h3>
<p>이번 포스팅은 아래 게시글들을 참고하여 작성되었다.</p>
<blockquote>
<p><a href="https://babeljs.io/">https://babeljs.io/</a> 
<a href="https://berkbach.com/%EC%9B%B9%ED%8C%A9-webpack-%EA%B3%BC-%EB%B0%94%EB%B2%A8-babel-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-react-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0-fb87d0027766">https://berkbach.com/%EC%9B%B9%ED%8C%A9-webpack-%EA%B3%BC-%EB%B0%94%EB%B2%A8-babel-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-react-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%84%B1%ED%95%98%EA%B8%B0-fb87d0027766</a></p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>