<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>minblog</title>
    <link>https://mxxcode.tistory.com/</link>
    <description>minblog!</description>
    <language>ko</language>
    <pubDate>Tue, 4 Aug 2026 21:14:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>minjae_4</managingEditor>
    <image>
      <title>minblog</title>
      <url>https://tistory1.daumcdn.net/tistory/5143612/attach/a9ca3e7b85324b3e864abc72b1aa1299</url>
      <link>https://mxxcode.tistory.com</link>
    </image>
    <item>
      <title>웹 성능 측정 지표(Web Vitals)</title>
      <link>https://mxxcode.tistory.com/127</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-05 오후 3.45.56.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;417&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UGLHC/btssZgWfNeH/0gZAzfEuyUax9kso2VodzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UGLHC/btssZgWfNeH/0gZAzfEuyUax9kso2VodzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UGLHC/btssZgWfNeH/0gZAzfEuyUax9kso2VodzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUGLHC%2FbtssZgWfNeH%2F0gZAzfEuyUax9kso2VodzK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;479&quot; height=&quot;237&quot; data-filename=&quot;스크린샷 2023-09-05 오후 3.45.56.png&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;417&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트의 성능은 사용자들에게 첫인상으로 또는 다시는 사이트에 돌아오지 않게 되는 중요한 요소일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 시작하기 전에도 느린 응답이나 이벤트 처리가 생기면 잘 처리가 되더라도 불편하다는 인식을 가지게 되었다. 개발 이후에는 서비스의 성공과도 연결될 수 있다는 생각에 성능 개선에 관심을 가지게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lighthouse로 성능을 측정하고 개선을 진행해보기도 하며 다양한 지표와 시점들을 보게 되었고 이를 정리해보고자 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TTFB (Time to first byte)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTFB는 브라우저가 페이지를 요청하고 첫번째 byte를 받는 사이의 시간을 의미한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DNS 검색과 HTTPS로 연결되는 경우 TCP 핸드쉐이크, SSL 핸드쉐이크 연결을 구축하는 시간도 포함한다.&lt;/li&gt;
&lt;li&gt;TTFB는 핵심 웹 성능 지표가 아니다, CSR은 빈 페이지를 받아 빠르지만 SSR은 이보다는 느리더라도 좋은 FCP, LCP 값을 가지기 때문이다.&lt;/li&gt;
&lt;li&gt;좋은 사용자 경험을 위해 800ms 이하여야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;FCP (First contentful paint)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FCP는 브라우저가 첫번째 컨텐트 bit(text, image, video, canvas, non-empty SVG)를 돔에 렌더링 하는 시간을 의미한다. 유저에게 페이지가 실제로 로딩 중이라는 피드백을 제공할 수 있게 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;iframes 내용은 제외된다.&lt;/li&gt;
&lt;li&gt;웹폰트 펜딩을 포함한 텍스트는 포함한다.&lt;/li&gt;
&lt;li&gt;유저는 페이지 콘텐츠 소비를 시작할 수 있는 첫 번째 시간이다.&lt;/li&gt;
&lt;li&gt;좋은 사용자 경험을 위해 1.8s 이하여야 한다.&lt;/li&gt;
&lt;li&gt;개선 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/i18n/ko/fcp/#fcp%EB%A5%BC-%EA%B0%9C%EC%84%A0%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;First Contentful Paint&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;LCP (Largest Contentful Paint)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LCP는 페이지의 메인 콘텐츠가 로드되었을 가능성이 있을 때 페이지 로드 타임라인에 표시된 시점, 가장 큰 콘텐츠를 기준으로 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 감지하는 로드 속도의 지표로 중요한 역할을 한다.&lt;/li&gt;
&lt;li&gt;즉 LCP가 빠르다면 사용자가 해당 페이지를 사용할 수 있다고 인지하는데 도움이 된다.&lt;/li&gt;
&lt;li&gt;좋은 사용자 경험을 위해서는 2.5s 이하여야 한다.&lt;/li&gt;
&lt;li&gt;느린 LCP가 발생하는 요소
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;느린 서버 응답 시간&lt;/li&gt;
&lt;li&gt;렌더링 차단 JavaScript 및 css&lt;/li&gt;
&lt;li&gt;느린 리소스 로드 시간&lt;/li&gt;
&lt;li&gt;클라이언트 사이드 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;개선 방법&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://web.dev/optimize-lcp/&quot;&gt;Optimize Largest Contentful Paint&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;FID (First Input Delay)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 링크를 클릭하거나, 버튼 클릭, JavaScript 기반 컨트롤을 사용하는 등 처음으로 상호 작용할 때부터 해당 상호작용에 대한 응답으로 브라우저가 실제로 인터렉티브한 동작을 시작하기까지의 시간을 의미한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;지연 부분만 측정&lt;/b&gt;하여 이벤트 처리 시간 자체나 UI 업데이트하는 데 걸리는 시간은 측정하지 않는다.&lt;/li&gt;
&lt;li&gt;좋은 사용자 경험을 위해 100ms 이하여야 한다.&lt;/li&gt;
&lt;li&gt;입력 지연은 브라우저의 메인 스레드가 다른 작업을 하고 있어 사용자에게 응답할 수 없어 발생한다.&lt;/li&gt;
&lt;li&gt;실제 사용자가 필요하기 때문에 개발 환경에서 측정할 수는 없다.&lt;/li&gt;
&lt;li&gt;2024년 3월에 INP (Interaction to Next Paint)로 대체 보류 중인 지표이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TTI (Time to Interactive)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TTI는 페이지가 로드되기 시작한 시점부터 주요 하위 리소스가 로드되고 사용자 입력에 신속하고 안정적으로 응답할 수 있는 시점까지의 시간을 말한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FCP가 시작된 시점부터 사용자가 인터렉티브한 작업이 가능한 시점의 시간&lt;/li&gt;
&lt;li&gt;좋은 사용자 경험을 위해 5s 미만이어야 한다.&lt;/li&gt;
&lt;li&gt;SSR과 같은 기술로 빠른 속도로 페이지가 인터렉티브 해 보이게는 가능하지만 실제로는 인터렉티브 하지 않습니다. 메인 스레드가 차단되었거나 이러한 요소를 제어하는 JS 코드가 로드되지 않았기 때문입니다.&lt;/li&gt;
&lt;li&gt;FCP와 TTI 사이를 최소화하기 위해 최대한의 노력을 기울여야 한다.&lt;/li&gt;
&lt;li&gt;개선 방법
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://web.dev/tti/#tti%EB%A5%BC-%EA%B0%9C%EC%84%A0%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Time to Interactive (TTI)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;FID와 TTI의 차이점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FID 첫 사용자 입력에 대한 지연 시간을 TTI는 입력 응답에 안정적인 시점을 말하기에 다릅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0869.jpg&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caFJCL/btstcQ2GSj2/qllr9kXkMfyKSjr74urWY1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caFJCL/btstcQ2GSj2/qllr9kXkMfyKSjr74urWY1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caFJCL/btstcQ2GSj2/qllr9kXkMfyKSjr74urWY1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaFJCL%2FbtstcQ2GSj2%2Fqllr9kXkMfyKSjr74urWY1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;635&quot; height=&quot;344&quot; data-filename=&quot;IMG_0869.jpg&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 이미지처럼 메인 스레드에 긴 작업이 점유중일 때 입력에 빠르게 응답할 수 없는 상황에서 지연이 일어난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 FID와 TTI와 연관이 있을 것이라고 생각하였으나 이는 다르고 TBT와 관련이 있어 개발 환경에서 TBT를 개선하면 FID 또한 개선된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CLS (Cumulative Layout Shift)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLS는 페이지의 전체 라이프사이클 동안 발생하는 모든 예기치 않은 레이아웃 이동에 대해 가장 큰 레이아웃 이동에 대한 점수를 의미한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;좋은 사용자 경험을 위해 CLS 점수가 0.1 이하여야 한다.&lt;/li&gt;
&lt;li&gt;영향받은 비율과 이동 거리 비율을 통해 계산된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;TBT (Total Blocking Time)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TBT는 페이지가 마우스 클릭, 화면 탭, 키보드 입력과 같은 사용자 입력에 응답하지 못하도록 차단된 총시간을 측정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FCP와 TTI 사이의 모든 50ms 이상 실행되는 작업의 차단 부분을 더하여 계산된다.&lt;/li&gt;
&lt;li&gt;좋은 사용자 경험을 위해 200ms 이하여야 한다.&lt;/li&gt;
&lt;li&gt;코드 스플리팅, 사용하지 않는 코드 제거, 효율적인 JS 로드로 점수를 향상할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SI (Speed Index)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SI는 페이지 로드 중에 콘텐츠가 시각적으로 표시되는 속도를 측정하는 성능 측정 항목&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뷰포트 크기에 따라 달라진다.&lt;/li&gt;
&lt;li&gt;페이지가 시각적으로 완료될 때까지 100ms 간격으로 페이지 완성 비율을 계산한다.&lt;/li&gt;
&lt;li&gt;좋은 사용성을 위해 3.4s 미만여야 한다.&lt;/li&gt;
&lt;li&gt;메인 스레드 작업을 최소화하거나 자바스크립트 실행 시간을 단축시키고 FOIT에 유의하여 개선할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Next.js에서 web vitals( TTFB, FCP, LCP, FID) 확인하기&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;_app.tsx에서 아래 코드와 같이 작성해 준다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1693896635235&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// _app.tsx
export function reportWebVitals(metric: NextWebVitalsMetric) {
  console.log(metric);
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 내용을 확인할 수 있다. (참고로 value는 duration을 의미하며 밀리세컨드이다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-04 오후 9.51.11.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbJB81/btstfmUhDIr/S2P9fXPwFix9LIBpj5eNrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbJB81/btstfmUhDIr/S2P9fXPwFix9LIBpj5eNrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbJB81/btstfmUhDIr/S2P9fXPwFix9LIBpj5eNrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbJB81%2FbtstfmUhDIr%2FS2P9fXPwFix9LIBpj5eNrK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;567&quot; height=&quot;508&quot; data-filename=&quot;스크린샷 2023-09-04 오후 9.51.11.png&quot; data-origin-width=&quot;960&quot; data-origin-height=&quot;860&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/pages/building-your-application/optimizing/analytics&quot;&gt;Optimizing: Analytics&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693896566810&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Optimizing: Analytics | Next.js&quot; data-og-description=&quot;Using Pages Router Features available in /pages&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/pages/building-your-application/optimizing/analytics&quot; data-og-url=&quot;https://nextjs.org/docs/pages/building-your-application/optimizing/analytics&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/br47GO/hyTPHMujhg/WUPJr9KAKKSdX6g5Hl3vY1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/b3lwhM/hyTPDJ44dG/M6CfhUc84YgRT9KKpDYPT1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/pages/building-your-application/optimizing/analytics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://nextjs.org/docs/pages/building-your-application/optimizing/analytics&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/br47GO/hyTPHMujhg/WUPJr9KAKKSdX6g5Hl3vY1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/b3lwhM/hyTPDJ44dG/M6CfhUc84YgRT9KKpDYPT1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Optimizing: Analytics | Next.js&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Using Pages Router Features available in /pages&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;nextjs.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 지표들을 확인해 보며 웹 성능을 측정하기 위한 다양한 기준들을 살펴보았다. Lighthouse 내용과 web vitals를 확인하니 어떤 부분에 문제가 있는지 빠르게 캐치할 수 있었다. 개선 방법은 다양하고 상황에 맞는 방법을 찾는 것이 개발자에게 필요한 역량일 것이라고 생각이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참조&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TTFB: &lt;a href=&quot;https://web.dev/ttfb/&quot;&gt;https://web.dev/ttfb/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;FCP: &lt;a href=&quot;https://web.dev/i18n/ko/fcp/&quot;&gt;https://web.dev/i18n/ko/fcp/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;LCP: &lt;a href=&quot;https://web.dev/i18n/ko/lcp/&quot;&gt;https://web.dev/i18n/ko/lcp/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;FID: &lt;a href=&quot;https://web.dev/i18n/ko/fid/&quot;&gt;https://web.dev/i18n/ko/fid/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TTI: &lt;a href=&quot;https://web.dev/tti/&quot;&gt;https://web.dev/tti/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;CLS: &lt;a href=&quot;https://web.dev/i18n/ko/cls/&quot;&gt;https://web.dev/i18n/ko/cls/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;TBT: &lt;a href=&quot;https://web.dev/tbt/&quot;&gt;https://web.dev/tbt/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SI: &lt;a href=&quot;https://developer.chrome.com/docs/lighthouse/performance/speed-index/&quot;&gt;https://developer.chrome.com/docs/lighthouse/performance/speed-index/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프론트엔드</category>
      <category>FCP</category>
      <category>LCP</category>
      <category>lighthouse</category>
      <category>metrics</category>
      <category>web vitals</category>
      <category>웹 성능 측정 지표</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/127</guid>
      <comments>https://mxxcode.tistory.com/127#entry127comment</comments>
      <pubDate>Tue, 5 Sep 2023 15:56:47 +0900</pubDate>
    </item>
    <item>
      <title>가비지 컬렉션(garbage collection)</title>
      <link>https://mxxcode.tistory.com/126</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-08-11 오후 9.54.00.png&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;707&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pYr4x/btsqZDEPy4p/LO0xwyC6PzcP5NTIYQzkY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pYr4x/btsqZDEPy4p/LO0xwyC6PzcP5NTIYQzkY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pYr4x/btsqZDEPy4p/LO0xwyC6PzcP5NTIYQzkY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpYr4x%2FbtsqZDEPy4p%2FLO0xwyC6PzcP5NTIYQzkY0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;293&quot; data-filename=&quot;스크린샷 2023-08-11 오후 9.54.00.png&quot; data-origin-width=&quot;1446&quot; data-origin-height=&quot;707&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 컴퓨터 과학을 CS50(네이버 부스트코스 강의)로 접하면서 C를 사용한 강의를 수강한 적이 있습니다. 이때 C 언어는 malloc() , free()와 같은 함수로 메모리 할당을 해제하는 모습을 보았고 자바스크립트에서는 어떠한 방식으로 메모리를 관리하는지 궁금하여 가비지 컬렉션에 대해 정리를 하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;garbage collection이란?&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;메모리 관리&lt;/b&gt; 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제하는 기능입니다.&lt;/blockquote&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;메모리 관리&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gif와 같이 컴퓨터는 우리가 작성한 변수, 함수, 객체들을 메모리에 할당한다. 하지만 계속 할당만 해서 메모리가 꽉 차게되면 작업중인 프로그램이 느려지고 결국은 강제종료되는 현상이 일어나게됩니다.&lt;/li&gt;
&lt;li&gt;가상메모리도 메모리 관리를 위한 기법 중 하나입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Garbage_collection.gif&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJJQes/btsqYPsxXkQ/Nl90KyyweIjNnIOA1HowRk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJJQes/btsqYPsxXkQ/Nl90KyyweIjNnIOA1HowRk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJJQes/btsqYPsxXkQ/Nl90KyyweIjNnIOA1HowRk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bJJQes/btsqYPsxXkQ/Nl90KyyweIjNnIOA1HowRk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;252&quot; data-filename=&quot;Garbage_collection.gif&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리스프라는 프로그래밍 언어의 문제를 해결하기 위해 존 매카시가 개발하였습니다.&lt;/li&gt;
&lt;li&gt;C, C++ 등의 프로그래밍 언어는 수동 메모리 관리를 가정하고 설계되었습니다. (malloc() , free())&lt;/li&gt;
&lt;li&gt;Jave, C# 같은 언어들은 처음부터 GC 기법을 염두에 두고 설계되어, 언어 정의에 GC이 포함되어 있습니다. JavaScript도 마찬가지입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;메모리 관리의 필요성 (...GC가 필요한 이유)&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수동으로 메모리를 관리하는 방식에서 프로그래머가 까먹고 해제하지 못했을 때, 혹은 실수가 발생했을 때 아래와 같은 버그가 발생하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메모리 누수(memory leak)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 이상 필요하지 않은 메모리가 정리되지 않고 남아있는 버그입니다.&lt;/li&gt;
&lt;li&gt;메모리 누수의 반복되면 메모리 고갈로 프로그램이 중단될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;유효하지 않은 포인터 (Dangling Pointer)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 해제된 메모리에 접근하는 버그입니다.&lt;/li&gt;
&lt;li&gt;댕글링 포인터인 메모리 접근 시 프로그램이 예측 불가능한 동작을 하게 되는 위험이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;이중 해제&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 해제된 메모리를 또 다시 해제하는 버그입니다.&lt;/li&gt;
&lt;li&gt;이 또한 오류를 일으킬 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GC의 단점&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 메모리를 해제할지 결정하는 데 비용이 듭니다. 객체가 필요없어지는 시점을 프로그래머가 미리 알고 있는 경우에도 &lt;b&gt;GC 알고리즘&lt;/b&gt;이 메모리 해제 시점을 추적해야 하므로, 이 작업은 오버헤드(처리 시간, 메모리가 소비)가 됩니다.&lt;/li&gt;
&lt;li&gt;GC이 일어나는 타이밍이나 점유 시간을 미리 예측하기 어렵습니다. 때문에 프로그램이 예측 불가능하게 일시적으로 정지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;할당된 메모리가 해제되는 시점을 알 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GC Algorithm&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 알고리즘으로 두가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;참조 횟수 계산 방식(Reference Counting)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0858.jpg&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;777&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYRy51/btsq09XxgGr/ecHb7MGn3oKEnqfLn8q7KK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYRy51/btsq09XxgGr/ecHb7MGn3oKEnqfLn8q7KK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYRy51/btsq09XxgGr/ecHb7MGn3oKEnqfLn8q7KK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYRy51%2Fbtsq09XxgGr%2FecHb7MGn3oKEnqfLn8q7KK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;275&quot; data-filename=&quot;IMG_0858.jpg&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;777&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Root Space - 스택 변수, 전역 변수 등 Heap 영역 참조를 담은 변수입니다.&lt;/li&gt;
&lt;li&gt;Heap 영역에 선언된 객체들에 참조 횟수를 기억하고 이 횟수가 0이 되면 해당 객체를 해제하는 방식입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한계점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;순환 참조의 경우 횟수가 1로 유지되는 문제가 있습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대표젹인 예로 도달할 수 없는 섬(Unreachable Island)이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-02-05 오전 3.22.33.png&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds0GCb/btsq1cfLEFn/CCWROdpcxLD0d1FBJWgBkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds0GCb/btsq1cfLEFn/CCWROdpcxLD0d1FBJWgBkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds0GCb/btsq1cfLEFn/CCWROdpcxLD0d1FBJWgBkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds0GCb%2Fbtsq1cfLEFn%2FCCWROdpcxLD0d1FBJWgBkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;350&quot; height=&quot;199&quot; data-filename=&quot;스크린샷 2023-02-05 오전 3.22.33.png&quot; data-origin-width=&quot;459&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;포인터 추적 방식(Mark And Sweep)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0859.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;923&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBBzSN/btsqYo9PBk7/kNy7eTiw1Nwdg6I21PQhb1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBBzSN/btsqYo9PBk7/kNy7eTiw1Nwdg6I21PQhb1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBBzSN/btsqYo9PBk7/kNy7eTiw1Nwdg6I21PQhb1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBBzSN%2FbtsqYo9PBk7%2FkNy7eTiw1Nwdg6I21PQhb1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;368&quot; height=&quot;283&quot; data-filename=&quot;IMG_0859.jpg&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;923&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Root와 연결된 객체들(Marked)은 두고 연결이 끊어진 객체들을 지우는 방식(sweep)입니다.&lt;/li&gt;
&lt;li&gt;객체가 가르키는 영역을 &amp;ldquo;사용중&amp;rdquo;으로 표시하고 그 객체가 가리키는 객체도 사용중으로 표시하면 접근 불가능한 메모리를 정리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;순환 참조의 문제 해결할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 한계점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 메모리를 언제 해제할지에 대해 수동으로 결정하는 것이 편할 때가 있습니다.&lt;/li&gt;
&lt;li&gt;수동으로 메모리를 해제하고 싶다면 객체 메모리가 unreachable 상태로 명시하는 기능이 있어야 합니다..&lt;/li&gt;
&lt;li&gt;2019년 현재의 JavaScript에서는 명시적으로 또는 프로그래밍 방식으로 가비지 컬렉션을 작동할 수 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;최적화 기법&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;generational collection(세대별 수집)&lt;/li&gt;
&lt;li&gt;incremental collection(점진적 수집)&lt;/li&gt;
&lt;li&gt;idle-time collection(유휴 시간 수집)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;JavaScript의 GC&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;자바스크립트와 같은 고수준 언어들은&lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt; 자동 메모리 관리 방법을 사용합니다. 가비지 콜렉터의 목적은 메모리 할당을 추적하고 할당된 메모리 블록이 더 이상 필요하지 않게 되었는지를 판단하여 회수하는 것입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Javascript는 브라우저 엔진 내의 가비지 컬렉터(Garbage Colloector)에서 메모리 관리를 수행합니다.&lt;/li&gt;
&lt;li&gt;GC의 기준은 도달 가능성(Reachability)에 따라서 메모리 관리를 수행합니다..&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 알고리즘을 사용할까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터넷 익스플로러 6, 7 은 DOM 오브젝트에 대해 Reference counting 알고리즘으로 GC를 수행했었습니다. (&lt;span style=&quot;text-align: start;&quot;&gt;최신 브라우저는 더 이상 참조 횟수 가비지 콜렉션 방식을 사용하지 않습니다.)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;현재 엔진은 Mark And Sweep 알고리즘을 사용합니다. (IE, Firefox, Opera, Chrome, Safari)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저수준 언어에서는 직접 메모리 할당을 해제하는 작업을 거치지만 고수준 언어는 가비지 컬렉터를 통해 관리합니다.&lt;/li&gt;
&lt;li&gt;자바스크립트에서는 브라우저, 노드의 V8 엔진의 GC를 통해 메모리를 관리합니다.&lt;/li&gt;
&lt;li&gt;고수준 언어인 자바스크립트에서는 메모리가 자동으로 관리되기에 이를 억지로 실행하거나 막을 수 없습니다. (수동으로 조작 불가)&lt;/li&gt;
&lt;li&gt;도달 가능성을 기준으로 유지, 해제하는 (Mark and Sweep) 알고리즘을 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참조&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.javascript.info/garbage-collection&quot;&gt;가비지 컬렉션&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691759387233&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;가비지 컬렉션&quot; data-og-description=&quot;&quot; data-og-host=&quot;ko.javascript.info&quot; data-og-source-url=&quot;https://ko.javascript.info/garbage-collection&quot; data-og-url=&quot;https://ko.javascript.info/garbage-collection&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/T94uB/hyTCFUIINF/bkw0r2zGAMegtkU9u42RBK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dxjdhu/hyTCJivAbi/iKhGOJEXL6c9Edy9jM4yC1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512&quot;&gt;&lt;a href=&quot;https://ko.javascript.info/garbage-collection&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.javascript.info/garbage-collection&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/T94uB/hyTCFUIINF/bkw0r2zGAMegtkU9u42RBK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/dxjdhu/hyTCJivAbi/iKhGOJEXL6c9Edy9jM4yC1/img.png?width=512&amp;amp;height=512&amp;amp;face=0_0_512_512');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;가비지 컬렉션&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.javascript.info&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management&quot;&gt;Memory management - JavaScript | MDN&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691759392033&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Memory management - JavaScript | MDN&quot; data-og-description=&quot;Low-level languages like C, have manual memory management primitives such as malloc() and free(). In contrast, JavaScript automatically allocates memory when objects are created and frees it when they are not used anymore (garbage collection). This automat&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management&quot; data-og-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_management&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/wgIBF/hyTCG0n020/Ku8qd04nAMjdt7VnYrVcc1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot;&gt;&lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Memory_Management&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/wgIBF/hyTCG0n020/Ku8qd04nAMjdt7VnYrVcc1/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Memory management - JavaScript | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Low-level languages like C, have manual memory management primitives such as malloc() and free(). In contrast, JavaScript automatically allocates memory when objects are created and frees it when they are not used anymore (garbage collection). This automat&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.mozilla.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)&quot;&gt;쓰레기 수집 (컴퓨터 과학) - 위키백과, 우리 모두의 백과사전&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691759390071&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;쓰레기 수집 (컴퓨터 과학) - 위키백과, 우리 모두의 백과사전&quot; data-og-description=&quot;위키백과, 우리 모두의 백과사전. 쓰레기 수집(garbage collection 가비지 컬렉션[*], GC)은 메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제&quot; data-og-host=&quot;ko.wikipedia.org&quot; data-og-source-url=&quot;https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)&quot; data-og-url=&quot;https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dQz5CQ/hyTCEBuFFy/ihSRAolOQ6uVt1wqm8A140/img.gif?width=397&amp;amp;height=252&amp;amp;face=0_0_397_252,https://scrap.kakaocdn.net/dn/qkXoV/hyTBCFgKD9/tkKoaTkeJQs7PcmCeCFtYk/img.gif?width=397&amp;amp;height=252&amp;amp;face=0_0_397_252&quot;&gt;&lt;a href=&quot;https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.wikipedia.org/wiki/%EC%93%B0%EB%A0%88%EA%B8%B0_%EC%88%98%EC%A7%91_(%EC%BB%B4%ED%93%A8%ED%84%B0_%EA%B3%BC%ED%95%99)&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dQz5CQ/hyTCEBuFFy/ihSRAolOQ6uVt1wqm8A140/img.gif?width=397&amp;amp;height=252&amp;amp;face=0_0_397_252,https://scrap.kakaocdn.net/dn/qkXoV/hyTBCFgKD9/tkKoaTkeJQs7PcmCeCFtYk/img.gif?width=397&amp;amp;height=252&amp;amp;face=0_0_397_252');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;쓰레기 수집 (컴퓨터 과학) - 위키백과, 우리 모두의 백과사전&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;위키백과, 우리 모두의 백과사전. 쓰레기 수집(garbage collection 가비지 컬렉션[*], GC)은 메모리 관리 기법 중의 하나로, 프로그램이 동적으로 할당했던 메모리 영역 중에서 필요없게 된 영역을 해제&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.wikipedia.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <category>garbage collection</category>
      <category>가비지 컬렉션</category>
      <category>메모리 관리</category>
      <category>메모리 누수</category>
      <category>자바스크립트</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/126</guid>
      <comments>https://mxxcode.tistory.com/126#entry126comment</comments>
      <pubDate>Fri, 11 Aug 2023 23:23:17 +0900</pubDate>
    </item>
    <item>
      <title>[React] key prop은 왜 사용할까?</title>
      <link>https://mxxcode.tistory.com/125</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YOFQE/btsoyXlARaS/Pi6CFl2PrXbdxyJ8EUWzo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YOFQE/btsoyXlARaS/Pi6CFl2PrXbdxyJ8EUWzo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YOFQE/btsoyXlARaS/Pi6CFl2PrXbdxyJ8EUWzo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYOFQE%2FbtsoyXlARaS%2FPi6CFl2PrXbdxyJ8EUWzo0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;344&quot; height=&quot;226&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트를 어느 정도 학습한 분들이라면 key prop에 대해 들어보았을 것이고 이것이 리스트 렌더링 할 때만 사용하는 값이라고 생각할 수 있다. key prop은 조금 더 의미를 가지고 있다. 이번 글은 리액트 공식문서 스터디를 진행하며 스터디원들과 이야기했던 key에 대해 작성하려고 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;map과 함께 사용하는 key prop&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 list를 렌더링하기 위해 map 메서드와 함께 사용한다. 리턴하는 가장 바깥쪽 JSX props로 key 값을 입력하지 않으면 다음과 같은 에러가 발생한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Warning: Each child in a list should have a unique &amp;ldquo;key&amp;rdquo; prop.&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 에러가 발생하여도 우리는 화면에 렌더링된 내용을 확인할 수 있다. 이는 React는 개발자가 &lt;b&gt;key를 작성하지 않으면 index를 key로 활용&lt;/b&gt;하기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;리스트 렌더링에서의 key의 목적&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다&lt;/span&gt;. 배열의 요소 삽입, 삭제, 정렬로 인한 이동시에 React는 key값을 통해 변화를 추론하고 DOM 트리를 업데이트하는데 도움이 된다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;index를 값으로 활용할 때의 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;index를 key로 활용하고 배열에 변경이 일어날 때 생기는 문제를 todo list를 통해 살펴보자, id 값이 있지만 이를 무시하고 Index로 배열을 렌더링하였다. 새로운 todo를 추가하면 배열의 맨 앞에 값이 입력된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7m8MD/btsoxal0jK8/ZPH3TzcLfo9kegHHKCpajk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7m8MD/btsoxal0jK8/ZPH3TzcLfo9kegHHKCpajk/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;430&quot; data-filename=&quot;Jul-23-2023 22-20-50.gif&quot; data-widthpercent=&quot;50&quot; width=&quot;373&quot; height=&quot;300&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7m8MD/btsoxal0jK8/ZPH3TzcLfo9kegHHKCpajk/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7m8MD%2Fbtsoxal0jK8%2FZPH3TzcLfo9kegHHKCpajk%2Fimg.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Avc7g/btsoEp9tqnT/XDjBk2iAQNKcTmI3zLAVT1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Avc7g/btsoEp9tqnT/XDjBk2iAQNKcTmI3zLAVT1/img.gif&quot; data-is-animation=&quot;true&quot; data-origin-width=&quot;534&quot; data-origin-height=&quot;430&quot; data-filename=&quot;Jul-23-2023 22-20-44.gif&quot; data-widthpercent=&quot;50&quot; width=&quot;304&quot; height=&quot;245&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Avc7g/btsoEp9tqnT/XDjBk2iAQNKcTmI3zLAVT1/img.gif&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAvc7g%2FbtsoEp9tqnT%2FXDjBk2iAQNKcTmI3zLAVT1%2Fimg.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌 key - index, 우 key - unique key&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번을 추가하자 input의 내용이 실제 컴포넌트를 따라가는것이 아닌 index에 따라 표시되고 있다. 배열에 요소를 삽입하여 &lt;b&gt;리렌더링으로 index를 다시 매핑&lt;/b&gt;하게 된다. 유니크한 key값을 설정하여 예상하지 못하게 동작되는 부분을 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 또 한가지 문제는 정확한 요소에 접근하고 작업이 이뤄지지 않고 자바스크립트에서 배열을 처리하는 것과 같이 업데이트가 일어나면 배열을 밀고 당기듯 &lt;b&gt;불필요한 리렌더링&lt;/b&gt;이 일어난다는 점이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;index key는 무조건 나쁜 것인가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 key를 index로 입력한다고 에러를 발생시키진 않는다. 앞서 설명한 내용과 같은 문제가 발생할 수 있으나 배열의 변경 없이 렌더링만 필요한 경우에는 index로 화면을 구성해도 문제는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 다른 값을 적용해야 한다는 압박감에 Math.random()으로 key를 생성하는 경우가 있다. 이는 렌더링 간의 key 불일치가 발생하여 모든 컴포넌트와 DOM이 매번 다시 생성될 수 있다. 이는 성능적인 문제와 입력값 손실을 야기한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;key로 어떤걸 사용해야 할까?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스의 데이터: 데이터 베이스에서 데이터를 가져오는 경우 포함된 id, key를 사용하자&lt;/li&gt;
&lt;li&gt;로컬에서 생성된 데이터: 데이터가 로컬에서 생성되고 유지되는 경우 crypto.randomUUID() 또는 uuid와 같은 패키지를 사용하자&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지켜야 할 규칙으로는&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;key는 형제간(특정 배열 렌더링을 위한 컴포넌트들) 고유해야 한다. 하지만 다른 배열과 JSX 노드에 동일한 key를 사용해도 된다.&lt;/li&gt;
&lt;li&gt;key는 변경되어서는 안 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;key를 활용한 state 보존/초기화&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용을 정리하기 위해 글을 작성하기 시작하였는데 서론이 길어졌다. React는 어떤 방식으로 컴포넌트의 변경을 식별하고 state 초기화와 보존과 연관되어 있는지가 이 글의 핵심이 될 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;React의 State 보존과 초기화 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 UI 트리에서의 위치를 통해 State가 어떤 컴포넌트에 속하는지 추적한다. 다음 그림을 통해 React가 컴포넌트들을 통해 UI 트리를 만들고 React DOM을 통해 브라우저의 DOM과 일치하도록 업데이트합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u6FzE/btsoDJ750Dz/sJbuCTszX9cc5uG62uyYwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u6FzE/btsoDJ750Dz/sJbuCTszX9cc5uG62uyYwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u6FzE/btsoDJ750Dz/sJbuCTszX9cc5uG62uyYwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu6FzE%2FbtsoDJ750Dz%2FsJbuCTszX9cc5uG62uyYwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1080&quot; height=&quot;240&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;240&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 각 컴포넌트에 정의된 state가 컴포넌트마다 정확하게 매핑되어 있다고 생각할 수 있지만 state 또한 React 내부에 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;React는 컴포넌트 UI 트리 위치를 이용해 각 state를 매핑&lt;/b&gt;한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;UI 트리로 state를 다룰 때의 문제점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용을 토대로 코드와 예시를 확인해 보자.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;import { useState } from 'react';

export default function App() {
  const [isMe, setIsMe] = useState(true)
  return (
  	&amp;lt;div&amp;gt;
    	{isMe ? &amp;lt;Counter name='me' /&amp;gt; : &amp;lt;Counter name='someone' /&amp;gt;}
        &amp;lt;button onClick={() =&amp;gt; setIsMe(prev =&amp;gt; !prev)}&amp;gt;바꾸기&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

function Counter({ name }) {
	const [count, setCount] = useState(0)
	return (
		&amp;lt;div&amp;gt;
      		&amp;lt;h1&amp;gt;{count}&amp;lt;/h1&amp;gt;
            &amp;lt;h3&amp;gt;{name}&amp;lt;/h3&amp;gt;
      		&amp;lt;button onClick={() =&amp;gt; setCount(count + 1)}&amp;gt;
            	+1
            &amp;lt;/button&amp;gt;
    	&amp;lt;/div&amp;gt;
	)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버튼을 누르면 count가 1씩 증가하는 컴포넌트와 me와 someone을 통해 다른 컴포넌트로 교체되도록 구현하였다. 실제로 작동하면 아래와 같은 문제가 발생하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jul-23-2023 22-45-50.gif&quot; data-origin-width=&quot;122&quot; data-origin-height=&quot;182&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/colH48/btsoz8UU0w6/TahkXKvQFCQchTwwol7Rt1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/colH48/btsoz8UU0w6/TahkXKvQFCQchTwwol7Rt1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/colH48/btsoz8UU0w6/TahkXKvQFCQchTwwol7Rt1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/colH48/btsoz8UU0w6/TahkXKvQFCQchTwwol7Rt1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;122&quot; height=&quot;182&quot; data-filename=&quot;Jul-23-2023 22-45-50.gif&quot; data-origin-width=&quot;122&quot; data-origin-height=&quot;182&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 내용은 Someone 컴포넌트로 교체되었는데 count state는 그대로 유지되고 있다. 우리는 컴포넌트가 교체되었다고 생각하고 있고 이는 틀린 내용은 아니다. 하지만 React가 변화를 추적하는 과정에서 UI 트리에서 변화가 없다고 판단하여 상태값을 보존하고 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 생각하는 방식으로 state를 초기화시켜 각 사람의 count를 따로 다루고 싶다면 UI 트리에 변동이 일어나야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 방법으로는 아래 3가지 방법이 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;...
//방법1 - 같은 위치에 다른 컴포넌트 렌더링
	&amp;lt;div&amp;gt;
    	{isMe ? (
	    &amp;lt;div&amp;gt;
		    &amp;lt;Counter name=&quot;me&quot; /&amp;gt;
      	&amp;lt;/div&amp;gt;
    ) : (
    	&amp;lt;section&amp;gt;
        	&amp;lt;Counter name=&quot;someone&quot; /&amp;gt;
        &amp;lt;/section&amp;gt;
    )}
    	&amp;lt;button onClick={() =&amp;gt; setIsMe((prev) =&amp;gt; !prev)}&amp;gt;&amp;lt;/button&amp;gt;
	&amp;lt;/div&amp;gt;

//방법2 - 다른 위치에 컴포넌트 렌더링
	&amp;lt;div&amp;gt;
		{isMe &amp;amp;&amp;amp;
	    &amp;lt;Counter name=&quot;me&quot; /&amp;gt;
    	}
    	{!isMe &amp;amp;&amp;amp;
	    	&amp;lt;Counter person=&quot;someone&quot; /&amp;gt;
    	}
    	&amp;lt;button onClick={() =&amp;gt; setIsMe((prev) =&amp;gt; !prev)}&amp;gt;&amp;lt;/button&amp;gt;
	&amp;lt;/div&amp;gt;

//방법3 - key를 이용해 state 초기화
	&amp;lt;div&amp;gt;
		{isMe ? &amp;lt;Counter key='me' name='me' /&amp;gt; : &amp;lt;Counter key='someone' name='someone' /&amp;gt;}
		&amp;lt;button onClick={() =&amp;gt; setIsMe(prev =&amp;gt; !prev)}&amp;gt;바꾸기&amp;lt;/button&amp;gt;
	&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 UI 트리를 잘 활용하여 반대로 상태값을 보존하는 장점으로 활용할 수 도 있다. 실제로 검색바의 state가 초기화되는 문제를 해결하기 위해 같은 위치에 검색바를 렌더링하여 문제를 해결한 적이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jul-23-2023 22-51-39.gif&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5HdG7/btsoyaeR61r/Nsi0UKrdZOuhDnyPl0mcWK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5HdG7/btsoyaeR61r/Nsi0UKrdZOuhDnyPl0mcWK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5HdG7/btsoyaeR61r/Nsi0UKrdZOuhDnyPl0mcWK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/5HdG7/btsoyaeR61r/Nsi0UKrdZOuhDnyPl0mcWK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;298&quot; data-filename=&quot;Jul-23-2023 22-51-39.gif&quot; data-origin-width=&quot;476&quot; data-origin-height=&quot;298&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 각 페이지마다 헤더 컴포넌트를 작성한 경우에는 UI 트리의 변경으로 검색어와 옵션이 초기화되었다. 페이지 이동이 일어나더라도 헤더의 UI 트리는 변화하지 않도록 하기 위해 React-router v6의 Nested Routes를 활용해 쉽게 리팩토링을 진행할 수 있었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지를 나눠서 설명하였지만 &quot;key는 React가 각 컴포넌트의 변화를 추적할 수 있도록 한다.&amp;rdquo;로 정리할 수 있겠다. 또한 key 값으로 Index 보다는 기능 추가와 유지보수를 위해 unique한 key 값을 활용하는 것이 좋다. 리액트 공식문서 스터디를 진행하며 이야기를 나눴던 실제로 state가 초기화되거나 보존되는 직접 경험한 문제를 주로 정리하려고 하였으나 공식 문서 정리가 된 것 같은 느낌이 든다. &amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참조&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.react.dev/learn/rendering-lists&quot;&gt;리스트 렌더링 &amp;ndash; React&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690120074460&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;리스트 렌더링 &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;ko.react.dev&quot; data-og-source-url=&quot;https://ko.react.dev/learn/rendering-lists&quot; data-og-url=&quot;https://ko.react.dev/learn/rendering-lists&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/KgECv/hyTpdSuAPz/g6gguWg9EUyyfIgKSEiM41/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/S318P/hyTo33n3YN/gBm1xjVXuTWVe4xP4K61Uk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://ko.react.dev/learn/rendering-lists&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.react.dev/learn/rendering-lists&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/KgECv/hyTpdSuAPz/g6gguWg9EUyyfIgKSEiM41/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/S318P/hyTo33n3YN/gBm1xjVXuTWVe4xP4K61Uk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;리스트 렌더링 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ko.react.dev/learn/preserving-and-resetting-state&quot;&gt;State를 보존하고 초기화하기 &amp;ndash; React&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690120075989&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;State를 보존하고 초기화하기 &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;ko.react.dev&quot; data-og-source-url=&quot;https://ko.react.dev/learn/preserving-and-resetting-state&quot; data-og-url=&quot;https://ko.react.dev/learn/preserving-and-resetting-state&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cN7KVZ/hyTpgIriUm/XJxuNZ0pwliGwsxW8ICII0/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/cpVycL/hyTo2pRgr0/etXniiK9ixfFd9s0gCtvjK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://ko.react.dev/learn/preserving-and-resetting-state&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ko.react.dev/learn/preserving-and-resetting-state&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cN7KVZ/hyTpgIriUm/XJxuNZ0pwliGwsxW8ICII0/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/cpVycL/hyTo2pRgr0/etXniiK9ixfFd9s0gCtvjK/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;State를 보존하고 초기화하기 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ko.react.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://react.vlpt.us/basic/11-render-array.html&quot;&gt;11. 배열 렌더링하기 &amp;middot; GitBook&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1690120076046&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;11. 배열 렌더링하기 &amp;middot; GitBook&quot; data-og-description=&quot;11. 배열 렌더링하기 이번에는 리액트에서 배열을 렌더링하는 방법을 알아보겠습니다. 이러 배열이 있다고 가정해봅시다. const users = [ { id: 1, username: 'velopert', email: 'public.velopert@gmail.com' }, { id: 2, &quot; data-og-host=&quot;react.vlpt.us&quot; data-og-source-url=&quot;https://react.vlpt.us/basic/11-render-array.html&quot; data-og-url=&quot;https://react.vlpt.us/basic/11-render-array.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vcIva/hyTpeqjL40/FD5W7YDObGG3arIJpJGhNk/img.png?width=500&amp;amp;height=631&amp;amp;face=0_0_500_631,https://scrap.kakaocdn.net/dn/Ax1D9/hyTpeDQQI7/vLorI026G7SN9ZD1B8W2D1/img.png?width=500&amp;amp;height=375&amp;amp;face=0_0_500_375&quot;&gt;&lt;a href=&quot;https://react.vlpt.us/basic/11-render-array.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react.vlpt.us/basic/11-render-array.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vcIva/hyTpeqjL40/FD5W7YDObGG3arIJpJGhNk/img.png?width=500&amp;amp;height=631&amp;amp;face=0_0_500_631,https://scrap.kakaocdn.net/dn/Ax1D9/hyTpeDQQI7/vLorI026G7SN9ZD1B8W2D1/img.png?width=500&amp;amp;height=375&amp;amp;face=0_0_500_375');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;11. 배열 렌더링하기 &amp;middot; GitBook&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;11. 배열 렌더링하기 이번에는 리액트에서 배열을 렌더링하는 방법을 알아보겠습니다. 이러 배열이 있다고 가정해봅시다. const users = [ { id: 1, username: 'velopert', email: 'public.velopert@gmail.com' }, { id: 2,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react.vlpt.us&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>React</category>
      <category>key prop</category>
      <category>react</category>
      <category>state 보존과 초기화</category>
      <category>리스트 렌더링</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/125</guid>
      <comments>https://mxxcode.tistory.com/125#entry125comment</comments>
      <pubDate>Sun, 23 Jul 2023 23:09:54 +0900</pubDate>
    </item>
    <item>
      <title>브라우저 렌더링 과정 - (리플로우와 리페인트)</title>
      <link>https://mxxcode.tistory.com/124</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-07-03 오후 4.01.41.png&quot; data-origin-width=&quot;1409&quot; data-origin-height=&quot;709&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0pLbN/btsmiF8wbfw/aPZtbyvVFlJYFEUmAK0vKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0pLbN/btsmiF8wbfw/aPZtbyvVFlJYFEUmAK0vKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0pLbN/btsmiF8wbfw/aPZtbyvVFlJYFEUmAK0vKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0pLbN%2FbtsmiF8wbfw%2FaPZtbyvVFlJYFEUmAK0vKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;278&quot; data-filename=&quot;스크린샷 2023-07-03 오후 4.01.41.png&quot; data-origin-width=&quot;1409&quot; data-origin-height=&quot;709&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접 중 질문을 받게 되었는데 답변을 완벽하게 하지 못했다는 느낌이 들었다. 이번 기회에 충분한 정리하고 정보 공유를 위해서 브라우저 렌더링에 대한 글을 작성하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;브라우저의 기본 구조&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저의 기본적인 구조는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;사용자 인터페이스 - 주소표시줄, 뒤로 가기/앞으로 가기 버튼 등 요청 페이지를 제외한 인터페이스&lt;/li&gt;
&lt;li&gt;브라우저 엔진 - 사용자 인터페이스와 렌더링 엔진사이의 동작을 제어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;렌더링 엔진&lt;/b&gt; - 요청한 컨텐츠를 표시, HTML과 CSS를 파싱하여 화면에 표시&lt;/li&gt;
&lt;li&gt;통신 - HTTP 요청과 같은 네트워크 호출에 사용&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;text-align: left;&quot;&gt;UI 백엔드 - 버튼, 체크 박스와 같은 기본적인 위젯를 그림&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;자바스크립트 해석기 - 자바스크립트 코드를 해석하고 실행&lt;/li&gt;
&lt;li&gt;자료 저장소 - localStorage, 쿠키와 같이 로컬에 데이터를 저장하는 파트&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0848.jpg&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JNlKI/btsmkvYD1w2/898oUtnOkC5TYSKW5Tzm10/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JNlKI/btsmkvYD1w2/898oUtnOkC5TYSKW5Tzm10/img.jpg&quot; data-alt=&quot;브라우저의 기본 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JNlKI/btsmkvYD1w2/898oUtnOkC5TYSKW5Tzm10/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJNlKI%2FbtsmkvYD1w2%2F898oUtnOkC5TYSKW5Tzm10%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;476&quot; height=&quot;376&quot; data-filename=&quot;IMG_0848.jpg&quot; data-origin-width=&quot;1532&quot; data-origin-height=&quot;1211&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;브라우저의 기본 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 내용을 토대로 간략한 브라우저 렌더링 과정은 다음과 같다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답받는다.&lt;/li&gt;
&lt;li&gt;브라우저의 렌더링 엔진은 서버로부터 응답된 HTML, CSS를 파싱하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다.&lt;/li&gt;
&lt;li&gt;브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.&lt;/li&gt;
&lt;li&gt;렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면에 HTML요소를 페인팅한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 깊은 내용을 위해 우리가 학습해야 할 부분은 렌더링 엔진이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;렌더링 엔진&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 엔진을 통해 XML 문서와 이미지도 표시할 수 있고 플러그인과 브라우저 확장 기능을 통해 PDF와 다른 유형의 파일도 있다. 이번 글에서는 HTML을 표시하기 위해 동작되는 내용들을 다루려고 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HTML을 파싱하며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링 엔진은 HTML을 파싱하여 DOM을 생성한다. 개발자 도구의 네트워크 탭을 열어보면 css, 자바스크립트, 이미지, 폰트 파일들도 응답받는다. 이는 브라우저 렌더링 엔진이 HTML을 파싱하는 도중에 외부 리소스를 로드하는 태그 link, img, script 등을 만나면 파싱을 일시 정지하고 해당 리소스 파일을 서버로 요청하기 때문이다. 작업이 끝나면 다시 HTML을 파싱하여 DOM tree를 구축한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일시정지된 상황에서 CSS를 파싱하며 CSSOM을 생성하고 CSSOM tree를 구축한다. DOM tree와 CSSOM tree를 합하여 렌더 트리를 구축하기에 이를 하나의 과정으로 간략화하는 경우가 있는 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 화면에 페인팅하고 이를 합성(Composite)함으로 렌더링 과정을 설명할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 경우 브라우저 렌더링 과정은 반복해서 실행되고 레이아웃 계산과 페인팅이 재차 실행된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바스크립트에 의한 노드 추가 또는 삭제&lt;/li&gt;
&lt;li&gt;브라우저 창의 리사이징에 의한 뷰포트 크기 변경&lt;/li&gt;
&lt;li&gt;HTML 요소의 레이아웃에 변경을 발생시키는 스타일 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이아웃 계산과 페인팅을 다시 실행하는 리렌더링은 비용이 많이 드는 작업이다. (성능에 악영향을 줌) 그렇기에 가급적 리렌더링이 빈번하게 발생하지 않도록 주의해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_0847.jpg&quot; data-origin-width=&quot;2141&quot; data-origin-height=&quot;622&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b7r9y0/btsl4lXPKbI/bHFt2PX0jhEec3MfLFbPT1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b7r9y0/btsl4lXPKbI/bHFt2PX0jhEec3MfLFbPT1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b7r9y0/btsl4lXPKbI/bHFt2PX0jhEec3MfLFbPT1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb7r9y0%2Fbtsl4lXPKbI%2FbHFt2PX0jhEec3MfLFbPT1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;719&quot; height=&quot;209&quot; data-filename=&quot;IMG_0847.jpg&quot; data-origin-width=&quot;2141&quot; data-origin-height=&quot;622&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리플로우와 리페인트&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 설명했던 브라우저 렌더링 과정이 반복해서 실행되는 경우에 대해서 더 깊게 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM, CSSOM이 변경된다. 변경된 둘은 다시 렌더 트리로 결합되고 변경된 렌더트리를 기반으로 레이아웃 페인트 과정을 거쳐 브라우저의 화면을 다시 렌더링한다. 이를 리플로우, 리페인트라고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리플로우는 레이아웃 계산을 다시 하는 것을 말한다. 노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행된다.&lt;/li&gt;
&lt;li&gt;리페인트는 재결합된 렌더 트리를 기반으로 다시 페인트하는 것을 말한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리플로우와 리페인트가 항상 함께 실행되는 것은 아니다 레이아웃의 변경이 없다면 리페인트만 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 CSS 속성 중 transform과 opacity는 GPU가 관여할 수 있는 속성이기에 리플로우, 리페인트가 생략된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;async/defer&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; HTML 파싱은 동기적으로 일어난다 그렇기에 CSS, img, script 태그를 만나면 파싱이 중단된다. 이 과정에서 DOM이 생성되기 전에 자바스크립트가 호출되고 실행되어 DOM API를 사용하게 된다면 에러가 발생할 수 있다. 그렇기에 body 최하단에 script를 배치하여 DOM 조작에 문제가 없게 하고 렌더링을 지연시기키지 않아 페이지 로딩 시간이 단축되도록 해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위치에 상관 없이 순서를 고려하기 위해 등장한 것이 script 태그의 async/defer 어트리뷰트이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;async&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTML 파싱과 외부 자바스크립트 파일의 로드가 비동기적으로 동시에 진행된다. 단 자바스크립트 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되고 이때 HTML 파싱이 중단된다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;defer&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;async와 같이 파싱과 파일의 로드가 비동기적으로 동시에 실행되지만 자바스크립트의 실행은 HTML 파싱이 완료된 직후 진행된다. 따라서 DOM에 접근해야 하는 자바스크립트가 있다면 유용하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 렌더링에 대해 찾아보며 어느 부분에 초점을 맞추는가에 따라 깊이와 방향이 완전히 달라질 수 있다는 것을 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저 렌더링 최적화를 위해서 자바스크립트, 폰트, 이미지의 크기를 줄이는 것뿐만이 아닌 리플로우와 리페인트를 관리하여 최적화하거나 CSS 속성을 활용한 렌더링 최적화 기법도 있고 개발자 도구의 퍼포먼스 탭을 활용하여 과정을 확인하는 방법 등 다양한 내용들을 확인할 수 있었다. 이번에 정리한 내용을 토대로 새로운 내용들을 발견하면 추가적인 학습이 필요할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고 자료&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[네이버 D2]&lt;a href=&quot;https://d2.naver.com/helloworld/59361&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://d2.naver.com/helloworld/59361&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;[모던 자바스크립트 Deep Dive]&lt;a href=&quot;https://product.kyobobook.co.kr/detail/S000001766445&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://product.kyobobook.co.kr/detail/S000001766445&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프론트엔드</category>
      <category>async defer</category>
      <category>렌더링 엔진</category>
      <category>리페인트</category>
      <category>리플로우</category>
      <category>브라우저 렌더링</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/124</guid>
      <comments>https://mxxcode.tistory.com/124#entry124comment</comments>
      <pubDate>Mon, 3 Jul 2023 17:16:03 +0900</pubDate>
    </item>
    <item>
      <title>REST API</title>
      <link>https://mxxcode.tistory.com/123</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;REST API.001.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qe7kc/btshsLY25X4/fjkkNLRKy7Sd3nUb1VM76K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qe7kc/btshsLY25X4/fjkkNLRKy7Sd3nUb1VM76K/img.jpg&quot; data-alt=&quot;REST API&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qe7kc/btshsLY25X4/fjkkNLRKy7Sd3nUb1VM76K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQe7kc%2FbtshsLY25X4%2FfjkkNLRKy7Sd3nUb1VM76K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;485&quot; height=&quot;273&quot; data-filename=&quot;REST API.001.jpeg&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;REST API&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;CS 학습 스터디를 진행하며 발표를 진행했던 내용들을 정리하여 블로그에 공유하려고 합니다.&lt;br /&gt;프론트엔드를 다루다 보면 만나게 되는 REST API에 대해 정리하였습니다.&lt;/blockquote&gt;
&lt;h2 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;REST + API ?&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: center;&quot;&gt;우선 REST API에 대해 알기 전에 간략하게 API, REST에 대해서 간략하게 알아보고 갑시다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;API란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application Programming Interface의 약자입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 정의 및 프로토콜 집합을 사용하여 두 소프트웨어 구성 요소가 서로 통신할 수 있게 하는 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터와 인간을 연결시키는&amp;nbsp;사용자 인터페이스(UI)와 반대로, API는 컴퓨터나 소프트웨어를 서로 연결합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;REST란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Representational State Transfer의 약자인 REST는 API 작동 방식에 대한 조건을 제시하는 소프트웨어 아키텍처입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 말하자면 기계와 기계가 웹을 이용하여 통신을 할 때 정해진 통신 규칙입니다. 인터넷과 같은 복잡한 네트워크에서 통신을 관리하기 위한 지침으로 만들어졌습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;REST의 6가지 스타일 규칙&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 조건을 제시한 아키텍처라고 했는데요, 조건에는 어떤 것들이 있는지 살펴봅시다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;균일한 인터페이스&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청이 어디에서 오는지와 무관하게, 동일한 리소스에 대한 모든 API 요청은 동일하게 보여야 합니다.&lt;/li&gt;
&lt;li&gt;형식은 서버 애플리케이션에 있는 리소스의 내부 표현과 다를 수 있습니다. 예를 들어, 서버는 데이터를 텍스트로 저장하되, HTML 표현 형식으로 전송할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클라이언트-서버 디커플링&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트와 서버 애플리케이션은 서로 간에 완전히 독립적이어야 한다.&lt;/li&gt;
&lt;li&gt;클라이언트 애플리케이션이 알아야 하는 유일한 정보는 요청된 리소스의 URI이다.&lt;/li&gt;
&lt;li&gt;요청된 데이터를 전달하는 것 말고는 클라이언트 애플리케이션을 수정하지 않아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;무상태 (Stateless)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST 아키텍처에서 무상태는 서버가 이전의 모든 요청과 독립적으로 클라이언트 요청을 완료하는 통신 방법을 나타냅니다.&lt;/li&gt;
&lt;li&gt;각 요청 간에 클라이언트의 정보를 저장하지 않고 분리하여 서로 연결되어있지 않아야 합니다.&lt;/li&gt;
&lt;li&gt;이는 서버가 매번 요청을 완전히 이해해서 이행할 수 있게 해 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;계층화 시스템&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호출과 응답이 서로 다른 계층을 통과합니다.&lt;/li&gt;
&lt;li&gt;클라이언트와 서버 애플리케이션이 서로 간에 직접 연결된다고 가정 X&lt;/li&gt;
&lt;li&gt;통신 루프에는 다수의 서로 다른 중개자가 있을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐싱 가능성&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가급적 리소스를 클라이언트 또는 서버 측에서 캐싱할 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;서버 응답에는 전달된 리소스에 대해 캐싱이 허용되는지 여부에 대한 정보도 포함되어야 합니다.&lt;/li&gt;
&lt;li&gt;서버측 확정성 증가와 함께 클라이언트 측의 성능 향상을 동시에 얻을 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Code on demand (optional)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적으로 정적인 리소스를 전송하지만 특정한 경우에는 응답에 프로그래밍 코드를 포함할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;API 발전 타임라인&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;api 타임라인.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I4wgS/btshpBw9wlM/EnmhplHt7UtfkLXEhXLhm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I4wgS/btshpBw9wlM/EnmhplHt7UtfkLXEhXLhm0/img.png&quot; data-alt=&quot;API Timeline&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I4wgS/btshpBw9wlM/EnmhplHt7UtfkLXEhXLhm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI4wgS%2FbtshpBw9wlM%2FEnmhplHt7UtfkLXEhXLhm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;768&quot; height=&quot;339&quot; data-filename=&quot;api 타임라인.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;API Timeline&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;REST API&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 기술을 의미하는 게 아닌 위에 설명한 두 가지 개념이 합쳐진 것입니다.&lt;/li&gt;
&lt;li&gt;아키텍처 스타일의 디자인 원칙을 준수하는 API입니다. 이러한 이유 때문에 REST API를 종종 &lt;b&gt;RESTful API&lt;/b&gt;라고도 합니다.&lt;/li&gt;
&lt;li&gt;HTTP를 이용한 통신에서 HTTP가 가진 잠재력을 최대한 이용할 수 있도록 유도하는 모범사례입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;339&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOcCey/btshtDT3B2M/rmV61M9skQTatg96r7sSE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOcCey/btshtDT3B2M/rmV61M9skQTatg96r7sSE1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOcCey/btshtDT3B2M/rmV61M9skQTatg96r7sSE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOcCey%2FbtshtDT3B2M%2FrmV61M9skQTatg96r7sSE1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;616&quot; height=&quot;272&quot; data-filename=&quot;11.png&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;339&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;HTTP request method&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP를 사용하기 때문에 같은 메서드를 사용합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;httpmethod.png&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GtWMF/btshlYzeU2c/0swHDGf8wXFjkkHNkNHJK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GtWMF/btshlYzeU2c/0swHDGf8wXFjkkHNkNHJK1/img.png&quot; data-alt=&quot;http method&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GtWMF/btshlYzeU2c/0swHDGf8wXFjkkHNkNHJK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGtWMF%2FbtshlYzeU2c%2F0swHDGf8wXFjkkHNkNHJK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;202&quot; data-filename=&quot;httpmethod.png&quot; data-origin-width=&quot;1245&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http method&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex - GET /movies/avatar2&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;REST API 설계 시 지켜야 하는 규칙&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 설계된 REST API는 각 요청이 어떤 동작이나 정보를 위한 것인지 그 요청의 모습 자체만으로 추론이 가능하다면 베스트 케이스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 대문자보다는 소문자를 사용하여야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;X -&amp;nbsp;&amp;lt;http://test.com/Comment/&amp;gt;
O -&amp;nbsp;&amp;lt;http://test.com/comment/&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 마지막에 슬래시( / )를 포함하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;X -&amp;nbsp;&amp;lt;http://test.com/test/&amp;gt;&amp;nbsp;&amp;nbsp;
O -&amp;nbsp;&amp;lt;http://test.com/test&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 언더바 대신 하이폰을 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;X -&amp;nbsp;&amp;lt;http://test.com/test_blog&amp;gt;
O -&amp;nbsp;&amp;lt;http://test.com/test-blog&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 파일확장자는 URI에 포함하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;X -&amp;nbsp;&amp;lt;http://test.com/photo.jpg&amp;gt;&amp;nbsp;
O -&amp;nbsp;&amp;lt;http://test.com/photo&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 행위를 포함하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;X - &amp;lt;http://test.com/delete-post/1&amp;gt;
O&amp;nbsp;-&amp;nbsp;&amp;lt;http://test.com/post/1&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;장점과 단점&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;확장성, 유연성, 독립성에서 이점을 얻을 수 있습니다.&lt;/li&gt;
&lt;li&gt;HTTP를 사용하므로 기존 인프라를 그대로 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;대규모의 고성능 통신을 안정적으로 지원할 수 있습니다.&lt;/li&gt;
&lt;li&gt;쉽게 구현, 수정이 가능해서 모든 API 시스템을 파악하고 여러 플랫폼에서 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 메서드가 한정적입니다.&lt;/li&gt;
&lt;li&gt;HTTP 통신 모델에 대해서만 지원합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API의 발전 타임라인을 통해 REST API에만 익숙했던 시야를 조금 더 넓힐 수 있었습니다. GraphQL과 같은 신기술들을 통해 단점이었던 UnderFetching과 OverFetching을 해결되는 모습 등을 통해 새로운 기술들에 대해서도 학습할 필요가 있다고 느꼈습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;참고&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/restful-api/?nc1=h_ls&quot;&gt;RESTful API란 무엇인가? - RESTful API 초보자 가이드 - AWS&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685018407860&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;company&quot; data-og-title=&quot;RESTful API란 무엇인가요? - RESTful API 설명 - AWS&quot; data-og-description=&quot;Amazon API Gateway는 어떤 규모에서든 개발자가 API를 손쉽게 생성, 게시, 유지 관리, 모니터링 및 보안 유지할 수 있도록 하는 완전관리형 서비스입니다. API Gateway를 사용하면 실시간 양방향 통신 애&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/what-is/restful-api/?nc1=h_ls&quot; data-og-url=&quot;https://aws.amazon.com/ko/what-is/restful-api/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/SDGhJ/hySLFUXEX1/zqDUOKv94r6O2J9pxfwhmK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/G7s82/hySKuOiV1E/mC68pu6IM0kOn6whdJT1Mk/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/restful-api/?nc1=h_ls&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/what-is/restful-api/?nc1=h_ls&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/SDGhJ/hySLFUXEX1/zqDUOKv94r6O2J9pxfwhmK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/G7s82/hySKuOiV1E/mC68pu6IM0kOn6whdJT1Mk/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;RESTful API란 무엇인가요? - RESTful API 설명 - AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Amazon API Gateway는 어떤 규모에서든 개발자가 API를 손쉽게 생성, 게시, 유지 관리, 모니터링 및 보안 유지할 수 있도록 하는 완전관리형 서비스입니다. API Gateway를 사용하면 실시간 양방향 통신 애&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://appmaster.io/ko/blog/rest-apiran-mueosimyeo-dareun-yuhyeonggwa-eoddeohge-dareungayo&quot;&gt;REST API란 무엇이며 다른 유형과 어떻게 다른가요? | AppMaster&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.ibm.com/kr-ko/cloud/learn/rest-apis&quot;&gt;rest-apis&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685018408684&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;REST API란? | IBM&quot; data-og-description=&quot;이 안내서에서는 네트워크 애플리케이션의 관리를 위한 아키텍처 스타일인 REST API를 통해 소프트웨어 개발 프로세스를 간소화하는 방법을 알아봅니다.&quot; data-og-host=&quot;www.ibm.com&quot; data-og-source-url=&quot;https://www.ibm.com/kr-ko/cloud/learn/rest-apis&quot; data-og-url=&quot;https://www.ibm.com/kr-ko/topics/rest-apis&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhi9cH/hySLC40v8Z/XFSPwUVLfbrkh41YRJJYd0/img.png?width=1312&amp;amp;height=464&amp;amp;face=0_0_1312_464&quot;&gt;&lt;a href=&quot;https://www.ibm.com/kr-ko/cloud/learn/rest-apis&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.ibm.com/kr-ko/cloud/learn/rest-apis&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhi9cH/hySLC40v8Z/XFSPwUVLfbrkh41YRJJYd0/img.png?width=1312&amp;amp;height=464&amp;amp;face=0_0_1312_464');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;REST API란? | IBM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 안내서에서는 네트워크 애플리케이션의 관리를 위한 아키텍처 스타일인 REST API를 통해 소프트웨어 개발 프로세스를 간소화하는 방법을 알아봅니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.ibm.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Development_common_sense#restful-api&quot;&gt;Interview_Question_for_Beginner/Development_common_sense at master &amp;middot; JaeYeopHan/Interview_Question_for_Beginner&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1685018407828&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - JaeYeopHan/Interview_Question_for_Beginner: Technical-Interview guidelines written for those who started studying progr&quot; data-og-description=&quot;:boy: :girl: Technical-Interview guidelines written for those who started studying programming. I wish you all the best. :space_invader: - GitHub - JaeYeopHan/Interview_Question_for_Beginner: Techn...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Development_common_sense#restful-api&quot; data-og-url=&quot;https://github.com/JaeYeopHan/Interview_Question_for_Beginner&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/geGCz/hySKBzQvZm/JqDk7lkg9aNQhbVCPZWSH0/img.png?width=721&amp;amp;height=458&amp;amp;face=0_0_721_458&quot;&gt;&lt;a href=&quot;https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Development_common_sense#restful-api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/Development_common_sense#restful-api&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/geGCz/hySKBzQvZm/JqDk7lkg9aNQhbVCPZWSH0/img.png?width=721&amp;amp;height=458&amp;amp;face=0_0_721_458');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - JaeYeopHan/Interview_Question_for_Beginner: Technical-Interview guidelines written for those who started studying progr&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;:boy: :girl: Technical-Interview guidelines written for those who started studying programming. I wish you all the best. :space_invader: - GitHub - JaeYeopHan/Interview_Question_for_Beginner: Techn...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프론트엔드</category>
      <category>API</category>
      <category>rest api</category>
      <category>REST API 규칙</category>
      <category>restful api</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/123</guid>
      <comments>https://mxxcode.tistory.com/123#entry123comment</comments>
      <pubDate>Thu, 25 May 2023 21:56:31 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 데브코스 - 5YES 팀 프로젝트 WuMo 회고</title>
      <link>https://mxxcode.tistory.com/122</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;WuMo | 우리들의 모임 프로젝트를 마치며&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;2023.02.16-03.15 약 한 달간의 집중 개발 기간을 마치고 최종발표와 함께 데브코스의 수료식도 함께 진행되었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt; 백엔드와 함께 진행하는 프로젝트인 만큼 걱정도 있었지만 좋은 팀원들을 만나게 되어 큰 트러블 없이 프로젝트를 끝마칠 수 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;프로젝트 소개&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;--001.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;891&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPrLoe/btr6RA2M5La/gxtYUQSDmP3ddXyROTVUQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPrLoe/btr6RA2M5La/gxtYUQSDmP3ddXyROTVUQk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPrLoe/btr6RA2M5La/gxtYUQSDmP3ddXyROTVUQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPrLoe%2Fbtr6RA2M5La%2FgxtYUQSDmP3ddXyROTVUQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;495&quot; data-filename=&quot;--001.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;891&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&amp;nbsp; ️ 모임 계획, 추억 관리를 위한 프라이빗 서비스&lt;/b&gt;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기획&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;평소&amp;nbsp;여행이나&amp;nbsp;모임을&amp;nbsp;계획할&amp;nbsp;때&amp;nbsp;어떻게&amp;nbsp;하시나요?&lt;br /&gt;그룹채팅방에서는&amp;nbsp;모임과&amp;nbsp;상관없는&amp;nbsp;이야기도&amp;nbsp;오가면서&amp;nbsp;모임에&amp;nbsp;필요한&amp;nbsp;장소나&amp;nbsp;사진&amp;nbsp;등이&amp;nbsp;잊히기&amp;nbsp;쉽습니다.&lt;br /&gt;그렇다면&amp;nbsp;모임과&amp;nbsp;관련된&amp;nbsp;내용만&amp;nbsp;모아 보면서&amp;nbsp;함께&amp;nbsp;계획을&amp;nbsp;완성하고&amp;nbsp;추억을&amp;nbsp;기록할&amp;nbsp;도구가&amp;nbsp;있다면&amp;nbsp;어떨까요?&amp;nbsp;&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 기능&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모임을 만들어 친구, 가족을 초대한다.&lt;/li&gt;
&lt;li&gt;후보지를 정하고 의견을 공유한다.&lt;/li&gt;
&lt;li&gt;마음에 드는 후보지를 일정으로 등록하면 타임라인 형식으로 모임 루트를 계획할 수 있습니다.&lt;/li&gt;
&lt;li&gt;다녀온 장소의 피드를 작성하여 추억을 기록할 수 있습니다.&lt;/li&gt;
&lt;li&gt;영수증 기능을 활용하여 정산할 때 도움을 받을 수 있습니다.&lt;/li&gt;
&lt;li&gt;다른 사람들의 추천 루트를 확인할 수 있고 공유할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;기술 스택&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-30 오후 6.27.21.png&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rSepJ/btr61RITCJF/Kw8ukKxkhFzij41HO5KWoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rSepJ/btr61RITCJF/Kw8ukKxkhFzij41HO5KWoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rSepJ/btr61RITCJF/Kw8ukKxkhFzij41HO5KWoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrSepJ%2Fbtr61RITCJF%2FKw8ukKxkhFzij41HO5KWoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;677&quot; height=&quot;440&quot; data-filename=&quot;스크린샷 2023-03-30 오후 6.27.21.png&quot; data-origin-width=&quot;1580&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Chakra-UI, Framer-Motion&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 프로젝트를 통해 CSS 작업에 소요되는 시간이 많다는 걸 느끼게 되었고 이번 프로젝트에서 구현해야 할 내용이 많았기에 과감하게 Chakra-UI를 채택하였다. styled-components와 함께 사용하여 전체적인 레이아웃과 약간의 수정으로 원하는 디자인을 제작할 수 있었다. 또한 Framer-Motion과 쉽게 조합할 수 있어 애니메이션 효과를 구현할 수 있었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Axios, JWT&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT Token에 대한 내용을 구현하며 이번 기회에 Axios의 장점을 잘 활용했다고 느꼈다. Axios Interceptor를 활용해서 요청, 응답, 에러 상황시 통신이 이뤄지기 이전에 처리를 통해 다른 팀원들이 토큰 관련 고민을 하지 않아도 앱이 동작하도록 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Access Token과 Refresh Token을 활용하여 Access Token 짧은 시간에 만료시키는 방법으로 보안성이 향상했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;React-Hook-Form&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Formik과 React-hook-form 중 후자를 택한 이유는 이전에 학습을 위한 프로젝트를 진행할 때 form을 상태로 관리할 때 내용이 변경될 때마다 re-rendering이 발생하는 문제를 겪어 uncontrolled Input으로 이를 해결할 수 있는 라이브러리를 적용하게 되었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Tanstack-Query (React-Query)&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 useEffect를 활용하여 서버와 통신을 통해 상태값을 관리하는 경우 불편함을 많이 느꼈었다. 꽤 긴 코드를 매번 작성해야 하고 useEffect 안의 함수를 빼기 위해서 useCallback을 또 사용해야 하는 상황이 자주 생겼다. 이와는 다르게 캐싱과 데이터 변경 시 refetch를 쉽게 사용할 수 있다는 것이 해당 라이브러리를 사용한 가장 큰 이유였다. 또한 isLoading, isError와 같은 분기처리로 각 상황에 맞는 처리를 구현하는 것도 훨씬 간단하였다. 결과적으로 코드 가독성이 좋아지고 더 적은 코드로 같은 기능을 구현할 수 있었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ETC..&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Recoil&lt;/b&gt;은 이번 프로젝트에서는 불가피한 상황이 아니라면 Global state 사용을 자제하였다. 모임 생성, 후보지 생성과 같은 각 컴포넌트 간의 props drilling이 일어나는 경우를 효과적으로 해결할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vite&lt;/b&gt;의 빠른 번들링으로 오래 기다리지 않아도 앱이 실행되어 개발 시간을 단축할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;구현한 기능&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인, 회원가입 기능&lt;/li&gt;
&lt;li&gt;유저 프로필, 수정 기능&lt;/li&gt;
&lt;li&gt;나의 모임 목록&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;고민한 부분들&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;사용자 인증, 인가&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그인, 회원가입을 구현하며 다른 팀원들의 편의를 위해 v1부터 v3까지 로그인을 구현하게 되었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;v1에서는 회원가입 후 로그인이 동작하는 것에 초점을 맞추어 팀원들의 작업이 최대한 딜레이 되지 않도록 노력했다.&lt;/li&gt;
&lt;li&gt;v2에서는 JWT 토큰을 재발행하여 보안을 높이는 작업을 진행하기 위해 Axios Interceptor, 보안 관련 학습을 병행하며 구현했다.&lt;/li&gt;
&lt;li&gt;v3를 진행하게 된 이유는 이번에 가장 깊게 생각한 이슈인 accessToken이 만료되었을 때 재발행하는 로직때문이였다. 초기에는 해당 에러코드를 받게 되면 reissue를 진행하였으나 이 경우 이전에 요청했던 통신 개수만큼 reissue를 진행하는 문제가 발생했다. 이를 해결하기 위해&amp;nbsp; flag를 설정하고 Promise를 배열에 담아 보관하고 재발행된 토큰을 이용하여 배열에 담긴 요청들을 처리하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1680172285913&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let lock = false;
let subscribers: ((token: string) =&amp;gt; void)[] = [];

const subscribeTokenRefresh = (cb: (token: string) =&amp;gt; void) =&amp;gt; {
  subscribers.push(cb);
};

const onRrefreshed = (token: string) =&amp;gt; {
  subscribers.forEach((cb) =&amp;gt; cb(token));
};

const getRefreshToken = async (): Promise&amp;lt;string | void&amp;gt; =&amp;gt; {
  const tokens = localStorage.getItem('tokens');
  if (tokens) {
    try {
      const response = await axiosInstance.post('/members/reissue', JSON.parse(tokens));
      const { accessToken, refreshToken } = response.data;
      lock = false;
      onRrefreshed(accessToken);
      subscribers = [];
      localStorage.setItem('tokens', JSON.stringify({ accessToken, refreshToken }));

      return accessToken;
    } catch (error) {
      lock = false;
      subscribers = [];
      localStorage.removeItem('tokens');
      location.replace(ROUTES.LANDING);
    }
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-927 오전 12.24.11.png&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KI4Ze/btr63ngnHYm/aNcWg1vxqsBixz4RXxdI90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KI4Ze/btr63ngnHYm/aNcWg1vxqsBixz4RXxdI90/img.png&quot; data-alt=&quot;실행 내용&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KI4Ze/btr63ngnHYm/aNcWg1vxqsBixz4RXxdI90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKI4Ze%2Fbtr63ngnHYm%2FaNcWg1vxqsBixz4RXxdI90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;664&quot; height=&quot;210&quot; data-filename=&quot;스크린샷 2023-03-927 오전 12.24.11.png&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실행 내용&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;UseController 활용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React hook form과 Chakra UI를 함께 사용하며 컴포넌트로 분리하여 재사용성을 높이기 위해 노력하고 리팩토링을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 register method를 활용하여 form이 있는 컴포넌트에서 바로 input들을 제어하였으나 관리해야할 Input들이 늘어나자 가독성이 떨어지는 문제가 발생하였다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;register method 대신 control method를 사용하여 form과 input을 분리할 수 있도록 했다.&lt;/li&gt;
&lt;li&gt;useController 훅을 사용하여 컴포넌트를 분리했다.&lt;/li&gt;
&lt;li&gt;resetField, setError, trigger와 같은 method들과 form에서 관리해야 할 상태 값들을&amp;nbsp; props로 전달하여 각 input들에서 form의 기능들을 사용할 수 있도록 하였다.&lt;/li&gt;
&lt;li&gt;결과적으로 73 lines에서 9 lines로 코드를 줄이고 가독성을 높혔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;React Hook Form과 Tanstack Query 혼용&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tanstack Query와 React hook form을 같이 활용할 때 문제가 발생했다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가져온 데이터의 stale 시간이 짧을 경우, 사용자가 작성한 내용이 초기화되었고 이를 활용하기 위해 useEffect에 dependency를 통해 이를 해결하였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1680172841203&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;useEffect(() =&amp;gt; {
    if (!myProfileInfo) return;
    setImageValues({ ...imageValues, imageBase64: myProfileInfo.profileImage });
    setValue('nickname', myProfileInfo.nickname);
  }, [myProfileInfo]);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로필 사진 수정 시 한 곳에서 사진의 변화로 모든 변경사항을 표현했다. 이를 처리하기 위해 Data를 상태 값으로 추가하고 각 조건을 고려하여 구분한 뒤 이를 코드로 표현하여 구현했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1680172875290&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getImage = async () =&amp;gt; {
    if (myProfileInfo.profileImage === imageValues.imageBase64)
      return myProfileInfo.profileImage;

    if (myProfileInfo.profileImage === null &amp;amp;&amp;amp; imageValues.imageBase64 !== null) {
      const imageUrl = await onSubmitImageFile(imageValues.imageFile);
      return imageUrl;
    }

    if (myProfileInfo.profileImage !== null &amp;amp;&amp;amp; imageValues.imageBase64 === null) {
      await deleteImage(myProfileInfo.profileImage);
      return null;
    }

    if (myProfileInfo.profileImage !== null &amp;amp;&amp;amp; imageValues.imageBase64 !== null) {
      await deleteImage(myProfileInfo.profileImage);
      const imageUrl = await onSubmitImageFile(imageValues.imageFile);
      return imageUrl;
    }

    return myProfileInfo.profileImage;
  };&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;&lt;b&gt;프로젝트를 마치고&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_Photo_2023-03-20-03-31-31.jpeg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct6Jvo/btr62XPWE2U/cOhkvyJSRYR9YUPBcCObiK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct6Jvo/btr62XPWE2U/cOhkvyJSRYR9YUPBcCObiK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct6Jvo/btr62XPWE2U/cOhkvyJSRYR9YUPBcCObiK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct6Jvo%2Fbtr62XPWE2U%2FcOhkvyJSRYR9YUPBcCObiK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;287&quot; height=&quot;383&quot; data-filename=&quot;KakaoTalk_Photo_2023-03-20-03-31-31.jpeg&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;느낀 점&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번의 스프린트로 힘들 수도 있었지만 한 팀으로 목표로 했던 기능들과 QA를 통해 원하는 결과물을 만들어 냈다는 것에 뿌듯함을 느낀다. 또한 백엔드 교육생분들과 협업을 진행하며 개발자로서 현업에 투입되었을 때 어떠한 과정을 거칠지 조금이나마 경험해 본 것이 이 프로젝트의 목표 중 하나였고 많은 점을 배운 것 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;아쉬웠던 점&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문서화&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문서화의 중요성을 프로젝트 중반 정도에 느끼게 되었다. 백엔드 팀원과 함께 프로젝트를 진행하다 보니 많은 이야기가 오갔고 시간이 지나 두 번 질문하는 경우도 발생했다. 개선을 위해 이후에는 간단하게라도 일지를 작성하였고 본인뿐만 아니라 다른 팀원들의 일지들도 확인하며 서로의 로직에 대한 이해도를 높일 수 있었다. 프로젝트 초반부터 진행했더라면 더 좋았을 내용이기에 다음부터는 습관화하는 노력을 들여야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;최적화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QA를 진행하며 시간이 많이 촉박해졌고 최적화를 잘 진행하지 못하였다. Lighthouse를 통해 확인해 보면 준수한 성능을 보여주지만 여전히 이미지, 폰트 최적화와 같은 부분이 필요하기 때문에 리팩토링을 통해 개선해보고 싶다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;앞으로의 WuMo&lt;/b&gt;&lt;/h4&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회고를 작성하기 이전에도 몇 가지 리팩토링을 진행했다. 비밀번호 관련 버그와 위에 언급했던 폼 관련 컴포넌트를 분리하는 작업을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로의 리팩토링 계획은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OAuth 구현: 기존 목표는 이메일 인증을 통한 자유로운 회원가입이었으나 OAuth를 통해 진입장벽을 낮게 설정하는 필요성을 느끼게 되었다. 백엔드 팀원이 이미 진행해 준 내용이 있기 때문에 이를 토대로 학습하고 프로젝트에 구현해 볼 계획이다.&lt;/li&gt;
&lt;li&gt;알림 기능(혹은 채팅 기능): 현재 프로젝트에서는 댓글과 같은 형식으로 의견을 주고받는다. 하지만 어디서 어떤 댓글을 달았는지 확인하려면 직접 들어가야 하기 때문에 사용성을 개선하고자 구현해보려고 한다. 기존 채팅 기능을 구현하고 싶다는 생각을 하였으나 채팅보다 우선되어야 할 기능이라고 느낀다.&lt;/li&gt;
&lt;li&gt;모임 목록 무한스크롤 구현: 모임 리스트가 많아진다면 로딩 시간이 점점 길어지기 때문에 구현 우선도가 높은 기능이라고 생각된다. Tanstack-Query의 Infinite Queries를 활용하여 이를 구현해 볼 계획이다.&lt;/li&gt;
&lt;li&gt;이미지, 폰트 최적화: 이미지 사이즈, 이미지 Encode와 같은 부분들을 개선하고 폰트 파일의 용량을 줄이는 방식을 통해 완성도 높은 프로젝트를 완성하고자 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 1달간 프로젝트를 위해 좋은 분위기를 유지해 주고 더 좋은 방향이라면 긍정적으로 의견을 받아준 백엔드 팀원들과 책임감 있게 끝까지 애써준 프론트엔드&amp;nbsp;팀원들에게 감사함을 전하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;배포 링크 &quot; href=&quot;https://5yes-wumo.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;배포 링크 &lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680193612657&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;WuMo - 우리들의 모임&quot; data-og-description=&quot;모임 계획은 우모에서, 우리들의 모임 WuMo&quot; data-og-host=&quot;5yes-wumo.vercel.app&quot; data-og-source-url=&quot;https://5yes-wumo.vercel.app/&quot; data-og-url=&quot;https://5yes-wumo.vercel.app/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://5yes-wumo.vercel.app/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://5yes-wumo.vercel.app/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;WuMo - 우리들의 모임&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;모임 계획은 우모에서, 우리들의 모임 WuMo&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;5yes-wumo.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/prgrms-web-devcourse/Team-5YES-WuMo-FE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;FE 깃허브 &lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680193625105&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - prgrms-web-devcourse/Team-5YES-WuMo-FE:   5yes팀 최종 프로젝트 wumo front-end&quot; data-og-description=&quot;  5yes팀 최종 프로젝트 wumo front-end. Contribute to prgrms-web-devcourse/Team-5YES-WuMo-FE development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/prgrms-web-devcourse/Team-5YES-WuMo-FE&quot; data-og-url=&quot;https://github.com/prgrms-web-devcourse/Team-5YES-WuMo-FE&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/vI3Kb/hyR5pHbDrm/ISOBeNKIGESPxvdZSRaqFK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/prgrms-web-devcourse/Team-5YES-WuMo-FE&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/prgrms-web-devcourse/Team-5YES-WuMo-FE&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/vI3Kb/hyR5pHbDrm/ISOBeNKIGESPxvdZSRaqFK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - prgrms-web-devcourse/Team-5YES-WuMo-FE:   5yes팀 최종 프로젝트 wumo front-end&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;  5yes팀 최종 프로젝트 wumo front-end. Contribute to prgrms-web-devcourse/Team-5YES-WuMo-FE development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래머스 데브코스/회고</category>
      <category>axios interceptor</category>
      <category>chakra-ui</category>
      <category>JWT</category>
      <category>react-hook-form</category>
      <category>Tanstack-Query</category>
      <category>WuMo 우리들의 모임</category>
      <category>데브코스</category>
      <category>팀 프로젝트</category>
      <category>협업</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/122</guid>
      <comments>https://mxxcode.tistory.com/122#entry122comment</comments>
      <pubDate>Fri, 31 Mar 2023 01:16:21 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 데브코스 - 팀 프로젝트 디그디그딥</title>
      <link>https://mxxcode.tistory.com/121</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Group 116.png&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;92&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/W6BoO/btrXBYRQISq/k5jCKZikKFYz0tQ1sskSK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/W6BoO/btrXBYRQISq/k5jCKZikKFYz0tQ1sskSK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/W6BoO/btrXBYRQISq/k5jCKZikKFYz0tQ1sskSK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FW6BoO%2FbtrXBYRQISq%2Fk5jCKZikKFYz0tQ1sskSK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;484&quot; height=&quot;92&quot; data-filename=&quot;Group 116.png&quot; data-origin-width=&quot;484&quot; data-origin-height=&quot;92&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;1월 5일부터 1월 20일까지 약 2주간 진행된 프로젝트를 기획하고 개발하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기획&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 시작 전에도 여러 가지 아이디어를 제시하고 이에 대해 이야기했었지만 API를 확인한 뒤에는 이를 최대로 활용하고 가능성이 높은 프로젝트들을 브레인스토밍을 통해 도출하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btN3O9/btrXBy65mg9/p6Phy96YZnuLD19qOE7x3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btN3O9/btrXBy65mg9/p6Phy96YZnuLD19qOE7x3K/img.png&quot; data-alt=&quot;기획 회의 피그잼&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btN3O9/btrXBy65mg9/p6Phy96YZnuLD19qOE7x3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtN3O9%2FbtrXBy65mg9%2Fp6Phy96YZnuLD19qOE7x3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1181&quot; height=&quot;402&quot; data-origin-width=&quot;1181&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기획 회의 피그잼&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특강에서 보았으나 잘 알지 못했던 신문물(?)에 익숙한 팀원 덕분에 피그잼을 활용하였고 두가지 후보가 있었으나 최종으로 내가 제시한 아이디어가 선택되어 이번 프로젝트에 더욱 책임감이 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 유저스토리를 작성해보며 프로젝트 주요 타겟을 정하고 간단한 사용 시나리오를 작성해 보며 콘셉트를 잡아 나갔다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트 소개&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &amp;nbsp; 커뮤니케이션을 통한 학습을 제공하는 개발자 전용 SNS&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트 주제는 &lt;i&gt;&quot;React 혹은 Vue.js 이용하여 소셜 네트워크를 구현&quot;&lt;/i&gt;이었다. 기획 단계에서도 학습, 스터디에 대한 내용이 주를 이뤘기 때문에 위와 같은 프로젝트 주제를 정하였고 재미 요소를 추가할 수 있는 새로운 두 가지 개념을 추가하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들이 &quot;그라운드&quot;에서 &quot;디깅&quot;을 통해 지식이 깊어질 수 있는 커뮤니티.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;그라운드&lt;/i&gt;&lt;/b&gt;: 하나의 주제와 관련된 포스트를 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;b&gt;디깅&lt;/b&gt;&lt;/i&gt;: 댓글을 남기는 행위를 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 아이디어를 제시할 때 항상 깊이있는 학습이 중요하다는 말을 자주 들었지만 막상 공부하다 보면 넘어가게 되는 부분이 많다고 느꼈다. 누군가 내가 확인하지 못한 부분을 추천해 주거나 함께 알아간다면 어떨까 하는 아이디어에서 시작되어 팀원들의 아이디어가 더해져서 더 깊게 들어가는 디깅이라는 단어와 그라운드를 사용하게 되었다. (또한 이에 어울리는 로고로 두더지를 선택하였다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개발 문화&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;Git branch 전략&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협업으로 진행되는 만큼 각 팀원들이 각자 역할을 가지고 진행되었다. 디자인, 문서화, Github 관리, PM, 프로젝트 설정등으로 나누었고 나는 여기에서 Github 관리를 맡게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/by52Vb/btrXyi4ZXzS/2IcOaHJCkO50BC2L85yBtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/by52Vb/btrXyi4ZXzS/2IcOaHJCkO50BC2L85yBtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/by52Vb/btrXyi4ZXzS/2IcOaHJCkO50BC2L85yBtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fby52Vb%2FbtrXyi4ZXzS%2F2IcOaHJCkO50BC2L85yBtK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;631&quot; height=&quot;194&quot; data-origin-width=&quot;631&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 시작 단계에서 바로 issue 템플릿과 pull request 템플릿을 만들었다. 팀에서 진행할 git branch 전략을 위해서 필요한 내용이였기 때문이다. 우리는 이슈를 통해 브랜치를 만들고 이를 pull request하는 방식으로 진행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 pull request에 한명 이상 Approve 해야 머지할 수 있도록 하였고 코드리뷰를 통해 코드 품질을 관리할 수 있었다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;기술 스택&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XKbVQ/btrXDgK7i9g/MkEqIkKVwTBOf6G5FGyYhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XKbVQ/btrXDgK7i9g/MkEqIkKVwTBOf6G5FGyYhK/img.png&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;380&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.4551%; margin-right: 10px;&quot; data-widthpercent=&quot;48.01&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XKbVQ/btrXDgK7i9g/MkEqIkKVwTBOf6G5FGyYhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXKbVQ%2FbtrXDgK7i9g%2FMkEqIkKVwTBOf6G5FGyYhK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;798&quot; height=&quot;380&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chNZuc/btrXBOaK1G9/hbLGIZ8bKbQ5snBLUUG30K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chNZuc/btrXBOaK1G9/hbLGIZ8bKbQ5snBLUUG30K/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;347&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;51.99&quot; style=&quot;width: 51.3821%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chNZuc/btrXBOaK1G9/hbLGIZ8bKbQ5snBLUUG30K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchNZuc%2FbtrXBOaK1G9%2FhbLGIZ8bKbQ5snBLUUG30K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;347&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TypeScript와 React를 사용하였고 스타일링을 위해 Styled Components를 사용하였다. 타입스크립트를 사용해 본 경험은 없었으나 팀원들과 진행하는 프로젝트인 만큼 이번 기회에 사용법을 익히며 진행해보고자 했다.(부딪히며 배우게 되었고 이 부분에서 팀원의 도움을 많이 받게 되어 감사함을 느낍니다.. &amp;zwj;♂️) &lt;span&gt;역시 팀원들이 있기에 두려움없이 사용해 볼 수 있었던 것 같다. 각 기술들의 장점과 단점을 느껴볼 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;장점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TypeScript - 타입을 미리 지정해줌으로써 휴먼에러를 많이 줄여주고 데이터 자동완성 기능 덕분에 편리한 작업을 이어갈 수 있었다.&lt;/li&gt;
&lt;li&gt;Recoil - 초기 설정을 진행해준 팀원 덕분에 짧은 코드로 전역상태값을 사용할 수 있어 아주 편리했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;단점&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TypeScript - 익숙하지 않고 제대로 타입을 지정해줘야 하기에 번거로움이 있다. (모든 타입을 any로 사용할 수 없기 때문에)&lt;/li&gt;
&lt;li&gt;Recoil - 이번 프로젝트는 규모가 크지 않았기에 전역 상태값에 대한 토의를 팀원과 계속 진행하게 되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기능 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트를 진행하며 내가 개발한 내용들은 아래와 같다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;검색 기능&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jan-31-2023 03-26-48.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YtAbp/btrXAkORZlP/AjhDovJLkXkOKj7mszjg3K/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YtAbp/btrXAkORZlP/AjhDovJLkXkOKj7mszjg3K/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YtAbp/btrXAkORZlP/AjhDovJLkXkOKj7mszjg3K/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/YtAbp/btrXAkORZlP/AjhDovJLkXkOKj7mszjg3K/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;609&quot; data-filename=&quot;Jan-31-2023 03-26-48.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키워드로 사용자와 그라운드를 분리하여 검색 가능하다. (깨알 같은 애니메이션도 사용감을 높이기 위해 적용하였다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용한 방법은 useSearchParams를 활용하여 q와 type으로 쿼리를 나누어서 searchPage로 이동하며 함께 넘겨주고 이를 통해 검색되도록 하였다. 원래 API의 내용은 그라운드 + 사용자, 사용자로 구분되어 있고 그라운드와 사용자가 따로 분리되어 있지 않았지만 팀원들의 의견을 통해 분리를 시도하였고 두 검색 API result의 차이점을 활용하여 분리하고 각 내용을 추가하는 것이 아닌 Promise.all로 한 번에 상태값에 저장되도록 코드를 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 가지 아쉬웠던 점은 각 포스트를 따로 가져와 한번에 표시해줘야 하기 때문에 검색 결과가 많지 않더라도 로딩시간이 길다는 점이었다. 이 문제에 관해서는 리팩토링을 통해 API를 개선하여 가져오면 좋을 것 같다고 생각하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675105509071&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const getSearch = useCallback(async () =&amp;gt; {
    const getType = searchParams.get('type');
    const getSearchTerm = searchParams.get('q');
    
	setLoading(true);
    if (getType === 'users') {
      const { data } = await axiosInstance.get&amp;lt;UserResponse[]&amp;gt;(
        `/search/users/${getSearchTerm}`
      );
      setUserResult(data);
    }
    if (searchParams.get('type') === 'posts') {
      const { data } = await axiosInstance.get&amp;lt;PostResponse[]&amp;gt;(
        `/search/all/${getSearchTerm}`
      );
      Promise.all(
        data.reduce&amp;lt;Promise&amp;lt;PostResponse&amp;gt;[]&amp;gt;((prev, post) =&amp;gt; {
          if ('author' in post) {
            prev.push(getPost(post._id));
          }
          return prev;
        }, [])
      ).then((data) =&amp;gt; setPostResult(data));
    }
    setLoading(false);
  }, [searchParams]);&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;메인 피드 구현 &lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jan-31-2023 03-59-25.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;970&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P8KnH/btrXykaE3hH/vTflmFZmkcg9oHyl6KJMwk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P8KnH/btrXykaE3hH/vTflmFZmkcg9oHyl6KJMwk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P8KnH/btrXykaE3hH/vTflmFZmkcg9oHyl6KJMwk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/P8KnH/btrXykaE3hH/vTflmFZmkcg9oHyl6KJMwk/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;609&quot; data-filename=&quot;Jan-31-2023 03-59-25.gif&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;970&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10개의 피드만 불러오고 무한스크롤을 통해 더보기 기능을 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무한스크롤은 강의를 통해 학습한 IntersectionObserver로 마지막 부분에 observer에 닿으면 그라운드를 더 불러오는 함수를 실행하도록 구현하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 기능에서도 API에서 약간의 한계가 있었지만 방법을 찾아 구현할 수 있었다. 내가 해결한 방식은 채널 관련 data를 요청하면 모든 포스트의 내용이 담긴 배열이 아닌 id값만 포함한 배열을 가져온다. 이를 통해 빠른 속도로 총그라운드의 수를 알 수 있고 한번 더 data를 fetching하는데 주어진 limit &amp;amp; offset으로 이미 모든 그라운드를 표시했다면 더 로딩하지 않는 코드를 작성하였다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;좋아요&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;94&quot; data-origin-height=&quot;39&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RFIr4/btrXBkA9HnK/7fuT8zmZTwyoLrcGY41en0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RFIr4/btrXBkA9HnK/7fuT8zmZTwyoLrcGY41en0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RFIr4/btrXBkA9HnK/7fuT8zmZTwyoLrcGY41en0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRFIr4%2FbtrXBkA9HnK%2F7fuT8zmZTwyoLrcGY41en0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;94&quot; height=&quot;39&quot; data-origin-width=&quot;94&quot; data-origin-height=&quot;39&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋아요 버튼 기능을 토글로 구현하였다. Recoil에 저장된 user 정보를 활용하여 여부를 확인하여 사용자에 따라 다르게 보여준다. 로그인이 안된 상황에서는 토스트를 띄워준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675106250399&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleLike = async () =&amp;gt; {
    const isLike = likesState.findIndex((like) =&amp;gt; like.user === user._id);
    if (!token) {
      return showToast({ message: ERROR_MESSAGES.REQUIRE_LOGIN });
    }

    if (isLike === -1) {
      try {
        const { data } = await createLike(_id);
        setLikesState([...likesState, data]);
        await getMyInfo();
        sendNotification('LIKE', data._id, author._id, _id);
        showToast({ message: SUCCESS_MESSAGES.LIKE_SUCCESS });
      } catch (error) {
        console.error(error);
        showToast({ message: ERROR_MESSAGES.SERVER_ERROR });
      }
    } else {
      try {
        setLikesState(
          likesState.filter((item) =&amp;gt; item._id !== likesState[isLike]._id)
        );
        deleteLike(likesState[isLike]._id);
        if (user.likes) {
          getMyInfo();
        }
        showToast({ message: SUCCESS_MESSAGES.UNLIKE_SUCCESS });
      } catch (error) {
        console.log(error);
        showToast({ message: ERROR_MESSAGES.SERVER_ERROR });
      }
    }
  };&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;공유&amp;nbsp; 기능&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 버튼을 클릭하면 클립보드에 해당 그라운드의 링크를 저장한다. useNavigator를 통해 어렵지 않게 구현할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1675106404595&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const handleShare = () =&amp;gt; {
    navigator.clipboard.writeText(`${window.location.host}/posts/${_id}`);
    showToast({ message: SUCCESS_MESSAGES.SHARE_SUCCESS });
  };&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;i&gt;댓글&amp;nbsp;&lt;/i&gt;&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Jan-31-2023 04-45-17.gif&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;964&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6kYyb/btrXCZjCtVM/zk9c9IhKkBKskeYvZvCUc1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6kYyb/btrXCZjCtVM/zk9c9IhKkBKskeYvZvCUc1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6kYyb/btrXCZjCtVM/zk9c9IhKkBKskeYvZvCUc1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/c6kYyb/btrXCZjCtVM/zk9c9IhKkBKskeYvZvCUc1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;609&quot; data-filename=&quot;Jan-31-2023 04-45-17.gif&quot; data-origin-width=&quot;396&quot; data-origin-height=&quot;964&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;댓글 작성과 본인의 글인 경우 삭제가 가능하도록 구현하였다. 사용성을 위해 댓글을 작성하면 맨아래로 스크롤을 내리는 기능도 구현했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;i&gt;각 컴포넌트, 페이지&lt;/i&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 구현한 부분들은 퍼블리싱까지 직접 진행하였는데 TopDown 방식으로 자주 사용되는 부분이 있다면 컴포넌트로 분리하는 방식으로 코드를 작성했다. 메인 페이지의 헤더, 각 그라운드의 사용자, 상세 페이지, 댓글 로그인 여부, 본인의 그라운드 혹은 댓글일 때 각 페이지에 따라 내용에 따라 달라지는 화면을 처리하는 내용이 생각보다 복잡해서 시간을 많이 소비하였지만 로직대로 괜찮은 결과물을 작성하였다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 깔끔한 코드를 작성하지 못한 것 같아 아쉬움이 많이 남는다. 이 또한 리팩토링을 통해 컴포넌트들의 코드를 조금 더 분리하고 재사용성이 좋게 개선해보려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;967&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1PTga/btrXBOu6kom/EsRkKn5RZGBYyAV63hL590/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1PTga/btrXBOu6kom/EsRkKn5RZGBYyAV63hL590/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1PTga/btrXBOu6kom/EsRkKn5RZGBYyAV63hL590/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1PTga%2FbtrXBOu6kom%2FEsRkKn5RZGBYyAV63hL590%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;618&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;967&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;이외에도 그라운드 삭제, 수정 그리고 알림 전송과 같은 API 통신 관련된 부분을 구현하고 팀원들이 수월하게 작업할 수 있도록 구현하여 따로 util/api 파일에 정리하였다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;협업&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음으로 협업을 진행하는 만큼 가장 익숙하지 않았던 점은 Git, GitHub 사용이였다. 물론 개인 프로젝트를 진행하며 나름 자신감을 가지고 있었지만 브랜치 전략을 사용하는 만큼 사용해보지 못한 다양한 기능들을 사용해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이슈와 브랜치, 각 컨벤션에 맞춰 pull request를 작성하는 일이 번거로울 수 있었지만 사용하다 보니 금방 익숙해졌고 정해진 틀이 있으니 편하다는 생각이 커졌다. 더 많은 기능을 사용해보고 싶다는 생각에 마일스톤과 프로젝트도 잠깐 사용해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소통에 있어서는 팀원들과 필요에 따른 회의와 하루에 두 번 정도의 스크럼을 진행하며 정말 많은 소통을 하였다. 이러한 방식을 토대로 서로가 원하는 UI나 기능들을 만족스럽게 구현할 수 있었다고 생각한다. 또한 &lt;span&gt;코어 타임 동안은 바로바로 질문을 할 수 있었고 모든 팀원들에게 도움을 받고 또 줄 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그리고 KPT(keep, problem, try) 방식을 적용하여 기술 관련, 협업 관련 부분들에 대한 각자의 고민과 진행사항을 중간 회고를 진행하며 소통하였고 다른 팀원들의 고충도 들을 수 있어서 팀워크에 큰 도움이 됐다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트를 마치며&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;돌이켜 생각해 보면 이번 프로젝트를 진행하며 가장 고민했던 부분은 API 통신 후 데이터 갱신에 대한 것이었다. 라이브러리를 사용하지 않았기에 하나씩 직접 작성하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;번거로움과 캐시를 적용해보지 못한 것에 아쉬움이 있어서 프로젝트 중간에 멘토님에게 질문을 했고 React query 라이브러리에 대해 설명해 주셨다. 리팩토링을 진행할 때 해당 라이브러리를 접목해보고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;그리고 과제에 제공된 기본 구현사항과 추가 사항에 집중하다 보니 디그디그딥만의 강점이 잘 드러나지 못한 부분이 아쉬웠다. 비즈니스적인 면에서 매력적인 기술을 사용해야 할 필요성을 느꼈고 이를 위해 코드를 효율적으로 보여주는 에디터 기능을 추가하는 것을 생각하고 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;위에서도 언급했지만 깔끔한 코드를 위한 리팩토링은 매 프로젝트마다 필요하다고 느끼기 때문에 이 또한 진행하려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아쉬운 점도 많이 있지만 처음 진행해 본 팀 프로젝트였던 만큼 많은 내용을 배웠다. 2주 만에 이 정도의 프로젝트를 만들 수 있다는 협업의 효율적인 면을 느꼈고 앞으로도 다양한 협업을 진행해보고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 프로젝트를 진행하기 전에 내가 목표로 삼았던 내용이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;각자 강점을 드러낼 수 있는 프로젝트로 나중에 취업에서도 플러스가 될 수 있는 경험을 해보고 싶다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;프로젝트를 마치고 팀원들과 이야기할 때 모두가 인정했던 부분은 각자 장점을 최대한 보여주었다는 점이다. 협업에서는 개발이 중요하지만 그 외에도 많은 점들이 필요하다는 것을 느끼게 되었고&amp;nbsp;&lt;/span&gt;&lt;span&gt;2주라는 짧은 기간 동안 프로젝트를 완성할 수 있었던 건 팀원들이 모두 자신의 역할을 다 해주었기 때문이라고 생각한다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;흐지부지 리팩토링 없이 넘어가지 않는 것이 다음의 목표다. 벌써 다음 팀 회의 일정이 잡혔기 때문에 내가 아쉽다고 느꼈던 부분을 꾸준히 개선해 나가야겠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;프로젝트 링크&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://dig-dig-deep.vercel.app&quot;&gt;https://dig-dig-deep.vercel.app&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1675099858520&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;DigDigDeep - 디그디그딥&quot; data-og-description=&quot;더 깊은 학습, 어디서든 디깅해요. 디그디그딥&quot; data-og-host=&quot;dig-dig-deep.vercel.app&quot; data-og-source-url=&quot;https://dig-dig-deep.vercel.app&quot; data-og-url=&quot;https://dig-dig-deep.vercel.app&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZNPzN/hyRssJRGb5/VPo7jKyoEJkXTVGokMdtIK/img.png?width=1200&amp;amp;height=633&amp;amp;face=0_0_1200_633&quot;&gt;&lt;a href=&quot;https://dig-dig-deep.vercel.app/&quot; data-source-url=&quot;https://dig-dig-deep.vercel.app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZNPzN/hyRssJRGb5/VPo7jKyoEJkXTVGokMdtIK/img.png?width=1200&amp;amp;height=633&amp;amp;face=0_0_1200_633');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;DigDigDeep - 디그디그딥&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;더 깊은 학습, 어디서든 디깅해요. 디그디그딥&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dig-dig-deep.vercel.app&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri&quot;&gt;https://github.com/prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1675099858520&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri: [디그디그딥] 커뮤니케이션을 통한 학습을 제공하는 개발&quot; data-og-description=&quot;[디그디그딥] 커뮤니케이션을 통한 학습을 제공하는 개발자 전용 SNS  . Contribute to prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri&quot; data-og-url=&quot;https://github.com/prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bA6FUo/hyRqYwRYdT/nsaVkymZPBZUfXvW1mz5c1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri&quot; data-source-url=&quot;https://github.com/prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bA6FUo/hyRqYwRYdT/nsaVkymZPBZUfXvW1mz5c1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri: [디그디그딥] 커뮤니케이션을 통한 학습을 제공하는 개발&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[디그디그딥] 커뮤니케이션을 통한 학습을 제공하는 개발자 전용 SNS  . Contribute to prgrms-fe-devcourse/FEDC3_DigDigDeep_Yuri development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래머스 데브코스/회고</category>
      <category>react</category>
      <category>TypeScript</category>
      <category>개발자 전용 SNS</category>
      <category>디그디그딥</category>
      <category>팀프로젝트</category>
      <category>프로그래머스 데브코스</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/121</guid>
      <comments>https://mxxcode.tistory.com/121#entry121comment</comments>
      <pubDate>Tue, 31 Jan 2023 18:35:20 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 데브코스 - FE#8 Vue 과제 회고</title>
      <link>https://mxxcode.tistory.com/120</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnc3fZ/btrW1OXH4oN/n1dQIhEwctTKNGElfejvYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnc3fZ/btrW1OXH4oN/n1dQIhEwctTKNGElfejvYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnc3fZ/btrW1OXH4oN/n1dQIhEwctTKNGElfejvYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdnc3fZ%2FbtrW1OXH4oN%2Fn1dQIhEwctTKNGElfejvYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;291&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;영화 검색 SPA with Vue&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Vue와 영화 검색이 가능한 API를 활용하여 간단한 영화 검색 웹페이지를 구현하는 과제로 실제 배포까지 진행되어야 했기에 serverless function에 이해도를 가져야 했던 과제였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;프리뷰&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://movie-finder-pi-ten.vercel.app/&quot;&gt;https://movie-finder-pi-ten.vercel.app/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;구현 방법&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;router를 활용하여 페이지를 분리하는 방법도 있었지만 규모가 크지 않았던 만큼 정말 SPA로 구현하고자 하였고 검색된 영화를 클릭하면 모달로 상세페이지를 표시하는 방식을 사용하였습니다. 스타일링을 위해서 부트스트랩과 약간의 Sass를 이용하였습니다. 상태 관리를 위해 props 넘겨주는 방식으로도 어렵지 않게 구현이 가능했지만 Vuex를 활용해보고 싶었기에 이를 적용하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Vuex 상태관리&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;본인이 사용하기 위해 정리한 상태값은 아래와 같다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674646065459&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;state() {
    return {
      isLoading: false,
      searchTerm: null,
      searchedMovies: {},
      selectedMovieId: null,
      selectedMovieDetail: [],
      defaultPhoto:
        'https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/No-Image-Placeholder.svg/1665px-No-Image-Placeholder.svg.png',
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&amp;nbsp;충분히 props로 관리할 수 있을 정도의 내용들이지만 이번 기회에 Vuex의 기회비용을 생각할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여기서 언급한 기회비용은 역시 Vuex를 사용하기 위한 세팅에 들어가는 시간이었다. 많은 시간이 필요한 것은 아니지만 props drilling이 일어나는 상황이 아니라면 굳이 사용할 필요는 없을 것 같았다. store에 따로 mutation과 action을 정리하고 이를 또 해당 컴포넌트로 가져와 사용해야 했기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;장점으로는 어디서든 상태값에 접근할 수 있기에 넘겨주고 다시 올리는 방식을 고민하지 않아도 괜찮았다. 이 과제에서는 관리하는 상태값이 많지 않아서 복잡해 보이지 않았으나 복잡한 경우에는 오히려 더 헷갈릴 수 도 있을 것 같다는 생각이 들었다. 다행이었던 점은 store를 여러 개로 쪼개서 정리할 수 있다는 점이었다. 정리를 잘해놓는다면 쉽게 사용할 수 있을 것 같았다. (물론 이걸 잘하기 위해서는 실력이 좋아야...)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;소소한 사용성&amp;nbsp; 개선을 위한 내용들&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;과제를 진행하며 더 적절한 v-model의 기능이나 간편화를 적용하지 못해 아쉬운 마음이 들지만 잘 적용했다고 느낀 부분도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;첫번째로 v-model.lazy를 활용해서 인풋값이 모두 작성되었을 때 상태를 변경하게 하였다. 이 lazy는 포커스 아웃이나 엔터를 눌렀을 때 상태를 변경하기에 불필요한 상태변경을 줄일 수 있었고 무엇보다 한글을 입력할 때에는 한 글자가 모두 입력되어야 상태가 제대로 변경되는 문제를 해결하기에 적절했다고 생각했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;두 번째로는 모달을 이용해서 불필요한 페이지 이동을 줄인 점이다. 물론 SPA로 동작하여 페이지를 이동한다고 느껴지진 않지만 이번 프로젝트에 적합한 방법이었다고 생각한다. 모달 바깥 부분이나 x버튼을 클릭하거나 esc키를 입력하면 모달 창이 닫히도록 하였다. 여기서는 상태값 selectedMovieDetail이 빈값이 아닐 때 모달을 보여주고 닫을 때는 이를 비우는 방식을 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;마지막으로는 간혹 API에 포스터가 없는 영화들이 있기에 사진이 없을때 준비해 둔 기본사진을 보여주는 방식으로 처리하였다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674647666139&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;methods: {
    defaultPhoto(url) {
      if (url === 'N/A') {
        return this.$store.state.movie.defaultPhoto
      }
      return url
    }
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;Serverless Function&lt;/b&gt;.&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 과제를 진행하며 가장 헤멘 부분인 것 같다. API키를 숨겨줘야 했기 때문이었는데 vercel을 통해 배포할 때 환경변수를 적용하면 빌드 후 제대로 배포가 되지 않는 문제가 발생했고 이를 해결하기 위해 같이 vercel을 사용하는 팀원과 많은 시간을 투자하였다. 전 팀 멘토님께서 어느 정도 언급을 해주셨기 때문에 뭐가 문제였는지 금방 찾을 수 있었으나 역시 적용 방법은 어려웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Vercel은 serverless function을 지원하는데 이는 로컬이 아닌 지정된 해당 서버에서 이 함수를 사용하는 것으로 중간 단계에서 진행되기 때문에 환경변수를 적용한 작업을 쉽게 처리할 수 있다. 아래 request와 같은 handler 함수를 작성하고 해당 내용을 가져와야 한다. 이를 적용한다면 개발자 도구에서 네트워크 탭에 접근하더라도 키값을 숨길 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1674648074356&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import fetch from 'node-fetch';
const API_END_POINT = process.env.API_END_POINT;
const API_KEY = process.env.API_KEY;

export default async function request(request, response) {
  try {
    const { id, plot } = request.query;
    const params = new URLSearchParams({ apikey: API_KEY, i: id, plot });
    const res = await fetch(`${API_END_POINT}?${params}`);
    const data = await res.json();
    return response.status(200).json(data);
  } catch (e) {
    console.error(e + ': api 통신 중 에러가 발생하였습니다.');
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;리팩토링을 진행한 부분&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;Getter, setter 이용한 searchTerm 변경 개선&quot; - searchTerm을 변경하기 위해 watch를 사용하여 변경될때 메서드를 적용하여 상태값을 바꾸는 방식을 사용하였으나 getter와 setter로 이를 간략하고 가독성이 좋게 정리하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;default photo 상태 변수로 변경&quot; - 한 곳에 정리후 이를 가져와 활용하는 방식을 사용하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;onClick 이벤트 id 파라미터로 직접 전달&quot; - DOM에 접근하여 id를 가져오는 방식을 사용하였으나 불필요한 부분이었고 이를 파라미터로 함수에 넘겨 처리하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;node js에서의 에러 표시 방법 수정&quot; - serverless function에서의 에러처리는 프론트엔드 단에서는 보이지 않는다는 문제점이 있기에 alert에서 console.error로 수정하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&quot;prettier 적용, inset으로 변경, 컨벤션 유지를 위한 코드 수정&quot;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;과제를 마치고...&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;Vue를 활용하여 프로젝트를 진행한 경험은 처음이였으나 좋은 강의를 통해 요구된 구현 사항들을 처리할 수 있었습니다. 추가 구현 사항으로 로딩 애니메이션과 이미지 해상도를 다르게 요청하고 open graph를 적용하는 등 다양한 부가 사항들도 공부해 볼 수 있었습니다. 기회가 된다면 Nuxt.js로 Vue SSR을 구현해보고 싶다고 느꼈습니다. vue를 사용하며 러닝 커브가 높다고 느꼈고 왜 인기 있는지 경험해 볼 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;실시간으로 이미지 크기를 다르게 요청할 수 있는 원리에 대해 참고한 자료&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1674648900103&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;AWS Lambda@edge로 실시간 이미지 리사이징(updated)&quot; data-og-description=&quot;AWS Lambda@edge(CloudFront)로 실시간 이미지 리사이징 기능을 구현합니다. Cloud 9으로 람다 함수를 작성하고 CloudWatch로 로그를 확인합니다.&quot; data-og-host=&quot;heropy.blog&quot; data-og-source-url=&quot;https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/&quot; data-og-url=&quot;https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cdt6gw/hyRoBARymC/6K6fT4YqhMqjzJAFfS565K/img.png?width=500&amp;amp;height=250&amp;amp;face=0_0_500_250,https://scrap.kakaocdn.net/dn/bETLI4/hyRozwkgoe/XVKwLfFjUo8pe6Iy1Xby3k/img.png?width=500&amp;amp;height=250&amp;amp;face=0_0_500_250,https://scrap.kakaocdn.net/dn/IcxKj/hyRoGITHOZ/Vbm4K8NtoKZIj4svosCN3K/img.jpg?width=2331&amp;amp;height=1375&amp;amp;face=0_0_2331_1375&quot;&gt;&lt;a href=&quot;https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://heropy.blog/2019/07/21/resizing-images-cloudfrount-lambda/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cdt6gw/hyRoBARymC/6K6fT4YqhMqjzJAFfS565K/img.png?width=500&amp;amp;height=250&amp;amp;face=0_0_500_250,https://scrap.kakaocdn.net/dn/bETLI4/hyRozwkgoe/XVKwLfFjUo8pe6Iy1Xby3k/img.png?width=500&amp;amp;height=250&amp;amp;face=0_0_500_250,https://scrap.kakaocdn.net/dn/IcxKj/hyRoGITHOZ/Vbm4K8NtoKZIj4svosCN3K/img.jpg?width=2331&amp;amp;height=1375&amp;amp;face=0_0_2331_1375');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;AWS Lambda@edge로 실시간 이미지 리사이징(updated)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;AWS Lambda@edge(CloudFront)로 실시간 이미지 리사이징 기능을 구현합니다. Cloud 9으로 람다 함수를 작성하고 CloudWatch로 로그를 확인합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;heropy.blog&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로그래머스 데브코스/회고</category>
      <category>FE#8</category>
      <category>kdt</category>
      <category>serverless fucntion</category>
      <category>Vue</category>
      <category>vuex</category>
      <category>데브코스</category>
      <category>프로그래머스</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/120</guid>
      <comments>https://mxxcode.tistory.com/120#entry120comment</comments>
      <pubDate>Wed, 25 Jan 2023 21:16:51 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 데브코스 - FE#7 CSS 과제 회고</title>
      <link>https://mxxcode.tistory.com/119</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOxcTj/btrW56hHfC9/lMNHvTZOQNwkf7fYO5KKk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOxcTj/btrW56hHfC9/lMNHvTZOQNwkf7fYO5KKk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOxcTj/btrW56hHfC9/lMNHvTZOQNwkf7fYO5KKk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOxcTj%2FbtrW56hHfC9%2FlMNHvTZOQNwkf7fYO5KKk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;291&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;CSS GitHub My Profile 클론&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;강의를 진행해 주신 강사님의 세션에서 저는 어떤 UI가 가장 클론 연습에 좋을까에 대한 질문을 했고 이에 대한 답변으로 깃헙 마이 페이지가 좋을 것 같다는 의견을 듣고 SASS로 SCSS 문법을 활용하여 진행하였습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;프리뷰&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;클론한 페이지&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://radiant-griffin-5973eb.netlify.app/&quot;&gt;https://radiant-griffin-5973eb.netlify.app/&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;원본 페이지&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;a href=&quot;https://github.com/mxx-kor&quot;&gt;https://github.com/mxx-kor&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTawYu/btrW38z5zgi/a94imQmtFUlaoXZNK4PlyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTawYu/btrW38z5zgi/a94imQmtFUlaoXZNK4PlyK/img.png&quot; data-origin-width=&quot;2548&quot; data-origin-height=&quot;1227&quot; data-is-animation=&quot;false&quot; width=&quot;407&quot; height=&quot;196&quot; style=&quot;width: 49.4873%; margin-right: 10px;&quot; data-widthpercent=&quot;50.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTawYu/btrW38z5zgi/a94imQmtFUlaoXZNK4PlyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTawYu%2FbtrW38z5zgi%2Fa94imQmtFUlaoXZNK4PlyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2548&quot; height=&quot;1227&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwiZTd/btrWS2n8x3H/gle7f4KCO5iABftmbAbap1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwiZTd/btrWS2n8x3H/gle7f4KCO5iABftmbAbap1/img.png&quot; data-origin-width=&quot;2543&quot; data-origin-height=&quot;1228&quot; data-is-animation=&quot;false&quot; width=&quot;379&quot; style=&quot;width: 49.3499%;&quot; data-widthpercent=&quot;49.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwiZTd/btrWS2n8x3H/gle7f4KCO5iABftmbAbap1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwiZTd%2FbtrWS2n8x3H%2Fgle7f4KCO5iABftmbAbap1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2543&quot; height=&quot;1228&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;원본 페이지(좌) 클론 페이지(우)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;시멘틱 태그를 활용하여 HTML 작성&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;CSS 강의와 그동안의 경험으로 이제는 어떤 구조로 HTML을 작성해야 하는지 이해도가 있었지만 시멘틱 태그에 대해 생각하고 구조를 작성하느라 시간을 조금 더 투자하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;header, main, nav, section, address, article 등등 상황에 맞는 다양한 태그들을 모두 적절히 사용해 볼 수 있었다. 경계가 모호한 경우도 있었지만 이 경우에는 그 태그 안의 내용을 생각하며 선택하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;grid와 flex를 적절히 활용&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;여태까지 내가 만들었던 구조와는 확실히 다르다는 생각이 들었던 점은 여러 요소들이 정렬된 듯 보이지만 다양한 구조를 가지고 있다는 점이다. header에서는 flex를 사용하고 main 컨테이너는 grid를 활용하여 깔끔하지만 단조롭지 않은 UI를 클론 해볼 수 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;mixin, CSS 변수, SASS 활용&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;따로 자바스크립트를 작성한 내용은 없으나 5일간의 기간만에 이 UI를 구현할 수 있었던건 SASS의 간편함 덕분이라고 생각이 든다. 특히 Nesting하여 따로 분리하여 작성하지 않아도 된다는 점이 편리했고 이로 인해 기존에 작성했던 내용에 접근하는 것도 한결 쉬웠다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;mixin으로는 글자 크기마다 정리된 CSS를 작성하고 가장 자주 사용했던 컨테이너 크기에 대해 정리해두었고 CSS 변수에는 색상, 각 이미지의 경로를 지정하여 이해하기 쉬운 CSS파일을 작성할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;다크모드 구현&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1674571613153&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@media (prefers-color-scheme: dark) {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이번 CSS 과제에서 추가로 작업한 내용은 다크모드 구현이다 미디어 쿼리의 prefers-color-scheme을 이용하여 사용자의 시스템 모드에 따라(다크모드, 라이트모드) 각 색상을 다르게 적용하는 방식이다. 아쉬웠던 점이 있다면 2가지 버전의 코드를 중복으로 작성하여 시간을 많이 소비했다는 점이다. 이에 대해 멘토님에게 질문을 하게 되었고 여기서 믹스인을 활용하는 방법이 있다는 점을 알게되었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;리팩토링을 진행한 부분&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;mixin, 변수 변환 안되도록, 변수 사용법 추가 - 이전에 제출한 파일에서는 mixin과 변수가 함께 컴파일되어 이를 수정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;a 태그 클래스로 정리 - a 태그에 클래스를 주고 이를 이용한 접근법을 사용하도록 수정하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;webpack 적용, 파일 정리 - webpack을 적용하여 컴파일 경로를 설정하고 각종 이미지 파일을 변수로 정리하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;autoprefixer 적용 - filter를 사용한 부분이 있는데 이를 후처리로 작업하여 더 범용적으로 사용할 수 있도록 컴파일하였습니다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;&lt;b&gt;과제를 마치고...&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR';&quot;&gt;이전에는 본인의 수준에서 구현하기 어려운 부분들을 보면 기존 기획했던 부분들을 바꾼 경우가 몇번 있었지만 이번 과제를 통해 더 다양한 CSS 사용법을 익히고 자신감을 얻었습니다. 여전히 어렵지만 경험해 본 내용들을 토대로 더 빠르게 더 가독성 좋게 CSS를 작성할 수 있게 되었습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프로그래머스 데브코스/회고</category>
      <category>CSS</category>
      <category>FE#7</category>
      <category>kdt</category>
      <category>sass</category>
      <category>scss</category>
      <category>과제</category>
      <category>데브코스</category>
      <category>프로그래머스</category>
      <category>프론트엔드 3기</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/119</guid>
      <comments>https://mxxcode.tistory.com/119#entry119comment</comments>
      <pubDate>Wed, 25 Jan 2023 00:06:20 +0900</pubDate>
    </item>
    <item>
      <title>프로그래머스 데브코스 - FE#6 Vanilla JS_2 과제 회고</title>
      <link>https://mxxcode.tistory.com/118</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;725&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMMEoJ/btrUyA63kEg/uZ72EDKEqEQbI2TB7JkTMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMMEoJ/btrUyA63kEg/uZ72EDKEqEQbI2TB7JkTMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMMEoJ/btrUyA63kEg/uZ72EDKEqEQbI2TB7JkTMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMMEoJ%2FbtrUyA63kEg%2FuZ72EDKEqEQbI2TB7JkTMk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;514&quot; height=&quot;291&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;725&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;VanillaJS_2 고양이 사진첩&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강의를 통해 만든 간단한 프로젝트를 보완하는 방식으로 과제가 진행되었고 Vanilla JS 컴포넌트 방식을 사용하여 진행한 프로젝트입니다. 이번 프로젝트에서는 데이터 정합성, setState 최적화를 진행해 보았습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;프로젝트의 기능들&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;breadcrumb&lt;/li&gt;
&lt;li&gt;탐색기 기능&lt;/li&gt;
&lt;li&gt;imageviewer 모달&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRJWKF/btrUukRNdt5/r3pyTqk98AyQPuAKQrRvg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRJWKF/btrUukRNdt5/r3pyTqk98AyQPuAKQrRvg1/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1660&quot; data-origin-height=&quot;1245&quot; data-filename=&quot;edited_스크린샷 2022-12-25 오후 4.54.41.png&quot; style=&quot;width: 53.8376%; margin-right: 10px;&quot; data-widthpercent=&quot;54.47&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRJWKF/btrUukRNdt5/r3pyTqk98AyQPuAKQrRvg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRJWKF%2FbtrUukRNdt5%2Fr3pyTqk98AyQPuAKQrRvg1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1660&quot; height=&quot;1245&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjSno8/btrUwtghhtM/RXsHK2F4g3S8XZvhk6nsdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjSno8/btrUwtghhtM/RXsHK2F4g3S8XZvhk6nsdk/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1928&quot; data-origin-height=&quot;1730&quot; data-filename=&quot;스크린샷 2022-12-25 오후 4.53.41.png&quot; style=&quot;width: 44.9996%;&quot; data-widthpercent=&quot;45.53&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjSno8/btrUwtghhtM/RXsHK2F4g3S8XZvhk6nsdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjSno8%2FbtrUwtghhtM%2FRXsHK2F4g3S8XZvhk6nsdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1928&quot; height=&quot;1730&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;미리 보기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;⏱️ 로딩의 지루함을 날려주는 isLoading&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트의 API가 로딩처리를 학습해보기 위해 상당히 긴 쓰로틀링이 랜덤 한 시간으로 걸려있었고 과제를 진행할수록 한국인은 어쩔 수 없구나라고 느끼게 되었다.  로딩처리를 하는 로직은 생각보다 훨씬 간단했다. fetch와 서버와 통신하거나 연산이 필요해 시간이 오래 걸리는 작업이 있다면 해당 부분을 함수로 만들어 시작하는 부분에서 isLoading의 상태를 true로 작업이 끝나면 false 바꿔주는 방식으로 진행되었다. 로딩 중이라는 표시가 눈에 보이니 기다리는 시간이 많이 지루하지 않았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2022-12-25 오후 5.09.24.png&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;1323&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNuX94/btrUvY17HKw/sHTv3JFpEAeBd4qtCOGJWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNuX94/btrUvY17HKw/sHTv3JFpEAeBd4qtCOGJWK/img.png&quot; data-alt=&quot;로딩 처리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNuX94/btrUvY17HKw/sHTv3JFpEAeBd4qtCOGJWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNuX94%2FbtrUvY17HKw%2FsHTv3JFpEAeBd4qtCOGJWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;346&quot; data-filename=&quot;edited_스크린샷 2022-12-25 오후 5.09.24.png&quot; data-origin-width=&quot;1924&quot; data-origin-height=&quot;1323&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;로딩 처리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;  그 외의 기능들&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;아주 다양한 기능을 가지고 있지는 않지만 탐색기로써 가져야 할 기본적인 기능들을 구현해보고 혼자 학습했다면 놓쳤을 예외처리들도 알게 되었다. Breadcrumb를 구현하는 방식은 노션 클로닝 프로젝트와는 다른 방식으로 상태값에 paths를 추가하여 기록하고 이동하면 이를 빼는 방식으로 진행되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;ImageViewer는 모달창 외의 부분을 클릭하거나 esc를 누르면 꺼지도록 하였고 onClose라는 함수를 통해 상태값을 null로 만들어 모달창을 끄는 방식이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;backspace 키를 누르면 이전 루트로 돌아가는 기능을 구현하였고 추가적으로 적용한 예외처리로는 로딩 중에 백스페이스를 누르는 경우에는 뒤로 가기 기능이 동작하지 않도록 조건문을 작성하였고 뒤로 가기가 진행되면 켜져 있던 모달을 끄고 이동하도록 처리하였다. 여러 가지 상황에 대해 생각해보며 더 나은 사용자 경험을 제공하는 방법에 대해 고려해보게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;b&gt;  데이터 정합성, setState 최적화&lt;/b&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;개인적으로 이번 프로젝트에서 가장 중점적으로 다뤄야 했던 내용이라고 생각한다. 예상하지 못한 오류를 대비하고 불필요한 연산을 줄여 최적화하는 코드에 대해 고민해보고 직접 작성해 보았다. validations라는 파일을 따로 만들고 여러 개의 함수들을 export 하였다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;더 세분화하여 확인하는 방법도 있지만 너무 작게 나누면 오히려 지저분할 것이라고 생각하여 필요한 내용들만 작성해 보았다. checkDifference는 객체의 경우를 비교하기 위해 stringyfy를 진행한 후 비교하는 방법을 사용하였다. 또한 typeof를 연산자를 활용하여 타입들도 체크하였다. checkUrl을 통해 기대하는 url값과 다른 경우에는 오류를 반환하도록 코드를 작성했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1671957051970&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const validationNewOperator = (target, component) =&amp;gt; {
  if (!(target instanceof component)) {
    throw new Error(`${component.name} 컴포넌트는 생성자 함수입니다. new 키워드를 추가해주세요.`);
  }
};

export const checkDifference = (prev, curr) =&amp;gt; {
  return JSON.stringify(prev) === JSON.stringify(curr);
};

export const checkIsObject = (state) =&amp;gt; {
  if (typeof state !== &quot;object&quot;) throw new TypeError(&quot;변경될 상태가 객체가 아닙니다.&quot;);
};

export const checkIsArray = (state) =&amp;gt; {
  if (!Array.isArray(state)) throw new TypeError(&quot;변경될 상태가 배열이 아닙니다.&quot;);
};

export const checkIsBoolean = (state) =&amp;gt; {
  if (typeof state !== &quot;boolean&quot;) throw new TypeError(&quot;변경될 상태가 boolean이 아닙니다.&quot;);
};

export const checkUrl = (url) =&amp;gt; {
  if (!url) return;
  if (typeof url === &quot;string&quot;) {
    if (!url.startsWith(`${API_END_POINT}/static`)) {
      throw new Error(&quot;변경될 URL이 올바르지 않습니다.&quot;);
    }
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;♻️ 리팩토링을 진행한 부분들&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;breadcrumb 최상단에서 root 출력, ko로 변경&quot;&lt;/li&gt;
&lt;li&gt;&quot;breadcrumb setState 최적화, setPaths 추가&quot;&lt;/li&gt;
&lt;li&gt;&quot;breadcrum 코드 정리&quot;&lt;/li&gt;
&lt;li&gt;&quot;태그 닫기, url 상수화, 조건문 개선&quot;&lt;/li&gt;
&lt;li&gt;&quot;validation 함수 이름 변경&quot;&lt;/li&gt;
&lt;li&gt;&quot;상수 사용, TypeError로 변경, 코드 개선&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;  느낀 점&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 코드 리뷰를 통해 많이 정리된 코드를 공유하였다. 더 간결한 방식과 좋은 네이밍, 그리고 놓친 부분들을 리팩토링을 진행하였고 이를 통해 많이 개선된 코드를 작성할 수 있게 되었다. 코드 리뷰를 받으며 중요하다고 느낀 점은 리뷰받은 잘못된 방식의 코드를 다음에 다시 사용하지 않도록 확실하게 정리할 필요가 있겠다고 느끼게 되었다. 난이도가 어렵진 않았지만 상태값 체크하는 방법에 대해 깊게 생각해본 과제였다.&lt;/p&gt;</description>
      <category>프로그래머스 데브코스/회고</category>
      <category>6주차 과제</category>
      <category>kdt</category>
      <category>setState 최적화</category>
      <category>데브코스</category>
      <category>정합성</category>
      <category>프론트엔드 3기</category>
      <author>minjae_4</author>
      <guid isPermaLink="true">https://mxxcode.tistory.com/118</guid>
      <comments>https://mxxcode.tistory.com/118#entry118comment</comments>
      <pubDate>Sun, 25 Dec 2022 17:45:57 +0900</pubDate>
    </item>
  </channel>
</rss>