<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>thalassophilia.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Thu, 16 Apr 2026 01:12:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>thalassophilia.log</title>
            <url>https://velog.velcdn.com/images/kim-jaemin420/profile/a0410b78-2b09-44ab-a8ae-f23d811e72fa/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. thalassophilia.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kim-jaemin420" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[tanstack query 오픈소스 기여 후기]]></title>
            <link>https://velog.io/@kim-jaemin420/tanstack-query-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/tanstack-query-%EC%98%A4%ED%94%88%EC%86%8C%EC%8A%A4-%EA%B8%B0%EC%97%AC-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 16 Apr 2026 01:12:44 GMT</pubDate>
            <description><![CDATA[<p>우리 팀에는 Vue로 구성된 프로젝트가 몇 개 있었고, 기존에는 전반적으로 Pinia를 중심으로 상태를 관리하고 있었다. 그런데 서버 상태와 클라이언트 상태를 같은 방식으로 다루는 데에는 분명한 한계가 있었고, 점점 vue-query를 도입해보자는 이야기가 나오기 시작했다.</p>
<p>vue-query를 도입하면서 가장 먼저 부딪힌 주제 중 하나는 &quot;쿼리 키와 쿼리 함수를 어떻게 관리할 것인가&quot;였다. 프로젝트 수가 늘어나고 화면마다 비슷한 데이터 요청 패턴이 반복되다 보니, 각자 useQuery를 호출하면서 queryKey, queryFn, staleTime, select 같은 옵션을 제각각 작성하는 방식은 금방 유지보수 비용으로 돌아올 수밖에 없었다.
그래서 우리 팀은 여러 상황을 가정해본 끝에 query factory 구조를 사용하자고 결정했다.</p>
<p>이 구조를 택한 이유는 단순히 코드를 예쁘게 정리하기 위해서만은 아니었다.
query factory를 사용하면 다음과 같은 장점이 있었다.</p>
<ul>
<li>쿼리 키를 한 곳에서 일관성 있게 관리할 수 있다.</li>
<li>queryFn과 공통 옵션을 함께 묶어 재사용할 수 있다.</li>
<li>같은 데이터를 조회하는 로직을 화면마다 조금씩 다르게 작성하는 일을 줄일 수 있다.</li>
<li>타입 추론을 최대한 유지한 채로 useQuery, prefetchQuery, invalidateQueries 등을 같은 기준으로 다룰 수 있다.</li>
</ul>
<p>특히 우리 팀에서는 &quot;쿼리 키를 표준화해서 관리한다&quot;는 점보다도, 쿼리 옵션까지 하나의 단위로 묶어서 재사용할 수 있다는 점을 중요하게 봤다.
예를 들어 어떤 데이터를 조회할 때 queryKey와 queryFn만 필요한 게 아니라, staleTime, gcTime, placeholderData, select 같은 옵션이 함께 따라붙는 경우가 많다. 이럴 때 query factory를 쓰면 &quot;이 쿼리는 이렇게 동작한다&quot;는 규칙 자체를 하나의 팩토리로 캡슐화할 수 있었다.</p>
<p>그런데 이 컨벤션을 구체적으로 정리하는 과정에서 한 가지가 계속 거슬렸다.
useQuery나 useInfiniteQuery는 query factory가 반환하는 옵션 객체를 자연스럽게 받아서 사용할 수 있었는데, prefetch는 흐름이 달랐다. 기존 코드에서 usePrefetchQuery를 찾아보니 아래와 같이 queryClient에 직접 접근하는 방식으로 사용하고 있었다.</p>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/35f2e51c-c7aa-4a88-8d19-14343b30468b/image.png" alt=""></p>
<p>처음에는 이 정도 차이는 그냥 감수하면 되는 것 아닐까 싶기도 했다. 하지만 생각해볼수록 이 방식은 우리가 정한 방향과 잘 맞지 않았다.</p>
<p>queryClient.prefetchQuery()를 직접 호출하면 결국 호출하는 쪽에서 다시 queryKey, queryFn, 옵션을 조립해야 한다. 그렇게 되면 조회 시점에는 query factory를 쓰고, prefetch 시점에는 다시 수동으로 옵션을 만든다는 이상한 구조가 된다.
겉으로 보기에는 사소한 차이 같지만, 실제로는 다음과 같은 문제가 생긴다.</p>
<ul>
<li>같은 쿼리를 조회할 때와 prefetch할 때 옵션이 미묘하게 달라질 수 있다.</li>
<li>팀이 정한 쿼리 팩토리 구조를 모든 진입점에서 일관되게 활용할 수 없다.</li>
<li>&quot;이 데이터는 어떤 정책으로 캐싱되는가&quot;라는 규칙이 분산된다.</li>
<li>나중에 옵션이 바뀌었을 때 조회 코드와 prefetch 코드를 각각 찾아 수정해야 한다.</li>
</ul>
<p>즉, 문제는 단순히 &quot;컴포저블이 하나 없어서 불편하다&quot;가 아니었다.
query factory를 도입해 얻고 싶었던 장점을 prefetch 경로에서는 제대로 누릴 수 없다는 점이 더 아쉬웠다. 팀이 컨벤션을 정한 이유가 중복 제거와 일관성 확보에 있었는데, prefetch만 예외처럼 빠져 있으면 결국 중요한 지점에서 다시 수동 관리로 돌아가게 되기 때문이다.</p>
<p>공식 문서를 다시 확인해보니, <code>tanstack/vue-query</code>에는 <code>usePrefetchQuery</code>와 <code>usePrefetchInfiniteQuery</code>가 제공되지 않고 있었다. 반면 React Query 쪽에는 이미 훅을 제공해주고 있었고,(사실상 vue 외에 모든 라이브러리/프레임워크에서 재공해주고 있었다) Vue에서는 queryClient를 직접 사용하는 방식을 안내해주고 있었다.</p>
<p>이게 정말 의도된 차이인지 궁금해서 기존 discussion을 찾아봤다. 그 과정에서 나와 비슷한 문제의식을 가진 질문이 이미 올라와 있는 것을 발견했다.</p>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/99ccbfbb-8392-4290-a450-22eadce7b762/image.png" alt=""></p>
<p>메인테이너의 답변은 꽤 긍정적이었다. 직접 프로젝트에서 컴포저블을 만들어 써도 되고, 원한다면 usePrefetchQuery를 추가하는 PR을 올려도 된다는 내용이었다.</p>
<p>다만 그 discussion은 2년 전에 작성된 것이었기 때문에, 지금도 같은 방향으로 받아들여질지 확인하고 싶었다. 그래서 다시 한 번 discussion을 남겼고,</p>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/646dda4b-52c2-4f7d-af8f-dd43c9fc8a85/image.png" alt=""></p>
<p>tkDodo 님이 흔쾌히 PR을 올려도 된다고 답변해주셨다.</p>
<p>답변을 확인한 뒤에는 &quot;우리 팀에서만 임시로 래퍼를 만들어 쓸까, 아니면 아예 upstream에 기여할까&quot;를 잠깐 고민했다.
로컬 래퍼를 만드는 건 빠른 해결책이지만, 결국 라이브러리 바깥에 별도의 규칙과 구현을 하나 더 두는 셈이었다. 그렇게 되면 팀 내부에서는 문제를 해결할 수 있어도, 장기적으로는 유지보수 포인트가 또 하나 생긴다. 반대로 upstream에 반영된다면 우리 팀뿐 아니라 같은 불편함을 겪는 다른 Vue Query 사용자들도 같은 방식으로 해결할 수 있다.
결국 &quot;팀의 불편함을 임시 우회가 아니라 구조적으로 없애보자&quot;는 쪽으로 생각이 기울었고, 구현을 시작하게 되었다.</p>
<p>우선 tanstack/query 레포를 포크한 뒤 CONTRIBUTING.md를 읽으면서 어떤 방식으로 기여를 진행해야 하는지부터 확인했다. 이후에는 이미 구현되어 있던 React Query 쪽 코드를 기준점으로 삼아 구조를 파악했다.
다만 단순 복붙으로 끝낼 수 있는 작업은 아니었다. 목표는 React와 동일한 사용 경험을 제공하되, Vue Query가 가지고 있는 반응성 특성을 자연스럽게 반영하는 것이었기 때문이다.</p>
<p>구현 과정에서는 특히 다음 부분을 신경 썼다.</p>
<p>React Query의 usePrefetchQuery 동작 방식과 API 표면을 먼저 확인했다.
Vue Query 쪽 기존 composable들이 MaybeRef, getter, computed 등을 어떻게 옵션으로 처리하는지 살폈다.
queryKey, enabled, 기타 옵션들이 Vue의 반응형 값으로 들어왔을 때도 자연스럽게 동작하도록 맞췄다.
단순히 <code>usePrefetchQuery</code>만 추가하는 것이 아니라, 빠져 있던 <code>usePrefetchInfiniteQuery</code>까지 함께 구현해 API 일관성을 맞췄다.
구현만으로 끝내고 싶지는 않았다. 이런 성격의 기능은 &quot;된다&quot;보다 &quot;기존 패턴과 충돌 없이 안정적으로 동작한다&quot;가 훨씬 중요하다고 생각했기 때문이다. 그래서 런타임 테스트와 타입 테스트를 함께 작성했다.
특히 아래와 같은 경우를 검증하려고 했다.</p>
<pre><code>- 쿼리 상태가 이미 캐시에 있을 때는 불필요하게 prefetch하지 않는지
- reactive option이 변경되었을 때 기대한 방식으로 반응하는지
- 타입 추론이 기존 query options 흐름을 해치지 않는지
- infinite query에서도 동일한 사용 경험을 제공하는지</code></pre><p>여기에 더해 문서도 함께 보완했다. 기능이 추가되더라도 공식 문서에 드러나지 않으면 실제 사용자 입장에서는 존재하지 않는 기능과 크게 다르지 않다고 생각했기 때문이다.
그래서 API 레퍼런스 문서에 <code>usePrefetchQuery</code>와 <code>usePrefetchInfiniteQuery</code> 관련 내용을 추가하고, 내비게이션에서도 해당 항목을 찾을 수 있도록 반영했다.</p>
<p>PR을 올리기 전에는 <code>CONTRIBUTING.md</code> 가이드에 적힌 절차대로 테스트를 모두 수행했다. 로컬에서 필요한 검증을 마친 뒤 PR을 올렸고, 약 열흘 정도 지난 후 메인테이너가 리뷰 후 머지해주었다.</p>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/3f9f0d35-12b1-4a26-8d93-10ee963f2352/image.png" alt=""></p>
<p><a href="https://github.com/TanStack/query/pull/10372">PR 링크</a></p>
<p>이제 vue-query에서 <code>usePrefetchQuery</code>와 <code>usePrefetchInfiniteQuery</code>를 <strong>@tanstack/vue-query 5.98.0</strong> 이상부터 사용할 수 있다.</p>
<p>이번 경험이 특히 의미 있었던 이유는, 단순히 &quot;오픈소스에 기여했다&quot;는 사실 때문만은 아니다.
처음 출발점은 꽤 작았다. 팀에서 Vue Query 도입 컨벤션을 논의하다가, query factory를 중심으로 구조를 맞추고 싶었고, 그 과정에서 prefetch만 유독 예외처럼 느껴졌다. 처음에는 그냥 팀 내부 유틸로 감싸서 해결할 수도 있었겠지만, 그렇게 하면 우리가 중요하게 생각했던 일관성과 재사용성의 기준이 라이브러리 바깥으로 밀려나게 된다. 나는 가능하면 팀이 정한 좋은 기준을 &quot;각자 알아서 지키는 규칙&quot;이 아니라, 도구 자체가 자연스럽게 받쳐주는 형태로 만들고 싶었다.</p>
<p>결국 이번 컨트리뷰팅은 기능 하나를 추가하는 작업이라기보다, 팀이 더 일관된 방식으로 서버 상태를 다루게 만들고 싶다는 문제의식에서 시작된 일이었다.
실제로 컨벤션을 정하는 과정에서 발견한 불편함을 그냥 참고 넘어가지 않고, 왜 불편한지 구조적으로 설명해보고, 라이브러리 레벨에서 풀 수 있는지 확인하고, 그 해결책을 직접 구현해 반영해봤다는 점이 개인적으로도 꽤 인상 깊었다.</p>
<p>무엇보다 좋았던 건, 이 작업이 개인적인 만족으로 끝나지 않았다는 점이다. 팀 입장에서는 query factory와 query options를 조회와 prefetch 양쪽에서 더 일관되게 활용할 수 있게 되었고, 나 역시 &quot;우리 팀이 더 좋은 방식으로 개발할 수 있도록 필요한 기반을 직접 개선해볼 수 있다&quot;는 감각을 얻었다.
앞으로도 팀에서 일하다가 반복적으로 마주치는 불편함이 있다면, 단순히 우회하는 데서 멈추지 않고 정말 개선할 가치가 있는 문제인지 한 번 더 생각해보고 싶다. 그리고 그게 가능하다면, 팀 내부뿐 아니라 바깥 생태계에도 도움이 되는 방식으로 풀어내고 싶다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스] 11.19 - 12.18 한달 회고]]></title>
            <link>https://velog.io/@kim-jaemin420/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4-11.19-12.18-%ED%95%9C%EB%8B%AC-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@kim-jaemin420/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4-11.19-12.18-%ED%95%9C%EB%8B%AC-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 01 Jan 2024 04:39:15 GMT</pubDate>
            <description><![CDATA[<p>데브코스를 시작한지도 어느 덧 3개월이 훌쩍 지났습니다. 이번 달의 강의에서는 <code>Vue</code>와 <code>React</code>를 중점으로 배우고 프로젝트를 진행했습니다.</p>
<h1 id="vue-사용후기">vue 사용후기</h1>
<h2 id="pinia-적용">Pinia 적용</h2>
<p>강의에서는 pinia의 option stores를 사용하여 프로젝트를 진행하였습니다. 영화 검색기에도 option store를 사용했는데 actions가 두 개 이상 늘어나니 가독성이 급격히 떨어지는 것을 느낄 수 있었습니다.
pinia에서 제공하는 다른 방법은 setup store입니다. 상태, getter, actions를 객체로 정의하는 option stores와 달리 setup store는 커스텀 훅과 비슷한 형식으로 상태, getter, actions를 정의할 수 있습니다. 이로 인해 option stores보다 action과 상태가 한 눈에 들어와 가독성, 유지 보수 면에서 좋다고 생각했습니다. 또, 프로젝트 전반에서 composition api를 사용하고 있기 때문에 일관성도 있다고 생각해 변경하게 되었습니다.</p>
<h2 id="usefetch-구현">useFetch 구현</h2>
<p>바닐라 자바스크립트로 프로젝트를 구현할때부터 <code>useFetch</code>를 만들어 사용하려는 시도를 했습니다. <code>get</code> 요청은 다른 요청과 다르게 UI와 밀접한 연관이 있습니다. 이는 데이터가 상태로 관리되어야 하기 때문에 따로 커스텀 훅 형식으로 개발해두면 다양한 <code>get</code> 요청에서 사용하여 중복 로직을 줄이고 함수들이 한 가지 일에 더 집중할 수 있다고 생각했습니다.</p>
<p>바닐라 자바스크립트 프로젝트에서는 렌더링 이슈 때문에 결국 <code>useFetch</code>를 사용하지 못했습니다. 이번 프로젝트에서 처음부터 <code>useFetch</code>를 구현할 생각은 없었지만 이번에 <code>get</code> 요청이 세 가지였고, 스토어에 로딩 상태, 에러 상태, 데이터 상태를 변경하는 로직들이 여러 번 등장하여 결국 만들게 되었습니다. 짧은 시간 안에 구현했기 때문에 범용성을 고려하여 개발하는 것이 까다로웠습니다. 특히 pagination 관련 로직들의 구현과 타입이 까다로웠습니다.</p>
<p>쉽게 생각하면 <code>tanstack query</code>의 <code>useQuery</code>를 가져다 사용하면 단순히 끝날 일이지만 손수 구현해가며 범용성과 구조를 고민해보는 시간이 많이 도움이 되었습니다. 기회가 된다면 <code>useQuery</code>를 참고하여 pagination 로직을 다시 구현해보는 것도 좋을 것 같습니다.</p>
<h2 id="후기">후기</h2>
<p>프로그래머스를 통해 Vue를 배우기 전까지는 Vue에 대한 필요성을 크게 느끼지 못했습니다. 리액트가 지배적인 시장 환경에서 저는 리액트를 주로 사용하고 있었고, Vue를 배울 계획이 없었습니다. 하지만 실제로 Vue를 경험해보니, 그 독특한 장점이 눈에 띄었고 흥미로웠습니다.</p>
<p>최근 리액트에 대한 불만이 쌓이며, 내심 리액트의 세계가 빨리 끝나기를 바라고 있었습니다. 리액트의 자유도는 다양한 기술적 접근을 가능하게 했지만, 이로 인해 많은 고민을 하기도 했습니다. 기술을 다루는 방식과 형태가 여러 가지였고, 하나의 기능을 더 할 때마다 지금 나의 방식이 최선에 가까운 방식인지 고민하고 best practice를 찾아보는 시간이 많았습니다. 이로 인해 불필요하게 개발 속도가 느려진다는 생각이 들기도 했고 스트레스로 다가왔습니다.
또, 리액트의 함수형 컴포넌트는 props나 상태가 바뀌면 컴포넌트와 그 컴포넌트의 자식 컴포넌트가 리렌더링 됩니다. 이는 리액트 함수형 컴포넌트의 고유한 특성이기 때문에 불필요한 리렌더링이 발생하진 않는지, 성능 이슈가 있는 건 아닌지 확인해야 하고, 이로 인해 <code>useCallback</code>, <code>useMemo</code>, <code>useEffect</code> 같은 것을 사용해야 했습니다. 앞에서 말한 것과 같이, 리액트는 자유롭기 때문에 <code>useEffect</code>나 <code>useMemo</code>를 어느 상황에서, 언제 써야 하는지, 또 쓴다면 어떻게 사용해야 하는지, 어떤 방법이 가독성을 덜 해치는지에 대한 고민도 뒤따라왔습니다.</p>
<p>이런 배경에서 Vue에 대해 알아보니, 여러 가지 장점이 마음에 들었습니다. 우선, props나 state가 변경된다고 script가 재평가 되는 것이 아니기 때문에 성능 최적화나 리렌더링에 대한 고민이 없었습니다. 프레임워크로서의 강제성은 기술적 결정에 대한 고민을 줄여주었고, 이벤트 수식어를 사용하여 이벤트 리스닝을 더 세밀하게 제어할 수 있습니다. 특히 Transition 컴포넌트는 리액트에서의 애니메이션 처리와 비교해 간결하고 편리해보였습니다.</p>
<p>그러나 막상 프로젝트를 시작하니 어색하고 불편한 점도 있었습니다.
리액트에 익숙해져 있어 부모 상태가 변경되면 당연히 자식 컴포넌트가 리렌더링 된다고 생각하여 꽤 많은 시간을 고민하기도 하고, 문제가 생겨서 검색했을 때 자료가 적어 애를 먹기도 했습니다. 또 타입스크립트로 ref로 선언한 변수의 타입을 지정할 때 항상 <code>Ref</code>를 사용해야 하는 점이 불편하기도 했습니다.
또, 리액트는 모든 이벤트를 delegation으로 처리하기 때문에 항상 돔에 직접 이벤트 바인딩해도 성능 이슈가 없으나, Vue는 각 돔 요소에 직접 이벤트 리스너를 할당하기 때문에 delegation을 직접 해야 하는 것도 불편한 점 중 하나였습니다.</p>
<p>Vue에 대해 딥 다이브한 것은 아니지만 짧게 나마 새로운 기술을 익히고 기존에 알고 있던 기술과 장단점을 알아 보는 게 즐거웠습니다.</p>
<h1 id="프로젝트-후기">프로젝트 후기</h1>
<p>작은 프로젝트를 현재 팀원들과 함께 협업하고 있습니다. 팀원분들이 항상 적극적이고 다양한 의견을 내주셔서 프로젝트가 더욱 풍부하고 다채로운 방향으로 나아가고 있다고 생각합니다. 의견 조율과 협업 과정에서 발생하는 어려움이 있지만, 이것 또한 중요한 학습 기회가 되고 있습니다. 서로 다른 배경과 경험을 가진 개발자들과 함께 일하면서, 의사소통의 중요성과 팀워크의 가치를 더욱 깊이 이해하게 되었습니다.</p>
<p>프로젝트의 진행 과정에서, 커뮤니케이션의 역할이 얼마나 중요한지 다시 한번 깨닫게 되었습니다. 모든 팀원이 자신의 의견을 자유롭게 표현할 수 있는 환경을 조성함으로써, 각자의 아이디어와 기술을 최대한 활용할 수 있었습니다.</p>
<p>개발 컨벤션과 스타일의 차이를 조율하는 과정은 시간이 많이 소요되고 있지만, 이를 통해 각 팀원의 개발 방식과 생각을 더 깊이 이해할 수 있었습니다. 서로의 작업 스타일을 존중하고 배우면서, 협업을 하는 방식을 배워갈 수 있었습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[바닐라 자바스크립트] 함수형 컴포넌트 만들기: 렌더링 최적화, 컴포넌트 단위 렌더링
]]></title>
            <link>https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%8B%A8%EC%9C%84-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-%EB%A0%8C%EB%8D%94%EB%A7%81-%EC%B5%9C%EC%A0%81%ED%99%94-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%8B%A8%EC%9C%84-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Wed, 13 Dec 2023 09:26:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>[바닐라 자바스크립트] 함수형 컴포넌트 만들기 시리즈
1편: <a href="https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0">컴포넌트 생성하기</a>
2편: <a href="https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-useState-%EB%A7%8C%EB%93%A4%EA%B8%B0">useState 만들기</a></p>
</blockquote>
<p>지난 포스트에서 <code>useState</code>까지 만들어봤습니다. 이번 포스트에서는 현재 프로젝트의 큰 문제점인 전체 컴포넌트 리렌더링 이슈를 개선해보겠습니다.</p>
<h2 id="✅-컴포넌트-단위-렌더링">✅ 컴포넌트 단위 렌더링</h2>
<h3 id="📍-계획">📍 계획</h3>
<p>useState의 <code>setState</code>가 호출되어 리렌더링이 될 때 어떻게 해당 컴포넌트만 리렌더링할 수 있을까요?
변경이 일어난 컴포넌트를 돔에서 찾아, 새로운 돔으로 갈아끼워주면 다른 컴포넌트들은 리렌더링되지 않을 수 있을 것 같습니다. 변경이 일어난 컴포넌트를 돔에서 쉽게 찾기 위해서 컴포넌트를 생성할때 컴포넌트 이름을 <code>id</code>로 가진 <code>div</code>으로 한 번 감싸 렌더링하면 쉽게 접근할 수 있습니다.</p>
<p>아래와 같은 흐름으로 구현하면 컴포넌트 단위 렌더링이 되도록 할 수 있을 것 같습니다.</p>
<blockquote>
</blockquote>
<ol>
<li>컴포넌트를 생성할 때, 컴포넌트 이름을 <code>id</code>로 가진 <code>div</code>으로 한 번 감싸 렌더링합니다.
ex) <code>&lt;div id=&quot;Todo&quot;&gt;${todo.element}&lt;/div&gt;</code></li>
<li><code>render</code> 함수를 호출할때 현재 변경이 일어난 컴포넌트를 넘깁니다.</li>
<li><code>render</code> 함수는 변경이 일어난 컴포넌트 이름을 <code>id</code>로 가진 돔이 있는지 확인하고, 있다면 새로 컴포넌트를 생성하여 innerHTML로 갈아끼워줍니다.</li>
</ol>
<h3 id="📍-기존-코드-확인">📍 기존 코드 확인</h3>
<ul>
<li><code>src/index.js</code><pre><code class="language-js">import createComponent from &#39;./core/component.js&#39;;
import App from &#39;./App.js&#39;;
</code></pre>
</li>
</ul>
<p>const render = () =&gt; {
    const $app = document.getElementById(&#39;app&#39;);
    const appComponent = createComponent(App);</p>
<pre><code>  $app.innerHTML = appComponent.element;
  appComponent.bindEvents();</code></pre><p>};</p>
<p>render();</p>
<pre><code>현재 `render` 함수는 App 컴포넌트를 생성하고 innerHTML을 사용하여 돔에 그리도록 구현되어 있습니다. 이 함수는 useState 훅에서 `setState`가 호출되어 상태가 변경되면 호출됩니다. 그렇기 때문에 작은 컴포넌트의 상태가 변경되더라도 전체 App이 다시 생성되고 그려집니다.

- `src/core/hooks/useState.js`

```js
import { getCurrentComponent } from &#39;../currentComponent.js&#39;;
import render from &#39;../render.js&#39;;

const componentsState = {};

function useState(initialValue) {
  // ...생략

  const setState = newValue =&gt; {
    const currentState = componentsState[id][stateIndex];

    const updatedState =
      typeof newValue === &#39;function&#39; ? newValue(currentState) : newValue;

    if (currentState !== updatedState) {
      componentsState[id][stateIndex] = updatedState;
      render();
    }
  };

  return [stateValue, setState];
}

export default useState;</code></pre><h2 id="✅-1-컴포넌트-생성시-div로-감싸-렌더링">✅ 1. 컴포넌트 생성시 div로 감싸 렌더링</h2>
<p>우선, 변경이 일어난 컴포넌트를 수월하게 찾기 위해 컴포넌트를 생성할 때 컴포넌트 이름을 <code>id</code>로 가진 <code>div</code>로 감싸는 것부터 해보겠습니다.</p>
<ul>
<li><code>src/core/component.js</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/2b6390dd-44e4-4f21-bf52-5832bf10b845/image.png" alt=""></p>
<p>현재 생성된 컴포넌트의 이름을 <code>id</code>로 가진 <code>div</code>로 감싸 다시 컴포넌트 인스턴스의 <code>element</code>에 할당해주었습니다.</p>
<h2 id="✅-2-새로운-render-함수-구현">✅ 2. 새로운 render 함수 구현</h2>
<p>우선, 기존에 <code>src/index.js</code>에 있는 <code>render</code>는 그대로 두고 새롭게 <code>render</code> 함수를 구현해보겠습니다.</p>
<ul>
<li><code>src/core/render.js</code></li>
</ul>
<pre><code class="language-js">const render = (component, props) =&gt; {
  // 여기서 createComponent를 호출
};

export default render;</code></pre>
<p>파라미터로 <code>component</code>와 <code>props</code>를 받아 <code>createComponent</code>를 호출하여 변경이 일어난 컴포넌트를 다시 호출합니다.</p>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/875caa26-4c47-40cd-9e06-5bf4da0b02d5/image.png" alt=""></p>
<p>변경이 일어나 리렌더링 해야 하는 컴포넌트를 <code>getElementById</code>로 찾고, 컴포넌트가 존재한다면 갈아 끼워줍니다. <code>innerHTML</code>을 사용할 경우 <code>&lt;div id=&quot;componentId&quot;&gt;&lt;/div&gt;</code>가 계속 중첩되어 렌더링 될테니 자신을 포함시켜 갈아끼우는 <code>outerHTML</code>을 사용합니다.</p>
<p><code>render</code> 함수는 <code>useState</code>에서 사용되고 있습니다. <code>useState</code>는 자신을 호출한 컴포넌트 정보를 <code>currentComponent</code>를 통해 알 수 있습니다. 상태가 변경됐을때 상태를 사용하는 컴포넌트를 리렌더링 하기 위해서는 <code>currentComponent</code>를 <code>render</code> 함수의 인수로 넘겨야 합니다.
현재 <code>currentComponent</code>에 저장되는 컴포넌트 정보는 컴포넌트 <code>id</code>와 <code>stateIndex</code>입니다. 여기에 추가로 <code>컴포넌트 함수</code> 자체와 <code>props</code>를 저장해야 합니다.</p>
<h2 id="✅-3-usestate에서-사용할-currentcomponent에-정보-추가">✅ 3. useState에서 사용할 currentComponent에 정보 추가</h2>
<ul>
<li><code>src/core/component.js</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/bc8a336a-7f72-41ff-95ff-ada94829138f/image.png" alt=""></p>
<p>기존 render는 App 컴포넌트만 렌더링 하는 반면, 새로 구현된 render는 파라미터로 컴포넌트 함수와 props를 받아 컴포넌트 인스턴스를 생성하고 innerHTML로 렌더링하고 있습니다. 기존 <code>render</code> 함수를 사용하고 있는 곳을 모두 변경하도록 하겠습니다.</p>
<ul>
<li><code>src/index.js</code></li>
</ul>
<pre><code class="language-js">import createComponent from &#39;./core/component.js&#39;;
import App from &#39;./App.js&#39;;

const $app = document.getElementById(&#39;app&#39;);
const appComponent = createComponent(App);

$app.innerHTML = appComponent.element;
appComponent.bindEvents();</code></pre>
<p>기존 <code>render</code> 함수를 제거하고 App 컴포넌트만 생성하여 렌더합니다.</p>
<ul>
<li><code>src/core/useState.js</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/25310c2f-cffd-4adc-8862-5ffa8230d794/image.png" alt=""></p>
<p><code>core/component.js</code>에서 컴포넌트 함수와 props를 주입해주고 있기 때문에 <code>useState</code>에서 가져와 <code>render</code> 함수의 인수로 주입하고 있습니다.</p>
<p>그럼 이제, 컴포넌트 단위로 렌더링이 이루어지는지 확인해보겠습니다.</p>
<h2 id="✅-컴포넌트-단위-렌더링-확인">✅ 컴포넌트 단위 렌더링 확인</h2>
<p>간단하게 제목을 토글하는 컴포넌트와 숫자를 count하는 컴포넌트 두 개를 렌더링 하겠습니다.</p>
<ul>
<li><code>src/App.js</code><pre><code class="language-js">import createComponent from &#39;./core/component.js&#39;;
import ToggleTitle from &#39;./components/ToggleTitle.js&#39;;
import Count from &#39;./components/Count.js&#39;;
</code></pre>
</li>
</ul>
<p>function App() {
  const toggleTitleComponent = createComponent(ToggleTitle);
  const countComponent = createComponent(Count);</p>
<p>  const bindEvents = () =&gt; {
    toggleTitleComponent.bindEvents();
    countComponent.bindEvents();
  };</p>
<p>  return {
    element: <code>&lt;main&gt;
        ${toggleTitleComponent.element}
        ${countComponent.element}
      &lt;/main&gt;</code>,
    bindEvents,
  };
}</p>
<p>export default App;</p>
<pre><code>- `src/components/ToggleTitle.js`
```js
import useState from &#39;../core/hooks/useState.js&#39;;

function ToggleTitle() {
  const [showTitle, setShowTitle] = useState(true);

  const bindEvents = () =&gt; {
    const toggleTitleButton = document.querySelector(&#39;.toggle-title&#39;);

    toggleTitleButton.addEventListener(&#39;click&#39;, () =&gt; setShowTitle(!showTitle));
  };

  return {
    element: `
      &lt;h1&gt;컴포넌트 단위 렌더링&lt;/h1&gt;
      &lt;button class=&quot;toggle-title&quot;&gt;제목 토글&lt;/button&gt;
    `,
    bindEvents,
  };
}

export default ToggleTitle;</code></pre><ul>
<li><code>src/components/Count.js</code><pre><code class="language-js">import useState from &#39;../core/hooks/useState.js&#39;;
</code></pre>
</li>
</ul>
<p>function Count() {
  const [count, setCount] = useState(0);</p>
<p>  const bindEvents = () =&gt; {
    const addCount = document.querySelector(&#39;.add-count&#39;);</p>
<pre><code>addCount.addEventListener(&#39;click&#39;, () =&gt; {
  setCount(count + 1);
});</code></pre><p>  };</p>
<p>  return {
    element: <code>&lt;div&gt;count: ${count}&lt;/div&gt;
      &lt;button class=&quot;add-count&quot;&gt;더하기&lt;/button&gt;</code>,
    bindEvents,
  };
}</p>
<p>export default Count;</p>
<pre><code>제대로 렌더링 되는지 확인해보았습니다.
![](https://velog.velcdn.com/images/kim-jaemin420/post/3b4d4f4d-db35-4ced-a1de-c4fd30e23143/image.gif)

최초 1번의 상태 변화 후에는 이벤트 리스너가 호출되지 않습니다. 그 이유는 현재 `bindEvents` 함수를 상위 컴포넌트에서 호출해주고 있기 때문입니다. 처음 렌더링 시에는 엔트리 포인트인 App이 생성되면서 하위 컴포넌트의 `bindEvents`를 호출해주지만, 그 이후에 `Count` 컴포넌트만 호출될 때에는 `bindEvents`가 호출되지 않기 때문에 이벤트 리스너가 바인딩 되지 않습니다.

이를 해결하기 위해 컴포넌트가 생성될때 `bindEvents`를 호출하도록 수정하겠습니다.

## ✅ 컴포넌트 생성시, 이벤트 바인딩 함수 호출

- `src/core/component.js`

![](https://velog.velcdn.com/images/kim-jaemin420/post/2a51eb1f-fac4-4895-86b5-11378f645e39/image.png)

`requestAnimationFrame`을 사용하여 돔이 생성된 후에 이벤트 바인딩 함수가 호출하도록 하였습니다. 컴포넌트가 생성될때 이벤트 바인딩을 해주니 더 이상 상위 컴포넌트가 하위 컴포넌트의 이벤트 바인딩 함수를 호출할 필요가 없습니다

- `src/App.js`

```js
import createComponent from &#39;./core/component.js&#39;;
import ToggleTitle from &#39;./components/ToggleTitle.js&#39;;
import Count from &#39;./components/Count.js&#39;;

function App() {
  const toggleTitleComponent = createComponent(ToggleTitle);
  const countComponent = createComponent(Count);

  return {
    element: `
      &lt;main&gt;
        ${toggleTitleComponent.element}
        ${countComponent.element}
      &lt;/main&gt;
    `,
  };
}

export default App;</code></pre><p>마지막으로 결과를 확인하겠습니다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/e863af78-86c7-4228-8aff-23e1acd2c9ad/image.gif" alt=""></p>
<p>첫 렌더링 이후에는 Count 상태나 ToggleTitle의 상태가 변하더라도 다른 컴포넌트에 영향을 주지 않습니다.</p>
<p>이렇게 간단하게 렌더링 최적화를 해보고, 이벤트 바인딩도 부모로부터 분리하게 되었습니다. 다음 포스트에서는 전역 상태 관리를 직접 구현해보도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[바닐라 자바스크립트] 함수형 컴포넌트 만들기: useState 만들기]]></title>
            <link>https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-useState-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-useState-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 05 Dec 2023 16:45:51 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>[바닐라 자바스크립트] 함수형 컴포넌트 만들기 시리즈
1편: <a href="https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0">컴포넌트 생성하기</a></p>
</blockquote>
<p>이전 포스트에서는 컴포넌트를 생성하는 함수를 구현하고 현재 사용 중인 상태 관리 방식의 문제점들에 대해 살펴보았습니다. 특히, 외부에서 state 변수를 직접 조작하는 구조의 위험성과 복잡성에 초점을 맞추었습니다. 이런 접근은 예기치 못한 사이드 이펙트를 일으키고, 상태의 추적과 디버깅을 어렵게 만듭니다. 이러한 문제들을 해결하기 위해, 더 안정적이고 선언적인 상태 관리 방법을 도입할 필요성을 느끼게 되었습니다.</p>
<p>이번 포스트에서는 React의 <code>useState</code>와 유사한 기능을 직접 구현하는 방법을 탐구해보려 합니다. <code>useState</code>는 React에서 상태를 관리하는 가장 기본적이면서도 강력한 훅입니다. 이를 통해 상태를 보다 효과적으로 관리하고, 컴포넌트의 렌더링을 더욱 효율적으로 제어할 수 있습니다. 또, 상태 관리의 복잡성을 줄이고 코드의 가독성과 유지보수성을 향상시킬 수 있습니다.</p>
<p>그럼 <code>useState</code>와 비슷하게 동작하는 훅을 <code>javascript</code>로 단계별로 구현해보겠습니다.</p>
<h2 id="✅-프로젝트-세팅">✅ 프로젝트 세팅</h2>
<p>이전 포스트에 이어 프로젝트를 진행하겠습니다. 지금까지 작성된 코드는 아래와 같습니다.</p>
<ul>
<li><p><code>src/core/component.js</code></p>
<pre><code class="language-js">function createComponent(component) {
  const componentInstance = component();

    return componentInstance;
}
</code></pre>
</li>
</ul>
<p>export default createComponent;</p>
<pre><code>
- `src/index.js`
```js
import createComponent from &#39;./core/component.js&#39;;
import App from &#39;./App.js&#39;;

const render = () =&gt; {
    const $app = document.getElementById(&#39;app&#39;);
    const appComponent = createComponent(App);

      $app.innerHTML = appComponent.element;
};

render();</code></pre><ul>
<li><code>src/App.js</code><pre><code class="language-js">import createComponent from &#39;./core/component.js&#39;;
import Todos from &#39;./components/Todos.js&#39;;
</code></pre>
</li>
</ul>
<p>function App() {
     const state = {
        todos: [&#39;item1&#39;, &#39;itme2&#39;];
    };</p>
<pre><code>  const setState = { /* ... 생략 */};

const todosComponent = createComponent(Todos, { todos: state.todos });

  return {
    element: `
        &lt;main&gt;
            ${todosComponent.element}
        &lt;/main&gt;
    `,
};</code></pre><p>}</p>
<p>export default App;</p>
<pre><code>
- `src/components/Todos.js`
```js
function Todos({ todos }) {
    return {
        element: `
            &lt;ul&gt;
                ${todos.map((todo) =&gt; `&lt;li&gt;${todo}&lt;/li&gt;`).join(&#39;&#39;)}
            &lt;/ul&gt;
        `,
    };
}

export default Todos;</code></pre><p>간단한 프로젝트 세팅을 마쳤습니다. 그럼 이제 어떤 흐름으로 <code>useState</code>를 구현할지 생각해보겠습니다.</p>
<h2 id="✅-usestate에서-상태-저장-방법">✅ useState에서 상태 저장 방법</h2>
<h3 id="📍-컴포넌트의-상태-저장하기">📍 컴포넌트의 상태 저장하기</h3>
<p>우선 useState로 상태를 관리하려면 컴포넌트의 상태를 변수에 저장하는 과정이 필요합니다. 여러 컴포넌트의 다양한 상태를 저장하려면 다음과 같은 형태로 저장할 수 있을 것 같습니다.</p>
<pre><code class="language-js">const componentsState = {
    ComponentA: [1, true, [&#39;todos&#39;]],
      ComponentB: [&#39;name&#39;],
};</code></pre>
<p>ComponentA에서는 값이 숫자인 상태, 불리언인 상태, 배열인 상태 세 가지를 사용하고 있고 ComponentB는 값이 문자열인 상태 한 가지만 사용하고 있습니다.</p>
<p><code>componentsState</code> 객체에 컴포넌트 이름이 키로, 상태 배열이 값으로 저장됩니다. <code>useState</code> 함수 내에서 현재 <code>stateIndex</code>를 저장하고 <code>stateIndex</code>를 1씩 늘려주면 현재 상태는 자신의 상태 index를 기억할 수 있고 다음 상태는 이전 상태 다음으로 저장될 수 있습니다.</p>
<pre><code class="language-js">const useState = () =&gt; {
      const componentName = 현재 컴포넌트의 이름;
      let stateIndex = 현재 컴포넌트의 stateIndex값;

    const setState = () =&gt; {
        // setState는 자신의 상위 스코프에 있는 stateIndex 값을 기억한다.
    };

      stateIndex += 1;

      return [componentsState[componentName][stateIndex], setState];
};</code></pre>
<p>이렇게 저장한다면 컴포넌트 내에서 상태들이 섞이지 않으면서 자신이 저장된 index 값을 기억할 수 있습니다.</p>
<p>그럼 이제 현재 렌더링 중인 컴포넌트에서 어떻게 <code>컴포넌트 이름</code>과 <code>stateIndex</code>를 가져와야 할지 고민해보겠습니다.</p>
<h3 id="📍-컴포넌트의-이름과-stateindex-초기값">📍 컴포넌트의 이름과 stateIndex 초기값</h3>
<p><code>useState</code>에서 현재 렌더링 중인 컴포넌트의 정보를 알려면 컴포넌트가 생성될때 이름과 <code>stateIndex</code> 초기값을 부여할 수 있을 것 같습니다.</p>
<ul>
<li><code>src/core/component.js</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/fcc3b4ae-8acd-461b-91ae-5be5fdcabb69/image.png" alt=""></p>
<p>기존 createComponent는 단순히 컴포넌트를 호출해서 반환하는 역할을 했습니다. <code>useState</code>에서 현재 렌더링중인 컴포넌트 정보를 알기 위해 컴포넌트를 호출할때 <code>currentComponent</code>라는 변수에 컴포넌트 이름과 <code>stateIndex</code>의 초기값을 할당해줍니다. <code>currentComponent</code>를 export 하고 있기 때문에 <code>useState</code>에서 import 하여 사용할 수 있습니다.</p>
<p>이렇게 <code>currentComponent</code> 변수에 현재 컴포넌트의 이름과 상태 index를 할당하면 <code>useState</code>에서 활용할 수 있습니다. </p>
<p>그런데 현재 구조에서는 한 가지 문제점이 있습니다. 컴포넌트 내에서 다른 여러 컴포넌트를 렌더링 했을 때 <code>currentComponent</code>에 저장된 값은 무엇일까요?
중첩된 컴포넌트가 어떻게 렌더링되는지 절차적으로 알아보겠습니다.</p>
<h2 id="✅-컴포넌트-렌더링-과정과-현재-컨텍스트-기억하기">✅ 컴포넌트 렌더링 과정과 현재 컨텍스트 기억하기</h2>
<h3 id="📍-중첩된-컴포넌트-렌더링-과정">📍 중첩된 컴포넌트 렌더링 과정</h3>
<p>우선 다음과 같은 상황을 가정하겠습니다.</p>
<pre><code class="language-js">function ParentComponent() {
    const [count, setCount] = useState(0);

      const childComponent = createComponent(ChildComponent);

      return {
        element: `
            &lt;div&gt;
                ${childComponent.element}
                count: ${count}
            &lt;/div&gt;
        `,
    };
}</code></pre>
<p><code>ParentComponent</code>내에서 <code>count</code>라는 상태를 선언하고 그 뒤에 <code>createComponent</code>를 사용하여 <code>ChildComponent</code>를 생성하고 렌더링까지 하고 있습니다.
그렇다면 콘솔 로그를 찍어 <code>currentComponent</code>에 어떤 값이 할당되고 있는지 확인해보겠습니다.</p>
<pre><code class="language-js">function ParentComponent() {
      console.log(1, currentComponent);

    const [count, setCount] = useState(0);

      console.log(2, currentComponent);

      const childComponent = createComponent(ChildComponent);

    console.log(3, currentComponent); // 여기는 ParentComponent이어야 하는데...

      return {
        element: `
            &lt;div&gt;
                ${childComponent.element}
                count: ${count}
            &lt;/div&gt;
        `,
    };
}</code></pre>
<p>결과는 아래와 같습니다.</p>
<pre><code class="language-js">1 {id: &#39;ParentComponent&#39;, stateIndex: 0}
2 {id: &#39;ParentComponent&#39;, stateIndex: 1}
3 {id: &#39;ChildComponent&#39;, stateIndex: 0}</code></pre>
<p>눈에 띄는 부분은 마지막 <code>3 {id: &#39;Todos&#39;, stateIndex: 0}</code> 입니다. 분명 현재 컨텍스트는 <code>ParentComponent</code>이지만 <code>currentComponent</code>에는 자식 컴포넌트인 <code>ChildComponent</code>가 저장되어 있습니다.
물론 <code>setState</code>는 호출당시의 <code>currentComponent</code> 정보를 기억하고 있기 때문에 이후에 <code>currentComponent</code> 값이 변경돼도 잘 작동합니다. 그러나, <code>currentComponent</code>에 현재 컨텍스트와 다른 정보를 저장하고 있다면 추후에 문제가 생길 가능성이 있습니다.
또, 항상 자식 컴포넌트 호출이 상태 선언 이후에 나오도록 강제하고 있지 않기 때문에 자식 컴포넌트 호출 이후 상태를 선언한다면 해당 상태는 제대로 저장되지 않습니다.
그렇기 때문에 자식 컴포넌트를 호출하고 나면 다시 부모 컴포넌트로 돌아올 수 있도록 구현해주어야 합니다.</p>
<h3 id="📍-자식-컴포넌트-렌더링-후-현재-컴포넌트로-돌아오기">📍 자식 컴포넌트 렌더링 후 현재 컴포넌트로 돌아오기</h3>
<p>자식 컴포넌트를 렌더링하고 현재 컴포넌트로 돌아오도록 하는 방법은 간단합니다. </p>
<ul>
<li><code>src/core/component.js</code><pre><code class="language-js">export let currentComponent = null;
</code></pre>
</li>
</ul>
<p>function createComponent(component) {
  currentComponent = { id: component.name, stateIndex: 0 };</p>
<p>  const componentInstance = component();</p>
<p>  return componentInstance;
}</p>
<p>export default createComponent;</p>
<pre><code>`createComponent`에서 `component`를 호출하고 있는데, 호출하기 전에 `currentComponent`를 변수에 저장합니다. 저장한 후에 `component`를 호출이 종료되면 `currentComponent`에 호출 이전에 저장해둔 값을 다시 돌려줍니다.
코드로 이해해보겠습니다.

- `src/core/component.js`

![](https://velog.velcdn.com/images/kim-jaemin420/post/3ff07815-6208-4cbf-8614-e88fb4722a2a/image.png)


`component`를 호출하기 전에 `previousComponent`라는 변수에 `currentComponent`를 할당합니다. 현재 컴포넌트를 호출하기 이전이니 `currentComponent`에는 부모 컴포넌트에 대한 정보가 들어있습니다. 호출이 종료하고 `currentComponent`에 `previousComponent`를 다시 할당해주면 이제 `currentComponent`에는 부모 컴포넌트가 됩니다.

이제 `ParentComponent` 어느 부분에서 `useState`를 호출하더라도 현재 컴포넌트 상태 배열에 잘 저장됩니다. 이제 useState를 구현할 준비가 되었습니다.

## ✅ useState 구현하기


앞서 컴포넌트 상태를 어떻게 저장해둘 것인지 설계를 해두었기 때문에 다음과 같이 작성할 수 있습니다. 

- `src/core/hooks/useState.js`

```js
import { currentComponent } from &#39;../component.js&#39;;

const componentsState = {};

const useState = (initialValue) =&gt; {
    const { id, stateIndex } = currentComponent;

      const state = componentsState[id][stateIndex];
    const setState = () =&gt; {};

      return [state, setState];
};

export default useState;</code></pre><p>얼추 모양이 잡혔습니다. 이제 <code>componentsState</code>객체에 상태 초기값들을 설정하고, <code>setState</code> 내부 구현만 하면 <code>useState</code> 완성입니다.</p>
<h3 id="📍-usestate-초기값-저장하기">📍 useState 초기값 저장하기</h3>
<p><code>useState</code>를 호출할때 파라미터로 초기값을 전달하는데 그 초기값을 <code>componentsState</code>에 저장해보겠습니다. 매우 간단하기 때문에 바로 코드로 구현하겠습니다.</p>
<ul>
<li><code>src/core/hooks/useState.js</code></li>
</ul>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/a7e1aa32-3e33-4328-b903-dfdc223f3986/image.png" alt=""></p>
<p><code>useState</code>를 호출한 컴포넌트가 <code>componentsState</code>에 등록되어 있지 않다면 빈 배열로 초기화 해줍니다. 이 빈 배열에 해당 컴포넌트의 상태들이 차곡차곡 저장되겠죠?</p>
<p>이후, 상태 배열의 <code>stateIndex</code> 값이 존재하지 않는다면 파라미터로 받은 <code>initialValue</code>로 초기화 해줍니다. 초기값 등록을 마쳤으니 이제 <code>setState</code>를 구현해봅시다.</p>
<h3 id="📍-setstate-구현하기">📍 setState 구현하기</h3>
<p><code>setState</code>의 파라미터로 들어오는 값은 두 가지로, 바꾸려고 하는 <code>새로운 값</code>, 혹은 <code>콜백 함수</code>입니다. 두 가지 케이스에 대해 알아보겠습니다.</p>
<ol>
<li><p><strong>새로운 값인 경우</strong></p>
<pre><code class="language-js">setState(5); // 상태를 숫자 5로 변경</code></pre>
<p>이 경우 상태를 새로운 값으로 할당해줍니다.</p>
</li>
<li><p><strong>콜백 함수인 경우</strong></p>
<pre><code class="language-js">setState(previousState =&gt; previousState + 1); // 현재 상태에 1을 더한 값으로 상태를 업데이트</code></pre>
<p>이 경우, <code>setState</code>는 콜백 함수를 호출하고, 이 함수의 반환값으로 상태를 설정합니다. 이 콜백 함수는 현재 상태를 인자로 받아 새로운 상태를 계산하는 데 사용됩니다.</p>
</li>
</ol>
<p>두 가지 케이스를 고려하여 <code>setState</code>를 구현합니다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/4c008da7-13cf-4fcb-9d4c-23dfd6c0a761/image.png" alt=""></p>
<p>변경된 값과 현재 상태 값이 다를 경우 상태를 변경하고 리렌더링합니다. 현재는 단순히 직접 비교를 통해 값을 변경하고 리렌더링 되고 있습니다. 객체나 배열의 경우 내용이 같더라도 참조값이 다르면 두 객체나 배열이 다르다고 평가됩니다. 추후 깊은 비교 등을 통해 개선할 수 있습니다.</p>
<h2 id="✅-정리">✅ 정리</h2>
<p>기존에 각 컴포넌트마다 상태를 선언하고 setState를 만드는 방법을 개선하고자 직접 <code>useState</code>를 구현해보았습니다. <code>useState</code>를 설계하고 구현함으로써 리액트의 <code>useState</code> 함수가 콜 스택에서 pop되더라도 어떻게 상태를 유지하고 변경할 수 있는지에 대해 이해해볼 수 있었습니다.</p>
<p>그러나 현재 구조에서는 상태가 변경될 때마다 전체 애플리케이션이 리렌더링되는 문제가 발생하고 있습니다. 하나의 컴포넌트에서 상태가 변경되면 그 컴포넌트와 관련이 없는 컴포넌트도 모두 리렌더링 됩니다. 이러한 문제를 해결하기 위해, 다음 포스트에서는 상태 변경이 발생한 컴포넌트와 그 자식 컴포넌트들만을 대상으로 하는 리렌더링 최적화를 시도해보도록 하겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[바닐라 자바스크립트] 함수형 컴포넌트 만들기: 컴포넌트 생성]]></title>
            <link>https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/%EB%B0%94%EB%8B%90%EB%9D%BC-%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%95%A8%EC%88%98%ED%98%95-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Tue, 31 Oct 2023 06:21:10 GMT</pubDate>
            <description><![CDATA[<p>바닐라 자바스크립트는 웹 개발의 기본이며, 많은 프론트엔드 개발자들이 처음에 접하게 되는 언어입니다. 이에 따라 많은 초보 개발자들은 바닐라 자바스크립트를 통해 기본적인 웹 어플리케이션을 구축하게 됩니다. 그러나 대부분의 자료와 예제는 전통적인 클래스 기반의 컴포넌트를 사용하여 작성되어 있습니다. 현대의 프론트엔드 트렌드는 리액트와 같은 라이브러리에서 볼 수 있듯이 함수형 컴포넌트를 향하고 있습니다. 이러한 변화의 흐름 속에서, 저는 프로그래머스 데브 코스를 진행하며 바닐라 자바스크립트로 함수형 컴포넌트를 어떻게 효과적으로 구현할 수 있을지 고민하게 되었습니다.</p>
<p>이 글에서는 바닐라 자바스크립트만을 사용해서 간단한 todo App을 만드는 과정을 함께 탐색해보겠습니다.</p>
<h2 id="✅-함수형-컴포넌트">✅ 함수형 컴포넌트</h2>
<p>우리의 목표는 리액트의 함수형 컴포넌트와 유사하게 사용할 수 있도록 바닐라 자바스크립트로 컴포넌트를 구현하는 것이기 때문에 리액트에서 함수형 컴포넌트가 어떤 방식으로 사용되는지 알아보겠습니다.</p>
<pre><code class="language-jsx">function MyComponent () {
    return &lt;div&gt;내 컴포넌트입니다.&lt;/div&gt;;
}

export default MyComponent;</code></pre>
<p>함수형 컴포넌트는 이름에서도 알 수 있듯이, 일반적인 함수로써 정의됩니다. 함수형 컴포넌트는 JSX (JavaScript XML) 또는 React Element를 반환하는 함수입니다. 함수형 컴포넌트의 장점 중 하나는 간결하고 읽기 쉽다는 것입니다.
이제, 바닐라 자바스크립트에서도 리액트의 함수형 컴포넌트와 유사하게 사용할 수 있도록 구현해보겠습나다.</p>
<h2 id="✅-컴포넌트-생성하기">✅ 컴포넌트 생성하기</h2>
<p>리액트의 장점 중 하나는 JSX라는 문법을 사용하여 UI를 간결하게 표현할 수 있다는 것입니다. JSX는 자바스크립트 XML의 약자로, HTML과 유사한 문법을 가진 자바스크립트의 확장 문법입니다. 하지만 이것은 리액트와 Babel과 같은 트랜스파일러를 함께 사용했을 때 가능합니다.</p>
<p>바닐라 자바스크립트에서는 JSX와 같은 특별한 문법을 사용하지 않기 때문에 바로 JSX를 반환하는 것이 불가능합니다. Babel과 같은 트랜스파일러 없이 바닐라 자바스크립트로 컴포넌트를 정의하려면, 기본 자바스크립트 문법을 사용해야 합니다.</p>
<p>이런 한계 때문에 우리의 바닐라 자바스크립트 컴포넌트에서는 문자열 형태의 HTML을 반환하는 방식을 사용하려고 합니다.</p>
<pre><code class="language-js">function MyComponent () {
    return {
        element: `&lt;div&gt;내 컴포넌트입니다.&lt;/div&gt;`;
    };
}

export default MyComponent;</code></pre>
<p>위와 같이 바닐라 자바스크립트에서 컴포넌트를 정의하면, 리액트의 JSX처럼 직관적이고 선언적인 UI 표현이 조금 더 어려워질 수 있지만, 문자열 형태로 UI를 반환하는 이 방식은 기존의 <code>createElement</code>를 사용한 방식보다는 좀 더 직관적이고 선언적이라고 볼 수 있습니다.</p>
<h3 id="📍-컴포넌트-렌더링-하기">📍 컴포넌트 렌더링 하기</h3>
<p>이 방법을 사용해서 컴포넌트를 렌더링 해봅시다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/c46694e3-8aaa-4309-84d6-25a11a346d6d/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/bb937106-261a-426c-aa81-de6f7329a9d8/image.png" alt="">
의도한대로 컴포넌트가 잘 렌더링됩니다. 현재는 테스트를 위해 script에 모든 코드를 작성했는데, 모듈을 사용하여 유지보수하기 편하도록 분할해보도록 하겠습니다.</p>
<ul>
<li><p><code>디렉토리 구조</code></p>
<pre><code>📦src
┣ 📂components    // component들이 속할 디렉토리
┃ ┗ 📜Todos.js
┗ 📜index.js   // entry point 이자, 렌더 함수 위치하는 곳</code></pre></li>
<li><p><code>index.html</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/f0672d49-25ac-41ce-9f98-d497f606764c/image.png" alt=""></p>
</li>
<li><p><code>src/index.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/3a7564e0-317f-4d00-a470-2225316e088b/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><code>src/components/Todos.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/f70d3366-f96e-495e-a3e4-0d77933cd1a9/image.png" alt=""></li>
</ul>
<p><code>index.js</code>는 애플리케이션의 엔트리 포인트 역할을 하는데, 현재 구조에서는 컴포넌트를 직접 불러와 렌더링하는 역할까지 담당하고 있습니다. 엔트리 포인트 역할을 하는 파일은 다른 파일과 의존성을 최소화해야 합니다. 현재 index.js는 여러 컴포넌트를 직접 불러와 렌더링하는 책임까지 지니고 있습니다. 이를 해결하기 위해 컴포넌트들의 렌더링 역할을 <code>App.js</code> 파일로 분리하겠습니다.</p>
<ul>
<li><code>src/App.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/f3c1d1c1-c1be-4edd-adfa-b967dd3bc501/image.png" alt=""></li>
</ul>
<ul>
<li><code>src/index.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/e362a39e-1d30-46bb-bd6b-5b0006ad82ac/image.png" alt=""></li>
</ul>
<p>이로써, <code>index.js</code>는 애플리케이션의 시작점으로써의 역할에만 집중하게 되었고, 실제 컴포넌트 구성과 렌더링은 <code>App.js</code>가 담당하게 되었습니다. 이런 구조 변경으로 인해 애플리케이션의 모듈화가 향상되고, 각 파일의 역할이 더욱 분명해졌습니다.</p>
<p>파일의 역할은 분명해졌으나, 또 다른 문제점이 있습니다. 파일 분리 전에는 <code>Todos 컴포넌트</code>가 반환하는 내용을 바로 확인할 수 있어 <code>Todos().element</code>의 사용에 큰 문제가 없었습니다. 그러나, 파일을 분리한 후의 render 함수에서<code>Todos().element</code> 방식의 렌더링은 가독성이 떨어집니다. 파일이 분리되면서 각 컴포넌트의 반환값이 무엇인지 직접 확인하기 힘들어졌고, 그 결과 element가 무엇을 나타내는지 파악하기가 더 어려워졌습니다.</p>
<p>이를 개선하기 위해, 컴포넌트를 생성하는 전용 함수를 도입하는 것이 좋을 것 같습니다. 컴포넌트가 이 함수를 통해 생성된다면, 코드는 더 선언적이며 의미 파악도 쉬워질 것입니다.</p>
<h2 id="✅-컴포넌트-생성-함수-만들기">✅ 컴포넌트 생성 함수 만들기</h2>
<ul>
<li><p><code>디렉토리 구조</code></p>
<pre><code>📦src
┣ 📂components
┃ ┗ 📜Todos.js
┣ 📂core   // 관련된 핵심 로직, 훅, 생명주기 함수들을 보관
┃ ┗ 📜component.js
┗ 📜index.js</code></pre><p>컴포넌트를 생성하는 함수는 컴포넌트를 렌더링할때 반드시 사용될 함수이고, 프로젝트 전반에서 사용될 예정이기 때문에 <code>core</code> 디렉토리에 위치시켰습니다.</p>
</li>
<li><p><code>src/core/component.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/c816df86-9f88-468e-a808-e417a52a739b/image.png" alt=""></p>
</li>
</ul>
<p>여기서 <code>createComponent</code>는 매우 간단한 작업을 수행합니다. 주어진 컴포넌트를 받아서 호출하고, 그 결과를 바로 반환합니다.</p>
<p>이제 <code>createComponent</code> 함수를 사용하여 컴포넌트를 렌더링 해봅시다.</p>
<ul>
<li><code>src/index.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/998be775-847f-4aeb-8e8f-0340ad0c0aa5/image.png" alt=""></li>
</ul>
<ul>
<li><code>src/App.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/e39e264c-7e18-4ea4-885a-6736ba252b51/image.png" alt=""></li>
</ul>
<p>위 코드에서는 <code>createComponent</code> 함수를 이용하여 <code>todosComponent</code>를 생성하였습니다. 이후, 이 컴포넌트의 DOM 요소를 참조하여 <code>app</code>에 추가하고 있습니다. <code>createComponent</code>가 단순한 동작만 수행하지만, 그 사용을 통해 코드의 가독성과 의미가 향상된 것을 볼 수 있습니다.</p>
<p>우리의 프로젝트는 발전해나가면서 점점 더 복잡한 요구사항을 반영해야 할 상황을 맞이하게 됩니다. 특히 컴포넌트를 활용해보며 느낀 점은, 모든 상태를 컴포넌트 내부에 고정시키는 것은 제한적이라는 것입니다.</p>
<p>예를 들어 보겠습니다. 현재 Todos 컴포넌트는 내부 상태를 바탕으로 할 일 목록을 표현하고 있습니다. 그렇다면 만약 일상의 할 일과 업무의 할 일을 분리해서 표현하고 싶다면 어떻게 해야 할까요? 이를 위해서는 Todos 컴포넌트가 외부에서 전달받은 데이터, 즉 <code>props</code>를 기반으로 렌더링을 수행해야 합니다. 그러나 현재의 구조에서는 컴포넌트가 <code>props</code>를 전달받을 수 없습니다.</p>
<p>이번 단계에서는 컴포넌트가 <code>props</code>를 받아들일 수 있도록 업데이트하는 작업을 진행해보도록 하겠습니다.</p>
<h3 id="📍-컴포넌트-props-추가하기">📍 컴포넌트 props 추가하기</h3>
<p>먼저, <code>props</code>를 추가하기 전에 리액트의 함수형 컴포넌트는 어떻게 <code>props</code>를 전달하고 받는지 간단히 살펴보겠습니다.</p>
<pre><code class="language-jsx">function ParentComponent() {
  return &lt;ChildComponent message=&quot;Hello from Parent!&quot; /&gt;;
}

function ChildComponent(props) {
  return &lt;h1&gt;{props.message}&lt;/h1&gt;;
}</code></pre>
<p>리액트에서, 부모 컴포넌트는 JSX의 속성을 통해 자식 컴포넌트에 props를 전달합니다. 그런 다음 자식 컴포넌트는 이 props를 파라미터로 받아 사용할 수 있습니다. 이처럼 리액트는 JSX의 간결하고 선언적인 문법을 통해 props의 전달과 사용이 자연스럽게 이루어집니다.</p>
<p>그러나, 바닐라 자바스크립트에서는 이와 같은 JSX 문법을 사용할 수 없습니다. 대신, 우리는 함수의 <code>arguments</code>를 활용하여 props를 전달할 수 있습니다.</p>
<p>현재 <code>createComponent</code> 함수는 컴포넌트 생성을 담당하며, 이 함수를 통해 컴포넌트에 props를 전달할 예정입니다. <code>createComponent</code> 함수 내에서 컴포넌트를 호출할 때, props를 함께 넘겨주는 방식으로 구현하면 바닐라 자바스크립트 환경에서도 props를 효율적으로 관리할 수 있을 것입니다.</p>
<ul>
<li><code>src/core/component.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/974b90f1-fcf3-48f2-b96d-7ebb0ba00b54/image.png" alt=""></li>
</ul>
<p>아주 간단하죠? 기존 코드에서 <code>props</code>를 추가로 받고 이를 컴포넌트에 주입합니다. 이를 활용하여 <code>Todos</code> 컴포넌트가 <code>props</code>로 받은 할 일 목록을 렌더링 하도록 변경하겠습니다.</p>
<ul>
<li><p><code>src/App.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/e22ca4f3-13c8-4803-a3cf-4aa03bcd0432/image.png" alt=""></p>
</li>
<li><p><code>src/components/Todos.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/6e743047-2099-435c-9448-20e6c76fcde1/image.png" alt=""></p>
</li>
<li><p><code>결과</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/dfb8faf9-e953-48a6-a876-152e79b9d520/image.png" alt=""></p>
</li>
</ul>
<p>잘 적용된 것을 볼 수 있습니다.</p>
<p>현재 컴포넌트는 오로지 정적인 정보만을 표시하는 역할에 집중하고 있습니다. 그러나 웹 애플리케이션의 핵심은 사용자와의 상호작용에 있습니다. 이러한 동적인 상호작용을 가능하게 하기 위해서는 이벤트 핸들러의 바인딩이 필수적입니다.</p>
<p>다음 단계에서는 컴포넌트에 이벤트 핸들러를 연결하는 방법에 대해 알아보겠습니다.</p>
<h2 id="✅-이벤트-처리">✅ 이벤트 처리</h2>
<h3 id="📍-이벤트-바인딩">📍 이벤트 바인딩</h3>
<p>이벤트 처리에 집중하기 위해, 단순한 컴포넌트 구조로 다시 돌아가 보겠습니다. 아래 이미지와 같은 구조를 가진 컴포넌트를 구현해 보겠습니다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/fbf56263-e7d6-465d-aa5f-b75a9ee9cc27/image.png" alt=""></p>
<p>변경할 컴포넌트들은 다음과 같습니다:</p>
<ul>
<li><code>src/App.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/797cf6a9-f01b-4423-930c-6946a1246cf2/image.png" alt=""></li>
</ul>
<ul>
<li><code>src/components/Todos.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/e884234a-c358-4966-af9c-33813feea63f/image.png" alt=""></li>
</ul>
<p><code>Todos 컴포넌트</code>가 호출될 때, 내부의 코드는 위에서 아래 순서대로 실행됩니다. 만약 함수 내부에서 DOM을 참조하게 된다면 어떤 문제가 발생할까요?</p>
<pre><code class="language-js">function Todos({ todos }) {
  // 여기서 버튼 DOM을 참조한다면 어떻게 될까요?

  return {
    element: `
            &lt;ul&gt;
              ${todos.map(todo =&gt; `&lt;li&gt;${todo}&lt;/li&gt;`).join(&#39;&#39;)}
            &lt;/ul&gt;
            &lt;button class=&quot;add-button&quot;&gt;할 일 추가&lt;/button&gt;
          `,
  };
}

export default Todos;
</code></pre>
<p>위와 같이 함수 내부에서 DOM을 참조하려고 시도하면, 원하는 DOM 요소가 아직 생성되지 않았기 때문에 참조에 실패하게 됩니다. 그렇다면 어떻게 <code>Todos 컴포넌트</code>가 완전히 렌더링된 시점을 알 수 있을까요? Todos 컴포넌트를 호출하는 부모 컴포넌트는 Todos의 DOM 요소들이 렌더링된 시점을 정확히 알고 있습니다. 이 점을 활용하면, 부모 컴포넌트에서 Todos 컴포넌트의 DOM이 완전히 렌더링된 후에 이벤트 바인딩을 수행할 수 있습니다.</p>
<p><code>bindEvents</code>라는 함수를 생성하여 DOM 참조 로직을 그 안에 포함시키고, 이 함수를 반환하도록 해 부모 컴포넌트에서 Todos 컴포넌트의 렌더링이 완료된 이후에 호출하도록 하겠습니다.</p>
<ul>
<li><code>src/components/Todos.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/e0d4e5ab-eac1-4fdc-8710-299e17b1cf24/image.png" alt=""></li>
</ul>
<ul>
<li><code>src/App.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/9e592c61-5bd2-42ee-abb4-fce4ae8897da/image.png" alt=""></li>
</ul>
<ul>
<li><code>src/index.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/746772a5-e16e-4cb4-bf23-cee64996b6e3/image.png" alt=""></li>
</ul>
<p><code>Todos 컴포넌트</code>의 렌더링 시점은 <code>App 컴포넌트</code>가, <code>App 컴포넌트</code>의 렌더링 시점은 최상위인 <code>index.js</code>가 알고 있기 때문에 이벤트 바인딩 함수의 호출을 상위에 위임하고 있습니다.  이러한 구조는 다음과 같은 몇 가지 문제점을 가지고 있습니다.</p>
<p>먼저, 상하위 컴포넌트 간의 강한 종속성이 형성됩니다. 이로 인해 코드의 유지보수가 어려워지며, 하위 컴포넌트를 독립적으로 테스트하기도 힘듭니다. 또한 이런 종속성은 하위 컴포넌트의 재사용성을 저하시킵니다.</p>
<p>이 문제의 원인은 각 컴포넌트가 자신의 렌더링 시점을 알지 못하기 때문입니다. 현재의 구조에서는 변경이 생길 때 전체 <code>app</code>이 리렌더링되는 방식을 사용하고 있어, 개별 컴포넌트 단위의 렌더링 관리가 되지 않습니다. 그 결과, 각 컴포넌트는 자신이 언제 렌더링 되는지 알 방법이 없고, 그 렌더링 시점은 상위 컴포넌트가 알고 있게 됩니다.</p>
<p>이 문제를 해결하기 위해서는 컴포넌트 단위로 렌더링을 관리하는 최적화 방안을 적용할 필요가 있습니다. 각 컴포넌트가 자신의 렌더링 생명주기를 스스로 관리하게 되면, 상위 컴포넌트에 대한 의존성을 줄일 수 있습니다. 이에 대한 구체적인 방법은 추후에 다뤄 보도록 하겠습니다.</p>
<h3 id="📍-이벤트-핸들러-함수-바인딩과-상태-변경">📍 이벤트 핸들러 함수 바인딩과 상태 변경</h3>
<p>이어서 이벤트 핸들러 함수를 생성하여 <code>할 일 추가</code> 버튼을 눌렀을때 상태가 변경되도록 해보겠습니다. 우선 현재 상태를 변경하는 <code>setState</code> 함수를 구현해보겠습니다.</p>
<ul>
<li><code>src/App.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/428cb654-ee1b-4a4b-9490-bfdca1996137/image.png" alt=""></li>
</ul>
<p><code>setState</code> 함수로 인해 상태가 재할당 되므로, state 변수의 선언을 <code>const</code>에서 <code>let</code>으로 바꾸었습니다. 리렌더링이 발생할 때마다 <code>App 컴포넌트</code>가 다시 호출되기 때문에, 상태가 초기화되는 것을 방지하기 위해 상태를 함수 외부로 이동시켰습니다. 상태 변경을 <code>Todos 컴포넌트</code>에서도 가능하게 하기 위해, props를 통해 전달하였습니다. 다음으로, <code>Todos 컴포넌트</code>에서 상태를 변경하는 이벤트 핸들러를 구현해보겠습니다.</p>
<ul>
<li><code>src/components/Todos.js</code>
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/6bda2287-4f3e-4c04-b527-675b46f04da6/image.png" alt=""></li>
</ul>
<p><code>props</code>를 통해 받은 상태 변경 함수를 이용하여 이벤트 핸들러에서 상태를 업데이트합니다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/28361aef-27ea-4224-be38-11b8bcac1301/image.gif" alt=""></p>
<p>하지만, 이러한 방식으로 상태를 관리하면 아래와 같은 다양한 문제점이 발생할 수 있습니다.</p>
<p>외부에서 <code>state</code> 변수를 직접 조작할 수 있는 구조는 예기치 않은 사이드 이펙트를 초래할 수 있습니다. 상태 관리는 원칙적으로 <code>setState</code> 함수를 통해서만 이루어져야 하나, 현재 구조에서는 여러 부분에서 상태를 수정할 가능성이 있습니다. 이렇게 되면 상태가 어디서 변경되었는지 추적하기가 어려워지고, 디버깅 역시 복잡해질 위험이 있습니다. 또한, 컴포넌트 내에서 상태를 활용하고 수정하다보면 렌더링 호출이 여러 곳에서 발생하게 됩니다. 이것은 렌더링의 정확한 시점을 파악하기 어렵게 만듭니다. 더불어, 현재 코드는 선언적이지 않아 <code>setState</code>의 작동 방식을 컴포넌트가 직접 알아야 하는 문제가 있습니다.</p>
<p>이런 문제점들을 극복하기 위해, React의 useState와 유사한 기능을 도입하는 것이 바람직해 보입니다.</p>
<p>바로 다음 포스트에서 <code>useState</code>를 직접 구현해보겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[데브코스] 9.18 - 10.18 한달 회고]]></title>
            <link>https://velog.io/@kim-jaemin420/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4-9.18-10.18-%ED%95%9C%EB%8B%AC-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@kim-jaemin420/%EB%8D%B0%EB%B8%8C%EC%BD%94%EC%8A%A4-9.18-10.18-%ED%95%9C%EB%8B%AC-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 23 Oct 2023 06:39:08 GMT</pubDate>
            <description><![CDATA[<p>데브코스를 시작한지도 어느덧 한 달이 지났습니다. 데브코스 시작 전, 감사하게도 면접 기회를 얻게 되었고 9월 18일에 면접을 보게 되었습니다. 결국 면접에 떨어져 데브코스를 오게 되었는데, 많이 아쉬우면서도 지금 상태로 운 좋게 붙었다 하더라도 회사에 1인분으로, 역할을 잘 해낼지에 대한 의문과 걱정도 많았으리라 생각합니다.
데브코스 과정에서 다음과 같은 내용을 배웠고 그에 대한 느낀점입니다.</p>
<h2 id="선언적-프로그래밍">선언적 프로그래밍</h2>
<p>데브 코스 강의 중 선언적 프로그래밍에 대한 주제가 나오게 되었습니다. 그동안 선언적 프로그래밍에 대해 제대로 이해하지 못해서 이번 기회에 알아보는 시간을 가졌습니다. 토스의 박서진님이 작성한 <a href="https://toss.tech/article/frontend-declarative-code">선언적인 코드 작성하기</a>를 읽고 이해한 선언적 프로그래밍은 다음과 같습니다.</p>
<p>결과 혹은 원하는 것에 대해 설명하지만 그 모든 과정을 코드를 해석하는 과정에서 알 필요는 없습니다. 그러므로 의미를 알기 어려운 로직을 그대로 드러내지 않고 의미를 가지도록 추상화 하는 것이 선언적 프로그래밍이라고 이해했습니다. 따라서 서진님께서는 &quot;선언적인 코드&quot;를 <code>추상화 레벨이 높아진 코드</code>이다 라고 표현한 것 같습니다.
다음은 이에 대한 이해를 돕기 위한 코드입니다.</p>
<pre><code class="language-html">&lt;script&gt;
const state = [1, 2, 3, 4, 5];
const $list = document.querySelector(&quot;.list&quot;);

for (let index = 0; index &lt; state.length; index += 1) {
    const $listItem = document.createElement(&quot;li&quot;);

    $listItem.innnerHTMl(state[index]);
    $list.appendChild($listItem);
}
&lt;/script&gt;</code></pre>
<p>위 코드는 해석하는 과정에서 필요하지 않는 과정이 그대로 노출되어 있습니다. state를 순회하는 for문이나 돔을 생성하고 삽입하는 과정은 한 번에 의도를 파악하기 어려워 보입니다. 이러한 과정을 추상화를 통해 과정을 적절히 숨기고 의미를 부여한다면 가독성과 재사용성이 올라갈 것 같습니다.</p>
<pre><code class="language-jsx">function List() {
    const [state, setState] = useState([1, 2, 3, 4, 5]);

      return (
      &lt;ul&gt;
          ${state.map((content) =&gt; (
            &lt;li&gt;{content}&lt;/li&gt;
        ))}
      &lt;/ul&gt;
    );
}</code></pre>
<p>위 코드를 보면 for문은 map을 사용함으로써 불필요한 과정이 숨겨진 것을 볼 수 있습니다. 또, JSX로 인해 돔이 어떤 구조로 생성되는지 한 눈에 파악할 수 있게 되었습니다. 코드를 이해해야 하는 입장에서 몰라도 되는 로직은 감춰지고 필요한 정보만 드러나 있어 빨리 파악할 수 있어졌습니다.</p>
<p>그러나, 무조건 추상화 하는 것이 좋은 방향은 아닙니다. 에어비앤비 클론 코딩을 진행했을때, 프로젝트 전반에서 사용되는 모든 버튼 컴포넌트를 하나의 컴포넌트로 추상화하여 사용하고자 했습니다. 여러 컴포넌트에서 사용하는 컴포넌트들을 각각 구현하는 것보다 하나의 컴포넌트를 사용하는 편이 atmoic한 컴포넌트이고 사용성이 좋을 것이라고 생각했습니다. 막상 common한 버튼 컴포넌트를 구현하고 보니, 버튼 크기, 색깔, 내용, 애니메이션 등 생각보다 너무 많은 정보들을 props로 전달해야 했습니다. 가독성과 사용성을 위해 추상화한 컴포넌트인데 오히려 가독성도, 사용성도 살리지 못했습니다. 아래 코드와 같이 사용해야 했습니다.</p>
<pre><code class="language-jsx">import { Button } from &quot;./common&quot;;

function Home() {
    return (
        &lt;main&gt;
               &lt;Button
                width={200}
                  height={80}
                content={&quot;바로가기&quot;}
                  color={primary}
                  onClick={handleClickButton}
                radius={0.5}
                  hoverColor={secondary}
                // 등등 너무 많았다...
            /&gt;
        &lt;/main&gt;
    );
}</code></pre>
<p>결국 추상화한 common Button은 폐기하게 되었습니다. 좋은 선언적 코드란 결국, <strong>유지 보수하기 좋도록</strong> 추상화한 코드라는 것을 알 수 있습니다. 무엇을 고려하든 항상 <strong>읽기 좋고 이해하기 좋은가</strong>, <strong>유지 보수 하기 좋은가</strong>가 우선이 돼야 한다는 것을 다시 한 번 느꼈습니다.
제가 만든 common Button의 패착은 너무 큰 단위의 컴포넌트를 추상화했기 때문이라고 생각합니다. 작은 단위의, 적은 일을 하는 컴포넌트 또는 함수를 추상화했을때 선언적 프로그래밍의 장점을 잘 활용할 수 있다고 생각합니다.
좋은 예시는 토스의 슬래시 라이브러리에서 많이 배웠습니다. 아래의 <code>ImpressionArea</code> 라는 컴포넌트는 작은 단위의 컴포넌트를 추상화하였습니다.</p>
<pre><code class="language-jsx">&lt;ImpressionArea onImpressionStart={() =&gt; { /* 보여지면 실행 */ }}&gt;
  &lt;div&gt;내가 보여지면 `onImpressionStart`가 호출돼요&lt;/div&gt;
&lt;/ImpressionArea&gt;</code></pre>
<p><code>ImpressionArea</code>로 인해 해당 컴포넌트는 <code>IntersectionObserverApi</code>나 스크롤 핸들러 로직을 직접적으로 사용하지 않을 수 있습니다. 또한, 추상화한 컴포넌트는 어떤 영역이 보이는지 또는 숨겨졌는지에 따라 함수를 호출하는 간단한 일만 하고 있기 때문에 컴포넌트를 사용할때도 유지보수할때도 큰 불편함이 없어보입니다.</p>
<p>결론적으로 <code>좋은 선언적인 코드</code>란 작은 단위의 로직이나 컴포넌트를 유지 보수하기 좋게 추상화한 코드라고 생각했습니다.</p>
<h2 id="코드를-언제-분리할-것인가">코드를 언제 분리할 것인가</h2>
<p>코드를 언제 분리할 것인가는 개발을 시작하면서 늘 하게 되는 고민입니다. 바닐라 자바스크립트로 todo App과 노션을 개발하면서 이에 대한 고민을 또 다시 하게 되었습니다.</p>
<p>먼저 todo App을 바닐라 자바스크립트로 구현했을 때 다음과 같은 상황이 있었습니다.</p>
<pre><code class="language-ts">function App() {
    const [todos, setTodos] = useState&lt;Todo[]&gt;([]);

      const todoFormComponent = createComponent(TodoForm, { setTodos });
    const todoListComponent = createComponent(TodoList, { todos, setTodos});

      return {
        element: `
            &lt;div&gt;
                ${todoFormComponent}
                ${todoListComponent}
            &lt;/div&gt;
        `,
    }
}</code></pre>
<p>App에서 todos에 대한 상태를 만들고 todos 상태를 변경할 수 있는 setter를 하위 컴포넌트의 props로 전달해 todos를 변경할 수 있게 했습니다. props로 setter만 전달하여 상태를 변경하면 코드가 더 간결해지고 편할거라 생각했으나 여기엔 여러 가지 문제가 있었습니다.
제가 간과한 것은 <strong>컴포넌트 책임</strong>입니다. 현재 todos 상태를 책임지고 있는 컴포넌트는 App입니다. 그렇기 때문에 App은 이 todos가 변경되고 사용되는 부분 또한 알고 있고 관리할 수 있어야 합니다. 만약 setTodos를 컴포넌트 props로 넘긴다면 이 todos가 하위 컴포넌트 내에서 어떻게 변경되는지 App은 알 수 없게 됩니다.
이러한 문제를 깨닫고 아래와 같이 사용하게 되었습니다.</p>
<pre><code class="language-ts">function App() {
    const [todos, setTodos] = useState&lt;Todo[]&gt;([]);

      const getTodos = () =&gt; { /* 여기서 setTodos 호출*/ };
    const addTodo = () =&gt; { /* 여기서 setTodos 호출*/ };
      const deleteTodo = () =&gt; { /* 여기서 setTodos 호출*/ };

      const todoFormComponent = createComponent(TodoForm, { addTodo });
    const todoListComponent = createComponent(TodoList, { todos, deleteTodo });

      return {
        element: `
            &lt;div&gt;
                ${todoFormComponent}
                ${todoListComponent}
            &lt;/div&gt;
        `,
    }
}</code></pre>
<p>여기서 저는 앞에서 나온 <strong>컴포넌트 책임</strong>에 대해 잘못 이해하고 todos 관련 로직을 분리해내는 것이 컴포넌트의 책임을 어겼다고 생각했습니다.
그러나, 이렇게 변경하면 또 아쉬운 점이 있습니다. 사실 App은 웹 애플리케이션의 엔트리 포인트 같은 역할을 하는 컴포넌트로 다른 컴포넌트를 렌더링하는 역할을 주로 하고 있습니다. 그런데, 지금은 todos라는 상태 또한 관리하고 있습니다. 오히려 todos 관련 로직을 따로 분리하게 되면 각자의 책임이 분리되어 App은 엔트리 포인트로서 단일 책임을 갖게 되고 todos 관련 로직은 <code>useTodos</code> 라는 훅으로 분리한다면 이 훅은 todos 책임만을 갖게 됩니다.
이렇게 어떤 코드를 분리할 것인가에 대한 고민을 하다 보니 컴포넌트의 책임과 선언적 프로그래밍에 대한 고민도 함께 하며 많은 것을 깨닫게 되었습니다.</p>
<p>이러한 고민을 하며 <strong>&quot;코드를 언제 분리할 것인가&quot;</strong>에 대한 저만의 기준이 필요하다고 느껴 정리를 해보았습니다. 지금 제가 깨달은 바를 통해 정리를 하자면 다음과 같은 상황에서 분리할 것 같습니다.</p>
<blockquote>
</blockquote>
<p>코드를 언제 분리할 것인가</p>
<ol>
<li>반복되는, 의미를 가진 코드</li>
<li>반복되지 않더라도 그 자체로 의미를 가지고 있는 로직</li>
<li>추상화가 필요한 경우</li>
</ol>
<p>지금까지 정말 많은 버전의 Todo를 만들었지만 구조와 책임에 대해 고민하며 만들었던 적은 없었던 것 같습니다. 어떤 것을 만드느냐는 중요하지 않고 어떤 고민을 하며 만드느냐가 중요하다는 것을 깨닫게 되는 시간이었습니다. 기능과 UI 구현보다는 보다 근본적인 것들에 대해 고민하는 시간을 데브코스에서 보내고 싶습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠키, 세션, 웹 스토리지]]></title>
            <link>https://velog.io/@kim-jaemin420/%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%EC%9B%B9-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-indexedDB</link>
            <guid>https://velog.io/@kim-jaemin420/%EC%BF%A0%ED%82%A4-%EC%84%B8%EC%85%98-%EC%9B%B9-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-indexedDB</guid>
            <pubDate>Thu, 21 Sep 2023 08:38:11 GMT</pubDate>
            <description><![CDATA[<h2 id="http의-특징과-쿠키와-세션을-사용하는-이유">HTTP의 특징과 쿠키와 세션을 사용하는 이유</h2>
<p>쿠키와 세션을 사용하게 된 이유는 HTTP 프로토콜의 특징이자 약점을 보완하기 위해 사용합니다. 우선 HTTP 프로토콜의 특징에 대해 알아보겠습니다.</p>
<h3 id="http-프로토콜">HTTP 프로토콜</h3>
<p>HTTP는 TCP 기반으로 데이터를 주고 받는데 사용되는 프로토콜입니다. HTTP는 클라이언트와 서버 간에 데이터를 전송하고, 웹 페이지, 이미지, 동영상 같은 여러 가지 리소스를 요청하고 전송하는 역할을 합니다. 
HTTP 프로토콜은 몇 가지 특징이 있는데, 그 중 <code>Connectionless</code>와 <code>Stateless</code>에 대해 살펴보겠습니다.</p>
<p><strong>1. Connectionless 프로토콜</strong>
클라이언트가 서버에 요청했을 때 그 요청에 맞는 응답을 보낸 후 연결을 끊는 처리방식입니다.</p>
<ul>
<li>HTTP 1.1버전에서 커넥션을 계속 유지하고, 요청에 재활용하는 기능이 추가되었습니다. HTTP Header에 keep-alive 옵션을 주어 커넥션을 재활용할 수 있습니다.(keep-alive 옵션은 default 옵션입니다.)</li>
</ul>
<blockquote>
<p>개인적으로 궁금했던 점
Q: HTTP 프로토콜은 TCP 기반 통신이다. TCP는 연결형 통신인데 HTTP 프로토콜은 왜 Connectionless 한걸까?
A: TCP 통신이 연결형 통신인 이유는 3-way handshake를 하기 때문이다. 3-way handshake는 연결을 설정하기 위해 신뢰성 있는 통신을 위한 초기 단계이다. 이 과정에서 클라이언트가 서버에 연결을 요청하고, 서버가 수락하는 과정을 거쳐 연결이 확립된다. 이를 통해 HTTP 프로토콜이 데이터를 주고 받을 준비를 마친다. HTTP는 요청을 보내고 응답을 받으면 연결을 유지하지 않기 때문에 TCP 기반임에도 Connectionless 합니다.</p>
</blockquote>
<p><strong>2. Stateless 프로토콜</strong>
커넥션을 끊는 순간 클라이언트와 서버의 통신이 끝나며 상태 정보는 유지하지 않는 특성이 있습니다. 클라이언트와 첫 번째 통신에서 데이터를 주고 받았다고 해도 두 번째 통신에서 이전 데이터를 유지하지 않습니다.
정보가 유지되지 않으니, 매번 페이지를 이동할 때마다 로그인을 해야 하거나 상품을 선택했는데 구매 페이지에서 선택한 상품의 정보가 없는 상황이 발생합니다.</p>
<p>따라서 이러한 특징을 보완하기 위해 쿠키와 세션을 사용하게 되었습니다. 쿠키와 세션의 가장 큰 차이점은 상태 정보의 저장 위치입니다.
쿠키는 <strong>클라이언트</strong>에 저장하고 세션은 <strong>서버</strong>에 저장합니다.</p>
<h2 id="쿠키">쿠키</h2>
<p>쿠키는 클라이언트에서 저장하고 관리하는 데이터들이고, 만료 날짜를 설정한다면 브라우저를 닫아도 데이터를 유지할 수 있습니다. 서버에서 Set-Cookie를 응답 헤더로 내려주면 클라이언트는 이를 받아 저장합니다. 쿠키의 취약점은 XSS(Cross-Site-Script) 공격을 당할 수 있는 것입니다. 보안에 취약하기 때문에 중요한 개인 정보를 저장할 수 없습니다.</p>
<p><strong>동작 순서</strong></p>
<ol>
<li>클라이언트가 페이지를 요청</li>
<li>서버가 쿠키 생성</li>
<li>생성한 쿠키 정보를 응답 Header에 담아 내려준다.</li>
<li>클라이언트는 받은 쿠키를 서버에 요청할때 요청 Header에 담아 보낸다.</li>
</ol>
<ul>
<li>사용 예시)</li>
</ul>
<ol>
<li>방문 사이트에서 로그인 시, &quot;아이디와 비밀번호를 저장하시겠습니까?&quot;</li>
<li>팝업창을 통해 &quot;오늘 이 창을 다시 보지 않기&quot; 체크</li>
</ol>
<h2 id="세션">세션</h2>
<p>세션도 쿠키처럼 클라이언트와 서버 간의 통신에서 사용자의 상태를 유지하는 데 사용됩니다. 쿠키는 클라이언트에서 저장하고 관리되는 반면, 서버는 클라이언트에게 session ID를 응답 Header를 통해 내려주고 실제 세션 정보는 서버측에 저장됩니다. 세션은 서버에 저장되기 때문에 과도한 세션 사용은 서버 부하를 초래할 수 있습니다. 이런 문제 때문에 최근엔 사용자 인증 문제를 JWT 토큰을 많이 사용하기도 합니다.</p>
<p><strong>동작 순서</strong></p>
<ol>
<li>클라이언트가 페이지 요청</li>
<li>서버는 요청 Header의 Cookie를 확인하여 Session-ID를 보냈는지 확인</li>
<li>Session-ID가 존재하지 않는다면 Session-ID를 새로 생성하여 응답 내려준다.</li>
<li>클라이언트는 받은 id를 쿠키에 저장하고 요청시 요청 Header에 담아 요청</li>
<li>서버는 요청 Header를 통해 Session-ID를 확인하고 응답 처리</li>
</ol>
<ul>
<li>사용 예시)</li>
</ul>
<ol>
<li>화면 전환시 로그인 유지(유저가 로그아웃 하기 전까지)</li>
</ol>
<h2 id="웹-스토리지">웹 스토리지</h2>
<p>웹 스토리지는 브라우저 내에서 데이터를 저장하고 관리할 수 있는 저장 공간으로 로컬 스토리지와 세션 스토리지가 있습니다. 쿠키는 약 4KB까지 저장 공간을 이용할 수 있지만 웹 스토리지는 약 5MB까지 저장 가능합니다. 또, 쿠키는 데이터를 String으로 관리되기 때문에 데이터를 사용하려면 따로 라이브러리를 사용하거나 직접 구현을 해야 하는 반면, 웹 스토리지는 key, value로 관리되고 getItem, setItem을 통해 쉽게 접근 가능합니다.</p>
<h3 id="로컬-스토리지">로컬 스토리지</h3>
<p>로컬 스토리지는 브라우저에 반영구적으로 데이터를 저장하며, 브라우저를 종료해도 데이터가 유지됩니다. 브라우저 자체에 반영구적으로 데이터가 유지되지만 도메인이 다른 경우에는 로컬 스토리지에 접근할 수 없습니다.</p>
<h3 id="세션-스토리지">세션 스토리지</h3>
<p>세션 스토리지는 각 세션마다 데이터가 개별적으로 저장됩니다. 예를 들어, 브라우저에서 여러 개의 탭을 실행하면 탭마다 개별적으로 데이터가 저장됩니다. 세션 스토리지는 로컬 스토리지와 다르게 세션을 종료하면 데이터가 자동으로 제거되고, 같은 도메인이더라도 다른 세션이라면 데이터에 접근할 수 없습니다.</p>
<h2 id="면접-관련-질문">면접 관련 질문</h2>
<ol>
<li><p>쿠키와 세션에 대해 설명해주세요.
쿠키와 세션은 HTTP 프로토콜의 단점을 보완하기 위해 사용합니다. 쿠키는 클라이언트에 저장되고 만료 날짜를 설정한다면 브라우저가 종료돼도 데이터가 유지됩니다. 서버에서 쿠키를 만들어 응답 헤더에 Set Cookie로 내려주면 클라이언트는 이를 저장합니다. 쿠키의 단점은 XSS 공격에 취약하여 개인 정보를 저장할 수 없습니다. 반면에 세션은 정보가 서버측에 저장되기 때문에 인증 정보를 저장할 수 있습니다. 서버는 클라이언트에게 세션 id를 응답 헤더에 담아 내려주고 클라이언트는 세션 id를 쿠키에 저장하고 요청할때 요청 헤더에 담아 요청합니다. 세션 정보가 서버에 있다보니 사용자가 과도할 경우 서버에 부하가 있을 수 있습니다.</p>
</li>
<li><p>웹 스토리지에 대해 설명해주세요.
웹 스토리지는 브라우저 내에서 데이터를 저장하고 관리할 수 있는 저장 공간으로 로컬 스토리지와 세션 스토리지가 있습니다.
로컬 스토리지는 브라우저에 데이터가 반영구적으로 저장되며 브라우저를 종료해도 데이터가 유지됩니다. 또, 도메인이 같을 경우에만 로컬 스토리지에 접근할 수 있습니다.
반면 세션 스토리지는 각 세션별로 데이터가 개별적으로 저장됩니다. 세션 스토리지는 세션이 종료되면 데이터가 제거되고, 같은 도메인이더라도 다른 세션이라면 데이터에 접근할 수 없습니다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[호이스팅과 TDZ]]></title>
            <link>https://velog.io/@kim-jaemin420/%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EA%B3%BC-TDZ</link>
            <guid>https://velog.io/@kim-jaemin420/%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EA%B3%BC-TDZ</guid>
            <pubDate>Wed, 20 Sep 2023 09:09:30 GMT</pubDate>
            <description><![CDATA[<h2 id="1️⃣-변수-호이스팅">1️⃣ 변수 호이스팅</h2>
<p>var 키워드로 선언한 변수는 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 <strong>선언 단계</strong>와 <strong>초기화 단계</strong>가 한 번에 진행됩니다. 변수 선언문을 포함한 모든 선언문(함수 선언문, 클래스 선언문 등)은 모두 스코프 최상단으로 끌어올려진 것 처럼 작동합니다. 런타임 이전에 렉시컬 환경에 번수 식별자를 등록하고 var 같은 경우 undefined로 초기화까지 진행됩니다.
그러나 let 키워드로 선언한 변수는 <strong>선언 단계</strong>와 <strong>초기화 단계</strong>가 분리되어 진행됩니다. 앞서 말한 것처럼 선언문은 스코프 최상단으로 끌어올려진 것처럼 동작하기 때문에 렉시컬 환경에 변수 식별자를 등록하지만 초기화는 하지 않습니다. 초기화는 변수 선언문에 도달했을 때 실행되고 선언 이전에 접근시 reference Error가 발생합니다.
스코프 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 <strong>일시적 사각지대(Temporal Dead Zone)</strong>라고 합니다.</p>
<h2 id="2️⃣-tdz">2️⃣ TDZ</h2>
<p>TDZ는 선언 전에 변수를 사용하는 것을 비 허용하는 개념상의 공간입니다. 다음의 그림을 통해 쉽게 이해할 수 있습니다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/8dc4bfbc-7b6b-4159-b401-c550d7f8d740/image.png" alt=""></p>
<p>var는 선언과 초기화가 동시에 되어 TDZ가 없는데, let과 const는 TDZ가 어떻게 생기는지 자바스크립트 엔진 코드를 통해 알아보겠습니다.</p>
<pre><code class="language-c">// var
var-&gt;AllocateTo(VariableLocation::PARAMETER, index);

// const
VariableProxy* proxy =
    DeclareBoundVariable(local_name, VariableMode::kConst, pos);
proxy-&gt;var()-&gt;set_initializer_position(position());

// let
VariableProxy* proxy =
    DeclareBoundVariable(variable_name, VariableMode::kLet, class_token_pos);
proxy-&gt;var()-&gt;set_initializer_position(end_pos);</code></pre>
<p>var같은 경우 <code>AllocateTo</code> 메서드를 통해 선언과 동시에 메모리에 공간을 확보해 저장합니다. 반면, let과 const는 <code>set_initializer_position</code> 메소드를 통해 해당 코드의 위치를 의미하는 position 값만 정해줍니다. 선언은 되어 있지만 변수를 위한 메모리에 공간이 확보되지 않은 상태가 됩니다. 이 시점에 let, const 키워드로 생성된 변수들이 <strong>일시적 사각지대</strong>에 들어가게 됩니다.</p>
<h2 id="3️⃣-면접-질문-대비">3️⃣ 면접 질문 대비</h2>
<p><strong>1. 호이스팅에 대해 설명해주세요.</strong>
호이스팅은 자바스크립트에서 변수 선언문을 포함한 함수 선언문이나 클래스 선언문 같은 모든 선언문을 사용했을때 스코프 최상단으로 끌어올려진 것 처럼 작동하는 것을 말합니다.
var 키워드를 사용한 변수 선언문을 예로 들었을 때, 런타임 이전에 선언 단계와 초기화 단계가 동시에 일어나 렉시컬 환경에 변수 식별자를 등록하고 undefined로 초기화까지 마칩니다. 그래서 선언 전에 에러없이 변수에 접근할 수 있습니다.
반면, let과 const는 런타임 이전에 초기화 단계가 진행되지 않아, 변수를 선언하기 전에 참조하면 reference Error가 발생합니다. 이 때문에 실제로 let과 const에서도 호이스팅이 발생하지만 발생하지 않는 것처럼 보입니다.</p>
<p><strong>2. 그렇다면 TDZ에 대해서도 알고 계신가요?</strong>
앞서 설명드린 것처럼 let과 const는 스코프의 시작 지점부터 변수 선언문, 다시 말해 초기화 단계 시작 지점까지 변수를 참조할 수 없습니다. 스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 <strong>TDZ</strong> 라고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[web server와 WAS]]></title>
            <link>https://velog.io/@kim-jaemin420/web-server%EC%99%80-WAS</link>
            <guid>https://velog.io/@kim-jaemin420/web-server%EC%99%80-WAS</guid>
            <pubDate>Tue, 19 Sep 2023 15:56:14 GMT</pubDate>
            <description><![CDATA[<p>웹 서버와 웹 어플리케이션 서버(WAS)의 차이에 대해서 알아봅시다.</p>
<h2 id="1️⃣-static-pages-vs-dynamic-pages">1️⃣ static pages VS dynamic pages</h2>
<p>우선, 정적 페이지와 동적 페이지에 대해서 알아봅시다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/f5ba097e-e4e0-40ec-9ab8-192b7df0beed/image.png" alt=""></p>
<h3 id="1️⃣-static-pages">1️⃣ static pages</h3>
<p>static pages는 바뀌지 않는 페이지로, 웹 서버는 파일 경로의 이름을 받고, 경로와 일치하는 file contents를 반환합니다.
항상 동일한 페이지를 반환합니다.</p>
<ul>
<li>image, html, css javascript 파일과 같이 컴퓨터에 저장된 파일들<h3 id="1️⃣-dynamic-pages">1️⃣ dynamic pages</h3>
dynamic pages는 인자에 따라 바뀌는 페이지로, 인자의 내용에 맞게 동적인 컨텐츠를 반환합니다.</li>
</ul>
<h2 id="2️⃣-웹-서버와-was의-차이">2️⃣ 웹 서버와 WAS의 차이</h2>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/e34eb6f7-d168-4a9d-bd64-01339d2e91a6/image.png" alt=""></p>
<h3 id="2️⃣-웹-서버">2️⃣ 웹 서버</h3>
<p>개념적으로 하드웨어와 소프트웨어로 구분됩니다.</p>
<ul>
<li>하드웨어: 웹 서버가 설치되어 있는 컴퓨터</li>
<li>소프트웨어: 웹 브라우저 클라이언트로부터 HTTP 요청을 받고, 정적인 컨텐츠(html, css등)를 제공하는 컴퓨터 프로그램</li>
</ul>
<h3 id="2️⃣-웹-서버의-기능">2️⃣ 웹 서버의 기능</h3>
<p>웹 서버는 HTTP 프로토콜을 기반으로, 클라이언트의 요청을 서비스하는 기능을 담당합니다.
웹 서버는 요청에 맞게 두 가지 기능을 할 수 있습니다.</p>
<blockquote>
</blockquote>
<ul>
<li>정적 컨텐츠 제공: WAS를 거치지 않고 바로 리소스 제공</li>
<li>동적 컨텐츠 제공: 클라이언트 요청을 WAS에 보내고, WAS에서 처리한 결과를 클라이언트에게 전달</li>
</ul>
<h3 id="2️⃣-웹-서버-종류">2️⃣ 웹 서버 종류</h3>
<blockquote>
<p>Apache, Nginx, IIS 등</p>
</blockquote>
<h3 id="2️⃣-was">2️⃣ WAS</h3>
<p>웹 어플리케이션 서버로 DB 조회 및 다양한 로직 처리 요구시 동적인 컨텐츠를 제공하기 위해 만들어진 어플리케이션 서버입니다.
HTTP를 통해 어플리케이션을 수행해주는 미들웨어입니다.
WAS는 웹 컨테이너 또는 서블릿 컨테이너라고도 불립니다.</p>
<h3 id="2️⃣-was-역할">2️⃣ WAS 역할</h3>
<blockquote>
<p>was = 웹 서버 + 웹 컨테이너</p>
</blockquote>
<p>웹 서버의 기능들을 구조적으로 분리하여 처리하는 역할을 합니다.</p>
<h3 id="2️⃣-was의-기능">2️⃣ WAS의 기능</h3>
<ol>
<li>프로그램 실행 환경 및 DB 접속 기능 제공</li>
<li>여러 트랜잭션 관리 기능</li>
<li>업무 처리하는 비즈니스 로직 수행</li>
</ol>
<h3 id="2️⃣-was-종류">2️⃣ WAS 종류</h3>
<blockquote>
<p>Tomcat, JBoss 등</p>
</blockquote>
<h2 id="3️⃣-웹-서버가-필요한-이유">3️⃣ 웹 서버가 필요한 이유</h2>
<p>웹 서버에서는 정적 컨텐츠만 처리하도록 기능을 분배해서 서버 부담을 줄이기 위해 필요합니다.</p>
<p>웹 문서(HTML)를 클라이언트로 보낼 때, 이미지 파일과 같은 정적 파일은 함께 보내지 않습니다.
먼저 HTML 문서를 받고, 이에 필요한 이미지 파일들을 다시 서버로 요청해서 받아오는 것입니다.
따라서, 웹 서버를 통해서 정적인 파일을 어플리케이션 서버까지 가지 않고 앞단에서 빠르게 보낼 수 있습니다.</p>
<h2 id="4️⃣-was가-필요한-이유">4️⃣ WAS가 필요한 이유</h2>
<p>WAS를 통해 요청에 맞는 데이터를 DB에서 가져와 비즈니스 로직에 맞게 그때마다 결과를 만들어 제공하면서 자원을 효율적으로 사용할 수 있습니다.</p>
<p>사용자가 원하는 요청에 대한 결과물을 모두 미리 만들어 놓고 서비스하기에는 자원이 부족하기 때문에 웹 서버만으로는 무리가 있습니다.
따라서, WAS를 통해 요청이 들어올때마다 DB와 비즈니스 로직을 통해 결과물을 만들어 제공합니다.</p>
<h2 id="5️⃣-was로-웹-서버-역할까지-처리할-수-있는-것-아닌가">5️⃣ WAS로 웹 서버 역할까지 처리할 수 있는 것 아닌가?</h2>
<p>WAS는 DB 조회, 다양한 로직을 처리하는데 집중해야 합니다. 따라서 단순한 정직 컨텐츠는 웹 서버에게 맡겨 기능을 분리시켜 서버 부하를 방지해야 합니다.
만약 WAS가 정적 컨텐츠 요청까지 처리하면 부하가 커지고 동적 컨텐츠 처리가 지연되면서 수행 속도가 느려지게 되고 페이지 노출 시간이 늘어나는 문제가 발생할 수 있습니다.</p>
<h2 id="6️⃣-면접-대비">6️⃣ 면접 대비</h2>
<p><strong>1. 웹 서버와 WAS에 대해 설명해주세요.</strong></p>
<p>웹 서버는 웹 브라우저 클라이언트로부터 HTTP 요청을 받고 HTML, CSS 파일 같은 정적인 컨텐츠를 제공해주는 컴퓨터 프로그램입니다. 반면, WAS는 클라이언트가 DB 조회나 다양한 로직 처리를 요청할 경우 동적인 컨텐츠를 제공해주기 위한 어플리케이션 서버입니다. 정적인 컨텐츠에 대한 요청이 들어오면 웹 서버가 리소스를 제공해주고 동적인 컨텐츠에 대한 요청이 들어오면 웹 서버가 요청을 WAS에 보내고 WAS에서 처리한 결과를 클라이언트에 전달해줍니다.
웹 서버의 예시로는 Apache, Nginx 등이 있고 WAS는 TomCat, JBOss등이 있습니다.</p>
<p><strong>2. 그렇다면 동적 컨텐츠를 제공하는 WAS만 사용하면 되는 것 아닌가요? 웹 서버가 필요한 이유에 대해 설명해주세요.</strong></p>
<p>WAS에서 정적 컨텐츠 요청까지 처리하게 되면 부하가 커지고 동적 컨텐츠 처리가 지연되면서 수행 속도가 느려지게 되고 페이지 노출 시간이 늘어나는 문제가 있을 수 있습니다. 따라서 정적 컨텐츠는 웹 서버가 처리하여 기능을 분리시켜 서버 부하를 방지하는 것이 좋습니다.</p>
<blockquote>
<p>출처
<a href="https://gyoogle.dev/blog/web-knowledge/Web%20Server%EC%99%80%20WAS%EC%9D%98%20%EC%B0%A8%EC%9D%B4.html">https://gyoogle.dev/blog/web-knowledge/Web%20Server%EC%99%80%20WAS%EC%9D%98%20%EC%B0%A8%EC%9D%B4.html</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[slash] 모노레포로 된 라이브러리 뜯어보기]]></title>
            <link>https://velog.io/@kim-jaemin420/slash-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EB%A1%9C-%EB%90%9C-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/slash-%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EB%A1%9C-%EB%90%9C-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%9C%AF%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 16 May 2023 07:40:08 GMT</pubDate>
            <description><![CDATA[<h2 id="📕-slash의-모노레포-구성-살펴보기">📕 slash의 모노레포 구성 살펴보기</h2>
<h3 id="📘-yarn-berry">📘 yarn berry</h3>
<p>프로젝트 root에 <code>.pnp.cjs</code>가 있는 것을 보아 <code>yarn berry</code>를 사용하고 있는 것을 알 수 있었다. <code>yarn berry</code>는 모듈 설치시 node_modules가 아닌 <code>.pnp.cjs</code> 파일을 생성한다. <code>.pnp.cjs</code> 파일에는 모듈의 버젼과 저장된 위치, 참조하고 있는 모듈들이 기록된다. 또, slash는 <code>.yarn/cache</code>도 올라와 있는 것을 볼 수 있었다. <code>.yarn/cache</code> 파일을 깃으로 관리하게 되면 <code>zero-install</code>, 즉 의존성 설치 과정 없이 바로 사용할 수 있다.
프로젝트가 참조하고 있는 모듈들은 어떤 것인지 알고 싶어 <code>.pnp.cjs</code> 파일에서 <code>dependencyTreeRoots</code>를 찾아 보았다. 아 이런 것도 모듈로 만들어서 쓸 수 있구나 싶은 것들이 많았다. <code>react</code>, <code>a11y</code>, <code>redirect</code> 등 기존에 존재하던 것도, 존재하지 않는 것도 모듈을 만들어 사용하고 있었다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/0d5a1dc3-297c-429b-90ad-66b74acff1bb/image.png" alt=""></p>
<h3 id="📘-lerna">📘 lerna</h3>
<p>root에 <code>lerna.json</code>을 보고 <code>lerna</code>를 사용하여 만들었다는 것을 알았다. 원티드 프리온보딩 5월 모노레포 수업에서 <code>turbo repo</code>만 다루어서 <code>lerna</code>에 대해선 잘 몰랐는데 알고보니 <code>turbo repo</code> 보다 먼저 나왔다. 각각 뭐가 다른지 궁금해 비교표를 찾아보았다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/f7276ddd-1509-4bca-94ba-e095eff8a9a9/image.png" alt="">
예상 외로 Nx가 가장 빠르고 확장 가능한 툴로 장점이 많았다. 이외에도 Nx와 Turborepo는 모두 캐싱 기능을 지원해서 빌드 측면에서 우수한 속도를 자랑하고, 의존성 그래프 시각화 기능을 통해 프로젝트의 구성 요소들이 서로 어떤 의존관계를 갖고있는 지 한눈에 파악이 가능하도록 해준다.
내 짧은 지식으로는 왜 <code>slash</code>가 <code>lerna</code>를 채택했는지 납득이 잘 가지 않았다. 추후 내 개인 프로젝트를 모노레포로 구성한다면, 나는 아마 <code>yarn berry workspace</code>를 쓰거나, <code>Nx</code>, <code>Turbo repo</code> 중에 선택하게 될 것 같다. 아니면<code>lerna with Nx</code>? (Nrwl이 lerna를 인수하여 Nx에 통합하고 개선하는 작업 중이며 <code>lerna with Nx</code>는 <code>Turbo</code>보다 5X 빠르다고 한다.)</p>
<blockquote>
<p>출처: <a href="https://d2.naver.com/helloworld/7553804">https://d2.naver.com/helloworld/7553804</a></p>
</blockquote>
<h3 id="📘-tsconfig">📘 tsconfig</h3>
<p>프로젝트 root에 tsconfig, eslint, prettier 설정 파일을 만들었다. <code>tsconfig.build.json</code>도 있었는데, exclude 설정을 통해 build에 제외시킬 파일을 미리 설정하여 효율적으로 build 하여 성능을 향상시킬 수 있다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/6af7508f-14bf-40ea-a8ae-53c0709ead4f/image.png" alt="">
root에 만들어둔 tsconfig 파일을 각 모듈에서 extends 하여 사용하고 있다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/ff4a954f-bf4f-40b3-9718-6249b46a4dfd/image.png" alt=""></p>
<h3 id="📘-packages">📘 packages</h3>
<p>packages 안에 모듈 파일을 단순 나열한 것이 아니라, react 관련 모듈과 아닌 것으로 나누어 분류했다. 훨씬 깔끔하고 찾아들어가기도 편해 보였다. 나중에 이 구조는 따라해봐야겠다. 
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/0f041232-a0b1-4ba1-9d17-1f37b5c7771e/image.png" alt="">
비슷한 구조를 가지고 있는 라이브러리 중에 <code>radix/ui</code>도 있다. <code>radix/ui</code>는 <code>packages&gt; core + react</code>로 구성했다.</p>
<h3 id="📘-packagesreact-모듈">📘 packages/react 모듈</h3>
<p>이름만 봤을 땐 react 기본 훅을 한 번 더 감싼거라고 생각했는데, 그게 아니라 react 기본 훅을 사용한 커스텀 훅 모듈이었다. react 폴더 안에서도 또 <code>components</code>, <code>hooks</code>, <code>utils</code>로 나누어져 있다. <code>hooks</code> 안에는 디렉토리 구분 없이 <code>useCallbackOne</code>, <code>useDebounce</code>, <code>useForceUpdate</code> 같은 파일이 들어있었고, <code>XX.en.md</code>, <code>XX.ko.md</code> 파일로 커스텀 훅의 정의와 사용 방법을 적어놓았다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/22a7dd84-8fec-4882-9f6f-df5033ce8d00/image.png" alt="">
설명을 다 적어두니 라이브러리 사용자 입장에선 엄청 편리하고 좋았다. 이것도 나중에 기억하고 써먹어야겠다.
그리고 각 파일의 테스트 코드도 있었다. 어떻게 테스트 코드를 짜야할지 막막할 때가 많았는데, 좋은 참고서가 될 것 같다. slash는 jest를 사용해 테스트 하고 있다.</p>
<p>평소 프로젝트를 만들 때, 스크린 리더 사용자에게만 보여주려고 할땐 <code>a11y-hidden</code> 스타일링을 적용해줬다. 괜찮은 방법에 대해서 많이 생각해봤는데 대안이 딱히 떠오르지 않아, 그냥 스타일을 줬었다. 그런데 토스는 <code>ScreenReaderOnly</code>이라는 훅을 만들어 사용하고 있었다. 이 방법이 가독성도 더 좋고 다른 설정도 추가할 수 있어 괜찮은 방법이었다. <code>ScreenReaderOnly</code> 이외에도 <code>HiddenHeading</code>이라는 스크린 리더 사용자가 헤더 기준으로 움직일 수 있게 숨겨진 헤딩 컴포넌트도 있다.</p>
<h3 id="📘-packagescommon-모듈">📘 packages/common 모듈</h3>
<p>common에서 가장 눈에 띄고 궁금했던건 <code>hangul</code>이었다. 안에는 한글 처리에 대한 함수들이 있었다. <code>josa</code> 라는 이름의 어떤 단어의 적절한 조사를 붙여주는 함수, <code>chosungIncludes</code>라는 문자열에서 초성 일치 검색을 하는 함수 등이 있다. 발음 나는 그대로 파일 명을 작성한 것과 한글 처리에 대해 알아볼 수 있었다.
그 외에도 storage, validators 등 여러 가지 util이 많았다.</p>
<h3 id="📘-cicd">📘 CI/CD</h3>
<p>slash는 <code>CircleCI</code>를 사용중이다. <code>circleCI</code>에 대해선 잘 모르는데 <code>config.yml</code> 파일을 들어가보니 <code>github actions</code>와 크게 달라보이진 않았다. config 파일에서 <code>yarn install --immutable</code>이 눈에 띄었는데, <code>immutable</code> 옵션을 사용하면 패키지 설치 과정에서 패키지의 버전이나 해시 값이 변경되지 않도록 보장한다고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[황준일]프레임워크 없이 만드는 SSR를 읽은 후기]]></title>
            <link>https://velog.io/@kim-jaemin420/%ED%99%A9%EC%A4%80%EC%9D%BC%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%97%86%EC%9D%B4-%EB%A7%8C%EB%93%9C%EB%8A%94-SSR%EB%A5%BC-%EC%9D%BD%EC%9D%80-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/%ED%99%A9%EC%A4%80%EC%9D%BC%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%97%86%EC%9D%B4-%EB%A7%8C%EB%93%9C%EB%8A%94-SSR%EB%A5%BC-%EC%9D%BD%EC%9D%80-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Tue, 09 May 2023 04:12:35 GMT</pubDate>
            <description><![CDATA[<p>[프레임워크 없이 만드는 SSR]을 읽고 SSR로 유명한 Next.js가 어떤 방식으로 SSR과 SSG를 하는지 알아보았다. SSG, SSR의 개념에서 build time이나 run time 등에 관한 개념이 나오므로 이 들의 개념을 먼저 알아보았다.</p>
<h2 id="build-time-vs-run-time">build time VS run time</h2>
<h3 id="build-time이란">build time이란?</h3>
<p>build time, 혹은 build step은 어플리케이션 코드가 배포되기 위한 일련의 단계를 말한다.
어플리케이션을 빌드할 때, Next.js는 코드를 production에 최적화된 파일로 변환하여 서버에 배포하고 사용자가 사용할 수 있도록 만든다. 이 최적화된 파일에는 다음과 같은 것들이 포함된다.</p>
<ul>
<li>정적으로 생성된 페이지의 HTML 파일들</li>
<li>server에서 렌더링될 페이지들의 JS code</li>
<li>client에서 interactive하게 만들어질 페이지들의 JS code</li>
<li>css file들</li>
</ul>
<h3 id="run-time이란">run time이란?</h3>
<p>run time 혹은 request time은 어플리케이션이 빌드되고 배포된 후에, 사용자의 요청에 의해 어플리케이션이 실행되는 기간을 말한다.</p>
<h3 id="번외-compile-time이란">번외) compile time이란?</h3>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/565d804c-4da4-4213-ad8f-22c2adda2a2b/image.png" alt=""></p>
<p>compile time이란 프로그램을 컴파일하는 동안 소요되는 시간을 뜻한다. 여기서 compile이란 소스 코드를 기계어로 변환하는 과정을 말한다. (A언어 -&gt; 컴파일 -&gt; B언어로 변환하는 과정)</p>
<p>컴파일 시간은 개발자에게 중요한 요소 중 하나이다. 긴 컴파일 시간은 개발자의 생산성을 저하시킬 수 있고, 코드 변경 후 효괄르 바로 확인하기 어렵게 만들 수 있다. 따라서 개발자들은 컴파일 시간을 최적화하기 위해 다양한 방법을 사용하기도 한다.</p>
<p><strong>참고) 컴파일 시간은 프로그램 실행 시간과는 별개의 개념이다.</strong>
컴파일 시간은 소스 코드를 변환하는 컴파일 과정에서 발생한 시간이고 실행 시간은 프로그램을 실행 시켜 실제로 동작하는 동안 소요되는 시간을 의미한다.</p>
<p>*<em>참고)자바스크립트는 인터프리터 언어로 컴파일 언어가 아니다. *</em>
자바스크립트는 컴파일 타임을 거치지 않는 인터프리터 언어이다. 하지만 v8엔진 같은 현대 자바스크립트 엔진은 일부 코드를 컴파일 하여 최적화 해준다. 따라서, 자바스크립트 자체는 인터프리터 언어이나, 자바스크립트 엔진은 컴파일을 해주고 있다.</p>
<h3 id="번외-런타임-에러와-컴파일-타임-에러">번외) 런타임 에러와 컴파일 타임 에러</h3>
<p>이 둘의 주요한 차이점은 오류가 발생하는 시점에 있다. 컴파일 타임과 런타임의 개념을 생각해 보면 이 둘의 차이점을 잘 알 수 있다.
컴파일러는 소스 코드를 분석하고 구문 오류, 타입 오류 같은 문법적으로 이상이 없는지 확인 하고 문제가 있다면 컴파일을 중단한다. 따라서 컴파일 타임 에러란, 문법적으로 이상이 없는지 검사하는 단계이다.
예를 들면 다음과 같은 경우를 들 수 있다.</p>
<ul>
<li><code>compile time error</code><pre><code class="language-ts">console.log(name}
// 문법에 맞지 않는 구문 사용</code></pre>
</li>
</ul>
<p>런타임 오류는 프로그램이 실행되는 도중에 발생하는 오류다. 런타임 오류는 컴파일 단계에서는 감지되지 않고 프로그램이 실행되는 동안 발생한다. 예를 들어, 예외 처리되지 않은 상황이나 메모리 오버플로우 등 다양한 상황에서 발생할 수 있다.</p>
<ul>
<li><code>run time error</code><pre><code class="language-ts">const a = 0;
const b = 1;
</code></pre>
</li>
</ul>
<p>console.log(b / a); // 문법적으로 문제는 없으나 0으로 나눔</p>
<pre><code>
&gt; 정리
- 컴파일 타임 에러 : 코드 컴파일 중 발생. 문법적 에러
- 런 타임 에러 : 프로그램 실행 중 발생. 실행 흐름이나 잘못된 동작 발생한 경우

## pre-rendering이란?
pre-rendering은 페이지를 미리 생성하여 정적인 HTML 파일로 변환하는 과정을 의미한다. pre-rendering은 서버 측에서 미리 페이지를 렌더링하여 정적 파일로 생성하거나, 빌드 시점에서 사전에 페이지를 렌더링하는 방식으로 이루어진다.

pre-rendering은 next.js와 같은 프레임워크에서 SSG(Static Site Generation) 또는 SSR(Server-side Rendering)과 함께 사용되는 기술이다. SSG에서는 **빌드 시점**에서 사전에 페이지를 렌더링하여 정적 HTML 파일을 생성하고, SSR에서는 요청 시점에 서버에서 페이지를 렌더링하여 동적으로 HTML을 생성한다.

## SSG란?
SSG는 사전에, build time에 정적인 HTML 파일을 생성하는 방식이다. SSG를 사용하면data를 사용하는 페이지 혹은 데이터를 사용하지 않은 페이지를 정적으로 생성할 수 있다. next.js에서는 데이터를 가져오기 위해 `getStaticProps` 또는 `getStaticPaths` 라는 메서드를 사용하여 빌드 시점에서 데이터를 가져오는 로직을 정의할 수 있다.

SSG는 사전에 페이지를 렌더링하여 정적인 HTML 파일로 생성한다. 이렇게 생성된 정적 파일은 서버에 배포되어 CDN(Content Delivery Network)을 통해 최종 사용자에게 제공된다. 정적 파일은 CDN의 캐싱 기능을 활용하여 성능을 향상시킬 수 있다.

## SSR이란?
페이지를 servier-side rendering 한다면, 페이지의 HTML은 매 요청마다 생성된다. 페이지를 server-side rendering 하기 위해서는 `getServerSideProps`라는 async 함수를 export 해야 한다. 매 요청마다 서버에서 이 함수를 호출할 것이다. 앞서 말한 `getStaticProps`와 얼핏 비슷한 것 같지만 `getServerSideProps`는 빌드 타임 대신에 매 요청시 호출된다는 것이 다르다.


## Next.js에서의 hydrate
- &lt;a href=&quot;https://github1s.com/vercel/next.js/blob/canary/packages/next/src/server/render.tsx&quot;&gt;`packages/next/src/server/render.tsx`&lt;/a&gt;

```ts
//...
export async function renderToHTML(
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
    query: NextParsedUrlQuery,
    renderOpts: RenderOpts,
): Promise&lt;RenderResult | null&gt; {
  // ...

  const renderDocument = aysnc () =&gt; {
      // ...
    async function loadDocumentInitialProps(
        renderShell?: (_App: AppType, _Component: NextComponentType) =&gt; Promise&lt;ReactReadableStream&gt;
    ) {
           // ...
        const html = ReactDOMServer.renderToString(/*⭐️*/
            &lt;Body&gt;
                  &lt;AppContainerWithIsomorphincFiberStructure&gt;
                      {renderPageTree(EnhancedApp, EnhancedComponent, {
                        ...props,
                          router,
                    })}
                  &lt;/AppContainerWithIsomorphincFiberStructure&gt;
              &lt;/Body&gt;
        );
        return { html, head };
      }
  };
}</code></pre><p>우선 서버에서 어떻게 HTML을 렌더링하고 있는지 살펴 보았다. <code>/server/render.tsx</code>에서 ReactDOMServer 메서드를 사용하여 렌더링하고 있었다. ReactDOMServer는 React component를 문자열로 변환하는 메서드를 제공한다. <code>renderToString</code>은 주어진 React 엘리먼트를 문자열로 변환해 완전히 렌더링된 HTML을 반환한다.</p>
<p>이어서 client쪽에선 어떻게 동작하는지 알아봤다.</p>
<ul>
<li><a href="https://github1s.com/vercel/next.js/blob/canary/packages/next/src/client/next.ts"><code>packages/next/src/client/next.js</code></a></li>
</ul>
<pre><code class="language-ts">initialize({})
    .then(() =&gt; hydrate())
    .catch(console.error);</code></pre>
<p><code>initialize()</code>가 실행되고 <code>hydrate()</code>가 실행된다.</p>
<ul>
<li><p><a href="https://github1s.com/vercel/next.js/blob/canary/packages/next/src/client/index.tsx"><code>client/index.tsx</code></a></p>
<pre><code class="language-ts">export async function initialize(opts: { webpackHMR?: any } = {}): Promise&lt;{
assetPrefix: string;
}&gt; {
  initialData = JSON.parse(document.getElementById(&#39;__NEXT_DATA__&#39;)!.textContent!);
    window.__NEXT_DATA__ = initialData;

    const prefix: string = initialData.assetPrefix || &#39;&#39;;

    appElement = document.getElementById(&#39;__next__&#39;);
    return { assetPredix: prefix }; 
}</code></pre>
<p><code>initialize()</code>는 서버에서 렌더링한 HTML에서 <code>__NEXT_DATA_</code>를 id로 갖는 엘리먼트를 window 객체에 저장한다.
이 부분은 vanilla ssr에서 다음 부분과 일치한다.</p>
</li>
<li><p><code>/src/ssr.js</code></p>
<pre><code class="language-js">import { App } from &quot;./components.js&quot;;
</code></pre>
</li>
</ul>
<p>export const generateHTML = ({ todoItems }) =&gt; <code>&lt;!DOCTYPE html&gt;
  &lt;html lang=&quot;en&quot;&gt;
  &lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Todo List&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;div id=&quot;app&quot;&gt;
      ${App(todoItems)}
    &lt;/div&gt;
    &lt;script&gt;window.__INITIAL_MODEL__ = ${JSON.stringify({ todoItems })}&lt;/script&gt;
    &lt;script src=&quot;./src/main.js&quot; type=&quot;module&quot;&gt;&lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;</code>; </p>
<pre><code>```js
&lt;script&gt;window.__INITIAL_MODEL__ = ${JSON.stringify({ todoItems })}&lt;/script&gt;</code></pre><p>vanilla ssr에서 한 방법과 동일하게 Next.js에서도 서버에서 렌더링한 HTML을 window 객체에 담아 client에서도 사용할 수 있게 한 것을 알 수 있었다.</p>
<ul>
<li><p><a href="https://github1s.com/vercel/next.js/blob/canary/packages/next/src/client/index.tsx"><code>/clinet/index.tsx</code></a></p>
<pre><code class="language-ts">export const hydrate(opts?: { beforeRender?: () =&gt; Promise&lt;void&gt; }) {
  // ...
    const renderCtx: RenderRouteInfo = {
      App; CachedApp,
        initial: true,
        Component: CachedComponent,
        props: initialData.props,
        error: initialErr,
  };

    render(renderCtx):
}</code></pre>
<p><code>hydrate()</code>는 실행하려는 페이지의 에러를 validate하고 렌더링할 때 필요한 context를 <code>render()</code>
의 인자로 전달한다.</p>
</li>
<li><p><a href="https://github1s.com/vercel/next.js/blob/canary/packages/next/src/client/index.tsx"><code>/clinet/index.tsx</code></a></p>
<pre><code class="language-ts">let shouldHydrate: boolean = true;
</code></pre>
</li>
</ul>
<p>function renderReactElement(domEl: HTMLElement, fn: (cb: () =&gt; void) =&gt; JSX.Element): void {
    // ...
      const reactEl = fn(shouldHydrate ? markHydrateComplete : markRenderComplete);</p>
<p>  // ...
  if (shouldHydrate) {
      ReactDOM.hydrate(reactEl, domEl);
    shouldHydrate = false;
  } else {
      ReactDOM.render(reactEl, domEl);
  }
}</p>
<pre><code>```ts
ReactDOM.hydrate(element, container[, callback]);</code></pre><p>그리고 ReactDOMServer로 렌더링된 HTML에 이벤트 리스터를 연결해준다. <code>ReactDOM.hydrate()</code>는 일반적으로 서버 사이드 렌더링(SSR)된 React 애플리케이션을 클라이언트에서 재사용할 때 사용된다. 서버에서 렌더링된 HTML과 클라이언트에서 생성된 React 컴포넌트를 일치시켜주는 역할을 한다. 즉, 서버에서 전달받은 HTML에 이미 렌더링된 상태가 포함되어 있으며, 클라이언트에서는 해당 HTML을 기반으로 컴포넌트를 초기화하여 이전에 렌더링된 상태를 유지한다.</p>
<p>이렇게 Next.js 내부에서 어떻게 hydrate 하는지 알아보았다. 블로그에서 vanilla js로 hydrate를 구현한 것과 크게 다르지 않게 구현되었다는 것을 알게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[황준일]Vanilla Javascript로 React useState hook 만들기를 읽은 후기]]></title>
            <link>https://velog.io/@kim-jaemin420/%ED%99%A9%EC%A4%80%EC%9D%BCVanilla-Javascript%EB%A1%9C-React-useState-hook-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/%ED%99%A9%EC%A4%80%EC%9D%BCVanilla-Javascript%EB%A1%9C-React-useState-hook-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Wed, 03 May 2023 09:27:43 GMT</pubDate>
            <description><![CDATA[<h2 id="컴포넌트가-여러-개일-때-usestate-만들기">컴포넌트가 여러 개일 때 useState 만들기</h2>
<pre><code class="language-js">let currentStateKey = 0;
const states = [];

function useState(initialState) {
  const key = currentStateKey;

  if (states.length === currentStateKey) {
    states.push(initialState);
  }

  const state = states[key];

  const setState = newState =&gt; {
    states[key] = newState;
    render();
  };

  currentStateKey += 1;

  return [state, setState];
}

function Counter() {
  const [count, setCount] = useState(1);

  window.increment = () =&gt; setCount(count + 1);

  return `
    &lt;div&gt;
      &lt;strong&gt;count: ${count}&lt;/strong&gt;
      &lt;button onclick=&quot;increment()&quot;&gt;증가&lt;/button&gt;
    &lt;/div&gt;
  `;
}

function Cat() {
  const [cat, setCat] = useState(&#39;고양이&#39;);

  window.meow = () =&gt; setCat(cat + &#39;야옹!&#39;);

  return `
    &lt;div&gt;
      &lt;strong&gt;${cat}&lt;/strong&gt;
      &lt;button onclick=&quot;meow()&quot;&gt;고양이의 울음소리&lt;/button&gt;
    &lt;/div&gt;
  `;
}

function render() {
  const $app = document.querySelector(&#39;#app&#39;);

  $app.innerHTML = `
    &lt;div&gt;
      ${Counter()}
      ${Cat()}
    &lt;/div&gt;
  `;

  currentStateKey = 0;
}

render();
</code></pre>
<p>블로그 글에 생략된 코드가 있어 이해에 어려움이 있었다. 블로그 본문에는 useState 함수 내부에 <code>const key = currentStateKey</code> 선언부가 빠져있었다. 이 부분이 빠지게 되면 currentStateKey는 계속 0을 가리키게 되고 <code>Cat</code>에서 setState를 사용하더라도 <code>Counter</code>의 상태가 변경되게 된다.</p>
<p>하지만, useState 내부에 <code>currentStateKey</code>를 변수에 복사하게 되면, 다르게 동작한다. 현재 <code>currentStateKey</code> 값이 아닌, 컴포넌트가 처음 호출될 때 당시의 <code>currentStateKey</code> 값을 기억하고 사용하므로 정확하게 상태를 변경할 수 있다.</p>
<h2 id="requestanimationframe은-태스크-큐에-들어가는가">requestAnimationFrame은 태스크 큐에 들어가는가?</h2>
<p><code>requestAnimationFrame</code>은 <code>setTimeout</code>이나 <code>setInterval</code>과는 다르게 현재 디스플레이 환경에 맞춰 최적의 빈도로 실행한다. 내가 알고 있는 큐는 task queue와 micro task queue가 있는데, <code>requestAnimationFrame</code>은 어디에 속하기에 최적의 빈도로 실행되는 것일까.</p>
<p>일반적으로 이벤트 루프가 콜스택에 무엇을 넣어 처리할지 결정할때, microtask를 가장 먼저 처리한다고 한다. microtask로는 promise 등이 속한다. 또, <code>setTimeout</code>이나 <code>setInterval</code> 같은 timer 함수는 task queue에 속한다.
microtask queue를 처리한 후에는 <strong>animation frame</strong>에 속하는 연산을 처리한다고 한다. 그러니까, queue는 <code>microtasck queue</code>, <code>task queue</code>, <code>animation frame</code> 이렇게 세 가지가 있는 것이다.
하지만 때로는 task queue에 있는 작업을 먼저 수행하기도 한다. 우선 순위는 animation frame이 먼저이지만, 초당 60프레임(혹은 주사율에 맞는)으로 보여주는 상황을 잘 충족하고 있다면 우선 순위를 task queue에게 양보하기도 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[황준일]Vanilla Javascript로 상태 관리 시스템 만들기를 읽은 후기]]></title>
            <link>https://velog.io/@kim-jaemin420/%ED%99%A9%EC%A4%80%EC%9D%BCVanilla-Javascript%EB%A1%9C-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0%EB%A5%BC-%EC%9D%BD%EC%9D%80-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/%ED%99%A9%EC%A4%80%EC%9D%BCVanilla-Javascript%EB%A1%9C-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0%EB%A5%BC-%EC%9D%BD%EC%9D%80-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 01 May 2023 09:35:39 GMT</pubDate>
            <description><![CDATA[<h2 id="observer-pattern에-대한-이해">observer pattern에 대한 이해</h2>
<p>Obserber pattern은 객체의 상태 변화를 관찰하는 옵저버들의 목록을 <code>객체에 등록해서</code> 상태 변화가 발생할 때마다 메서드 등을 통해 객체가 직접 목록의 각 옵저버들에게 통지하도록 하는 디자인 패턴.</p>
<h2 id="objectdefineproperty">Object.defineProperty()</h2>
<p>객체에 새로운 속성을 직접 정의하거나 이미 존재하는 속성을 수정한 후, 해당 객체를 반환한다.</p>
<pre><code class="language-js">Object.defineProperty(obj, attributeName, descriptor)</code></pre>
<p>매개변수</p>
<ul>
<li><code>obj</code> : 속성을 정의할 객체</li>
<li><code>attributeName</code> : 새로 정의하거나 수정하려는 속성의 이름 또는 Symbol</li>
<li><code>descriptor</code> : 새로 정의하거나 수정하려는 속성에 접근하거나 추가하려는 동작</li>
</ul>
<h3 id="descriptor">descriptor</h3>
<p>descriptor는 <code>데이터 서술자(data descriptors)</code>와 <code>접근자 서술사(access descriptors)</code> 두 가지 형식을 취할 수 있다. 데이터 서술자는 값을 가지는 속성을 기술할 때 사용한다. 접근자 서술자는 getter-setter 함수를 한 쌍으로 가지는 속성을 기술할 때 사용한다. 서술자는 두 유형 중 하나여야 하며, 두 유형을 동시에 나타낼 순 없다.
데이터 서술자와 접근자 서술자는 모두 객체로, <code>configurable</code>, <code>enumerable</code> 이 두 가지 키를 선택적으로 가질 수 있다.
<code>configurable</code> : 속성 값을 변경할 수 있고, 객체 삭제 가능하면 true. 기본 값은 false.
<code>enumerable</code> : 객체 속성 열거 시 노출되면 true. 기본 값은 false.</p>
<ul>
<li><p>데이터 서술자
데이터 서술자가 가질 수 있는 키는 다음과 같다.(선택적으로)
<code>value</code> : 속성에 연관된 값. javascript 리터럴 값은 모두 사용할 수 있다. 기본 값은 undefined.
<code>writable</code> : 할당 연산자로 속성의 값을 바꿀 수 있으면 true이다. 기본 값은 false.</p>
</li>
<li><p>접근자 서술자
접근자 서술자가 가질 수 있는 키는 다음과 같다.(선택적으로)
<code>get</code> : 속성의 접근자로 사용할 함수.
<code>set</code> : 속성의 설정자로 사용할 함수.</p>
</li>
</ul>
<p><code>value</code>또는 <code>writable</code>을 <code>get</code> 또는 <code>set</code> 키와 함께 가지고 있으면 오류 발생.</p>
<pre><code class="language-js">const obj = {};

Object.defineProperty(obj, &#39;a&#39;, {
    value: 37,
      writable: true,
      enumerable: true,
      configurable: true,
});
// &#39;a&#39; 속성이 obj 객체에 존재하고 값은 37

const bValue = 38;

Object.defineProperty(obj, &#39;b&#39;, {
    get() {
        console.log(bValue);
          return bValue;
    },

      set(newValue) {
        bValue = newValue;
          console.log(bValue);
    },

      enumerable: true,
      configurable: true,
});
// &#39;b&#39; 속성이 obj 객체에 존재하고 값은 38
// obj.b를 재정의 하지 않는 이상(set 하지 않는 이상)
// obj.b의 값은 항상 bValue와 동일함</code></pre>
<h2 id="vanilla-javascript로-observer-pattern-만들기">vanilla javascript로 observer pattern 만들기</h2>
<p>블로그의 코드를 잘게 뜯어서 이해해보자.</p>
<ul>
<li><p><code>observable</code></p>
<pre><code class="language-js">const observable = storeObj =&gt; {
Object.keys(obj).forEach(key =&gt; {
  let _value = obj[key];
  const observers = new Set();

  Object.defineProperty(storeObj, key, {
    get () {
      if (currentObserver) observers.add(currentObserver);
      return _value;
    },

    set (value) {
      _value = value;
      observers.forEach(fn =&gt; fn());
    }
  })
})
return obj;
}</code></pre>
<p>옵저버들의 목록을 저장할 객체 <code>observers</code> 생성한다. 중복으로 생성될 것을 방지하기 위해 Set 객체를 사용해준다. </p>
<pre><code class="language-js">const observable = storeObj =&gt; {
    // ...
  const observers = new Set();
    // ...
};</code></pre>
<p><code>Object.definePropert</code>의 <code>getter</code>를 사용하여 store 객체에 들어있는 상태에 접근할 수 있도록 한다. </p>
<pre><code class="language-js">const observable = storeObj =&gt; {
  //...
    Object.defineProperty(storeObj, key, {
      get() {
          return _value;
      },
  });
    // ...
};</code></pre>
<p>옵저버 함수를 생성할 때마다 <code>observers</code> 객체에 등록해주는 로직이 반복되기 때문에 <code>observers</code> 객체에 옵저버 함수를 추가하는 로직을 <code>getter</code>에 넣는다.</p>
<pre><code class="language-js">let currnetObserver = null;
</code></pre>
</li>
</ul>
<p>const observe = fn =&gt; {
      // currentObserver라는 변수로 현재 옵저브 함수를 등록 후 호출
    currentObserver = fn;
      fn();
      currentObserver = null;
}</p>
<p>const observable = storeObj =&gt; {
    // ...
      Object.defineProperty(storeObj, key, {
        get() {
            if (currentObserver) observers.add(currentObserver);</p>
<pre><code>          return stateValue;
    },
})
  // ...</code></pre><p>};</p>
<pre><code>`setter`에는 파라미터로 새로운 값을 받아 state를 변경해주고 `observers` 객체의 observer들을 모두 호출해 상태가 변경됐음을 알린다.
```js
//...
const observable = storeObj =&gt; {
      // ...
    get() {... },
    set(value) {
        _value = value;
         observers.forEach(fn =&gt; fn());
    },
};
//...</code></pre><h2 id="flux-pattern">Flux Pattern</h2>
<p>DOM에서 observer pattern을 붙여 간단한 store를 만들어 사용할 수 있다. 여기에 <code>Flux pattern</code>을 붙인다면 <code>Redux</code>나 <code>Vuex</code>가 된다.</p>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/6b7ee03d-7462-480e-8038-43f075e11e10/image.png" alt=""></p>
<p>Flux의 가장 큰 특징은 <strong>단방향 데이터 흐름</strong>이다.</p>
<p><strong>Action</strong>
어떤 이벤트가 발생했을 때 그 이벤트에 대한 정보를 가진 액션 객체를 만들어 <code>Dispatcher</code>에게 전달한다.</p>
<p><strong>Dispatcher</strong>
들어오는 <code>Action</code> 객체를 받아 데이터를 어떻게 바꿔줄 것인지, 어떤 행동을 할지 결정하는 곳.</p>
<p><strong>Store</strong>
데이터, 즉 상태를 가지고 있는 곳.</p>
<p><strong>View</strong>
우리가 알고 있는 보여주는 View의 역할.</p>
<p>데이터의 흐름은 다음과 같다.</p>
<ul>
<li><code>Dispatcher</code> =&gt; <code>Store</code></li>
<li><code>Store</code> =&gt; <code>View</code></li>
<li><code>View</code> =&gt; <code>Action</code></li>
<li><code>Action</code> =&gt; <code>Dispatcher</code></li>
</ul>
<h3 id="vuex">Vuex</h3>
<p><img src="https://velog.velcdn.com/images/kim-jaemin420/post/91d00140-9649-4026-9ad1-d8f2229b4d5c/image.png" alt=""></p>
<ul>
<li>actions, mutations, state를 묶어서 store라고 보면 된다.</li>
<li>state를 변화시킬 수 있는 것은 오직 mutations다.</li>
<li>actions는 backend api를 가져온 다음에 mutations를 이용하여 데이터를 변경한다.</li>
<li>state가 변경되면 state를 사용 중인 컴포넌트를 업데이트한다.</li>
</ul>
<h3 id="redux-만들기에서-frozenstate를-반환하는-이유">Redux 만들기에서 frozenState를 반환하는 이유</h3>
<ul>
<li><p><code>src/core/Store.js</code></p>
<pre><code class="language-js">const createStore = (reducer) =&gt; {
  const state = observable(reducer());

  const frozenState = {};

    Object.keys(state).forEach(key =&gt; {
      Object.defineProperty(frozenState, key, {
          get: () =&gt; state[key],
      });
  });

    // ...생략

    const getState = () =&gt; frozenState;

    return { getState, dispatch };
};</code></pre>
<p>실제 state를 반환하는 것이 아닌 frozen state를 만들어 반환하는 이유를 생각해보았다. observable이 반환한 결과인 state는 getter와 setter가 모두 등록된 객체이다. 이 객체를 반환하게 되면 dispatcher 뿐만 아니라 직접적으로 상태를 할당하여 변경할 수 있게 된다. 이를 방지하게 위해 getter만 설정된 frozen state를 반환해 dispatcher로만 상태를 변경할 수 있게 했다.</p>
</li>
</ul>
<h2 id="observable과-observer를-사용할-때-고려해야-할-것들"><code>Observable</code>과 <code>observer</code>를 사용할 때 고려해야 할 것들</h2>
<h3 id="1최적화">1.최적화</h3>
<ol>
<li>상태가 변경되어 render를 해야한다. 그런데 변경된 상태가 이전 상태와 값이 똑같은 경우는 어떻게 해야할까?<pre><code class="language-js">state.a = 1;
state.a = 1;
state.a = 1;
state.a = 1;</code></pre>
</li>
</ol>
<p>이런 경우 다시 렌더링 되지 않도록 방어로직이 필요하다.</p>
<pre><code class="language-js">export const observable = obj =&gt; {
    Object.keys(obj).forEach(key =&gt; {
        let _value = obj[key];
          const observers = new Set();

          Object.defineProperty(obj, key, {
            get() {
              // ...
            },
              set(value) {
                  // 원시타입, 객체 타입으로 나누어 값이 같은지 검사.
                if (_value === value) return;
                  if (JSON.stringify(_value) ===JSON.stringify(value)) return;
                  //...
            }
        })
    })
};</code></pre>
<p>그런데, Set, Map, WeekSet, WeekMap 같은 것들은 <code>JSON.stringify</code>로 변환되지 않는다. 이런 경우 추가적인 검사 로직이 필요하다.</p>
<h4 id="동일한-set의-리렌더링-방지하기">동일한 Set의 리렌더링 방지하기</h4>
<p><code>new Set()</code>을 JSON.stringify로 변환하면 모든 Set은 <code>{}</code>로 변환되어 모두 동일하게 판별된다. 동일한 <code>Set</code>인지 판별하기 위해 함수를 따로 만들었다.</p>
<ul>
<li><p><code>src/observer.js</code>
동일한 set인지 확인하는 함수</p>
<pre><code class="language-js">const isSameSet = (set1, set2) =&gt; {
  if (!(set2 instanceof Set &amp;&amp; set1 instanceof Set)) return false;

   if (set1.size !== set2.size) return false;
   const haveEverySameElement = [...set1].every(element =&gt; set2.has(element));

   if (haveEverySameElement) return true;
};
</code></pre>
</li>
</ul>
<p>const observable = obj =&gt; {...};</p>
<pre><code>observable 함수 내에 적용
```js
const observable = obj =&gt; {
    // ...
      return new Proxy(obj, {
    get(target, name) {
      //...
    },
    set(target, name, value) {
      if (target[name] === value) return true;

      if (JSON.stringify(target[name]) === JSON.stringify(value)) return true;

      if (isSameSet(target[name], value)) return true;

      target[name] = value;
      observerMap[name].forEach(fn =&gt; fn());

      return true;
    },
  });
};</code></pre><p>이렇게 추가하니 앞에 있는 </p>
<pre><code class="language-js">if (JSON.stringify(target[name]) === JSON.stringify(value)) return true;</code></pre>
<p>배열, 객체 검사 로직에 항상 true로 걸리는 문제가 있어, 배열 객체 검사도 하나의 함수로 분리해주었다.</p>
<ul>
<li><p><code>src/observer.js</code></p>
<pre><code class="language-js">const isSameObject = (object1, object2) =&gt; {
if (object1 instanceof Set || object2 instanceof Set) return false;

if (JSON.stringify(object1) === JSON.stringify(object2)) return true;
};
</code></pre>
</li>
</ul>
<p>const observable = obj =&gt; {
    return new Proxy(obj, {
        // ...
          set(target, name, obj) {
            if (target[name] === value) return true;
             if (isSameObject(target[name], value)) return true;
             if (isSameSet(target[name], value)) return true;
              // ...
        }
    })
};</p>
<pre><code>Map, WeakSet, WeakMap도 이와 같은 방식으로 방어 로직을 짤 수 있을 것 같다.



### 2. 연속으로 변경되는 상태

여러 가지 상태가 연속적으로 변경되는 경우에는 어떻게 해야 좋을까?

```js
state.a = 1;
state.b = 2;</code></pre><p>단순히 console.log를 찍는 경우라면 상관없지만, 브라우저에 DOM으로 렌더링 되는 경우라면 이야기가 다르다. 이럴 때 <code>requestAnimationFrame</code>과 <code>debounce</code>를 이용하여 한 프레임에 한 번만 렌더링 되도록 만들어 주어야 한다.</p>
<h4 id="requestanimationframe이란">requestAnimationFrame이란?</h4>
<p><code>requestAnimationFrame</code>은 <code>setTimeout</code>처럼 동작하지만 mozilla에 의해 개선된 function이다.</p>
<p><code>requestAnimationFrame</code>은 다음과 같은 순서로 동작한다.</p>
<ol>
<li>브라우저에게 수행하기를 원하는 애니메이션을 알린다.</li>
<li>다음 렌더링(다음 리페인트)가 진행되기 전에 해당 애니메이션을 업데이트 하는 함수를 호출하게 한다.
<code>requestAnimationFrame</code>은 리렌더링 되기 전에 실행할 콜백함수를 인자로 받는다.</li>
</ol>
<p><code>requestAnimationFrame()</code> 함수는 보통 1초에 60회 호출되지만, 일반적으로 대부분의 브라우저에서는 W3C의 권장사항에 따라 호출 횟수가 <strong>디스플레이 주사율과 일치</strong>하게 된다.</p>
<p>쉽게 말해서, <code>requestAnimationFrame</code>은 1프레임에 1회 호출된다. 보통 1초에 60프레임이다. </p>
<h4 id="settimeout을-쓰지-않는-이유">setTimeout을 쓰지 않는 이유</h4>
<p>그렇다면 익숙한 <code>setTimeout</code>을 사용하지 않는 이유는 뭘까. 비슷하게 동작하는데 <code>requestAnimationFrame</code>을 사용하는 이유에 대해서 알아 보았다.</p>
<p><code>setTimeout</code>과 <code>requestAnimationFrame</code>의 가장 큰 차이점은 다음과 같다.
우선 <code>setTimeout</code>은 정확한 시간에 맞춰 콜백 함수를 호출하지 않는다.</p>
<pre><code class="language-js">const foo = () =&gt; console.log(&#39;foo&#39;);
const bar = () =&gt; console.log(&#39;bar&#39;);

setTimeout(foo, 0);
bar();</code></pre>
<p>이 코드를 실행해보면 foo는 0초 후에 콘솔에 찍히지 않는다. 먼저 setTimeout 함수가 실행되면 콜백 함수 foo를 호출 스케줄링하고 종료되어 콜 스택에서 pop된다. 이후 bar가 호출 되어 콜 스택에 들어감과 동시에 콜백 함수 foo가 태스크 큐에 푸시되는 것이 동시에 일어난다. 이 예제는 지연 시간이 0이지만, 지연 시간이 4ms 이하인 경우 최소 지연 시간이 4ms가 지정된다.
따라서, delay를 0초로 설정했음에도 불구하고 항상 4ms의 지연 시간을 갖는다. 또한, 지연 시간이 지났음에도 현재 콜스택에 실행 중인 함수가 있다면 대기해야 한다.</p>
<p><code>setTimeout</code>을 사용하지 않는 이유를 정리하면 다음과 같다.</p>
<ol>
<li>정확한 시간에 호출되지 않을 수 있다.</li>
<li><code>setTimeout</code>은 브라우저에서 일어나는 일을 고려하지 않는다.
 페이지가 비활성화 되어 있을 때도 CPU를 독차지 하고 있을 수 있다.</li>
<li><code>setTimeout</code>이 원할 때 화면을 업데이트 한다.
 열악한 브라우저의 경우, repaint 하는 동안 setTimeout으로 인해 또 다시 repaint 될 수 있다.</li>
</ol>
<p>반면, <code>requestAnimationFrame</code>은 미리 지정된 시간에 repaint 하는 것이 아니라, 브라우저가 다음 repaint 할 때 예약한다. 브라우저의 상황에 맞게 동작을 예약할 수 있는 것이다.
정리하면 <code>requestAnimationFrame</code>은</p>
<ul>
<li>브라우저에서 애니메이션을 최적화할 수 있다.</li>
<li>비활성 탭의 애니메이션이 중지되므로 CPU를 보다 효율적으로 사용할 수 있다.</li>
<li>배터리 친화적이다.</li>
</ul>
<p><a href="https://web.dev/optimize-javascript-execution/">Optimize Javascript execution</a>에서는 자바스크립트 실행 최적화를 위해 아래와 같이 권장한다.</p>
<ol>
<li>시각적 업데이트를 위해 setTimeout 또는 setInterval을 사용하지 말 것. 대신 항상 requestAnimationFrame을 사용할 것.</li>
<li>오래 실행되는 JavaScript를 기본 스레드에서 Web Workers로 이동할 것.</li>
<li>마이크로 작업을 사용하여 여러 프레임에 걸쳐 DOM을 변경할 것.</li>
<li>Chrome DevTools의 타임라인 및 JavaScript 프로파일러를 사용하여 JavaScript의 영향을 평가할 것.</li>
</ol>
<h4 id="적용하기">적용하기</h4>
<p><code>requestAnimationFrame</code>과 <code>debounce</code>를 사용하여 직접 적용해보자.</p>
<ul>
<li><p>어떻게 동작하는지 먼저 이해하기.</p>
<pre><code class="language-js">const debounceFrame = (callback) =&gt; {
  let currentCallback = -1;

    return () =&gt; {
        // 이전에 등록된 callback이 있다면 취소
      cancelAnimationFrame(currentCallback);
        // 1프레임 뒤에 실행되도록 한다.
        currentCallback = requestAnimationFrame(callback);
  };
};
</code></pre>
</li>
</ul>
<p>debounceFrame(() =&gt; console.log(1));
debounceFrame(() =&gt; console.log(2));
debounceFrame(() =&gt; console.log(3));
debounceFrame(() =&gt; console.log(4));
debounceFrame(() =&gt; console.log(5)); // 이것만 실행된다.</p>
<pre><code>여러 번 debounceFrame 함수가 실행된다는 건 여러 번의 상태가 변경되어 render 함수를 호출되는 경우라고 생각할 수 있다.
여러 번 render 함수가 호출될 때, 하나하나 반영되지 않고 제일 마지막으로 호출된 것만 실행하도록 해야 한다.

```js
const debounceFrame = (callback) =&gt; {
    let currentCallback = -1;

     return () =&gt; {
        cancelAnimationFrame(currentCallback);
          currentCallback = requestAnimationFrame(callback);
    };
};

const observe = fn =&gt; {
      // fn은 렌더를 일으키는 것과 관련있는 함수이다.
    currentObserver = debounceFrame(fn);
      fn();
      currentObserver = null;
};</code></pre><p>이렇게 상태가 변경될 때마다 렌더링 되는 것이 아니라, 1프레임 당 한 번만 렌더링 되기 때문에 최적화에 도움이 된다.</p>
<h3 id="3proxy">3.Proxy</h3>
<p><code>Proxy</code>를 사용하는 것은 성능보다는 가독성에 더 도움이 된다. <code>Proxy</code>는 이전에 사용하던 <code>Object.defineProperty</code>를 대신하여 사용할 수 있다.
<code>Object.defineProperty</code>는 IE를 지원하기 위해 사용하는 API이다. 최신 브라우저에서는 <code>Proxy</code>를 사용한다면 더 쉽게 <code>Observable</code>을 만들 수 있다.</p>
<pre><code class="language-js">const observable = obj =&gt; {

  const observerMap = {};

  return new Proxy(obj, {
    get (target, name) {
      observerMap[name] = observerMap[name] || new Set();
      if (currentObserver) observerMap[name].add(currentObserver)
      return target[name];
    },
    set (target, name, value) {
      if (target[name] === value) return true;
      if (JSON.stringify(target[name]) === JSON.stringify(value)) return true;
      target[name] = value;
      observerMap[name].forEach(fn =&gt; fn());
      return true;
    },
  });

}</code></pre>
<h4 id="proxy란">Proxy란?</h4>
<p><code>Object.defineProperty</code>와 비슷하게 한 객체에 대한 기본 작업을 가로채고 재정의하는 프록시를 만들 수 있다. <code>Proxy</code>는 두 매개변수를 받는다.</p>
<ul>
<li><p>target: 프록시할 원본 객체</p>
</li>
<li><p>handler: 가로채는 작업과 가로채는 작업을 재정의하는 방법을 정의하는 객체</p>
</li>
<li><p>사용 예시</p>
<pre><code class="language-js">const target = {
message1: &quot;hello&quot;,
message2: &quot;everyone&quot;
};
</code></pre>
</li>
</ul>
<p>const handler2 = {
  get(target, prop, receiver) {
    return &quot;world&quot;;
  }
};</p>
<p>const proxy2 = new Proxy(target, handler2);</p>
<pre><code>`target` 객체의 속성을 가로채는 `get()` 처리기를 사용했다.

- 결과
```js
console.log(proxy2.message1); // world
console.log(proxy2.message2); // world</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[황준일]Vanilla Javascript로 웹 컴포넌트 만들기를 읽은 후기]]></title>
            <link>https://velog.io/@kim-jaemin420/Vanilla-Javascript%EB%A1%9C-%EC%9B%B9-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0%EB%A5%BC-%EC%9D%BD%EC%9D%80-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/Vanilla-Javascript%EB%A1%9C-%EC%9B%B9-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0%EB%A5%BC-%EC%9D%BD%EC%9D%80-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 13 Apr 2023 13:22:56 GMT</pubDate>
            <description><![CDATA[<h2 id="render-vs-mounted">render VS mounted</h2>
<p>컴포넌트를 분할하면서, 원래 render 메서드 하나로만 관리하다가 <code>render 메서드</code>와 <code>mounted 메서드</code>를 분리했다. render와 mounted를 분리한 이유가 뭘까?
render 안에서 mounted가 호출된다. 만약 특정 돔에 하위 컴포넌트를 추가해야 할 때 특정 돔이 만들어지지 않았을 수 있기 때문에</p>
<pre><code class="language-html">&lt;div id=&quot;app&quot;&gt;&lt;/div&gt;</code></pre>
<p>처럼 미리 존재해야할 돔을 미리 렌더링 되어야 한다. 이 과정을 render에서 하고 나중에 추가돼야할 컴포넌트는 mounted에서 렌더링 된다.</p>
<h2 id="this">this</h2>
<h3 id="this-binding이-필요한-이유">this binding이 필요한 이유</h3>
<ul>
<li><p><code>src/App.js</code></p>
<pre><code class="language-js">class App extends Component {
  mounted() {
    new Items($items, {
      /*
      filteredItems,
      toggleItem: toggleItem.bind(this),
      */![](https://velog.velcdn.com/images/kim-jaemin420/post/b680d4f5-6fd4-4d50-a70e-edc8d982c752/image.png)

      deleteItem: deleteItem.bind(this),

    })
  }

  deleteItem(seq) {
    const items = [...this.state.items];

    items.splice(items.findIndex(item =&gt; item.seq === seq), 1);
    this.setState({ items });
  }
}
</code></pre>
</li>
</ul>
<p>export default App;</p>
<pre><code>여기서 deleteItem, toggleItem 메서드에 this를 바인딩해주는 이유가 뭘까?

- `src/components/Items.js`
```js
class Items {
    setEvent() {
      const { deleteItem, toggleItem } = this.props;

      this.addEvent(&#39;click&#39;, &#39;.deleteBtn&#39;, ({target}) =&gt; {
        deleteItem(Number(target.closest(&#39;[data-seq]&#39;).dataset.seq));
      });
    }
}

export default Items;</code></pre><p>Items 컴포넌트에서 this.props.deleteItem으로 접근 가능하다. 그리고 deleteItems 안에서는 this.state.items를 참조하고 있다.
this는 함수를 호출하는 형태에 따라 바인딩이 달라진다.</p>
<blockquote>
<p>호출 방식에 따른 this</p>
</blockquote>
<ul>
<li>일반 함수처럼 호출: window</li>
<li>메소드로 호출: 메서드를 호출한 객체</li>
<li>생성자 함수 호출 : 생성자 함수가 (미래에) 생성할 인스턴스</li>
</ul>
<p>처음 글을 읽을때, this binding을 왜 꼭 해줘야하는지 이해가 안돼서 binding 해주지 않고 this.props로 넘겨줘봤다.</p>
<p>이때, Items에서 호출하게 되면, deleteItem를 일반 함수 호출하듯이 호출했으므로 deleteItem 메서드 안에서 참조하는 this는 window 객체를 가리키고 있을거라고 예상했다.</p>
<ul>
<li><p><code>src/App.js</code></p>
<pre><code class="language-js">class App {
  mounted() {
    new Items($items, {
      deleteItem,
      //...
    })
  }

  deleteItem() {
    // this???
  }
}
</code></pre>
</li>
</ul>
<p>export defualt App</p>
<pre><code>```js
class Items {
    setEvent() {
      const { deleteItem, toggleItem } = this.props;

      this.addEvent(&#39;click&#39;, &#39;.deleteBtn&#39;, ({target}) =&gt; {
        deleteItem(Number(target.closest(&#39;[data-seq]&#39;).dataset.seq));
      });
    }
}</code></pre><p>그러나 실제로 window 객체가 찍히는게 아니라 <code>undefined</code>가 찍히는 것을 볼 수 있었다. 
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/0436b733-8de1-4c2d-bedc-3abc8205d992/image.png" alt=""></p>
<p>strict mode일 경우 일반 함수로 호출 시 this는 undefined가 바인딩 되는데, 따로 strict mode를 설정하지 않았는데 undefined가 되는게 이해가 가지 않았다.
알고 보니 module을 사용하면 자동으로 strict mode가 되기 때문에 this가 undefined로 잡히는 것을 알 수 있었다.</p>
<p>그렇다면, Items에서 일반 함수가 아닌 <code>this.props.deleteItem()</code>으로 호출했을때 <code>deleteItem 메서드</code> 안에서의 this는 어떤 것을 가리키게 될까?
이 경우에 this는 당연히 <code>this.props</code>를 가리키게 된다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/c757d36b-f059-4535-8181-0ab6a3b633b2/image.png" alt=""></p>
<p><code>deleteItem 메서드</code>안에서 필요한 this는 <code>this.props</code>객체가 아니라 App 생성자 함수가 만들어낸 인스턴스 객체이다. 이렇게 메서드를 어떻게 호출되냐에 따라 this가 변경되어 혼동을 준다. 또 다른 컴포넌트에서 <code>deleteItem 메서드</code>를 호출시켜 App의 상태를 변경시키므로 this binding 없이는 App의 상태를 변경할 방법이 없다. 따라서 꼭 binding을 해줘야 한다는 것을 알 수 있었다.</p>
<h3 id="this-binding을-하고-메서드를-내-맘대로-호출하면">this binding을 하고 메서드를 내 맘대로 호출하면?</h3>
<p>this binding을 해주어도 함수를 내 마음대로 호출하면 어떻게 될지 궁금해졌다.</p>
<ul>
<li><p><code>src/App.js</code></p>
<pre><code class="language-js">class App {
  mounted() {
    new Items($items, {
      deleteItem: deleteItem.bind(this),
      //...
    })
  }

  deleteItem() {...}
}
</code></pre>
</li>
</ul>
<p>export defualt App</p>
<pre><code>- `src/components/Items.js`
```js
class Items {
    setEvent() {
      this.addEvent(&#39;click&#39;, &#39;.deleteBtn&#39;, ({target}) =&gt; {
        this.props.deleteItem(Number(target.closest(&#39;[data-seq]&#39;).dataset.seq));
      });
    }
}</code></pre><p><code>this.props.deleteItem()</code>으로 호출했음에도 불구하고 bind 해준 this가 찍히는 걸 알 수 있다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/6926068b-8242-4d58-8070-4b2435773fd7/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[원티드] 프론트엔드 프리온보딩 1월 후기]]></title>
            <link>https://velog.io/@kim-jaemin420/%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-1%EC%9B%94-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/%EC%9B%90%ED%8B%B0%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%94%84%EB%A6%AC%EC%98%A8%EB%B3%B4%EB%94%A9-1%EC%9B%94-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 13 Apr 2023 13:22:33 GMT</pubDate>
            <description><![CDATA[<h1 id="프리온보딩을-수강하며-알게된-것들">프리온보딩을 수강하며 알게된 것들</h1>
<h2 id="emotion을-써보자">emotion을 써보자</h2>
<p>이번 프로젝트에는 emotion을 사용했다. 그동안 styled-component, tailwind, scss 등을 사용해봤는데 emotion을 사용해본적은 없어서 경험해보고 싶었기도 하고 emotion이 styled-component보다 빠르기도 해서 선택하게 되었다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/4b277423-1ed0-4c63-8a03-7430dac3c2cd/image.png" alt=""></p>
<p>emotion-css-mode의 렌더링 속도가 4위를 차지했다. 물론 inline style 방식만 월등히 빠르지만 그 이외 것도 styled-component보다 앞섰다. 퍼포먼스 면에서도 아주 미세하지만 emotion이 앞선다.</p>
<p>렌더링 속도 표에서 놀라웠던건, react with inline styles의 속도가 아주 빠르다는 것이었다. react를 처음 배웠을때 inline style을 사용하면 렌더링될때마다 style 객체를 다시 계산하여 성능 이슈가 생기기 때문에 inline style을 지양하라고 배웠다. 그런데 속도 비교를 보니, 리렌더 속도나 mount time 모두 아주 빨랐다. 이제 생각해보니 styled 함수를 호출하고 객체를 생성하고 import, export 하는 시간보다는 단순 객체를 계산하는 시간이 더 빠를 것 같다는 생각이 들었다.</p>
<p><strong>stitches 발견!</strong>
style 라이브러리를 찾다가 stitches를 알게 되었다. 사용방법은 Emotion이나 styled-component와 크게 다르지 않은데 속도가 비교가 안될만큼 빨랐다. 다음 프로젝트때 한 번 사용해보면서 알아보는 것도 좋을 것 같다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/9db98804-eaf9-4ce0-9d93-a56327cf5130/image.png" alt=""></p>
<blockquote>
<p>출처:</p>
</blockquote>
<ul>
<li><a href="https://github.com/A-gambit/CSS-IN-JS-Benchmarks/blob/master/RESULT.md">https://github.com/A-gambit/CSS-IN-JS-Benchmarks/blob/master/RESULT.md</a></li>
<li><a href="https://dev.to/meetdave3/styled-components-vs-emotion-js-a-performance-perspective-4eia">https://dev.to/meetdave3/styled-components-vs-emotion-js-a-performance-perspective-4eia</a></li>
<li><a href="https://stitches.dev/docs/benchmarks">https://stitches.dev/docs/benchmarks</a></li>
</ul>
<h2 id="react-query-적용기">react query 적용기</h2>
<h3 id="react-query를-써봤다고-할-수-있나">react query를 써봤다고 할 수 있나</h3>
<p>개발 동아리 활동을 하면서 react query를 한 번 사용해 본 적이 있다. 깊게 알아보고 사용한건 아니었고, 요즘 react query를 많이 사용한다, 캐싱을 알아서 해주기 때문에 편리하다 등의 장점으로 가볍게 사용해봤다.
1월 프리온보딩 수업을 듣고 내가 얼마나 가볍게 알고 사용하는지, 또 사실 react query를 써본게 아니라는 걸 알게 되었다.</p>
<h3 id="왜-react-query인가">왜 react query인가?</h3>
<p>프론트엔드 개발을 할 때 항상 고민되는 것 중 하나가 state이다. 어떻게 해야 상태관리를 잘할 수 있는가를 늘 고민하고 상태 관리를 하기 위해 상태 관리 매니저를 많이 사용한다. 그 중 가장 유명한 상태 관리 매니저는 <code>redux</code>이다. 리덕스를 통해 복잡한 상태를 하나의 스토어로 관리할 수 있다.
그런데, 서버로부터 받은 데이터를 리덕스 스토어에 저장하는 것이 리덕스에서 추구하는 <code>진실한 정보의 원천(Single source of truth)</code>에 부합하는가를 고민해볼 필요가 있다. 기존에 api 요청을 통해 서버에서 데이터를 가져오면 진실한 정보의 원천인 redux store에 넣어 두고, 그 데이터를 업데이트 하며 사용했다. 그러나 store는 진짜 진실한 정보의 원천일까?</p>
<p>그럴 수도 있고 그렇지 않을 수 있다. 하지만 대부분의 경우 서버 데이터가 서버를 떠나 store에 저장되는 이상 store는 원본이 아니라 서버 데이터의 복사본일 뿐이다. 우리는 서버로부터 받아온 데이터의 최신 여부를 따지는 매커니즘인 HTTP 캐시를 알고 있다. 이 개념을 비동기 호출하는 여러 데이터들에서도 적용해볼 수 있다.
react query, swr 등의 캐시 패러다임의 라이브러리가 <code>stale-while-revalidate</code>를 채택하고 있다.</p>
<p>왜 react query가 필요한지 정리하면 다음과 같다.</p>
<ul>
<li>서버로부터 데이터를 받아 store에 저장하게 되면 저장된 데이터는 dispatch된 시점에 캡쳐(capture)된 데이터가 저장된다.</li>
<li>호출된 시점에서 서버 데이터와 클라이언트에 저장된 데이터는 같겠지만 다시 호출되기 전까지는 같다는 것을 보장할 수 없다.
따라서, 진실한 정보의 원천에 위배되며 리덕스는 데이터 캐싱에 적합하지 않다.데이터가 필요할때마다 api를 호출하고 그 비용을 줄일 수 있는 react query를 사용하면 진실한 정보의 원천에 보다 가까워질 수 있다.</li>
</ul>
<h3 id="query-key는-배열로">query key는 배열로</h3>
<p>query key는 문자열, 배열 모두 가능했으나 React query V4 이후 query key는 배열만 가능하다. 문자열이 되더라도 배열로 쓰는 것이 휴먼 에러도 줄이고 유지보수면에서도 좋으니 배열을 사용하자.</p>
<h4 id="query-key-배열-구조">query key 배열 구조</h4>
<p>배열로 query key를 생성할때 가장 일반적인 것부터 점차 가장 구체적인 것으로 나열한다.</p>
<pre><code class="language-js">[&#39;todos&#39;, &#39;list&#39;, { filters: &#39;all&#39; }]
[&#39;todos&#39;, &#39;list&#39;, { filters: &#39;done&#39; }]
[&#39;todos&#39;, &#39;detail&#39;, 1]
[&#39;todos&#39;, &#39;detail&#39;, 2]</code></pre>
<p>이 구조를 사용하면 하나의 특정 목록을 무효화하거나 todos와 관련있는 모든 query key를 무효화할 수 있다. 그런데, 이렇게 일일이 배열을 직접 넣어주는 방법도 마찬가지로 유지보수를 어렵게 만든다. 그렇기 때문에 기능 하나당 하나의 query key factory를 만들어 쓰는 것이 좋다.</p>
<pre><code class="language-ts">const todoKeys = {
  all: [&#39;todos&#39;] as const,
  lists: () =&gt; [...todoKeys.all, &#39;list&#39;] as const,
  list: (filters: string) =&gt; [...todoKeys.lists(), { filters }] as const,
  details: () =&gt; [...todoKeys.all, &#39;detail&#39;] as const,
  detail: (id: number) =&gt; [...todoKeys.details(), id] as const,
}</code></pre>
<p>todoKeys라는 하나의 객체 안에 각각의 key가 등록되어 있기 때문에 여전히 독립적으로 접근이 가능하다.</p>
<h4 id="query-key-파일을-어디에-위치시킬까">query key 파일을 어디에 위치시킬까?</h4>
<p>query key를 어디에 위치해야 할지 고민이 많았다. 처음엔 hooks/queries 안에 넣으려다가 여러 가지 커스텀 훅 사이에 query 파일이 들어있으면 다른 사람이 봤을 때 더 찾기 어려울 것 같은 느낌이 들어 고민 끝에 consts/query.ts 파일을 만들어 그 안에 TODOS_KEYS 객체를 생성했다.</p>
<pre><code class="language-text">📦hooks
 ┣ 📂queries
 ┃ ┣ 📜index.ts
 ┃ ┣ 📜...todo 관련 커스텀훅
 ┣ 📜index.ts
 ┣ 📜...일반적인 커스텀훅</code></pre>
<p>그런데, <code>Kent C.Dodds</code>의 코로케이션을 통한 유지 관리 가능성을 읽고나니 consts/query.ts에 생성한게 좋은 생각은 아니었던 것 같다. 일단 기능에 따라 query key 파일의 구분 없이 하나의 query.ts라고 명시한 것은 유지보수적으로 좋지 않다. 또, consts는 전역적으로 재사용성이 있는 변수를 담아야 하는데 query key는 query custom hook에서만 사용되기 때문에 디렉토리 위치도 맞지 않았다. Tkdodo님의 블로그에서 feature 디렉토리에 각각의 queries.ts 파일을 만들어 관리하는 것을 추천했으나, 지금 내 디렉토리 구조가 다음처럼 분산되어 있어 그 방법은 적용하기 힘들었다.
&lt;Tkdodo 추천 방법&gt;</p>
<pre><code class="language-text">- src
  - features
    - Profile
      - index.tsx
      - queries.ts
    - Todos
      - index.tsx
      - queries.ts</code></pre>
<p>&lt;현재 내 디렉토리 구조&gt;</p>
<pre><code class="language-text">📦src
 ┣ 📂components
 ┣ 📂consts
 ┣ 📂hooks
 ┣ 📂pages
 ┣ 📂...생략</code></pre>
<p>결국 고민 끝에 hooks/queries 하위에 todos라는 디렉토리를 만들고 그 안에 todo query key 파일과 다른 todo 관련 커스텀 훅을 넣는 것이 제일 낫다고 결론내렸다.</p>
<h3 id="invalidatequeries를-쓰지-않았다면-react-query를-써봤다고-하지말자">invalidateQueries를 쓰지 않았다면 react query를 써봤다고 하지말자.</h3>
<p>useMutation을 사용하면 데이터가 변경된다. 이때 따로 refetch하는 함수를 호출할 필요가 없이, 호출할 대상이 stale하다고 알려주면 된다.(ex. 캐시에게 모진 말 하기 : 너 상했어..)
쿼리의 상태는 쿼리 키를 중심으로 관리되는데, 이 쿼리 키는 Mutation에서 사용할 수 있다.</p>
<pre><code class="language-ts">function useUpdateTodo() {
  return useMutation({
    mutationFn: updateTodo,
    onSuccess: () =&gt; {
       queryClient.invalidateQueries({ queryKey: TODO_KEYS.list() });
    },
  });
}</code></pre>
<p>mutation 이후에 어떤 쿼리 키가 상했는지 알려주어야 한다.</p>
<h4 id="invalidatequeries-vs-refetch">invalidateQueries VS refetch</h4>
<p>invalidateQueries로 상했다고(stale) 알려주는 것과 그냥 refetch를 호출하는 것이 뭐가 다른지 궁금해져서 찾아봤다.
invalidation은 더 &quot;똑똑한&quot; refetching이다. refetch는 쿼리에 대한 observer가 없더라도 항상 refetch를 실행할 것이다. 반면에, invalidation은 그냥 다음 번에 observer가 마운트 될 때 refetch할 수 있게 상했다고만 표시하는 것이다. 먄약 퀴리에 active된 observer가 있다면 이 둘의 차이는 없다.</p>
<blockquote>
<p>출처: <a href="https://github.com/TanStack/query/discussions/2468">https://github.com/TanStack/query/discussions/2468</a></p>
</blockquote>
<h3 id="react-query에서-optimistic-updates-사용해보기">react query에서 optimistic updates 사용해보기</h3>
<h4 id="optimistic-updates란">optimistic updates란?</h4>
<p>optimistic update란 직역하면 낙관적 업데이트이다. 보통 프론트에서 서버로 POST 요청을 하고 서버에서 내려준 response를 프론트가 받아 UI를 업데이트 한다. 반면 optimistic update는 이와 다르게 접근한다. 프론트가 보낸 요청을 &quot;성공했다치고&quot; 서버에서 응답을 받기 전에 UI를 미리 업데이트 한다.
사람들은 눈을 깜빡이는 것처럼 반응하는 인터페이스를 선호한다. 과거에는 버튼이 클릭됐을때 비활성화 하여 이를 충족시켰으나, 요소를 비활성화 하는 것은 사용자에게 통제할 수 없는 수동적 기다림을 의미하므로 optimistic UI는 비활성화 상태를 통째로 건너뛰어 기다림 대신에 낙관적인 결과로 소통한다.</p>
<p>처음엔 응답을 받기 전에 UI를 미리 업데이트 하는게 오히려 유저에게 혼란을 줄 것이라고 생각했다. 하지만, API가 안정적이고 예측가능한 수준이며, 프론트엔드가 UI에 적절한 액션을 취하는 한, 유저가 처음에 취한 행동에 대한 응답으로 오류가 뜰 확률은 상당히 낮다고 한다. &quot;Denys Mishunov&quot;에 의하면 오류 상황은 1-3%에 머무른다고 한다. 97-99%의 성공 응답을 확신할 수 있다면, optimistic updates가 유저에게 혼란을 줄 확률은 적을 것이다.</p>
<p>optimistic updates는 즉각적인 사용자 피드백이 필요한 작은 mutations에서 매우 효과적이다.</p>
<h4 id="react-query로-optimistic-updates-구현해보기">react query로 optimistic updates 구현해보기</h4>
<pre><code class="language-ts">const usePostTodo = () =&gt; {
  const queryClient = useQueryClient();

  return useMutation(createTodo, {
    onMutate: async (newTodo) =&gt; {
      await queryClient.cancelQueries({ queryKey: TODO_KEYS.lists() });

      const previousTodos = queryClient.getQueryData(TODO_KEYS.lists());

      await queryClient.setQueryData&lt;Array&lt;CreateTodoRequest&gt;&gt;(
        TODO_KEYS.lists(),
        (oldTodo) =&gt; oldTodo &amp;&amp; [...oldTodo, newTodo]
      );

      return previousTodos;
    },

    onError: (error, newTodo, previoustTodos) =&gt; {
      queryClient.setQueryData(TODO_KEYS.lists(), previoustTodos);
    },

    onSettled: () =&gt; {
      queryClient.invalidateQueries(TODO_KEYS.lists());
    },
  });
};</code></pre>
<ol>
<li>onMutate: useMutation의 query 함수인 createTodo가 실행되기 전에 호출된다.</li>
<li>cancelQueries: 혹시 발생할지도 모르는 refetch를 취소하여 데이터가 꼬이지 않게 한다.</li>
<li>실패했을때 보여줄 이전 데이터를 미리 저장해둔다.</li>
<li>setQueryData: 서버 응답이 오기 전에 UI를 미리 업데이트 해둔다.</li>
<li>에러 발생하면 이전 데이터로 다시 setQueryData 해준다.</li>
<li>OnSettled에서 실패했든 성공했든 상했다고 알려 서버 데이터와 동기화해준다.</li>
</ol>
<h4 id="optimistic-updates-아무때나-쓰지말자">optimistic updates 아무때나 쓰지말자.</h4>
<p>react query 공식 홈페이지를 보고 todo app에서 새 todo를 등록할때 optimistic updates를 적용했다. 적용후 에러 상황을 만들어 롤백되는 것을 보니 새로운 todo가 등록됐다 바로 사라졌다. optimistic updates를 쓰면 더 나은 사용자 경험을 제공한다 했는데, 이 경우는 전혀 그런 것 같지 않았다. optimistic updates는 언제 써야 적절한 것인가를 고민해보게 되었다.</p>
<p>optimistic updates는 다음과 같은 경우에 사용해야 한다.</p>
<ol>
<li>성공 또는 실패 응답 이상을 기대할 수 없는 이분법적인 요소들에 적용한다.(<code>좋아요</code>, <code>별표</code> 또는 <code>저장된 검색에서</code>)</li>
<li>optimistic updates를 적용하는 곳은 인터페이스의 다른 부분에 연결되어서는 안된다.
: 여러 테이블에 데이터를 함께 저장하는 경우, 하나라도 실패하면 전체 API가 실패로 처리되기 때문에 실패 확률이 높다.</li>
<li>API 응답 시간은 매우 빨라야 한다.</li>
<li>API 성공률은 100%에 가까워야 한다.</li>
</ol>
<p><strong>때때로 사용자는 지연을 예상한다</strong>
만약, 글을 비공개에서 공개로 전환하는 작업이나 결제 또는 예약을 하는 경우를 생각해보자. 단순한 버튼을 누르는 것일지라도 사용자는 중요한 작업이라고 생각하기 때문에 진행이 지연될 것을 예상한다. 글을 비공개 하는 즉시 작업이 이루어지거나 결제 버튼을 누르는 즉시 결제가 완료되는 즉각적인 피드백은 오히려 사용자를 불신하게 만든다. 이러한 작업들에는 optimistic updates 사용을 지양하자.</p>
<blockquote>
<p>출처: </p>
</blockquote>
<ul>
<li><a href="https://story.pxd.co.kr/1193">https://story.pxd.co.kr/1193</a></li>
<li><a href="https://dnlytras.com/blog/optimistic-updates">https://dnlytras.com/blog/optimistic-updates</a></li>
</ul>
<h2 id="typescript를-써보자">typescript를 써보자</h2>
<h3 id="객체의-key와-value를-타입으로-변환하기">객체의 key와 value를 타입으로 변환하기</h3>
<p>어떤 변수의 타입이 string이 아닌 특정한 문자열로 타입을 좁히려고 하니, const 상수를 새로 만들고 그 상수로 객체를 참초하여 value를 사용해야 했다. 그때 객체의 key 혹은 value에 대한 타입을 알아야 했다.
타입스크립트의 <code>keyof</code> 와 <code>typeof</code> 를 사용하여 객체에 대한 타입을 알아낼 수 있었다.</p>
<h4 id="typeof-연산자--객체-자체를-타입으로-변환">typeof 연산자 : 객체 자체를 타입으로 변환</h4>
<p>typeof : 객체 데이터를 객체 타입으로 변환해주는 연산자</p>
<p>변수로 선언한 객체는 당연히 타입으로 사용할 수 없다. 만약 선언한 객체에 들어있는 key, value 구조를 그대로 가져와 독립된 타입으로 사용하고 싶다면 객체 이름 앞에 <code>typeof</code> 키워드를 명시해주면 된다.</p>
<pre><code class="language-typescript">const person1 = {
    name: &#39;jaemin&#39;,
    age: 28,
    job: &#39;unemployed&#39;,
};

// person1 객체를 타입으로 변환
type Person = typeof person1;

const person2: Person = {
    name: &#39;calmdown man&#39;,
      age: 40,
      job: &#39;youtuber&#39;,
};</code></pre>
<p>함수 또는 클래스도 <code>typeof</code> 연산자를 사용하여 타입으로 변환할 수 있다.</p>
<h4 id="keyof-연산자--객체-타입에서-key를-타입으로-변환">keyof 연산자 : 객체 타입에서 key를 타입으로 변환</h4>
<p>keyof : 객체 타입에서 key들만 뽑아 유니온 타입으로 만들어주는 연산자</p>
<pre><code class="language-ts">type Person {
    name: string;
    age: number;
    married: boolean;
}

type PersonKey = keyof Person;
// type PersonKey = name | age | married

const name: PersonKey = &#39;name&#39;;
const age: PersonKey = &#39;age&#39;;
const married: PersonKey = &#39;married&#39;;</code></pre>
<p>만약 특정 객체의 key들만 뽑아오고 싶다면 아래와 같이 사용할 수 있다.</p>
<pre><code class="language-ts">const person = {
    name: &#39;jaemin&#39;,
      age: 28,
      married: false,
};

type Person = keyof typeof person;
// type Person = &quot;name&quot; | &quot;age&quot; | &quot;married&quot; 

const name: Person = &#39;name&#39;;
const age: Person = &#39;age&#39;;</code></pre>
<h4 id="객체-타입에서-value를-타입으로-변환">객체 타입에서 value를 타입으로 변환</h4>
<p>다음은 객체에서 value 값들만 뽑아 유니온 타입으로 만들어주는 방법이다.</p>
<pre><code class="language-ts">const person = {
    name: &#39;jaemin&#39;,
      age: 28,
      married: false,
};

type PersonValue = typeof person[keyof typeof person];
// type PersonValue = string | number | boolean</code></pre>
<p>person 객체 value들의 type이 아니라 리터럴 값을 가지고 싶다면 아래와 같이 사용할 수 있다.</p>
<pre><code class="language-ts">const person = {
    name: &#39;jaemin&#39;,
      age: 28,
      married: false,
} as const;

type PersonValue = typeof person[keyof typeof person];
// type PersonValue = false | &quot;jaemin&quot; | 28</code></pre>
<h3 id="typescript에서-dts-파일-사용하기">typescript에서 d.ts 파일 사용하기</h3>
<p>객체의 value의 type을 변환할때 매번 <code>typeof obj[keyof typeof obj]</code> 이렇게 적는건 불편하기도 하고 가독성이 떨어지기도 한다. 재사용할 수 있게 utils 파일에 넣으려고 하다가 typescript에서 제공하는 d.ts 파일에 대해 알게 되었다.</p>
<p>ambient module: import export 없이 전역에서 가져다 쓸 수 있는 기능</p>
<h2 id="axios-instance-만들어-쓰기">axios instance 만들어 쓰기</h2>
<h3 id="axioscreate">axios.create</h3>
<p>모든 요청에 포함되는 공통된 config 설정이 있기 때문에 공통 config가 들어가있는 axios instance를 만들어 사용하는게 좋다고 생각했다.</p>
<pre><code class="language-ts">const axiosInstance = axios.create({
    baseURL: import.meta.env.VITE_BASE_URL,
    timeout: 10000,
    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
  });</code></pre>
<p>axios의 timeout 값이 필요한 이유는 서버의 네트워크에 문제가 생겨 서버 통신이 지연될때 timeout 값으로 설정한 시간이 지나면 서버와의 연결을 즉시 중단한다. timeout 값을 5000(5초)로 설정했다면 5초가 지나도 서버로부터 정상적인 응답을 받지 못하는 경우 네트워크가 중단됩니다.
만약 timeout을 설정하지 않았다면 클라이언트는 계속 서버에 연결을 유지하고 응답을 기다리게 된다. 이때 방문자가 많을 경우, 즉 서버에 요청이 많은 상태라면 과부하 등의 이유로 서버가 다운되는 등 더 큰 문제가 발생할 수 있다. 따라서 적절한 timeout 시간을 설정하여 에러 메시지를 보여주는 등의 설정이 필요하다. timeout 값이 너무 짧으면 방문자가 접속하지 못하는 문제가 있을 수 있다. 대부분 5초에서 10초 사이의 값을 사용하지만 서버 환경 및 네트워크 상황을 고려하여 설정할 필요가 있다.</p>
<p>여기까지 하고 난 후 axios.create가 무엇을 반환하는지 궁금해졌다. return 값을 찍어봐도 함수가 찍혀 자세히 알기가 어려워 axios 코드를 까봤다.</p>
<h3 id="axios-interceptors">axios interceptors</h3>
<h4 id="axiosinterceptorsrequest">axios.interceptors.request</h4>
<p>local storage에 token이 있다면 headers에 포함하여 보내야 했다. 이것도 매 요청마다 확인하는 로직이 반복할테니 interceptos.request에 token을 검사하는 로직을 넣어 반복을 줄이고 관심사를 분리하기로 했다.</p>
<pre><code class="language-ts">axiosInstance.interceptors.request.use(
    (config) =&gt; {
      const token = getLocalStorage(ACCESS_TOKEN);

      if (config.headers) (config.headers as AxiosHeaders).set(&#39;Authorization&#39;, token);

      return config;
    },
    (error) =&gt; Promise.reject(error)
  );</code></pre>
<h4 id="axiosinterceptorsresponse">axios.interceptors.response</h4>
<p>서버에서 응답으로 내려주는 데이터가 다음과 같아서 렌더링에 필요한 데이터를 사용하려면 response.data.data로 접근해야 했다.</p>
<pre><code class="language-ts">{
  config: ...,
  data: { data: Array(7) },
  headers: ...,
  request: ...,
  status: ...,
  ...
}</code></pre>
<p>react query를 도입하기 전에는 대부분의 요청의 then절에서 response.data.data를 적어주었으나, react query를 사용하고 커스텀 훅에서 .data.data로 접근하려니 가독성이 떨어진다고 생각해, interceptors.response에서 데이터를 가공하기로 했다.</p>
<ul>
<li><p><code>src/api/base.ts</code></p>
<pre><code class="language-ts">const createApiMethod =
(axiosInstance: AxiosInstance, method: Method) =&gt;
(config: AxiosRequestConfig): Promise =&gt; {
  axiosInstance.interceptors.response.use((response) =&gt; {
    return response.data;
  });

  return axiosInstance({
    ...config,
    method,
  });
};
</code></pre>
</li>
</ul>
<p>const api = {
  get: createApiMethod(axiosInstance, HTTP_METHODS.GET),
  post: createApiMethod(axiosInstance, HTTP_METHODS.POST),
  patch: createApiMethod(axiosInstance, HTTP_METHODS.PATCH),
  put: createApiMethod(axiosInstance, HTTP_METHODS.PUT),
  delete: createApiMethod(axiosInstance, HTTP_METHODS.DELETE),
};</p>
<pre><code>createApiMethod라는 함수 내부에서 interceptors.response 로직을 넣어주었고 각 메서드마다 createApiMethod를 호출하는 구조로 작성했다. 내가 예상한건 axios instance에 interceptors.response가 한 번만 등록되는 것이었는데 실제로 그렇게 동작하지 않았다. 처음엔 잘 동작하다가 그 이후로 response에 response.data 들어오거나 undefined가 돌어왔다. 
axios를 뜯어보니 responseInterceptorChain 배열에 interceptor가 push되고 promise chaining으로 연산됐다. 지금 코드를 보면 get, post, patch, ..등을 호출될때마다 interceptors.response.use를 호출하고 chain 배열에 push 된다. 첫 번째 파라미터인 (response) =&gt; response.data는 fulfilled 될때 호출되고 있었기 때문에 response에 undefined가 들어오는 오류가 있었던 것이다.
```ts
// pseudo code
chain = [
  // ...생략,
  onResponseFulfilled1, onResponseRejected1,
  onResponseFulfilled2, onResponseRejected2
];

promise = Promise.resolve(config)
             // ... 생략
             .then(onResponseFulfilled1, onResponseRejected1)
             .then(onResponseFulfilled2, onResponseRejected2)</code></pre><p>그렇다면 이제 호출될때마다 interceptor가 등록되는게 아니라 처음 인스턴스가 생성될때 한 번만 등록되도록 해야 했다.</p>
<ul>
<li><code>src/apis/base.ts</code><pre><code class="language-ts">const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL,
  timeout: 10000,
  headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
});
</code></pre>
</li>
</ul>
<p>axiosInstance.interceptors.request.use(
    (config) =&gt; {
      const token = getLocalStorage(ACCESS_TOKEN);</p>
<pre><code>  if (config.headers) (config.headers as AxiosHeaders).set(&#39;Authorization&#39;, token);

  return config;
},
(error) =&gt; Promise.reject(error)</code></pre><p>  );</p>
<p>  axiosInstance.interceptors.response.use(
    <T>(response: AxiosResponse<T>) =&gt; {
      console.log(response);</p>
<pre><code>  return response.data;
},
(error) =&gt; Promise.reject(error)</code></pre><p>  );</p>
<p>// ... 이외 다른 함수 생략</p>
<pre><code>base 파일 내부에 instance create 함수와 interceptor들을 선언했다. 이렇게 하면 문제를 해결할 수 있지만 전역에 드러나는게 신경쓰여 즉시실행함수로 감싸주었다.

```ts
const axiosInstanceWithInterceptors = (() =&gt; {
  const axiosInstance = axios.create({
    baseURL: import.meta.env.VITE_BASE_URL,
    timeout: 10000,
    headers: { &#39;Content-Type&#39;: &#39;application/json&#39; },
  });

  axiosInstance.interceptors.request.use(
    (config) =&gt; {
      const token = getLocalStorage(ACCESS_TOKEN);

      if (config.headers) (config.headers as AxiosHeaders).set(&#39;Authorization&#39;, token);

      return config;
    },
    (error) =&gt; Promise.reject(error)
  );

  axiosInstance.interceptors.response.use(
    &lt;T&gt;(response: AxiosResponse&lt;T&gt;) =&gt; {
      console.log(response);

      return response.data;
    },
    (error) =&gt; Promise.reject(error)
  );

  return axiosInstance;
})();</code></pre><h4 id="axios-custom-instance의-request-response-type">axios custom instance의 request, response type</h4>
<p>typescript 숙련도도 높지 않고 axios custom instance를 직접 만들어 사용한 적도 없다보니 요청들의 request, response 타입을 어디서 잡아줘야 할지 막막했다. response들의 type이 <code>unknown</code> 으로 잡히다보니 response.data로 참조할 수 없었다.
<img src="https://velog.velcdn.com/images/kim-jaemin420/post/63bb4e7b-5924-405b-b355-7faaac2272b1/image.png" alt="">
then절에서 response 타입을 일일이 명시해주는 방법이 먼저 떠올랐지만 내가 봐도 가독성이 떨어졌고 기존 axios instance 처럼 타입을 명시해줬으면 했다.</p>
<pre><code class="language-ts">axios.get&lt;requestType, responseType&gt;(url)</code></pre>
<p>고생 끝에 알아낸 방법...
내가 만들어낸 요청 메서드들은 <code>createApiMethod</code>를 호출하여 만들어진다. 이 함수 내부에서 제네릭을 사용하면 메서들르 호출할때 전달한 타입이 request, response에 전달될 수 있었다.</p>
<ul>
<li><code>src/apis/base.ts</code><pre><code class="language-ts">const createApiMethod =
(axiosInstance: AxiosInstance, method: Method) =&gt;
// &lt;T, K&gt; 제네릭으로 전달 (T: request type, K: response type)
&lt;T, K&gt;(config: AxiosRequestConfig&lt;T&gt;): Promise&lt;K&gt; =&gt; {
  return axiosInstance({
    ...config,
    method,
  });
};
</code></pre>
</li>
</ul>
<p>const api = {
  get: createApiMethod(instance, HTTP_METHODS.GET),
  post: createApiMethod(instance, HTTP_METHODS.POST),
  patch: createApiMethod(instance, HTTP_METHODS.PATCH),
  put: createApiMethod(instance, HTTP_METHODS.PUT),
  delete: createApiMethod(instance, HTTP_METHODS.DELETE),
};</p>
<p>```
제네릭으로 전달할 수 있다는걸 몰라 며칠 고민했다. 제네릭 공부 좀 할걸...</p>
<h2 id="소감-찐후기">소감. 찐후기</h2>
<p>개발을 배우고 수도 없이 만들어봤던 todo였지만 이번 todo는 만들면서 다양한 고민을 해보게 되었다. 그동안 혼자 하는 프로젝트들은 큰 고민없이 내 맘대로 짰었는데 이번에는 혼자 하는 프로젝트지만 네이밍, 구조, 가독성에 대해 고민해가며 만들었다. 물론 혼자 하다보니 고민하는 시간이 무한정 길어지기도 해서 다음 프로젝트 때는 기한에 맞춰 완성하는 것을 연습해볼 필요가 있다고 느꼈다.
이번에 가장 인상 깊었던 경험은 처음으로 라이브러리 코드도 열어보았다는 것이다. 많은 선배님들이 라이브러리 까보면서 공부해라 라고 하셨을땐 코드를 보기만 해도 어렵고 이해도 안가서 나같은 짜바리는 아직 그럴 단계가 못된다고 생각하고 넘겼다. 그런데, 막연히 axios create나 interceptors가 어떻게 돌아가는지 궁금해서 코드를 열어보니 생각보다 어렵지 않았고 내 코드에서 어떤 결과물이 반환되는지 예상할 수 있게 되었다. 내가 사용한 방법들이 문제 해결에 있어 최선의 방법은 아니겠지만 이런 경험을 해봤다는 것 자체로 만족스러웠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[웹] OAuth]]></title>
            <link>https://velog.io/@kim-jaemin420/%EC%9B%B9-OAuth</link>
            <guid>https://velog.io/@kim-jaemin420/%EC%9B%B9-OAuth</guid>
            <pubDate>Tue, 10 Aug 2021 08:57:24 GMT</pubDate>
            <description><![CDATA[<h1 id="sns-로그인-oauth">SNS 로그인, OAuth</h1>
<p>요즘 다양한 웹 사이트에서는 현재의 웹 사이트에서 사용자에 대한 인증을 요구할 때, 외부 서버에서 이미 인증되어 있는 정보를 전달받아 현재 웹 서버에 인증을 하는 방식을 지원하고 있습니다. 이 때 사용되는 프로토콜이 OAuth 프로토콜입니다. 이러한 방식은 사용자에게 웹 사이트마다 인증을 해야 하는 불편한 작업을 줄여 보다 편리한 웹 서비스를 제공합니다.</p>
<blockquote>
<ul>
<li>프로토콜 : A 시스템과 B 시스템의 통신을 원할하게 수용하도록 해주는 통신 규약, 약속</li>
</ul>
</blockquote>
<p>2007년 처음으로 Oauth 1.0의 초안이 발표되었고 그 뒤로는 사람들에게 많이 알려지게 되었습니다. 그러나 점점 커져가는 네트워크 시장에서 한계가 나타나기 시작했고 2012년 Oauth 2.0을 새롭게 제시하였습니다. 현재 우리가 사용하고 있습니다.</p>
<p>Oauth 2.0에서 크게 바뀐 점은 다음과 같습니다.</p>
<ol>
<li>모바일 어플리케이션에서도 사용이 용이</li>
<li>반드시 HTTPS를 사용하기에 보안 강화</li>
<li>Access Token의 만료기간이 생김</li>
</ol>
<p>또, Oauth 2.0의 인증 방식은 크게 4가지입니다.</p>
<ol>
<li><strong>Authorization Code Grant</strong></li>
<li>Implicit Grant</li>
<li>Resource Owner Password Credentials Grant</li>
<li>Client Credentials Grant</li>
</ol>
<p>각 인증 방식에는 장단점이 존재합니다. 가장 많이 쓰이는 <strong>Authorization Code Grant</strong> 방식을 예로 들어 동작 순서를 알아봅시다.</p>
<p><img src="https://images.velog.io/images/kim-jaemin420/post/0e857da3-4b6b-450d-9440-35ca233b63d4/image.png" alt=""></p>
<blockquote>
<ul>
<li>Resource Owner : 일반 사용자, User</li>
</ul>
</blockquote>
<ul>
<li>Client : 우리가 관리하는 어플리케이션 서버(User 아님!)</li>
<li>Authorization Server : 권한을 관리하는 서버입니다. Access Token, Refresh Token을 발급, 재발급 해주는 역할을 합니다.</li>
<li>Resorce Server: Oauth2.0을 관리하는 서버(Google, Facebook, Naver 등)의 자원을 관리하는 서버입니다. 주의할 점은 우리가 만드는 서버의 자원을 관리하는 곳이 아닙니다. Oauth2.0 관리 서버의 자체 API를 의미합니다.</li>
</ul>
<p><img src="https://images.velog.io/images/kim-jaemin420/post/5309b59b-c36b-4c43-9d97-d07a9f05e342/image.png" alt=""></p>
<ol>
<li><p>Resource Owner(사용자)가 Client(우리 서버)에게 인증 요청을 합니다.</p>
</li>
<li><p>Client는 Authorization Request를 통해 Resource Owner에게 인증할 수단(ex Facebook, Google 로그인 url)을 보냅니다.</p>
</li>
<li><p>Resource Owner는 해당 Request를 통해 인증을 진행하고 인증을 완료했다는 신호로 Authorization Grant를 url에 실어 Client에게 보냅니다.</p>
</li>
<li><p>Client는 해당 권한증서(Authorization Grant)를 Authorization Server에 보냅니다.</p>
</li>
<li><p>Authorization Server는 권한증서를 확인 후, 유저가 맞다면 Client에게 Access Token, Refresh Token, 그리고 유저의 프로필 정보(id 포함) 등을 발급해줍니다. </p>
</li>
<li><p>Client는 해당 Access Token을 DB에 저장하거나 Resource Owner에게 넘깁니다.</p>
</li>
<li><p>Resource Owner(사용자)가 Resource Server에 자원이 필요하면, Client는 Access Token을 담아 Resource Server에 요청합니다.</p>
</li>
<li><p>Resource Server는 Access Token이 유효한지 확인 후, Client에게 자원을 보냅니다.</p>
</li>
<li><p>만일 Access Token이 만료됐거나 위조되었다면, Client는 Authorization Server에 Refresh Token을 보내 Access Token을 재발급 받습니다. </p>
</li>
<li><p>그 후 다시 Resource Server에 자원을 요청합니다.</p>
</li>
<li><p>만일 Refresh token도 만료되었을 경우, Resource Owner는 새로운 Authorization Grant를 Client에게 넘겨야합니다. (이는 다시 사용자가 다시 로그인 하라는 말입니다.)</p>
</li>
</ol>
<p>** 여기서 2편의 Access Token + Refresh Token 편을 보신 독자분들이라면 인증 과정이 유사하다는 것을 알 수 있습니다. Access Token, Refresh Token을 이용한 인증 방식은 한 서버에서 모두 관리하는 반면, 여기 OAuth에서는 Authorization Server에서 인증+권한 관리를 하고 Resource Server에서는 자원에 대한 관리만 합니다. </p>
<p>** 다시 한 번 강조하지만 Oauth 2.0 은 우리가 이전에 봤던 (사용자-서버) 구조가 아닌 (사용자 - 서버 - Oauth 서버) 입니다. 우리가 만들 서비스들의 인증을 돕기 위한 서비스가 바로 OAuth입니다. Resource Server는 우리의 서버가 아닌 Oauth를 관리하는 서버의 일부임을 명심하세요.</p>
<p>자 여기까지라면 왜 OAuth를 알아야하는지 궁금하실 겁니다. 왜냐하면 SNS 로그인을 제공하는 Google, Facebook, Naver 등은 모두 OAuth2.0 프레임워크를 통해 로그인 API를 제공하기 때문입니다!</p>
<p>이제 SNS 로그인 동작방식에 대해 알아보도록 하겠습니다. SNS 로그인은 간단하게 봤을 때 OAuth2.0 + 서버 인증(세션/쿠키 , 토큰기반 인증)으로 구성됩니다. </p>
<p>본 설명은 페이스북 로그인을 예로 들겠습니다. 또한 OAuth2.0에 사용되는 명칭은 이해하기 쉽게 바꿔서 설명하도록 하겠습니다. </p>
<p> <img src="https://images.velog.io/images/kim-jaemin420/post/58d9a3f7-4d11-4dc7-84d2-d39d6e9d117f/image.png" alt=""></p>
<ol>
<li><p>사용자(Resource Owner)가 서버에게 로그인을 요청합니다.</p>
</li>
<li><p>서버는 사용자에게 특정 쿼리들을 붙인 페이스북 로그인 URL을 사용자에게 보냅니다.</p>
</li>
<li><p>사용자는 해당 URL로 접근하여 로그인을 진행한 후 권한증서(code)를 담아 서버에게 보냅니다.</p>
</li>
<li><p>서버는 해당 권한 증서를 Facebook의 Authorization Server로 요청합니다.</p>
</li>
<li><p>서버는 권한 증서를 확인 후, Access Token, Refresh Token, 유저의 정보(고유 id 포함) 등을 돌려줍니다.</p>
</li>
</ol>
<p>** 여기서 프로필 이미지나 이메일 주소, 이름 등을 얻을 수도 있는데 이는 초기에 관리자가 권한 설정을 어디까지 하느냐에 따라 다릅니다. 페이스북 이름에 대해서만 접근할 수 있는 권한을 설정하면 이름 값만 Authorization Server에서 돌려줄 것입니다.</p>
<ol start="6">
<li><p>받은 고유 id를 key값으로 해서 DB에 유저가 있다면 로그인, 없다면 회원가입을 진행합니다.</p>
</li>
<li><p>로그인이 완료되었다면 세션/쿠키 , 토큰기반 인증 방식을 통해 사용자의 인증을 처리합니다.</p>
</li>
</ol>
<p>** 우리가 만들 서버에서 OAuth를 이용하기 위해서는 사전에 OAuth에 등록하는 과정이 필요합니다. 등록 후 APP_ID와 CLIENT_ID 등을 보내야 OAuth 에서는 어느 서비스인지를 알 수 있습니다.</p>
<p>** 페이스북 로그인을 인증을 이용하는 경우, 대부분은 Resource Server(페이스북 자체 API)를 사용하지 않습니다. 따라서 Access Token, Refresh Token은 실제로 쓰이지 않습니다. 우리의 서버에서 access token을 검증할 수도 없을 뿐더러 인증의 수단으로 활용하기엔 부족한 점이 많습니다. 따라서 보통 7번 절차처럼 Authorization Server로 부터 얻는 고유 id값을 활용해서 DB에 회원관리를 진행합니다.  </p>
<h2 id="🗒-reference">🗒 Reference</h2>
<blockquote>
<ul>
<li><a href="https://tansfil.tistory.com/60?category=475681">https://tansfil.tistory.com/60?category=475681</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://velog.io/@undefcat/OAuth-2.0-%EA%B0%84%EB%8B%A8%EC%A0%95%EB%A6%AC">https://velog.io/@undefcat/OAuth-2.0-%EA%B0%84%EB%8B%A8%EC%A0%95%EB%A6%AC</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[세션과 쿠키, 그리고 JWT]]></title>
            <link>https://velog.io/@kim-jaemin420/%EC%84%B8%EC%85%98%EA%B3%BC-%EC%BF%A0%ED%82%A4-%EA%B7%B8%EB%A6%AC%EA%B3%A0-JWT</link>
            <guid>https://velog.io/@kim-jaemin420/%EC%84%B8%EC%85%98%EA%B3%BC-%EC%BF%A0%ED%82%A4-%EA%B7%B8%EB%A6%AC%EA%B3%A0-JWT</guid>
            <pubDate>Mon, 09 Aug 2021 10:08:14 GMT</pubDate>
            <description><![CDATA[<h1 id="웹의-기본-세션과-쿠키-그리고-jwt에-대해서">웹의 기본, 세션과 쿠키 그리고 JWT에 대해서...</h1>
<h2 id="📌-http-요청">📌 HTTP 요청</h2>
<p>현재 모바일이나 웹 서비스에서 가장 많이 사용하고 있는 통신은 HTTP 통신입니다. HTTP 프로토콜 환경에서 서버는 클라이언트가 누구인지 확인해야 합니다. HTTP 프로토콜은 <strong>connectionless</strong>하고 <strong>stateless</strong>한 특징이 있기 때문입니다.</p>
<blockquote>
<ul>
<li><strong>Connectionless</strong>
  클라이언트가 요청을 한 후 응답을 받으면 연결을 끊어버리는 특성</li>
</ul>
</blockquote>
<ul>
<li><strong>Stateless</strong>
  통신이 끝나면 상태를 유지하지 않는 특성
 연결이 끊는 순간 클라이언트와 서버의 통신이 끝나며 상태 정보는 유지하지 않는 특성이 있다.</li>
</ul>
<p>이러한 http 통신의 특징 때문에 요청하는 대상이 누군지 구별할 필요가 있습니다. 그런데, 사용자 계정정보를 요청에 담아보내는 방식은 보안에 굉장히 취약합니다. 따라서 나온 방식이 세션/쿠키 입니다.</p>
<h2 id="📌-세션과-쿠키">📌 세션과 쿠키</h2>
<p><img src="https://images.velog.io/images/kim-jaemin420/post/de1b246a-bf37-4cfa-a481-30782e8c225a/image.png" alt=""></p>
<p>순서를 살펴보면 다음과 같습니다.</p>
<ol>
<li>사용자가 로그인합니다.</li>
<li>서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여하여 세션 저장소에(주로 Redis를 사용) 저장한 후, 이와 연결되는 세션 ID를 발급받습니다.</li>
<li>사용자는 서버에서 해당 세션 ID를 받아 쿠키에 저장한 후, 인증이 필요한 요청마다 쿠키를 헤더에 실어 보냅니다.</li>
<li>서버에서는 쿠키를 받아 세션 저장소에서 대조를 한 후, 대응되는 정보를 가져옵니다.</li>
<li>인증이 완료되고 서버는 요청에 맞는 데이터를 보내줍니다.</li>
</ol>
<p>세션 쿠키 방식은 기본적으로 세션 저장소를 필요로 합니다.(주로 Redis 사용) 세션 저장소는 로그인을 했을 때 사용자의 정보를 저장하고 열쇠가 되는 세션 ID값을 만듭니다. 그리고 HTTP 헤더에 세션 id를 실어 사용자에게 보냅니다. 그러면 사용자는 세션 id를 쿠키로 보관하고 있다가 인증이 필요한 요청을 보낼 때 쿠키와 함께 보냅니다. 웹 서버에서는 세션 저장소에서 세션 id를 받고 저장되어 있는 정보와 매칭시켜 인증을 완료합니다.</p>
<blockquote>
<p>※ 세션과 쿠키의 차이
세션 : 서버에서 가지고 있는 정보
쿠키 : 사용자에게 발급된 세션 id를 저장하기 위한 공간
<br />
※※ 쿠키만으로 인증을 사용한다는 말은 서버의 자원을 사용하지 않는다는 말입니다. 이는 클라이언트가 인증 정보를 책임지게 되는건데, 보안에 매우매우 취약합니다. 따라서 보안과는 상관없는 장바구니나 자동로그인 설정 같은 경우에 사용됩니다.
결과적으로 인증의 책임을 서버가 지게 하기 위해서 세션을 사용합니다. (서버를 해킹하는게 더 어렵기 때문입니다.) 사용자는 쿠키를 이용하고, 서버에서는 쿠키를 받아 세션의 정보를 접근하는 방식으로 인증합니다.</p>
</blockquote>
<h3 id="세션쿠키-방식의-장점">세션/쿠키 방식의 장점</h3>
<ol>
<li>보안 측면에서 안정적입니다.</li>
</ol>
<p>세션/쿠키 방식은 기본적으로 쿠키를 매개로 인증을 거칩니다. 쿠키에 담긴 세션 id는 세션 저장소에 저장되어 있는 사용자 정보를 얻기 위한 열쇠입니다. 따라서 쿠키가 담긴 HTTP 요청이 노출되더라도 쿠키 자체는 유의미한 값을 가지고 있지 않습니다. 중요한 정보들은 서버 세션에 있기 때문입니다.</p>
<ol start="2">
<li>서버 자원에 접근하기 용이합니다.</li>
</ol>
<p>각각의 사용자는 고유한 id를 발급받게 됩니다. 따라서 서버에서는 쿠키 값을 받았을 때 회원정보를 확인할 필요없이 어떤 회원인지 알 수 있어 서버의 자원에 접근하기 쉽습니다.</p>
<h3 id="세션쿠키-방식의-단점">세션/쿠키 방식의 단점</h3>
<ol>
<li>세션 하이재킹 공격에 취약</li>
</ol>
<p>쿠키는 의미있는 정보가 없기 때문에 안전합니다. 그러나, 만약 A 사용자의 쿠키를 해커가 가로채서 HTTP 요청을 보내면 서버의 세션 저장소에서는 A 사용자의 정보를 뿌려줄 수 있습니다. (세션 하이재킹 공격)</p>
<p>이는 HTTPS를 사용해 요청 자체를 탈취해도 안의 정보를 읽기 힘들게 하거나, 세션에 유효시간을 넣어주는 방식으로 해결할 수 있습니다.</p>
<ol start="2">
<li>서버 부하⬆️</li>
</ol>
<p>서버에서 세션 저장소를 사용합니다. 따라서 서버에 추가적인 저장공간을 필요로 하게 되고 자연스럽게 서버 부하도 높아질 것입니다.</p>
<h2 id="📌-jwt-토큰-기반-인증">📌 JWT 토큰 기반 인증</h2>
<h3 id="jwt-토큰-구성-요소">JWT 토큰 구성 요소</h3>
<p>JWT는 세션/쿠키와 함께 많이 쓰이는 인증 방식입니다. JWT는 인증에 필요한 정보들을 암호화시킨 토큰을 말합니다. 세션/쿠키 방식과 유사하게 사용자는 Access Token을 HTTP 헤더에 실어 서버에 보냅니다.</p>
<p>토큰을 만들기 위해서 Header, Payload, Verify Signature가 필요합니다. 해커가 토큰을 조작하여 A 사용자의 데이터를 훔쳐보고 싶다고 가정하겠습니다. 그래서 payload에 있던 A의 ID를 인코딩한 후 토큰을 서버로 보냅니다. 그러면 서버가 검사했을 때 payload는 A 사용자 정보지만 verify signature는 해커의 payload를 기반으로 암호화되었기 때문에 유효하지 않는 토큰으로 간주됩니다. 따라서 해커는 SECRET KEY를 알지 못하는 이상 토큰을 조작할 수 없습니다.</p>
<h3 id="jwt-인증-방식">JWT 인증 방식</h3>
<p><img src="https://images.velog.io/images/kim-jaemin420/post/5e0054f9-af4b-4f2c-98cb-0014e6d65888/image.png" alt=""></p>
<ol>
<li>사용자가 로그인을 합니다.</li>
<li>서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여한 후, 기타 정보와 함께 Payload에 넣습니다.</li>
<li>JWT 토큰의 유효기간을 설정합니다.</li>
<li>암호화할 SECRET KEY를 이용해 Access Token 발급합니다.</li>
<li>사용자는 Access Token을 받아 저장한 후, 인증이 필요한 요청마다 토큰을 헤더에 실어 보냅니다.</li>
<li>서버에서는 해당 토큰의 Verify Signature를 SECRET KEY로 복호화한 후, 조작 여부, 유효기간을 확인합니다.</li>
<li>검증이 완료된다면, payload를 디코딩하여 사용자의 ID에 맞는 데이터를 가져옵니다.</li>
</ol>
<p>세션/쿠키 방식과 가장 큰 차이점은 세션/쿠키는 세션 저장소에 유저의 정보를 넣는 반면, JWT는 토큰 안에 유저의 정보를 넣는다는 점입니다. 클라이언트 입장에서는 HTTP 헤더에 쿠키를 실느냐, 토큰을 실느냐의 차이만 있지만, 서버 측에서는 인증을 위해 암호화를 하느냐, 별도의 저장소를 이용하느냐 하는 차이가 있습니다.</p>
<h3 id="jwt-장점">JWT 장점</h3>
<ol>
<li>추가 저장소가 필요하지 않습니다.</li>
</ol>
<p>세션/쿠키는 별도의 저장소 관리가 필요한 반면, jwt는 발급한 후 검증만 하면 되기 때문에 추가 저장소가 필요하지 않습니다. 이는 stateless한 서버를 만드는 입장에서는 큰 장점입니다. <em>stateless는 어떠한 별도의 저장소도 사용하지 않는, 즉 상태를 저장하지 않는 것을 의미합니다.</em> 이는 서버를 확장하거나 유지, 보수하는데 유리합니다.</p>
<ol start="2">
<li>확장성이 뛰어납니다.</li>
</ol>
<p>토큰 기반으로 하는 다른 인증 시스템에 접근이 가능합니다. 예를 들어 Facebook으로 로그인, 구글 로그인 등은 모두 토큰을 기반으로 인증을 합니다. 이에 선택적으로 이름이나 이메일 등을 받을 수 있는 권한도 받을 수 있습니다.</p>
<h3 id="jwt-단점">JWT 단점</h3>
<ol>
<li>한 번 발급한 JWT는 돌이킬 수 없습니다.</li>
</ol>
<p>세션/쿠키의 경우 만일 쿠키가 악의적으로 이용된다면, 해당하는 세션을 지워버릴 수 있습니다. 하지만 JWT는 한 번 발급되면 유효기간이 완료될 때 까지 계속 사용이 가능합니다. 따라서 악의적인 사용자는 유효기간이 지나기 전까지 정보를 털어갈 수 있습니다.</p>
<p>※해결책※
기존의 Access Token의 유효기간을 짧게 하고 Refresh Token이라는 새로운 토큰을 발급합니다.</p>
<ol start="2">
<li>Payload 정보가 제한적입니다.</li>
</ol>
<p>Payload는 따로 암호화되지 않기 때문에 디코딩하면 누구나 정보를 확인할 수 있습니다. 따라서 유저의 중요한 정보들은 Payload에 넣을 수 없습니다.</p>
<ol start="3">
<li>JWT 길이가 깁니다.</li>
</ol>
<p>세션/쿠키 방식에 비해 JWT의 길이가 깁니다. 따라서 인증이 필요한 요청이 많아질수록 서버의 자원낭비가 발생하게 됩니다.</p>
<h3 id="refresh-token과-access-token">Refresh Token과 Access Token</h3>
<p>앞서 말한 JWT의 문제점의 해결방법이었던 Refresh Token에 대해 알아보겠습니다. Access Token을 통한 인증 방식의 문제는 제 3자에게 탈취당할 수 있습니다. Access Token의 유효기간을 줄인다면 사용자는 그때마다 다시 로그인을 해야 합니다. 반대로 유효기간을 늘리면 토큰을 더 오래 사용할 수 있어 보안에 취약합니다. 이때 Refresh Token을 사용하여 해결할 수 있습니다.</p>
<p>Refresh Token은 Access Token과 똑같은 형태의 JWT입니다. 처음에 로그인했을 때 Access Token과 동시에 발급되는 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 발급해주는 열쇠가 됩니다.</p>
<p>예를 들어, Refresh Token의 유효기간은 2주, Access Token의 유효기간을 1시간이라고 가정하겠습니다. 사용자는 1시간 동안 자유롭게 api 요청을 하다가 이후에 Access Token이 만료됩니다. 그러면 Refresh Token이 만료되기 전까지 새로 Access Token을 발급받을 수 있습니다.</p>
<blockquote>
<p>** Access Token은 여전히 탈취당할 수 있습니다. 그러나, 토큰이 유효한 시간이 짧기 때문에 기존보다 더 안전합니다.
** Refresh Token의 유호기간이 만료되면 사용자는 다시 로그인해야 합니다. Refresh Token 역시 탈취의 위험이 있기 때문에 유효기간을 적절히 설정해야 합니다. (보통 2주)</p>
</blockquote>
<h4 id="refresh-token을-사용한-jwt-인증-과정">Refresh Token을 사용한 JWT 인증 과정</h4>
<p><img src="https://images.velog.io/images/kim-jaemin420/post/afdb71ab-2aa5-469e-9fe1-9c884641cae3/image.png" alt=""></p>
<ol>
<li>사용자가 로그인합니다.</li>
<li>서버에서는 DB에서 회원을 조회합니다.
3-4. 로그인이 완료되면 Access Token과 Refresh Token을 발급합니다. 이때 일반적으로 회원 DB 혹은 별도의 저장소에 Refresh Token을 저장해둡니다.</li>
<li>클라이언트는 Refresh Token을 저장소에 저장하고 Access Token을 헤더에 실어 요청을 보냅니다.
6-7. 서버에서 Access Token을 검증하고 데이터를 응답해줍니다.</li>
</ol>
<p>시간이 흘러 Access Token이 만료됩니다.</p>
<ol start="9">
<li>클라이언트는 같은 방식으로 Access Token이 담긴 헤더와 함께 요청을 보냅니다.
10-11. 서버는 Access Token이 만료됨을 확인하고 권한없음으로 응답해줍니다.</li>
</ol>
<blockquote>
<p>** 반드시 서버에서 토큰 만료를 확인할 필요는 없습니다. 클라이언트에서 토큰의 Payload를 통해 유효기간을 알 수 있습니다. 따라서 프론트엔드단에서 API 요청을 보내기 전에 토큰이 만료됐다는 것을 알고 재발급 요청을 할 수도 있습니다.</p>
</blockquote>
<ol start="12">
<li>클라이언트는 Refresh Token과 Access Token을 함께 서버로 보냅니다.</li>
<li>서버는 받은 Access Token이 조작되지 않았는 지 확인하고 Refresh Token과 사용자의 DB에 저장되어 있던 Refresh Token을 비교합니다. 토큰이 동일하고 유효기간이 지나지 않았다면 새로운 Access Token을 발급해줍니다.</li>
<li>서버는 새로운 Access Token을 헤더에 담아 응답을 해줍니다.</li>
</ol>
<h4 id="refresh-token의-장점과-단점">Refresh Token의 장점과 단점</h4>
<p><strong>장점</strong></p>
<p>기존의 Access Token만 사용할 때보다 안전합니다.</p>
<p><strong>단점</strong></p>
<ol>
<li><p>구현이 복잡합니다. 검증 프로세스가 길어져 구현이 조금 더 힘들어졌습니다.</p>
</li>
<li><p>Access Token이 만료될 때마다 새롭게 발급하는 과정에서 생기는 HTTP 요청 횟수가 많아졌습니다. 이는 서버의 자원 낭비로 귀결됩니다.</p>
</li>
</ol>
<h2 id="🗒-reference">🗒 Reference</h2>
<blockquote>
<ul>
<li><a href="https://medium.com/@d971106b/%EC%82%BD%EC%A7%88%EA%B8%B0%EB%A1%9D-%EB%A1%9C%EA%B7%B8%EC%9D%B8-api-%EC%9E%91%EC%84%B1-jwt-refresh-token-access-token-http-only-92570160fa1c">https://medium.com/@d971106b/%EC%82%BD%EC%A7%88%EA%B8%B0%EB%A1%9D-%EB%A1%9C%EA%B7%B8%EC%9D%B8-api-%EC%9E%91%EC%84%B1-jwt-refresh-token-access-token-http-only-92570160fa1c</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://covenant.tistory.com/201">https://covenant.tistory.com/201</a></li>
<li><a href="https://tansfil.tistory.com/59?category=475681">https://tansfil.tistory.com/59?category=475681</a></li>
<li><a href="https://tansfil.tistory.com/58?category=475681">https://tansfil.tistory.com/58?category=475681</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Next] Next.js 시작하기]]></title>
            <link>https://velog.io/@kim-jaemin420/Next-Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@kim-jaemin420/Next-Next.js-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sun, 01 Aug 2021 05:26:26 GMT</pubDate>
            <description><![CDATA[<h1 id="nextjs-시작하기">Next.js 시작하기</h1>
<h2 id="📌-프로젝트-setup">📌 프로젝트 Setup</h2>
<p>Next.js는 <code>create-next-app</code>을 이용하여 어플리케이션을 생성하는 것을 권장합니다. <code>create-next-app</code>은 set up에 필요한 것들을 자동적으로 준비해줍니다. 만약 타입스크립트를 사용하고 싶다면 <code>--typescript</code> 플래그를 사용하세요.</p>
<pre><code class="language-code">npx create-next-app
# 혹은
yarn create next-app

npx create-next-app --typescript
# 혹은
yarn create next-app --typescript</code></pre>
<p>만약 기존 프로젝트에 next를 추가한다면 다음과 같이 할 수 있습니다. next, react, react-dom을 설치해주세요.</p>
<pre><code class="language-code">npm install next react react-dom
# 혹은
yarn add next react react-dom</code></pre>
<p><code>package.json</code>을 열고 <code>scripts</code>를 수정해주세요.</p>
<pre><code class="language-json">&quot;scripts&quot;: {
  &quot;dev&quot;: &quot;next dev&quot;,
  &quot;build&quot;: &quot;next build&quot;,
  &quot;start&quot;: &quot;next start&quot;,
  &quot;lint&quot;: &quot;next lint&quot;
}</code></pre>
<h2 id="📌-pages">📌 Pages</h2>
<p>Next.js는 <code>**pages**</code>개념을 기반으로 구축되었습니다. 어플리케이션의 페이지들은 <code>pages</code> 디렉토리 안의 <code>js</code>, <code>jsx</code>, <code>ts</code>, <code>tsx</code> 파일 안의 리액트 컴포넌트를 렌더링합니다. Next는 <code>pages</code> 디렉토리 안의 파일들을 코드 스플리팅된 컴포넌트로 만들어주므로 디렉토리 이름은 항상 <code>pages</code>여야 합니다.</p>
<p>각각의 페이지들의 라우트 이름은 파일 이름과 연관이 있습니다. 예를 들어, <code>pages</code>디렉토리 안에 <code>about.js</code> 파일이 있다면 라우터 이름은 <code>/about</code>이 됩니다. 동적으로 바뀌는 라우트 또한 사용할 수 있습니다. <code>pages/posts/[id].js</code> 파일 이름을 이런식으로 작성하면, 동적으로 변하는 라우터를 만들 수 있습니다. <code>posts/1</code>, <code>posts/2</code>.. 등이 가능합니다.</p>
<p>어플리케이션을 구동하기 위해 <code>npm run dev</code> 혹은 <code>yarn dev</code>를 실행합니다. <code>http://localhost:3000</code>에 들어가면 보이는 화면은 <code>pages/index.js</code> 파일입니다.</p>
<h2 id="📌-pre-rendering">📌 Pre-rendering</h2>
<p>기본적으로 Next.js는 모든 페이지를 미리 렌더링(pre-rendering)합니다. react는 최초에 비어있는 HTML 하나만 서버에서 받은 후 모든 것을 클라이언트 사이드에서 렌더링하는 반면, Next.js는 미리 각 페이지들의 HTML을 생성해둡니다. 이렇게 되면, Pre-rendering으로 인해 검색 최적화와 더 나은 성능을 가져올 수 있습니다.</p>
<h3 id="두-가지-형식의-pre-rendering">두 가지 형식의 Pre-rendering</h3>
<p>Next는  <strong>Static Generation</strong>과 <strong>Server-side Rendering</strong> 두 가지 형태의 pre-rendering이 있습니다. 이 둘은 페이지들의 HTML을 <strong>언제</strong> 생성하느냐에 따라 다릅니다.</p>
<ul>
<li><strong>Static Generation (추천)</strong></li>
</ul>
<p>HTML을 build 할 때 생성하고 요청할때 이를 재사용합니다.</p>
<ul>
<li><strong>Server-side Rendering</strong></li>
</ul>
<p>요청할 때마다 HTML을 생성합니다.</p>
<p>중요한 점은, 여러분이 어떤 방식으로 pre-rendering 할 것인지 정할 수 있습니다. 대부분의 페이지는 Static Generation 방식을 사용하고 다른 것들은 서버 사이드 렌더링 방식을 사용해서 하이브리드 next app을 만들 수 있습니다.</p>
<p>static generation 방식은 추가적인 작업 없이 CDN에서 캐시할 수 있기 때문에 next는 이 방식을 추천합니다. 이 두 가지 pre-rendering 방식과 함께 react처럼 클라이언트 사이드 렌더링을 사용할 수도 있습니다.</p>
<blockquote>
<p>CDN이란?
CDN은 서버와 사용자 사이의 물리적 거리를 줄여 웹 페이지의 로드 지연을 최소화하는, 촘촘히 분산된 서버로 이루어진 플랫폼입니다.</p>
</blockquote>
<p>Static Generation은 어플리케이션을 build 할 때 생성한다고 했습니다. next에서는 데이터가 있을때도, 그리고 데이터가 없을 때도 정적으로 페이지를 만들 수 있습니다. 각각의 경우를 통해 알아봅시다.</p>
<h3 id="data-fetching이-없을-때-static-generation">data fetching이 없을 때 Static Generation</h3>
<p>기본적으로 next는 데이터를 가져오지 않고 static generation을 사용하여 페이지를 미리 렌더링합니다.</p>
<pre><code class="language-react">function About() {
  return &lt;div&gt;About&lt;/div&gt;
}

export default About</code></pre>
<p>data fetching을 하지 않는다면 문제될 게 없어보입니다. data fetching이 있는 경우는 어떨까요?</p>
<h3 id="data-fetching이-있을-때-static-generation">data fetching이 있을 때 Static Generation</h3>
<p>어떤 페이지들은 pre-rendering을 위해 데이터를 가져와야 합니다. 데이터를 가져오는 경우는 크게 두 가지가 있고 각각의 경우에, next가 제공해주는 함수를 사용할 수 있습니다.</p>
<ol>
<li><p>페이지의 <strong>내용</strong>이 외부 데이터에 의존한다 =&gt; <code>getStaticProps</code> 사용</p>
</li>
<li><p>페이지의 <strong>경로</strong>가 외부 데이터에 의존한다 =&gt; <code>getStaticPaths</code> 사용</p>
</li>
</ol>
<h4 id="1-페이지의-내용이-외부-데이터에-의존">1. 페이지의 내용이 외부 데이터에 의존</h4>
<pre><code class="language-js">function Blog({ posts }) {
  return (
    &lt;ul&gt;
      {posts.map((post) =&gt; (
        &lt;li&gt;{post.title}&lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )
}

export default Blog</code></pre>
<p>props로 정보를 받아 렌더링 해주는 컴포넌트가 있다고 할때, pre-render를 하기 전에 데이터를 받아와야 합니다. 이때 <code>getStaticProps</code>를 사용할 수 있는데 이 함수는 build 타임에 호출되고 받아온 데이터를 Pre-render 할 때 전달해줍니다.</p>
<pre><code class="language-js">function Blog({ posts }) {
  // posts 렌더링
}

// 이 함수는 build time에 호출됩니다.
export async function getStaticProps() {
  // 외부 api 호출
  const res = await fetch(&#39;https://.../posts&#39;)
  const posts = await res.json()

  // { props: { posts } }를 반환함으로써,
  // `posts`를 build time에 prop으로 받을 수 있습니다.
  return {
    props: {
      posts,
    },
  }
}

export default Blog</code></pre>
<h4 id="2-페이지의-경로가-외부-데이터에-의존">2. 페이지의 경로가 외부 데이터에 의존</h4>
<p>앞서 말했듯이 next에서는 동적 라우트를 사용해서 페이지를 만들 수 있습니다. 예를 들어, <code>pages/posts/[id].js</code> 라는 파일을 생성하고 <code>posts/1</code> 주소로 접근한다면 id를 1로 인식하여 페이지가 렌더링됩니다.</p>
<p>이 paths가 동적으로 받아온다면 다음과 같이 쓸 수 있습니다.</p>
<pre><code class="language-js">// getStaticPaths도 build 타임에 호출됩니다.
export async function getStaticPaths() {
  const res = await fetch(&#39;https://.../posts&#39;)
  const posts = await res.json()

  const paths = posts.map((post) =&gt; ({
    params: { id: post.id },
  }))

  // { fallback: false}는 다른 라우터는 404여야 한다는 것을 뜻합니다.
  return { paths, fallback: false }
}</code></pre>
<p><code>pages/posts/[id].js</code>에서 <code>getStaticProps</code>를 사용하여 특정 id 값을 props로 받도록 합니다.</p>
<pre><code class="language-js">function Post({ post }) {
  // posts 렌더링
}

export async function getStaticPaths() {
  // ...
}

export async function getStaticProps({ params }) {
  const res = await fetch(`https://.../posts/${params.id}`)
  const post = await res.json()

  return { props: { post } }
}

export default Post</code></pre>
<h3 id="server-side-rendering">Server-side Rendering</h3>
<p>만약 <strong>Server-side Rendering</strong>을 사용한 페이지라면, 매 요청마다 HTML이 생성됩니다. server-side rendering을 사용하고 싶은 페이지에서 <code>getServerSideProps</code>라는 비동기 함수를 export하면, 서버는 이 함수를 요청이 올때마다 호출합니다.</p>
<pre><code class="language-js">function Page({ data }) {
  // 데이터 렌더링
}

// 이 함수는 매 request마다 호출됩니다
export async function getServerSideProps() {
  // 외부 API로부터 데이터를 가져옵니다
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // props를 통해 데이터를 넘겨줍니다(getStaticProps와 유사)
  return { props: { data } }
}

export default Page</code></pre>
<p>Static Generation 방식과 비슷하지만 다른 점은 <code>getStaticProps</code>는 빌드 타임에 호출되고 <code>getServerSideProps</code>는 매 요청마다 호출된다는 것입니다.</p>
<h2 id="📌-언제-static-generation을-사용해야-할까요">📌 언제 Static Generation을 사용해야 할까요?</h2>
<p>next는 가능하면 <strong>Static Generation</strong>을 사용하는 것을 권장합니다. CDN으로부터 받은 데이터를 한 번만 빌드할 수 있다면 모든 요청마다 서버에서 렌더링하는 것보다 훨씬 빠르고 적은 리소스를 사용할 수 있기 때문입니다. </p>
<p>마케팅 페이지, 블로그 포스트, 이커머스 상품의 리스트, 도큐먼트 페이지 등 많은 타입의 페이지에서 <strong>Static Generation</strong>을 사용할 수 있습니다.</p>
<p>스스로에게 물어봅시다. <code>&quot;사용자의 요청 전에 이 페이지가 pre-render 될 수 있는가?&quot;</code> 이 질문의 대답이 YES라면, Static Generation을 통해 퍼포먼스와 SEO 측면으로 많은 이점이 생길 것입니다.</p>
<p>반면, 사용자 요청 이전에 pre-render 할 수 없다면 Static generation은 좋은 생각이 아닙니다. 여러분의 페이지의 데이터가 자주 업데이트 되고 페이지의 컨텐트가 매 요청마다 바뀌는 경우가 여기에 해당됩니다. 이런 경우, 다음 두 가지 방법이 있습니다.</p>
<ul>
<li><p><strong>Static Generation</strong>과 <strong>Client-side Rendering</strong> 함께 사용 : 페이지의 일부분을 pre-rendering 하지 않고 그 부분을 client-side에서 자바스크립트 코드를 동작시켜 구성할 수 있습니다.</p>
</li>
<li><p><strong>Server-side Rendering</strong> 사용: 모든 요청마다 페이지를 pre-render 할 수 있습니다. 이 방법은 CDN에 의해 페이지가 캐싱될 수 없기 때문에 느리지만 항상 최신 상태를 유지합니다.</p>
</li>
</ul>
<h2 id="📋-reference">📋 Reference</h2>
<blockquote>
<p><a href="https://nextjs.org/docs/basic-features/pages">https://nextjs.org/docs/basic-features/pages</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[react]Virtual DOM이란? | What is Virtual DOM?]]></title>
            <link>https://velog.io/@kim-jaemin420/reactVitual-Dom%EC%9D%B4%EB%9E%80-What-is-Virtual-Dom</link>
            <guid>https://velog.io/@kim-jaemin420/reactVitual-Dom%EC%9D%B4%EB%9E%80-What-is-Virtual-Dom</guid>
            <pubDate>Tue, 13 Jul 2021 11:05:09 GMT</pubDate>
            <description><![CDATA[<h1 id="virtual-dom이란">Virtual DOM이란?</h1>
<p>Virtual DOM은 말 그대로 가상 돔입니다. 그런데, 가상 돔이 왜 필요할까요? 이를 이해하기 위해서는 브라우저의 작동 방식에 대해 알아볼 필요가 있습니다.</p>
<h2 id="📌-브라우저-작동-방식">📌 브라우저 작동 방식</h2>
<p><img src="https://images.velog.io/images/kim-jaemin420/post/ed6d061e-acb8-4cae-ad79-429205f2c7db/image.png" alt=""></p>
<p>&quot;브라우저 엔진&quot;과 &quot;렌더링 엔진&quot;은 뭘까요?</p>
<p>그림에서 알 수 있듯이 브라우저 엔진은 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어해주는 엔진입니다.
렌더링 엔진은 HTML documents와 웹 페이지의 리소스들을 브라우저에 시각적으로 출력해줍니다.</p>
<p><strong>브라우저가 화면에 그리는 과정</strong></p>
<ol>
<li>요청과 응답</li>
</ol>
<p>여러분이 어떤 링크를 누르거나 URL에 주소를 입력하고 엔터를 누르면 그 페이지에서 HTTP 요청이 이루어지고 해당 서버에서 HTML document를 응답으로 줍니다. (이 사이에 정말 많은 것들이 일어납니다.)</p>
<ol start="2">
<li>HTML 파싱과 DOM 생성</li>
</ol>
<p>브라우저는 HTML 소스코드를 파싱하고 돔 트리를 생성합니다. 이 돔 트리는 데이터를 표현한 것인데, 여기에는 HTML 태그들에 해당하는 노드와 태그 사이의 텍스트 청크들에 해당하는 텍스트 노드가 들어가 있습니다. 돔 트리의 루트 노드는 <code>&lt;html&gt;</code> 입니다.</p>
<ol start="3">
<li>CSS 파싱과 CSSOM 생성</li>
</ol>
<p>브라우저 렌더링 엔진은 HTML을 처음부터 한 줄씩 순차적으로 파싱하여 DOM을 생성합니다. DOM을 생성해 나가다가 CSS를 로드하는 link 태그나 style 태그를 만나면 DOM 생성을 일시 중단합니다. 그리고 link 태그의 href 어트리뷰트에 지정된 CSS 파일을 서버에 요청하여 로드한 CSS 파일이나 style 태그 내의 CSS를 해석하여 CSSOM(CSS Object Model)을 생성합니다. 이후 CSS 파싱을 완료하면 HTML 파싱이 중단된 지점부터 다시 파싱하기 시작해서 DOM을 생성합니다.</p>
<ol start="4">
<li>렌더 트리 생성</li>
</ol>
<p>렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 각각 DOM과 CSSOM을 생성합니다. 그리고 DOM과 CSSOM은 렌더링을 위해 렌더 트리로 결합됩니다. 렌더 트리는 돔 트리의 일종이지만 완전히 동일하진 않습니다. 렌더 트리는 스타일 정보를 알고 있어서 만약 당신이 <code>div</code>을 <code>display: none;</code>으로 숨겼다면 이는 렌더 트리에 표현되지 않습니다. 즉, 화면에 렌더링되지 않는 노드들은 포함되지 않습니다.</p>
<p>완성된 렌더 트리는 각 HTML 요소의 레이아웃(위치와 크기)을 계산하는 데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력됩니다.</p>
<h2 id="📌-reflow--repaint">📌 Reflow &amp; Repaint</h2>
<p>리플로우는 문서의 일부 또는 전체를 다시 렌더링할 목적으로 문서에 있는 요소의 위치와 형상을 다시 계산하는 것을 말합니다. 가끔 문서의 단일 요소를 리플로우 하려면 상위 요소와 그 뒤에 오는 모든 요소를 리플로우 해야할 수도 있습니다.</p>
<p>리페인트는 이름에서 알 수 있듯이 요소의 가시성에 영향을 주지만 레이아웃에는 영향을 미치지 않는 것들을 다시 그리는 것을 말합니다. (테두리, 색깔, 등을 말합니다.)</p>
<p>렌더 트리를 구성하는 데 사용된 입력 정보를 변경하면 리플로우, 리페인트 둘 중 하나 또는 모두 발생할 수 있습니다. 리플로우와 리페인트는 비용이 많이 들 수 있고 안좋은 사용자 경험을 줄 수 있으며 UI가 느려질 수 있습니다.</p>
<h2 id="📌-virtaul-dom-vs-real-dom">📌 Virtaul DOM VS Real DOM</h2>
<p>DOM이 변경될 때마다 브라우저는 CSS를 다시 계산해야 하며 레이아웃을 수행하고 웹 페이지를 다시 칠해야 합니다. (실제 돔에서는 이렇게 동작합니다.)</p>
<p>이 시간을 최소화하기 위해 Ember는 key/value observation 기술을 사용하고 앵귤러는 dirty checking을 사용합니다. 이 기술을 사용하면 바뀐 돔 노드만 업데이트 하거나, 앵귤러의 경우 더럽혀진 노드(바뀐 노드를 말합니다.)만 업데이트할 수 있습니다.</p>
<p>그러나, 브라우저는 화면을 다시 그리는데 걸리는 시간을 단축하기 위해 충분히 똑똑해졌습니다. 시간을 단축하는 것은 별로 중요해지지 않게 됐습니다. 이제는 리페인트를 발생하는 돔의 변화를 최소화하고 하나로 묶어서 처리하는 것이 중요해졌습니다.</p>
<p>DOM 변화를 줄이고 일괄 처리하는 전략은 바로 virtual dom에 있습니다.</p>
<h2 id="📌-virtual-dom은-정말-빠른가요">📌 Virtual DOM은 정말 빠른가요?</h2>
<p>react는 실제 DOM의 복사본을 메모리에 저장합니다. DOM을 수정하면 먼저 이러한 변경 사항을 메모리 내 DOM에 적용합니다.(이것 또한 가상돔입니다.) 그런 다음 diffing 알고리즘으로 변화한 부분을 파악합니다. 마지막으로, 변경 사항을 <strong>일괄 처리</strong>하고 호출을 통해 <strong>한 번에</strong> 실제 돔에 적용합니다. 따라서, 리플로우와 리페인트를 최소화합니다.</p>
<p>그렇다면, virtual dom은 정말 빠를까요?
virtual dom이 real dom과 비교했을 때 절대적으로 빠른 것은 아닙니다. 단지, <strong>변경 사항을 최소화하고 하나로 묶어 한 번에!!</strong> real-dom에 적용하는 것 뿐입니다.</p>
<h2 id="📋-reference">📋 Reference</h2>
<blockquote>
<ul>
<li>⌜모던 자바스크립트 딥 다이브⌟</li>
</ul>
</blockquote>
<ul>
<li><a href="https://ko.reactjs.org/docs/reconciliation.html">https://ko.reactjs.org/docs/reconciliation.html</a></li>
<li><a href="https://medium.com/swlh/what-the-heck-is-repaint-and-reflow-in-the-browser-b2d0fb980c08">https://medium.com/swlh/what-the-heck-is-repaint-and-reflow-in-the-browser-b2d0fb980c08</a></li>
<li><a href="https://medium.com/@gethylgeorge/how-virtual-dom-and-diffing-works-in-react-6fc805f9f84e">https://medium.com/@gethylgeorge/how-virtual-dom-and-diffing-works-in-react-6fc805f9f84e</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[웹팩]웹팩이란? | 웹팩이 하는 일과 필요한 이유]]></title>
            <link>https://velog.io/@kim-jaemin420/%EC%9B%B9%ED%8C%A9%EC%9B%B9%ED%8C%A9%EC%9D%B4%EB%9E%80-%EC%9B%B9%ED%8C%A9%EC%9D%B4-%ED%95%98%EB%8A%94-%EC%9D%BC%EA%B3%BC-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0</link>
            <guid>https://velog.io/@kim-jaemin420/%EC%9B%B9%ED%8C%A9%EC%9B%B9%ED%8C%A9%EC%9D%B4%EB%9E%80-%EC%9B%B9%ED%8C%A9%EC%9D%B4-%ED%95%98%EB%8A%94-%EC%9D%BC%EA%B3%BC-%ED%95%84%EC%9A%94%ED%95%9C-%EC%9D%B4%EC%9C%A0</guid>
            <pubDate>Tue, 06 Jul 2021 06:38:58 GMT</pubDate>
            <description><![CDATA[<h1 id="웹팩이-하는-일과-필요한-이유">웹팩이 하는 일과 필요한 이유</h1>
<h2 id="📌-웹펙이란">📌 웹펙이란?</h2>
<p>웹팩은 리액트를 배우고 있다면 다들 한 번쯤 들어봤을 프레임워크입니다. 그렇다면 웹팩은 무슨 일을 하는걸까요?</p>
<p>웹팩은 최신 프론트엔드 프레임워크에서 가장 많이 사용되는 모듈 번들러입니다. 웹펙에서 모듈이란, 웹 애플리케이션을 구성하는 모든 자원을 말합니다. HTML, CSS, Javascript, Images, Font 등 파일 하나하나가 모두 모듈입니다.</p>
<p>결국, 웹팩은 모듈을 번들링 해주는 <strong>모듈 번들러</strong>입니다. 그렇다면 모듈 번들링은 뭘까요?</p>
<h2 id="📌-모듈-번들링이란">📌 모듈 번들링이란?</h2>
<p>웹 애플리케이션을 구성하는 몇십, 몇백 개의 자원들을 하나의 파일로 병합 및 압축 해주는 동작을 모듈 번들링이라고 합니다.</p>
<p><img src="https://images.velog.io/images/kim-jaemin420/post/5f01d363-e740-47c6-a721-ee5752d72364/image.png" alt=""></p>
<h2 id="📌-웹팩의-등장-배경">📌 웹팩의 등장 배경</h2>
<p>웹팩이 등장한 이유는 크게 3가지입니다.</p>
<blockquote>
<ol>
<li>파일 단위의 자바스크립트 모듈 관리의 필요성</li>
<li>웹 개발 작업 자동화 도구</li>
<li>웹 애플리케이션의 빠른 로딩 속도와 높은 성능</li>
</ol>
</blockquote>
<h3 id="🔍-파일-단위의-자바스크립트-모듈-관리의-필요성">🔍 파일 단위의 자바스크립트 모듈 관리의 필요성</h3>
<p>자바스크립트의 변수 유효 범위는 기본적으로 전역 범위를 갖습니다. 이 때문에 서로 다른 모듈에서 같은 이름의 변수를 사용한다면, 서로가 서로를 의도치 않게 변경할 수 있습니다.</p>
<p>이렇다 보니 파일 단위로 변수를 관리하고 싶고 자바스크립트를 모듈화하고 싶어지는데, 이전까지는 AMD, CommonJS와 같은 라이브러리를 사용했습니다.</p>
<p>그렇다면 AMD, CommonJS와 웹팩의 차이점은 뭘까요?</p>
<h4 id="amd-vs-commonjs-vs-webpack">AMD VS CommonJS VS Webpack</h4>
<p>AMD 방식은 Requirejs, CommonJS 방식은 Browserify가 인기가 많았습니다. Requirejs는 콜백 함수를 통해 모듈들을 전달받는 구조였기 때문에 모듈이 많아질수록 관리하기가 힘들었고, Browserify는 node.js로 코드를 작성해야 했기 때문에 모듈이 많아지면 node.js를 다루는 상황이 많아져 모듈 관리를 하나로 유지하고 싶었습니다.</p>
<p>반면, 웹팩은 AMD와 CommonJS를 동시에 지원하고, 기본적으로 부분을 캐싱하여 변경점만 번들링하는 방식이기 때문에 속도가 빠르고 테스터 런너와의 연동도 훨씬 좋습니다.</p>
<h3 id="🔍-웹-개발-작업-자동화-도구">🔍 웹 개발 작업 자동화 도구</h3>
<p>이전에는 프론트엔드 개발 업무를 할 때 가장 많이 반복하는 작업은 코드를 수정하고 저장한 뒤 새로 고침을 누르는 것이었습니다. 그래야 화면에 변경된 내용을 볼 수 있었습니다.</p>
<p>이외에도 웹 서비스를 개발하고 웹 서버에 배포할 때 아래와 같은 작업들을 해야 했습니다.</p>
<ul>
<li>HTML, CSS, JS 압축</li>
<li>이미지 압축</li>
<li>CSS 전처리기 변환</li>
</ul>
<p>이러한 일들을 자동화 해주는 도구들이 필요했고, 이전에는 Grunt와 Gulp 같은 도구들이 등장했습니다.</p>
<h4 id="grunt-vs-gulp-vs-webpack">Grunt VS Gulp VS Webpack</h4>
<p>Grunt와 Gulp는 <code>Task Runner</code>이고, 웹팩은 <code>Package Bundler</code>입니다.</p>
<blockquote>
<p><code>Task Runner</code> : 반복 가능한 특정 작업을 자동화
<code>Package Bundler</code> : 종속성을 가진 애플리케이션 모듈을 정적인 소스로 재생산합니다.</p>
</blockquote>
<p>Gulp는 종속성 관리를 할 수 없지만 웹팩은 종속성 관리가 가능하고 이는 규모가 큰 프로젝트에서 빛을 발합니다. 또, 웹팩 커맨드를 실행할 때 <code>--watch</code> 옵션을 주면 변경 내용을 감시해서 적용할 수 있습니다.</p>
<h3 id="🔍-웹-애플리케이션의-빠른-로딩-속도와-높은-성능">🔍 웹 애플리케이션의 빠른 로딩 속도와 높은 성능</h3>
<p>일반적으로 5초 이내에 웹 사이트가 표시되지 않으면 대부분의 사용자들은 해당 사이트를 벗어나거나 집중력을 잃게 됩니다.</p>
<p>그래서 웹 사이트의 로딩 속도를 높이기 위해 많은 노력들이 있었습니다. 그 중 대표적인 방법이 브라우저에서 서버로 요청하는 파일 숫자를 줄이는 것입니다. 파일 숫자를 줄이기 위해 파일들을 압축하고 병합하는 작업을 진행했습니다.</p>
<p>뿐만 아니라 초기 페이지 로딩 속도를 높이기 위해 나중에 필요한 자원들은 나중에 요청하는 레이지 로딩이 등장했습니다.</p>
<p>웹팩은 기본적으로 자원이 필요할 때, 그 때 그 때 요청하자는 철할을 갖고 있습니다. 웹팩의 코드 스플리팅 기능을 이용하여 원하는 모듈을 원하는 타이밍에 로딩할 수 있습니다.</p>
<h2 id="📎-reference">📎 Reference</h2>
<blockquote>
<ul>
<li><a href="https://joshua1988.github.io/webpack-guide/guide.html">https://joshua1988.github.io/webpack-guide/guide.html</a></li>
</ul>
</blockquote>
<ul>
<li><a href="https://ui.toast.com/weekly-pick/ko_20160212">https://ui.toast.com/weekly-pick/ko_20160212</a></li>
<li><a href="https://kdydesign.github.io/2017/07/27/webpack/">https://kdydesign.github.io/2017/07/27/webpack/</a></li>
<li><a href="https://stackshare.io/stackups/grunt-vs-gulp-vs-webpack">https://stackshare.io/stackups/grunt-vs-gulp-vs-webpack</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>