<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발하는 음대생</title>
    <link>https://jwookj.tistory.com/</link>
    <description>배우는 것을 즐기고, 모든 배움은 소통과 공유를 통해 배가 된다고 믿습니다.
사람의 삶을 바꾸는 서비스에 참여하기 원합니다.</description>
    <language>ko</language>
    <pubDate>Sun, 9 Aug 2026 08:13:52 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>JWOOKJ</managingEditor>
    <image>
      <title>개발하는 음대생</title>
      <url>https://tistory1.daumcdn.net/tistory/4332418/attach/bd4f65ff8a4f404f92b6ae024290be00</url>
      <link>https://jwookj.tistory.com</link>
    </image>
    <item>
      <title>transform 애니메이션이 성능이 더 좋은 이유</title>
      <link>https://jwookj.tistory.com/119</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;브라우저의 렌더링 과정&lt;/b&gt;&lt;br&gt;&lt;a href=&quot;https://jwookj.tistory.com/97&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;2021.06.04 - [개발/WEB] - TIL #28 브라우저는 어떻게 작동하는가&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;TIL #28 브라우저는 어떻게 작동하는가&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;나는 개발을 할 때, 내가 개발하고 있는 서비스를 폭넓게 이해한 상태에서 실사용자의 입장을 생각하며 개발을 하는 사람이 좋은 개발자라고 생각한다. 개발을 공부하며 내가 무언가 만들 수 있&quot; data-og-host=&quot;jwookj.tistory.com&quot; data-og-source-url=&quot;https://jwookj.tistory.com/97&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eZSCC/hyWvXkIvXy/rBGxRvTwbecW3dn5VYnE3K/img.png?width=500&amp;amp;height=339&amp;amp;face=0_0_500_339,https://scrap.kakaocdn.net/dn/dwOMP7/hyWvIgNLFF/LXTu4YEYJv3Rp9btRdHs3K/img.png?width=500&amp;amp;height=339&amp;amp;face=0_0_500_339,https://scrap.kakaocdn.net/dn/btcut5/hyWvRdJAy9/qJKRSPXDjDfWWTKT0zfdP1/img.png?width=1150&amp;amp;height=537&amp;amp;face=0_0_1150_537&quot; data-og-url=&quot;https://jwookj.tistory.com/97&quot;&gt;&lt;a href=&quot;https://jwookj.tistory.com/97&quot; target=&quot;_blank&quot; data-source-url=&quot;https://jwookj.tistory.com/97&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eZSCC/hyWvXkIvXy/rBGxRvTwbecW3dn5VYnE3K/img.png?width=500&amp;amp;height=339&amp;amp;face=0_0_500_339,https://scrap.kakaocdn.net/dn/dwOMP7/hyWvIgNLFF/LXTu4YEYJv3Rp9btRdHs3K/img.png?width=500&amp;amp;height=339&amp;amp;face=0_0_500_339,https://scrap.kakaocdn.net/dn/btcut5/hyWvRdJAy9/qJKRSPXDjDfWWTKT0zfdP1/img.png?width=1150&amp;amp;height=537&amp;amp;face=0_0_1150_537')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;TIL #28 브라우저는 어떻게 작동하는가&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;나는 개발을 할 때, 내가 개발하고 있는 서비스를 폭넓게 이해한 상태에서 실사용자의 입장을 생각하며 개발을 하는 사람이 좋은 개발자라고 생각한다. 개발을 공부하며 내가 무언가 만들 수 있&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;jwookj.tistory.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;br&gt;먼저 브라우저의 렌더링은 간단하게 &lt;b&gt;style&lt;/b&gt; - &lt;b&gt;layout&lt;/b&gt; - &lt;b&gt;paint&lt;/b&gt;- &lt;b&gt;composite&lt;/b&gt;의 단계로 나눠지게 되는데,&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;style&lt;/b&gt;의 단계에서 HTML을 통한 &lt;b&gt;DOM트리 생성&lt;/b&gt;, CSS를 통한 &lt;b&gt;CSSOM트리 생성&lt;/b&gt;과&lt;b&gt; Javascript 엔진에 의한 javascript 코드 해석&lt;/b&gt;이 일어나게 되고 해당 결과물들을 통해 &lt;b&gt;render tree&lt;/b&gt;를 만들게 됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;layout&lt;/b&gt; 단계에서는 reder tree를 기반으로 tree의 각 노드들의 크기와 위치를 계산하게 됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;paint&lt;/b&gt;의 단계에서는 노드를 실제로 화면에 그리는 작업이 일어나게 되는데 이때 특정 htm태그(video, canvas)와 css속성(opacity, transform, will-change 등...)에 따라 &lt;b&gt;GPU에게 일임하는 그래픽 레이어&lt;/b&gt;를 별도로 생성하게 됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;composite&lt;/b&gt;의 단계는 그려진 paint의 레이어를 합치는 작업을 하게 됩니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;transform 애니메이션의 장점&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;reflow&lt;/b&gt;&amp;nbsp;:&amp;nbsp;생성된 DOM 노드의 레이아웃(너비, 높이 등) 변경 시 영향받는 모든 노드(자식, 부모)의 수치를 다시 계산하여 렌더 트리를 재생성하는 작업입니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;CSS의 transform 속성은 CPU가 아닌 GPU에서 처리됩니다.&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;GPU는 반복되는 작업의 병렬처리에 최적화되어있는 장치이고 CPU의 메인 쓰레드와는 별개로 작동합니다.&lt;br&gt;이러한 특징 때문에 transform 속성을 사용해 애니메이션을 구현할 경우 CPU의 부하를 덜고 더 나은 하드웨어의 성능을 기대할 수 있게 됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;transform 속성은 애니메이션이 일어날 경우 위에서 언급한 paint 단계에서 그래픽 레이어로 구분되기 때문에 &lt;b&gt;reflow가 일어나지 않게 되고&lt;/b&gt; 이후 composite 단계의 작업만 발생하게 되기 때문에 훨씬 효율적인 렌더링이 가능해집니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&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://mong-blog.tistory.com/entry/CSS-%EC%99%9C-transform-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C-with-GPU-Reflow&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;&lt;b&gt;https://mong-blog.tistory.com/entry/CSS-%EC%99%9C-transform-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C-with-GPU-Reflow&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[CSS] 왜 transform 애니메이션 성능이 좋을까? (with. GPU, Reflow)&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;1. Left 애니메이션은 왜 끊길까? transform, left 속성으로, 대각선 방향으로 움직이는 공 애니메이션을 만들어봤다. See the Pen Animate with Translate and left by KumJungMin (@kumjungmin) on CodePen. 보기에는 둘 다 잘&quot; data-og-host=&quot;mong-blog.tistory.com&quot; data-og-source-url=&quot;https://mong-blog.tistory.com/entry/CSS-%EC%99%9C-transform-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C-with-GPU-Reflow&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cJwec2/hyWvNJbrCD/p7obPjKDX5U65lFnxEKHk0/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/cOS12O/hyWvQ6YPFW/PdxxakoaPsf8APOSfWesX0/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/6AHpR/hyWvWTEK1N/PfOxLIK2F8xjQb6xvd59F0/img.png?width=2502&amp;amp;height=1114&amp;amp;face=0_0_2502_1114&quot; data-og-url=&quot;https://mong-blog.tistory.com/entry/CSS-%EC%99%9C-transform-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C-with-GPU-Reflow&quot;&gt;&lt;a href=&quot;https://mong-blog.tistory.com/entry/CSS-%EC%99%9C-transform-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C-with-GPU-Reflow&quot; target=&quot;_blank&quot; data-source-url=&quot;https://mong-blog.tistory.com/entry/CSS-%EC%99%9C-transform-%EC%95%A0%EB%8B%88%EB%A9%94%EC%9D%B4%EC%85%98-%EC%84%B1%EB%8A%A5%EC%9D%B4-%EC%A2%8B%EC%9D%84%EA%B9%8C-with-GPU-Reflow&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cJwec2/hyWvNJbrCD/p7obPjKDX5U65lFnxEKHk0/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/cOS12O/hyWvQ6YPFW/PdxxakoaPsf8APOSfWesX0/img.png?width=800&amp;amp;height=420&amp;amp;face=0_0_800_420,https://scrap.kakaocdn.net/dn/6AHpR/hyWvWTEK1N/PfOxLIK2F8xjQb6xvd59F0/img.png?width=2502&amp;amp;height=1114&amp;amp;face=0_0_2502_1114')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;[CSS] 왜 transform 애니메이션 성능이 좋을까? (with. GPU, Reflow)&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;1. Left 애니메이션은 왜 끊길까? transform, left 속성으로, 대각선 방향으로 움직이는 공 애니메이션을 만들어봤다. See the Pen Animate with Translate and left by KumJungMin (@kumjungmin) on CodePen. 보기에는 둘 다 잘&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;mong-blog.tistory.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://ssocoit.tistory.com/258&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://ssocoit.tistory.com/258&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;[CSS] Blink엔진에서 transform 속성은 Reflow와 Repaint가 발생하지 않는다&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Reflow와 Repaint에 대해서 너무 겉핥기로만 알고있는게 아닐까.. 고민하게 만들어준 문제입니다. 단순히 어떤 동작이 발생하는지만 파악하고 넘어가는 것보다는 사용하는 CSS 속성에 따라 어떻게 &quot; data-og-host=&quot;ssocoit.tistory.com&quot; data-og-source-url=&quot;https://ssocoit.tistory.com/258&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bQZ75v/hyWvJzZ7if/gW9ZVruFdeZBM9bYWzEYEK/img.png?width=640&amp;amp;height=264&amp;amp;face=0_0_640_264,https://scrap.kakaocdn.net/dn/cpHTld/hyWvSRfYkP/YJc8wfmNfCdk2ymQ9Hu6v0/img.png?width=640&amp;amp;height=264&amp;amp;face=0_0_640_264,https://scrap.kakaocdn.net/dn/CcgQz/hyWvLYTNMD/PIvFJ8FYhUobxM2XO5kdi1/img.png?width=1150&amp;amp;height=537&amp;amp;face=0_0_1150_537&quot; data-og-url=&quot;https://ssocoit.tistory.com/258&quot;&gt;&lt;a href=&quot;https://ssocoit.tistory.com/258&quot; target=&quot;_blank&quot; data-source-url=&quot;https://ssocoit.tistory.com/258&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bQZ75v/hyWvJzZ7if/gW9ZVruFdeZBM9bYWzEYEK/img.png?width=640&amp;amp;height=264&amp;amp;face=0_0_640_264,https://scrap.kakaocdn.net/dn/cpHTld/hyWvSRfYkP/YJc8wfmNfCdk2ymQ9Hu6v0/img.png?width=640&amp;amp;height=264&amp;amp;face=0_0_640_264,https://scrap.kakaocdn.net/dn/CcgQz/hyWvLYTNMD/PIvFJ8FYhUobxM2XO5kdi1/img.png?width=1150&amp;amp;height=537&amp;amp;face=0_0_1150_537')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;[CSS] Blink엔진에서 transform 속성은 Reflow와 Repaint가 발생하지 않는다&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Reflow와 Repaint에 대해서 너무 겉핥기로만 알고있는게 아닐까.. 고민하게 만들어준 문제입니다. 단순히 어떤 동작이 발생하는지만 파악하고 넘어가는 것보다는 사용하는 CSS 속성에 따라 어떻게 &lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;ssocoit.tistory.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/CSS</category>
      <category>CSS</category>
      <category>transform</category>
      <category>렌더링</category>
      <category>성능최적화</category>
      <category>애니메이션</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/119</guid>
      <comments>https://jwookj.tistory.com/119#entry119comment</comments>
      <pubDate>Thu, 4 Jul 2024 18:42:00 +0900</pubDate>
    </item>
    <item>
      <title>베스트 앨범</title>
      <link>https://jwookj.tistory.com/118</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/A4ES5/btsHWf1XRWT/5DqpALbO5Zja8dOngvZEpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/A4ES5/btsHWf1XRWT/5DqpALbO5Zja8dOngvZEpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/A4ES5/btsHWf1XRWT/5DqpALbO5Zja8dOngvZEpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FA4ES5%2FbtsHWf1XRWT%2F5DqpALbO5Zja8dOngvZEpK%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;499&quot; height=&quot;174&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&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;/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 href=&quot;https://jwookj.tistory.com/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2022.02.07 - [코딩테스트/Javascript] - 베스트 앨범&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718170749901&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;jwookj.tistory.com&quot; data-og-source-url=&quot;https://jwookj.tistory.com/110&quot; data-og-url=&quot;https://jwookj.tistory.com/110&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dlRB0W/hyWg0KiJQG/CZVrKmwOkynn4Cm6qWi6FK/img.png?width=499&amp;amp;height=174&amp;amp;face=0_0_499_174,https://scrap.kakaocdn.net/dn/cafU2O/hyWldBfvba/zVkW903V9ARkT5CSygdewK/img.png?width=499&amp;amp;height=174&amp;amp;face=0_0_499_174&quot;&gt;&lt;a href=&quot;https://jwookj.tistory.com/110&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jwookj.tistory.com/110&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dlRB0W/hyWg0KiJQG/CZVrKmwOkynn4Cm6qWi6FK/img.png?width=499&amp;amp;height=174&amp;amp;face=0_0_499_174,https://scrap.kakaocdn.net/dn/cafU2O/hyWldBfvba/zVkW903V9ARkT5CSygdewK/img.png?width=499&amp;amp;height=174&amp;amp;face=0_0_499_174');&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;#문제 스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시하려 합니다. 노래는 고유 번호로 구분하며, 노래를 수록하는 기준은 다음과 같습니다. 속한 노&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jwookj.tistory.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;
&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;pre id=&quot;code_1718173363533&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const solution = (genres, plays) =&amp;gt; {
  const genreMap = new Map();

  genres.map((genre, index) =&amp;gt; [genre, plays[index]]).forEach(([genre, play], index) =&amp;gt; {
    const musicData = genreMap.get(genre) || { total: 0, songs: [] }
    genreMap.set(genre, {
      total: musicData.total + play,
      songs: [...musicData.songs, { play, index }]
        .sort((a, b) =&amp;gt; b.play - a.play)
        .slice(0, 2)
    })
  })

  return [...genreMap.entries()].
    sort((a, b) =&amp;gt; b[1].total - a[1].total).
    flatMap(genre =&amp;gt; genre[1].songs).
    map(song =&amp;gt; song.index);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;b&gt;flatMap&lt;/b&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;a href=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1718173681597&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;Array.prototype.flatMap() - JavaScript | MDN&quot; data-og-description=&quot;Array 인스턴스의 flatMap() 메서드는 배열의 각 요소에 주어진 콜백 함수를 적용한 다음 그 결과를 한 단계씩 평탄화하여 형성된 새 배열을 반환합니다. 이 메서드는 map() 뒤에 깊이 1의 flat()을 붙이&quot; data-og-host=&quot;developer.mozilla.org&quot; data-og-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap&quot; data-og-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ciqnFg/hyWlk8drwk/fD5qwHOgQvWQcBoVIbkn2k/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/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ciqnFg/hyWlk8drwk/fD5qwHOgQvWQcBoVIbkn2k/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;Array.prototype.flatMap() - JavaScript | MDN&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Array 인스턴스의 flatMap() 메서드는 배열의 각 요소에 주어진 콜백 함수를 적용한 다음 그 결과를 한 단계씩 평탄화하여 형성된 새 배열을 반환합니다. 이 메서드는 map() 뒤에 깊이 1의 flat()을 붙이&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트/Javascript</category>
      <category>flatmap</category>
      <category>베스트앨범</category>
      <category>자바스크립트</category>
      <category>코딩테스트</category>
      <category>코테</category>
      <category>프로그래머스</category>
      <category>해시테이블</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/118</guid>
      <comments>https://jwookj.tistory.com/118#entry118comment</comments>
      <pubDate>Wed, 12 Jun 2024 15:29:42 +0900</pubDate>
    </item>
    <item>
      <title>react에서 Next.js v14로 마이그레이션 하기</title>
      <link>https://jwookj.tistory.com/117</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;tl;dr&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.studyfront.co.kr/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;StudyFront&lt;/span&gt;&lt;/a&gt;(구 Hypen) 프론트엔드 스택을 react +typescript + vite에서 Next.js v14로 변경했습니다.&lt;br&gt;많관부~ &lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&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://www.studyfront.co.kr/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;StudyFront&lt;/span&gt;&lt;/a&gt;에서는 사용자들끼리 입시에 관한 고민상담, 정보 공유를 위한 커뮤니티 서비스를 최근 새로 오픈했습니다.&lt;br&gt;커뮤니티 서비스를 오픈한 이유는 여러가지가 있었지만, 그 중 큰 비중을 차지한 한가지는 ‘&lt;b&gt;신규 사용자의 유입&lt;/b&gt;’이었습니다.&lt;br&gt;이를 위해서는 **각 게시글에 대한 SEO(Search Engine Optimization)**가 이루어지는 것이 첫번째 관문이라고 생각되었습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size18&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;초기 StudyFront(구 Hypen)는 개발단에서 &lt;b&gt;빠른 속도와 테스트, 업데이트&lt;/b&gt;가 필요했고, 이를 위해 개발자의 숙련도(개발 속도), 초기 개발 환경 구성의 용이함에 초점을 맞춰 &lt;b&gt;react + typescript + vite&lt;/b&gt;를 활용한 프로젝트로 구성되어 있었습니다.&lt;br&gt;해당 스택의 문제점은 커뮤니티 서비스가 새로 오픈하고 &lt;b&gt;각 게시글 페이지 별로 메타태그를 적용&lt;/b&gt;해야하는 상황에서 부각되었습니다.&lt;br&gt;react는 기본적으로 **CSR(Client Side Rendering)**을 지원하는 &lt;b&gt;SPA(Single Page Application)&lt;/b&gt; 라이브러리이고, CSR을 지원하는 SPA 라이브러리에서는 단 하나의 HTML 파일만을 사용하여 웹페이지를 렌더링하기 때문에 각 페이지별 SEO를 위한 메타태그를 적용하기 어려운 상황이었습니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &amp;nbsp;SPA(Single Page Application)&amp;nbsp;: 기존의 웹페이지는 각 페이지마다 서버에서 HTML을 생성하여 클라이언트로 전달해주는 MPA(Multiple Page Application) 방식 이었습니다.&lt;br&gt;&lt;br&gt;해당 방식은 UX측면, 서버의 부하등의 단점이 존재했는데, 이를 해결하기 위해 프로젝트 내에 단 하나의 HTML파일을 두고 페이지 이동 시, 해당 HTML 파일 내에서 Javascript를 활용하여 동적으로 페이지를 렌더링 하는 SPA 방식이 등장하게 되었습니다.&lt;br&gt;&lt;br&gt;흔히 프론트엔드 프레임워크 3대장(react, vue, angular)이라고 불리는 프레임워크들이 해당 방식을 채택하고 있습니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size18&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;해당 문제를 해결하기 위해 3가지의 선택지를 고려했습니다.&lt;/p&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;react-snap + react-helmet을 활용한 정적 페이지 + 동적 메타태그 적용&lt;/li&gt;&lt;li&gt;server-side-rendering을 위한 자체 서버 구축&lt;/li&gt;&lt;li&gt;react &lt;b&gt;=&amp;gt;&lt;/b&gt; Next.js로의 마이그레이션&lt;/li&gt;&lt;/ol&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Next.js는 react를 위한 프레임워크입니다. react에서 제공하지 않는 기능, 혹은 사용하기 복잡한 기능의 사용을 용이하게 도와줍니다. (ex. server-side-rendering, etc…)&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. react-snap + react-helmet을 활용한 정적 페이지 + 동적 메타태그 적용&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;react-snap 라이브러리는 업데이트 된지 5-6년 되었고, 동적 라우팅에 대해 제대로 대응하지 못한다는 문제점과 react-helmet 라이브러리는 &lt;a href=&quot;https://github.com/nfl/react-helmet/issues/377&quot; target=&quot;_self&quot;&gt;&lt;span&gt;동적으로 생성된 메타태그가 제대로 구글 크롤러봇에 의해 수집되지 못한다는 이슈&lt;/span&gt;&lt;/a&gt;가 있었기에 선택에서 제외했습니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;b&gt; react-snap&lt;/b&gt; : 프로젝트 빌드시에 모든 경로의 페이지를 정적 HTML 파일로 빌드해주는 라이브러리입니다. &lt;br&gt;&lt;b&gt;react-helmet&lt;/b&gt; : 동적으로 HTML head 태그 내에 메타태그를 삽입할 수 있게 해주는 라이브러리입니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. server-side-rendering을 위한 자체 서버 구축&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;작성하신 글을 읽을 때마다 감탄하게 되는 &lt;b&gt;Evan Moon&lt;/b&gt;님의 &lt;a href=&quot;https://evan-moon.github.io/2018/09/25/vue-ssr/&quot; target=&quot;_self&quot; title=&quot;Vue Server Side Rendering&quot;&gt;&lt;span&gt;Vue Server Side Rendering&lt;/span&gt;&lt;/a&gt; 글을 보고 자체적으로 server side rendering 서버를 구축하는 것이 오히려 개발 기간을 단축시킬 수 있지 않을까 고민했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;하지만 서버 구축 이후의 배포 과정, 적용 후 발생할 side effect와 이후의 확장성을 고려했을 때 해당 목적만을 지닌 서버를 구축하기 보다는 범용적으로 사용되고 있는 프레임워크의 힘을 빌리는 것이 조금 더 서비스를 위한 방법이라고 생각되었습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. react =&amp;gt; Next.js로의 마이그레이션&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;최종적으로 Next.js로 마이그레이션 하는 작업이 가장 안정적이고 빠르게 문제를 해결할 수 있다고 생각되었습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Next.js를 알기 전에 알아야 할 지식&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;마이그레이션 과정을 설명하기 전에 Next.js를 사용하기 위해 알고 있으면 좋을 지식들을 먼저 간략하게 정리합니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp; &lt;b&gt; 렌더링(Rendering) &lt;/b&gt;: 웹 프론트엔드 개발에서 렌더링은 코드가 렌더링 엔진(Blink, Gecko, Webkit)에 의해 브라우저에 그려지는 과정을 이야기기합니다.&lt;br&gt;크게 &lt;b&gt;DOM 트리, CSSOM 트리 생성 → 렌더링 트리 생성 → 레이아웃 → 페인팅&lt;/b&gt;의 단계로 이루어집니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;CSR(Client Side Rendering)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;CSR은 말 그대로 클라이언트 측에서 모든 렌더링을 진행하는 것입니다.&lt;br&gt;서버측에서는 초기에 SPA의 기본 뼈대가 되는 skeleton HTML(index.HTML)을 전달해 준 뒤에 Javascript 파일을 전송합니다.&lt;br&gt;클라이언트는 초기 HTML의 DOM트리를 생성한 후에 Javascript 코드 요청을 보내고 받아온 Javascript 코드의 내용에 따라 기존 DOM트리를 확장하여 동적으로 DOM트리를 생성, 조작하게 됩니다.&lt;br&gt;react는 이 과정에서의 DOM트리 조작 최적화를 위해 &lt;a href=&quot;https://ko.legacy.reactjs.org/docs/faq-internals.html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Virtual DOM&lt;/span&gt;&lt;/a&gt;을 활용합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SSR(Server Side Rendering)&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;SSR은 서버 측에서 먼저 페이지에 보여질 HTML을 생성한 후에 클라이언트에게 완성된 HTML을 전달합니다.&lt;br&gt;클라이언트는 완성된 HTML을 통해 DOM트리를 생성하게 되고, 사용자와의 상호작용(ex. click, focus, etc…)을 위해 Javascript 코드를 받아와 &lt;a href=&quot;https://react.dev/reference/react-dom/client/hydrateRoot#hydrating-server-rendered-html&quot; target=&quot;_self&quot;&gt;&lt;span&gt;hydrate&lt;/span&gt;&lt;/a&gt;의 과정을 통해 HTML에 Javascript 로직을 주입하는 거치게 됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Server Component&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;React Server Component(RSC)&lt;/b&gt;는 Meta의 react 팀에서 새로 도입한 기술입니다.&lt;br&gt;&lt;a href=&quot;https://danabra.mov/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Dan Abramov&lt;/span&gt;&lt;/a&gt;(전 Meta react 팀, redux 창시자)에 의해 &lt;a href=&quot;https://www.youtube.com/watch?v=TQQPAU21ZUw&quot; target=&quot;_self&quot;&gt;&lt;span&gt;2020년에 처음으로 발표&lt;/span&gt;&lt;/a&gt; 후 react v18에서의 실험적 기능을 거쳐 Next.js v13의 app router 방식에 사용 되었습니다. (개인적으로 좋아하는 &lt;a href=&quot;https://overreacted.io/things-i-dont-know-as-of-2018/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Dan의 글&lt;/span&gt;&lt;/a&gt;도 첨부합니다.)&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Component?&lt;/b&gt;&lt;/h3&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  &lt;b&gt;컴포넌트(component)&lt;/b&gt; : 리액트에서 사용되는 독립된 블럭 단위의 재사용 가능한 ui 코드를 뜻합니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;기존 리액트의 컴포넌트는 모두 클라이언트에서 생성되는 컴포넌트였습니다. 서버와의 통신은 데이터를 갱신할 때만 이루어 졌고, 갱신된 데이터에 따라 컴포넌트는 동적으로 ui를 생성했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;즉 기존 컴포넌트는 서버의 자원에 직접적으로 접근할 수 없었고, 서버에서 허용된 정보만을 요청을 통해 받아올 수 있었습니다.&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zW0pR/btsHhQU1xFw/k7XqomtTuCoycIaYhKA8kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zW0pR/btsHhQU1xFw/k7XqomtTuCoycIaYhKA8kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zW0pR/btsHhQU1xFw/k7XqomtTuCoycIaYhKA8kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzW0pR%2FbtsHhQU1xFw%2Fk7XqomtTuCoycIaYhKA8kk%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;991&quot; height=&quot;479&quot; data-origin-width=&quot;991&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react는 서버 컴포넌트를 통해 기존 컴포넌트가 갖고 있던 불편함을 해소할 수 있는 방법을 제시했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;서버 컴포넌트는 &lt;b&gt;서버에서 생성되는 컴포넌트&lt;/b&gt;입니다.&lt;br&gt;위의 SSR과 조금 헷갈릴 수 있지만 가장 큰 차이는 &lt;b&gt;SSR은 string HTML을 반환&lt;/b&gt;하게 되고, &lt;b&gt;서버 컴포넌트는 RSC(React Server Component)를 payload 형태로 반환&lt;/b&gt;한다는 점입니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;479&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lf1uT/btsHhsGHL2L/XDtrSBRso7t0dj6bXixbh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lf1uT/btsHhsGHL2L/XDtrSBRso7t0dj6bXixbh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lf1uT/btsHhsGHL2L/XDtrSBRso7t0dj6bXixbh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flf1uT%2FbtsHhsGHL2L%2FXDtrSBRso7t0dj6bXixbh1%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;985&quot; height=&quot;479&quot; data-origin-width=&quot;985&quot; data-origin-height=&quot;479&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Server Component의 특징&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;b&gt;서버 자원(파일 시스템, DB, etc…)에 직접적으로 접근&lt;/b&gt;할 수 있습니다.&lt;/li&gt;&lt;li&gt;이미 서버에서 생성된 컴포넌트를 받기 때문에 컴포넌트를 구성하기 위한 추가적인 Javascript 코드의 양을 줄일 수 있게 됩니다.&lt;/li&gt;&lt;li&gt;서버에서 랜더링 되기 때문에 랜더링에 필요한 &lt;b&gt;데이터를 가져오는 시간과 클라이언트가 요청하는 횟수를 줄여서 성능 향상&lt;/b&gt;에 도움을 줄 수 있습니다.&lt;/li&gt;&lt;li&gt;서버에서 생성되고 관리되기 때문에 기존 component의 life cycle을 관리하는 hook( useState, useEffect)과 커스텀 hook을 사용하지 못합니다.&lt;/li&gt;&lt;li&gt;마찬가지로 서버에서 생성되기 때문에 브라우저에서 접근 가능한 window api에 접근하지 못합니다.&lt;/li&gt;&lt;li&gt;DOM을 통해 사용자와 상호작용할 수 있는 Event Listener를 사용하지 못합니다.&lt;/li&gt;&lt;li&gt;현재 React Server Component는 &lt;b&gt;함수형 component만을 지원&lt;/b&gt;하기에 class형 component는 사용할 수 없습니다.&lt;/li&gt;&lt;/ul&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Next.js v13&lt;/b&gt;&lt;/h2&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Next의 최신 버전은 14이고 현재 StudyFront에서도 14버전을 사용하고 있지만 대대적인 변화가 생긴 버전은 13이기에 13버전의 변화를 중점으로 정리하겠습니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;Next.js는 react를 위한 프레임워크입니다. 이전 Next.js v12에 대한 내용은 &lt;a href=&quot;https://www.notion.so/wiki/spaces/SDTH/pages/483917841&quot; target=&quot;_self&quot;&gt;&lt;span&gt;여기&lt;/span&gt;&lt;/a&gt;에 정리해 놓았습니다.&lt;br&gt;react에서 server component를 공개한 후 Next에서도 server component를 활용한 새로운 아키텍처와 기능을 추가하였습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;app router&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Next.js는 pages router 방식을 사용하여 page 폴더 내에 배치한 폴더 구조에 따라 url routing을 지원했습니다.&lt;br&gt;이후 server component를 좀 더 용이하게 사용하고자 Next.js 에서는 새로 app router방식을 공개했습니다. (pages router 방식을 사용할 수도 있습니다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;app router를 사용하게 된다면 프로젝트 내 컴포넌트는 &lt;b&gt;기본적으로 서버 컴포넌트를 사용&lt;/b&gt;하게 됩니다.&lt;br&gt;app router 방식에서도 app 디렉토리 내의 폴더 구조를 통해 라우팅이 되고 각 폴더의 page파일을 해당 경로의 기본 페이지로 인식합니다. (각 폴더 명이 경로명으로 적용됩니다.)&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;554&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyUQ4w/btsHhQOdpYx/MNKIEyeTlVAFQm0UEbr7d0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyUQ4w/btsHhQOdpYx/MNKIEyeTlVAFQm0UEbr7d0/img.png&quot; data-alt=&quot;StudyFront 중첩 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyUQ4w/btsHhQOdpYx/MNKIEyeTlVAFQm0UEbr7d0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyUQ4w%2FbtsHhQOdpYx%2FMNKIEyeTlVAFQm0UEbr7d0%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;223&quot; height=&quot;554&quot; data-origin-width=&quot;223&quot; data-origin-height=&quot;554&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;StudyFront 중첩 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위의 예시를 보면 폴더명에 따라 url은 &lt;a href=&quot;http://www.studyfront.co.kr/calculator%EA%B0%80&quot; target=&quot;_self&quot;&gt;&lt;span&gt;www.studyfront.co.kr/calculator&lt;/span&gt;&lt;/a&gt; 가 되고 해당 주소로 진입했을 때 가장 먼저 보여지는 ui는 page.tsx가 됩니다. (app 디렉토리 내 root 경로의 page.tsx파일은 &lt;a href=&quot;http://www.studyfront.co.kr/calculator%EA%B0%80&quot; target=&quot;_self&quot;&gt;&lt;span&gt;www.studyfront.co.kr/&lt;/span&gt;&lt;/a&gt; 의 초기 ui를 보여주게 됩니다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;페이지별 중첩 구조&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;574&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nEQnC/btsHddqQ2iC/2vALtn8iUGuS1S5tcixEV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nEQnC/btsHddqQ2iC/2vALtn8iUGuS1S5tcixEV0/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;amp;nbsp; https://nextjs.org/docs/app/building-your-application/routing&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nEQnC/btsHddqQ2iC/2vALtn8iUGuS1S5tcixEV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnEQnC%2FbtsHddqQ2iC%2F2vALtn8iUGuS1S5tcixEV0%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;1434&quot; height=&quot;574&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;574&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;amp;nbsp; https://nextjs.org/docs/app/building-your-application/routing&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;app router 내 각 폴더(url 경로)는 위의 구조를 갖게 됩니다.&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;layout&lt;/b&gt; : 페이지를 이루는 컴포넌트 중 가장 상위의 개념이 됩니다. 페이지에서 공통으로 적용하는 ui를 담을 수 있습니다. 하위 컴포넌트의 상태가 변경되어도 &lt;b&gt;재렌더링 되지 않습니다&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;page&lt;/b&gt; : 경로에 따른 페이지 고유의 ui를 담을 수 있는 컴포넌트입니다. 페이지에서 보여주어야 하는 핵심 ui를 보여줍니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;template&lt;/b&gt; : layout과 비슷하게 공통으로 적용하는 ui를 담을 수 있지만, 하위 페이지의 상태 변경시 &lt;b&gt;재렌더링이 되어야 하는 ui&lt;/b&gt;를 담아야 합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;error&lt;/b&gt; : 페이지 내 에러 발생시 보여지는 ui를 담고 있습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;loading&lt;/b&gt; : 페이지 내 로딩 발생시 보여지는 ui를 담고 있습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;not-found&lt;/b&gt; : 해당 경로에 존재하는 페이지가 없을 경우 보여지는 ui를 담고 있습니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;global-error&lt;/b&gt; : app 디렉토리의 root 경로에만 추가할 수 있는 파일로 &lt;b&gt;전역 에러&lt;/b&gt;를 담당하는 ui를 담고 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  app router에서는 react의 &lt;a href=&quot;https://react-ko.dev/reference/react/Suspense&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Suspense&lt;/span&gt;&lt;/a&gt;와 &lt;a href=&quot;https://react-ko.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Error boundary&lt;/span&gt;&lt;/a&gt;를 적극적으로 활용하고 있기 때문에 각 페이지 별로 loading.tsx, error.tsx를 통해 선언적으로 로딩 상태와 에러 상태를 관리해줄 수 있습니다.&lt;/blockquote&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;HTML Streaming&lt;/b&gt;&lt;/h4&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;/li&gt;&lt;li&gt;서버에서 페이지의 HTML 렌더링하기&lt;/li&gt;&lt;li&gt;페이지의 HTML, CSS, Javascript를 클라이언트로 전달하기&lt;/li&gt;&lt;li&gt;아직 사용자와의 상호작용이 되지 않는 페이지를 HTML, CSS로 보여주기&lt;/li&gt;&lt;li&gt;hydrate 과정을 통해 사용자와의 상호작용이 되도록 적용하기&lt;/li&gt;&lt;/ol&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cymOkp/btsHd9BJesG/5LFLYK9Kkvinh9Kwdana40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cymOkp/btsHd9BJesG/5LFLYK9Kkvinh9Kwdana40/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;amp;nbsp; https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cymOkp/btsHd9BJesG/5LFLYK9Kkvinh9Kwdana40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcymOkp%2FbtsHd9BJesG%2F5LFLYK9Kkvinh9Kwdana40%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;1434&quot; height=&quot;549&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;amp;nbsp; https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;위의 단계들은 순차적이고 다음 단계의 진행을 차단합니다. 또한 react는 Javascript hydration이 일어난 이후에 ui를 표현해 줄 수 있기 때문에, 사용자는 위의 모든 단계가 끝나기 전까지는 빈 페이지를 보게 됩니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yhyKw/btsHgDhvqce/813AJfG3Qz7ATK5iVIgY8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yhyKw/btsHgDhvqce/813AJfG3Qz7ATK5iVIgY8k/img.png&quot; data-alt=&quot;출처 :&amp;amp;amp;amp;nbsp; https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yhyKw/btsHgDhvqce/813AJfG3Qz7ATK5iVIgY8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyhyKw%2FbtsHgDhvqce%2F813AJfG3Qz7ATK5iVIgY8k%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;1434&quot; height=&quot;654&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처 :&amp;amp;amp;nbsp; https://nextjs.org/docs/app/building-your-application/routing/loading-ui-and-streaming&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;HTML streaming은 서버에서 렌더링 되는 HTML을 좀 더 작은 단위의 chunk로 나누어서 점진적으로 받아올 수 있게 합니다.&lt;br&gt;이를 통해 사용자는 페이지의 먼저 렌더링 된 부분부터 순차적으로 볼 수 있게 됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;Next.js v12 이상부터 서버에서 렌더링되는 페이지는 자동으로 HTML streaming이 적용됩니다.&lt;br&gt;HTML streaming과 react Suspense를 활용하여 좀 더 세밀하게 ux를 향상시킬 수도 있습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { Suspense } from 'react'
import { PostFeed, Weather } from './Components'

export default function Posts() {
  return (
    &amp;lt;section&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;p&amp;gt;Loading feed...&amp;lt;/p&amp;gt;}&amp;gt;
        &amp;lt;PostFeed /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
      &amp;lt;Suspense fallback={&amp;lt;p&amp;gt;Loading weather...&amp;lt;/p&amp;gt;}&amp;gt;
        &amp;lt;Weather /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    &amp;lt;/section&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마이그레이션 적용기&lt;/b&gt;&lt;/h2&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  Next.js는 react에서 각 스펙에 따른 마이그레이션 방법을 문서로 제공합니다. StudyFront는 빌드 툴로 &lt;a href=&quot;https://ko.vitejs.dev/guide/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;vite&lt;/span&gt;&lt;/a&gt;를 사용하고 있었기에 기본적인 설정은 &lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/upgrading/from-vite&quot; target=&quot;_self&quot;&gt;&lt;span&gt;해당 문서&lt;/span&gt;&lt;/a&gt;를 참고하여 마이그레이션을 진행하였습니다.&lt;/blockquote&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;recoil 사용하기&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js v13 이후 기본 컴포넌트가 서버 컴포넌트로 적용되기 때문에, 프론트엔드 전역 상태관리를 위한 라이브러리인 recoil을 사용하려면 추가적인 작업을 해주어야 합니다.&lt;br&gt;recoil을 전역으로 사용해주기 위해 모든 컴포넌트의 상위에 &amp;lt;RecoilRoot&amp;gt; 컴포넌트를 선언해 주어야 하는데 RecoilRoot컴포넌트는 내부적으로 react의 context api를 사용하고 있습니다.&lt;br&gt;context api는 프론트엔드 측에서 상태관리를 위해서 사용하는 react의 스펙으로, &lt;b&gt;구독하는&lt;/b&gt; &lt;b&gt;값이 변경될 때마다 구독을 신청한 컴포넌트를 재렌더링&lt;/b&gt; 합니다.&lt;br&gt;이때 서버측에서 렌더링이 진행되는 서버 컴포넌트의 경우 구독하는 값이 변경됨에 따라 컴포넌트를 계속해서 새로 그리게 된다면 서버에 부하를 일으킬 수 있습니다.&lt;br&gt;또한 react에서 서버 컴포넌트를 만든 이유는 서버측의 데이터를 반영하는데에 중점을 가진 컴포넌트를 만들고자 함이었는기 때문에 프론트엔드 측의 사용자와의 상호작용을 위한 값과는 별도로 관리를 위해 서버 컴포넌트에서는 context api를 사용할 수 없도록 만들었습니다.&lt;br&gt;그런 이유로 Next.js에서 recoil을 사용하기 위해서는 클라이언트 컴포넌트로 &amp;lt;RecoilRoot&amp;gt;를 감싸주는 과정을 진행해야 합니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// Next.js에서 클라이언트 컴포넌트를 사용하기 위해서는 컴포넌트 파일 상단에 해당 문구를 추가해야합니다.
'use client';

import React, { ReactNode } from 'react';
import { RecoilRoot } from 'recoil';

const RecoilWrapper = ({ children }: { children: ReactNode }) =&amp;gt; {
  return &amp;lt;RecoilRoot&amp;gt;{children}&amp;lt;/RecoilRoot&amp;gt;;
};

export default RecoilWrapper;
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이후 app 디렉토리 최상단의 layout.tsx에서 &amp;lt;RecoilWrapper&amp;gt; 컴포넌트로 children 속성을 감싸줍니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import React, { ReactNode } from 'react';
import RecoilWrapper from '../RecoilWrapper';

const RootLayout = ({ children }: { children: ReactNode }) =&amp;gt; {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;div id=&quot;root&quot;&amp;gt;
          &amp;lt;RecoilWrapper&amp;gt;
              &amp;lt;Template&amp;gt;{children}&amp;lt;/Template&amp;gt;
          &amp;lt;/RecoilWrapper&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
};

export default RootLayout;
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;styled components 사용하기&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js 공식문서에서는 &lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/styling/css-in-js#styled-components&quot; target=&quot;_self&quot;&gt;&lt;span&gt;styled components를 사용하기 위한 설정&lt;/span&gt;&lt;/a&gt;을 문서로 제공하고 있습니다.&lt;br&gt;해당 문서에서는 SSR을 위한 설정까지는 제공하고 있지만 문서 최상단에 해당 경고가 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Warning: CSS-in-JS libraries which require runtime JavaScript are not currently supported in Server Components. Using CSS-in-JS with newer React features like Server Components and Streaming requires library authors to support the latest version of React, including concurrent rendering.&lt;br&gt;We're working with the React team on upstream APIs to handle CSS and JavaScript assets with support for React Server Components and streaming architecture.&lt;br&gt;&lt;br&gt;경고: 런타임 자바스크립트가 필요한 CSS-in-JS 라이브러리는 현재 서버 컴포넌트에서 지원되지 않습니다. 서버 컴포넌트 및 스트리밍과 같은 최신 React 기능과 함께 CSS-in-JS를 사용하려면 라이브러리 작성자가 동시 렌더링을 포함한 최신 버전의 React를 지원해야 합니다. 저희는 React 서버 컴포넌트 및 스트리밍 아키텍처를 지원하는 CSS 및 JavaScript 에셋을 처리하기 위해 업스트림 API에 대해 React 팀과 협력하고 있습니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;현재 Next.js&lt;b&gt;에서 Server Component를 사용시 CSS-in-JS 라이브러리는 지원하지 않고 있음&lt;/b&gt;을 알 수 있습니다.&lt;br&gt;해당 이슈를 해결하기 위해서 StudyFront에서는 서버 컴포넌트가 필요한 컴포넌트내의 스타일을 별도의 스타일 파일을 생성 후 기본 css를 적용하여 해결하였습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;179&quot; data-origin-height=&quot;305&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nzOzS/btsHgbZVWwU/122efqMlCKDkcrPK0e8qv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nzOzS/btsHgbZVWwU/122efqMlCKDkcrPK0e8qv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nzOzS/btsHgbZVWwU/122efqMlCKDkcrPK0e8qv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnzOzS%2FbtsHgbZVWwU%2F122efqMlCKDkcrPK0e8qv1%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;179&quot; height=&quot;305&quot; data-origin-width=&quot;179&quot; data-origin-height=&quot;305&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;error handling&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js에서는 react의 &lt;a href=&quot;https://react-ko.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Error boundary&lt;/span&gt;&lt;/a&gt;를 활용하여 선언적 방식으로 에러를 처리할 수 있게 합니다.&lt;br&gt;경로 폴더 내 error 파일을 추가하여 ui 렌더링 시 에러가 발생하면 error ui를 노출하게 됩니다.&lt;br&gt;만일 해당 경로 폴더에 error 파일이 없다면, 상위 폴더의 error ui를 노출하게 됩니다.&lt;br&gt;&lt;b&gt;Server Component의 경우 컴포넌트 내에서 발생한 에러는 가장 가까운 error 파일로 전달&lt;/b&gt;되게 됩니다.&lt;br&gt;Error boundary는 &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;비동기 코드 오류 (Promise, setTimeout, setInterval, async/await, etc…)&lt;/li&gt;&lt;li&gt;Error boundary 자체의 오류&lt;/li&gt;&lt;li&gt;렌더링 이외의 모든 오류…&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 부분들 중 비동기 관련 코드는 서버에서 데이터를 받아올 때 필수적으로 사용되기 때문에 StudyFront 프로젝트에서는 컴포넌트 내 &lt;b&gt;state를 사용하여 오류를 error 파일로 전달&lt;/b&gt;하고 있습니다.&lt;/p&gt;&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;'use client';

import React, { ReactNode, useEffect, useState } from 'react';
import { useRouter } from 'Next/navigation';
import { useSetRecoilState } from 'recoil';

import { fetchGetUserInfo } from '../../core/fetcher/user';
import { getStorage } from '../../core/util/function';
import { userState } from '../../store/user';

const PrivateTemplate = ({ children }: { children: ReactNode }) =&amp;gt; {
  const router = useRouter();
  const setUserInfo = useSetRecoilState(userState);

  // 오류 상태
  const [, setError] = useState(null);

  useEffect(() =&amp;gt; {
    if (getStorage('token') === null) {
      router.replace('/?link=login');
    } else {
      (async () =&amp;gt; {
        try {
          const result = await fetchGetUserInfo();
          setUserInfo(result);
        } catch (error: any) {

        // setError를 통해 오류를 던지는 함수를 곧바로 실행
          setError(() =&amp;gt; {
            throw error;
          });
        }
      })();
    }
  }, []);

  return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
};

export default PrivateTemplate;
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;react-router-dom → next/navigation&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;react-router-dom은 react에서 &lt;a href=&quot;https://reactrouter.com/en/main&quot; target=&quot;_self&quot;&gt;&lt;span&gt;라우팅을 적용하기 위한 라이브러리&lt;/span&gt;&lt;/a&gt;입니다.&lt;br&gt;react 프로젝트에서는 클라이언트 측 라우팅을 위해 모든 페이지를 연결하는 route를 선언하고 실행할 수 있게 합니다. 즉 해당 route를 통해 url 경로에 따라 서로 다른 컴포넌트를 노출하도록 설정할 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;// 예시 코드이기 때문에 별도의 import 코드는 추가하지 않았습니다.

const Router = () =&amp;gt; {
  const isLogin = useRecoilValue(loginState);

  const router = createBrowserRouter([
    {
      // url path에 따라 다른 컴포넌트를 노출하도록 설정해 줍니다.
      path: '/',
      element: (
        &amp;lt;ErrorBoundary FallbackComponent={ErrorComponent}&amp;gt;
          &amp;lt;Template&amp;gt;
            &amp;lt;NewHome /&amp;gt;
          &amp;lt;/Template&amp;gt;
        &amp;lt;/ErrorBoundary&amp;gt;
      ),
    },
    {
      path: '/mypage',
      element: isLogin ? (
        &amp;lt;ErrorBoundary FallbackComponent={ErrorComponent}&amp;gt;
          &amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
            &amp;lt;Template&amp;gt;
              &amp;lt;MyPageTemplate /&amp;gt;
            &amp;lt;/Template&amp;gt;
          &amp;lt;/Suspense&amp;gt;
        &amp;lt;/ErrorBoundary&amp;gt;
      ) : (
        &amp;lt;Navigate to=&quot;/&quot; /&amp;gt;
      ),
      children: [
        {
          path: 'my',
          element: &amp;lt;MyInfo /&amp;gt;,
        },
        {
          path: 'life_record',
          element: &amp;lt;LifeRecordHistory /&amp;gt;,
        },
        {
          path: 'result',
          element: &amp;lt;MyPageResultTemplate /&amp;gt;,
          children: [
            {
              path: 'early',
              element: &amp;lt;EarlyResultHistory /&amp;gt;,
            },
            {
              path: 'regular',
              element: &amp;lt;RegularResultHistory /&amp;gt;,
            },
          ],
        },
        {
          path: 'purchase',
          element: &amp;lt;PurchaseHistory /&amp;gt;,
        },
      ],
    },
    {
      path: '/calculator',
      element: (
        &amp;lt;Suspense fallback={&amp;lt;Loading /&amp;gt;}&amp;gt;
          &amp;lt;Template&amp;gt;
            &amp;lt;Calculator /&amp;gt;
          &amp;lt;/Template&amp;gt;
        &amp;lt;/Suspense&amp;gt;
      ),
    },
    {
      path: '*',
      element: &amp;lt;Navigate to=&quot;/&quot; /&amp;gt;,
    },
  ]);

  return &amp;lt;RouterProvider router={router} /&amp;gt;;
};

export default Router;

&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;하지만 Next.js는 내부적으로 폴더 구조 기반의 라우팅을 제공하고 있기 때문에 해당 react-router-dom을 사용할 필요가 없습니다.&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;&lt;p data-ke-size=&quot;size18&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;react-router-dom 라이브러리에는 페이지간 이동(navigating &amp;amp; linking)에 필요한 여러가지 hook을 제공하고 있습니다.&lt;br&gt;react-router-dom&lt;b&gt;의 대표적인 hook&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;b&gt;useNavigate&lt;/b&gt; : 현재 페이지에서 다른 페이지로 이동하는 기능을 제공합니다. (window.history api를 사용하여 경로간 정보(state)를 전달할 수도 있습니다.)&lt;/li&gt;&lt;li&gt;&lt;b&gt;useLocation&lt;/b&gt; : 현재 페이지에서 가지고 있는 정보(현재 경로의 이름, 이전 경로에서 전달해준 정보(state), etc…)들에 접근할 수 있는 기능을 제공합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;useParams&lt;/b&gt; : 동적 라우팅에 사용되는 path parameter 값을 가져올 수 있는 기능을 제공합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;useSearchParams :&lt;/b&gt; &lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/query-param&quot; target=&quot;_self&quot;&gt;&lt;span&gt;query parameter&lt;/span&gt;&lt;/a&gt; 값을 key / value형식으로 가져올 수 있는 기능을 제공합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;react-router-dom을 사용하지 않게 되면서 Next에서는 navigating과 linking을 구현하기 위해 next/navigation ****패키지를 사용하게 되었습니다.&lt;br&gt;이때 react-router-dom에서 제공하는 hook도 사용을 못하기에 프로젝트 전반에 있는 navigating &amp;amp; linking에 관련된 &lt;b&gt;모든 hook들을&lt;/b&gt; next/navigation&lt;b&gt;에서 제공하는 hook으로 교체하는 작업을 진행&lt;/b&gt;했습니다.&lt;/p&gt;&lt;blockquote data-ke-style=&quot;style3&quot;&gt; &lt;a href=&quot;http://%20https://github.com/vercel/next.js/discussions/48426&quot; target=&quot;_blank&quot;&gt;&lt;span&gt; https://github.com/vercel/next.js/discussions/48426&lt;/span&gt;&lt;/a&gt;&lt;br&gt;현재 Next.js에는 navigating &amp;amp; linking을 적용하기 위한 패키지가 두가지 존재합니다.&lt;br&gt;&lt;br&gt;&lt;b&gt;next/router&amp;nbsp;&lt;/b&gt;: Next.js 13버전 이전에 사용되던 패키지로 가볍고 적은 기능을 가지고 있습니다. &lt;b&gt;app router 방식에는 사용할 수 없습니다.&lt;/b&gt;&lt;b&gt;&lt;br&gt;&lt;/b&gt;&lt;br&gt;&lt;b&gt;next/navigation&lt;/b&gt;&amp;nbsp;: Next.js 13버전 이후에 개발된 패키지로 next/router와 비교하여 더 많은 기능을 가지고 있고 &lt;b&gt;modern Next.js 프로젝트에 권장&lt;/b&gt;됩니다.&lt;/blockquote&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;next/navigation의 대표적인 hook&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;b&gt;useRouter&lt;/b&gt; : 페이지 이동, 페이지 새로고침 등 경로와 관련된 기능을 제공합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;useParams&lt;/b&gt; : 동적 라우팅에 사용되는 path parameter 값을 가져올 수 있는 기능을 제공합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;userSearchParams&lt;/b&gt; : get 메서드를 통해 &lt;a href=&quot;https://docs.tosspayments.com/resources/glossary/query-param&quot; target=&quot;_self&quot;&gt;&lt;span&gt;query parameter&lt;/span&gt;&lt;/a&gt; 값의 key를 인자로 받아 value 값을 가져오는 기능을 제공합니다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;usePathname :&lt;/b&gt; 현재 페이지의 경로를 가져오는 기능을 제공합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 교체하는 과정에서 &lt;b&gt;문제가 생겼습니다&lt;/b&gt;.&lt;br&gt;&amp;nbsp;&lt;br&gt;기존 useNavigate hook은 state 매개변수를 통해 현재 경로에서 이동하려는 경로로 &lt;b&gt;데이터를 전달&lt;/b&gt;할 수 있었습니다. 하지만 next/navigation의 useRouter hook은 해당 기능을 제공하지 않고 있었습니다. (개인적인 추측으로는 &lt;b&gt;SSR 방식과 CSR 방식의 컴포넌트를 혼합하여 사용할 수 있는 Next.js의 특성&lt;/b&gt;과 &lt;b&gt;클라이언트 측의 보안과 성능&lt;/b&gt;을 이유로 해당 기능을 제공하지 않는 것으로 생각됩니다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;그런 이유로 기존에 페이지를 이동하며 전달해 주었던 정보를 다른 방식으로 이동한 페이지에서 접근할 수 있도록 수정해 주어야 했습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size18&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;StudyFront에서는 해당 문제를 &lt;a href=&quot;https://developer.mozilla.org/ko/docs/Web/API/Window/sessionStorage&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;b&gt;sessionStorage&lt;/b&gt;&lt;/span&gt;&lt;/a&gt;에 정보를 저장하는 방식과 &lt;b&gt;url query parameter&lt;/b&gt;로 전달하는 방식을 데이터의 특성에 따라 적절히 사용하는 방식으로 해결하였습니다.&lt;/p&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Metadata&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Next.js로 마이그레이션을 진행한 가장 주된 이유입니다.&lt;br&gt;Next.js에서는 Metadata를 적용할 수 있도록 컨벤션을 제공하고 있습니다.&lt;br&gt;메타데이터는 서버에서 생성되는 컴포넌트에 적용할 수 있기 때문에, 적용 전에 &lt;b&gt;Server Component와 Client Component를 섞어서 사용하는 기본적인 패턴&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;Server Component 내부에서 Client Component를 import하여 사용하는 것은 가능합니다.&lt;/li&gt;&lt;li&gt;Client Component 내부에서 Server Component를 import하여 사용하는것은 불가능합니다. =&amp;gt; 사용하기 위해서는 &lt;b&gt;Server Component를 Client Component의 props로 전달하여 사용&lt;/b&gt;해야 합니다.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 패턴을 토대로 각 페이지의 Layout.tsx을 Server Component로 선언함과 동시에 Metadata를 생성해 주었습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// StudyFront 메인 페이지

import React, { ReactNode } from 'react';
import type { Metadata } from 'next';

import Template from '../components/_d_template/Template';
import RecoilWrapper from '../RecoilWrapper';
import GlobalStyle from '../style/GlobalStyle';
import StyledComponentsRegistry from '../style/registry';

export const metadata: Metadata = {
  title: '스터디프론트',
  description:
    '내가 갈 대학 예측을 AI와. 생기부 1인자가 되고 싶다면 스터디프론트에서',
  keywords: [
    '스터디프론트',
    'studyfront',
    '생기부',
    '생활기록부',
    '글자수 세기',
    '입시',
    '입시캐스트',
    '정시 예측',
    '수시 예측',
    '모의고사',
    '입시 커뮤니티',
    '입시 정보',
    '컨설팅',
    '의대',
  ],
  openGraph: {
    title: '입시의 최전선 | 스터디프론트 STUDYFRONT',
    description:
      '내가 갈 대학 예측을 AI와. 생기부 1인자가 되고 싶다면 스터디프론트에서',
    siteName: '입시의 최전선 | 스터디프론트 STUDYFRONT',
    url: '&amp;lt;https://www.studyfront.co.kr&amp;gt;',
    type: 'website',
    images: [
      {
        url: '&amp;lt;https://www.studyfront.co.kr/assets/opengraph_img.png&amp;gt;',
      },
    ],
  },
  alternates: {
    canonical: '&amp;lt;https://www.studyfront.co.kr/&amp;gt;',
  },
};

const RootLayout = ({ children }: { children: ReactNode }) =&amp;gt; {
  return (
    &amp;lt;html lang=&quot;en&quot;&amp;gt;
      &amp;lt;body&amp;gt;
        &amp;lt;div id=&quot;root&quot;&amp;gt;
          &amp;lt;GlobalStyle /&amp;gt;
          &amp;lt;RecoilWrapper&amp;gt;
            &amp;lt;StyledComponentsRegistry&amp;gt;
              &amp;lt;Template&amp;gt;{children}&amp;lt;/Template&amp;gt;
            &amp;lt;/StyledComponentsRegistry&amp;gt;
          &amp;lt;/RecoilWrapper&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/body&amp;gt;
    &amp;lt;/html&amp;gt;
  );
};

export default RootLayout;&lt;/code&gt;&lt;/pre&gt;&lt;div id=&quot;root&quot;&gt;
  &amp;nbsp; 
&lt;/div&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;페이지 경로를 동적으로 받아오는 경우 generateMetadata 메서드를 사용하여 동적으로 Metadata를 생성해 줄 수 있습니다.&lt;/p&gt;&lt;pre class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot;&gt;&lt;code&gt;// 커뮤니티의 입시의 모든 것 개별 게시글 페이지

import React, { ReactNode } from 'react';
import { Metadata } from 'next';

export const generateMetadata = async ({
  params,
}: {
  // url 경로의 path parameter를 통해 게시글의 id를 가져옵니다.
  params: { post_id: string };
}): Promise&amp;lt;Metadata&amp;gt; =&amp;gt; {
  const postId = params.post_id;

  // path parameter를 통해 받아온 id를 사용하여 서버에서 해당 게시글의 정보를 불러옵니다.
  const result = await fetch(
    `${process.env.NEXT_PUBLIC_API_URL}/board/goal/${postId}`,
    {
      headers: {
        'Content-type': 'application/json',
      },
    },
  ).then((res) =&amp;gt; res.json());

  const post = result.result[0];

  // 불러온 게시글을 바탕으로 메타데이터를 정의해 줍니다.
  return {
    title: ` ${post.title} | 스터디프론트 입시의 모든 것`,
    description: `${post.content}`,
    keywords: [
      '고등학교 추천',
      '학원 추천',
      '대학 입시 고민',
      '생기부 수정',
      '입시변화',
      '입결',
      '의대 공대 진로고민',
      '입시의 모든 것',
      `${post.title}`,
    ],
    openGraph: {
      title: `${post.title} | 스터디프론트 입시의 모든 것`,
      description: `${post.content}`,
      siteName: '입시의 모든 것 | 스터디프론트 커뮤니티',
      images: ['https://www.studyfront.co.kr/assets/opengraph_img.png'],
      url: `https://www.studyfront.co.kr/community/goal/detail/${postId}`,
      type: 'website',
    },
    alternates: {
      canonical: `https://www.studyfront.co.kr/community/goal/detail/${postId}`,
    },
  };
};

const GoalDetailLayout = ({ children }: { children: ReactNode }) =&amp;gt; {
  return &amp;lt;&amp;gt;{children}&amp;lt;/&amp;gt;;
};

export default GoalDetailLayout;&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;위의 과정들을 통해 다행히 큰 이슈 없이 StudyFront의 Next.js 마이그레이션을 완료했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Next.js는 업데이트 주기가 빠르고 새로운 기술들을 적극적으로 적용하는 프레임워크입니다.&lt;br&gt;현재 웹 개발의 여러 부분에서 가장 선두적인 행보를 보이는 &lt;a href=&quot;https://vercel.com/home&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Vercel&lt;/span&gt;&lt;/a&gt;이 개발을 주도하고 있기에, 앞으로도 웹 개발 시장을 한동안 주도할 것으로 생각됩니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;react에서 Next.js로 변경하는 과정에서 성가신 부분도 있었고 막막한 부분도 있었지만, 적용하고 나니 프로젝트가 조금 정돈된 부분을 느끼면서 프레임워크의 장점을 조금 체감하게 되었습니다.&lt;br&gt;동시에 프레임워크의 편리한 부분들의 이면에 감춰진 적용된 기술들을 알기 위해 노력하는 것도 프레임워크를 잘 사용하기 위해서는 필요한 과정이라고 생각되었습니다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;출처&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;Next.js 14 변경점&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;a href=&quot;https://yozm.wishket.com/magazine/detail/2324/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://yozm.wishket.com/magazine/detail/2324/&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://nextjs.org/blog/next-14&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://nextjs.org/blog/next-14&lt;/span&gt;&lt;/a&gt;&lt;b&gt;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Next.js 14 업데이트 살펴보기 | 요즘IT&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;지난 10월 26일, Next.js 14가 발표되었습니다. 13 버전 업데이트의 변화가 워낙 커서 그런지 이번에는 상대적으로 변경 사항이 적게 느껴지기도 했습니다. 이번 업데이트에서는 13 버전에서 소개된 A&quot; data-og-host=&quot;yozm.wishket.com&quot; data-og-source-url=&quot;https://yozm.wishket.com/magazine/detail/2324/&quot; data-og-url=&quot;https://yozm.wishket.com/magazine/detail/2324/yozm.wishket.com/magazine/detail/2324/&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/2324/yozm.wishket.com/magazine/detail/2324/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://yozm.wishket.com/magazine/detail/2324/&quot;&gt;&lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Next.js 14 업데이트 살펴보기 | 요즘IT&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;지난 10월 26일, Next.js 14가 발표되었습니다. 13 버전 업데이트의 변화가 워낙 커서 그런지 이번에는 상대적으로 변경 사항이 적게 느껴지기도 했습니다. 이번 업데이트에서는 13 버전에서 소개된 A&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;yozm.wishket.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Next.js 14&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Next.js 14 includes included performance, stability for Server Actions, a new course teaching the App Router, and more.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/blog/next-14&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhgHRS/hyVZlnsyB5/NwY7tUFuidCwjtHkUsDuFK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/cAuJyL/hyV2tjPPsJ/iqa55T8CpPdKvQjuwUoz0K/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot; data-og-url=&quot;https://nextjs.org/blog/next-14&quot;&gt;&lt;a href=&quot;https://nextjs.org/blog/next-14&quot; target=&quot;_blank&quot; data-source-url=&quot;https://nextjs.org/blog/next-14&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhgHRS/hyVZlnsyB5/NwY7tUFuidCwjtHkUsDuFK/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/cAuJyL/hyV2tjPPsJ/iqa55T8CpPdKvQjuwUoz0K/img.png?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Next.js 14&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Next.js 14 includes included performance, stability for Server Actions, a new course teaching the App Router, and more.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;nextjs.org&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;Server Component&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;a href=&quot;https://www.youtube.com/watch?v=TQQPAU21ZUw&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=TQQPAU21ZUw&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://nextjs.org/docs/app/building-your-application/rendering/server-components&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/react-server-components/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://tech.kakaopay.com/post/react-server-components/&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://patterns-dev-kr.github.io/rendering-patterns/react-server-components/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://patterns-dev-kr.github.io/rendering-patterns/react-server-components&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;React Server Components&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;JavaScript번들에 포함되지 않고 SSR을 보완하는 중간 추상화 렌더링 - zero-bundle-size React Server Components 서버 드리븐 멘탈 모델을 통한 모던 UX를 구현하는데 초점이 맞추져 있으며 현재 React…&quot; data-og-host=&quot;patterns-dev-kr.github.io&quot; data-og-source-url=&quot;https://patterns-dev-kr.github.io/rendering-patterns/react-server-components/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ccNKtc/hyV2vvbB6H/d5yKqjJakLpTDd9nqtebX1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/gEJpb/hyVZp4sgLL/tqFwP1KNmq4fKcMatoTZT1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080&quot; data-og-url=&quot;https://patterns-dev-kr.github.io/rendering-patterns/react-server-components/&quot;&gt;&lt;a href=&quot;https://patterns-dev-kr.github.io/rendering-patterns/react-server-components/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://patterns-dev-kr.github.io/rendering-patterns/react-server-components/&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ccNKtc/hyV2vvbB6H/d5yKqjJakLpTDd9nqtebX1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080,https://scrap.kakaocdn.net/dn/gEJpb/hyVZp4sgLL/tqFwP1KNmq4fKcMatoTZT1/img.jpg?width=1920&amp;amp;height=1080&amp;amp;face=0_0_1920_1080')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;React Server Components&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;JavaScript번들에 포함되지 않고 SSR을 보완하는 중간 추상화 렌더링 - zero-bundle-size React Server Components 서버 드리븐 멘탈 모델을 통한 모던 UX를 구현하는데 초점이 맞추져 있으며 현재 React…&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;patterns-dev-kr.github.io&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;React 18: 리액트 서버 컴포넌트 준비하기 | 카카오페이 기술 블로그&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;공식 릴리즈 전인 리액트 서버 컴포넌트에 대해 알아보고 준비해 봅니다.&quot; data-og-host=&quot;tech.kakaopay.com&quot; data-og-source-url=&quot;https://tech.kakaopay.com/post/react-server-components/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c3bU1H/hyV2txm2Fn/sIJpf9sm7xZlnX9tS6tcY1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/bePbt5/hyV2v20UEL/NgVyskEWTwdkQ5fJkOOHjK/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319&quot; data-og-url=&quot;https://tech.kakaopay.com/post/react-server-components/&quot;&gt;&lt;a href=&quot;https://tech.kakaopay.com/post/react-server-components/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://tech.kakaopay.com/post/react-server-components/&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c3bU1H/hyV2txm2Fn/sIJpf9sm7xZlnX9tS6tcY1/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319,https://scrap.kakaocdn.net/dn/bePbt5/hyV2v20UEL/NgVyskEWTwdkQ5fJkOOHjK/img.png?width=480&amp;amp;height=319&amp;amp;face=0_0_480_319')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;React 18: 리액트 서버 컴포넌트 준비하기 | 카카오페이 기술 블로그&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;공식 릴리즈 전인 리액트 서버 컴포넌트에 대해 알아보고 준비해 봅니다.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;tech.kakaopay.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Rendering: Server Components | Next.js&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Learn how you can use React Server Components to render parts of your application on the server.&quot; data-og-host=&quot;nextjs.org&quot; data-og-source-url=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/yAQyD/hyVZqhXC3L/VkOanKYPzPO5QBDQAyaWt1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/iibhC/hyVZr2gtku/MhAx3QDzMyQ0hKNcNiFZxk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/ddL1ph/hyV2w1VnRH/fZZfK1CiRKwX8imVQOxJ71/img.png?width=1600&amp;amp;height=785&amp;amp;face=0_0_1600_785&quot; data-og-url=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot;&gt;&lt;a href=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot; target=&quot;_blank&quot; data-source-url=&quot;https://nextjs.org/docs/app/building-your-application/rendering/server-components&quot;&gt;&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/yAQyD/hyVZqhXC3L/VkOanKYPzPO5QBDQAyaWt1/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/iibhC/hyVZr2gtku/MhAx3QDzMyQ0hKNcNiFZxk/img.png?width=843&amp;amp;height=441&amp;amp;face=0_0_843_441,https://scrap.kakaocdn.net/dn/ddL1ph/hyV2w1VnRH/fZZfK1CiRKwX8imVQOxJ71/img.png?width=1600&amp;amp;height=785&amp;amp;face=0_0_1600_785')&quot;&gt; &lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;Rendering: Server Components | Next.js&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;Learn how you can use React Server Components to render parts of your application on the server.&lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;nextjs.org&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;br&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;a href=&quot;https://yozm.wishket.com/magazine/detail/646/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://yozm.wishket.com/magazine/detail/646/&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;웹 브라우저의 렌더링이란? | 요즘IT&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;우리가 인식하지 못할 뿐, 웹페이지는 미리 만들어진 것을 가져오는 게 아니라 실시간으로 그려지는 것에 가깝습니다. 실시간으로 웹사이트가 그려지는 과정, 이 과정을 웹 브라우저의 렌더링 &quot; data-og-host=&quot;yozm.wishket.com&quot; data-og-source-url=&quot;https://yozm.wishket.com/magazine/detail/646/&quot; data-og-url=&quot;https://yozm.wishket.com/magazine/detail/646/yozm.wishket.com/magazine/detail/646/&quot;&gt;&lt;a href=&quot;https://yozm.wishket.com/magazine/detail/646/yozm.wishket.com/magazine/detail/646/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://yozm.wishket.com/magazine/detail/646/&quot;&gt;&lt;div class=&quot;og-image&quot;&gt;&lt;/div&gt;&lt;div class=&quot;og-text&quot;&gt;&lt;p class=&quot;og-title&quot;&gt;웹 브라우저의 렌더링이란? | 요즘IT&lt;/p&gt;&lt;p class=&quot;og-desc&quot;&gt;우리가 인식하지 못할 뿐, 웹페이지는 미리 만들어진 것을 가져오는 게 아니라 실시간으로 그려지는 것에 가깝습니다. 실시간으로 웹사이트가 그려지는 과정, 이 과정을 웹 브라우저의 렌더링 &lt;/p&gt;&lt;p class=&quot;og-host&quot;&gt;yozm.wishket.com&lt;/p&gt;&lt;/div&gt;&lt;/a&gt;&lt;/figure&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/프로젝트 회고</category>
      <category>CSR</category>
      <category>html streaming</category>
      <category>javascript</category>
      <category>Next.js</category>
      <category>react</category>
      <category>Server Component</category>
      <category>Spa</category>
      <category>SSR</category>
      <category>typescript</category>
      <category>서버컴포넌트</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/117</guid>
      <comments>https://jwookj.tistory.com/117#entry117comment</comments>
      <pubDate>Wed, 8 May 2024 12:13:15 +0900</pubDate>
    </item>
    <item>
      <title>Input type=number에서 알파벳 e와 연산기호가 입력된다...?!</title>
      <link>https://jwookj.tistory.com/116</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발견한 moment!&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 개발 중인 서비스에서는 입시 결과를 예측하기 위해 사용자의 과목 점수를 input으로 입력받고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력될 값은 점수일 뿐이라고 생각해서 input 태그의 type 속성을 number로 지정하고 테스트를 하는 와중에 &lt;b&gt;알파벳 e와 -, + 부호가 입력되는 현상을 발견했습니다...!&lt;/b&gt;  &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;스크린샷 2024-03-22 오후 12.08.07.png&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;64&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nW7SW/btsFYsioSoD/I8croNbv4VM1reDSNx5wD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nW7SW/btsFYsioSoD/I8croNbv4VM1reDSNx5wD1/img.png&quot; data-alt=&quot;&amp;amp;lt;input type=&amp;quot;number&amp;quot;&amp;amp;gt;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nW7SW/btsFYsioSoD/I8croNbv4VM1reDSNx5wD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnW7SW%2FbtsFYsioSoD%2FI8croNbv4VM1reDSNx5wD1%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;326&quot; height=&quot;64&quot; data-filename=&quot;스크린샷 2024-03-22 오후 12.08.07.png&quot; data-origin-width=&quot;326&quot; data-origin-height=&quot;64&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;lt;input type=&quot;number&quot;&amp;gt;&lt;/figcaption&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;왜 그런건지에 대해 조금 서칭을 해보니 input type=number의 경우&amp;nbsp;&lt;b&gt;'부동소수점'&lt;/b&gt;의 표현을 위해 알파벳 &lt;b&gt;e&lt;/b&gt; 혹은 &lt;b&gt;E&lt;/b&gt;가 입력될 수 있도록 &lt;a href=&quot;https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#floating-point-numbers&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;html spec&lt;/a&gt;으로 지정되어 있음을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(한국어 '&lt;b&gt;ㄷ'&lt;/b&gt;도 입력이 됩니다. 키보드 keycode를 통해 입력 받을지에 대한 유무를 판별하고 있는 듯 합니다.)&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;부동소수점에서 e의 의미&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 큰 수를 부동소수점으로 표현할 때 e를 사용하게 되는데, &lt;b&gt;e 뒤에 오는 숫자는 10의 지수&lt;/b&gt;를 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지수가 &lt;b&gt;양수이면 소수점을 지수만큼(e 뒤의 숫자) 오른쪽으로&lt;/b&gt; 이동시키고 &lt;b&gt;음수라면 왼쪽으로 이동&lt;/b&gt;시키면 부동소수점(e)을 사용하지 않았을 경우의 원래 값을 알 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;예시)&lt;br /&gt;12e2 = 12 X 10&amp;sup2; = 1200&lt;br /&gt;1443e-3 = 1443 X 1/10&amp;sup3; = 1.443&lt;/blockquote&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;input태그를 사용하며 이러한 현상을 해결하려면 type속성을 text로 설정한 후에 이벤트 핸들러 내에서 &lt;b&gt;정규식&lt;/b&gt; 혹은 &lt;b&gt;키보드의 keycode&lt;/b&gt;를 통해 숫자만 입력되도록 처리해야 될 것 같습니다.&lt;b&gt;&amp;nbsp; &lt;/b&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;혹시나 type=&quot;number&quot;속성을 사용할 경우 이러한 예외상황을 꼭 고려하여 사용하시길 바랍니다!&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;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://stackoverflow.com/questions/31706611/why-does-the-html-input-with-type-number-allow-the-letter-e-to-be-entered-in&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://stackoverflow.com/questions/31706611/why-does-the-html-input-with-type-number-allow-the-letter-e-to-be-entered-in&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711085390287&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;Why does the HTML input with type &amp;quot;number&amp;quot; allow the letter 'e' to be entered in the field?&quot; data-og-description=&quot;I have the following HTML5 input element: &amp;lt;input type=&amp;quot;number&amp;quot;&amp;gt; Why does this input allow for the character 'e' to be entered in the input field? No other alphabet character is abl...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/31706611/why-does-the-html-input-with-type-number-allow-the-letter-e-to-be-entered-in&quot; data-og-url=&quot;https://stackoverflow.com/questions/31706611/why-does-the-html-input-with-type-number-allow-the-letter-e-to-be-entered-in&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dAJtBh/hyVAGTeqkZ/2C0lGDtanbBJsoDtaQsxw0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/31706611/why-does-the-html-input-with-type-number-allow-the-letter-e-to-be-entered-in&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/31706611/why-does-the-html-input-with-type-number-allow-the-letter-e-to-be-entered-in&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dAJtBh/hyVAGTeqkZ/2C0lGDtanbBJsoDtaQsxw0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&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;Why does the HTML input with type &quot;number&quot; allow the letter 'e' to be entered in the field?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I have the following HTML5 input element: &amp;lt;input type=&quot;number&quot;&amp;gt; Why does this input allow for the character 'e' to be entered in the input field? No other alphabet character is abl...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://hianna.tistory.com/413&quot;&gt;https://hianna.tistory.com/413&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1711085460018&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;[Javascript] input 박스에 숫자만 입력되도록 설정하는 4가지 방법&quot; data-og-description=&quot;input 박스에서 사용자에게 숫자만 입력되도록 설정하는 방법 4가지를 소개합니다. input 박스에 숫자만 입력되도록 설정하는 4가지 방법 1. 'type'을 'number'로 지정하기 2. 입력된 keycode 체크하기 3. on&quot; data-og-host=&quot;hianna.tistory.com&quot; data-og-source-url=&quot;https://hianna.tistory.com/413&quot; data-og-url=&quot;https://hianna.tistory.com/413&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cHa37a/hyVDA4UHc8/6rfzva7yxcu55w6XGlyKrk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/MPtT8/hyVDBv0ZIw/8G0dZOgCWKyjWuGuSrOSxk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/zxTed/hyVDIomaHf/rAS60bRtZB4ktV2W0Fwp5k/img.jpg?width=1936&amp;amp;height=2592&amp;amp;face=0_0_1936_2592&quot;&gt;&lt;a href=&quot;https://hianna.tistory.com/413&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hianna.tistory.com/413&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cHa37a/hyVDA4UHc8/6rfzva7yxcu55w6XGlyKrk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/MPtT8/hyVDBv0ZIw/8G0dZOgCWKyjWuGuSrOSxk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/zxTed/hyVDIomaHf/rAS60bRtZB4ktV2W0Fwp5k/img.jpg?width=1936&amp;amp;height=2592&amp;amp;face=0_0_1936_2592');&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;[Javascript] input 박스에 숫자만 입력되도록 설정하는 4가지 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;input 박스에서 사용자에게 숫자만 입력되도록 설정하는 방법 4가지를 소개합니다. input 박스에 숫자만 입력되도록 설정하는 4가지 방법 1. 'type'을 'number'로 지정하기 2. 입력된 keycode 체크하기 3. on&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hianna.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;</description>
      <category>개발/몰랐던 moment!</category>
      <category>e</category>
      <category>html</category>
      <category>input</category>
      <category>input태그</category>
      <category>type=number</category>
      <category>몰랐던 moment!</category>
      <category>부동소수점</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/116</guid>
      <comments>https://jwookj.tistory.com/116#entry116comment</comments>
      <pubDate>Fri, 22 Mar 2024 14:19:54 +0900</pubDate>
    </item>
    <item>
      <title>2022년 회고록</title>
      <link>https://jwookj.tistory.com/115</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;좀 늦은 2022년 회고입니다.&lt;br&gt;부트캠프에서 공부하던 시간이 벌써 2년이 되었고 개발자로 일한지도 벌써 1년 반정도가 되었네요&lt;br&gt;그리고 2021년의 회고를 적고 2021년 3월 이후는 글을 적지 않았습니다.&lt;br&gt; &lt;br&gt;글을 쓰기 싫었다거나, 딱히 이유가 있어서 그런건 아니고 육아와 일을 병행하다보니 저도 모르게 번아웃이 왔나 봅니다.&lt;br&gt;결과적으로는 2021년 회고 때 적었던 3가지를 하나도 지키지 못한 해였습니다. (  반성하자 진짜로....)&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nBO1T/btrXn7hCB7h/aEhFhISltv8M3J9ovK6F81/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nBO1T/btrXn7hCB7h/aEhFhISltv8M3J9ovK6F81/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nBO1T/btrXn7hCB7h/aEhFhISltv8M3J9ovK6F81/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnBO1T%2FbtrXn7hCB7h%2FaEhFhISltv8M3J9ovK6F81%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;464&quot; height=&quot;177&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://jwookj.tistory.com/108&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;2022.01.02 - [개발/끄적끄적] - 2021년 회고록&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;2021년 회고록&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;매년 그렇지만 2021년도 참 많은 일들이 지나갔다. 먼저 개발자로서 커리어를 시작했다는 점이 인생에 있어서 큰 터닝포인트가 되지 않았나 싶다. 태어나서 처음 다녀보는 직장이었고, 어리숙한 &quot; data-og-host=&quot;jwookj.tistory.com&quot; data-og-source-url=&quot;https://jwookj.tistory.com/108&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bKjj1t/hyRq10j86R/MVQhS7GqdodVM9W7VyVWq0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cefwAI/hyRq0tzId4/ruTNw1K382C5UHu93Sfb6k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot; data-og-url=&quot;https://jwookj.tistory.com/108&quot;&gt;
 &lt;a href=&quot;https://jwookj.tistory.com/108&quot; target=&quot;_blank&quot; data-source-url=&quot;https://jwookj.tistory.com/108&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bKjj1t/hyRq10j86R/MVQhS7GqdodVM9W7VyVWq0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/cefwAI/hyRq0tzId4/ruTNw1K382C5UHu93Sfb6k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;2021년 회고록&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;매년 그렇지만 2021년도 참 많은 일들이 지나갔다. 먼저 개발자로서 커리어를 시작했다는 점이 인생에 있어서 큰 터닝포인트가 되지 않았나 싶다. 태어나서 처음 다녀보는 직장이었고, 어리숙한 &lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;jwookj.tistory.com&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 블로그 정리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;프론트 개발자라면 블로그 정도는 직접 만들어서 관리해야지! 라는 당찬 포부는 깃헙에 외롭게 남아있습니다....  &lt;br&gt;집에 오면 아이와 놀아주고, 그 후에는 무지성으로 쉬고 싶다는 생각만이 들어서 꾸준히 이어나가지 못했던 부분입니다.&lt;br&gt;그래도 중간중간 제가 구상한 부분에 대한 테스트는 해왔기 때문에 기술 스택과 첫 화면은 구현이 된 상태입니다.(Next.js , Notion API) &lt;br&gt;(다만 최근 obsidian이라는 굉장히 탐나는 메모툴을 알게되어서 notion대신 obsidian의 사용을 고민중입니다. )&lt;br&gt;&lt;a href=&quot;https://obsidian.md/&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://obsidian.md/&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;opengraph&quot; data-og-title=&quot;Obsidian&quot; data-ke-align=&quot;alignCenter&quot; data-og-description=&quot;Obsidian: A knowledge base that works on local Markdown files.&quot; data-og-host=&quot;obsidian.md&quot; data-og-source-url=&quot;https://obsidian.md/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/AyWNR/hyRqXp7iWV/iTBVuf4r5ikHVqBAw7gzq1/img.png?width=1500&amp;amp;height=500&amp;amp;face=0_0_1500_500,https://scrap.kakaocdn.net/dn/dqk40t/hyRrbaO5J2/GUzQRuoK9yMK2DfUK8Njik/img.png?width=1500&amp;amp;height=500&amp;amp;face=0_0_1500_500,https://scrap.kakaocdn.net/dn/vgHDO/hyRq01qcno/VJzL5u6UNwffRD22y3FFD1/img.png?width=452&amp;amp;height=452&amp;amp;face=0_0_452_452&quot; data-og-url=&quot;https://obsidian.md/&quot;&gt;
 &lt;a href=&quot;https://obsidian.md/&quot; target=&quot;_blank&quot; data-source-url=&quot;https://obsidian.md/&quot;&gt;
  &lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/AyWNR/hyRqXp7iWV/iTBVuf4r5ikHVqBAw7gzq1/img.png?width=1500&amp;amp;height=500&amp;amp;face=0_0_1500_500,https://scrap.kakaocdn.net/dn/dqk40t/hyRrbaO5J2/GUzQRuoK9yMK2DfUK8Njik/img.png?width=1500&amp;amp;height=500&amp;amp;face=0_0_1500_500,https://scrap.kakaocdn.net/dn/vgHDO/hyRq01qcno/VJzL5u6UNwffRD22y3FFD1/img.png?width=452&amp;amp;height=452&amp;amp;face=0_0_452_452')&quot;&gt; 
  &lt;/div&gt;
  &lt;div class=&quot;og-text&quot;&gt;
   &lt;p class=&quot;og-title&quot;&gt;Obsidian&lt;/p&gt;
   &lt;p class=&quot;og-desc&quot;&gt;Obsidian: A knowledge base that works on local Markdown files.&lt;/p&gt;
   &lt;p class=&quot;og-host&quot;&gt;obsidian.md&lt;/p&gt;
  &lt;/div&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;올해 3월이 되기 전까지는 프로토타입 정도라도 제 블로그를 만들어서 배포해볼 계획입니다! &lt;br&gt;(이번에는 부디 이루어지게 도와주세요...! 개발신님...!  )&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1IHPh/btrXwh36rK9/ncc994G2SCiUZhUKAc2p4K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1IHPh/btrXwh36rK9/ncc994G2SCiUZhUKAc2p4K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1IHPh/btrXwh36rK9/ncc994G2SCiUZhUKAc2p4K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1IHPh%2FbtrXwh36rK9%2Fncc994G2SCiUZhUKAc2p4K%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;525&quot; height=&quot;146&quot; data-origin-width=&quot;345&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 운동&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이 또한 제대로 지키지 못한 부분입니다.&lt;br&gt;하반기 이전에는 '&lt;b&gt;가끔'&lt;/b&gt; 아침에 조깅을 하고 퇴근 후에는 집에서 간단한 맨몸 운동은 '&lt;b&gt;조금씩'&lt;/b&gt; 했었지만 9월 회사 워크숍에서 발을 조금 다친 후에는 거의 운동을 못하다시피 했습니다.&lt;br&gt;그러다보니 몸이 운동을 안하는 환경에 익숙해졌고 결과적으로는 크게 불편하지 않습니다....?  ‍♂️&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ctHZBA/btrXpbjzIpO/7ohNIqwdYsgZicvtzXtsI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ctHZBA/btrXpbjzIpO/7ohNIqwdYsgZicvtzXtsI0/img.png&quot; data-alt=&quot; 해야지...해야지... &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ctHZBA/btrXpbjzIpO/7ohNIqwdYsgZicvtzXtsI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FctHZBA%2FbtrXpbjzIpO%2F7ohNIqwdYsgZicvtzXtsI0%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;288&quot; height=&quot;225&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; 해야지...해야지... &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;하지만 이렇게 살다가는 언젠가는 분명 건강을 소홀히 한 댓가(?)를 분명히 받게 될 것 같았습니다.&lt;br&gt;그래서 PT를 받기로 했고 집앞 헬스장에 등록 했습니다.&lt;br&gt;오랜만에 방문하는 헬스장이라서 조금 어색하지만 빠르게 적응해서 얼른 최상의 컨디션을 유지하는 몸상태로 끌어올리려고 합니다!&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PB8ql/btrXwi9Nz2N/pfRVJICtZk6VKF2cDeZEn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PB8ql/btrXwi9Nz2N/pfRVJICtZk6VKF2cDeZEn1/img.png&quot; data-alt=&quot; ㅠㅠㅠㅠㅠ &quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PB8ql/btrXwi9Nz2N/pfRVJICtZk6VKF2cDeZEn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPB8ql%2FbtrXwi9Nz2N%2FpfRVJICtZk6VKF2cDeZEn1%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;585&quot; height=&quot;324&quot; data-origin-width=&quot;585&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt; ㅠㅠㅠㅠㅠ &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 커리어&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;회사에서는 프론트 개발자로서 일을 하고 있고 프론트 생태계에서의 트랜드를 제가 하고 싶은대로 적용해 볼 수 있는 환경이라서 이것저것 많이 프로젝트에 적용해본 한 해였습니다.&lt;br&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;i&gt;코드 컨벤션 수립 (협업을 위한 lint, prettier 규칙과 git 컨벤션 포함) =&amp;gt; 현재도 필요에 따라 계속 개선중&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;타입스크립트 도입&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;yarn berry 도입&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;Next.js 도입&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;프로젝트시 새로운 툴, 혹은 라이브러리에 대한 설명 간략하게 문서화하여 공유&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;프로젝트에서의 이슈사항과 해결과정 문서화하여 공유&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;성의있는(?) readme..... (제발...)&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;이러한 부분들을 시도했습니다.&lt;br&gt;현재는 주로 사용하던 styled-component 대신 tailwind를 적용해보며 장단점을 확인해보고 있습니다.&lt;br&gt; &lt;br&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;i&gt;부하테스트를 위한 툴 (Jmeter / grasshopper / nGrinder)의 사용법과 결과 해석 방법을 알아보았습니다.&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;외주 프로젝트의 폐쇄망 작업을 위해 리눅스 centOS 서버에서 npm과 node를 설치할 수 없는 끔찍한 상황을 경험했습니다.... (root 계정이요...?)&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;&lt;i&gt;시연을 위한 불필요한 프론트 작업&lt;/i&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;등을 경험했습니다.&lt;br&gt; &lt;br&gt;마지막 3번의 부분은 실제 사용되는 디자인과 기획된 프로덕트가 아닌, 실제 디자인과 기획이 나오기 전까지 현재 제가 속한 팀이 얼만큼 작업을 했는지를 보고하기 위한, 제가 느끼기엔 굉장히 불필요한 프론트 작업이었습니다. &lt;br&gt;이러한 부분을 개선하기 위해 어떻게 프로젝트의 진행 방식을 개선해야 할까를 고민해보았지만, 프로젝트가 진행되는 방식 같은 부분은 저 혼자 바꿀 수 있는 것이 아니란 것도 크게 느낀 한 해였습니다.&lt;br&gt; &lt;br&gt;그리고 이러한 부분들이 제가 앞으로 가져나갈 개발자로서의 커리어를 생각하게 되었습니다.&lt;br&gt;&lt;b&gt;현재 하고 있는 일을 내가 정말 전문성을 갖고 있다고 말할 수 있을 정도로 잘 하고 있는가?&lt;/b&gt;&lt;br&gt;&lt;b&gt;현재 하고 있는 일을 과연 내가 10년 후에도 잘 할 수 있는가?&lt;/b&gt;&lt;br&gt;&lt;b&gt;현재 하고 있는 일에 대해 나는 얼만큼 만족하는가?&lt;/b&gt;&lt;br&gt;이런 커다란 고민에서부터 파생된 작은 고민들까지 많은 생각을 하고 있습니다.&lt;br&gt;처음에는 하기 싫은 고민이었지만 아마 커리어 내내 이런 고민들은 끊이지 않을 것 같기도 하고, 이런 고민이 전혀 없는 것도 도태되었다는 신호인것 같아서 계속 고민해보려고 합니다.&lt;br&gt; &lt;br&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;/li&gt;
 &lt;li&gt; 한가지 부분에 있어서 꾸준히 깊은 부분까지 파는 장인이 되는 것도 굉장히 어려운 부분이고, 충분히 인생을 걸어볼만한 도전이라고 생각되지만 개인적으로 비단 소프트웨어 뿐만이 아닌 여러 부분에서, 각 상황과 환경에 맞는 전체적인 설계를 이루어냈을 때, 더 큰 성취감을 느끼는 것 같습니다.&lt;/li&gt;
 &lt;li&gt; 이러한 부분을 찾기까지는 &lt;b&gt;XL8의 배휘동님&lt;/b&gt;의 글이 많은 도움이 되었습니다.&lt;/li&gt;
 &lt;li&gt; &lt;b&gt;&lt;a href=&quot;https://steady-study.super.site/frontend-engineer-career-roadmap&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://steady-study.super.site/frontend-engineer-career-roadmap&lt;/span&gt;&lt;/a&gt;&lt;/b&gt;&lt;/li&gt;
 &lt;li&gt;&lt;b&gt;하지만 그런 일을 하기에는 현재 경험과 실력이 부족하다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;입니다.&lt;br&gt; &lt;br&gt;여기에서의 실력은 제가 맡은 부분(현재 프론트 웹 개발)만이 아닌 전체적인 프로젝트의 구조의 설계와 필요한 비용, 인력, 기간등을 산출하고 적절하게 분배할 수 있는 능력을 염두에 두고 적었습니다.&lt;br&gt; &lt;br&gt;그리고 결과적으로는 제가 부족한 부분인 경험과 실력을 모두 채우기 위해 일과 학습을 병행할 수 있는 &lt;b&gt;특수대학원에 소프트웨어 전공으로 진학하기로 결정했습니다.  &lt;/b&gt;&lt;br&gt;시간은 내면 되고 노력은 하면 되는 것이기에 고민할 필요 없었지만 비용적인 부분에서 많은 고민을 했습니다.&lt;br&gt;학기마다 등록금이 만만치 않아서 합격발표를 받은 후에도 몇번씩 등록하지 말까라는 고민을 했지만 이후, 더 나은 실력과 더 넓은 시야를 가지고 일을 하는데에 도움이 될 것이라고 생각되어 진학을 결심했습니다.  &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;2022년의 회고는 '2021년의 회고에서 지키지 못한 점을 개선해서 지켜야겠다' 라는 느낌의 회고가 된것 같네요 ㅎㅎ..&lt;br&gt;2023년에는 꼭 위의 다짐을 잘 지켜서 제가 만든 블로그에서 2023년의 회고를 적도록 해야겠습니다. (제발....)&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djXwuN/btrXnKtCEp9/OJqNYi3XyEePpKKQNU59Fk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djXwuN/btrXnKtCEp9/OJqNYi3XyEePpKKQNU59Fk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djXwuN/btrXnKtCEp9/OJqNYi3XyEePpKKQNU59Fk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjXwuN%2FbtrXnKtCEp9%2FOJqNYi3XyEePpKKQNU59Fk%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;291&quot; height=&quot;210&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;/p&gt;</description>
      <category>개발/끄적끄적</category>
      <category>2022년 회고</category>
      <category>반성</category>
      <category>프론트 개발</category>
      <category>회고</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/115</guid>
      <comments>https://jwookj.tistory.com/115#entry115comment</comments>
      <pubDate>Sun, 29 Jan 2023 22:26:43 +0900</pubDate>
    </item>
    <item>
      <title>TIR ##5 [How To Study Design Patterns as a Web Developer]</title>
      <link>https://jwookj.tistory.com/114</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;중간logo120.png&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbRavR/btrxVfqCE13/d309pVkthLOKLOCGecMT51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbRavR/btrxVfqCE13/d309pVkthLOKLOCGecMT51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbRavR/btrxVfqCE13/d309pVkthLOKLOCGecMT51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbRavR%2FbtrxVfqCE13%2Fd309pVkthLOKLOCGecMT51%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;120&quot; height=&quot;132&quot; data-filename=&quot;중간logo120.png&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;학습을 목적으로 Medium에서 읽은 글을 번역한 글입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;번역이 완벽하지 않을 수 있고,&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;직역하기보다는 문맥에 맞추어 자연스럽게 정리하려고 했습니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Sans Demilight', 'Noto Sans KR'; color: #000000;&quot;&gt;오류나, 개선하면 좋을 부분들에 대한 피드백은 언제나 환영합니다. &lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;How&amp;nbsp;To&amp;nbsp;Study&amp;nbsp;Design&amp;nbsp;Patterns&amp;nbsp;as&amp;nbsp;a&amp;nbsp;Web&amp;nbsp;Developer&lt;/b&gt;&lt;/h2&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;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;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;하지만 특정한 부분에서는 서로의 의견이 맞지 않았다. &lt;i&gt;&lt;b&gt;'디자인 패턴 '&lt;/b&gt;&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;나느 이러한 부분에 대해서 다른 동료들과 이야기하던 중, 커뮤니티 내의 많은 시니어 개발자들이, 숙련된 프론트엔드 개발자의 작업에 디자인 패턴이 중요하다는 것을 믿지 않는다는 것을 알고 놀랐다.&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;/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&gt;&amp;nbsp; &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;/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&gt;&amp;nbsp; &lt;/span&gt;(예를 들어 chain of responsibility와 같은) 프레임 워크를 제안할 수 있다.&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&gt;, &lt;/span&gt;실제로&lt;span&gt; &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;패턴들이&lt;span&gt; &lt;/span&gt;적용된&lt;span&gt; &lt;/span&gt;코드를&lt;span&gt; &lt;/span&gt;통해&lt;span&gt; &lt;/span&gt;어떻게&lt;span&gt; &lt;/span&gt;그들이&lt;span&gt; &lt;/span&gt;당신이&lt;span&gt; &lt;/span&gt;매일&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;서비스의&lt;span&gt; &lt;/span&gt;코드퀄리티를&lt;span&gt; &lt;/span&gt;상승시켰는지&lt;span&gt; &lt;/span&gt;알아볼&lt;span&gt; &lt;/span&gt;것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&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; What a Design Pattern really is&lt;/b&gt;&lt;/h3&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;p data-ke-size=&quot;size16&quot;&gt;이러한 23가지의 패턴은 90년도 초기에 Gang of Four라는 이름의 베스트셀러 책으로 엮어져서 나왔고, 빠르게 널리 채택되어서 소프트웨어 개발 분야의 초석이 되었다.&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;우리가 알고 있어야 하는 점은, 책은 출판된 지 30년 정도 되었고, 이 당시는 객체지향 기반의 언어에 굉장히 많은 영향을 받고 있었고, 사람들은 객체지향 프로그래밍에 대한 아이디어에 빠져있었다는 것이다.&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&gt; &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;/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;이 글을 읽고 있는 많은 사람들이 class 개념을 매일 사용하는 것이 아니기 때문에 이러한 개념을 배우는 것이 시간낭비라고 생각할 수 있다.&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;/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;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;&amp;nbsp;&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;p data-ke-size=&quot;size16&quot;&gt;다행히, 디자인 패턴은 클래스나 객체,&lt;span&gt;&amp;nbsp; &lt;/span&gt;혹은 상속 vs 결합에 관한 것이 아니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인&lt;span&gt; &lt;/span&gt;패턴은&lt;span&gt; &lt;/span&gt;복잡한&lt;span&gt; &lt;/span&gt;문제와&lt;span&gt; &lt;/span&gt;문제에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;간단하고&lt;span&gt;, 확장성있고, &lt;/span&gt;높은&lt;span&gt; &lt;/span&gt;재사용성을&lt;span&gt; &lt;/span&gt;가지는&lt;span&gt; &lt;/span&gt;해결책들의&lt;span&gt; &lt;/span&gt;요약본이며&lt;span&gt;, &lt;/span&gt;우리는&lt;span&gt; &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;본질에&lt;span&gt; 집중해야한다.&lt;/span&gt;&lt;/p&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;span&gt;&lt;/span&gt;how to think about patters&lt;/b&gt;&lt;/h3&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;이러한 과정을 거친 후에야 특정한 패러다임의 관점에서 패턴에 대해 이야기할 수 있다.&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;&lt;b&gt;chain of responsibility&lt;/b&gt;&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;&lt;b&gt;What problem does it solve?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 독립된 작업들은 하나의 액션에 대한 응답으로 실행되어야 한다.&lt;span&gt;&amp;nbsp;&lt;/span&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;What is the na&amp;iuml;ve solution?&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;/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;b&gt;What is the solution proposed by the pattern?&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;/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;p data-ke-size=&quot;size16&quot;&gt;현실적인 예&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 배틀 시스템을 가진 브라우저 기반 RPG 게임을 만들고 있다고 가정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP(최소 기능 제품)의 일부로, 먼저 아이템을 이용해 공격 혹은 방어가 가능한 두 캐릭터를 생성할 수 있다.(Ninja , Monster)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직관적으로&lt;span&gt; &lt;/span&gt;생각했을&lt;span&gt; &lt;/span&gt;때의&lt;span&gt; &lt;/span&gt;공격&lt;span&gt; &lt;/span&gt;시스템은&lt;span&gt; 이런식으로 &lt;/span&gt;구현될&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;441&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxZgLR/btrxXMnHrLx/xhSn2JK72ruTwiyHu28lM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxZgLR/btrxXMnHrLx/xhSn2JK72ruTwiyHu28lM1/img.png&quot; data-alt=&quot;출처:&amp;amp;nbsp;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxZgLR/btrxXMnHrLx/xhSn2JK72ruTwiyHu28lM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxZgLR%2FbtrxXMnHrLx%2FxhSn2JK72ruTwiyHu28lM1%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;407&quot; height=&quot;441&quot; data-origin-width=&quot;407&quot; data-origin-height=&quot;441&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;nbsp;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;MVP&lt;/span&gt;승인&lt;span&gt; &lt;/span&gt;이후&lt;span&gt; &lt;/span&gt;프로덕트&lt;span&gt; &lt;/span&gt;팀은&lt;span&gt; &lt;/span&gt;공격을&lt;span&gt; &lt;/span&gt;수정하거나&lt;span&gt; 무효화 할 &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;아이템과&lt;span&gt; &lt;/span&gt;능력을&lt;span&gt; &lt;/span&gt;가진&lt;span&gt; Wizard&lt;/span&gt;와&lt;span&gt; Ghost, 2&lt;/span&gt;개의&lt;span&gt; &lt;/span&gt;새로운&lt;span&gt; &lt;/span&gt;캐릭터를&lt;span&gt; &lt;/span&gt;추가하기로&lt;span&gt; &lt;/span&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6TzaZ/btrxO8FDR0R/p4iIzxz7Ixpd0rx9lbKsd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6TzaZ/btrxO8FDR0R/p4iIzxz7Ixpd0rx9lbKsd0/img.png&quot; data-alt=&quot;출처:&amp;amp;nbsp;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6TzaZ/btrxO8FDR0R/p4iIzxz7Ixpd0rx9lbKsd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6TzaZ%2FbtrxO8FDR0R%2Fp4iIzxz7Ixpd0rx9lbKsd0%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;457&quot; height=&quot;556&quot; data-origin-width=&quot;457&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;nbsp;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&lt;/figcaption&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;/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;(예를 들어, wizard는 ninja처럼 sword를 사용할 수 있지만, ghost는 monster처럼 shield를 들 수 없다.)&lt;span&gt;&amp;nbsp;&lt;/span&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;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;=&amp;gt; 당신의 MVP의 두 번째 사이클이 굉장히 성공적이어서 프로덕트 팀은 최소 10개의 새로운 캐릭터와 각 캐릭터마다 공격을 특정한 방법으로 수정할 수 있는 20개의 아이템을 추가하기로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;na&amp;iuml;ve 한 해결책을 통한 유지보수와 확장은 불가능한 작업임이 증명되었다.&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;b&gt;&lt;span&gt;Applying the pattern &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째로알&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;점은&lt;span&gt;, &lt;/span&gt;공격은&lt;span&gt; &lt;/span&gt;액션이고&lt;span&gt; &lt;/span&gt;아이템은&lt;span&gt; &lt;/span&gt;액션에&lt;span&gt; &lt;/span&gt;대응하는&lt;span&gt; &lt;/span&gt;응답이라는&lt;span&gt; &lt;/span&gt;것이다&lt;span&gt;. &lt;/span&gt;따라서&lt;span&gt;, &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;아이템을&lt;span&gt; &lt;/span&gt;액션&lt;span&gt; &lt;/span&gt;핸들러에&lt;span&gt; &lt;/span&gt;따라&lt;span&gt; &lt;/span&gt;쉽게&lt;span&gt; &lt;/span&gt;분류할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있고&lt;span&gt; &lt;/span&gt;게임&lt;span&gt; &lt;/span&gt;상태에&lt;span&gt; &lt;/span&gt;따라&lt;span&gt; 대기열내에 &lt;/span&gt;모을&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&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;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TuQYy/btrxVGWfz6C/Np6xp670Bkh4j63dn3KOg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TuQYy/btrxVGWfz6C/Np6xp670Bkh4j63dn3KOg1/img.png&quot; data-alt=&quot;출처:&amp;amp;nbsp;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TuQYy/btrxVGWfz6C/Np6xp670Bkh4j63dn3KOg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTuQYy%2FbtrxVGWfz6C%2FNp6xp670Bkh4j63dn3KOg1%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;279&quot; data-origin-width=&quot;567&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처:&amp;nbsp;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&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;Implementing a pattern&lt;/b&gt;&lt;/h3&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Chain of Responsibility(CoR)를 구현해 보기 위해 고전적인 oop방식으로 구현을 해보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만&lt;span&gt; &lt;/span&gt;여기서&lt;span&gt; &lt;/span&gt;구현하고&lt;span&gt; &lt;/span&gt;멈추면&lt;span&gt; 안된다. &lt;/span&gt;클래스와&lt;span&gt; &lt;/span&gt;객체가&lt;span&gt; &lt;/span&gt;사용되지&lt;span&gt; &lt;/span&gt;않는&lt;span&gt; &lt;/span&gt;함수의&lt;span&gt; &lt;/span&gt;합성과&lt;span&gt; &lt;/span&gt;함수의&lt;span&gt; &lt;/span&gt;배열만으로도&lt;span&gt; &lt;/span&gt;충분히&lt;span&gt; &lt;/span&gt;패턴을&lt;span&gt; &lt;/span&gt;구현할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/p&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;OOP implementation&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 CoR의 구현은 작업 큐에 모아놓기 위해 객체 구성을 사용한다. 각 작업은 클래스로 구현되며, 작업 큐의 다음 작업을 가리키는 next라는 레퍼런스를 포함하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로&lt;span&gt;, &lt;/span&gt;레퍼런스는&lt;span&gt; &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;핸들러에&lt;span&gt; &lt;/span&gt;의해&lt;span&gt; &lt;/span&gt;확장될&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;추상&lt;span&gt; &lt;/span&gt;클래스&lt;span&gt; &lt;/span&gt;내에서&lt;span&gt; &lt;/span&gt;캡슐화되어&lt;span&gt; &lt;/span&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;p data-ke-size=&quot;size16&quot;&gt;타입 스크립트로구현한&lt;span&gt; &lt;/span&gt;간단한&lt;span&gt; &lt;/span&gt;예제&lt;/p&gt;
&lt;pre id=&quot;code_1648589241302&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;interface Item {
  new (next: Item): Item;
  next: Item;
  process: (attack: Attack) =&amp;gt; Attack
};
abstract class BaseItem implements Item {
  constructor(public next: Item) {}
  abstract process(attack: Attack): Attack;
}
class Sword extends BaseItem {
  process(attack: Attack) {
    const copy = { ...attack, attack.strength *= 3 };
    return this.next ? this.next.process(copy) : copy;
  }
}
class GhostlyShape extends BaseItem {
  process(attack: Attack) {
    if (attack.isPhysical()) return null;
    else next(attack);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 GoF책에서도 볼 수 있고, 블로그 글에서 가장 많이 찾아볼 수 있는 구현 예시이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Refactorin Guru&lt;/span&gt;에서는&lt;span&gt; &lt;/span&gt;이것은&lt;span&gt; &lt;/span&gt;어떻데&lt;span&gt; &lt;/span&gt;프로젝트에서&lt;span&gt; &lt;/span&gt;활용해야&lt;span&gt; &lt;/span&gt;하는지에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;a href=&quot;https://refactoring.guru/design-patterns/chain-of-responsibility#structure&quot;&gt;&lt;span&gt;좋은&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;예시&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;보여준다&lt;span&gt;.&lt;/span&gt;&lt;/p&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;Closure-based implementation&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoR을 함수 구성으로 사용하기 위해서는 클로저의 개념을 이해해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클로저는 함수 밖에서 함수 스코프를 가지는 변수에 접근할 수 없게 된 후에도, 생성 당시 스코프를 유지하고 있는 변수를 계속 참조하고 있는 (기술? 현상?)이다. &lt;i&gt;(본문에서는 생성 당시 스코프를 유지하고 있는 변수를 계속 참조하고 있는 함수라고 설명)&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;클로저 기반의 CoR은 작업 큐 내 다음 핸들러를 가리키는 래퍼런스(next)를 유지하기 위해서 핸들러의 초기 스코프를 이용한다.&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&gt; 타입스크립트 &lt;/span&gt;구현&lt;span&gt; &lt;/span&gt;예시&lt;/p&gt;
&lt;pre id=&quot;code_1648589419370&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Modifier = (attack: Attack) =&amp;gt; Attack;
type Item = (next?: Modifier) =&amp;gt; Modifier;
const sword: Item = next =&amp;gt; attack =&amp;gt; {
  const copy = { ...attack, strength: attack.strength * 3 };
  return next ? next(copy) : copy;
}
const ghostlyShape: Item = next =&amp;gt; attack =&amp;gt; {
  if (isPhysical(attack) return null;
  else next(attack);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핸들러의 조합을 간단한 compose를 사용하여 처리할 수도 있다.&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&gt;compose&lt;/span&gt;는&lt;span&gt; &lt;/span&gt;배열의&lt;span&gt; &lt;/span&gt;다음&lt;span&gt; &lt;/span&gt;항목으로&lt;span&gt; &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;항목을&lt;span&gt; &lt;/span&gt;호출하며&lt;span&gt;, &lt;/span&gt;이는&lt;span&gt; &lt;/span&gt;다음&lt;span&gt; &lt;/span&gt;변수의&lt;span&gt; &lt;/span&gt;클로저에&lt;span&gt; &lt;/span&gt;의해&lt;span&gt; &lt;/span&gt;유지된다&lt;span&gt;. &lt;/span&gt;항목은&lt;span&gt; next&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;호출하여&lt;span&gt; &lt;/span&gt;실행을&lt;span&gt; &lt;/span&gt;다음&lt;span&gt; &lt;/span&gt;핸들러로&lt;span&gt; &lt;/span&gt;전달하거나&lt;span&gt; &lt;/span&gt;중지할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/p&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;  CoR as an array of functions&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것은 CoR을 구현하는 가장 간단한 방법이다. 그래서 오히려 사람들이 패턴으로 생각하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작업 큐는 순서대로 호출되는 간단한 함수의 배열로 구현될 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1648589479428&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function sword (attack) {
  return { ...attack, strength: attack.strength * 3 };
};
function getAttack() {
  const { attacker, foe } = game.state;
  const modifiers = [
    ...attacker.attackItems,
    ...foe.defenseItems
  ];
  return modifiers.reduce((acc, currentItem) =&amp;gt; {
    return currentItem(acc);
  }, attacker.getAttack());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 접근방법의 장점은 간단하다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점은&lt;span&gt; &lt;/span&gt;복잡하게&lt;span&gt; &lt;/span&gt;추가하지&lt;span&gt; &lt;/span&gt;않으면&lt;span&gt; &lt;/span&gt;실행을&lt;span&gt; &lt;/span&gt;중단하기&lt;span&gt; &lt;/span&gt;어렵다는&lt;span&gt; &lt;/span&gt;것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&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;  Real-world applications&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 현업에서 일을 할 때, 혹은 프로젝트를 진행할 때의 예시를 포함하지 않는다면, 프런트엔드 개발자가 디자인 패턴을 공부해야 한다는 주장은 그저 moot point에 불과하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;moot point : 현재 상황과 관련이 없기 때문에 중요하지 않은 사실&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CoR패턴은 프로젝트를 사용자의 의지에 따라 확장성 있게 만들어준다는 점에서 이미 강력한 도구임을 증명했으며, 몇몇의 성공적인 JS라이브러리들은 이러한 점을 위해 CoR패턴을 사용한다.&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&gt; &lt;/span&gt;웹팩이&lt;span&gt; &lt;/span&gt;각각&lt;span&gt; CoR&lt;/span&gt;과&lt;span&gt; &lt;/span&gt;함수&lt;span&gt; composition, 함수배열을 &lt;/span&gt;사용하여&lt;span&gt; &lt;/span&gt;어떻게&lt;span&gt; &lt;/span&gt;개발자들이&lt;span&gt; &lt;/span&gt;기능을&lt;span&gt; &lt;/span&gt;확장하여&lt;span&gt; &lt;/span&gt;사용할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있도록&lt;span&gt; &lt;/span&gt;만들었는지&lt;span&gt; &lt;/span&gt;살펴보자&lt;/p&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;Redux Middleware&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리덕스 문서는 dispatch 함수의 응답 내에서 실행되는, 개발자가 정의한 동작을 설치할 수 있도록 applyMiddleware API에 대해 설명되어 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 middleware는 리덕스 API에 접근 가능하고, 위에서 보았던 클로저 기반 CoR구현과 정확히 같은 방법을 사용하는 핸들러를 반환하는 factory function에 의해 생성된다.&lt;/p&gt;
&lt;pre id=&quot;code_1648589632532&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function actionLogger({ getState }) {
  return next =&amp;gt; action =&amp;gt; {
    console.group(`Action of type ${action.type}`);
    console.log('action:', JSON.stringify(action, null, 1));
    console.log('prev state:', getState());
    console.groupEnd();
    return next(action);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 리덕스는 접근 가능한 API를 통해 factory function을 호출한 후에 compose를 사용하여 반환된 핸들러들을 모아놓는다. (source code&amp;nbsp;&lt;a href=&quot;https://github.com/reduxjs/redux/blob/master/src/applyMiddleware.ts#L80-L81&quot;&gt;&lt;span&gt;here&lt;/span&gt;&lt;/a&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;a href=&quot;https://github.com/svrcekmichal/redux-axios-middleware&quot;&gt;&lt;span&gt;action-based fetching solutions&lt;/span&gt;&lt;/a&gt;부터&amp;nbsp;&lt;a href=&quot;https://github.com/reduxjs/redux-thunk&quot;&gt;&lt;span&gt;function action types&lt;/span&gt;&lt;/a&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;&amp;nbsp;&lt;a href=&quot;https://redux.js.org/introduction/ecosystem#middleware&quot;&gt;&lt;span&gt;Redux Ecosystem page&lt;/span&gt;&lt;/a&gt;&lt;span&gt;에서&lt;/span&gt; &lt;span&gt;리덕스에서&lt;/span&gt; &lt;span&gt;제공하는&lt;/span&gt; CoR&lt;span&gt;이&lt;/span&gt; &lt;span&gt;적용된&lt;/span&gt; &lt;span&gt;커스텀&lt;/span&gt; &lt;span&gt;기능들에&lt;/span&gt; &lt;span&gt;대한&lt;/span&gt; &lt;span&gt;리스트를&lt;/span&gt; &lt;span&gt;볼&lt;/span&gt; &lt;span&gt;수&lt;/span&gt; &lt;span&gt;있다&lt;/span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&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;Webpack loader&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹팩의 핵심기능은 규칙(test와 loader들을 사용하여 어떻게 소스파일을 다룰 것인지를 명시한 객체)으로 구현된다.&lt;span&gt;&amp;nbsp;&lt;/span&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;그 후, 일치 규칙에 의해 명시된 loader chain이 해당 소스에 적용되어 webpack이 임의의 언어를 변환하여 JS로 출력할 수 있게 된다.&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&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648589686470&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          {
            loader: 'css-loader',
            options: { modules: true }
          },
          { loader: 'sass-loader' }
        ]
      }
    ]
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;loader chain들은 reverse order of declaration이라고 하는 CoR의 함수의 배열을 사용하여 구현되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 loader는 체인에 존재하는 다음 로더의 출력으로 인해 실행되고 마지막 순서의 로더는 유효한 자바스크립트 코드를 제공할 것으로 예상한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체인 내에서 실행을 중지하거나 다음 노드로 넘어가기 위해 각 로더들은 this.emitError, this.callback과 같이 this객체에 바인딩되어있는 함수를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을&lt;span&gt; &lt;/span&gt;반환하는&lt;span&gt; &lt;/span&gt;방식으로도&lt;span&gt; &lt;/span&gt;동일한&lt;span&gt; &lt;/span&gt;작업을&lt;span&gt; &lt;/span&gt;실행할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/p&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;Other patterns&lt;/b&gt;&lt;/h3&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;p data-ke-size=&quot;size16&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;i&gt;React는 Composite(합성) 패턴을 활용하여 렌더링 할 수 있는 작은 ui 단위들을 이용해, 복잡한 ui를 구현한다.&amp;nbsp;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;React-Redux는 React의 컴포넌트들을 Redux store의 데이터와 연결하기 위해 dercorator패턴을 사용한다.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;Vue는 상태 관리에 따른 ui의 rerender를 위해 Observer패턴을 광범위하게 사용한다.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;Babel은&amp;nbsp; plugin들이 소스코드를 검사하고 수정할 수 있도록 Visitor패턴을 사용한다.&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디자인 패턴의 사용 예는 비단, 라이브러리에만 국한되지 않으며 당신의 애플리케이션에서도 이 글에서 논의된 디자인 패턴들의 개념을 사용하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;동일한&lt;span&gt; &lt;/span&gt;결과를&lt;span&gt; &lt;/span&gt;얻을&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;디자인 패턴에 대한 깊은 이해는 소프트웨어 개발자가 가질 수 있는 가장 강력한 도구 중 하나이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 귀중한 개념들이 오랫동안 객체지향적 설계를 설명하는 관점에서만 사용되었기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&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;p data-ke-size=&quot;size16&quot;&gt;다행히, 패턴들은 일반적인 상황에서 마주치는 문제의 해결책에 대해 설명해주기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&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;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;p data-ke-size=&quot;size16&quot;&gt;그러니 클래스 다이어그램을 통해 가르치는 패턴을 발견한다면 포기하지 말자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&lt;span&gt; &lt;/span&gt;글에서&lt;span&gt; &lt;/span&gt;설명된&lt;span&gt; &lt;/span&gt;과정을&lt;span&gt; &lt;/span&gt;적용한다면&lt;span&gt; &lt;/span&gt;당신은&lt;span&gt; &lt;/span&gt;어떤&lt;span&gt; &lt;/span&gt;패턴이라도&lt;span&gt; 마스터 하고 &lt;/span&gt;당신의&lt;span&gt; &lt;/span&gt;무기고에서&lt;span&gt; &lt;/span&gt;강력한&lt;span&gt; &lt;/span&gt;무기를&lt;span&gt; &lt;/span&gt;만들&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;b&gt;&lt;a href=&quot;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot;&gt;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1648589878122&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;How To Study Design Patterns as a Web Developer&quot; data-og-description=&quot;A framework for describing Design Patterns in a paradigm-independent fashion&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot; data-og-url=&quot;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Ks6PW/hyNR6QIZPg/QSa0s7OdJmj5KrQ7Gn3KE1/img.jpg?width=1000&amp;amp;height=773&amp;amp;face=0_0_1000_773,https://scrap.kakaocdn.net/dn/uoDHd/hyNR19ISs1/cj3bIfzcJtCvmHaiZ7bdYk/img.jpg?width=1000&amp;amp;height=773&amp;amp;face=0_0_1000_773,https://scrap.kakaocdn.net/dn/cdDgTg/hyNQ2vDH1n/iCCrHlk4AfXOMQ3q8r0bqk/img.png?width=457&amp;amp;height=556&amp;amp;face=347_78_372_105&quot;&gt;&lt;a href=&quot;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/arctouch/how-to-study-design-patterns-as-a-web-developer-e54284958e48&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Ks6PW/hyNR6QIZPg/QSa0s7OdJmj5KrQ7Gn3KE1/img.jpg?width=1000&amp;amp;height=773&amp;amp;face=0_0_1000_773,https://scrap.kakaocdn.net/dn/uoDHd/hyNR19ISs1/cj3bIfzcJtCvmHaiZ7bdYk/img.jpg?width=1000&amp;amp;height=773&amp;amp;face=0_0_1000_773,https://scrap.kakaocdn.net/dn/cdDgTg/hyNQ2vDH1n/iCCrHlk4AfXOMQ3q8r0bqk/img.png?width=457&amp;amp;height=556&amp;amp;face=347_78_372_105');&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;How To Study Design Patterns as a Web Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;A framework for describing Design Patterns in a paradigm-independent fashion&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;</description>
      <category>개발/Medium</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/114</guid>
      <comments>https://jwookj.tistory.com/114#entry114comment</comments>
      <pubDate>Wed, 30 Mar 2022 06:40:54 +0900</pubDate>
    </item>
    <item>
      <title>타입스크립트 프로그래밍 2주차</title>
      <link>https://jwookj.tistory.com/113</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;타입스크립트 프로그래밍 2주차&amp;nbsp;&lt;/b&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;b&gt;함수&lt;/b&gt;&lt;/h2&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;i&gt;&lt;b&gt; 매개변수의 타입은 추론하지 않는다.&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;  매개변수의 타입은 개발자가 직접 명시해 주어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645788687004&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//매개변수 타입 지정
const human = (name: string, age: number) =&amp;gt; {
	return `name is ${name}, age is ${age}`
}

console.log(human(&quot;Jay&quot;, 12))
//name is Jay, age is 12

//매개변수 타입 미지정
const human = (name, age) =&amp;gt; {
	return `name is ${name}, age is ${age}`
}

console.log(human(&quot;Jay&quot;, 12))
//Parameter 'name' implicitly has an 'any' type.
//Parameter 'age' implicitly has an 'any' type.

//매개변수와 반환 타입 지정
const human = (name: string, age: number): string =&amp;gt; {
	return `name is ${name}, age is ${age}`
}

console.log(human(&quot;Jay&quot;, 12))
//name is Jay, age is 12&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;* 본 책에서는 반환 타입을 명시할 수 있으나 타입스크립트의 타입추론은 강력한 기능이기 때문에 특별한 경우가 아닌 이상 개발자가 굳이 명시할 필요는 없다고 설명하고 있다.&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;&lt;i&gt;* 타입스크립트에서 함수를 함수 생성자 (new Function()) 통해 함수를 선언하는 것은 매개변수 타입과 반환 타입을 지정해 줄 수 없기 때문에 안전하지 않다.&lt;/i&gt;&lt;/p&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;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;  기본값을 가지게 되는 매개변수는 &lt;b&gt;=&lt;/b&gt; 연산자를 통해 기본 매개변수로 명시하여 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  타입스크립트에서의 arguments 객체는 인수들의 타입을 모두 any로 취급하기 때문에 안전하지 않다. &lt;b&gt;arguments객체 대신 전개구문(Spread Syntax)&lt;/b&gt;을&amp;nbsp;사용하여 명시해 준다.&lt;/p&gt;
&lt;pre id=&quot;code_1645790479685&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//선택적 매개변수
let sum = (a:number, b:number, c?:number) =&amp;gt; {
 return c ?  a + b + c : a + b
}
console.log(sum(1, 2, 3))
//6
console.log(sum(1, 2))
//3

//선택적 매개변수를 명시하지 않고 함수의 인수를 모자르게 입력했을 경우
let sum = (a:number, b:number, c:number) =&amp;gt; {
 return c ?  a + b + c : a + b
}
console.log(sum(1, 2))
//Expected 3 arguments, but got 2.

//기본 매개변수
let sum = (a:number, b:number, c = 5) =&amp;gt; {
 return c ?  a + b + c : a + b
}
console.log(sum(1, 2))
//8
console.log(sum(1, 2, 3))
//6

//나머지 매개변수
let sum = (...array : number[]) =&amp;gt; {
 return array.reduce((total, n) =&amp;gt; total + n, 0) 
}

console.log(sum(1, 2, 3))
//6&lt;/code&gt;&lt;/pre&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;* 기본 매개변수 사용시에도 타입을 명시해 줄 수 있지만&amp;nbsp; (c:number = 3)&lt;i&gt;타입스크립트의 타입추론을 통해 기본값의 타입으로 매개변수의 타입을 지정해 주기 때문에 &lt;b&gt;특별한 경우가 아닌 이상 따로 타입을 명시해 줄 필요가 없다&lt;/b&gt;.&lt;/i&gt;&lt;/i&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;  타입 수준 코드로&lt;b&gt; 함수의 전체 타입&lt;/b&gt;을 지정할 때 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1645791281464&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//단축 시그니처
type Greet = (name: string) =&amp;gt; string

//전체 시그니처
type Greet = {
 (name: string): string
}

const greet: Greet = (name) =&amp;gt; `Hi ${name}!`
console.log(greet(&quot;Jay&quot;))
//Hi Jay!&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;i&gt;함수의 타입(Greet)에 이미 매개변수의 타입과 반환 타입을 지정해 주었으므로 함수를 선언할 때 다시 타입을 지정할 필요가 없다.&lt;/i&gt;&lt;/p&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;p data-ke-size=&quot;size16&quot;&gt;  호출 시그니처가 여러 개인 함수&lt;/p&gt;
&lt;pre id=&quot;code_1645791754685&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Reservation이라는 타입을 선언했다는 가정하에 Reserve 타입 선언
//타입 시그니처
type Reserve = {
    //왕복 예약
    (from: Date, to: Date, destination: string): Reservation
    //편도 예약
    (from: Date, destination: string): Reservation
}

//구현 시그니처
let reserve: Reserve = (
    from: Date,
    toOrDestination: Date | string,
    destination?: string
) =&amp;gt; {...}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;i&gt;오버로드 시그니처를 구현할 경우에는 오버로드 시그니처를 &lt;b&gt;직접 결합하여 선언&lt;/b&gt;해 주어야 한다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;* 오버로드 시그니처를 구현할 경우에는 가능한한 타입을 특정하는 것이 좋다. (any 대신 stirng | Date등)&lt;/i&gt;&lt;/p&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;다형성(generic)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;함수의 매개변수 혹은 반환값의 타입을 지정할 수 없는 상황에는 &lt;b&gt;제네릭 타입&lt;/b&gt;을 사용하여 해결할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  제네릭 타입은 꺽쇠를 사용하여 표현해 준다. &lt;b&gt;&amp;lt;T&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1645792324830&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;type Filter = {
    &amp;lt;T&amp;gt;(array: T[], f: (item: T) =&amp;gt; boolean): T[]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* &lt;i&gt;위의 예에서 Filter라는 타입으로 구현한 함수를 호출할 때 타입스크립트는&amp;nbsp;&lt;b&gt;인수로 받은 배열의 요소들의 타입을 추론하여 해당 타입을 제네릭 타입 &amp;lt;T&amp;gt;에 할당한다&lt;/b&gt;.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;* 제네릭 타입의 변수는 꼭 T로 하지 않아도 상관없다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/Typescript</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/113</guid>
      <comments>https://jwookj.tistory.com/113#entry113comment</comments>
      <pubDate>Fri, 25 Feb 2022 21:37:12 +0900</pubDate>
    </item>
    <item>
      <title>프린터</title>
      <link>https://jwookj.tistory.com/112</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIDcJl/btrsAb8Kxl1/oqRVSJuoA4MtdF6H060j80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIDcJl/btrsAb8Kxl1/oqRVSJuoA4MtdF6H060j80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIDcJl/btrsAb8Kxl1/oqRVSJuoA4MtdF6H060j80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIDcJl%2FbtrsAb8Kxl1%2FoqRVSJuoA4MtdF6H060j80%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;499&quot; height=&quot;174&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/p&gt;
&lt;div&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;1. 인쇄 대기목록의 가장 앞에 있는 문서(J)를 대기목록에서 꺼냅니다.
2. 나머지 인쇄 대기목록에서 J보다 중요도가 높은 문서가 한 개라도 존재하면 J를 대기목록의 가장 마지막에 넣습니다.
3. 그렇지 않으면 J를 인쇄합니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 4개의 문서(A, B, C, D)가 순서대로 인쇄 대기목록에 있고 중요도가 2 1 3 2 라면 C D A B 순으로 인쇄하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 알고 싶습니다. 위의 예에서 C는 1번째로, A는 3번째로 인쇄됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 대기목록에 있는 문서의 중요도가 순서대로 담긴 배열 priorities와 내가 인쇄를 요청한 문서가 현재 대기목록의 어떤 위치에 있는지를 알려주는 location이 매개변수로 주어질 때, 내가 인쇄를 요청한 문서가 몇 번째로 인쇄되는지 return 하도록 solution 함수를 작성해주세요.&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;현재 대기목록에는 1개 이상 100개 이하의 문서가 있습니다.&lt;/li&gt;
&lt;li&gt;인쇄 작업의 중요도는 1~9로 표현하며 숫자가 클수록 중요하다는 뜻입니다.&lt;/li&gt;
&lt;li&gt;location은 0 이상 (현재 대기목록에 있는 작업 수 - 1) 이하의 값을 가지며 대기목록의 가장 앞에 있으면 0, 두 번째에 있으면 1로 표현합니다.&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;prioritieslocationreturn&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[2, 1, 3, 2]&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[1, 1, 9, 1, 1, 1]&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에 나온 예와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 #2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개의 문서(A, B, C, D, E, F)가 인쇄 대기목록에 있고 중요도가 1 1 9 1 1 1 이므로 C D E F A B 순으로 인쇄합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;#나의 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1644237004632&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const solution = (priorities, location) =&amp;gt; {
  //출력 횟수
  let count = 1;
  while (priorities.length !== 0) {
    //리스트내 가장 큰 숫자의 인덱스
    let bigIndex = priorities.indexOf(Math.max(...priorities));
    //location과 bigIndex가 같을 경우 바로 count값 return
    if (location === bigIndex) {
      return count;
    }
    let length = priorities.length;
    //배열의 첫 인자부터 bigIndex앞의 인자까지 배열을 자른다
    let moveBackArray = priorities.splice(
      0,
      priorities.indexOf(Math.max(...priorities))
    );
    //자른 배열을 뒤로 붙임
    priorities = [...priorities, ...moveBackArray];
    //배열의 변경에 따른 location 변수의 변경
    location &amp;lt; bigIndex
      ? (location += length - bigIndex)
      : (location -= bigIndex);
    //변경된 배열에서 가장 앞의 인자(가장 큰 값을 가진 인자)제거
    priorities.shift();
    //배열의 길이가 줄었으므로 location도 1 줄어듬
    location -= 1;
    //가장 큰 수가 하나 출력됬으므로 count 증가
    count++;
  }
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트/Javascript</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/112</guid>
      <comments>https://jwookj.tistory.com/112#entry112comment</comments>
      <pubDate>Mon, 7 Feb 2022 21:30:26 +0900</pubDate>
    </item>
    <item>
      <title>기능개발</title>
      <link>https://jwookj.tistory.com/111</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UxOzP/btrsMz1zBOd/7rjVi9skWYDA0fc5lgk53K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UxOzP/btrsMz1zBOd/7rjVi9skWYDA0fc5lgk53K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UxOzP/btrsMz1zBOd/7rjVi9skWYDA0fc5lgk53K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUxOzP%2FbtrsMz1zBOd%2F7rjVi9skWYDA0fc5lgk53K%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;499&quot; height=&quot;174&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;프로그래머스 팀에서는 기능 개선 작업을 수행 중입니다. 각 기능은 진도가 100%일 때 서비스에 반영할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 각 기능의 개발속도는 모두 다르기 때문에 뒤에 있는 기능이 앞에 있는 기능보다 먼저 개발될 수 있고, 이때 뒤에 있는 기능은 앞에 있는 기능이 배포될 때 함께 배포됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 배포되어야 하는 순서대로 작업의 진도가 적힌 정수 배열 progresses와 각 작업의 개발 속도가 적힌 정수 배열 speeds가 주어질 때 각 배포마다 몇 개의 기능이 배포되는지를 return 하도록 solution 함수를 완성하세요.&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;작업의 개수(progresses, speeds배열의 길이)는 100개 이하입니다.&lt;/li&gt;
&lt;li&gt;작업 진도는 100 미만의 자연수입니다.&lt;/li&gt;
&lt;li&gt;작업 속도는 100 이하의 자연수입니다.&lt;/li&gt;
&lt;li&gt;배포는 하루에 한 번만 할 수 있으며, 하루의 끝에 이루어진다고 가정합니다. 예를 들어 진도율이 95%인 작업의 개발 속도가 하루에 4%라면 배포는 2일 뒤에 이루어집니다.&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;progressesspeedsreturn&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[93, 30, 55]&lt;/td&gt;
&lt;td&gt;[1, 30, 5]&lt;/td&gt;
&lt;td&gt;[2, 1]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;[95, 90, 99, 99, 80, 99]&lt;/td&gt;
&lt;td&gt;[1, 1, 1, 1, 1, 1]&lt;/td&gt;
&lt;td&gt;[1, 3, 2]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #1&lt;br /&gt;첫 번째 기능은 93% 완료되어 있고 하루에 1%씩 작업이 가능하므로 7일간 작업 후 배포가 가능합니다.&lt;br /&gt;두 번째 기능은 30%가 완료되어 있고 하루에 30%씩 작업이 가능하므로 3일간 작업 후 배포가 가능합니다. 하지만 이전 첫 번째 기능이 아직 완성된 상태가 아니기 때문에 첫 번째 기능이 배포되는 7일째 배포됩니다.&lt;br /&gt;세 번째 기능은 55%가 완료되어 있고 하루에 5%씩 작업이 가능하므로 9일간 작업 후 배포가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 7일째에 2개의 기능, 9일째에 1개의 기능이 배포됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 #2&lt;br /&gt;모든 기능이 하루에 1%씩 작업이 가능하므로, 작업이 끝나기까지 남은 일수는 각각 5일, 10일, 1일, 1일, 20일, 1일입니다. 어떤 기능이 먼저 완성되었더라도 앞에 있는 모든 기능이 완성되지 않으면 배포가 불가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 5일째에 1개의 기능, 10일째에 3개의 기능, 20일째에 2개의 기능이 배포됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;#나의 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1644235603288&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//maxDays의 값을 반환하는 함수
const setMaxDays = (index, progressArray, speedArray) =&amp;gt; {
  return Math.ceil((100 - progressArray[index]) / speedArray[index]);
};

const solution = (progresses, speeds) =&amp;gt; {
  //전역 변수 초기화
  let answer = [];
  let funcCount = 1;
  let maxDays = setMaxDays(0, progresses, speeds);
  for (let i = 1; i &amp;lt; progresses.length; i++) {
    const currentDays = setMaxDays(i, progresses, speeds);
    //maxDays보다 걸리는 기간이 적을 경우
    if (maxDays &amp;gt;= currentDays) {
      funcCount++;
      //maxDays보다 걸리는 기간이 클 경우
    } else {
      answer.push(funcCount);
      funcCount = 1;
      maxDays = currentDays;
    }
    //작업목록의 마지막 인자일 경우
    if (i === progresses.length - 1) {
      answer.push(funcCount);
    }
  }
  return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트/Javascript</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/111</guid>
      <comments>https://jwookj.tistory.com/111#entry111comment</comments>
      <pubDate>Mon, 7 Feb 2022 21:24:37 +0900</pubDate>
    </item>
    <item>
      <title>베스트 앨범</title>
      <link>https://jwookj.tistory.com/110</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b03GcE/btrsAcmbqoZ/fvUElhyafuxD4kFUxiKRm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b03GcE/btrsAcmbqoZ/fvUElhyafuxD4kFUxiKRm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b03GcE/btrsAcmbqoZ/fvUElhyafuxD4kFUxiKRm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb03GcE%2FbtrsAcmbqoZ%2FfvUElhyafuxD4kFUxiKRm1%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;499&quot; height=&quot;174&quot; data-filename=&quot;codingTest.png&quot; data-origin-width=&quot;499&quot; data-origin-height=&quot;174&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/p&gt;
&lt;ol 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;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노래의 장르를 나타내는 문자열 배열 genres와 노래별 재생 횟수를 나타내는 정수 배열 plays가 주어질 때, 베스트 앨범에 들어갈 노래의 고유 번호를 순서대로 return 하도록 solution 함수를 완성하세요.&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;genres[i]는 고유번호가 i인 노래의 장르입니다.&lt;/li&gt;
&lt;li&gt;plays[i]는 고유번호가 i인 노래가 재생된 횟수입니다.&lt;/li&gt;
&lt;li&gt;genres와 plays의 길이는 같으며, 이는 1 이상 10,000 이하입니다.&lt;/li&gt;
&lt;li&gt;장르 종류는 100개 미만입니다.&lt;/li&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;genresplaysreturn&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;[&quot;classic&quot;, &quot;pop&quot;, &quot;classic&quot;, &quot;classic&quot;, &quot;pop&quot;]&lt;/td&gt;
&lt;td&gt;[500, 600, 150, 800, 2500]&lt;/td&gt;
&lt;td&gt;[4, 1, 3, 0]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입출력 예 설명&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;classic 장르는 1,450회 재생되었으며, classic 노래는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고유 번호 3: 800회 재생&lt;/li&gt;
&lt;li&gt;고유 번호 0: 500회 재생&lt;/li&gt;
&lt;li&gt;고유 번호 2: 150회 재생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pop 장르는 3,100회 재생되었으며, pop 노래는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고유 번호 4: 2,500회 재생&lt;/li&gt;
&lt;li&gt;고유 번호 1: 600회 재생&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 pop 장르의 [4, 1]번 노래를 먼저, classic 장르의 [3, 0]번 노래를 그다음에 수록합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;#나의 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1644234808457&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const solution = (genres, plays) =&amp;gt; {
  let answer = [];
  //주어진 배열들을 map구조로 정리할 변수 선언
  const genreMap = new Map();
  //내림차순 정렬에 사용될 callback함수 선언
  const sortByDescending = (a, b) =&amp;gt; {
    return b - a;
  };

  //선언한 genreMap에 key(genre) value(total, lists)값으로 할당
  genres.map((_genre, _idx) =&amp;gt; {
    //각 음악의 고유번호, 재생횟수 배열형태로 선언
    const eachSongInfo = [_idx, plays[_idx]];
    //genreMap내 해당 genre가 key값으로 존재할 경우 total과 lists값 수정 존재하지 않을 경우 새로 생성
    if (!genreMap.has(_genre)) {
      genreMap.set(_genre, { total: plays[_idx], lists: [eachSongInfo] });
    } else {
      //genreMap내 존재하는 genre의 값을 eachGenre 변수에 할당
      const eachGenre = genreMap.get(_genre);
      genreMap.set(_genre, {
        total: eachGenre.total + plays[_idx],
        lists: [...eachGenre.lists, eachSongInfo],
      });
    }
  });

  //정리된 genreMap을 배열형태로 변환
  const genreArray = Array.from(genreMap);

  //total값 내림차순 정렬
  genreArray.sort((a, b) =&amp;gt; sortByDescending(a[1].total, b[1].total));

  //각 장르별 lists의 plays값들을 내림차순으로 정렬
  for (let i = 0; i &amp;lt; genreArray.length; i++) {
    genreArray[i][1].lists.sort((a, b) =&amp;gt; sortByDescending(a[1], b[1]));
    //lists의 plays값이 높은 최대 2개의 고유번호값을 answer배열에 추가
    if (genreArray[i][1].lists.length &amp;lt; 2) {
      answer.push(genreArray[i][1].lists[0][0]);
    } else {
      for (let j = 0; j &amp;lt; 2; j++) {
        answer.push(genreArray[i][1].lists[j][0]);
      }
    }
  }
  return answer;
};&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;#다른 풀이&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1644235295177&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;function solution(genres, plays) {
    var dic = {};
    genres.forEach((t,i)=&amp;gt; {
        dic[t] = dic[t] ?  dic[t] + plays[i] :plays[i];        
    });

    var dupDic = {};
    return genres          
          .map((t,i)=&amp;gt; ({genre : t, count:plays[i] , index:i}))
          .sort((a,b)=&amp;gt;{               
               if(a.genre !== b.genre) return dic[b.genre] - dic[a.genre];
               if(a.count !== b.count) return b.count - a.count;
               return a.index - b.index;
           })
           .filter(t=&amp;gt;  {
               if(dupDic[t.genre] &amp;gt;= 2) return false;
               dupDic[t.genre] = dupDic[t.genre] ? dupDic[t.genre]+ 1 : 1;
               return true;
            })
           .map(t=&amp;gt; t.index);    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* 굳이 자바스크립트의 Map객체를 사용하지 않아도 되지만 Map객체에 대해 좀 더 알기 위해 사용해 보았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style8&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트/Javascript</category>
      <author>JWOOKJ</author>
      <guid isPermaLink="true">https://jwookj.tistory.com/110</guid>
      <comments>https://jwookj.tistory.com/110#entry110comment</comments>
      <pubDate>Mon, 7 Feb 2022 21:04:54 +0900</pubDate>
    </item>
  </channel>
</rss>