<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>chan_snk.log</title>
        <link>https://velog.io/</link>
        <description>FE개발자</description>
        <lastBuildDate>Wed, 18 Dec 2024 18:18:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>chan_snk.log</title>
            <url>https://velog.velcdn.com/images/chan_snk/profile/8dc9ae51-3091-4be2-8c04-59b5b134f2cd/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. chan_snk.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/chan_snk" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Heap (최소 힙)]]></title>
            <link>https://velog.io/@chan_snk/Heap-%EC%B5%9C%EC%86%8C-%ED%9E%99</link>
            <guid>https://velog.io/@chan_snk/Heap-%EC%B5%9C%EC%86%8C-%ED%9E%99</guid>
            <pubDate>Wed, 18 Dec 2024 18:18:14 GMT</pubDate>
            <description><![CDATA[<h1 id="✨들어가면서">✨들어가면서...</h1>
<p>하루에 한 문제 이상 코테문제를 풀려고 도전중인데 처음으로 heap에 관한 문제로 들어오게됐다.</p>
<p>heap이란 무엇인지..? 어떤 장점이 있어서 언제 사용해야하는지, 또한 어떻게 구현해야 하는지 알아보자.</p>
<p>heap에 관해서 하나도 모르는 상태에서 첫 문제를 풀기까지 알아봤던 과정대로 설명해보려 한다.</p>
<h1 id="heap">Heap</h1>
<p><strong>heap 이란?</strong></p>
<blockquote>
<p><strong>힙(Heap)</strong>은 <strong>완전 이진 트리(Complete Binary Tree)</strong>의 일종으로, 특정 조건을 만족하는 자료구조이다. 힙의 종류에는 <strong>최소 힙(Min Heap)</strong>과 <strong>최대 힙(Max Heap)</strong>이 있으며, 그 차이는 부모 노드와 자식 노드 간의 값 관계에 있다.</p>
</blockquote>
<p>우선 heap에 관한 설명을 찾아본 결과 다음과 같은 정보를 얻을 수 있었다.</p>
<p>완전이진트리..? 최소 힙? 최대 힙? 
이것들이 뭔지 모른채로 문제를 풀 수 없기에 하나씩 알아보자.</p>
<p><strong>완전 이진트리?</strong></p>
<blockquote>
<p><strong>완전 이진트리란</strong>, 모든 레벨이 가득 차 있어야 하며, 마지막 레벨을 제외한 모든 레벨이 왼쪽부터 차례대로 채워진 이진 트리를 의미한다. 
즉, 트리의 마지막 레벨에서만 빈 노드가 있을 수 있으며, 그 빈 노드들은 오른쪽부터 차례대로 빈 자리가 나온다.</p>
</blockquote>
<p>힙은 항상 완전이진트리 형태로 구성되어 있어야 하며, 이를 통해 빠른 삽입과 삭제가 가능하다.</p>
<p>이렇게 이진트리로 이루어졌기 때문에 heap자료구조에서 시간복잡도는 O(logN)을 유지할 수 있다고 한다.</p>
<p><code>어떻게 O(logN)을 유지하지..? 라는 궁굼증이 여기서 생겼는데 뒤에서 자세히 알아보자.</code></p>
<p><strong>최소 힙, 최대 힙에 대해 알아보기 전에...</strong></p>
<ul>
<li>힙 자료구조의 동작원리<blockquote>
<p>힙 자료구조는 기본적으로 <strong>삽입(Insert)</strong>과 <strong>삭제(Extract)</strong> 연산을 통해 동작한다. 이때, <strong>최소 힙(Min Heap)</strong>과 <strong>최대 힙(Max Heap)</strong>의 동작 원리는 부모 노드와 자식 노드 간의 값을 비교하는 방식에서 차이가 난다.</p>
</blockquote>
</li>
</ul>
<p>삽입과 삭제를 해야하는 문제가 있다? ==&gt;heap을 떠올려 볼 수 있을것 같다.</p>
<p>어떻게 삽입, 삭제하길래 heap자료구조를 사용하는 걸까?</p>
<p><strong>최소 힙</strong></p>
<p>heap자료구조는 위에서 봤던것 처럼 완전 이진트리 구조로 되어있다.</p>
<p>그렇다면  <strong>최소 힙은?</strong> </p>
<blockquote>
<p>부모 노드의 값이 항상 자식 요소의 값보다 작은 tree구조이다.
따라서 heap의 <strong>루트 노드(최상위 노드)</strong> 는 항상 가장 작은 값을 가지게 된다.</p>
</blockquote>
<ul>
<li><p>값을 삽입할 경우</p>
<blockquote>
<p>값을 삽입할 때는, <strong>새로운 값을 리프 노드(가장 마지막)</strong>에 추가한 뒤, 부모와 비교하면서 위로 올라가며<strong>(Heapify Up)</strong> 힙의 특성을 유지한다.</p>
</blockquote>
</li>
<li><p>값을 삭제할 경우</p>
<blockquote>
<p>최소 힙에서 삭제하는 연산은 항상 <strong>루트 노드의 값을 삭제</strong>하는 방식이다. 삭제된 후, 트리의 구조를 다시 힙 속성에 맞게 재조정해야 한다. 이 과정은 <strong>Heapify Down</strong>이라고 불립니다.</p>
</blockquote>
</li>
</ul>
<p>삽입, 삭제, 두가지 경우 모두 <strong>부모 노드 &lt;==&gt; 자식 노드들</strong> 간의 비교를 반복하며 이뤄진다는 공통점이 있다.</p>
<p><strong>삽입만 예를 들어보자면</strong></p>
<p> 예시
 현재 힙: [10, 15, 20, 17] </p>
<pre><code>      10
    /   \
  15     20
 /
 17</code></pre><p>삽입 값: 5
새로운 상태: 5는 리프 노드로 삽입되고, 부모인 10과 비교하여 위치를 바꿔 올라간다. 
결과적으로:</p>
<pre><code>    5
   / \
 10   20
 /      \
15      17</code></pre><p>앞서 언급한 것처럼 <strong>부모 노드 &lt;==&gt; 자식 노드들</strong>의 비교만 위쪽 방향으로 계속해서 수행을 하게되고 이로 인해** O(logN)이라는** 비교적 효과적인 시간 복잡도를 얻을 수 있는 것이다.</p>
<p><strong>최대 힙</strong></p>
<p>최소 힙과 반대되는 구조로 생각하면 되기때문에 생략한다..</p>
<p><strong>Heapify Down , Heapify Up</strong></p>
<p>앞서 살펴본 내용을 바탕으로 정리를 해보면</p>
<blockquote>
<ul>
<li>1.heap은 이진 트리구조</li>
</ul>
</blockquote>
<ul>
<li><ol start="2">
<li>최소 힙, 최대 힙 이 존재한다.</li>
</ol>
</li>
<li><ol start="3">
<li>삽입과 삭제과정은 부모노드 &lt;==&gt; 자식노드의 비교로 순차적으로 이뤄진다.</li>
</ol>
</li>
</ul>
<p>3번의 과정을 수행하는 동작이 바로 <strong>heapifyDown , heapifyUp</strong> 이다.</p>
<pre><code>     1
   /  \
  2    3</code></pre><p> 각 레벨에서 다음과 같은 삼각형 형태의 부모, 자식 노드의 비교가 이뤄지게 되는데 그것을 수행하는 것을 <strong>heapifyDown , heapifyUp</strong> 이라고 한다.</p>
<blockquote>
<p><strong>heapifyDown</strong> : <strong>삭제 과정</strong>에서 최상위 노드를 제거 후 마지막 요소를 최상위 요소에 배치후, 아래로 <strong>순차적으로 부모&lt;==&gt;자식</strong> 노드의 비교가 이뤄짐</p>
</blockquote>
<blockquote>
<p><strong>heapifyUp</strong>: <strong>삽입 과정</strong>에서 리프 노드부터 위쪽 방향으로 순차적으로 부모&lt;==&gt;자식 노드의 비교가 이뤄짐</p>
</blockquote>
<p><strong>우선순위 큐</strong></p>
<p>구현 전에 마지막으로 알아야 할 것이 <strong>우선순위 큐</strong>이다.</p>
<p>일반적으로 <strong>큐</strong> 라고한다면 선입선출(LIFO)구조를 가진 것으로 알고있었는데 우선순위 큐는 무엇이지??</p>
<blockquote>
<p>우선순위 큐는 일반적인 큐와는 다르게, 큐에 삽입된 각 요소가 우선순위를 가짐에 따라 처리 순서가 결정된다. 
기본 큐는 선입선출(FIFO) 방식에 따라 먼저 들어온 요소부터 처리하지만, 
우선순위 큐에서는 우선순위가 높은 요소부터 먼저 처리된다. 이때 우선순위는 숫자나 값에 따라 정해지며, 우선순위 큐에 삽입된 항목들은 우선순위에 맞게 정렬되거나, 우선순위가 높은 항목이 가장 먼저 꺼내진다.</p>
</blockquote>
<p>여기서 드는 의문은 <strong>우선순위 큐 와 힙은 무슨 관계가</strong> 있냐는 것이다.</p>
<p><strong>우선순위 큐는 우선순위에 따라</strong>서 어떤 일을 수행하게 되고 이러한 동작이 
<strong>최대 힙 or 최소 힙의</strong> 자료구조와 딱 맞아 떨어진다는 것이다.</p>
<p>즉, 우선순위 큐는 힙 자료구조를 사용하여 구현하는 것이 가장 적합하다.</p>
<h1 id="js로-구현-해보자최소-힙">JS로 구현 해보자(최소 힙)</h1>
<p>코테 문제를 풀기위해서 검색해본 결과 js에서는 heap자료구조를 직접 구현해야 한다고 했다. 앞선 설명들을 바탕으로 직접 구현 해보자.</p>
<pre><code class="language-js">const createMinHeap = () =&gt; {
  const heap = []

  // 부모 인덱스 계산
  const getParentIndex = (index) =&gt; Math.floor((index - 1) / 2)
  // 왼쪽 자식 인덱스 계산
  const getLeftChildIndex = (index) =&gt; index * 2 + 1
  // 오른쪽 자식 인덱스 계산
  const getRightChildIndex = (index) =&gt; index * 2 + 2

  // 값을 삽입하고 최소 힙을 유지
  const insert = (value) =&gt; {
    heap.push(value)
    heapifyUp()
  }
  const swap = (el1, el2) =&gt; ([heap[el1], heap[el2]] = [heap[el2], heap[el1]])
  // 최소값을 추출하고 최소 힙을 유지
  const extractMin = () =&gt; {
    if (heap.length === 0) return null
    if (heap.length === 1) return heap.pop()

    const min = heap[0]
    heap[0] = heap.pop() // 마지막 값을 루트로 이동
    heapifyDown()
    return min
  }

  // 삽입 시 힙 속성 유지
  const heapifyUp = () =&gt; {
    let index = heap.length - 1
    while (index &gt; 0) {
      const parentIndex = getParentIndex(index)
      if (heap[index] &gt;= heap[parentIndex]) break
      swap(index, parentIndex)
      index = parentIndex
    }
  }

  // 추출 시 힙 속성 유지
  const heapifyDown = () =&gt; {
    let index = 0
    const length = heap.length

    while (getLeftChildIndex(index) &lt; length) {
      const leftChildIndex = getLeftChildIndex(index)
      const rightChildIndex = getRightChildIndex(index)
      let smallerChildIndex = leftChildIndex

      if (
        rightChildIndex &lt; length &amp;&amp;
        heap[rightChildIndex] &lt; heap[leftChildIndex]
      ) {
        smallerChildIndex = rightChildIndex
      }

      if (heap[index] &lt;= heap[smallerChildIndex]) break
      swap(index, smallerChildIndex)
      index = smallerChildIndex
    }
  }

  return { insert, extractMin, heap }
}

</code></pre>
<h3 id="1-최소-힙을-정의-해주자">1. 최소 힙을 정의 해주자.</h3>
<pre><code class="language-js">const createMinHeap = ()=&gt;{
  const heap = [] // 자료들을 넣기위한 heap 정의
  const insert = ()=&gt;{} // 자료들의 삽입을 담당
  const extractMin = ()=&gt;{} // 자료들의 삭제를 담당
  const heapifyDown = ()=&gt;{} // 최소힙 구조를 유지시켜줄 함수
  const heapifyUp = ()=&gt; {} // 최소힙 구조를 유지시켜줄 함수
  const 나머지 필요한 함수들 ()=&gt;{}
}</code></pre>
<p>최소힙을 생성해줄 함수를 정의하고 해당 함수의 구성요소는 위와 같다.</p>
<p>앞서 설명한 것처럼 <strong>insert 과정에는 heapifyUp(위로 올라가면서 비교)</strong> 함수가 실행되며 최소 힙 구조를 유지해줄 것이고,</p>
<p>*<em>extarct 과정에는 heapifyDown(아래로 내려가며 비교) *</em>함수가 실행되며 최소 힙 구조를 유지해 줄 것이다.</p>
<h3 id="2-heapifyup삽입">2. heapifyUp(삽입)</h3>
<pre><code class="language-js">const heapifyUp = ()=&gt;{
  let index = heap.length -1 // 삽입과정은 리프 노드부터 시작하기 때문

  while(index &gt; 0){
   const parentIndex =  getParentIndex(index) // Math.floor((index - 1) / 2)
   if(heap[index] &gt;= heap[parentIndex]) break
   // 자식요소가 부모요소보다 크다면 최소힙 만족 그자리에 위치

   swap(index,parentIndex) // swap을 통해 두 노드의 값을 교환 
   index = parentIndex 
   // 최소 힙 조건을 만족하지 못했다면 index를 부모 인덱스로 교환후 while문 재 실행 
  }
}
</code></pre>
<p>삽입 과정에서 일어나는 heapifyUp과정이다 </p>
<p>heap구조의 리프노드에 새로운값을 넣어주고 부모 노드와의 비교를 이어가며 최소 힙 구조를 만족시켜준다.</p>
<p>이렇게 정의한 heapifyUp함수는 삽입 과정이기에 insert() 내부에서 호출만 해주면된다.</p>
<h3 id="3insert">3.insert</h3>
<pre><code class="language-js"> const insert = (value) =&gt; {
    heap.push(value) // 새로운 값을 배열의 마지막(리프노드)에 삽입
    heapifyUp() // heapifyUp 과정을 통해 최소 힙 조건 만족
  }

</code></pre>
<p>새로운 요소를 삽입(리프 노드에) 할 때마다 앞서 정의한 heapifyUp()을 실행 시켜주면서 최소 힙 구조를 유지한다.</p>
<h3 id="4heapifydown">4.heapifyDown</h3>
<pre><code class="language-js">const heapifyDown = () =&gt; {
    let index = 0 // 삭제 과정은 최상위 노드 부터 실행된다.
    const length = heap.length

    while (getLeftChildIndex(index) &lt; length) {
   // 현재 노드의 왼쪽 자식 노드가 힙의 범위 내에 있는지 확인
  // 왼쪽 자식이 존재하면 계속해서 힙의 구조를 유지하기 위한 비교 작업을 진행

      const leftChildIndex = getLeftChildIndex(index) //전체코드에 나와있음
      const rightChildIndex = getRightChildIndex(index)
      let smallerChildIndex = leftChildIndex

      if (
        rightChildIndex &lt; length &amp;&amp;
        heap[rightChildIndex] &lt; heap[leftChildIndex]
      ) {
        smallerChildIndex = rightChildIndex
      }

      if (heap[index] &lt;= heap[smallerChildIndex]) break
      swap(index, smallerChildIndex)
      index = smallerChildIndex
    }
  }
</code></pre>
<p>heapifyUp과 비교해서 조금 복잡해 보일 수 있지만 코드를 따라가보면 앞서 설명한 개념과 일치한 동작을 확인 할 수있다.</p>
<h3 id="5extractmin">5.extractMin</h3>
<pre><code class="language-js">
  const extractMin = () =&gt; {
    if (heap.length === 0) return null
    if (heap.length === 1) return heap.pop()

    const min = heap[0]
    heap[0] = heap.pop() // 마지막 값을 루트로 이동
    heapifyDown()
    return min
  }</code></pre>
<p>앞서 본것 처럼 삭제 과정에는 heapifyDown() 함수를 실행해서 최소 힙 자료구조를 유지해 준다.</p>
<h1 id="마무리">마무리</h1>
<p>이렇게 힙 자료구조에 대해 알아보았다.
힙은 <strong>최소값</strong> 또는 <strong>최대값을</strong> 빠르게 추출할 수 있는 특성 덕분에 우선순위 큐와 같은 상황에서 유용하다.</p>
<p>최소 힙은 <strong>최소값을 먼저 처리해야 할 때</strong>, 최대 힙은 <strong>최대값을 먼저 처리해야 할 때</strong> 적합하다. 또한, K개의 최소값/최대값을 구하는 문제, 힙 정렬, 실시간 데이터 스트림 처리 등에서 효율적으로 활용된다.</p>
<p>이러한 특성을 기억하고, 필요할 때 적합한 힙 자료구조를 적용하면 문제 해결에 큰 도움이 될 것같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Next.js 더 잘 사용해보기(Routing)]]></title>
            <link>https://velog.io/@chan_snk/Next.js-%EB%8D%94-%EC%9E%98-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0Routing</link>
            <guid>https://velog.io/@chan_snk/Next.js-%EB%8D%94-%EC%9E%98-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0Routing</guid>
            <pubDate>Sun, 25 Aug 2024 10:24:51 GMT</pubDate>
            <description><![CDATA[<h1 id="🚕들어가면서">🚕들어가면서...</h1>
<p>처음으로 Next.js로 프로젝트를 만들면서 폴더구조를 관리하는데 햇갈리는 부분이 생겼다.</p>
<p>그동안 React로만 프로젝트를 진행했었는데 React는 폴더구조를 내마음대로 만들어도 전혀 문제가 되는 부분이 없었는데 Next.js로 프로젝트를 만들다 보니 문제점이 생겼다.</p>
<p>바로 <strong>폴더구조를 어떻게 관리해야 효율적으로 만들 수 있을까?</strong> 이다.</p>
<p>Next.js를 잘 사용하기 위해서 routing 방식을 잘 이해하면 폴더구조도 설계하기 쉽지 않을까 생각한다.</p>
<p>이번 글에서는 <strong><code>파일 기반 라우팅</code> , <code>동적 라우팅</code></strong> 은 많이 사용해 봤을것 같지만 
<strong><code>인터셉팅 라우팅(intercepting routing)</code> , <code>병렬 라우팅(parallel routing)</code></strong> 은 생소할 것 같아서 이에 대해 다뤄보려고 한다.</p>
<h1 id="🚩preview">🚩Preview</h1>
<h3 id="🎈파일-기반-라우팅">🎈파일 기반 라우팅</h3>
<p>Next.js프로젝트를 만들어 봤다면 익숙한 방식일 것이다.</p>
<blockquote>
</blockquote>
<pre><code class="language-tsx">|-- app
|    |-- example
|    |      |- page.tsx  </code></pre>
<p>위와 같은 폴더 구조를 생성하면, <code>/example</code> 경로로 이동했을 때 <code>example &gt; page.tsx</code> 페이지가 렌더링된다.</p>
<h3 id="🎈동적-라우팅dynamic-routing">🎈동적 라우팅(dynamic routing)</h3>
<p>파일 기반 라우팅 방식에서 조금더 나아가서** 동적으로 생성되는 URL**에 의해 페이지 구성이 달라진다면? </p>
<p>이때 사용할 수 있는방식이 <strong>동적 라우팅(dynamic routing)</strong> 이다.</p>
<blockquote>
</blockquote>
<pre><code class="language-tsx">|-- app
|    |-- shop
|    |      |- [shopId]
|    |             |- page.tsx</code></pre>
<p>위와 같이 shop 폴더 아래에 [shopId]라는 폴더를 생성하면 해당 경로는 동적인 URL 파라미터를 받을 수 있게된다.</p>
<p>이 경우 [shopId] 폴더 아래에 위치한 page.tsx 컴포넌트는 params라는 객체를 props로 받아, URL에서 전달된 파라미터 값을 사용할 수 있다.</p>
<p>params 객체 안에는 동적 라우팅 폴더 이름과 동일한 키 값이 포함되어 있으며, 이를 통해 URL에서 동적으로 생성된 값을 컴포넌트 내에서 사용할 수 있다.</p>
<p>예를 들어, /shop/1, /shop/2와 같은 경로로 접근할 때, [shopId]는 각각 1, 2 등의 값으로 해석된다.</p>
<blockquote>
</blockquote>
<pre><code class="language-tsx">// URL에 shop/1 을 입력했다면??
type ShopPageProps = {params : {shopId : string }}
export default function Shop({params}:ShopPageProps){
  console.log(params.shopId) // &quot;1&quot;
}</code></pre>
<h1 id="🔎병렬-라우팅-parallel-routing">🔎병렬 라우팅 (parallel routing)</h1>
<p>Next.js에서는 <code>layout.tsx</code> 파일을 사용할 수 있고 <code>layout.tsx</code>은 상위 layout과 중첩된다.</p>
<p>프로젝트를 진행하던 layout에 <code>header컴포넌트</code>를 배치하고 사용하다가 문제가 발생했었다.</p>
<p>많은 페이지들에서 header컴포넌트를 사용하기에 layout에 header를 배치했는데 특정 페이지에서는 header가 필요 없을 수 있다는 것이었다.</p>
<p>이를 해결하기위해 layout의 유무에 따라 폴더를 그룹화하고 관리하는 방식을 고려했었다. 그러나 폴더의 깊이가 점점 깊어지면서 관리가 복잡해질 것 같다는 생각이 들었다.</p>
<p>이러한 문제를 해결하기 위해 <strong>병렬 라우팅(parallel routing)</strong>을 알게 되었다.</p>
<h3 id="병렬-라우팅에-대한-이해">병렬 라우팅에 대한 이해</h3>
<p><a href="https://nextjs.org/docs/app/building-your-application/routing/parallel-routes">Next.js 공식문서에는</a> 다음과 같이 나오는데 </p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/5cd6808f-f235-48cf-9df3-e36addee5d54/image.png" alt=""></p>
<p><strong>공식문서에서는 다음과 같이 설명하고 있다.</strong></p>
<blockquote>
<p><strong>병렬 라우트(Parallel Routes)</strong>는 동일한 레이아웃 내에서 하나 이상의 페이지를 동시에 또는 조건에 따라 렌더링할 수 있게 해줍니다. 이는 대시보드나 소셜 사이트의 피드와 같이 매우 동적인 섹션에서 유용합니다.</p>
</blockquote>
<p>예를 들어, 대시보드를 고려해 보았을 때, 병렬 라우트를 사용하면 팀 페이지와 분석 페이지를 동시에 렌더링할 수 있습니다.</p>
<p>@가 붙은 폴더를 <strong><code>slot</code></strong>이라고 하는데 *<em>slot은 *</em> 가장 가까운 부모 layout에 props로 전달 될 수 있다고 공식문서에서 설명하고 있다.</p>
<blockquote>
<p><strong>💥Info</strong></p>
</blockquote>
<ul>
<li>slot은 경로에는 포함되지 않는다.</li>
<li>@team &gt; team &gt; page.tsx 경로를 예시로 보면
실제 URL은 /team 으로 작성되며 이때 page컴포넌트를 보여주게 된다.</li>
</ul>
<pre><code class="language-tsx">export default function Layout({
  children,
  team,
  analytics,
}: {
  children: React.ReactNode
  analytics: React.ReactNode
  team: React.ReactNode
}) {
  return (
    &lt;&gt;
      {children}
      {team}
      {analytics}
    &lt;/&gt;
  )
}
</code></pre>
<p><strong>주의 할 점!</strong></p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/f2b5a9c8-98eb-4403-a2a3-4e1325776a3e/image.png" alt=""></p>
<p>병렬 라우팅을 사용할 경우 <code>default.tsx</code> 파일을 생성해 줘야한다.</p>
<p>default파일은 해당 페이지가 render되기 전 혹은 해당 페이지가 없을 경우 보여줄 fallback페이지다.</p>
<p>만약 default파일이 없을 경우 404페이지로 이동되니 주의해야한다.!</p>
<p>fallback페이지를 따로 사용자에게 보여줄 필요가 없다면 </p>
<pre><code class="language-tsx">export default function Default(){
  return null    // null 을 return하는 방식을 사용
}</code></pre>
<p>단순히 null을 return해줘도 된다.</p>
<h3 id="언제-사용해야-하지">언제 사용해야 하지?</h3>
<p>공식문서를 읽었을때 언제 사용하면 좋을지 판단하기까지 조금 애매했다.</p>
<p><strong>왜</strong> 사용해야 하는지에 대한 설명이 자세하게 안나와 있었기 때문이다.</p>
<p>병렬 라우팅은 언제 사용하면 좋을지 생각해본 결과는 다음과 같다.</p>
<blockquote>
<ul>
<li>한 페이지에서 2개 이상의 api를 불러오며 페이지를 구성하는 경우</li>
</ul>
</blockquote>
<ul>
<li>동일한 layout에서 조건에 따라서 rendering 해야할 경우</li>
<li>modal을 사용하는 경우(인터셉팅 라우팅 방식과 같이사용)</li>
</ul>
<p>*<em>🎉병렬 api호출 *</em></p>
<p><a href="https://min-kyung.tistory.com/200">병렬 라우팅 방식을 사용해서 성능개선</a>한 글을 봤었는데 왜 병렬 라우팅을 사용하면 좋은지 자세하게 설명해주고 있다.</p>
<p>가장 좋은 점은 api를 각 페이지 컴포넌트에서 다룰 수 있다는 것이었다.</p>
<p>만약 병렬 라우팅을 사용하지 않고 한번에 api를 처리하게 되면 하나의 api호출에서 에러가 발생할 경우 해당 에러가 페이지 전체로 퍼지게 된다.</p>
<p>그렇다면 page전체를 사용자가 볼 수 없게되는 문제점이 생기는데 </p>
<p>병렬 라우팅을 사용해서 페이지 단위로 api를 다룬다면 하나의 api가 생겨도 문제되는 컴포넌트만 error ui를 제공해주고 나머지 구성요소들은 정상동작하기 때문에** 사용자 경험을 향상 시킬것이다**.</p>
<p>또한 api를 병렬로 요청하기 때문에 복잡한 api를 불러올 경우 더 빠른 로딩을 할 수 있다.</p>
<p>*<em>🎉같은 layout에서 조건부 render *</em></p>
<p>특정 조건에서만 보여줘야 할 컴포넌트가 있다면 병럴 라우팅을 사용해서 처리하기 좋을 것 같다.</p>
<p>만약 비로그인 사용자일 경우에만 header를 보여주지 말도록 코드를 작성해야할 경우를 생각해보면 적절한 예시일 것 같다.</p>
<h1 id="🔎인터셉팅-라우팅-intercepting-routing">🔎인터셉팅 라우팅 (intercepting routing)</h1>
<h3 id="인터셉팅-라우팅에-대한-이해">인터셉팅 라우팅에 대한 이해</h3>
<p>공식문서에서 intercepting routing에 대해 다음과 같은 설명을 한다.</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/378e9012-1dac-4078-a11a-9150a1f625a3/image.png" alt=""></p>
<blockquote>
<p>인터셉팅 라우팅(Intercepting routes)은 현재 레이아웃 내에서 애플리케이션의 다른 부분에서 라우트를 불러올 수 있게 해주는 라우팅 패러다임입니다. 이 방식은 사용자가 다른 컨텍스트로 전환하지 않고도 라우트의 콘텐츠를 표시하고자 할 때 유용합&gt;니다.</p>
<p>예를 들어, 피드에서 사진을 클릭했을 때, 사진을 모달로 표시하여 피드 위에 오버레이할 수 있습니다. 이 경우, Next.js는 /photo/123 라우트를 인터셉트하고 URL을 숨기며, 이를 /feed 위에 오버레이합니다.</p>
</blockquote>
<p>공식문서에서는 overlay를 예시로 알려줘서 처음에 좀 혼동이 있었다.
결국 인터셉팅 라우팅을 통해 특정 url접근시 overlay로 보여주는 동작 방식으로 많이 사용할 것이지만 개념에 대해 이해해 보자면 다음과 같다.</p>
<pre><code class="language-tsx">|-- app
    |-- main
        |-- page.tsx
        |-- (..sub)  // 이 부분이 인터셉팅 라우팅을 설정하는 곳
    |-- sub
        |-- page.tsx
    |-- min
        |-- page.tsx
</code></pre>
<p>다음과 같은 폴더구조를 가진 프로젝트라면</p>
<blockquote>
<ul>
<li>main경로에서 sub경로로 이동할 땐 url을 인터셉팅</li>
</ul>
</blockquote>
<ul>
<li>min경로에서 sub로 이동할 땐 실제 sub페이지로 이동</li>
</ul>
<p><strong>사용 방법</strong></p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/429fe566-82e8-41c2-8a03-86298c46309a/image.png" alt=""></p>
<p><strong><code>.</code></strong> 을 사용해서 폴더구조를 생성하는데 
<img src="https://velog.velcdn.com/images/chan_snk/post/44574f11-a1a6-472b-9619-acf920b34301/image.png" alt=""></p>
<p>다음과 같은 방식으로 폴더구조를 생성한다.
(.)이 포함된 폴더 아래는 가로채고 싶은 페이지의 컴포넌트와 같은 폴더구조로 생성해주면 된다.</p>
<h3 id="참고영상">참고영상</h3>
<p>예시를 돕기위해 영상을 첨부해본다.</p>
<p><strong>URL에 직접 경로를 입력해서 이동하는 경우</strong></p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/a9c522af-688f-49d2-8a6d-3cc5cd6ca51b/image.gif" alt=""></p>
<p><strong>인터셉팅 라우팅 적용</strong></p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/095d6637-f273-4bce-bd6c-42163c10e3a7/image.gif" alt=""></p>
<blockquote>
<p><strong>URL을 확인해 보면 둘다 URL이 변경됐지만 보여지는 ui가 다른걸 확인 할 수 있다.</strong></p>
</blockquote>
<h3 id="언제-사용해야-하지-1">언제 사용해야 하지?</h3>
<p>공식 문서에서 언급한 것처럼, 인터셉팅 라우팅은 주로 모달 형식으로 페이지를 표시할 때 효과적이다. </p>
<p>이 방식은 특정 콘텐츠를 현재 페이지 위에 오버레이로 렌더링할 수 있게 해주며, 사용자 경험을 향상시킬 수 있다.</p>
<p>예를 들어 사용자가 피드에서 사진을 클릭했을 때, 사진을 모달로 띄워서 보여주는 방식이 이에 해당한다.</p>
<p> 이 경우, URL은 /photo/123이지만 페이지는 모달 형태로 오버레이되어 보여진다.
 사용자는 여전히 피드 페이지를 보고 있으며, 사진을 모달로 열어볼 수 있다.</p>
<p>또한, <strong>병렬 라우팅(parallel routing)</strong>과 결합하면 더욱 강력한 효과를 볼 수 있다.
병렬 라우팅을 사용하면 여러 페이지를 동시에 렌더링할 수 있기 때문에 </p>
<p>예를 들어 대시보드에서 사이드바와 메인 콘텐츠를 병렬로 렌더링하면서도, 모달을 사용하여 상세 정보를 오버레이로 띄우는 것이 가능하다.</p>
<h1 id="내-의견">내 의견</h1>
<p>병렬 라우팅, 인터셉팅 라우팅에 대해 알아보면서 이런 생각을 했다.</p>
<blockquote>
<p>_ &quot;굳이 사용안해도 비슷하게 동작시킬 수 있는거 아닌가?&quot;_</p>
</blockquote>
<p><strong>왜 사용하는가</strong>에 대해서 고민해봤는데 Next.js에서 그냥 만들어둔게 아니구나... 라는 생각을 했다.</p>
<p><strong>병렬 라우팅</strong></p>
<p>한페이지에서 보여질 컴포넌트를 독립적으로 처리함으로써 개발과정에서 복합적인 case를 생각할 필요를 줄여줄 수 있다고 생각한다.</p>
<p>또한 성능면에서도 병렬로 api를 다루기에 <strong>UX DX향상에</strong> 도움을 줄 수 있는 도구 아닌가 생각한다.</p>
<p><strong>인터셉팅 라우팅</strong></p>
<p>병렬 라우팅에 모달을 적용해서 사용한다면 굉장히 효과적일 것같다.</p>
<p> 사용자가 특정 행동을 했을 때 새로운 페이지로 전환하는 대신, 현재 페이지에서 오버레이로 추가 정보를 표시할 수 있다. UX면에서 더 좋은 경험일 것이다.</p>
<p>또한 확장성 면에서 효과적이라고 생각한다.</p>
<p>개발과정중에 이러한 고민을 하게되는데</p>
<blockquote>
<p>해당 페이지는 다른곳에서 모달로 보여주면 좋을것 같은데?
혹은 , 해당 모달은 나중에 기능이 커지면 페이지로 전환할 여지가 있지 않을까?</p>
</blockquote>
<p>위의 도구들을 사용한다면 페이지와 모달의 경계를 생각하지 않고 작업해도 변형하기 좋은 형태기 때문에 DX향상에 도움이 되지않을까 싶다.</p>
<h1 id="참고자료">참고자료</h1>
<ul>
<li><a href="https://min-kyung.tistory.com/200">Next.js의 parallel route를 이용하여 페이지 성능 개선하기</a></li>
<li><a href="https://www.youtube.com/watch?v=Ft2qs7tOW1k">Next.js Modal with Parallel &amp; Intercepting Routes, shadcn/ui Dialog</a></li>
<li><a href="https://nextjs.org/docs/app/building-your-application/routing/intercepting-routes">Next.js 공식문서</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[NewTips회고(문제를 해결해보자)]]></title>
            <link>https://velog.io/@chan_snk/%ED%9A%8C%EA%B3%A0%EC%9D%B4%EB%9F%B0%EA%B2%83%EB%93%A4%EC%9D%84-%EC%A0%81%EC%9A%A9</link>
            <guid>https://velog.io/@chan_snk/%ED%9A%8C%EA%B3%A0%EC%9D%B4%EB%9F%B0%EA%B2%83%EB%93%A4%EC%9D%84-%EC%A0%81%EC%9A%A9</guid>
            <pubDate>Fri, 16 Aug 2024 16:50:36 GMT</pubDate>
            <description><![CDATA[<h1 id="🚗들어가면서">🚗들어가면서...</h1>
<p>약 두달의 기간동안 NewTips라는 Project를 진행했다.</p>
<blockquote>
<p><strong>NewTips란??</strong></p>
</blockquote>
<ul>
<li>소비자, 판매자의 네일예약 과정에서 불편함을 해소해주기 위해 만든 서비스로
NextJs 와 TypeScript로 진행한 Project이다.</li>
</ul>
<p>마감기한까지 성공적인 결과물을 얻어내지 못했다고 <a href="https://velog.io/@chan_snk/NewTips-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0%EC%A7%80%EA%B8%88-%EC%BD%94%EB%93%9C%EC%B9%A0-%EB%95%8C%EA%B0%80-%EC%95%84%EB%8B%88%EC%95%BC">이전회고</a>에서 작성한 것처럼 
이번 프로젝트에는 올바르지 못한 개발 방향에 따른 영향으로 다양한 문제점을 겪게됐다.</p>
<p>이번 회고에서는 <strong>코드적인 문제점</strong>에 대해 작성해보려고 하는데 
내가 프로젝트에서 맡았던 부분들은 하나의 큰 문제점에서 부터 시작했다.</p>
<blockquote>
<p><strong>문제의 원인은 무엇일까...</strong></p>
</blockquote>
<ul>
<li>우리팀은 설계적인 부족함에 코드를 작성하는 과정에서 시간에 쫓기게 됐다.</li>
<li><em>NextJs*</em>를 사용한 project임에도 불구하고 Framework에 대한 충분한 고민 없이 React에서 하던 작업방식을 도입해서 코드를 작성하기 바빳다.</li>
<li>SSR 방식과 CSR방식을 충분히 고민해보지 않고 모든 작업을 클라이언트 측에서 동작하도록 코드를 작성하게 됐는데. 여기서 부터 문제가 발생했다.</li>
</ul>
<h3 id="🎁나의-task">🎁나의 Task</h3>
<p>NewTips에서 내가 맡아서 작업한 부분들은 다음과 같다.</p>
<blockquote>
<ul>
<li>api요청의 자동화를 위한 axios-instnace설정</li>
</ul>
</blockquote>
<ul>
<li>tanstack-qeury를 ssr - csr 두 환경에서 모두적용하기 위한 설정</li>
<li>사용자 권한에 따른 접근권한 설정</li>
</ul>
<p>기본적으로 팀원들이 프로젝트를 진행하면서 작성해야할** 기본 설정**에 대한 작업들이 주된 task였다.</p>
<p>설정관련 setting을 작업하며 팀원들이 내가 작업한 코드를 최대한 간편하게 사용할 수 있도록 코드를 작성하는 것이 목표였는데 SSR 과 CSR 두 환경에서 문제없이 사용하도록 동작하는 과정에서 문제점을 겪게된 부분들을 얘기해보려 한다.</p>
<h3 id="🔧내가-문제를-해결했던-방식">🔧내가 문제를 해결했던 방식</h3>
<p>문제점을 해결하는 과정에서 나는 3단계를 거쳐서 문제를 해결했다.</p>
<blockquote>
<ul>
<li><ol>
<li>원인분석 : 어디서 문제점이 발생하는지 </li>
</ol>
</li>
</ul>
</blockquote>
<ul>
<li><ol start="2">
<li>목표 설정 : 여러가지 해결 방식 중 어떤 방식이 프로젝트에서 최선일지</li>
</ol>
</li>
<li><ol start="3">
<li>해결 : 해결 목표에 맞게 코드를 작성해서 문제해결</li>
</ol>
</li>
</ul>
<p>이렇게 3단계를 거쳐서 문제를 해결했는데 프로젝트를 진행하며 겪게된 주된 문제점** 세가지를** 위와같은 해결방식에 맞게 해결했던 경험을 작성해 보려 한다.</p>
<h1 id="💥axios의-사용">💥axios의 사용</h1>
<p>NextJs에서 fetch를 사용해도 되는데 axios를 사용할 필요가 있었나... 
지금와서 생각해보면 굳이라는 생각이 들긴하지만 다음과 같은 이유로 axios를 사용했다.</p>
<blockquote>
<ul>
<li>axios-instance설정으로 baseUrl설정 api요청 함수를 편리하게 작성</li>
</ul>
</blockquote>
<ul>
<li>axios-interceptor 사용으로 요청 전,후의 일관된 작업을 다루는데 편리함</li>
</ul>
<p><strong>다음과 같은 이유로 axios를 도입, instance설정을 완료</strong>한 후 프로젝트를 진행하던 과정에서 문제상황을 맞이하게 되었다.</p>
<h3 id="🤔문제원인">🤔문제원인</h3>
<blockquote>
<ul>
<li>이미 api호출 함수에서 axiosInstance() 를 사용한 클라이언트 패칭 작업이 모두 완료된 상황</li>
</ul>
</blockquote>
<ul>
<li>서버 패칭을 위해서 api호출을 해야하는데  기존 함수들은 클라이언트 측에서만 동작하기 때문에 문제가 발생했다.</li>
</ul>
<pre><code class="language-tsx">// 기존 axios-instance코드 (쿠키를 가져오는 상황에서 문제가 발생)

export const axiosInstance = () =&gt; {
    let instance = axios.create(instanceConfig)
    instance = setRequestInterceptor(instance, contentType)
    instance = setResponseInterceptor(instance)
    return instance
}

const setRequestInterceptor = () =&gt; {
    instance.interceptors.request.use(
        (config) =&gt; {
            const accessToken = getCookie(ACCESS_TOKEN)
            // 쿠키를 가저오는 getCookie함수는 클라이언트 측에서만 동작을 하는 상황
            if (accessToken &amp;&amp; config.headers) {
                config.headers.Authorization =`Bearer$ {accessToken}`
            }

</code></pre>
<p>api요청을 보내기 위해서는 요청 header에 access_token 정보를 포함시켜서 보내야하는데 
현재 getCookie함수는 <strong>클라이언트 측에서만 동작</strong>하는 코드이기에 문제가 발생했다.</p>
<h3 id="📝목표설정">📝목표설정</h3>
<p>어떤 코드가 <strong>문제의 원인</strong>이고 이것을 해결한다면 문제 또한 해결 가능하다는 것은 인식했다.</p>
<blockquote>
<p>getCookie함수가 클라이언트 측에서만 동작하니 서버 환경에서도 동작하도록 코드를 수정해준다면 문제가 해결될 것이다.</p>
</blockquote>
<p>가장 간단한 해결책으로 처음 생각한 방식은 두개의 axiosInstance를 생성하는 방법이다.</p>
<pre><code class="language-tsx">
const axiosInstanceServer = ()=&gt;{ 서버 패칭시 사용 }
const axiosInstanceClient = ()=&gt;{ 클라이언트 패칭시 사용}
</code></pre>
<p>위와 같이 2개의 instance를 생성한다면 문제는 당연히 해결될 것이다.</p>
<p><strong>정말 최선인가?</strong></p>
<p>위와같은 방식으로 해결하는 것이 정말 최선인가? 라고 생각했을때 별로라는 판단을 했다.</p>
<p>설정작업의 담당자로서 팀원들에게 <strong>&quot;코드의 사용법을 숙지시켜야 한다&quot;</strong> 라는 방식은 좋은 방식이 아니라고 생각을 햇다.</p>
<p>설정파일의 담장으로서 작업을하면서 목표로 한 부분은 내가 작업한 코드를 팀원들이 가져다 쓰기만 해도 <strong>자동으로 동작시키도록 하는것이 목표이기에</strong> 더 좋은 방식을 고민해보고 팀원들과 이와 관련한 회의도 열어서 최적의 방법을 고민해봤다.</p>
<p><strong>axios문제의 해결 목표</strong></p>
<p>내가 최종적으로 완성하고 싶은 코드의 형태는 다음과 같았다.</p>
<blockquote>
<p>이미 api호출 함수들에 axiosInstance()라는 코드가 포함되어 있는 상태이다.
따라서. 기존코드의 수정 없이 axiosInstace설정 내부에서 모든 것을 해결하는 것이다.</p>
</blockquote>
<h3 id="👌해결책">👌해결책</h3>
<p><code>getCookie</code> 함수는 <a href="https://github.com/andreizanik/cookies-next"><code>cookies-next</code>라이브러리</a>를 사용하고 있었다.</p>
<p>해당 라이브러리를 사용한 이유는 서버환경 &amp; 클라이언트 환경 모두다 getCookie가 가능했기 때문에 사용하게 되었는데</p>
<pre><code class="language-tsx">// getCookie() 서버와 클라이언트 환경에서 각각 사용하는 방법

const client_cookie  = getCookie(&quot;key&quot;) 
// 인자로 키값을 넣어준다

const server_cookie = getCookie(&quot;key&quot;,{cookies})
// 키값 + next/headers의 cookies를 두번째 인자로 추가
</code></pre>
<p>이를 활용한 axiosInstance 의 코드는 다음과 같다</p>
<blockquote>
<ol>
<li>typeof window === &quot;undefiend&quot;를 활용해 코드가 실행되는 환경을 check<ol start="2">
<li>getServerCookie() , getClientCookie() 함수를 정의 해서 cookie를 가져옴</li>
<li>가저온 access_token을 요청 header에 추가</li>
</ol>
</li>
</ol>
</blockquote>
<pre><code class="language-tsx">const setRequestInterceptor = () =&gt; {
    instance.interceptors.request.use(
        async (config) =&gt; {
            const accessToken = await getAccessToken() // 실행환경에 맞는 accessToken 저장
            const validAccessToken = await getValidAccessToken(accessToken) // 토큰 만료여부에 따라서 유효한 토큰 반환
            if (validAccessToken &amp;&amp; config.headers) {
                config.headers.Authorization = `Bearer ${validAccessToken}`
</code></pre>
<p>완성된 코드는 어려운 코드가 아니지만 이를 해결하기 위해 여러가지 코드를 고민해보고,
팀원들과 회의를 통해 현재 어떤 문제가 있는데 이를 해결하기 위해 여러가지 해결방식중 
최선의 방법이 무엇인지 논의해보는 과정에서 목표한 코드를 완성할 수 있었다.</p>
<blockquote>
<p><a href="https://github.com/mobi-projects/nail-case-client/pull/198/files">axios설정을 변경한 PR확인하기</a></p>
</blockquote>
<h1 id="💥tanstack-query의-사용">💥tanstack-query의 사용</h1>
<p>react-query를 사용하면 api호출과 전역상태 관리를 동시에 사용하는 장점이 있다.
이전 react 프로젝트에서도 자주 사용해왔는데 </p>
<p>next-js에서도 사용해도 좋을것 같다는 판단에 서버에서 받아온 data를 tanstack-qeury 라이브러리를 통해 관리했다.</p>
<h3 id="🤔문제원인-1">🤔문제원인</h3>
<p>굉장히 편리한 장점이 있는 tanstack-qeury에서도 우리 프로젝트와 맞지 않는 부분이 있었는데 <strong>서버패칭을</strong> 계획하면서 문제점이 발생했다.</p>
<p>*<em>서버 패칭과정에서는 tanstack-query를 사용하지 못한다..!! *</em></p>
<p>우리 팀은 이미 api패칭과정에서 많은 부분을 useQeury 또는 useMutation을 통해 관리하고 있었는데 서버패칭에서는 이를 적용하기 힘든 상황을 맞이했다.</p>
<h3 id="📝목표설정-1">📝목표설정</h3>
<p><strong>해결 목표</strong></p>
<blockquote>
<p>이번 해결 목표도 이전과 동일했다.</p>
</blockquote>
<ul>
<li>이미 기존 코드가 작성되어 있는 상황. 기존 코드 수정없이 동작에 문제없도록 코드를 작성하는 방향</li>
<li>react-qeury를 서버패칭에도 활용하는 방향</li>
</ul>
<p><strong>이를 해결하기 위해 몇가지 해결방식을 생각 해 볼 수 있엇는데</strong></p>
<blockquote>
<ol>
<li>서버패칭이 필요하다면? =&gt; tanstack-qeury사용 없이 api호출후 결과를 사용</li>
<li>initialData props를 활용해서 초기 데이터를 패칭후 클라이언트 컴포넌트에게 전달</li>
</ol>
</blockquote>
<p><strong>두 방식모두 해결은 가능한 코드이지만 최선이라는 생각은 들지 않았다.</strong></p>
<blockquote>
<ol>
<li>팀원들에게 서버패칭시 주의 사항을 알려줘야함</li>
<li>props로 data를 넘겨주는 과정에 props drilling 이 일어날 수 있음</li>
<li>axios를 사용하기 때문에 api를 서버1회 클라이언트1회 2회를 호출해야 해서 비효율 적이다.</li>
</ol>
</blockquote>
<p>위와같은 문제점 때문에 다른 해결 방식을 고민해보던 중 
<a href="https://tanstack.com/query/latest/docs/framework/react/reference/hydration#hydrationboundary">HydrationBoundary</a>라는 것을 알게 되었고 이를 적용하기로 했다.</p>
<h3 id="👌해결책-1">👌해결책</h3>
<p>HydrationBoundary를 사용하면 prefetchQuery를 통해 서버패칭을 진행하고 
클라이언트 컴포넌트에서 <strong>동일한 queryKey, queryFn을</strong>호출 한다면 캐싱된 데이터를 불러와서 api를 재호출 하는 일 또한 막을 수 있엇다.</p>
<p><strong>팀원들과 공유하기</strong></p>
<p>처음 적용해보는 내용이라 팀원들과 해당 내용을 공유 하기 위해서 </p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/86ae30ae-0ed2-4d0d-a8ef-564de1b93880/image.png" alt=""></p>
<p>사용법과 함께 연습과 해당 내용에 개념들을 담은 <a href="https://codesandbox.io/p/devbox/mystifying-bell-s7flst?layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clz2e3aay0006356kuwz1oy3d%2522%252C%2522sizes%2522%253A%255B79.0170481869426%252C20.982951813057397%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clz2e3aay0002356krolq7c6m%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clz2e3aay0004356kzg75iv5h%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clz2e3aay0005356kgy9i7n4m%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B48.455760343936944%252C51.544239656063056%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clz2e3aay0002356krolq7c6m%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clz2e3aax0001356kiuk3ddd3%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522filepath%2522%253A%2522%252FREADME.md%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522id%2522%253A%2522clz2e3aay0002356krolq7c6m%2522%252C%2522activeTabId%2522%253A%2522clz2e3aax0001356kiuk3ddd3%2522%257D%252C%2522clz2e3aay0005356kgy9i7n4m%2522%253A%257B%2522id%2522%253A%2522clz2e3aay0005356kgy9i7n4m%2522%252C%2522activeTabId%2522%253A%2522clz679bzl00c8356kx5u71on6%2522%252C%2522tabs%2522%253A%255B%257B%2522type%2522%253A%2522TESTS%2522%252C%2522id%2522%253A%2522clz679bzl00c8356kx5u71on6%2522%252C%2522mode%2522%253A%2522permanent%2522%257D%255D%257D%252C%2522clz2e3aay0004356kzg75iv5h%2522%253A%257B%2522id%2522%253A%2522clz2e3aay0004356kzg75iv5h%2522%252C%2522activeTabId%2522%253A%2522clz679fhg00ct356k7fpz4bwa%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clz679fhg00ct356k7fpz4bwa%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522TERMINAL%2522%252C%2522shellId%2522%253A%2522clzwul14t0020dci56wjn31fj%2522%257D%255D%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Atrue%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D">연습공간</a>을 제공해서 팀원들과 공유했다.</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/cef53218-a2ec-4682-9342-0964f3d2c8e4/image.png" alt=""></p>
<p>이를 통해서 SEO최적화를 위한 server-fetching의 설정을 완료할 수 있었고 SSR CSR환경 둘다 코드 작성가능 하도록 했다.</p>
<blockquote>
<p><a href="https://github.com/mobi-projects/nail-case-client/pull/199">HydartionBoundary 관련 PR확인하기</a></p>
</blockquote>
<h1 id="💥사용자-권한-여부에-따른-경로-설정">💥사용자 권한 여부에 따른 경로 설정</h1>
<h3 id="🤔문제원인-2">🤔문제원인</h3>
<p>NewTips프로젝트는 사용자 권한에 따른 분기처리가 3가지 존재한다.</p>
<blockquote>
<ol>
<li>비로그인 사용자</li>
<li>로그인한 소비자</li>
<li>로그인한 판매자</li>
</ol>
</blockquote>
<p>3가지 경우의 수가 존재하니 페이지별로 권한을 부여해야하는데...
react를 활용한 프로젝트라면 router설정을 통해서 작업해왔는데 NextJs에서는 react-router-dom 처럼 한 파일에서 설정하는 방식을 경험해 본적이 없었다.</p>
<h3 id="📝목표설정-2">📝목표설정</h3>
<p><strong>첫 해결 방식</strong>
각 <code>page.tsx</code> 파일에서 인증여부를 check후 접근 불가 페이지라면? 특정경로로 redirect시키는 방식을 떠올렸다.</p>
<p>위와같은 해결 방식 또한 앞에서 해결했던 해결방식과 일치하지 않았다.</p>
<blockquote>
<p>&quot;각 페이지 작업자들에게 페이지 컴포넌트를 작업할땐 경로설정을 해주세요~&quot;</p>
</blockquote>
<p>위와같이 요청을 해야하는데 효율적인 방식은 아니라는 판단을 했다.</p>
<p><strong>더 좋은 방식</strong></p>
<p>한 파일에서 모든 권한 설정관련 코드를 관리하면 편하지 않을까?
이와 관련된 내용를 찾아보던 중 역시 프레임 워크답게 이를 도와주는 파일이 존재한다는 것을 알게 되었다.</p>
<h3 id="👌해결책-2">👌해결책</h3>
<p><code>middleware.ts</code>라는 파일의 존재와 역할을 알게되었다.</p>
<p>이또한 처음 작업해보는 내용이라 팀원들과 회의시간에 내용을 공유하고 설정 방식에대한 주석으로 이해를 돕도록 작성했다.
<img src="https://velog.velcdn.com/images/chan_snk/post/244b6d14-0fc0-448b-b93f-40da4b882bde/image.png" alt=""></p>
<blockquote>
<p><a href="https://github.com/mobi-projects/nail-case-client/pull/170">middleware설정 관련 PR확인하기</a></p>
</blockquote>
<h1 id="🤷♂️글을-마치며">🤷‍♂️글을 마치며...</h1>
<p>프로젝트를 진행하며 다양한 문제상황들을 맞이했는데 코드에는 정답이 없기에 동작만을 우선시한다면 어떤 방식이든 코드를 작성해도 문제없을 것이다.</p>
<p><strong>협업이기에</strong> 여러가지 방식을 고민해보고, 또한 팀원들과 공유하기 가장 좋은 방법을 고민해보는 과정에서 다양한 지식을 얻을 수 있엇고 스스로 해결하지 못한 문제를 회의를 통해 풀어나가는 과정에서 더 좋은 결과를 얻을 수 있었던것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[NewTips 프로젝트 회고(지금 코드칠 때가 아니야..!) ]]></title>
            <link>https://velog.io/@chan_snk/NewTips-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0%EC%A7%80%EA%B8%88-%EC%BD%94%EB%93%9C%EC%B9%A0-%EB%95%8C%EA%B0%80-%EC%95%84%EB%8B%88%EC%95%BC</link>
            <guid>https://velog.io/@chan_snk/NewTips-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0%EC%A7%80%EA%B8%88-%EC%BD%94%EB%93%9C%EC%B9%A0-%EB%95%8C%EA%B0%80-%EC%95%84%EB%8B%88%EC%95%BC</guid>
            <pubDate>Thu, 08 Aug 2024 05:47:02 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/chan_snk/post/a8a59396-f8b1-4d7a-9bf7-d3ce69214f00/image.png" alt=""></p>
<h1 id="🚗들어가면서">🚗들어가면서...</h1>
<p>23년 11월 쯤 우연히 front-end를 배우기 시작했다. 
Korea It Academy 라는 학원에서 수업을 마치고 1~7월까지 <strong>MOBI</strong>라는 단체에서 다양한 front-end stack들을 배우고 경험하며 어느덧 마지막 최종 프로젝트를 진행하게되었다.</p>
<p>총 기간 <strong>5월 15일 ~ 7월 15</strong>일까지 일정을 잡고 MOBI에서 섭외한 BE &amp; Designer 팀과 처음으로 프로젝트를 진행하며 느꼇던 <strong>다양한 경험</strong>들을 글로 남겨보려한다. </p>
<p><em>결과적으로 프로젝트의 완성이라는 목적에는 실패했지만 그 간의 배운점을 
notion에 기록된 내용들을 돌아보며 살펴보자.</em></p>
<h1 id="👌미리보기">👌미리보기</h1>
<p>간단하게 프로젝트에 대한정보만 살펴보자.</p>
<blockquote>
<p>총기간 : 5월15~ 7월15일  <em>(현재도 진행중~)</em>
인원 : FE : 4명 BE : 3명 : Desgin : 1명 
프론트 사용스택 : NextJs, TypeScript TailwindCss
협업 툴 : github, jira, notion, discord</p>
</blockquote>
<p><strong>NewTips에 대해서</strong></p>
<ul>
<li><p>NewTips는 현재 네일아트 예약과정에서 판매자, 소비자가 모두 불편함을 느낄 수 있다는 문제점을 인식.</p>
</li>
<li><p>가게 사장님 : 고객들과 채팅을 통해 예약에관한 대화를 해야만 한다.</p>
</li>
<li><p>소비자 : 원하는 디자인, 시술내용, 시간등을 예약하기까지 다양한곳에서 검색, 채팅을 통해 가게와 일정조율.</p>
</li>
<li><p>이런 문제점을 바탕으로 우리 팀은 문제해결을 위한 <strong>가설</strong>을 세우고 이를 바탕으로 <strong>MVP를 설정, 검증</strong>하는 과정까지를 목표로 진행.</p>
</li>
<li><p><a href="https://pale-sunshine-f78.notion.site/fd0d8c4411af485fb5c7b60c6b743248?pvs=4">notion에 작성한 가설 &amp; MVP</a></p>
<div align="center">

</li>
</ul>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/070269d4-f12a-401d-80ed-4ad516392b8a/image.png" alt=""></p>
</div>

<p>다음과 같은 방식으로 5월 19일의 첫 회의를 시작으로 프로젝트를 시작했다.</p>
<h1 id="😰계획한-대로-했다면">😰계획한 대로 했다면...</h1>
<p>우선 결과부터 살펴보면, 프로젝트는 처음 MVP를 설정하고, 일정을 계획하고 HappyCase를 생각하며 신나게 출발했지만.... 완성된 결과물은 실패하고 말았다.</p>
<p>지난 프로젝트를 돌아보며 느꼇던것, 그 간의 계속된 issue들을 겪으며 정말 적고싶은 것들이 많이 생각이난다.</p>
<p>그 중 가장 중요하게 배웠던 내용들에 대해 <span style="color:blue"><strong>3가지</strong></span> 키워드와 함께 살펴보자.</p>
<blockquote>
<ul>
<li><ol>
<li><strong>서비스 !== 코드</strong></li>
</ol>
</li>
<li><ol start="2">
<li><strong>모래성</strong></li>
</ol>
</li>
<li><ol start="3">
<li><strong>기억상실</strong></li>
</ol>
</li>
</ul>
</blockquote>
<h1 id="💥서비스--코드">💥서비스 !== 코드</h1>
<p>짧은 기간이지만 그동안 여러 작은 프로젝트들을 경험하면서** 서비스를 만드는 것은 코드다! **라는 생각으로 코딩을 해왔던것 같다.</p>
<p>단순히 만들고싶은 것들을 코드로 작성해서 화면에 그려지는 것 자체에 굉장한 재미를 느꼇고 이런방식이면 <strong>어떤 서비스든 만들 수 있겟는데?</strong> 라는 생각을 하곤했엇다.</p>
<p>이번 프로젝트에 임할때도 어서 코드를 치고싶은 생각에 들떠서 시작을 했는데 지금까지 몰랐던것이 있었다.</p>
<h3 id="서비스는-그냥-만들어지는게-아니다">서비스는 그냥 만들어지는게 아니다</h3>
<p>그 동안 일상에서 사용해왔던 다양한 서비스들이 어느순간 뚝딱 만들어졌다고 생각해왔던것 같다. </p>
<p>예약서비스를 예시로 생각해보면 다음과 같은 기능들이 필요할 것이다.</p>
<blockquote>
<ul>
<li>사용자가 처음 보게될 main페이지</li>
</ul>
</blockquote>
<ul>
<li>예약하기 위한 예약페이지</li>
<li>사용자 정보를 관리할 마이페이지</li>
<li>그 외, 다양한 기능들과 부가적인 ui들(모달, toast메세지 등등)</li>
</ul>
<blockquote>
<p><em>&quot;단순히 예약서비스를 만들어야 하니 위와같은 기능을 만들어서 사용자들에게 제공하자.!&quot;</em></p>
</blockquote>
<p>이것이 서비스에대한 나의 이해였던것 같다.</p>
<p><strong>회고를 작성하는 시점의 나는 서비스를 다르게 정의하고 싶다.</strong></p>
<blockquote>
<p><em>&quot;일상의 문제점을 찾아서 그것을 해결해주자..!&quot;</em></p>
</blockquote>
<p>프로젝트를 시작할 때 MOBI에서 검증하고싶은 가설을 세우고 그것을 바탕으로 MVP를 정하라는 조언을 받았었다. </p>
<p>어쩌면 완전한 서비스를 처음부터 시작해보는 과정이 처음이라 
그 당시에는 <strong>왜 그래야하는지</strong> 이해하지 못했다.</p>
<p>돌아보며 가설과 MVP가 어떤 의미를 갖는지 생각해보면 다음과 같은 이유가 아닌가 생각이든다.</p>
<blockquote>
<ul>
<li><ol>
<li>문제점을 발견하고 해결해야만 같은 문제점을 느낀 사용자들을 끌어들일 수 있다.</li>
</ol>
</li>
</ul>
</blockquote>
<ul>
<li><ol start="2">
<li>가설을 세우고 검증하는 과정에서 서비스에 대한 가능성을 점칠 수 있다.</li>
</ol>
</li>
<li><ol start="3">
<li>MVP를 설정함으로서 불필요한 작업, 시간낭비를 줄일 수 있다.</li>
</ol>
</li>
</ul>
<p>7월 15일 이후는 서비스를 완성하기 위해 팀원들과 계속 개발을 진행중인데 
가설과 MVP를 중점으로 개발을 하면서 이전보다 더 효율적으로 진행되는것 같다.
팀원 모두가 같은 목표를 바라보며 개발을 하는 과정이 <strong>더 확실한 목표, 빠른 진행속도</strong>를 가져다주고 있다.</p>
<h3 id="코드-치기전에-정말-필요한-개발인지-체크하자">코드 치기전에 정말 필요한 개발인지 체크하자.</h3>
<blockquote>
<p><em>&quot;우리팀은 프로젝트를 시작하며 Designer분에게 저희 이런 서비스를 만들거 같은데 작업해주세요~&quot;</em> 라고 요청했다.</p>
</blockquote>
<p>이렇게 며칠이 지난후 확인한 figma에는 정말 다양한 페이지들이 존재했는데 
figma를 바탕으로 모든 페이지를 작업하는 것을 목표로 했엇다.</p>
<p>프로젝트의 마무리 시점에 완성된 페이지들을 보면 MVP에 일치하지 않는 페이지들이 여럿 있는데.. </p>
<p><strong>정말 가장중요한 가치인가</strong>에 대해 조금만 더 고민해 봤더라면 
지금 사용하지않는 페이지, 기능들을 만들시간에 MVP에 적합한것들을 만드는것을 우선으로 했을것이다.</p>
<p>결국 프로젝트의 완성시점에서 돌아보면 모두 완성 해야 할 것들이지만 순서가 달랐더라면 어땟을까 하는 아쉬움으로 남는다.
_*<em>그저 빠른 시간안에 모든 코드를 완성하겠어..! *</em>_라는 생각으로 비효율적으로 진행했다.</p>
<h1 id="🏰모래성">🏰모래성</h1>
<blockquote>
<p><strong>&quot;모래성&quot;</strong> 하면 떠오르는 것은... =&gt; <strong>&quot;쉽게 무너진다&quot;</strong>.
그것이 이번 우리팀의 <strong><span style="color:red">설계</span></strong>와 같다는 생각을 한다.</p>
</blockquote>
<p>앞에서 언급한것 처럼 우리팀은 서비스를 개발하는 것이 아닌 코드를 치는 방향으로 진행됬었다.</p>
<p>그러다보니 서비스의 설계, 여러가지 case들을 제대로 회의하지 않고 바로 코드를 치는 단계로 들어갔다.</p>
<blockquote>
<p>이정도 속도면 충분히 정해진 기간안에 완성해낼 수 있겠어..!</p>
</blockquote>
<p>이것의 초반의 생각이었다.</p>
<p>하지만... 잘 설계되지 않은 서비스는 모래성과 같았고 곧 이전 작업들이 필요없어 지는 순간이 왔다.</p>
<p>어느덧 데모데이가 다가올때쯤...</p>
<blockquote>
<p> <em>Chan : 어.. 저희 데모데이까지 가설검증하기 위해서 필요한 내용들은 작업을 못하겠는데요...</em></p>
</blockquote>
<p><strong>더 편리한 예약 서비스</strong> 라는것을 만들기 위해서 프로젝트를 시작해서 검증을 받아야할 순간이 다가왔는데 정작 그 시점 완성된 것은 예약과정에 대한 flow가 아닌 가상데이터를 그저 화면에 보여주고 있었을 뿐...</p>
<h3 id="설계란">설계란?</h3>
<p>실제 회사였다면 아마 설계를 직접하진 않겠지만.. 설계는 이런것이 아닐까 생각한다.</p>
<blockquote>
<ul>
<li>문제점 파악 : 왜 이서비스를 개발해야 하는지에 대한 파악</li>
</ul>
</blockquote>
<ul>
<li><p>가설 : 이렇게 한다면 문제점이 해결되지 않을까?</p>
</li>
<li><p>MVP : 문제점을 해결하기 위해 최우선시 해야할 것</p>
</li>
<li><p>검증 : 가설과 MVP를 바탕으로 최소한의 서비스를 만들고 평가받는 과정</p>
<p>위의 네가지 항목에 맞게 일정을 계획하는 과정이 설계가 아닐까 생각한다.</p>
</li>
</ul>
<blockquote>
<p><em>이런 기능은 필요하지 않을까요???
어,,, 이것도 있으면 좋을것 같은데..</em></p>
</blockquote>
<p>설계가 부족하다 보니 프로젝트를 진행하는 과정에 위와같은 대화를 많이 하게되었고 
그렇게 하나 둘, 꼭 필요한 기능이아닌 것들을 포함시키면서 엄청난 비효율을 발생시켰다.</p>
<p>위의 내용들을 고민할 시간에 설계한 범위 내에서 error case, 더 효율적인 코드에대해 고민했다면 보다 좋은 서비스를 만들 수 있지않았을까 싶다.</p>
<h1 id="⏱기억상실">⏱기억상실</h1>
<p>프로젝트의 중간 FE 팀의 steak holder역할을 맡게 되었다. </p>
<p>기존 steak holder 였던 팀원분이 가장 잘하셔서 steak holder 역할을 수행해 주셧는데 너무많은 짐을 짊어진 탓에,,,, 힘들어하셧다.</p>
<p>steak holder를 맡을때 쯤 FE팀에는 계속되는 task delay이슈가 있었는데... </p>
<p>이에대한 <a href="https://pale-sunshine-f78.notion.site/Front-team-94c7b1b98c714e21bfb498cf4760e8ee?pvs=4">개선방안을 notion</a>에 작성해서 팀원들과 공유하며 시작했다.</p>
<div align="center">

<p><img src="https://velog.velcdn.com/images/chan_snk/post/fb970157-cfaa-4e96-8aa2-558dcfeb704c/image.png" alt=""></p>
</div>


<p>개인적인 작업스타일은 자유로운 분위기의 작업
기한과 task만 정해진다면 자유롭게 기간안에 마무리하는 방식을 선호한다.</p>
<p>이전까지 우리팀도 그런방식으로 진행하지 않았던 터라 이런 방법이라면 더 빠른 진행을 할 수 있지 않을까? 하고 제안했엇다.</p>
<p>하지만 이방법은 성공적이진 않았다...</p>
<p>내가 그렇다고 해서 *<em>남들도 그럴것이다.. *</em>라는 생각이 틀렸던것 같다.</p>
<h3 id="다음-plan은">다음 plan은?</h3>
<p>한번의 제안사항이 성공적이지 못해 다시 원인이 무엇인가 생각해봤다.</p>
<p>불완전한 설계에 따른 계속되는 회의, 수정사항이 생겼다
이를 반영하는 과정에서(PR) 에서 팀원 모두 수정사항에 대해 공유가 안되서 PR이 merge가 안됐다. 어느 순간 PR이 10개가 넘게 쌓이는 상황이 발생했다..</p>
<p>결국 나는 다음과 같은 방식을 제안했다.</p>
<blockquote>
<p>Chan : <em>저희 매일밤 12시에 모여서 PR을 확인하고 모두 merge하고 퇴근하는 걸로 합시다.!</em></p>
</blockquote>
<p>다음과 같은 방식으로 결국 PR이 쌓이는일은 없었고 조금 더 빠르게 작업이 진행됐다.</p>
<h3 id="더-좋은-방법이-있었을탠데-문서화">더 좋은 방법이 있었을탠데.. (문서화)</h3>
<p>내 MBTI는 ENTP-A이다.
항상 즉흥적인것을 좋아하고 계획을 짜는것, 그것을 어딘가에 적어두는것과는 거리가 먼 삶이었다.</p>
<p>하지만 프로젝트를 진행하며 느낀점은.... <strong>문서화 너무 필요하다는 것이었다.</strong></p>
<blockquote>
<p>우리팀이 겪은 문제점 :
 잦은회의  =&gt; 그로 인해 계속 변경되는 수정사항을 모두 숙지하지 못함. </p>
</blockquote>
<p>회의때는 다양한 의견을 주고받느라 정신없고 결국 문서화를 하지않으면 어떤 내용이 최종 결정사항인지 기억하지 못한다.</p>
<p>팀원모두와 내용을 공유하기 위해. <strong><code>기억상실</code></strong>을 막기위해서 <strong>문서로 꼭 기록해두자.</strong></p>
<h1 id="🎉마감기한-그-이후">🎉마감기한 그 이후...</h1>
<p>결국 마감기한까지 원하던 목표만큼 완성하진 못했지만. 그 사이 많은 것들을 경험했고 이를 바탕으로 서비스는 계속 개발을 진행중이다.</p>
<p><strong>회고</strong>라고 한다면 어떤 서비스인지, 어떤 코드를 작성했는지, 어떻게 개선했는지... 등등 작성하는 것이 일반적인 것같은데. 
이번 회고에서는 코드보다 더 중요하다 생각했던 것들만 정리해봤다.</p>
<p>그래도 열심히 개발기간동안 완성한 코드들에 대해서는 <a href="https://velog.io/@chan_snk/%ED%9A%8C%EA%B3%A0%EC%9D%B4%EB%9F%B0%EA%B2%83%EB%93%A4%EC%9D%84-%EC%A0%81%EC%9A%A9">2편</a>에 적었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[브라우저의 동작 원리 이해하기]]></title>
            <link>https://velog.io/@chan_snk/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@chan_snk/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 07 Aug 2024 02:56:53 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가면서">들어가면서...</h2>
<p>좋은 웹 경험은 빠른 로드, 페이지와 원활한 interaction에 의해 결정된다.
브라우저의 동작원리를 이해하는 것은 개발자가 위의 두 가지 목표를 이루는 것을 도와줄 것이다.</p>
<h3 id="1-탐색">1. 탐색</h3>
<p>탐색은 웹페이지를 로딩하는 첫 단계이다. 사용자는 URL을 입력하거나 링크를 클릭하는 동작을 함으로써 페이지를 이동, 로드하게 된다. </p>
<h3 id="2-웹-페이지를-탐색하는-방법">2. 웹 페이지를 탐색하는 방법</h3>
<p>우리는 웹페이지를 사용할때 주소창에 사이트의 URL을 입력하고 페이지를 이동한다.
이때 사용하는 URL은 예를들어 다음과 같은 형식으로 입력한다.</p>
<blockquote>
<p><a href="http://www.go-to-page.com">www.go-to-page.com</a></p>
</blockquote>
<p>text로 된 주소를 입력하면 해당 페이지로 이동 (HTML 로드) 한다.</p>
<p>브라우저는 text로된 주소를 입력받으면 DNS조회를 통해 실제 주소와 일치하는 IP를 찾아서 서버에 여러 자원을 요청하는 방식으로 사용자는 페이지를 볼 수 있게 된다.</p>
<h3 id="3-dnsdomain-name-system">3. DNS(Domain Name System)</h3>
<p>DNS란 IP에 이름을 붙혀서 <code>클라이언트 &lt;---&gt; 서버</code> 간의 정보를 매칭시켜주는 시스템이다.</p>
<p>사용자인 우리는 페이지를 이동 할 때 실제 서버주소를 가지고 있는 IP를 사용한다면 굉장히 사용하기 불편할 것이다.</p>
<p>이를 위해 IP주소마다 이름을 붙혀서 사용하는데 이게 DNS의 동작 방식이다.</p>
<p>클라이언트는 기억하기 쉬운 URL로 브라우저에서 요청을 보내고 브라우저는 URL을 입력받아서 해당 URL에 맞는 IP를 찾아서 서버로 자원 요청을 보낸다.</p>
<p>여기서 조금 더 알아보면 효율적인 서버와 클라이언트 측의 소통관계를 도와주기 위해 한번 조회 된 DNS는 설정된 시간만큼 캐싱을 한다.</p>
<p>이전에 사용자가 한번 요청을 보낸 URL이라면 캐싱된 시간동안은 동일한 URL로 DNS조회 요청이온다면 바로 DNS조회요청을 보내는 것이 아닌, 이전에 캐싱한 주소를 그대로 응답해준다고 한다. 
이를 통해 조금더 빠른 소통을 도와준다.</p>
<h3 id="4-브라우저의-기본-구조">4. 브라우저의 기본 구조</h3>
<ul>
<li><ol>
<li>사용자 인터페이스 - 주소 표시줄, 이전/다음 버튼, 북마크 메뉴 등. 요청한 페이지를 보여주는 창을 제외한 나머지 모든 부분이다.</li>
</ol>
</li>
<li><ol start="2">
<li>브라우저 엔진 - 사용자 인터페이스와 렌더링 엔진 사이의 동작을 제어.</li>
</ol>
</li>
<li><ol start="3">
<li>렌더링 엔진 - 요청한 콘텐츠를 표시. 예를 들어 HTML을 요청하면 HTML과 CSS를 파싱하여 화면에 표시함.</li>
</ol>
</li>
<li><ol start="4">
<li>통신 - HTTP 요청과 같은 네트워크 호출에 사용됨. 이것은 플랫폼 독립적인 인터페이스이고 각 플랫폼 하부에서 실행됨.</li>
</ol>
</li>
<li><ol start="5">
<li>UI 백엔드 - 콤보 박스와 창 같은 기본적인 장치를 그림. 플랫폼에서 명시하지 않은 일반적인 인터페이스로서, OS 사용자 인터페이스 체계를 사용.</li>
</ol>
</li>
<li><ol start="6">
<li>자바스크립트 해석기 - 자바스크립트 코드를 해석하고 실행.</li>
</ol>
</li>
<li><ol start="7">
<li>자료 저장소 - 이 부분은 자료를 저장하는 계층이다. 쿠키를 저장하는 것과 같이 모든 종류의 자원을 하드 디스크에 저장할 필요가 있다. HTML5 명세에는 브라우저가 지원하는 &#39;웹 데이터 베이스&#39;가 정의되어 있다.</li>
</ol>
</li>
</ul>
<h3 id="5-랜더링-과정">5. 랜더링 과정</h3>
<p>DNS조회를 통해 웹 서버로부터 응답을 받게된다. 요청이 성공한다면 서버로부터 자원을 넘겨받게 되는데 넘겨받은 자원을 화면에 보여주는 것이 랜더링 과정이다.</p>
<p><a href="https://velog.io/@chan_snk/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%EC%95%8C%EA%B8%B0%EC%A0%84%EC%97%90-">이 전에 작성한 글</a>에서 동작순서에 대해 다룬 글이 있다. </p>
<h3 id="참고문서">참고문서</h3>
<blockquote>
<p><a href="https://developer.mozilla.org/ko/docs/Web/Performance/How_browsers_work">웹페이지를 표시한다는 것: 브라우저는 어떻게 동작하는가</a>
<a href="https://velog.io/@wlwl99/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC-%EA%B5%AC%EC%A1%B0">브라우저 동작 원리 &amp; 구조</a>
<a href="https://d2.naver.com/helloworld/59361">브라우저는 어떻게 동작하는가?</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤣반성문...미안하다!![프로젝트 회고]]]></title>
            <link>https://velog.io/@chan_snk/%EB%B0%98%EC%84%B1%EB%AC%B8...%EB%AF%B8%EC%95%88%ED%95%98%EB%8B%A4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@chan_snk/%EB%B0%98%EC%84%B1%EB%AC%B8...%EB%AF%B8%EC%95%88%ED%95%98%EB%8B%A4%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Sun, 19 May 2024 18:14:42 GMT</pubDate>
            <description><![CDATA[<h1 id="🎉들어가면서">🎉들어가면서...</h1>
<p>오늘 글 에서는 이번에 진행했던 <span style="color:blue"><strong>chaeg-check</strong></span> 프로젝트에 대한 회고를 적어보려고 한다. 프로젝트 진행중에는 PR과 여러 검색을 통해 코드적인 실력을 향상시킬 수 있는 기회가 있었는데 <code>소통,협업</code>적인 부분의 성장을 이루기 위해서는 회고를 통해서 각자의 의견을 글로 남겨보고 이해 해보는 시간이 가장 좋을 것이라는 생각이 든다.</p>
<h2 id="🤷♂️이번-프로젝트는">🤷‍♂️이번 프로젝트는?</h2>
<p>2주간의 프로젝트와 1주의 refactoring을 끝마치며 회고를 적어보려고 한다.
<span style="color:blue"><strong>chaeg-check</strong></span> 이라는 book-review서비스를 만들어봤는데 </p>
<p>우여곡절 끝에 프로젝트를 초기목적에 거의 부합하게 완성은 하긴했지만 부족한 부분이 많았던것 같다.</p>
<h2 id="chaeg-check">Chaeg Check</h2>
<p>배포 주소 : <a href="https://chaegcheck.vercel.app/">https://chaegcheck.vercel.app/sign</a>
프로젝트 저장소 : <a href="https://github.com/mobi-projects/chaeg-check?tab=readme-ov-file">https://github.com/mobi-projects/mobi-3rd-1-typescript</a>
개발 기간 : 2024.4.29 ~ 2024.5.12</p>
<h2 id="🔧이런-방식으로-회고해-보겠습니다">🔧이런 방식으로 회고해 보겠습니다.</h2>
<p>이번 회고는 (Keep, Problem, Try)형식으로 작성해보려한다.</p>
<blockquote>
<p>1.이런 경험은 좋았습니다. 다음에도 적용한면 좋을것 같아요~
2.이런 부분은 개선하면 좋을것 같아요.(대체 방안과 함께)
3.이런 부분은 이번엔 미리정해두지 않았지만 다음에 적용해보면 어떨까요?</p>
</blockquote>
<p>이번 프로젝트를 진행하며 생각했던 내용들을 글로 적어보고 팀원들과 공유한다면 
다같이 더 성장할 수 있는 계기가 될 것 같아서 다음의 세가지를 위주로 회고를 시작해본다.</p>
<h1 id="👍이런경험은-좋았습니다">👍이런경험은 좋았습니다</h1>
<h2 id="😎1새로운-개발-진행방식">😎1.새로운 개발 진행방식</h2>
<p>우선 이번 프로젝트에서 개인적으로 가장 좋았다고 생각한 부분은 바로 <span style="color:blue"><strong>기능을 먼저</strong> </span>개발한 후 퍼블리싱을 진행하는 방식이었다.</p>
<h3 id="이전-진행방식은">이전 진행방식은?</h3>
<p>지금까지 약 8~9개월정도의 프론트앤드 개발자가 되기 위해서 이런저런 프로젝트를 진행했었는데 항상 <code>공용컴포넌트 =&gt; 페이지 퍼블리싱 =&gt; 기능추가</code> 의 방식으로 진행해왔다. </p>
<p>여기서 항상 문제점이 있었는데 너무 완벽하게 publishing을 마무리하고 <code>기능</code>을 완성하려고 하니 항상 속도가 쳐지고 프로젝트를 마무리하지 못하는 문제점이 있었다.</p>
<h3 id="이번에는">이번에는?</h3>
<p>이번 프로젝트에서는 팀에서 가장 잘하시는 팀원분의 의견으로 <code>기능</code>을 먼저 완성하고 <code>publishing은 나눠서 마무리하는 것은 어떨까요?</code> 라는 의견을 적용했는데 실제로 만족스러운 진행속도가 나왔다. </p>
<p>예를 들어 이런 방식으로 진행을 했는데
 <img src="https://velog.velcdn.com/images/chan_snk/post/2c6b69a5-e39f-4d98-b1f8-ce31e62738ed/image.png" alt="">
위와같이 최소한의 ui배치와 영역의 구분만 해주고 기능에 관한 pr을 열어서 기능을 모두 완료한후 publishing을 진행했다.</p>
<h3 id="좋았던-이유는">좋았던 이유는?</h3>
<p>이런 진행방식이 <strong>효과적이었다</strong> 라고 생각한 이유는 <span style="color:blue"><strong>병렬적인 작업</strong></span>을 하기 더 유리한 진행방식이란 생각을 했기때문이다.</p>
<p>이전에 publishing우선 개발에서는 정확한 설계가 없어서 공용컴포넌트 or 페이지 컴포넌트를 만든후에 수정해야할 부분이 계속추가되고 pr을 마무리하는 과정에서 시간이 딜레이 된 부분이 있다고 생각한다.</p>
<p>하지만 모든 페이지에 기본적인 ui를 배치해두고 기능또한 동작하는 상태이기에 
공용컴포넌트 or 페이지 컴포넌트를 작업하는 과정에서 변수를 줄일수있었다. 
그렇기때문에 각자맡은 퍼블리싱작업을 병렬적으로 진행해도 수월한 진행을 하게 된거 같다.</p>
<h2 id="😎2수정-전후-비교를-보여주는-pr">😎2.수정 전,후 비교를 보여주는 PR</h2>
<h3 id="지금까지-나의-pr방식은">지금까지 나의 PR방식은</h3>
<p>이전까지 PR은 <code>내가 이런 기능들을 작업했으니 코드를 보고 평가해주세요~</code> 라고만 생각해서 어느정도 작성해두면 review해주는 사람들이 알아서 봐주겠지? 라는 안일한 생각이 있었던것 같다.</p>
<p>이제와서야 이렇게 느꼈다는 것은 같이 늘 같이 작업했던 <strong>Jeff님</strong>이 많이 이해를 해주셨던 덕분에 큰 문제의식없이 PR을 꼼꼼하게 작성하지 않아도 열심히 리뷰를 달아주셧던것 같다. 
<strong>이 기회를 빌려 사과를 드리겠습니다...🤣</strong></p>
<h3 id="수정-전-후-비교를-보여주는-pr">수정 전, 후 비교를 보여주는 PR</h3>
<p>이번에도 늘 하던대로 pr을 올리던 와중 Jeff님의 PR을 보며 <strong>이 사람이 PR에서 전달하고자 하는 내용이 확실히 눈에 잘 보인다 ** 라고 생각하게 된 PR방식이 있었는데 
바로 **수정 전, 후</strong>를 ts코드와 함께 보여주는 PR이었다.
<img src="https://velog.velcdn.com/images/chan_snk/post/40949673-83cd-46b6-bbfb-8fbfec3e301e/image.png" alt="">
실제 이번 프로젝트에서 <a href="https://github.com/mobi-projects/chaeg-check/pull/106">Jeff님이 올려주신 PR인데</a> 이런식으로 PR을 올려주신 것을 읽으며 확실히 코드에 가서도 어떤부분을 확인해야하고 작업내용이 무엇인지 한눈에 들어왔다.</p>
<p>개인이 PR을 작성하는 시간은 조금 늘어날수 있어도 팀원들의 리뷰시간은 줄어들고 이는 팀 전체의 생산성이 올라가는 방향이라는 생각을 한다. 
따라서 앞으로의 프로젝트에도 이런 부분은 적용해서 작업한다면 좋을것같다.</p>
<h2 id="😎3칭찬은-고래도-춤추게-한다">😎3.칭찬은 고래도 춤추게 한다</h2>
<p>늘 작업하던 팀원들과 PR을 하는 과정에서 어느순간 서로의 코드에 좋은부분보다 개선해야할 부분만 집중해서 확인하게 됐던것 같다.
<img width="500" alt="image" src="https://github.com/mobi-projects/chaeg-check/assets/144839872/62aaf081-e69a-46ad-b444-5a3ee459d367">
내가 올린 PR에 한 팀원이 위와같은 리뷰를 작성해주셧는데 칭찬리뷰에 즐겁게 작업했던 기억이 있다.
그러나 프로젝트를 진행할 수록 서로 바쁘고 스트레스도 쌓이다보니 어느순간 수정요청사항에 대한 리뷰만 달리게된것 같다.</p>
<p>나도 이런 부분은 신경쓰지 못했는데 회고를 하는 과정에서 문득떠올랐다.
아무래도 코드를 작성하지만 사람들이 모여하는 작업이고 각팀원의 기분, 컨디션을 향상시키는 것도 생산성의 증가일탠데 간단한 한줄의 <strong>칭찬</strong>을 통해 생산성의 증가를 시킬수 있다면 굉장히 좋은 방법 아닐까?</p>
<p>물론 너무 남발하면 효과가없겠지만 고생한 팀원들을 위해 특정 코드에대해 <code>칭찬할 부분은 없을까?</code>를 의식하며 리뷰를 해보는 것도 좋은방향일것 같다. </p>
<pre><code class="language-ts">ex) 이런 식으로 잘한부분을 칭찬해보면 좋을것 같아요~

const CheckMyComponent = ()=&gt;{
   return &lt;div&gt;칭찬해줘~ &lt;/div&gt;
}
</code></pre>
<blockquote>
<p>오 여기서 이런 컴포넌트명을 사용하셨다니 굉장한데요?? </p>
</blockquote>
<p>와같은 디테일한 칭찬 한 줄에 즐겁게 작업할 수 있는 원동력이 될 것 같다.</p>
<h1 id="😱이런점은-개선하면-좋을것-같아요">😱이런점은 개선하면 좋을것 같아요</h1>
<h2 id="👏1왜-소통과-협업이-중요한가에-대해">👏1.왜 소통과 협업이 중요한가에 대해</h2>
<p>우선 개선할점에 대해 작성해보기 전에 <span style="color:blue"><strong>소통,협업</strong></span>이 왜 중요한지 생각해 봐야할 것 같다.</p>
<p>사실 이전에 항상 <code>협업,소통,문서화</code> 이런 부분들이 아주 중요하다고 들었을땐 이런생각을 했었다. <code>코드만 잘치고 서비스만 잘 완성하면 되는거 아니야?</code> 
제일 중요한건 서비스의 완성이고 소통,협업 이런것들은 그것을 위한 <code>수단</code> 아닌가라는 건방진 생각을 했었지만 이런생각이 이번 프로젝트에서 어려움을 만드는 원인을 만들었다고 생각한다. </p>
<p>이번 프로젝트를 진행할때만해도 <code>드디어 내실력을 보여줄때야!!</code>라는 기대감에 들떠 시작을 했다. 하지만 얼마 가지 않아서 몇가지 의견다툼이 생겼다.</p>
<p>회고를 작성하며 생각해보면 팀원모두 <code>더 좋은방향</code>이라는 하나의 목표를 공유하지만 서로 생각하는 방식이 다르기에 의견충돌이 생겼고 이를 해결하기 가장 좋은 방법은 
<span style="color:blue"><strong>효과적인 소통</strong></span>임에도 그것이 잘 이뤄지지 않았다는 생각이든다.</p>
<p>이에대해 다음 내용들을 보며 좀 살펴보려한다.</p>
<h2 id="👏2글로-작성해보는-것은-어떨까">👏2.글로 작성해보는 것은 어떨까?</h2>
<h3 id="첫-의견-충돌">첫 의견 충돌</h3>
<p>사실 이전까지 프로젝트에서는 이렇게까지 의견충돌을 해본 적이 없었다.
첫 의견충돌의 발생은 바로  <code>기능먼저 vs publishing먼저</code>에서 Jeff님과 의견 충돌이 있었다.</p>
<p>개인적인 생각으로는 이제 충분히 연습을 해봤으니 publishing을 우선 진행하고 기능을 해도 시간이 충분하다는 생각이있었다.</p>
<p>이에 반해 Jeff님은 그동안의 시행착오를 바탕으로 기능을 먼저 완성하고 publishing을 진행해야 시간내로 완성할 수 있다는 판단을 하셨다.</p>
<h3 id="이유가-뭘까">이유가 뭘까?</h3>
<p>첫 대면회의에서 이와같은 의견충돌이 발생했고 그 당시에는 서로 가장 중요한 <code>왜</code>에대한 이유는 서로 제시하지 않고 각자의 그림을 그렸기때문에 좀 큰 의견대립을 겪었던것 같다. 그래서 어느 순간 논점을 벗어난 <span style="color:blue"><strong>감정만을 앞세운</strong></span> 토론을 했다고 생각한다.</p>
<p>그 후에 비대면으로 각자의 의견을 한번더 얘기해볼 시간이 있었는데 <code>왜</code>에 대한 이유를 글로 적어서 읽어보니 충분히 납득할만 했고 이를 적용해서 충분히 만족할만한 결과를 얻어낸것 같아서 좋은의견을 내주신 Jeff님 덕분에 좋은 경험을 하게됐다.</p>
<h3 id="이번-경험을-바탕으로">이번 경험을 바탕으로</h3>
<p>이렇게 지나고보니 이번 의견충돌의 문제점은 바로 <strong>각자가 생각하는 방향을 충분히 설명해주지 않았던 것이 원인이 아니었을까?</strong> 라고 생각이된다. 결국 두 의견 모두 효과적인 진행 방향에대한 같은 목적을 가지고 있음에도 각자의 그림을 그렸기 때문이고 이런 내용을 <span style="color:blue"><strong>소통</strong></span>을 통해 해결했으면 쉽게 넘어갈 문제였지만 그렇지 못했다고 생각한다.</p>
<p>앞으로의 프로젝트에서도 이와같은 문제가 생긴다면 다음의 방식으로 해결해보려고 하면 좋을것같다.</p>
<blockquote>
<ol>
<li>구체적으로 <code>왜</code>에대한 이유를 제시해본다.</li>
<li>각자 생각하는 바를 글로 적어보자.</li>
</ol>
</blockquote>
<p>상대방을 설득하기 위해 <code>왜</code> 에대한 것을 납득시키는 것이 가장 이상적인 방향이라고생각한다. 그렇기때문에 내가 생각한것에 <code>왜</code>라는 이유와함께 의견을 제시해보는 것은 어떨까?</p>
<p>때론 말로 하는 것보다 글로 적어서 의견을 어필하는것이 효과적일때가 있다.
말로하다보면 정리되지않은 생각을 전달할수도 있고 그렇기에 논점을 벗어난 대화로 이어질 가능성이 있다.
각자의 의견을 글로 적어서 팀원들과 공유한다면 의견을 제시하는 사람도 논리적으로 글을 작성할 수 있고 팀원들도 차분하게 내용을 받아들일 수 있을 것이라고 생각한다.</p>
<h2 id="😎3더좋은-의견이-있다면-설득해보자">😎3.더좋은 의견이 있다면 설득해보자</h2>
<p>아무래도 프로젝트 경험이 부족하다보니 어디까지를 팀원들과 의견을 나눠야할지 좀 서툴렀던 부분이 있다. PR과정 중에 있었던 일을 예로 알아보자. </p>
<h3 id="너무좋은-아이디어이지만-공유했다면-더-좋았을것-같아요">너무좋은 아이디어이지만 공유했다면 더 좋았을것 같아요</h3>
<p>프로젝트의 진행과 동시에 <code>모비</code>에서는 <code>쏙쏙들어오는 함수형 코딩</code>이라는 책을 읽고있었다.
해당 책에서는 <strong>클린코드</strong>에 관한 내용을 다루는데 한 PR에 이를 적용한 PR이 올라온 적이 있었다.</p>
<p>배운것을 적용해보고 시도해보는 과정은 언제나 긍정적이고 좋은 방향이라고 생각한다.
여기서 한가지 얘기하고싶은 것은 팀프로젝트이기에 <code>해당내용이 팀원들과 잘 공유되었나</code> 
에 관한것이다.</p>
<p>어느순간 올라온 PR에서 <strong>클린코드</strong>를 적용한 것을 확인했을땐 PR을 올려주신 분이 앞으로 클린코드를 작성하는 방향으로 프로젝트를 하자고 제안하는것인가? 라는 의문이들었다.</p>
<p>같은 책을 읽은 상태이기 때문에 클린코드를 적용했다는 것은 당연히 알아챘지만 
<code>나도 클린코드를 적용해서 PR을해야하는가?</code>에 대한 언급이 없었기 때문에 어떤 방향을 원하는지 정확하게 파악못했던 일이 있었다. 이에 대해 다음과 같은 리뷰 를 작성했었다.
<img src="https://velog.velcdn.com/images/chan_snk/post/e889783e-081a-49d9-9d6a-378f5bb10650/image.png" alt=""></p>
<p>이 PR이 올라올때쯤에 아무래도 모두 너무바쁘고 완성이라는 하나의 목표만을 향해 달려가던 와중이라 이런부분을 생각하지 못하고 지나갔을거라고 생각한다.</p>
<p>위와같은 리뷰를 작성한 <code>나</code>또한 이전 PR들에서 <code>팀원들에게 내 의견을 충분히 전달했는가?</code> 라는 질문에 그렇지 못했다라고 대답할 것이다. 
예시를 위해 이런 에피소드를 소개했지만 나도 충분히 의견을 제시하지 않고 팀프로젝트에 내 의견을 반영해서 올린 적이 있을것이다. </p>
<p>아마 이전에 내 PR에 <code>이런것들좀 미리 설명해주면 좋았잖아요</code>라는 부분이 없었던 것은 팀원들이 배려해준것이 아닐까라고 생각한다. <strong>죄송합니다 ㅜㅜ 앞으로 주의할게요</strong>😘</p>
<h3 id="앞으로는">앞으로는?</h3>
<p>이런 경험을 바탕으로 앞으로 더 좋은 의견이 있다면 팀원들에게 모두 공유를 하고진행하면 좋을 것 같다. </p>
<p>PR에 좋은 의견을 적용한 코드와 함께 <code>이런것을 적용해봅시다!</code>라는 의견을 남기거나
혹은 회의,문서로 팀원전체와 공유하는 과정이 있다면 좋을 것이다. </p>
<h2 id="😎4앞으로-이렇게-하면-좀더-좋을것-같아요">😎4.앞으로 이렇게 하면 좀더 좋을것 같아요</h2>
<p>위에서 언급했던 내용들 모두 <span style="color:blue"><strong>소통</strong></span>이 조금 부족했기 때문에 생긴일들이라고 생각한다. 
아직 경험이 적기에 완벽한 설계를 못했고 추가되는 사항들, 의견들에대해 의견을 나눌시간을 정해뒀다면 더 효율적이었을것 같다.</p>
<p><strong>소통,협업</strong>은 프로젝트 진행과정 중에는 <span style="color:blue"><strong>PR과</strong></span> 직결된다는 것을 이번 프로젝트를 통해 많이 느꼇다. 
이번에 좋았던내용, 부족했던 내용들을 참고해서 앞으로 <strong>PR을 소통의 도구</strong>로 활용한다면
개선의 여지가 있을것 같다.</p>
<p>또한 바쁘더라도 주에 2~3회 의견을 나눌 시간을 꼭 가져보는 것이 좋다고 생각한다.</p>
<h1 id="🙌이런점은-다음에-시도해보면-어떨까">🙌이런점은 다음에 시도해보면 어떨까?</h1>
<h2 id="🤣1맘에안드는게-있으면-얘기해">🤣1.맘에안드는게 있으면 얘기해!</h2>
<p>이전과 다르게 조금더 많은 일들이 있었다.
처음으로 완성을 목표로한 프로젝트였기 때문일까?? 모두 바쁘고 예민한 부분이 있었다.
처음으로 팀원들과 조금 격한? 대화도 해보고 어쩌면 감정이 상한 부분도있었을 것이다.</p>
<h3 id="불만사항">불만사항</h3>
<p>프로젝트의 마무리쯤에 팀원분과 이런 대화를 한적이 있었다.
<code>난 솔직히 팀원분들이 나에게 어떤것을 원하는지 모르겠다..!</code>라는 얘기로 불만사항에 대해 얘기를 하셔서 이에대해 대화를 나눈 일이 있었다.</p>
<p>솔직히 말해서 프로젝트를 진행하며 <strong>완성</strong>이라는 것에 너무 집중한 나머지 
팀원들의 컨디션,감정들을 깊게 생각못하고 가다보니 쌓인것이 폭발했을 것이라고  생각한다.</p>
<h3 id="이럴땐-대화로-꼭-풀어야한다">이럴땐 대화로 꼭 풀어야한다</h3>
<p>이에대해 서로 생각했던 부분들, 부족했던 부분들에 대해 솔직하게 얘기하고 앞으로의 진행방향에 대해 대화를 나눴다. 
그 동안 오해의 소지가 있었던 부분들에 대해 다 설명하고 그 과정에서 잘못된 부분들은 분명히 사과하고 <code>앞으로 이렇게 진행하면 좋을것 같아요~</code>라는 말과 함께 잘 해결하고 대화를 마무리 지었던 기억이있다.</p>
<p>개인적으로는 그렇게 말을꺼내주셔서 오히려 고마웠다. 불만사항들을 표출하기까지 힘들었을탠데 용기내서 얘기해주신 것이 고마웠다. </p>
<p>나도 프로젝트중에 불만들이 있었지만 <code>이런 얘기를 꺼내면 감정이 상하진 않을까?</code> 고민되고 그런것들을 담고가자니 답답하고..... 고민되는 부분이 있었다.</p>
<h3 id="이런것을-적용해보면-어떨까">이런것을 적용해보면 어떨까?</h3>
<p>이런 일이 있을 수 있기때문에 1주에 한번 혹은 2주에 한번 불만사항을 얘기해보는 시간을 가지는 것은 어떨까?</p>
<p>아무래도 민감한 사항이기에 잘못얘기했다가는 싸움으로 번질 수 있기때문에 규칙을 정해서 진행한다면 효과적일 것 같은데</p>
<blockquote>
<ol>
<li>서로의 잘못보다 각자 본인의 잘못을 돌아보는 시간이라고 생각해보자.</li>
<li>불만사항에 대해 들었음에도 이건아니다 싶으면 글로 작성해서 다시 서로의 글을 읽어보자.</li>
</ol>
</blockquote>
<p>이런 약간의 규칙을 세우고 진행한다면 오히려 감정상하는 일 없이 원활한 진행이 되지않을까?</p>
<h2 id="🙌2기다려">🙌2.기다려!</h2>
<p>첫 의견충돌후 운영진분께 이런 질문을 한적이 있었다. 
<code>Q : 팀원들과의 충돌이 있을때는 어떻게 해결하셧나요?</code>
이에대해 답변해주신 부분을 이번에 최대한 지키려고 노력했었는데 바로 이것이었다.
<code>A : 우선은 상대방이 말을 다끝낼때까지 들어주세요~</code></p>
<p>이전까지 의견을 나눌때 대화도중 <code>이건아닌데</code> 라는 생각이 들면 중간에 말을 끊고 들어간 적이 많았던것 같다. 
이렇게 대화를 하면 상대방도 답답한 부분을 말하고싶어서 마음이 급해지고, 중간에 끊고 들어간 나도 급하게 얘기를해서 심해지면 <strong>자존심싸움</strong>으로도 번지는 일을 경험했었다.</p>
<p>방금전 언급했던 에피소드에서는 조언을 들었던것을 바탕으로 불만사항에대해 모두 얘기하실때까지 기다렸다가 내 의견을 전달해봤다. 확실히 상대방에게 모두 말할 시간을 주고, 나 또한 그 사이에 좀더 차분하게 생각을 정리해서 의견을 전달하다 보니 
차분한 대화를 이어갈 수 있었고 이전보다는 안정적인 대화흐름을 경험한 것 같다.
<em>(하지만 중간중간 또 끼어든 적이있습니다.. 죄송합니다..)</em></p>
<h3 id="앞으로는-1">앞으로는?</h3>
<p>앞으로는 팀원들과의 소통에서 이런 방식을 적용해보면 어떨까?</p>
<blockquote>
<p> 팀원1 : 제 의견은 <del>~ 합니다.
팀원2 : 의견제시는 다 하신건가요? // 질문을해서 답을 받아보자
팀원1 : 네!
팀원2 : 이렇게 말씀해 주셨는데 제 의견은 이렇습니다</del></p>
</blockquote>
<p>모든 의견제시에서 이 과정을 지킨다면 좀 답답하겠지만 아이디어 회의, 코드에대한 의견 같은 부분들은 이런 방식을 적용해보면 좋을것 같다.</p>
<h1 id="📃마치며">📃마치며...</h1>
<p>회고를 진행하며 더많은 것들이 떠오르지만 너무 길어지는 감이 있어서 몇 가지만 작성해봤다.</p>
<p>사실 이렇게 다 작성하고보니 의욕이 너무앞선 나머지 중요한 것(팀원들의 감정)을 우선시 하지 못하고 한가지 목표에만 너무 몰두했던것 같아서 굉장히 미안한 감정이 든다.</p>
<p>회고이지만 사실 반성문 이라고 하고싶은 심정이다. 이번 프로젝트에서 느꼇던 <strong>PR의 중요성 소통의 중요성</strong>을 이제서야 절실하게 느낀다는 것은 그 동안 팀원들이 나를 많이 배려해줬기 때문이라 생각한다. 이번 회고에 꼭 팀원들에게 사과를 전하고싶다.</p>
<p>그 동안 상처가 되는 말이나 행동들을 견뎌준 팀원들에게 감사하고 앞으로 신경쓰겠습니다!</p>
<p>이렇게 지난 프로젝트를 마무리하며 회고를 한것을 바탕으로 다음엔 더좋은 결과가 있기를 바란다. </p>
<p>수고하셧습니다~👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Velog 잘 작성하려면?]]></title>
            <link>https://velog.io/@chan_snk/Velog-%EC%9E%98-%EC%9E%91%EC%84%B1%ED%95%98%EB%A0%A4%EB%A9%B4</link>
            <guid>https://velog.io/@chan_snk/Velog-%EC%9E%98-%EC%9E%91%EC%84%B1%ED%95%98%EB%A0%A4%EB%A9%B4</guid>
            <pubDate>Sat, 20 Apr 2024 00:40:20 GMT</pubDate>
            <description><![CDATA[<h1 id="🎉들어가면서">🎉들어가면서...</h1>
<p>velog란 공간은 많은 개발자들이 자신의 경험을 토대로 지식,의견,느낀점 등을 다른사람들과 공유하고 소통하는 공간이다. 프론트앤드 개발자가 되기위해서 나 또한 velog를 작성하고 매일 다른사람들의 글을 읽는다.
그렇게 많은 글들을 읽다보면 문득 드는 생각이 있는데 <strong>어? 이글은 너무 잘 읽히는데?</strong> 하는 글들이 있다. 반면 내가 정말 필요한 내용이지만 <strong>여러번 읽어도 글의 내용을 잘 이해못하겠다..</strong>라는 글들이 보인다. 이 차이는 어디서 올까라고 생각하다 이 글을 작성해본다.</p>
<h1 id="📃velog를-작성해야하는-이유">📃velog를 작성해야하는 이유?</h1>
<h2 id="설득">설득</h2>
<p><strong>velog를 잘 작성하기</strong>의 내용을 보기전에 <strong>왜 잘 작성해야만 하는가</strong>에 대한 이유가 먼저 필요할 것 같다.</p>
<p>좋은 개발자란 무엇인가에 대해 먼저 생각해봐야 할 것 같은데 좋은 개발자란 무엇일까?
아직 취업준비를 하고 있는 초보 개발자이지만 내가 생각하는 좋은 개발자는 코드를 잘짜고 구현을 성공시키는 것 외에 팀원들과 소통을 잘 하는것 또한 포함된다고 생각한다.</p>
<p>협업을 하다보면 팀원들과 의견충돌이 자주 나는데 좋은 개발자라면 자신의 의견을 상대방에게 잘 <strong>설득</strong>할줄 알아야 한다고 생각된다.</p>
<p>그것이 velog를 잘 작성하는것이랑 무슨 연관이있나? 라고 생각이 들 수 있다.</p>
<p>글을 잘 작성한다는 것은 나의 의견을 누군가에게 잘 전달하는것과 같다고 생각한다. 
이는 협업에서도 소통과 연결될 것이고 이것은 좋은 개발자가 되는 것과 연결되지 않을까?
좋은 velog글을 작성한다는 것은 불특정 다수의 개발자들에게 나의 경험과 생각을** 설득**하는것과 같다고 생각이된다. </p>
<h2 id="취업을위해">취업을위해?</h2>
<p>velog는 공유,소통을 위한 공간이기도 하지만 요즘은 취업을 위한 <code>포트폴리오</code> 용도로 작성하는 사람또한 많다. 그렇다면 시간을 투자하기로한 이상 좋은 <code>포트폴리오</code>가 되도록 유의미하게 작성했으면 하는 바램이있다. 그렇게 잘 작성된 글이 나의 취업에 좋은영향을 미치고 또한 동시에 개발을 공부하는 누군가에게 도움이 되는 글이 된다면 선순환의 반복이 되지않을까?</p>
<h2 id="나의-이야기">나의 이야기</h2>
<p>나 또한 진행하고있는 <strong>Mobi</strong> 라는 단체에서 진행하는 스터디 때문에 매주 velog를 작성하게 되었는데 평소 글을 자주 작성하던것이 아니라서 매주 글을 작성하는데 어떻게 하면 좀 더 나의 이야기를 잘 전달할 수 있을까 고민하게 된다. </p>
<p>좋은 글을 작성하다는 것이 굉장히 어려웠다. 내가 느낀 것을 동료들과 공유하고 그들이 경험하지 못한것들을 내가 작성한 velog를 읽는 것만으로도 함께 공유할 수 있고 생각해 볼 수 있는 시간을 만든다는 것이 쉬운 일은 아니였다. </p>
<p>살면서 글을 얼마나 작성해봤을까? 평소 말로 나의 의견을 전달하는 것은 어려운 과정이 아니었지만 다른사람에게 내 글을 한번 읽는것만으로 의견을 전달하는것은 만만치 않다. </p>
<p>그렇게 많은 고민을 하면서 매주 글을 작성하던와중 아주 긴 글임에도 정말 잘 읽히는 글들을 몇개 보게되었다. <strong>이 글들과 내 글들의 차이점은 무엇인가?</strong> 생각해보며 좋은 글 작성법에 대해 고민해봤다. </p>
<p>그래서 이번 시간엔 내가생각하는 좋은 글의 구조에대해 작성해보려 한다.</p>
<h1 id="🎁좋은-글의-구성요소는">🎁좋은 글의 구성요소는?</h1>
<p>좋은 글을 작성하기 위해선 물론 양질의 정보, 그에 동반되는 글쓴이의 의견이 포함되는것이 당연히 좋은 글일 것이다. 하지만 모두가 그렇게 많은 지식을 갖고 글을 작성하진 않을 것이다. 
그렇다면 어떻게 글을 작성하면 가독성을 높혀서 생각을 잘 전달할 수 있을까?</p>
<p>많은 글들을 읽어보며 생각한 <strong>좋은 글을 작성하는 방법</strong>에 대해서 다음 네가지의 방법을 활용할 것을 제안한다.</p>
<blockquote>
<p><strong>1.목차 2.서사 3.질문 4.예시 5.그 외</strong></p>
</blockquote>
<h2 id="1목차">1.목차</h2>
<p>내가 velog를 이용하며 느낀 좋은 경험은 바로 목차가 보여진다는 것이다.
Markdown언어로 작성되기 때문에 <code>#</code>을 이용하면 오른쪽 상단에 <code>#</code>의 개수에따라 목차가 생성되는 것을 확인 할 수 있다. 이것을 적극 활용할 것을 추천한다.</p>
<p>목차를 잘 활용한다는 것이 무엇일까? 목차를 눈에 잘 들어오도록 작성하라는 것인가?</p>
<p>목차는 잘 활용한다는 것은 글을 작성하는 첫 시작에 목차가 어떻게 보여질지 생각하며  구조를 잡는 것을 의미한다.</p>
<p>velog를 작성할때 많은 사람들이 빈칸에 어떻게 글을 적어나가야 할까 고민할것이라고 생각한다. 이때 목차를 먼저 작성해보는 것이 글이 전체적으로 어떻게 보일까생각해보는 과정이고 이를 통해 글의 전체적인 방향을 잡을 수 있을것이다.</p>
<blockquote>
<p> <img src="https://velog.velcdn.com/images/chan_snk/post/889c6751-a4a7-4e49-8e17-01698c1debc7/image.png" alt=""></p>
</blockquote>
<p>위와같이 먼저 구조를 잡고나면 해당 주제에 어떤 내용들을 적어야할지 목표가 더 잘 보인다.</p>
<h2 id="2서사">2.서사</h2>
<p>백과사전을 읽는것과 소설책을 읽는것 둘중 어떤글을 읽는것이 더 재미있냐 라고 물어본다면 대다수가 소설책을 읽는것이 재미있다고 답할것이다. 그 차이는 어디서 올까?</p>
<p>바로 글에 흐름이 있다. velog에서는 개발에대한 내용이 주를 이루는데 단순 <code>정보</code>만 전달하는 글은 가독성이 높지못할것이다. 어떻게하면 독자에게 좀더 흡입력있는 글을 제공할 수 있을까 고민해봤는데 좋은 글에선 <strong>내용의 흐름이</strong> 있다는 것이었다.</p>
<p>내가 재미있게 읽엇던 글들은 보통 이런 흐름으로 진행했다.</p>
<blockquote>
<ul>
<li><ol>
<li>어떻게 이글을 작성하게 되었는지 </li>
</ol>
</li>
</ul>
</blockquote>
<ul>
<li><ol start="2">
<li>글의 내용을 이해하기위한 배경지식</li>
</ol>
</li>
<li><ol start="3">
<li>실제 프로젝트에서 적용했던 사례를 바탕으로 설명</li>
</ol>
</li>
<li><ol start="4">
<li>적용하며 겪었던 어려움 or 문제점 그리고 그것을 해결한 과정</li>
</ol>
</li>
<li><ol start="5">
<li>글을 마무리하며 겪은 느낀점</li>
</ol>
</li>
</ul>
<h2 id="3질문">3.질문</h2>
<p>위에서 언급한것처럼 <strong>목차&amp;서사</strong> 를 잘활용하면 어느정도 글의 틀이 잡힐것이다. 
그렇다면 추가적으로 독자들에게 흥미를 일으키는 요소는 무엇일까?</p>
<p>바로 계속해서 독자들에게 <strong>질문</strong>을 던지는것이다. 재밌게 읽었던 글들을 생각해보면 항상 작성자는 나에게 질문을 던지면서 같이 생각해보도록 유도했었다. 받은 질문을 토대로 정답에 대해 고민해보고 난 후에 답을 알아과는 과정을 겪는것이 글을 읽고난후에 더 머리속에 남은 내용이 많았던거 같다.</p>
<p>단순히 정보를 전달하기보다 계속해서 질문을 함으로써 글을읽으며 작성자의 고민을 함께 생각해보고 해답을 고민해보는 것이 좀더 흡입력 있는 글을 만드는 요소가아닐까 생각이든다.</p>
<h2 id="4예시">4.예시</h2>
<p>앞에 1~3번을 활용해서 글을 작성하고 난후 생각해 볼것은 글을 접한사람들이 활용해볼수있는 예시코드를 같이 작성해주는것이 효과적일것이라고 생각된다.</p>
<p>예시코드는 글을 읽는 과정에서 text로 설명하는 것보다 효과적으로 작용할때가 많다.
하지만 한번에 긴 코드를 제공해주는것은 오히려 가독성을 떨어뜨릴 수 있다고 생각된다.</p>
<p>예시 상황을 보며 생각해보자. 이전에 작성한 글 중 Dropdown컴포넌트를 headless component로 만드는 과정에대해 작성한 적이있다.
그렇다면 예시코드로 어떻게 작성했는지 보여줘야할 필요가있는데 </p>
<blockquote>
<pre><code class="language-JS">// 예시를 위해 간단하게 적어봤습니다.
const Dropdown = ()=&gt;{
    return &lt;DropdownWrapper&gt;
          &lt;DropdownTrigger&gt;...구성요소&lt;DropdownTrigger/&gt;
        &lt;DropdownItem&gt;...구성요소&lt;DropdownItem/&gt;
      &lt;/DropdownWrapper&gt;
}</code></pre>
</blockquote>
<pre><code>
예시상황을 위해 간단하게 작성했지만 사실 아주 긴 코드이다. 
만일 Dropdown에 대한 코드를 한번에 보여준다면 과연 사람들이 하나씩 다살펴볼까? 아마도 직접 작성한 코드가 아니라서 독자들은 코드를 다 읽어보진 않을것이다. 또한 그 후에 진행되는 코드에대한 설명이 머리속에 이해가 안돼 다시 위로올라와서 코드를 확인하는 과정을 반복할것이다. 이는 좋은 방식이 아니라 생각되는데 조금 더 효과적으로 전달하려면 다음과 같은 방법은 어떨까?
&gt; - 1.Dropdown.jsx
```JS
const Dropdown = ()=&gt;{
    return &lt;DropdownWrapper&gt;
          &lt;DropdownTrigger/&gt;
        &lt;DropdownItem/&gt;
      &lt;/DropdownWrapper&gt;
}</code></pre><ul>
<li>2.DropdownTrigger.jsx<pre><code class="language-JS">const DropdownTrigger=()=&gt;{
return &lt;DropdownTrigger/&gt;...구성요소&lt;DropdownTrigger/&gt;
}     </code></pre>
</li>
<li>3.DropdownItem.jsx<pre><code class="language-JS">const DropdownTrigger=()=&gt;{
return  &lt;DropdownItem&gt;...구성요소&lt;DropdownItem/&gt;
}     </code></pre>
</li>
</ul>
<p>위와같이 코드의 진행을 좀 더 조각내서 보여주는것이 이해를 돕는 더 좋은 방법아닐까 생각이된다.
설명해야할 내용에 해당하는 코드들을 하나씩 보여주고 그에 맞는 자세한 설명을 추가하는 방식이 예시코드를 이해시키기 가장 적합한 방식일 것이다.</p>
<p>이렇게 함께 예시를통해 경험한 코드를 각자 환경에 맞게 적용해 볼수있는 기회를 제공하는 것이 좋은 글의 요소라고 생각된다.</p>
<h2 id="5그-외">5.그 외</h2>
<p>그 외적으로 gif,이모지 를 활용해 재밌는것들을 보여주며 글의 흥미를 불러일으키는 것들도 좋은 글의 요소가 될 것이다.</p>
<h1 id="🎉마치며">🎉마치며...</h1>
<p>당연히 모든사람의 생각이 다르기때문에 좋은글에 대한 <strong>정답</strong>또한 없을것이라 생각한다. 나도 글을작성해본 경험이 아주적기때문에 이 글이 잘 작성한 글 이라고 할 수는 없을것 같다. </p>
<p>처음엔 velog작성을 포트폴리오를 위해 작성하게 되었지만 많은 글을 읽어보고 작성해보면서 누군가작성한 글이 나에게 큰 도움이 되었고 나 또한 좋은 영향력을 다른사람들에게 돌려주고 싶다.
나와같이 velog작성을 처음해보고 어떻게 적어야할지 고민인 사람들에게 조금이나마 도움이 되었으면 좋겠고 다들 어떻게 각자의 방식으로 이야기를 풀어내면 좋을지 생각해 볼 수있는 계기가 됬으면 좋겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[관심사분리 (Logic vs View) 2탄]]></title>
            <link>https://velog.io/@chan_snk/%EA%B4%80%EC%8B%AC%EC%82%AC%EB%B6%84%EB%A6%AC-Logic-vs-View-2%ED%83%84</link>
            <guid>https://velog.io/@chan_snk/%EA%B4%80%EC%8B%AC%EC%82%AC%EB%B6%84%EB%A6%AC-Logic-vs-View-2%ED%83%84</guid>
            <pubDate>Fri, 12 Apr 2024 22:41:35 GMT</pubDate>
            <description><![CDATA[<h1 id="✨들어가면서">✨들어가면서...</h1>
<p><a href="https://velog.io/@chan_snk/%EA%B4%80%EC%8B%AC%EC%82%AC%EB%B6%84%EB%A6%ACLogic-vs-View">1편</a>의 마지막 <code>관심사 분리의 시작을 큰 관점에서 본다면 View vs Logic 을 나누눈 것부터 시작이 아닐까해서 이 글을 작성해보았다</code>라는 멘트와 함께 마무리 지었던것이 기억난다.</p>
<p>그렇다면 이번 2편에서는 단순히 View 와 Logic을 나누는 것 뿐만아니라 어떤 방법을 사용해서 나눌건지, 또한 그 이점은 무엇인지에 대해서 알아보고자 한다.</p>
<h1 id="🚗시작">🚗시작</h1>
<p>2편을 작성하게된 이유는 dropdown이라는 컴포넌트를 만드는 과정에서 부터 출발한다.</p>
<p>이번주차 과제로 그동안 해보고 싶었던것을 구현해볼 시간이 생겼고 아직 컴포넌트로 만들어보지 않았던 dropdown을 만들게 되었다. </p>
<p>항상 작업해오던 방식으로 다음과 같이 <code>dropdown</code>컴포넌트를 생성했다.</p>
<blockquote>
<pre><code class="language-JS">// dropdown.jsx
const Dropdown = ({ items }) =&gt; {
  const [isOpen, setIsOpen] = useState(false)
  const [selectedOption, setSelectedOption] = useState(&quot;select&quot;)
  const boxRef = useRef(null)
  const Arrow = isOpen ? &quot;⬆️&quot; : &quot;⬇️&quot;
  return (
    &lt;DropDownWrapper onBlur={() =&gt; setIsOpen(false)} ref={boxRef} {...rest}&gt;
      &lt;DropDownTrigger onClick={() =&gt; setIsOpen((prev) =&gt; !prev)}&gt;
        &lt;Title&gt;{selectedOption}&lt;/Title&gt;
        &lt;div&gt; {Arrow}&lt;/div&gt;
      &lt;/DropDownTrigger&gt;
      {isOpen &amp;&amp; (
        &lt;DropDownItemsWrapper&gt;
          {items.map((item, idx) =&gt; (
            &lt;DropDownItems
              isOpen={isOpen}
              key={idx}
              onMouseDown={() =&gt; {
                setSelectedOption(item)
                setIsOpen(false)
              }}

              {item}
            &lt;/DropDownItems&gt;
          ))}
        &lt;/DropDownItemsWrapper&gt;
      )}
    &lt;/DropDownWrapper&gt;
  )
}
export default Dropdown
 ... styled-components 를사용한 css code들 ...</code></pre>
</blockquote>
<pre><code>
위와같이 하나의 dropdown.jsx파일을 생성하고 그 아래에 state 와 event를 관리하는 함수들이 하나로 엮여서 딱봐도 한눈에 들어오지 않는다는 느낌이든다.
개선의 여지가 있을것 같은데 그래서 여러가지 dropdown과 관련된 글들을 검색하기 시작했다.

# 📃어떤 글

자주사용하게 될 컴포넌트인데 좀더 좋은방법은 없을까? 하는 생각에 관련 글들을 많이 읽어보았다. 

그 중 [dropdown 을 만드는 글](https://ykss.netlify.app/translation/headless_component_a_pattern_for_composing_react_uis/)을 읽고난 후 방향을 잡을 수 있었다.
위 글에서는 어떻게 관심사분리를 진행했는지를 순서대로 보여준다. 또한 그렇게 했을때의 효과를 설명해주고 있는데 마침 위에서 작성한 내가처음 만든 `dropdown`과 같은 코드로 시작을 하고 있어서 나도 이번 글에서 [dropdown 을 만드는 글](https://ykss.netlify.app/translation/headless_component_a_pattern_for_composing_react_uis/)의 진행방식을 차용해서 느낀점과 함께 애기해보려 한다.




# ✨dropdown 컴포넌트


## ☝초기의 dropdown
앞서 봤던것처럼 초기 나의 dropdown은 다음과 같은 구조였다.
```JS
const Dropdown = ({ items }) =&gt; {
   // ... state, event-handlers ... 와같은 다양한 것들
  return (
  // ... 컴포넌트의 view를 구성하는 부분...
}
export default Dropdown
 ... styled-components 를사용한 css code들 ...</code></pre><p>지금까지는 이러한 형태를 문제없이 사용해왔는데 여기서 문제가될수 있는부분은 어떤 것일까?</p>
<p>바로 <code>관리</code> &amp; <code>확장성</code> 면에서 나의 코드는 좋지 못할 수 있다고 보여진다.</p>
<h3 id="1-관리">1 관리</h3>
<p>View적인 측면에서 우선 생각해보자
하나의 DropDown 컴포넌트로 관리하게 되면 코드를 추가하는 것이 복잡해질 가능성이 있다.
그래서 다음과 같은 방법으로 DropDown컴포넌트를 관리해보자.</p>
<blockquote>
<pre><code class="language-JS"> &lt;DropDownWrapper&gt;
      &lt;DropDownTrigger&gt;
        &lt;Title&gt;{selectedOption}&lt;/Title&gt;
        &lt;div&gt; {Arrow}&lt;/div&gt;
      &lt;/DropDownTrigger&gt;
      {isOpen &amp;&amp; (
        &lt;DropDownItemsWrapper&gt;
          {items.map((item, idx) =&gt; (
            &lt;DropDownItems&gt;{item}&lt;/DropDownItems&gt;   
          ))}
        &lt;/DropDownItemsWrapper&gt;
      )}
    &lt;/DropDownWrapper&gt;
  )
}</code></pre>
</blockquote>
<pre><code>
dropdown 컴포넌트는 위와같이 trigger가 존재하고 trigger를 클릭할때마다 isopen이라는 상태가 토글되면서 그아래 items라는 컴포넌트가 보여지는 구조이다.

코드의 구성을 보기위해 props를 모두제거한 상태로 작성을했는데 구조가 한눈에 들어오는 이상적인 구조는 아닌것 같다. 

위 글에서 제시한 방법에 따라 view를 더 명확하게 보기위해서 trigger.jsx와 items.jsx라는 두가지 컴포넌트로 나누어 보자.
&gt; 
```JS
// dropdownTrigger.jsx
const DropDownTrigger = ({ isOpen, selectedOption, clickCallback }) =&gt; {
  const Arrow = isOpen ? &quot;⬆️&quot; : &quot;⬇️&quot; // 위아래 화살표모양의 아이콘
  return (
    &lt;S.DropDownTriggerWrapper onClick={() =&gt; clickCallback()}&gt;
      &lt;S.Title&gt;{selectedOption}&lt;/S.Title&gt;
      &lt;div&gt; {Arrow}&lt;/div&gt;
    &lt;/S.DropDownTriggerWrapper&gt;
  )
}
export default DropDownTrigger
-------------------------------------------------------------------
// dropdownItems.jsx
const DropdownItems = ({ clickCallback, items }) =&gt; {
  return (
    &lt;S.DropDownItemsWrapper&gt;
      {items.map((item, idx) =&gt; (
        &lt;S.DropDownItems
          key={idx}
          onMouseDown={() =&gt; {
            clickCallback(item)
          }}
        &gt;
          {item}
        &lt;/S.DropDownItems&gt;
      ))}
    &lt;/S.DropDownItemsWrapper&gt;
  )
}
export default DropdownItems


위와같이 두가지의 더 작은 조각으로 분리하고 두 컴포넌트를 dropdown.jsx에서 불러와보자.
&gt;  ```JS
// Dropdown.jsx
const Dropdown = ({items})=&gt;{
    &lt;DropDownWrapper&gt;
      &lt;DropDownTrigger
        isOpen={isOpen}
        clickCallback={onClickTrigger}
        selectedOption={selectedOption}
      /&gt;
      {isOpen &amp;&amp; &lt;DropdownItems items={items} clickCallback={onClickItems} /&gt;}
    &lt;/DropDownWrapper&gt;
  )
}
export default Dropdown</code></pre><p>view를 명확하게 보기위해서 컴포넌트를 나눴더니 목표로했던 <code>관리</code>적인 측면에서 더 좋아보인다.
만약 추가될 컴포넌트가 생긴다면 더 효과적으로 수정 할 수 있을것 같다.</p>
<h3 id="2-확장성">2 확장성</h3>
<p>dropdown컴포넌트이기때문에 프로젝트의 요구사항에 따라 단순 click event뿐만 아니라 keyboard event와같은 다양한 기능들이 추가 될 수가있다.</p>
<p>예시 상황으로 keyBoard event가 추가된다고 생각해보자.</p>
<blockquote>
<pre><code class="language-JS">// DropDown.jsx
const Dropdown = ({ items, ...rest }) =&gt; {
  const [isOpen, setIsOpen] = useState(false)
  const [selectedOption, setSelectedOption] = useState(&quot;select&quot;)
  const boxRef = useRef(null)
  const onClickTrigger = () =&gt; {
    setIsOpen((prev) =&gt; !prev)
  }
  const onClickItems = (item) =&gt; {
    setSelectedOption(item)
    setIsOpen(false)
  }
  const keyBoardEvent = (e)=&gt;{
    ... 키보드 이벤트를 정의할 부분 ...
  }
  return (
    &lt;S.DropDownWrapper
      onBlur={(e) =&gt; {
        setIsOpen(false)
      }}
      ref={boxRef}
      {...rest}

      &lt;DropDownTrigger
        isOpen={isOpen}
        clickCallback={onClickTrigger}
        selectedOption={selectedOption}
      /&gt;
      {isOpen &amp;&amp; &lt;DropdownItems items={items} clickCallback={onClickItems} /&gt;}
    &lt;/S.DropDownWrapper&gt;
  )
}
export default Dropdown</code></pre>
</blockquote>
<pre><code>
`   ... 키보드 이벤트를 정의할 부분 ...`의 코드는 많이 길어질 여지가있다.

DropDown컴포넌트를 더 명확하게 보기위해서 Trigger 와 Items 컴포넌트로 분리해서 파일의 관리 포인트를 줄였는데 위의 예시상황과 같이 새로운 event가 추가된다면 
DropDown컴포넌트의 `관리포인트` 가 많아질 것이고 이는 관리의 어려움으로 이어질 것이다.

그렇다면 어떻게 이 문제점을 해결 할 수 있을까?


# ✨View 와 Logic의 분리

위의 상황에서 문제된것 그것은 바로 추가되는 logic에대한 처리 일 것이다.
다음 과정에서 logic을 따로관리함으로써 view와 logic을 완전히 분리시켜보자.

## 🎁Custom Hooks활용

위 글에서 제시한 방법은 `Custom hooks`를 활용하는 것이다.

```JS
// use-drop-down.js
export const useDropDown = () =&gt; {
  const [isOpen, setIsOpen] = useState(false)
  const [selectedOption, setSelectedOption] = useState(&quot;select&quot;)
  const boxRef = useRef(null)
  const onClickTrigger = () =&gt; {
    setIsOpen((prev) =&gt; !prev)
  }
  const onClickItems = (item) =&gt; {
    setSelectedOption(item)
    setIsOpen(false)
  }
  const onClickWrapper = () =&gt; {
    setIsOpen(false)
  }

  return {
    isOpen,
    selectedOption,
    boxRef,
    onClickWrapper,
    onClickTrigger,
    onClickItems,
  }
}</code></pre><p>위와같이 <strong>use-drop-down.js</strong>를 생성하고 이를 DropDown.jsx에서 불러와서 구조를 확인하자.</p>
<blockquote>
<pre><code class="language-JS">const Dropdown = ({ items }) =&gt; {
  const {
    isOpen,
    selectedOption,
    boxRef,
    onClickWrapper,
    onClickTrigger,
    onClickItems,
  } = useDropDown()
  return (
    &lt;S.DropDownWrapper onBlur={onClickWrapper} ref={boxRef}&gt;
      &lt;DropDownTrigger
        isOpen={isOpen}
        clickCallback={onClickTrigger}
        selectedOption={selectedOption}
      /&gt;
      {isOpen &amp;&amp; &lt;DropdownItems items={items} clickCallback={onClickItems} /&gt;}
    &lt;/S.DropDownWrapper&gt;
  )
}
export default Dropdown
... css code들 ...</code></pre>
</blockquote>
<pre><code>
hooks를 적용한 결과물은 다음과 같다. 우리가 Dropdown컴포넌트에서 확인해야할 부분은 바로 view에 대한 부분이다. logic에대한 부분은 hooks로 관리하면서 다음과 같이 깔끔해진 결과물을 눈으로 확인 할 수 있다.

## 😎분리해야하는 이유
위의 예시코드에서 확인한 것처럼 view와 logic을 분리해서 코드가 한눈에 들어오게 되었다.
이로인해 DropDown 컴포넌트는 `유지보수`&amp; `재사용` 적인 측면에서 이점이 생겼다. 

### 1. 유지보수
지금까지의 작업을 통해서 우린 DropDown컴포넌트를 **유지 보수**하기 쉽게 만들었다.

- 만약 view대한 수정이 필요하다면 ? ==&gt; DropDown.jsx 컴포넌트 혹은 하위 컴포넌트를 수정해주면 된다.
- 만약 logic에대한 수정이 필요하다면 ? ==&gt; use-drop-down.js파일에서 추가,수정,삭제 되는 logic을 추가해주고 간단하게 DropDown컴포넌트에 불러와서 적용시켜주기만 하면된다.

### 2. 재사용
모든 컴포넌트를 다음과같은 형태로 만드는 것은 비효율적인 과정일 수 있다.
하지만 로그인페이지의 input 혹은 DropDown 같은 컴포넌트는 어떤 프로젝트에서도 사용될 가능성이 있다. 

logic을 view에서 분리시킴으로써 이런 컴포넌트들은 어떤 프로젝트에서든 재사용하기 쉬워지게 되었다.

실제 DropDown컴포넌트를 재사용한 예시를 보며 알아보자.

# ✨적용

간단하게 TodoList를 만드는 프로젝트를 진행하던 중 DropDown을 사용해야했는데 
만들어둔 DropDown을 재사용 해봤다. 이번 프로젝트의 css는 tailwindCss를 사용했는데 적용과정을 살펴보자.

&gt; ```JS
//view의 구조
  &lt;S.DropDownWrapper &gt; //전체 dropdown을 감싸는 Wrapper
      &lt;DropDownTrigger // 펼쳐지기전 dropdown의 형태
      /&gt;
      {isOpen &amp;&amp; &lt;DropdownItems/&gt;} // 펼쳐진 item들의 형태
  &lt;/S.DropDownWrapper&gt;
  )
}</code></pre><p>logic은 기본동작만 구현해놨기때문에 간단하게 use-drop-down.js를 가져오기만 하면된다.
dropdown은 다음과 같은 구조를 가지기 때문에 프로젝트의 요구사항에 맞게 뼈대에 옷만 입혀주는 과정을 해주면 된다 다음과 같이 말이다. </p>
<blockquote>
<pre><code class="language-JS"></code></pre>
</blockquote>
<div
      className="relative w-[20rem] cursor-pointer"
      onBlur={onClickWrapper}
      ref={boxRef}>
      <DropDownTrigger
        isOpen={isOpen}
        clickCallback={onClickTrigger}
        selectedOption={urlParams.get('option')}
      />
      {isOpen && <DropdownItems items={items} clickCallback={onClickItems} />}
    </div>
```

<p>같은 과정이기때문에 예시로 하나의 예시코드만 불러왔지만 trigger와 items에도 동일한 작업을 해주면 다음과 같이 잘 동작되는 것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/15c31023-e3fe-4590-a322-a634e6f06bff/image.gif" alt=""></p>
<h1 id="🙌마치며">🙌마치며...</h1>
<p>이번 작업을 진행하면서 view 와 logic을 분리하는 과정은 좀 귀찮은 작업일 수 있지만 후에 얻을 이점을 생각한다면 꼭 필요한 작업이 아닌가 하는 생각이 든다.</p>
<p>지금까지 작업한 과정을통해 dropdown 컴포넌트는 headless component가 되었는데.
view와 logic을 분리하는데 가장 큰 역할 을 한것은 hooks라는생각이 든다.</p>
<p>사실 지금까지 hooks를 왜사용하는지 잘 이해하지 못했엇다. 그저 상태를 포함한 함수? 라고 만 생각이 들었엇는데 일련의 과정을 통해 hooks의 장점을 이해하게 되었고 custom hooks를 사용하는것이 view와 logic을 분리하는데 결정적인 역할을 했다는 생각이 든다. 또한 logic 과 view를 나눈다는 것이 확장성, 관리 측면에서 어떤 이점을 가져다주는지 생각해 볼 수 있엇던것 같다.</p>
<h1 id="참고자료👇">참고자료👇</h1>
<p><a href="https://ykss.netlify.app/translation/headless_component_a_pattern_for_composing_react_uis/">(번역) 헤드리스 컴포넌트: 리액트 UI를 합성하기 위한 패턴</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Intersection Observer를 활용한 무한 스크롤 만들어보기]]></title>
            <link>https://velog.io/@chan_snk/Intersection-Observer%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@chan_snk/Intersection-Observer%EB%A5%BC-%ED%99%9C%EC%9A%A9%ED%95%9C-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%8C%EB%93%A4%EC%96%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 04 Apr 2024 10:37:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/chan_snk/post/373ae91e-0f6b-44a0-9c43-ac26a9134724/image.jpg" alt=""></p>
<h1 id="🤷들어가면서">🤷‍들어가면서...</h1>
<p>이번주에는 그동안 직접 구현해보고싶었던 intersection-observer를 활용해서 무한스크롤을 구현해봤다.</p>
<p>무한 스크롤을 구현하는 과정에서 겪었던 어려움과 해결 과정을 공유하고자 이 글을 작성해본다. 많은 사람들이 그러하듯 구현을 위해서 많은 검색을 통해 해결을 하게되는데 이번 무한 스크롤을 검색했을땐 doucument.querySelector와 같이 직접 DOM태그를 조작하는 방식에대한 예시가 많이 보였다. 하지만 지금까지 <strong>React로 작업했을땐 그런 방식을 사용하지 않았엇는데?</strong> 라는 의문이 들었다. 아래 글을 같이보며 어떤 방식으로 의문을 해결했고 무한스크롤을 구현했는지 알아보자.</p>
<p>무한스크롤을 구현하기위해 RadImage라는 간단한 프로젝트를 만들었다.
RadImage의 컨셉은 Random-Image라는 컨셉의 서비스인데 상단에 category-bar를 두고 해당 category에 맞는 random image를 보여주는 프로젝트이다.</p>
<p>무한스크롤의 동작을 이해하기 위해 RadImage Project를 보며 알아보자.</p>
<h1 id="🔧설계">🔧설계</h1>
<p>우선 RadImage의 설계를 간단하게 알아보자.</p>
<h2 id="1-landing-page">1. Landing Page</h2>
<p>간단하게 랜덤이미지 들을 보기전에 거쳐가는 페이지가 존재한다.
<img width="500" alt="image" src="https://github.com/ssi02014/react-query-tutorial/assets/144839872/b74354c5-896c-4bb4-a946-1e7db2b1cd0e">
위와같이 접속하면 Landing Page가 등장하고 <code>전시관으로</code> 버튼을 누르면 이제 부터 Random-Image들이 전시된 페이지를 볼 수 있다.</p>
<h2 id="2-gallery-page">2. Gallery Page</h2>
<p><strong>Gallery Page에서 보여질 것</strong></p>
<ul>
<li>어떤 주제의 사진을 볼 것인지 선택할 수 있는 category-bar</li>
<li>해당 주제에 맞는 Infinity-Scroll을 지원하는 이미지들</li>
</ul>
<h1 id="👓미리보기">👓미리보기</h1>
<p>우선 intersection-observer를 활용하기 전에 어떻게 동작하는지 알아보자.
<em>(글자 수를 줄이기 위해 io = intersection-observer로 사용하겠습니다.)</em></p>
<h2 id="1-io-인스턴스-생성하기">1. io 인스턴스 생성하기</h2>
<p>io를 사용하기 위해서는 다음 과 같이 instance를 생성해야한다.</p>
<pre><code class="language-JS">const intersectionObserver = new InterSectionObserver((callback,option))</code></pre>
<p>그 후 생성한 io를 이용해서 관찰하고싶은 DOM요소, 예를들어<code>&lt;img/&gt;</code>태그 와 같은 요소들을 관찰하는 것이다.</p>
<pre><code class="language-JS">//example
const images = document.querySelectorAll(&quot;img&quot;)
images.forEach((img)=&gt;io.observe(img))
</code></pre>
<p>위의 예시코드 처럼 document.querySelectorAll() 를 사용해 DOM요소를 특정하고 </p>
<p>io에서는 여러가지 메서드를 제공하는데 그 중 사용할 몇가지만 살펴보자.</p>
<blockquote>
<ul>
<li><ol>
<li>observe()
<code>io.observe(관찰대상)</code>를 통해 원하는 요소를 관찰 시작</li>
</ol>
</li>
</ul>
</blockquote>
<ul>
<li><ol start="2">
<li>unobserve()
<code>io.unobserve(관찰대상)</code>를 통해 관찰대상 해제</li>
</ol>
</li>
<li><ol start="3">
<li>disconnect()
만일 여러개의 관찰대상이 있었다면 한번에 해제하기 위해 <code>io.disconnect()</code>를 사용해 모든 관찰대상을 해제 할 수 있다.</li>
</ol>
</li>
</ul>
<h2 id="2-io-parameter-사용하기">2. io parameter 사용하기</h2>
<p><strong>InterSectionObserver((entries,observer)=&gt;{},options)</strong></p>
<p><strong>1. options</strong></p>
<p>options는 객체 형태로 사용해야한다. 그 안에 특정 옵션들을 사용할 수 있는데 </p>
<ol>
<li>root =&gt; 관찰대상과의 교차점의 기준
따로 선언해주지 않으면 기본값은 null이고 이 때 뷰포트를 기준으로 삼는다.</li>
<li>rootMargin =&gt; root의 margin을 설정
css margin값과 같이 문자열안에 <code>rootMargin: &quot;상px 우px 하% 좌%&quot;</code>의 형태로
px이나 %단위로 선언해주면 된다 .</li>
<li>threshold =&gt; 관찰대상과 root의 교차되는 영역의 넓이에따라 observer를 실행
0~1의 값으로 선언해주면된다.</li>
</ol>
<pre><code class="language-JS">// example
const options = {
    root: null // viewport를 기준으로 삼는다
      rootMargin: &quot;0px 0px 15px 0px&quot; // root의 marginbottom을 15px로 설정
      threshold : 0.3 // 관찰대상이 root와 30% 겹쳐질때 관찰 시작
}

new InterSectionObserver((callback,options))</code></pre>
<p><strong>2. callback</strong></p>
<p>callback에는 두가지 parameter를 가질 수 있는데 관찰할 대상(Target)이 등록되거나 가시성(Visibility, 보이는지 보이지 않는지)에 변화가 생기면 관찰자는 콜백(Callback)을 실행한다.</p>
<p>이번 작업에서는 메서드중 isintersecting만 사용해볼 것인데 </p>
<pre><code class="language-JS">//example

const io = new IntersetionObserver((entries)=&gt;{
    entries.forEach((entry)=&gt;
    if(entry.isintersecting){ // 요소와 root가 option에만족하게 겹쳐졋을때 true
   // 관찰대상이 root영역과 겹쳣을때 동작할 logic 작성
})},{})</code></pre>
<p>위와같이 io에 관찰중일때의 동작과 option을 정의한 후 사용할 컴포넌트 에서 io instance를 생성함으로써 동작시킬 수 있다.</p>
<p>여기까지 프로젝트의 설계와 간단하게 interstion-observer에대해 간단하게 알아 봤으니 실제로 코드를 작성해서 동작시켜보자.</p>
<p>(<em>추가로 더 많은 메서드 들이 존재하는데 더 많은 내용은 아래 <code>참고자료</code> 에서 확인하시면 됩니다.</em>)</p>
<h1 id="✨queryselector로-관찰observe하기">✨querySelector로 관찰(Observe)하기</h1>
<h2 id="1설계">1.설계</h2>
<p>무한스크롤을 구현하기 위해서 검색해본 결과 많은 글에서 document.qeurySelectorAll(&quot;&quot;)을 사용해서 DOM요소를 특정하고 그 후에 DOM요소와 root가 교차했을때 다음 data를 불러오는 로직을 확인할 수 있었다.</p>
<p>이때까지만 해도 무한스크롤의 동작을 잘 이해하지못해서 동작의 이해를 위해 같은 방식으로 구현해보기로 했다. 설계는 다음과 같다.</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/f8d1f7f6-0099-4480-a517-8429d85ea5a3/image.png" alt=""></p>
<p>api로 데이터를 불러오는 것이아니라서 임의로 한번에 5개씩 이미지를 불러오도록 했다. 
root는 viewport로 설정하고 last-image와 viewport가 30%겹쳣을때 다음5개의 image를 불러오는 동작을 시켜보자.</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/7b174c04-c6fb-4547-b234-0b3453b62d10/image.png" alt=""></p>
<h2 id="2구현">2.구현</h2>
<h3 id="1loadimages">1.loadImages()</h3>
<p>우선 loadImages 함수를 만들어서 n개의 image를 불러오는 함수를 만들었다.</p>
<pre><code class="language-JS">// loadiImages.jsx

// params  imgLength :number - 전체 image데이터의 길이값 
// params  loadNum :number - 함수실행시 추가로 불러올 데이터 숫자 number

const loadimages = (imgLength = 0, loadAmount) =&gt; {
  return Array.from({ length: loadAmount }, (_, idx) =&gt; {
    if ((imgLength + idx) % loadAmount === loadAmount - 1) {
      return (
        &lt;img
          width={800}
          height={800}
          src=&quot;https://loremflickr.com/320/240/&quot;
          key={imgLength + idx + 1}
          className=&quot;last-image&quot;
        /&gt;
      )
    } else {
      return (
        &lt;img
          width={500}
          height={800}
          src=&quot;https://loremflickr.com/320/240/&quot;
          key={imgLength + idx + 1}
        /&gt;
      )
    }
  })
}
</code></pre>
<p>위와 같이 loadImages() 를 실행하면 loadAmount만큼의 image component를 생성하고 
마지막 ImageComponent에 className =&quot;last-image&quot;라는 것을 추가해 마지막 요소를 관찰대상으로 삼을 준비를 마쳤다.</p>
<p>이제 io를 정의하고 io를 통해서 무한스크롤을 구현해보자.</p>
<h3 id="2useinfinityscrollquery">2.useInfinityScrollQuery()</h3>
<pre><code class="language-JS">// use-infinty-scroll-query

export const useInfinityScrollQuery = () =&gt; {
  const [imageArr, setImageArr] = useState(loadimages(0, 5))

  const io = new IntersectionObserver(
    (entries) =&gt; {
      entries.forEach((entry) =&gt; {
        if (entry.isIntersecting) { // 마지막 이미지가 root와 30%교차했을때

          //요소중 className === last-image인 요소라면 className을 삭제
          if (entry.target.className === &#39;last-image&#39;) {
            entry.target.classList.remove(&#39;last-image&#39;)
          }
          const newArray = loadimages(imageArr.length, 5)
          setImageArr((prev) =&gt; [...prev, ...newArray])
        }
      })
    },
    { threshold: 0.3 },
  )

  return { imageArr, io }
}
</code></pre>
<p>customhook을 생성해서 img data를 담을 상태와 io를 반환하는 useInfinityScrollQuery() 를 생성했다. 
last-image라는 calssName을 가진 태그가있다면 기존 last-image라는 calssName을 지우고 loadImage()를 실행해 기존 imageArr뒤에 추가해준다.</p>
<p>loadImage()함수와 useInfinityScrollQuery()를 생성해서 모든 준비를 마쳤으니
페이지 컴포넌트에서 불러와보자.</p>
<h3 id="3실행">3.실행</h3>
<pre><code class="language-JS">// pagecomponent

     ...페이지구성 코드

  useEffect(() =&gt; {
    const lastImage = document.querySelector(&#39;.last-image&#39;)
    io.observe(lastImage)

    return () =&gt; {
      io.disconnect(lastImage)
    }
  }, [imageArr])

    ...페이지구성 코드
</code></pre>
<p>페이지 컴포넌트에서 다음과 같이 useEffect를 사용해서 io를 생성하고 DOM요소를 관찰하도록 설정해줬다.</p>
<h2 id="3결과물">3.결과물</h2>
<p> 눈으로 좀더 쉽게 확인하기 위해 마지막 이미지의 크기를 더 크게 두었다.</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/6a9523aa-8293-49da-b70c-f540c899df8a/image.gif" alt=""></p>
<p>위의 영상과 같이 교차점에 도착하면 className을삭제하면서 새로운 list를 가져오는 것을 확인 할 수 있었다.</p>
<h1 id="😱문제점">😱문제점</h1>
<p>검색을 통해 알아본 무한스크롤은 위의 코드와 같이 동작하는 방법을 많이 다루고있엇는데 React에서 직접 DOM 요소를 조작하는 것은 보편적으로 권장되지 않는다고 한다. 이는 React가 가상 DOM을 사용하여 실제 DOM 조작을 추상화하고, 직접적인 DOM 조작은 React의 상태 및 렌더링과 충돌할 수 있기 때문이다. React에서는 상태를 변경하여 UI를 업데이트하고, 이를 통해 React가 자동으로 가상 DOM을 조작하고 실제 DOM을 업데이트하도록 하는 것이 일반적이라고 한다.</p>
<p>직접 DOM 조작이 권장되지 않는 이유는 다음과 같은데</p>
<blockquote>
<ul>
<li>1.<strong>React의 상태와 동기화 문제</strong>: 직접적인 DOM 조작은 React의 가상 DOM과 동기화되지 않을 수 있다. 이는 예기치 않은 결과를 초래할 수 있으며, React의 상태 변경에 대한 업데이트를 놓칠 수 있다.</li>
</ul>
</blockquote>
<ul>
<li>2.<strong>성능 문제</strong>: React는 가상 DOM을 사용하여 필요한 최적화를 수행한다. 직접적인 DOM 조작은 React의 최적화 메커니즘을 우회하고 성능을 저하시킬 수 있다.</li>
</ul>
<p>같이 작업하는 고수 개발자님이 Ref를 사용하면 더 좋은 코드를 작성 할 수 있다고하셨는데 그래서 지금 작성한 코드로 프로젝트를 마무리 할 수는 없을것 같았다.
그래서 다른 방법을 찾아봤는데</p>
<p>☝바로 ref를 이용하는 방법이다. 다음 내용에서 더 알아보자.</p>
<h1 id="✨ref로-관찰observe하기">✨ref로 관찰(Observe)하기</h1>
<h2 id="1설계-1">1.설계</h2>
<p> useRef를 사용해 DOM요소를 관리하고 해당 DOM요소를 만나면 loadImages를 실행하도록하면 좋지않을까?</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/9fbe61c8-a492-4cbe-8e64-504aa30cbc02/image.png" alt=""></p>
<p>마지막DOM요소를 하나더 두고 해당 요소를 만나면 그 위에 상태로 관리되는 ImageArr를 추가하는 방식으로 구현하도록 설계했다.
(추후에 마지막DOM요소는 loading-spinner가된다.)</p>
<h2 id="2구현-1">2.구현</h2>
<h3 id="1getimginfoarray">1.getImgInfoArray()</h3>
<pre><code class="language-JS"> // getImgInfoArray.js


// params  keyword :string - 프로젝트의 요구사항 : 어떤 이미지를 불러올지 
// params  arrayLength :number - 몇개의 이미지를 불러올지

const getImgInfoArray = (keyword, arrayLength) =&gt; {
  const baseUrl = import.meta.env.VITE_APP_IMG_BASE_URL
  const timestamp = getTimeStamp()
  const result = []
  for (let i = 0; i &lt; arrayLength; i++) {
    const imgId = timestamp + i
    const imgSrc = baseUrl + `${keyword}/?random=${imgId}`
    result.push({
      id: imgId,
      src: imgSrc,
    })
  }
  return result
}
</code></pre>
<p>getImgInfoArray() 라는 함수를 생성해 arrayLength만큼의 이미지와 프로젝트의 요구사항에 맞는 id값을 담은 result를 return하는 함수를 생성한다.</p>
<h3 id="2useinfinityscroll">2.useInfinityScroll()</h3>
<pre><code class="language-JS">// useInfinityScroll.js

// params  searchKeyword :strign - 어떤 주제를 가진 이미지를 가져올지
// params  perPage :number - 한번에 몇개의 이미지를 불러올지

const useInfinityScroll = (searchKeyword, perPage) =&gt; {
  /**
   * @info 배열에는 아래와 같은 타입의 객체가 담깁니다.
   * {
   *  id: number
   *  src: string
   * }
   */
  const [imgInfoArray, setImgInfoArray] = useState([])

  const bottomElementRef = useRef(null)

  const intersectionObserver = useMemo(() =&gt; {
    return new IntersectionObserver(
      (entries) =&gt; {
        entries.forEach((entry) =&gt; {
          if (entry.isIntersecting) {
            const newArray = getImgInfoArray(searchKeyword, perPage)
            setImgInfoArray((prev) =&gt; [...prev, ...newArray])
            return
          }
        })
      },
      {
        rootMargin: &#39;60px&#39;,
        threshold: 1,
      },
    )
  }, [searchKeyword, perPage])

  useEffect(() =&gt; {
    if (bottomElementRef === null || !bottomElementRef.current) return
    const target = bottomElementRef.current
    intersectionObserver.observe(target)

    return () =&gt; {
      intersectionObserver.disconnect(bottomElementRef)
    }
  }, [bottomElementRef, intersectionObserver])

  return { imgInfoArray, bottomElementRef }
}
</code></pre>
<p>다음과 같이 useInfinityScroll()을 생성하고 imageData와 Ref를 return하는 customhook을 생성했다.
useInfinityScroll()을 생성한후 페이지컴포넌트에서 비구조화할당을 통해 간단하게 사용해 볼 수 있을거 같다.</p>
<pre><code class="language-JS">&lt;SpinningCrirle ref={bottomElementRef}/&gt;</code></pre>
<p>useInfinityScroll()을 페이지 컴포넌트에서 불러온후 imageArr로 화면에 image들을 보여주고 가장 아래 위와같이 spinning circle에 ref를 연결해주면 목표했던 동작을 완료 할 수 있엇다.</p>
<h1 id="🎉결과물시연영상">🎉결과물(시연영상)</h1>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/36e8d901-44f7-4a47-8dae-d74778a4c6f7/image.gif" alt=""></p>
<h1 id="😘마치며">😘마치며...</h1>
<p>이 글은 단순히 무한스크롤을 구현하는 과정을 보여주기 위해 작성한 글은 아니다.
구현과정에서 어떤 문제점이 있엇는지, 또 어떻게 그것을 해결했는지 까지의 과정을 공유하고 싶었다. 
새로운 것을 공부할땐 항상 검색을 통해 구현하게 되고 그 방법이 좋은지 안좋은지에 대한 판단은 항상 동작을 다 이해한 후에 내릴 수 있었다.
비록 첫번째 코드는 앞으로 사용하지 않겠지만 과연 저 과정이 없었다면 왜 DOM을 직접 조작하면 안되는지에 대한 내용을 알 수 있었을까? 또한 무한스크롤의 동작을 쉽게 이해할 수 있었을까? 하는 생각이든다. </p>
<p>안좋은 코드는 있어도 안좋은 경험은 없다고 생각하기 때문에 이 글을 읽은 모두가 더 좋은 방법이 있는지 어떤 방식으로 기능이 구현되는지에 대해 계속해서 의문을 품고 공부했으면 좋겟다.</p>
<h1 id="👇참고자료">👇참고자료</h1>
<ul>
<li><a href="https://heropy.blog/2019/10/27/intersection-observer/">Intersection Observer - 요소의 가시성 관찰</a></li>
<li><a href="https://velog.io/@khy226/intersection-observer%EB%9E%80-feat-%EB%AC%B4%ED%95%9C-%EC%8A%A4%ED%81%AC%EB%A1%A4-%EB%A7%8C%EB%93%A4%EA%B8%B0">intersection observer란? (feat: 무한 스크롤 만들기)</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[🤷‍♂️성능 최적화에 대해 알아보기 전에 ...]]></title>
            <link>https://velog.io/@chan_snk/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%EC%95%8C%EA%B8%B0%EC%A0%84%EC%97%90-</link>
            <guid>https://velog.io/@chan_snk/%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94%EB%A5%BC-%EC%95%8C%EA%B8%B0%EC%A0%84%EC%97%90-</guid>
            <pubDate>Sat, 30 Mar 2024 01:45:46 GMT</pubDate>
            <description><![CDATA[<h1 id="들어가면서">들어가면서...</h1>
<p>이번 과제에서는 Core Web Vital에 대해 알아보고 어떻게 성능을 향상시킬 수 있는 지 방법을 찾아보는 것을 진행했다. </p>
<p>Core Web Vital에는 LCP, FID, CLS 세가지가 있는데 해당 내용을 들을 검색하고 이해하는 과정이 만만치 않다고 느꼇다. 그 이유는 React에서 화면의 Render하는 과정이 어떻게 동작하는지 모르고 DOM은 무엇이고 화면은 어떻게 그려지는지 등에 대한 이해가 부족하기 때문에 진행이 안되는 문제점이 있었다.</p>
<p>처음 이 내용을 접하는 다른 사람들도 같은 어려움을 겪지 않을까 하는 생각에 이에 대해 같이 다뤄보려한다.</p>
<h1 id="✨window">✨Window</h1>
<p>우선 뒤에 내용들을 살펴보기전에 간단하게 Window란 무엇인지 보고 넘어가자.</p>
<blockquote>
<p>자바스크립트의 최상위 객체 / 전역 객체 / 모든 객체가 소속된 객체</p>
</blockquote>
<p>브라우저를 의미하는 window라는 최상위 객체가 존재한다. 브라우저는 수 많은 객체들로 구성이 되는데 그 중 <code>최상위</code> 객체가 바로 window인 것이다. 그 아래 DOM, BOM, JS가 존재하는데 흔히 JS코드로 작성을 할때 alert(), comfirm(), 등등 우리가 그냥 사용했던 메서드들 또한 window.alert(), window.confirm() 이었던 것이다.</p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/606bc5d1-c7f0-4a02-831c-70da24fe73c2/image.png" alt=""></p>
<p>브라우저는 간단하게 위와 같은 구조로 되어있다고 한다. 여러 객체들을 Tree형태로 관리하고 있고 그 중 최상위 객체가 Window인 것이다. 그럼 이제 DOM,BOM에 대해 알아보자.</p>
<h1 id="✨domdocument-object-model">✨DOM(Document Object Model)</h1>
<h2 id="dom이란-무엇인가">DOM이란 무엇인가?</h2>
<blockquote>
<p>DOM(문서객체모델) : 객체 지향 모델로써 구조화된 문서를 표현하는 방식</p>
</blockquote>
<p>React를 공부한 분들은 모두 한번쯤은 들어봤을 것이다 <code>DOM TREE를 그린다</code>
라는 말은 한번쯤은 들어봤을 것이다. 그렇다면 DOM TREE가 무엇인가?</p>
<p>우리는 React를 통해서 코드들을 작성하고 이 코드를 바탕으로 화면에 무엇인가를 그려낸다. 이때 항상 HTML코드가 존재할 탠데 브라우저는 이 HTML코드를 읽을 수 없다.
HTML코드는 단순 문자열에 불과하기 때문이다. 
그렇기에 브라우저가 HTML tag들을 이해하기 위해서 브라우저가 이해 할 수있는 DOM TREE형태로 변형한다. 다음과 같은 형태로 DOM TREE가 구성된다. </p>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/4d1f918c-1e7f-4a2b-9301-a1f70258f61a/image.png" alt=""></p>
<p>다음은 브라우저가 HTML을 이해하는 방식이다.</p>
<ul>
<li><p>파싱(Parsing): 브라우저는 HTML 코드를 읽고 파싱하여 문서 구조를 이해한다. 이 과정에서 HTML 코드는 각각의 요소(element), 속성(attribute), 텍스트 노드(text node) 등으로 분석된다. 예를 들어 <code>&lt;div&gt;</code> 요소는 DOM에서 하나의 요소 노드로 해석되고, 속성들은 해당 요소 노드의 속성으로 해석된다.</p>
</li>
<li><p>DOM 트리 구성: 파싱된 결과를 바탕으로 브라우저는 DOM(Document Object Model) 트리를 구성한다. DOM 트리는 HTML 문서의 구조를 표현하는 트리 구조이며, 각각의 노드는 HTML 요소를 나타낸다. DOM 트리의 루트 노드는 <code>&lt;html&gt;</code>요소이고, 그 하위에는 <code>&lt;head&gt;</code> 요소와<code>&lt;body&gt;</code>요소 등이 포함된다.</p>
</li>
<li><p>브라우저는 이렇게 구성된 DOM 트리를 사용하여 HTML 문서의 내용을 이해하고 렌더링한다. DOM 트리를 통해 브라우저는 각 요소의 위치, 스타일, 이벤트 등을 파악하고 화면에 적절히 표시할 수 있다. 따라서 HTML 코드가 DOM 트리로 구성되면 브라우저는 해당 문서를 이해하고 사용자에게 화면에 표시할 수 있다.</p>
</li>
</ul>
<h1 id="✨bombrowser-object-model">✨BOM(Browser Object Model)</h1>
<h2 id="bom이란-무엇인가">BOM이란 무엇인가?</h2>
<blockquote>
<p> BOM(브라우저객체모델) : 웹브라우저의 창이나 프래임을 추상화해서 프로그래밍적으로 제어할 수 있도록 제공하는 수단이다.</p>
</blockquote>
<p> BOM이란 위와같은 것인데 무슨 말일까? 
 BOM의 역할은 웹 브라우저의 버튼, URL 주소 입력 창, 타이틀 바 등 웹브라우저 윈도우 및 웹페이지의 일부분을 제어할수 있게끔 하는 것이다.</p>
<p>  흔히 사용하는 BOM객체로는 </p>
<ul>
<li><p>window: Global Context. 브라우저 창 객체</p>
</li>
<li><p>screen: 사용자 환경의 디스플레이 정보 객체</p>
</li>
<li><p>location: 현재 페이지의 url을 다루는 객체</p>
</li>
<li><p>navigator: 웹브라우저 및 브라우저 환경 정보 객체</p>
</li>
<li><p>history: 현재의 브라우저가 접근했던 URL history</p>
<p>와 같은 것들이 있는데 React로 작업을 해본 분들이라면 다들 한번쯤은 사용해 봣을 법한 것들이다. 사실 이것들이 Window 객체 아래에있는 것들 이었던 것이다. </p>
</li>
</ul>
<h1 id="✨랜더링-과정">✨랜더링 과정</h1>
<p>  그렇다면 지금까지 React를 통해 화면을 그리기 전에 뒤에서 어떤 동작이 있는지 알아 봤는데 이젠 화면을 Render하는 과정에는 어떤 과정이 있는지 알아보자.</p>
<p>모두 아는 것처럼 화면을 구성하기 위해서는 HTML,CSS,JS파일들을 하나로 묶어서 화면을 그리게 된다. 그렇다면 그 과정은 어떻게 될까?</p>
<h2 id="1parsing">1.Parsing</h2>
<p>  브라우저는 HTML을 이해하기 위해 DOM TREE로 바꾸는 과정이 필요하다.
  이 과정에서 HTML에 CSS가 포함 된다면 CSSOM(CSS Object Model) Tree 를 또 그리게 되는데 
  <img src="https://velog.velcdn.com/images/chan_snk/post/9a40886d-7cb7-453a-9793-98fa901c296a/image.png" alt=""></p>
<p>  위 와 같은 tree구조를 갖게 된다. 노란색으로 된 부분이 각 요소에 어떤 CSS가 포함 되었는지 파악하는 CSSOM Tree이고 이를 바탕으로 화면을 구성하게 된다.</p>
<p>DOM Tree 와 CSSOM Tree를 합쳐서 랜더링을 시키기 위해 Render Tree를 구성하게 된다.</p>
<h2 id="2layout">2.Layout</h2>
<p>Layout 단계에서는 렌더 트리를 화면에 배치하기 위해 정확한 위치와 크기를 계산한다. 
최상단인 루트부터 노드를 순회하면서 노드의 위치와 크기를 계산하고 렌더 트리에 반영한다.</p>
<h2 id="3paint">3.Paint</h2>
<p>  이 단계에서는 앞서 Layout이 계산된 값들을 이용해 실제 픽셀로 바꾸는 단계이다. 
  그 후에 <code>composite</code> 단계를 거쳐서 실제 화면에 나타낸다.</p>
<h1 id="✨reflow--repaint">✨Reflow &amp; Repaint</h1>
<p>  React에서는 가상DOM을 만들어 기존 DOM과 달라진 부분을 비교해서 화면을 그린다고 알고있다. 
  화면을 render하는 원인은 뷰포트의 크기변경, HTML요소의 변화에 따라 Layout을 다시그리는 경우 등이 있다.</p>
<p> Render를 다시 하는 과정에서 Layout을 수정하는 경우 <strong>Reflow</strong>
  픽셀만 다시 Render하는 경우를** Repaint**라고 한다.</p>
<p>  그렇다면 앞서 살펴본 내용을 바탕으로 화면을 어떻게 그려내는지 알아보자.</p>
<h2 id="1reflow">1.Reflow</h2>
<p>  Reflow과정은 Layout계산부터 다시하는 과정이다. 즉 DOM Tree를 다시 그리게 되고 이는 DOM요소의 위치, 크기 를 다시 계산하는 과정이 된다. Relofw과정에서 Layout계산후 Repaint과정까지 진행해야 하므로 큰 비용이 발생하는 과정이다.</p>
<h2 id="2repaint">2.Repaint</h2>
<p>  Repaint과정은 Layout은 그대로이고 color, backgroundColor, visibility와 같은 속성이 변할때 일어난다. 
  Reflow와는 다르게 Repaint과정 후 바로 composite과정으로 넘어가기 때문에 비용이 더 싼 작업이라고 볼 수 있다. 이는 성능향상의 요인으로 작용 할 수 있어보인다.</p>
<p> 이해를 위해 아래와 같은 사진을 첨부한다. 
  <img src="https://velog.velcdn.com/images/chan_snk/post/4ea106f4-1f9a-4dd2-8e85-e3e2f6ad1fcb/image.png" alt=""></p>
<h1 id="✨글을-마치며">✨글을 마치며...</h1>
<p> 지금까지 살펴본 내용을 바탕으로 Core Web Vital의 지표들이 낮아지는 원인과 개선 방안에 대해 실마리를 얻을 수 있을 것 같다.</p>
<p>  화면을 그리기 위한 배경 지식을 알았으니 성능을 향상 시키기 위해서는 불필요한 파일, css 만드는 것을 지양 해야겠다는 것을 알 수 있다.</p>
<p>  이면의 과정을 알기 전까진 왜 불필요한 tag들 사용하지 않는 변수,파일들을 생성하면 안되는지 에 대한 이해 없이 코드를 작성했던것 같다.</p>
<p>  이번 과제를 하며 공부하는 내용의 배경지식들까지 알아가고 이것을 바탕으로 더 좋은 코드를 작성하는 과정이 필요해 보인다고 느꼇다. 다들 궁굼한 것을 열심히 찾아보고 성장하는 사람이 될 수 있으면 좋겠다. </p>
<p>모두 검색이나 링크를 통해 더 자세한 내용꼭 확인해 봤으면 좋겠다.</p>
<p>긴 글을 읽어주셔서 감사합니다~~❤ </p>
<h1 id="👇참고-자료">👇참고 자료</h1>
<p>  <a href="https://enjoydev.life/blog/frontend/1-dom">
웹을 이루는 핵심 요소를 알아봅시다! - DOM, BOM, JavaScript</a>
[BOM (Browser Object Model) 완벽 정복하기]
  (<a href="https://geniee.tistory.com/33">https://geniee.tistory.com/33</a>)
  <a href="https://velog.io/@bcgrhio/Reflow-%EC%99%80-Repaint">Reflow 와 Repaint</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[😱Vite vs Webpack 무엇이다른가...]]></title>
            <link>https://velog.io/@chan_snk/Vite-vs-Webpack-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%8B%A4%EB%A5%B8%EA%B0%80</link>
            <guid>https://velog.io/@chan_snk/Vite-vs-Webpack-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%8B%A4%EB%A5%B8%EA%B0%80</guid>
            <pubDate>Fri, 29 Mar 2024 22:12:33 GMT</pubDate>
            <description><![CDATA[<h1 id="👏들어가기에-앞서">👏들어가기에 앞서...</h1>
<h2 id="번들러란">번들러란?</h2>
<p>먼저 Vite와 Webpack에 대해 알아보기 전 <strong>번들러</strong> 란 무엇인지 알아야 할 거 같다. </p>
<h3 id="번들러를-왜-사용하는가-">번들러를 왜 사용하는가 ?</h3>
<blockquote>
<p>번들러란? =&gt; 여러개의 파일들을 하나의 파일로 묶어주는 도구</p>
</blockquote>
<p>처음엔 브라우저는 ES Modules를 지원하지 않았다고 한다.
즉 ES6 import 문법을 사용할 수 없고, 그 말은 <code>&lt;script&gt;</code>간 모듈을 보낼 수 없다는 의미다.</p>
<p>초기에는 간단한 스크립트 파일 몇개만 이용해서 서비스가 가능했지만 점점 사이트가 커지고 수요가 많아 질 수록 관리가 어려워졌다고 한다.</p>
<p>이에 따라서 웹사이트 전처리의 요구사항이 점점 많아지게 되었고 이를 자동화 해줄 것들이 필요해지게되는데, 이런이유에서 이제 알아볼 Webpack,Vite등 번들러들이 등장 하게 되었다.</p>
<h1 id="✨webpack">✨Webpack</h1>
<h2 id="webpack의-장점">webpack의 장점</h2>
<h3 id="1-간편한-설정">1. 간편한 설정</h3>
<p>webpack은 하나의 설정파일에서 entry 와 output을 설정할 수 있고
어떤 plugin과 loader로 파일을 다룰 건지 명시 되어있다.</p>
<p>이를 확인해보기 위해 test프로젝트를 생성하고 webpack.config.js를 확인 해보자.
test프로젝트를 CRA로 생성해보니 webpack.config.js가 보이지 않는다.</p>
<pre><code class="language-JS">npm run eject</code></pre>
<p>위와같은 명령어를 입력하니 
<img src="https://velog.velcdn.com/images/chan_snk/post/94db1c6c-7f6c-45fc-b8bd-393ab77bc97b/image.png" alt="">
숨겨졌던 파일이 생성되고 
<img src="https://velog.velcdn.com/images/chan_snk/post/2adad0b9-59d4-43b1-b923-8996bf38757e/image.png" alt=""></p>
<p>webpack.config.js에서 다음과 같이 entry와 output설정을 확인 할 수 있었다.</p>
<p><strong>entry, output 이란?</strong>
webpack은 번들링 하는과정에서 일반 적으로 진입점인 entry point를 생성하는데
이는 보통  <code>src/index.js</code>로 삼고 이 entry point를 기준으로 의존적인 모듈들을 찾아서 하나의 묶음으로 번들링 하는 과정을 거친다고 한다.
위에서 본것처럼 webpack.config.js에서 진입점과 묶어진 번들들이 생성될 output을 설정 할 수 있다. 또한 해당 파일에서 plugin과 loader도 다루고 있으니 하나의 파일에서 간편하게 설정하는 것이 장점이다.</p>
<h3 id="2plugin-과-loader">2.plugin 과 loader</h3>
<p>webpack은 2012년 처음 나왔다. 반면에 vite는 2021년에 등장했는데 더 오랜시간 사용된 만큼 다양한 plugin과 loader를 사용 할 수 있다.
loader를 통해서 파일들을 변환, 번들링, 빌드를 진행하고 plugin을 통해서 output 파일을 튜닝한다. 필요에 따라 다양한 plugin, loader가 오픈소스에서도 개발 되고있다고 하니 
이 또한 webpack의 장점이다.</p>
<h3 id="3code-splitting">3.Code Splitting</h3>
<p>Code Splitting이 webpack의 가장 큰 장점이라고 한다.</p>
<p>Code Splitting이란 간단하게 애플리케이션에서 필요하지 않은 부분들을 따로 분할하여 별도의 번들로 만드는 기술이고, 이렇게 분할된 번들은 필요할 때만 로딩되므로 초기 로딩 속도를 향상시키고, 사용자 경험을 향상시킨다고 한다.</p>
<p>예를들어 SplitChunksPlugin을 사용하여 공통 모듈을 분리하여 중복을 최소화하고 번들 크기를 최적화하고 , 동적 import를 통해 필요할때만 로드를 하는등 더 많은 방법이 있는거 같은데 다 알아보진 못했다.</p>
<h2 id="rollup">Rollup</h2>
<p>번들러들 검색하다보면 Rollup, esbuild와 같은 것들이 더 나오는데 webpack, vite와는 무엇이 다른가? 간단하게 알아보자.</p>
<p>Webpack과 Rollup은 번들링 방식에 차이가 있다.</p>
<ul>
<li>Webpack: Webpack은 의존성 그래프를 생성하고 이를 기반으로 모든 모듈을 하나의 번들로 묶는다. 이는 모든 모듈을 평가하고 모든 종속성을 해결한 후에 번들을 생성하는 방식이다.</li>
<li>Rollup: Rollup은 ES6 모듈을 사용하여 코드를 분석하고 이를 하나의 파일로 번들링한다. Rollup은 Tree-shaking과 같은 최적화 기능을 통해 번들 크기를 최소화한다.</li>
</ul>
<p>더 많은 차이점이 존재하는데 이로 인해 Webpack은 주로 대규모 애플리케이션의 개발 및 빌드에 사용되고, Rollup은 주로 라이브러리 및 패키지의 번들링에 사용된다.</p>
<h1 id="✨vite">✨Vite</h1>
<h2 id="esbuild">esbuild</h2>
<p>Vite에 대해 알아보기 전에 또 다른 번들러인 esbuild에 대해 잠깐 알아보자.</p>
<p><strong>esbuild는 다른 번들러와 무엇이 다른가?</strong></p>
<p>esbuild는 다른 번들러가 내부적으로 JavaScript기반으로 번들링 하는 것과 다르게 Go언어를 기반으로 번들링을 한다. 이는 기존 속도보다 빠른 포퍼먼스를 보여준다.</p>
<p>하지만 아직 설정이 유연하지 못하고 안정성 이슈가 있어서 정식 버전이 Release되지 못한 상태라고 한다.</p>
<p>이러한 esbuild의 단점을 보완시킨 번들러가 Vite라고 한다.</p>
<h2 id="vite의-장점">Vite의 장점</h2>
<h3 id="1빠른-속도">1.빠른 속도</h3>
<p>vite의 가장큰 장점은 빠른 속도이다. vite는 esbuild를 통해 번들링을 하기 때문에 빠른 속동의 번들링을 제공한다.</p>
<p>또한 Vite는 ES 모듈을 기반으로 하는 개발 서버를 제공한다. 이는 파일을 실제로 번들링하는 것이 아니라, 각각의 모듈을 개별적으로 서빙하는 방식으로 동작시킨다.
따라서 모듈이 변경될 때마다 해당 모듈만 다시 번들링하므로 개발 서버의 속도가 매우 빠르다.</p>
<p>Vite는 HMR을 통해 변경된 모듈만 실시간으로 업데이트합니다. 이는 전체 애플리케이션을 다시 로드할 필요 없이 변경된 모듈만 빠르게 반영되어 빠른 개발 속도를 제공합니다.</p>
<h2 id="간단한-비교">간단한 비교</h2>
<p>속도면에서 Vite가 빠르기 때문에 Vite를 사용하는 것이 무조건 적으로 좋아 보이는데 
꼭 그런것 만은 아니라고 한다.</p>
<p>Vite는 개발 환경과 프로덕션 환경의 설정이 다르기 때문에 빌드 안정성이 낮아서 개발 환경에서만 사용한다는 의견도 많다고 한다.</p>
<p>또한 vite는 기본적으로 <code>&lt;root&gt;/index.html</code> 파일이 빌드를 위한 진입점이기 때문에, 순수한 JS 번들을 생성하기 위해서는 라이브러리 모드를 설정해야 한다.</p>
<h2 id="👇참고-자료">👇참고 자료</h2>
<p><a href="https://bepyan.github.io/blog/2023/bundlers">차세대 번들러 비교 및 분석 (feat. webpack, rollup, esbuild, vite)</a></p>
<p><a href="https://enjoydev.life/blog/frontend/4-module-bundler">모듈 번들러란? - Webpack vs Vite 무엇을 써야 할까요?</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[핵심 웹 지표 (Core Web Vital)]]></title>
            <link>https://velog.io/@chan_snk/%ED%95%B5%EC%8B%AC-%EC%9B%B9-%EC%A7%80%ED%91%9C-Core-Web-Vital</link>
            <guid>https://velog.io/@chan_snk/%ED%95%B5%EC%8B%AC-%EC%9B%B9-%EC%A7%80%ED%91%9C-Core-Web-Vital</guid>
            <pubDate>Wed, 27 Mar 2024 11:47:28 GMT</pubDate>
            <description><![CDATA[<h1 id="🤳core-web-vital-이란">🤳Core Web Vital 이란?</h1>
<p><strong>(1) LCP (최대 콘텐츠 풀 페인트)
(2) FID (최초 입력 지연)
(3) CLS (누적 레이아웃 이동)</strong>
<img src="https://velog.velcdn.com/images/chan_snk/post/953362f3-67de-4789-a399-921ab997b2b9/image.png" alt="">
<a href="https://web.dev/explore/learn-core-web-vitals?hl=ko">출처 : web.dev</a></p>
<p>사용자의 경험에 결정적인 영향을 미치는 측정가능한 지표 중 가장 중요한 
세가지 항목을 <code>핵심 웹 지표</code> 라고한다.</p>
<p>그 외에도 </p>
<p><strong>(4) TTFB (최초 바이트까지의 시간)
(5) FCP (최초 콘텐츠 풀 시간)</strong></p>
<p>두가지 지표를 추가해서 다섯 가지를 알아보자.</p>
<h2 id="😱그-전에">😱그 전에...</h2>
<p>성능 최적화에 대해 알아보기 전에 기본적으로 알아야 할 부분이 있다.</p>
<p>브라우저는 위에서부터 차례대로 로드되는데 HTML파일을 읽는 중 css나js파일을 만나면 해당 파일을 읽는동안 웹페이지의 랜더링은 일시적으로 차단된다. 즉 이부분을 해결 하면 성능향상을 할 수 있을 것으로 기대된다. 
이제 몇가지 방법들에 대해 알아보자.</p>
<h2 id="👌1-lcp-최대-콘텐츠-풀-페인트">👌(1) LCP (최대 콘텐츠 풀 페인트)</h2>
<h3 id="lcp최대-콘텐츠-풀-페인트란">LCP(최대 콘텐츠 풀 페인트)란?</h3>
<p>LCP는 사용자가 처음 페이지로 이동한 시점을 기준으로 뷰포트(사용자의 화면에 보여지는 영역)에 표시되는 가장 큰 이미지 or 텍스트 블록의 렌더링 시간을 말한다.</p>
<p>LCP의 평가 기준은 아래 그림과 같다.  2.5초 이하일때 좋은 사용자 경험을 줄 수 있다.
<img src="https://web.dev/static/articles/lcp/image/good-lcp-values.svg?hl=ko" width="n%" height="n%">
<a href="https://web.dev/articles/lcp?hl=ko">출처 : web.dev</a></p>
<h3 id="lcp를-최적화하는-방법">LCP를 최적화하는 방법</h3>
<p><strong>랜더차단 리소스 제거</strong></p>
<p>👍 불필요한 css제거</p>
<ul>
<li><p>css는 랜더링 차단 리소스이기 때문에 불필요하게 선언된 css들은 삭제해주는 것이 좋다.
공용 스타일을 적용해서 관리해준다면 성능향상에 도움을 줄 수 있다.</p>
</li>
<li><p>또한 selector또한 복잡하게 선언하면 레이아웃을 그리는데 시간이 소요되기때문에 간결하게 작성해주는 것이 유지보수, 성능면에서 이점이 있다.</p>
</li>
</ul>
<p>👍 preload,  preconnect, prefetch</p>
<ul>
<li><p><code>rel=&quot;preload&quot;</code>는 image,css,js가 바로 필요하지 않은 경우 랜더링과 병렬로 다운받게하고 완료시에 웹페이지에 적용되도록 해준다.</p>
</li>
<li><p><code>rel=&quot;preconnect&quot;</code>는 현재 페이지에서 외부 도메인 리소스를 참고하는 것을 브라우저에게 알려고 ,서드파티 자원을 연결할때 활용하면 좋다.</p>
</li>
<li><p><code>rel=&quot;prefetch&quot;</code>는 앞으로 사용될 것이라고 예상되는 리소스를 미리 가져온다.
브라우저는 rel=&quot;prefetch&quot;가 적용된 리소스들을 가져와 캐시에 저장한다.
예를들어 게시판 리스트 검색 시 사용자가 다음에 클릭할 부분은 첫 번째 게시글 또는 검색 리스트 2페이지일 확률이 높다. 이러한 경우 첫 번째 게시글의 상세페이지 또는 2페이지를 prefetch 적용할 수 있다.</p>
</li>
</ul>
<p><strong>이미지 최적화</strong>
👍 lazyloading 사용하기</p>
<pre><code class="language-JS">&lt;img src=&quot;item.jpg&quot; loading=&quot;lazy&quot; alt&gt;</code></pre>
<ul>
<li>위와같이 선언함으로써 화면에 보여질 필요가없는 image들은 loading을 늦출 수 있다. 지연 로딩을 하면 뷰포트 밖에 이미지들은 로딩되지 않는다.</li>
</ul>
<p><strong>TTFB (최초 바이트까지의 시간)를 줄이자</strong></p>
<p>(4)TTFB (최초 바이트까지의 시간)란 사용자가 페이지 로딩을 시작하고 브라우저가 HTML 응답으로 첫 바이트를 받아오기까지의 시간을 의미한다.</p>
<h2 id="👌2-fid-최초-입력지-지연">👌(2) FID (최초 입력지 지연)</h2>
<h3 id="fid최초-입력-지연란">FID(최초 입력 지연)란?</h3>
<ul>
<li><p>FID는 유저와 페이지가 처음으로 상호작용하는 순간부터 (예를 들어, 링크를 클릭하거나, 버튼을 탭하는 등) 브라우저가 해당 인터렉션에 대한 응답으로 실제로 이벤트 핸들러 처리를 시작하기까지의 시간을 측정한다.</p>
</li>
<li><p>좋은 사용자 경험을 제공하기 위해서는 사이트의 최초 입력 지연 값이 100ms 이하 여야 한다.</p>
</li>
</ul>
<h3 id="fid를-최적화-하는법">FID를 최적화 하는법</h3>
<p><strong>JavaScript실행시간 단축</strong></p>
<p> 50ms 이상 메인 스레드를 차단하는 모든 코드는 긴 작업은 FID수치를 높인다.
긴 작업을 분할해서 작성하는 것이 좋다.</p>
<p>사용하지 않는 Javascript 지연시켜 FID를 낮춘다.</p>
<h2 id="👌3-cls-누적-레이아웃-이동">👌(3) CLS (누적 레이아웃 이동)</h2>
<h3 id="cls-누적-레이아웃-이동란">CLS (누적 레이아웃 이동)란?</h3>
<p>CLS는 첫 페인팅 이후 뷰포트 내 콘텐츠 위치 변화에 대한 수치이다.
범위는 0~1이고 좋은 사용자 경험을 위해서는 0.1이내로 유지하는 것이 좋다.</p>
<h3 id="cls를-최적화-하는법">CLS를 최적화 하는법</h3>
<p> <strong>이미지의 크기를 정하자</strong></p>
<p>이미지의 크기를 정해두고 스켈레톤UI를 미리 보여줌으로써 레이아웃의 영역이 이미 정해지기 때문에 사용자가 예상하지 못한 레이아웃 변화를 일으키지 않는다.</p>
<p><strong>기존 컨텐츠의 중간에 추가하는것은 지양</strong></p>
<p>기존 레이아웃의 상단, 중간에 레이아웃이 추가되는 것은 전체적인 레이아웃이 변하게 되기 때문에 CLS에 영향을 미친다.
*단 사용자 입력 후 500ms 이내에 발생하는 레이아웃 이동은 CLS에 포함되지 않는다고 한다.</p>
<p><strong>FOUT/FOIT를 유발하는 웹 폰트</strong></p>
<p>FOUT(Flash Of Unstyled Text) 웹폰트가 로드 되기전까지 대체 폰트로 보여진후 로딩후에 적용되는 것이다. FOUT현상은 자연스럽지 못한 경험을 하게 한다.</p>
<p>FOIT(Flash Of Invisible Text) 웹 폰트를 로드하기 전까지 텍스트가 보이지 않는 현상</p>
<h2 id="👌4ttfb-최초-바이트까지의-시간">👌(4)TTFB (최초 바이트까지의 시간)</h2>
<p>TTFB(Time to First Byte)는 HTTP 요청을 했을때 처음 byte (정보) 가 브라우져에 도달하는 시간을 의미한다.</p>
<p>좋은 사용자 경험을위한 TTFBs는 0.8s 이하라고 한다.</p>
<h2 id="👌5fcp-최초-콘텐츠-풀-시간">👌(5)FCP (최초 콘텐츠 풀 시간)</h2>
<p><strong>최초 콘텐츠풀 페인트(FCP)</strong>는 브라우저가 DOM에서 첫 번째 콘텐츠 비트를 렌더링하여, 페이지가 실제로 로드되고 있다는 첫 번째 피드백을 사용자에게 제공하는 경우이다. FCP 이후에 사용자는 웹 페이지가 로드되어 있다는 것을 느낄 수 있습니다.</p>
<p>1.8초 이하로 유지하는것이 좋다고 한다.</p>
<h3 id="😁참고자료">😁참고자료</h3>
<p><a href="https://velog.io/@hsecode/%EC%B5%9C%EC%A0%81%ED%99%94-%ED%95%B5%EC%8B%AC%EC%A0%81%EC%9D%B8-%EC%9B%B9-%EC%A7%80%ED%91%9C-%EA%B0%9C%EC%84%A0%ED%95%98%EA%B8%B0">최적화-핵심적인-웹-지표-개선하기</a>
<a href="https://ui.toast.com/posts/ko_202012101720">LCP(Largest Contentful Paint) 최적화하기</a>
<a href="https://hjk329.github.io/%EC%B5%9C%EC%A0%81%ED%99%94/improve-lcp/">코어 웹 바이탈 최적화</a>
<a href="https://web.dev/articles/lcp?hl=ko#improve-lcp">web.dev</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[관심사분리(Logic vs View)]]></title>
            <link>https://velog.io/@chan_snk/%EA%B4%80%EC%8B%AC%EC%82%AC%EB%B6%84%EB%A6%ACLogic-vs-View</link>
            <guid>https://velog.io/@chan_snk/%EA%B4%80%EC%8B%AC%EC%82%AC%EB%B6%84%EB%A6%ACLogic-vs-View</guid>
            <pubDate>Fri, 22 Mar 2024 23:51:57 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가면서">들어가면서...</h2>
<h3 id="이번과제에는-sphagettie-code-refactor를-진행했다">이번과제에는 sphagettie code Refactor를 진행했다.</h3>
<ul>
<li>👇 해당 과제의 내용은 다음과 같다. <ul>
<li><span style="color:indianred">게시글</span> &amp; <span style="color:indianred">댓글</span>  목록을 보여주는 페이지이며 <span style="color:indianred">pagination</span> 기능이 구현된 페이지이다.</li>
<li><code>게시글</code>, <code>댓글</code>, <code>pagination</code> api요청이가능하며 모달창을 통해 페이지를 이동하도록 작성되어있다.</li>
</ul>
</li>
</ul>
<h3 id="😱문제점">😱문제점</h3>
<ul>
<li><p>sphagettie code를 열어보니 아래와같았다.</p>
<pre><code class="language-JavaScript">const LIMIT_TAKE = 20;
const PostDetailPage = () =&gt; {
const [params] = useSearchParams();
const [postDetail, setPostDetail] = useState([]);
const [commentList, setCommentList] = useState([]);
const [isOpenCommentList, setIsOpenCommentList] = useState(false);

   ... {fetching함수}
  ... {UseEffetc()}
   ... {ClickEvent()}

return (
  &lt;div&gt;
   ...{component}
   ...{PagiNation}
  &lt;/div&gt;
);
};
export default PostDetailPage;</code></pre>
<p>위와같은 두개의 페이지컴포넌트가 존재하고 각페이지를 위한 PagiNation컴포넌트도 두개 존재하는 상황이었다.</p>
</li>
</ul>
<p>==&gt; Page컴포넌트가 Logic 과 State를 모두 관리하는것이 문제처럼 보여진다.</p>
<h3 id="😎설계">😎설계</h3>
<p>간단하게 코드의 기능과 문제점을 살펴봤으니 해결방법을 생각해보자.</p>
<ul>
<li><ol>
<li>재사용되는 컴포넌트를 하나의 컴포넌트로 합치기.</li>
</ol>
</li>
<li><ol start="2">
<li>각기능에 맞는 logic들을 별도의 파일로 분리해서 관리하기.</li>
</ol>
</li>
<li><ol start="3">
<li>너무많은 state와 useEffect를 가지고있으니 따로분리하기.</li>
</ol>
</li>
</ul>
<p>다음과 같이 <code>Refactor</code> 를위해 설계를 하고 진행해보자.</p>
<h3 id="👏refactor">👏Refactor</h3>
<p><strong>1.비슷한 컴포넌트 재사용하기</strong></p>
<pre><code class="language-JavaScript">const PostPageNation = () =&gt; {
  ...{생략}
  const fetchPostPageNation = useCallback(async () =&gt; {
    const response = await axios.get(&quot;/api/{여기만바뀜}, {
  ...{생략}

export default PostPageNation;</code></pre>
<ul>
<li><p>PagiNation컴포넌트는 <code>PagiNation.Commnet</code>,<code>PagiNation.Post</code> 두가지가 존재하는데 자세히보니 <span style="color:indianred">{여기만 바뀜}</span>에 해당하는 부분 -&gt; 즉 api요청주소만 바뀌고있다.</p>
</li>
<li><p>두가지 컴포넌트를 <span style="color:blue">path</span> props를 전달받아 api요청을 다르게하는 하나의 PagiNation컴포넌트로 만들었다.</p>
<pre><code class="language-JavaScript">const PostPageNation = ({path}) =&gt; {
...{생략}
const fetchPostPageNation = useCallback(async () =&gt; {
  const response = await axios.get(`/api/${path}`, {
...{생략}
</code></pre>
</li>
</ul>
<p>export default PostPageNation;</p>
<pre><code>
- 여기까진 잘 진행된거 같다. 하지만 다음 과정에서 문제가 생겼다.
우선 처음으로 수정한 코드를 보자.

**2.logic 과 state 별도의 파일에서 분리하기.**

logic과 state를 포함한 코드를 별도의 파일로 분리하기위해서 customhook을 이용하기로 했다.


```JavaScript
/ use-fetch-data.js     /

export const useFetchData = () =&gt; {
  const [params, setParams] = useSearchParams()
  const [postData, setPostData] = useState()
  const [postDetail, setPostDetail] = useState()

  const getParamValues = ({ keyArr }) =&gt; {
    const urlObj = {}
    keyArr.forEach((key) =&gt; (urlObj[key] = params.get(key)))
    return urlObj
  const getParamValues = () =&gt; {
    return {
      take: params.get(KEY.TAKE),
      page: params.get(KEY.PAGE),
      limit: params.get(KEY.LIMIT),
    }
  }
  const setParamValues = ({ keyValueArr }) =&gt; {
    keyValueArr.forEach((keyVal) =&gt; params.set(keyVal[0], keyVal[1]))
  const setParamValues = ({ page, limit, take }) =&gt; {
    params.set(KEY.PAGE, page)
    params.set(KEY.LIMIT, limit)
    params.set(KEY.TAKE, take)
    setParams(params)
  }

  const fetchDataByFormAndAdd = async ({ form = &quot;Posts&quot;, address }) =&gt; {
    const response = await axios.get(`/api/${address}`, {
      params: {
        take: params.get(KEY.TAKE),
        page: params.get(KEY.PAGE),
        limit: params.get(KEY.LIMIT),
      },
    })
    setPostData(response.data[form])
  }
  const fetchPostDetail = async () =&gt; {
    const response = await axios.get(&quot;/api/post&quot;)
   }
  return {
    getParamValues,
    setParamValues,
    fetchDataByFormAndAdd,
    fetchPostDetail,
    postData,
    postDetail,
  }</code></pre><p><strong>위와같이 하나의 hook함수를 생성해서 기능들을 빼내는것에는 성공을 했는데 .....
<span style="color:indianred">큰문제를 발견했다.</span></strong></p>
<ul>
<li>use-fetch-data를 각 컴포넌트들에서 불러와 사용하니 기존코드보다 관리, 재사용적인 면에서 refactor가 진행됬다고 보여지는데 <pre><code class="language-JavaScript">/ PostDetailPage.jsx /
</code></pre>
</li>
</ul>
<p>PostDetailPage = () =&gt; {
  const {
    setParamValues,
    getParamValues,
    fetchPostDataByUrlAndDataForm,
    fetchDataByFormAndAdd,
    fetchPostDetail,
    postData: commentList,
    postDetail,
  } = useFetchData()</p>
<p>  useEffect(() =&gt; {
    checkUserAuth()
    fetchPostDetail()
    setParamValues({ page: 1, take: 10, limit: 10 })
  }, [])</p>
<p>  const paramValues = getParamValues({
    keyArr: [KEY.PAGE, KEY.LIMIT, KEY.TAKE],
  })
  const { page } = getParamValues()</p>
<p>  useEffect(() =&gt; {
    if (!isOpenCommentList) return
    fetchDataByFormAndAdd({
      form: &quot;Comments&quot;,
      address: &quot;comments&quot;,
    })
  }, [page])</p>
<p>  const onClickMoreComments = async () =&gt; {
    setIsOpenCommentList(true)
    await fetchDataByFormAndAdd({
      form: &quot;Comments&quot;,
      address: &quot;comments&quot;,
    })
  }</p>
<pre><code>- 위와 같이 hook함수를 이용해 관심사분리를 했지만 
- 가독성은 여전히 refactor전과 달라진게없다.
- 또한 패칭을위한 UseEffect 또한 너무많이 선언되어있다.

**3.👀관심사 분리**

방향이 잘못된거같아 검색을 하던중 좋은 글을 발견했다.
[관심사 분리에관한 글](https://velog.io/@eunbinn/modularizing-react-apps) 을 읽어보고 다시 방향을 잡을 수 있엇다.
`☝ 재밌는 내용이니 읽어보세요~~`

내가 생각했던 관심사 분리란 view를 담당하는 컴포넌트에서 logic을 추출 하기만 하면 된다 라고 생각했던것 같다. 

[관심사 분리에관한 글](https://velog.io/@eunbinn/modularizing-react-apps) 을 이해한 바에따르면 
  - 1. 기능과 view를 분리할 것
  - 2. 기능들 또한 관심사 에 따라 분리할 것 
  - 3. 분리된 기능들은 사용 목적에 따라 관리 할 것 

**이전 코드의 문제점**
하나의 큰 hooks를 생성함으로써 오히려 hooks의 관리가 어려워지는 문제점이 생겼다.

**4.수정된 코드**

`기존 use-feteh-data.js를 기능에 따라 5가지의 hooks로 분리했다.`
- use-url-manipulator.js
``` JavaScript
export const useURLManipulator = () =&gt; {
  const [params, setParams] = useSearchParams()

  useEffect(() =&gt; {
    setParamValues({ page: 1, take: 10, limit: 10 })
  }, [])

  /**
   * @description query key:value쌍을 반환
   */
  const getParamValues = () =&gt; {
    return {
      take: params.get(KEY.TAKE),
      page: params.get(KEY.PAGE),
      limit: params.get(KEY.LIMIT),
    }
  }
  /**
   * @description query key에 값을 선언
   */
  const setParamValues = ({ page, limit, take }) =&gt; {
    params.set(KEY.PAGE, page)
    params.set(KEY.LIMIT, limit)
    params.set(KEY.TAKE, take)
    setParams(params)
  }

  return { getParamValues, setParamValues }
}
</code></pre><ul>
<li><p>use-fetch-pagenation.js</p>
<pre><code class="language-JavaScript">export const useFetchPageNation = ({ path }) =&gt; {
const [pageNation, setPageNation] = useState()
const { getParamValues } = useURLManipulator()
const { page } = getParamValues()

useEffect(() =&gt; {
  fetchPageNationByPath({ path })
}, [page])

/**
 * @paramter path - string : &quot;posts&quot; | &quot;comments&quot;
 */
const fetchPageNationByPath = async ({ path }) =&gt; {
  const response = await axios.get(`/api/${path}`, {
    params: getParamValues(),
  })
  setPageNation(response.data.PageNation)
}

return {
  fetchPageNationByPath,
  pageNation,
}
}</code></pre>
<p>위의 코드들 처럼 각기능에 따라 hooks를 생성하고 기존page컴포넌트에 있던 useEffect,clickevent 들을 따로 관리해주었다.</p>
</li>
</ul>
<p><strong>5.결과물</strong></p>
<pre><code class="language-JavaScript">const PostDetailPage = () =&gt; {
  const { postDetail } = useFetchDetail()
  const {
    commentList,
    onClickMoreComments,
    onClickHiddenComments,
    isOpenCommentList,
  } = useFetchComment()

  if (!postDetail) return
  return (
    &lt;div&gt;
      &lt;h1&gt;Post Detail Page&lt;/h1&gt;
      &lt;div&gt;
        &lt;p&gt;제목: {postDetail.title}&lt;/p&gt;
        &lt;p&gt;내용: {postDetail.content}&lt;/p&gt;
        {!isOpenCommentList ? (
          &lt;button onClick={onClickMoreComments}&gt;댓글 보기&lt;/button&gt;
        ) : (
          &lt;&gt;
            &lt;button onClick={onClickHiddenComments}&gt;댓글 숨기기&lt;/button&gt;
            {commentList?.map((comment) =&gt; (
              &lt;div key={comment.id}&gt;
                &lt;p&gt;{comment.content}&lt;/p&gt;
                &lt;p&gt;{comment.User.nickName}&lt;/p&gt;
              &lt;/div&gt;
            ))}
            &lt;PageNation path=&quot;comments&quot; /&gt;
          &lt;/&gt;
        )}
      &lt;/div&gt;
    &lt;/div&gt;
  )
}
export default PostDetailPage
</code></pre>
<p>그 결과 위의 코드처럼 pageComponet에서는 hooks에서 받아온 state하나만로 fetching을 진행하고 그외의 clickevent, useEffect등은 관리할 필요가 없어졌다.</p>
<p>기존의 코드에서는 pageComponent에서 60~70줄에 걸쳐 작성되어 있던것들이 관심사 분리를 통해 오직 view만 관리하도록 코드를 수정할 수 있엇다.</p>
<h2 id="마치며">마치며...</h2>
<p>관심사 분리의 시작을 큰 관점에서 본다면 <code>View vs Logic</code> 을 나누눈 것부터 시작이 아닐까해서 이 글을 작성해보았다. 아직 실력이 없어 두서없이 써내려갔지만 
관리할 point를 줄이고 확장성을 높이는 것이 관심사 분리아닐까 생각이 든다.
계속해서 맞이할 상황이기에 다음에 더 좋은 내용으로 작성해야겠다. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[TailwindCss vs StyledComponent]]></title>
            <link>https://velog.io/@chan_snk/TailwindCss-vs-StyledComponent</link>
            <guid>https://velog.io/@chan_snk/TailwindCss-vs-StyledComponent</guid>
            <pubDate>Fri, 15 Mar 2024 22:10:34 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가면서">들어가면서...</h2>
<h3 id="이번-과제에서는-동일한-컴포넌트를-두가지-라이브러리를-사용해서-생성했다">이번 과제에서는 동일한 컴포넌트를 두가지 라이브러리를 사용해서 생성했다.</h3>
<p>공용컴포넌트를 생성하는 과제였기 때문에 props를 통해서 재사용가능 하도록 설계했다.</p>
<h4 id="🙌설계">🙌설계</h4>
<ul>
<li>1.purpose (input의 사용목적)<ul>
<li>input의 사용목적에따라 넓이를 네가지로 설정.</li>
</ul>
</li>
<li>2.hasButton (검색이필요한 input이라면) <ul>
<li>클릭할 버튼이 필요할 때를 위한 button의 유무.</li>
</ul>
</li>
<li>3.hasIcon (버튼에 icon이필요한경우)<ul>
<li>버튼에 text대신 icon이 필요한 경우</li>
</ul>
</li>
</ul>
<p>간단하게 목적에 따라 세가지 정도의 dseign을 결정하는 props를 전달받아 
형태를 결정 할 수 있게 설계했다.</p>
<p>😱그 전에 간단하게 두가지 라이브러리에대해 알아보자.</p>
<h2 id="styledcomponents">StyledComponents</h2>
<h4 id="😘장점">😘장점?</h4>
<p>가장 큰 장점은 별도의 CSS 파일을 만들지 않고 하나의 파일 안에 스타일을 관리 할 수 있다는 점이다.</p>
<pre><code class="language-JavaScript"> ex) const Wrapper = styled.div`
      display: flex;
      justify-content: center;
      align-items: center;
`</code></pre>
<p>위와같이 style은 따로정의 하기 때문에 코드의 가독성을 해치지 않고 한파일에서 관리 할 수 있다는 것이 가장 큰 장점으로 보인다.</p>
<h4 id="😂단점">😂단점?</h4>
<p>아쉬운 점은 스타일을 가진 tag를 만들때마다 이름을 지어주어야한다...
프로젝트를 진행하게되면 수 많은 컴포넌트를 생성하는데 매번 이름을 지어주어야하는 고민에 빠지게된다.
또한 팀원들과 정확한 convetion을 정하지 않는다면 같은 역할을 하는 컴포넌트의 이름이 제각각이 될 수 있다.</p>
<pre><code class="language-JavaScript">      display: flex;
      justify-content: center;
      align-items: center;
`</code></pre>
<p>위의 예시처럼 자식요소들을 가운데로 모아주는 tag를 생성할때
CenterWrapper, CenterContainer, CenterWrap, CenterBox ....등등
작성자마다 다른 이름을 붙히는 경우가 발생한다.</p>
<h2 id="tailwindcss">TailwindCss</h2>
<h4 id="😘장점-1">😘장점?</h4>
<p>장점은 빠른 코드작성이 가장 큰 장점이라 생각된다.</p>
<p>이전 작업들에선 styled-components를 사용해서 매번 tag이 이름을 선언해주고 또한 해당 태그가 어떤 style이적용 되었는지 확인하기 위해서는 다시 선언부에 가서 확인,수정을 해줘야했다.
하지만 tag선언과 동시에 style을 작성하는 tailwind-css덕분에 간편하게 작성할 수 있엇다.</p>
<p>또한 <strong>tailwind css intellisense</strong>  은 자동완성을 지원해주는 extension이다.
이를 통해 생산성이 올라가는 장점이 있었다.</p>
<h4 id="😂단점-1">😂단점?</h4>
<p>모두가 꼽는 아쉬운 점은 바로 <strong>가독성</strong> 이다. 다양한 옵션, 기능이 존재하는 컴포넌트 일수록 
점점 옆으로 길어지는 style속성에 코드가 눈에 들어오지 않는 점이다.</p>
<p>또한 제공된 utility class를 사용하기 때문에 모르는 속성들을 직접 찾아가며 작성해야 하는 점이다.</p>
<h4 id="😎지금까지-두-라이브러리의-장단점을-간단하게-살펴봤는데-실제-같은-컴포넌트를-만들땐-어떤-차이를-보일까-다음-코드를-보며-알아보자">😎지금까지 두 라이브러리의 장단점을 간단하게 살펴봤는데 실제 같은 컴포넌트를 만들땐 어떤 차이를 보일까? 다음 코드를 보며 알아보자.</h4>
<h2 id="inputwith-styled-componet">Input(with styled-componet)</h2>
<h4 id="앞서-설계했던-것-처럼-공용컴포넌트-input을-생성했다">앞서 설계했던 것 처럼 공용컴포넌트 input을 생성했다.</h4>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/9a56a6d5-8ea9-473b-8c7a-701186696d07/image.png" alt=""></p>
<pre><code class="language-JavaScript">const MBInput = ({
  purpose,
  hasButton = false,
  placeHolder,
  buttonName = &quot;버튼&quot;,
  hasIcon = false,
  ...props
}) =&gt; {
  return (
    &lt;Wrapper&gt;
      &lt;CustomInput
        purpose={purpose}
        placeholder={placeHolder || &quot;palceHolder&quot;}
        {...props}
      &gt;&lt;/CustomInput&gt;
      {hasButton &amp;&amp; (
        &lt;ButtonBase hasIcon={hasIcon}&gt;
          {hasIcon ? (
            &lt;IconBase src=&quot;https://svgsilh.com/svg/1093183.svg&quot; /&gt;
          ) : (
            buttonName
          )}
        &lt;/ButtonBase&gt;
      )}
    &lt;/Wrapper&gt;
  )
}
export default MBInput

const Wrapper = styled.div`
  display: flex;
  justify-content: center;
  align-items: center;
`
const CustomInput = styled.input&lt;InputCssType&gt;`
  height: ${FONT_SIZE.small};
  padding: 1rem;
  cursor: pointer;
  border: 1px solid ${COLOR.grayScale[500]};
  border-radius: 1.5rem;
  ${({ purpose }) =&gt; InputCss[purpose as PURPOSE_TYPE]}
  &amp;:hover {
    background-color: ${COLOR.theme.main.light};
  }
`
const ButtonBase = styled.button&lt;ButtonCssType&gt;`
  min-width: 2rem;
  min-height: 2rem;
  position: relative;
  right: 3.5rem;
  border: none;
  background-color: transparent;
  &amp;:hover {
    transform: scale(1.2);
    background-color: transparent;
  }
  ${({ hasIcon }) =&gt;
    !hasIcon &amp;&amp;
    css`
      border-radius: 8px;
      border: 1px solid ${COLOR.grayScale[600]};
      &amp;:hover {
        transform: scale(1.2);
        background-color: ${COLOR.theme.mainOppsite.light};
      }
    `}
  cursor: pointer;
`
const IconBase = styled.img`
  width: 2rem;
  height: 2rem;
`</code></pre>
<p>위와 같이 코드를 작성하여 input컴포넌트를 생성했다. 위의 코드를 보면 분기가 생성됬기 때문에 너무 코드가 길어지고 props를 넘겨주기위해 매번<strong>_ ${({})=&gt; }_</strong> 의 형태로 작성해줘야 하는 번거로움이 보인다.
또한 현재는 style만 포함된 코드이지만 해당 input태그의 기능을 하는 logic이 포함된다면?
너무 긴 코드가 작성될 것이고 그것이 불편해 따로 css파일을 분리해서 관리한다면 
styled-components의 장점이 사라지는 것 아닐까? 라는 생각이 든다.</p>
<p>그렇다면 동일한 컴포넌트를 tailwind-css를 사용해 만들어 보자.</p>
<h2 id="inputwith-tailwind-css">Input(with tailwind-css)</h2>
<p><img src="https://velog.velcdn.com/images/chan_snk/post/ef7f5b76-d390-4262-a45c-ea41260a8a3a/image.png" alt=""></p>
<pre><code class="language-JavaScript">const MBInput = ({
  pusrpose,
  shape = &quot;rectangle&quot;,
  iconUrl,
  hasButton = false,
  hasIcon = false,
  buttonText = &quot;버튼&quot;,
}) =&gt; {
  return (
    &lt;div className={WrapperVarinats({ pusrpose, shape })}&gt;
      &lt;input className=&quot;w-[80%] outline-none bg-transparent text-ellipsis&quot; /&gt;
      {hasButton &amp;&amp;
        (hasIcon ? (
          &lt;img
            className=&quot;w-8 h-8 cursor-pointer  hover:scale-125&quot;
            src={iconUrl || `https://svgsilh.com/svg/1093183.svg`}
          /&gt;
        ) : (
          &lt;button className=&quot;w-[8rem] h-fit  rounded-[5rem] border-[1px] border-solid border-grayScale-600 hover:bg-theme-mainOppsite-light&quot;&gt;
            {buttonText}
          &lt;/button&gt;
        ))}
    &lt;/div&gt;
  )
}

export default MBInput

const WrapperVarinats = cva(
  &quot;h-[5rem] pl-[1rem] pr-[1rem] flex items-center justify-between border-solid border-[1px] border-grayScale-600 focus-within:bg-theme-main-light &quot;,
  {
    variants: {
      pusrpose: {
        search: &quot;w-[30rem] &quot;,
        resgisterSmall: &quot;w-[20rem]&quot;,
        resgisterNormal: &quot;w-[50rem]&quot;,
        resgisterLarge: &quot;w-[100rem]&quot;,
      },
      shape: {
        round: &quot;rounded-[5rem]&quot;,
        rectangle: &quot;&quot;,
      },
    },
  }
)

</code></pre>
<p>위의 코드는 tailwind-css로 작성한 input컴포넌트이다.앞서 봤던 styled-components의 코드와 비교를 해보자.</p>
<p>우선 가장 눈에띄는 점은 <strong>줄</strong> 수가 짧아진 점이다. tag의 이름을 선언해주는 부분이 사라지면서 코드가 간결해진 느낌을 받는다.</p>
<p>이전 코드에서는 분기처리를 하기위해 <strong>InputCss = {key:css``}</strong> 의 형태를 사용해서 분기처리를 한반면 현재 코드에서는 <strong>cva</strong>(class-variance-authority)를 통해 분기처리를 해준것을 확인할 수 있다.</p>
<p><strong><em>cva란?</em></strong>
간단하게 설명하자면 props에 의해 분기가 생성되면 해당 case에 맞는 css를 적용해준다.
<em><strong>cva(&quot;&quot;,{})</strong></em> 형태로 작성하는데 첫번째 parameter에는 공통된 css를 작성해주고 
두번째 parameter에는 <em>*<em>{variants:{}} *</em></em> variants아래 객체형태로 분기에 맞는 css를 작성해주면된다.</p>
<h4 id="여기까지-두-라이브러리의-간단한-장단점-실제-코드를-보며-비교를-해봤는데-과연-어떤-라이브러리가-더-사용하기-좋은-것일까">여기까지 두 라이브러리의 간단한 장단점, 실제 코드를 보며 비교를 해봤는데 과연 어떤 라이브러리가 더 사용하기 좋은 것일까?</h4>
<h2 id="🎉승자는">🎉승자는?</h2>
<p>무조건 좋은 라이브러리는 없다고 생각한다. 개발자의 환경, 역량에 맞는 라이브러리를 선택하게 될탠데
개인적으로는 <strong>tailwind-css</strong> 에 한표를 주고싶다.
utility class를 알고 있어야만 작성할 수 있고, 코드의 가독성이 떨어지는 단점이 있는 반면에
보다 간결한 분기처리, 자동완성과 제공된 키워드를 활용한 style적용, 또한 tag의 이름을 고민할 필요 없이 작성할 수 있다는 장점이 나에게는 크게 다가왔다.</p>
<p>이번 과제에서 처음 사용해 보는 라이브러리임에도 더 좋다고 느끼게 만드는 장점이 분명히있는 라이브러리라고 생각이된다.</p>
<h2 id="글을-마치며">글을 마치며...</h2>
<p>다양한 css라이브러리중 많이 사용되는 두가지의 라이브러리만 비교해봤지만 내가 추구하는 이상적인 개발자가 되기 위해서는 더 많은 경험을 해봐야 할것이다.
지금까지 styled-components가 최고의 라이브러리인줄 알았지만 tailwind-css를 사용하며 더 좋은 것이 있다는 생각을 했다. 
다양한 경험을 통해 최선의 결정을 내릴 수 있는 개발자가 되면 좋겠다.</p>
<h3 id="👏bonus">👏bonus</h3>
<p>위에서 다루지않은 내용중에 clsx와 merge에 관한내용이 있다.
tailwind-css를 보다 잘 활요하게 해주는 것들인데 링크를 남겨두었으니 관심이있다면 참고해서 성장하는데 도움이 되었으면 좋겠다. </p>
<blockquote>
<p><a href="https://velog.io/@qwzx16/tailwind%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-React-Component-%EA%B4%80%EB%A6%ACtailwind-merge-cva-clsx-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0">작성자 걍걍규&#39;s velog</a> </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[WebStorage를 사용해볼까?]]></title>
            <link>https://velog.io/@chan_snk/WebStorage%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%BC%EA%B9%8C</link>
            <guid>https://velog.io/@chan_snk/WebStorage%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%BC%EA%B9%8C</guid>
            <pubDate>Sun, 10 Mar 2024 11:05:38 GMT</pubDate>
            <description><![CDATA[<h2 id="들어가면서">들어가면서..</h2>
<h3 id="이번-과제에서-가상-userdata를-생성하고-table형태로-화면에-보여주는-과제를-진행-하였다">이번 과제에서 가상 userData를 생성하고 Table형태로 화면에 보여주는 과제를 진행 하였다.</h3>
<pre><code class="language-javascript">const creatRandomUserData = (numPeople) =&gt; {
  const MockData = Array(numPeople)
    .fill(0)
    .map((_, idx) =&gt; {
      return {
        id: idx,
        name: creatRandomUserName(),
        birth: creatRandomUserBirth(),
        phone: creatRandomPhone(),
        loginRecords: creatRandomLastLoginRecords(),
      }
    })
  return MockData</code></pre>
<ul>
<li>creatRandomUserData() 함수를 생성해 화면이 첫 로드될때 다음 함수를 실행하고 페이지네이션 기능을 구현해 동작시킬 예정이었다.<h4 id="😱문제점">😱문제점!</h4>
<pre><code class="language-javascript">const mockData = creatRandomUserData(270)</code></pre>
mockData를 다음과 같은 형태로 불러왔더니 state의 변화에 따라 rerender가 일어나고 기존의 mockData가아닌 새로운 데이터를 생성하는 문제가 발생했다.</li>
</ul>
<h4 id="해결방법">해결방법?</h4>
<p>처음 불러온 mockData를 web-stroage에 저장해서 저장된데이터를 불러와 사용하면 데이터가 유지되지 않을까?? 라는 생각에 web-storage에대한 searching을 진행했다.</p>
<h2 id="webstoragelocalstorage-vs-sessionstorage">WebStorage(LocalStorage vs SessionStorage)</h2>
<p>web-storage를 살펴보니 두가지 저장 공간이있는데 차이점과 공통점을 살펴보자.</p>
<h4 id="1localstorage">1.LocalStorage</h4>
<ul>
<li>브라우저나 OS가 재시작하더라도 데이터가 파기되지 않는다.</li>
<li>오리진이 같은 경우 데이터는 모든 탭과 창에서 공유한다.<blockquote>
<p> 오리진 이란?</p>
</blockquote>
<ul>
<li>URL 체계 (http,https)</li>
<li>호스트 (naver.com) 등의 도메인</li>
<li>포트 (5173 등등)</li>
</ul>
</li>
<li>👏 모든 탭과 창에서 공유하지만 다른브라우저(chorme, safari)인 경우에는 데이터를 공유하지 않는다.<h4 id="2sessionstorage">2.SessionStorage</h4>
</li>
<li>세션이 종료되면(창을 닫으면) 데이터는 사라진다. 단 새로고침 할 경우에는 유지됨.</li>
<li>LocalStorage와 다르게 하나에 탭에서만 데이터를 가진다.</li>
<li>👏 각각 탭에서만 데이터를 저장하고 있으며 다른탭,브라우저와 데이터를 공유할 수 없다.</li>
</ul>
<h4 id="공통점">공통점</h4>
<ul>
<li><ol>
<li>서버가아닌 클라이언트측에 데이터를 저장할 수 있다. 쿠키와 달리 서버요청에 포함되지 않는다.</li>
</ol>
</li>
<li><ol start="2">
<li>쿠키는 4KB의저장공간인 반면, storage는 약 5MB의 저장공간을 가질 수 있다.</li>
</ol>
</li>
</ul>
<h4 id="차이점">차이점</h4>
<ul>
<li><ol>
<li>데이터의 지속성 차이</li>
</ol>
<ul>
<li>localStorage는 데이터를 명시적으로 삭제하지 않는이상 계속 유지하는 반면 
  sessionStorage는 탭이 닫히면 데이터가 삭제된다.</li>
</ul>
</li>
</ul>
<h3 id="간단한-사용법">간단한 사용법.</h3>
<ul>
<li>사용법은 아주 간단하다. 예시로는 localStorage를 사용하지만 sessionStorage사용법은 local  -&gt; session으로만 바꿔주면 된다.</li>
</ul>
<ol>
<li>localStorage.setItem(key,value)을 통해 특정 key값에 value를 설정</li>
<li>localStorage.getItem(key) 설정된 key값을 통해 value를 가져오기.</li>
<li>localStorage.clear() 모든 데이터를 삭제
이외에도 removeItem(key),length,key(index)등등을 지원한다.</li>
</ol>
<h4 id="😱주의할점">😱주의할점</h4>
<ul>
<li>웹 스토리지를 사용할때 주의해야할 것이 있다.
오직 문자형 데이터타입(string)만 지원한다는 것이다..!!!</li>
<li>해결방법은???
=&gt; 웹 스토리지를 사용할 때 위와 같은 문제를 피하기 위해서 많이 사용하는 방법이 JSON 형태로 데이터를 읽고 쓰는 것입니다.
JSON.stringfy형태로 데이터를 저장하고 불러올때는 JSON.parse를 통해 다시 문자열을 제거해주는 방식으로 사용하면 문제가 해결된다.</li>
</ul>
<h2 id="어디에-사용하면-좋지">어디에 사용하면 좋지?</h2>
<p>여기까지 web-storage에대해 알아본 결과 현재 issue가생겼던 부분에 사용하는 것은 적합하지 않다고 판단이 들었다... 그렇다면 어디에 사용하면 좋을까라는 생각이든다.</p>
<p>👍이를 알아보기 위해 다음과같은 블로그 글을 참고해서 알아봤다.</p>
<h3 id="웹-스토리지를-활용한-대표적인-기능">웹 스토리지를 활용한 대표적인 기능</h3>
<p>1 .sesstionStorage를 활용해서 사용자가 &#39;입력폼&#39;을 입력하다가 페이지에서 벗어난 경우 백업/복구
2. 글쓰기를 하다가 사용자가 창을 벗어난 경우 관련 작성하던 내용 백업/복구용
3. 웹서버에 필수적으로 접근해야 하는 캐쉬용(캐쉬로 먼저 서비스 제공, 차후에 업데이트)
4.웹페이지의 개인화 설정들에 대한 저장과 제공(캐쉬로 활용)
5.현재 읽은 글의 히스토리 저장(카운팅, 읽은글 표시 등으로 활용)
6.Canvas나 이미지에 대한 임시 저장 기능(base64로 변환)
7.웹페이지간 정보 전달(웹서버를 경유하지 않고 정보 로컬에 유지)
출처: <a href="https://ktko.tistory.com/entry/HTML-%EC%9B%B9-%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%EA%B8%B0%EB%8A%A5%EA%B3%BC-%EC%98%88%EC%A0%9C-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%99%9C%EC%9A%A9-%EB%B0%A9%EB%B2%95">https://ktko.tistory.com/entry/HTML-웹-스토리지-기능과-예제-그리고-활용-방법</a> [KTKO 개발 블로그와 여행 일기]</p>
<p>다음과 같은 부분에 자주 사용된다고 한다... 아마도 사용자의 기록들을 저장해 
서비스이용에 편리성을 주는 용도로 사용되는 것이 아닐까 라는 생각이든다. 이부분 에대해서는 추후에 적용해보며 공부하도록 해야겠다.</p>
<h2 id="해결방법-1">해결방법</h2>
<p>아직 초보 front-end개발자인 나에겐 바로떠오르지 않았지만 
issue를 해결하기 위해선 useMemo를 사용하면 간단하게 해결될 문제였다.</p>
<pre><code class="language-javascript">const mockData = useMemo(() =&gt; creatRandomUserData(270), [])</code></pre>
<p>다음과 같은 방식으로 mockData를 할당해 준다면 state의 변화에도 이전과 같은 data를 유지할 수 있다.</p>
<p>front-end개발자의 역할은 client측과 surver를 이어주는 것....!
추후에 서버에서 데이터가 넘어올 것을 생각하고 과제를 진행해야 하겠지만 서버측에서 데이터를 정리해서 보내줄 것인데 front-end측에서 storage에 저장해서 과제를 이어나가는 것은 over-engineering이라고 스승님께서 조언해 주셔서 다시 생각해봤다.</p>
<h2 id="회고-time">회고 time</h2>
<p>과제를 해결해나가는데 있어서는 storage에대한 내용은 아무연관이 없었지만 
어떻게 해결해 나가는 방법이 좋은 것 일까에대한 생각을 해볼 수 있는 좋은 시간이었다.
지금당장 적용하지 못하는 내용이지만 추후에 꼭 다루어야 할 내용인거 같아서 동료들과 내용을 공유하고 싶어서 posting했다. 다들 화이팅!</p>
]]></description>
        </item>
    </channel>
</rss>