<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>쥐로그</title>
    <link>https://e-juhee.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 08:17:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>e-juhee</managingEditor>
    <image>
      <title>쥐로그</title>
      <url>https://tistory1.daumcdn.net/tistory/5753181/attach/053384c89d0247619fb0e7b894294e04</url>
      <link>https://e-juhee.tistory.com</link>
    </image>
    <item>
      <title>React 공식 문서 읽기 스터디 회고</title>
      <link>https://e-juhee.tistory.com/entry/React-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;정글 수료 후 동기들과 함께한 React 공식 문서 읽기 스터디가 드디어 끝났다!  &lt;br /&gt;다음 스터디 주제를 정하기 위해서 모여서&lt;br /&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;이 스터디원들과 제일 먼저 공부했던 것은 면접 준비를 위한 모던 자바스크립트 Deep Dive 책 읽기였고,&lt;br /&gt;책을 다 읽고 나서는 본격적으로 리액트를 공부해 보기 위해서 공식 문서를 읽었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액트 공식 문서를 전부 읽는 데 걸린 기간은 무려 199일... (Sep 28, 2023 ~ Mar 25, 2024)&lt;/p&gt;
&lt;h1&gt;1. 이전 스터디 회고&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 좋았던 점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성범) 모르는 것을 알게되어 좋았다&lt;/li&gt;
&lt;li&gt;주희) 스터디 안 했으면 아예 공부 안 했을 것 같은데 조금이라도 하게 되어서 좋았다&lt;/li&gt;
&lt;li&gt;성범) 개발이 재밌어보인다. 새로운 것을 배워서 적용시키고 기능에 대해 깊은 고민을 하는 모습을 볼 수 있는 것이 좋았다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 아쉬운 점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내 차례가 아니면 잘 안 듣게 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;채림) JS 딥다이브때까지는 다 열심히 했는데, 리액트 독스는 잘 안 쓸 것 같은 API도 있고 하니까, 흥미가 떨어짐&lt;/li&gt;
&lt;li&gt;성범) 실제로 안 쓰는 것도 공부하니까 흥미가 떨어짐&lt;/li&gt;
&lt;li&gt;성범) 자잘한 것들(실제로 안 쓰는 것들)도 적용해 보면 좋았겠다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;경민) 온라인이다보니 집중력이 떨어진다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캠을 켜는 것은 어떨까?&lt;/li&gt;
&lt;li&gt;흥미로운 주제를 잘 정하면 동기부여가 될 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 느낀 점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성범) 우리 스터디의 목적을 다시 잡아보자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;친목? 공부? 논의하는 시간 갖기?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;경민) 스터디 할 때는 공부가 되는 것 같은데 나중에 기억이 안 난다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조금 느리게 배우더라도 확실하게 알기 위해서 적용해 가면서 배우는 것 좋을 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;성범) 배울 때는 써보고싶다는 생각이 들다가도 지나면 잊어버림&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 스터디의 목적&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 스터디를 통해 얻고싶은 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성범) 꾸준히 공부하는 데 의의를 둔다.&lt;/li&gt;
&lt;li&gt;주희) 정확한 용어를 사용하고 사용하는 기술의 개념을 설명할 수 있는 사람이 된다.&lt;/li&gt;
&lt;li&gt;주희) 최신 트렌드를 파악한다.&lt;/li&gt;
&lt;li&gt;주희) 공부한 내용에 대해 비슷한 수준의 친구들과 논의하며 더 깊게 이해한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;승훈) 정글 친구들과 함께 논의하는 시간을 갖는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;채림) 강제로 공부한다.&lt;/li&gt;
&lt;li&gt;성범) 배운 개념을 실제로 적용해 보며 더 잘 이해한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 이 방법은 시간이 많이 들 테니 스터디 횟수를 줄이고 농도를 높이면 어떨까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3. 스터디 방식&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 스터디를 위한 개인 공부에 투자하고 싶은 시간 (준비 시간)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성범) 2~3주에 2시간, 한번에 몰아서 하는 식&lt;/li&gt;
&lt;li&gt;주희) 하루에 15분, 잘게 쪼개서 하는 식&lt;/li&gt;
&lt;li&gt;경민) 1주에 2시간 / 머리로는 매일 꾸준히 하고 싶지만, 실천이 어려울 듯?&lt;/li&gt;
&lt;li&gt;승훈) 1주에 1~2시간&lt;/li&gt;
&lt;li&gt;채림) n주에 1시간 반 ~ 2시간&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;2) 모임에 투자하고 싶은 시간 (준비 시간 제외)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 일주일에 2시간&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;3) 공부 빈도&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매주 다 같이 공부하기 VS 돌아가며 한 명이 공부해서 알려주는 방식 (현재방식)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 현재 방식은 시간 많이 안 써도 되어서 좋았지만, 배우는 게 적었다.&lt;/li&gt;
&lt;li&gt;성범) 시간은 그대로 두고, 공부 방식을 바꾸는 방식은 어떨까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4) 신규 멤버 영입&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;찬
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성범) 영입하고 싶은 분이 있는데 그분에게 좋은 기회라고 생각한다.&lt;/li&gt;
&lt;li&gt;주희) 정글 사람들이 들어오는 것은 좋을 것 같다.&lt;/li&gt;
&lt;li&gt;주희) 우리끼리만 있을 때보다 새로운 멤버가 생기면 눈치가 보여서 열심히 하게 될 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;반
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 친해지기 전까지는 서로 불편할 수 있다.&lt;/li&gt;
&lt;li&gt;주희) 정글 사람이 아니라면 공감대가 적어서 편하게 대화하기 힘들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 앞으로 뭘 공부할까?&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 책 스터디&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;책 후보
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클린코드: 경민, 주희, 채림&lt;/li&gt;
&lt;li&gt;컴퓨터 밑바닥의 비밀: 경민, 주희, 승훈&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;b&gt;  &lt;/b&gt;리액트 딥다이브: &lt;/b&gt;주희, 승훈, 채림, 경민&lt;b&gt;&amp;nbsp;&amp;larr; 로 결정&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;방식 논의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 책을 읽되 하루에 딱 1~2p만 읽는 건 어떨까? 그리고 다른 스터디도 병행&lt;/li&gt;
&lt;li&gt;승훈) 각 챕터에 대한 질문을 만들고 답변을 달자&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) Next.js 공식 문서 읽기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방식 논의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;채림) 기존 공식문서를 번역해서 똑같이 만들기?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 그대로 클론 하지 말고 우리가 보기 편한 방식으로 만들면 어떨까?&lt;/li&gt;
&lt;li&gt;채림) 시간이 오래 걸릴 테니 리액트 공식문서 읽었던 것처럼 우선 내용을 정리만 하고 끝나면 그것을 프로젝트화 하는 게 낫겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;성범) 배운 개념을 직접 적용해 보자
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 간단한 프로젝트를 만들어두고 그때 배운 개념을 억지로라도 적용해 본다. 만약 적용이 어렵다면 왜 안 되는지 변명을 준비하며 공부해 온다.&lt;/li&gt;
&lt;li&gt;채림) 어차피 간단한 프로젝트를 만들 거면 독스로 프로젝트를 만들고 거기에 적용하면 되지 않나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주희) 그러면 우리의 독스 프로젝트가 지저분해질 듯..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;3) React&lt;span&gt;&amp;nbsp;&lt;/span&gt;공식 문서 다시 보면서 적용해 보기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방식 논의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성범) 리액트 독스를 적용해 보면서 제대로 다시 읽는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 style=&quot;color: #000000; text-align: start;&quot;&gt;5. 결론&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://search.shopping.naver.com/book/catalog/43543026664?cat_id=50010920&amp;amp;frm=PBOKPRO&amp;amp;query=%EB%A6%AC%EC%95%A1%ED%8A%B8+deepdive&amp;amp;NaPm=ct%3Dluzl5ixc%7Cci%3D55d00ed0a2ed996630a6de5657eb6209669c4428%7Ctr%3Dboknx%7Csn%3D95694%7Chk%3D2b2f3b9c709423af0d6c9c62826368a1885ead84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;모던 리액트 Deep Dive&lt;/span&gt;&lt;/a&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하루에 7~8p정도씩 읽는다. (&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;4달 안에 끝내보자)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;각 챕터에 1인당 1개 이상의 질문을 필수로 만들어서 Git에 올린다. (중복 불가)&lt;/li&gt;
&lt;li&gt;질문 올리는 마감 시간을 정해두고 늦으면 벌금을 걷어서 회식하기  &lt;/li&gt;
&lt;li&gt;스터디 시간에 랜덤으로 답변자 정해서 답변하기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스터디를 통해서 JS 딥다이브 책도 완독하고, 리액트 독스도 처음부터 끝까지 다 읽어보고&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;리액트 딥다이브 읽으면서 예제가 많으면 적용해 보는 프로젝트 하나 만들어서 적용하며 보는 것도 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 스터디에서 나의 목표는, 리액트에 관해서 검색할 일이 생길 때 내 블로그에서 편하게 찾아볼 수 있도록 잘 정리해 두는 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;독스를 한 번 읽었으니, 읽은 내용 + 독스 내용 + 그리고 적용해 본 내용을 함께 정리해 두면 미래의 나에게 쓸모가 있겠지~!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;이건 일단 하루에 1p씩만 읽어보자..ㅎㅎ&amp;nbsp;&lt;/p&gt;</description>
      <category>회고</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/158</guid>
      <comments>https://e-juhee.tistory.com/entry/React-%EC%8A%A4%ED%84%B0%EB%94%94-%ED%9A%8C%EA%B3%A0#entry158comment</comments>
      <pubDate>Sun, 14 Apr 2024 23:09:07 +0900</pubDate>
    </item>
    <item>
      <title>[React] createRoot, hydrateRoot</title>
      <link>https://e-juhee.tistory.com/entry/React-createRoot-hydrateRoot</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리액트를 쓸 수 있게 해주는 루트를 만드는 API들!&lt;br /&gt;Next.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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Docs&lt;br /&gt;  &lt;a href=&quot;https://react.dev/reference/react-dom/client/createRoot&quot;&gt;createRoot&lt;/a&gt;&lt;br /&gt;  &lt;a href=&quot;https://react.dev/reference/react-dom/client/hydrateRoot&quot;&gt;hydrateRoot&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;/p&gt;
&lt;h1&gt;createRoot&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;브라우저의 DOM 노드 안에 React 컴포넌트를 표시하는 루트를 생성하는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;1. Reference&lt;/h1&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = createRoot(domNode);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트를 생성한 후, 그 안에 React 컴포넌트를 표시하기 위해 &lt;code&gt;root.render(&amp;lt;App /&amp;gt;;)&lt;/code&gt;를 호출해야 한다.&lt;/li&gt;
&lt;li&gt;온전히 React만으로 구축된 앱은 보통 루트 컴포넌트에 대한 &lt;code&gt;createRoot&lt;/code&gt; 호출이 하나만 있다.&lt;/li&gt;
&lt;li&gt;페이지의 일부에만 React를 사용하는 경우 필요한 만큼 여러 개의 독립적인 루트를 가질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) Parameters&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;domNode&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트를 생성할 DOM 엘리먼트&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;options (optional)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트에 대한 옵션을 가진 객체&lt;/li&gt;
&lt;li&gt;onRecoverableError: 오류로부터 자동으로 복구될 때 호출될 콜백&lt;/li&gt;
&lt;li&gt;identifierPrefix: useId에 의해 생성된 ID에 사용할 문자열 접두사로, 같은 페이지에서 여러 개의 루트를 사용할 때 충돌을 피하는 데 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) Returns&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;render&lt;/code&gt; 메서드와 &lt;code&gt;unmount&lt;/code&gt; 메서드가 있는 객체를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱이 서버에서 렌더링되는 경우 &lt;code&gt;createRoot&lt;/code&gt;를 사용할 수 없고, &lt;code&gt;hydrateRoot&lt;/code&gt;를 사용해야 한다.&lt;/li&gt;
&lt;li&gt;앱에 &lt;code&gt;createRoot&lt;/code&gt; 호출이 하나만 있을 가능성이 높다. 프레임워크를 사용하는 경우에는 프레임워크가 대신 호출 해줄걸&lt;/li&gt;
&lt;li&gt;현재 컴포넌트의 자식이 아닌 DOM 트리의 다른 부분(ex: Modal, Tooltip 등)에 JSX 조각을 렌더링하려는 경우에는 &lt;code&gt;createPortal&lt;/code&gt;을 사용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서, 리턴되는 객체에 담긴 &lt;code&gt;render&lt;/code&gt; 메서드와 &lt;code&gt;unmount&lt;/code&gt; 메서드에 대해 알아보자&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;1-1. root.render(reactNode)&lt;/h1&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;root.render(&amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이걸 호출해서 JSX 조각을 React 루트의 브라우저 DOM 노드에 표시한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;root.render(&amp;lt;App /&amp;gt;);&lt;/code&gt;를 호출하면, React는 루트 안에 &lt;code&gt;&amp;lt;App/&amp;gt;&lt;/code&gt;을 표시하고 그 안의 DOM 관리를 인수한다.  &lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) Parameters&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;reactNode&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표시하려는 React 노드&lt;/li&gt;
&lt;li&gt;일반적으로 &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt;과 같은 JSX 조각이 되지만, &lt;code&gt;createElement()&lt;/code&gt;로 구성된 React 엘리먼트, 문자열, 숫자, null, undefined도 ㄱㄴ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) Returns&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;undefined&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;처음 호출할 때, React는 React 컴포넌트를 렌더링하기 전에 React 루트 안에 있던 &lt;b&gt;기존 HTML 컨텐츠를 전부 지우고&lt;/b&gt; React 컴포넌트를 그 안에 렌더링한다.&lt;/li&gt;
&lt;li&gt;서버에서 또는 빌드 중에 React에 의해 생성된 HTML이 루트의 DOM 노드에 포함된 경우, &lt;code&gt;createRoot&lt;/code&gt; 대신에 이벤트 핸들러를 기존 HTML에 첨부하는 &lt;code&gt;hydrateRoot()&lt;/code&gt;를 사용해야 한다.&lt;/li&gt;
&lt;li&gt;같은 루트에 render를 여러 번 호출하면, React는 최신 JSX를 반영하도록 필요에 따라 DOM을 업데이트한다.
&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;같은 루트에 다시 render를 호출하는 것은 set 함수를 호출하는 것과 비슷하다.&lt;/li&gt;
&lt;li&gt;즉, 불필요한 업데이트는 알아서 피한다.  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;1-2. root.unmount()&lt;/h1&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;root.unmount();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React 루트 안에 렌더링된 트리를 파괴한다.&lt;/li&gt;
&lt;li&gt;온전히 React만으로 작성된 앱에는 보통 &lt;code&gt;root.unmount&lt;/code&gt;을 호출하지 않는다.&lt;/li&gt;
&lt;li&gt;이 함수는 React 루트의 DOM 노드 (또는 그 조상 노드)가 다른 코드에 의해 DOM에서 제거될 수 있는 경우에 유용하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex: DOM에서 비활성 탭을 제거하는 jQuery 탭 패널은 탭이 제거되면 그 안에 있는 모든 것도 DOM에서 제거될 테니 이때 &lt;code&gt;root.unmount&lt;/code&gt;를 호출해서 제거된 루트의 컨텐츠 관리를 중지하도록 React에 알려줘야 한다.&lt;/li&gt;
&lt;li&gt;그러지 않으면 제거된 루트 내부의 컴포넌트가 구독과 같은 전역 리소스를 정리하고 확보할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;root.unmount&lt;/code&gt;를 호출하면 루트의 모든 컴포넌트가 unmount되고, 트리의 이벤트 핸들러나 state가 제거되며, 루트 DOM 노드에서 React가 분리된다.&lt;/li&gt;
&lt;li&gt;Parameters와 Returns 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;root.unmount&lt;/code&gt;를 호출하고 나면 같은 루트에 대해 &lt;code&gt;root.render&lt;/code&gt;를 다시 호출할 수 없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;unmount된 루트에서 &lt;code&gt;root.render&lt;/code&gt;를 호출하려고 하면 &lt;code&gt;Cannot update an unmounted root&lt;/code&gt; 오류가 발생한다.&lt;/li&gt;
&lt;li&gt;but, 해당 노드의 이전 루트가 unmount 된 후 동일한 DOM 노드에 새로운 루트를 만드는 건 ㄱㄴ (새로 createRoot 해야된다는 뜻)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;2. Usage&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) React뿐인 앱 렌더링하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전체 앱에 대해 단일 루트를 생성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'));
root.render(&amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 코드는 시작할 때 한 번만 실행하면 된다.&lt;/li&gt;
&lt;li&gt;앱이 온전히 React만으로 작성되었으면 추가적으로 루트를 더 만들거나 &lt;code&gt;root.render&lt;/code&gt;를 다시 호출할 필요 없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 시점부터 React는 전체 앱의 DOM을 관리하므로,
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트를 더 추가하려면 App 컴포넌트 안에 중첩 ㄱㄱ&lt;/li&gt;
&lt;li&gt;UI 업데이트는 각 컴포넌트의 state를 통해 ㄱㄱ&lt;/li&gt;
&lt;li&gt;모달, 툴팁과 같은 추가 컨텐츠를 DOM 노드 외부에 표시해야 하는 경우 portal로 렌더링 ㄱㄱ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTML이 비어있으면 앱의 JS 코드가 로드되고 실행될 때까지 사용자에게 빈 페이지가 표시되어서 느리게 느껴질 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 문제를 해결하기 위해 서버에서 or 빌드 중에 컴포넌트로부터 초기 HTML을 생성할 수 있다.&lt;/li&gt;
&lt;li&gt;이런 최적화를 기본적으로 수행하는 프레임워크를 사용하는 것을 추천한다.&lt;/li&gt;
&lt;li&gt;실행 시점에 따라 이를 SSR(server-side rendering) 또는 SSG(static site generation)라고 한다.&lt;/li&gt;
&lt;li&gt;SSR이나 SSG를 사용하는 앱은 createRoot 대신 hydrateRoot를 호출해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그러면 React는 DOM 노드를 파괴하고 재생성하는 대신 재사용(hydrate)한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) 부분적으로 React로 작성된 페이지 렌더링하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React가 관리하는 각 최상위 UI에 대한 루트를 생성하기 위해 &lt;code&gt;createRoot&lt;/code&gt;를 여러 번 호출하고, 루트마다 &lt;code&gt;root.render&lt;/code&gt;를 호출해서 각각 다른 컨텐츠를 표시할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;import './styles.css';
import { createRoot } from 'react-dom/client';
import { Comments, Navigation } from './Components.js';

const navDomNode = document.getElementById('navigation');
const navRoot = createRoot(navDomNode); 
navRoot.render(&amp;lt;Navigation /&amp;gt;);

const commentDomNode = document.getElementById('comments');
const commentRoot = createRoot(commentDomNode); 
commentRoot.render(&amp;lt;Comments /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;document.createElement()&lt;/code&gt;를 사용해서 새 DOM 노드를 생성하고 문서에 수동으로 추가할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;const domNode = document.createElement('div');
const root = createRoot(domNode); 
root.render(&amp;lt;Comment /&amp;gt;);
document.body.appendChild(domNode); // You can add it anywhere in the document&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 기능은 React 컴포넌트가 다른 프레임워크로 작성된 앱 내부에 있는 경우에 유용하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 루트 컴포넌트 업데이트하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 루트에서 &lt;code&gt;render&lt;/code&gt;를 여러 번 호출할 수 있다.&lt;/li&gt;
&lt;li&gt;컴포넌트 트리 구조가 이전 렌더링과 일치하면 React는 기존 state를 유지한다.&lt;br /&gt;를 볼 수 있는 괴상한 예시  &lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const root = createRoot(document.getElementById('root'));

let i = 0;
setInterval(() =&amp;gt; {
  root.render(&amp;lt;App counter={i} /&amp;gt;);
  i++;
}, 1000);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이딴식으로 계속 다시 render를 호출해도 컴포넌트 안의 state가 유지된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하지만 이렇게 render를 여러 번 호출하는 경우는 드문 일이다. 일반적으로는 컴포넌트가 state를 업데이트 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;3. Troubleshooting&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) &lt;code&gt;Target container is not a DOM element&lt;/code&gt; 오류&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;createRoot&lt;/code&gt;에 전달한 것이 DOM 노드가 아닐 때 나는 오류이다.&lt;/li&gt;
&lt;li&gt;잘 모르겠으면 콘솔에 로깅해보자&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;const domNode = document.getElementById('root');
console.log(domNode); // ???
const root = createRoot(domNode);
root.render(&amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&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;id를 잘못 적었거나&lt;/li&gt;
&lt;li&gt;HTML에서 &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; 태그는 그 뒤에 나타나는 DOM 노드를 볼 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) &lt;code&gt;Functions are not valid as a React child.&lt;/code&gt; 오류&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;root.render&lt;/code&gt;에 전달한 것이 React 컴포넌트가 아닐 때 나는 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 서버에서 렌더링된 HTML이 처음부터 다시 생성됨&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에서 렌더링된 앱은 &lt;code&gt;createRoot&lt;/code&gt; 대신 &lt;code&gt;hydrateRoot&lt;/code&gt; 써야 함!&lt;/li&gt;
&lt;li&gt;앱이 서버에서 렌더링되어 React에 의해 생성된 초기 HTML을 포함하는 경우, &lt;code&gt;root.render&lt;/code&gt;를 호출하는 과정에서, 모든 HTML이 삭제되고 모든 DOM 노드가 처음부터 다시 생성된다.&lt;/li&gt;
&lt;li&gt;이렇게 하면 속도가 느려지고, 포커스와 스크롤 위치가 재설정되며, 그 밖의 다른 사용자 입력들도 손실될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;hydrateRoot&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;react-dom/server&lt;/code&gt;를 통해 사전에 생성한 HTML 컨텐츠를 가진 브라우저 DOM 노드 안에 React 컴포넌트를 표시할 수 있게 해주는 API&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;1. Reference&lt;/h1&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, reactNode);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 서버 환경에서 React에 의해 렌더링된 기존 HTML에 React를 붙이는(attach) 방식&lt;/li&gt;
&lt;li&gt;React는 DOM 노드 내부에 존재하는 HTML에 붙어 그 안의 DOM을 관리하게 된다.&lt;/li&gt;
&lt;li&gt;전부 React로 구성된 앱은 일반적으로 루트 컴포넌트와 함께 hydrateRoot를 한 번만 호출한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) Parameters&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;domNode&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버에서 루트 요소로서 렌더링된 DOM 요소&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;reactNode&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 HTML을 렌더링하는 데 사용된 React 노드&lt;/li&gt;
&lt;li&gt;보통 ReactDOM 서버 메서드인 &lt;code&gt;renderToPipeableStream(&amp;lt;App /&amp;gt;)&lt;/code&gt;으로 렌더링된 JSX 조각인 &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt;이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;options (optional)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;루트에 대한 옵션을 가진 객체&lt;/li&gt;
&lt;li&gt;onRecoverableError: 오류로부터 자동으로 복구될 때 호출될 콜백&lt;/li&gt;
&lt;li&gt;identifierPrefix: useId에 의해 생성된 ID에 사용할 문자열 접두사로, 같은 페이지에서 여러 개의 루트를 사용할 때 충돌을 피하는 데 유용하다. &lt;b&gt;서버에서 사용된 것과 동일한 접두어여야 한다!&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) Returns&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;render&lt;/code&gt; 메서드와 &lt;code&gt;unmount&lt;/code&gt; 메서드가 있는 객체를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;hydrateRoot&lt;/code&gt;는 렌더링된 컨텐츠가 서버에서 렌더링된 컨텐츠와 동일하다고 예상하므로, 불일치를 버그로 취급하고 고쳐야 한다.&lt;/li&gt;
&lt;li&gt;dev mode에서 React는 hydration 중 일어나는 불일치에 대해 경고해준다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 불일치는 서버에서 렌더링된 HTML과 클라이언트 사이드에서의 React 컴포넌트 간에 일치하지 않는 부분이 있는 것을 의미한다.&lt;/li&gt;
&lt;li&gt;속성이 불일치할 경우 해당 속성이 올바르게 적용되지 않을 수 있다.&lt;/li&gt;
&lt;li&gt;대부분의 앱에서 불일치는 드물기 때문에 성능을 위해 모든 markup을 검증하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;App에서 &lt;code&gt;hydrateRoot&lt;/code&gt;는 단 한 번만 호출하게 될 것이고, 프레임워크를 사용한다면 프레임워크가 대신 해줄걸!&lt;/li&gt;
&lt;li&gt;App을 사전에 렌더링된 HTML 없이 클라이언트에서 직접 렌더링한다면 &lt;code&gt;hydrateRoot&lt;/code&gt; 말고 &lt;code&gt;createRoot&lt;/code&gt;를 사용 ㄱㄱ&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이어서, 리턴되는 객체에 담긴 &lt;code&gt;render&lt;/code&gt; 메서드와 &lt;code&gt;unmount&lt;/code&gt; 메서드에 대해 알아보자&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;1-1. root.render(reactNode)&lt;/h1&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;root.render(&amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;hydrate된 React 루트 내부의 컴포넌트를 업데이트하기 위해 &lt;code&gt;root.render&lt;/code&gt;를 호출한다.&lt;/li&gt;
&lt;li&gt;리액트는 hydrate된 루트 안에서 &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt;을 업데이트 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) Parameters&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;reactNode&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업데이트하려는 React 노드&lt;/li&gt;
&lt;li&gt;일반적으로 &lt;code&gt;&amp;lt;App /&amp;gt;&lt;/code&gt;과 같은 JSX 조각이 되지만, &lt;code&gt;createElement()&lt;/code&gt;로 구성된 React 엘리먼트, 문자열, 숫자, null, undefined도 ㄱㄴ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) Returns&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;undefined&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;root가 hydrating을 마치기 전에 &lt;code&gt;root.render&lt;/code&gt;를 호출하면, React는 기존의 서버에서 렌더링된 HTML 내용을 지우고 전체 루트를 클라이언트 렌더링으로 전환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;1-2. root.unmount()&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;createRoot와 완전 똑같다!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;root.unmount();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React 루트 안에 렌더링된 트리를 파괴한다.&lt;/li&gt;
&lt;li&gt;온전히 React만으로 작성된 앱에는 보통 &lt;code&gt;root.unmount&lt;/code&gt;을 호출하지 않는다.&lt;/li&gt;
&lt;li&gt;이 함수는 React 루트의 DOM 노드 (또는 그 조상 노드)가 다른 코드에 의해 DOM에서 제거될 수 있는 경우에 유용하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex: DOM에서 비활성 탭을 제거하는 jQuery 탭 패널은 탭이 제거되면 그 안에 있는 모든 것도 DOM에서 제거될 테니 이때 &lt;code&gt;root.unmount&lt;/code&gt;를 호출해서 제거된 루트의 컨텐츠 관리를 중지하도록 React에 알려줘야 한다.&lt;/li&gt;
&lt;li&gt;그러지 않으면 제거된 루트 내부의 컴포넌트가 구독과 같은 전역 리소스를 정리하고 확보할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;root.unmount&lt;/code&gt;를 호출하면 루트의 모든 컴포넌트가 unmount되고, 트리의 이벤트 핸들러나 state가 제거되며, 루트 DOM 노드에서 React가 분리된다.&lt;/li&gt;
&lt;li&gt;Parameters와 Returns 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 주의사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;root.unmount&lt;/code&gt;를 호출하고 나면 같은 루트에 대해 &lt;code&gt;root.render&lt;/code&gt;를 다시 호출할 수 없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;unmount된 루트에서 &lt;code&gt;root.render&lt;/code&gt;를 호출하려고 하면 &lt;code&gt;Cannot update an unmounted root&lt;/code&gt; 오류가 발생한다.&lt;/li&gt;
&lt;li&gt;but, 해당 노드의 이전 루트가 unmount 된 후 동일한 DOM 노드에 새로운 루트를 만드는 건 ㄱㄴ (새로 createRoot 해야된다는 뜻)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;2. Usage&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) 서버에서 렌더링된 HTML을 hydrate하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;react-dom/server&lt;/code&gt;로 앱의 HTML을 만들었으면 클라이언트에서 hydrate 해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import { hydrateRoot } from 'react-dom/client';

hydrateRoot(document.getElementById('root'), &amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 하면 서버 HTML을 브라우저 DOM node에서 React 컴포넌트를 이용해 hydrate 해준다.&lt;/li&gt;
&lt;li&gt;(위에서도 설명했지만) 보통 앱을 시작할 때 단 한 번만 실행한다. (프레임워크를 쓴다면 프레임워크가 알아서 해줄걸!)&lt;/li&gt;
&lt;li&gt;앱을 hydrate 하기 위해서 React는 컴포넌트의 로직을 사전에 서버에서 만들어진 HTML에 붙인다(attach).&lt;/li&gt;
&lt;li&gt;hydration을 통해 서버에서 만들어진 초기의 HTML 스냅샷을 브라우저에서 실행되는 fully interactive한 앱으로 변환한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hydrateRoot&lt;/code&gt;에 전달한 React 트리는 서버에서 만들었던 것과 동일한 출력을 생성해야 한다.
&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;WHY? 사용자 경험을 위해서!&lt;/li&gt;
&lt;li&gt;앱이 더 빨리 로드되는 환상을 보여주기 위해서 HTML 스냅샷을 JS 코드가 로드되기 전에 보여주는 것인데, 갑자기 다른 내용을 보여주면 그 환상이 와장창.. 깨짐  &lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주로 이런 이유들로 인해 hydration 에러가 발생한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새 줄(newlines) 같은 추가적인 공백이 React가 생성한 HTML 주변에 있는 경우&lt;/li&gt;
&lt;li&gt;렌더링 로직에서 &lt;code&gt;typeof window !== 'undefined'&lt;/code&gt;와 같은 검사를 하는 경우&lt;/li&gt;
&lt;li&gt;렌더링 로직에서 &lt;code&gt;window.matchMedia&lt;/code&gt;와 같은 브라우저 전용 API를 사용하는 경우&lt;/li&gt;
&lt;li&gt;서버와 클라이언트에서 다른 데이터를 렌더링하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;React는 일부 hydration 오류에서 복구되지만, 다른 버그처럼 마찬가지로 수정해야 하는 문제이다.&lt;/li&gt;
&lt;li&gt;알아서 잘 복구되면 성능 저하뿐이지만, 최악의 경우 이벤트 핸들러가 잘못된 요소에 연결될 수도 있다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2) Document 전체를 hydrate하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱 전체가 React인 경우 &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; 태그를 포함해 JSX로 된 전체 document를 렌더링할 수 있다.&lt;/li&gt;
&lt;li&gt;글로벌 변수인 document를 hydrateRoot의 첫번째 인자로 주면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;coffeescript&quot;&gt;&lt;code&gt;import { hydrateRoot } from 'react-dom/client';
import App from './App.js';

hydrateRoot(document, &amp;lt;App /&amp;gt;);&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) 불가피한 hydration 불일치 에러 억제하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쩔수 없이 element의 속성이나 텍스트가 서버와 클라이언트에서 서로 다를 수밖에 없다면(ex: 타임스탬프) hydration 불일치 경고를 안 보이게 할 수 있다.&lt;/li&gt;
&lt;li&gt;해당 element에 &lt;code&gt;suppressHydrationWarning={true}&lt;/code&gt;를 전달하면 됨&lt;/li&gt;
&lt;li&gt;적용한 요소에만 작동하며(자식은 ㄴㄴ), 예외적인 escape hatch일 뿐이므로 남용 금지!&lt;/li&gt;
&lt;li&gt;이제 React는 이것을 수정하려고 시도하지 않을 것이며, 미래의 업데이트까지 일관성을 잃을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;export default function App() {
  return (
    &amp;lt;h1 suppressHydrationWarning={true}&amp;gt;
      Current Date: {new Date().toLocaleDateString()}
    &amp;lt;/h1&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4) 서로 다른 클라이언트와 서버 컨텐츠 다루기&lt;/h2&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;클라이언트에서는 Effect 안에서 true로 할당되는 state를 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;이렇게 하면 처음에는 서버와 동일한 결과물을 렌더링하므로 불일치 문제를 피할 수 있다.&lt;/li&gt;
&lt;li&gt;But! 이렇게 하면 두 번 렌더링되므로 hydration이 느려진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// index.html
&amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;h1&amp;gt;Is Server&amp;lt;/h1&amp;gt;&amp;lt;/div&amp;gt;

// App.js
export default function App() {
  const [isClient, setIsClient] = useState(false);

  useEffect(() =&amp;gt; {
    setIsClient(true);
  }, []);

  return (
    &amp;lt;h1&amp;gt;
      {isClient ? 'Is Client' : 'Is Server'}
    &amp;lt;/h1&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5) hydrate된 root 컴포넌트 업데이트하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;root의 hydrating이 끝난 이후에 &lt;code&gt;root.render&lt;/code&gt;를 호출해서 업데이트 할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;createRoot&lt;/code&gt;와 다르게 HTML로 최초의 컨텐츠가 이미 렌더링 되어 있기 때문에 자주 사용할 필요는 없다.&lt;/li&gt;
&lt;li&gt;hydration 후에 &lt;code&gt;root.render&lt;/code&gt;를 호출했을 때, 컴포넌트의 트리 구조가 이전에 렌더링한 구조와 일치한다면 React는 그 상태를 유지한다.&lt;/li&gt;
&lt;li&gt;이것도 createRoot에서와 마찬가지로 이미 hydrate된 root에 &lt;code&gt;root.render&lt;/code&gt;를 호출하는 것은 흔한 일이 아니다. 컴포넌트 안에서 state를 업데이트 하도록..^a^&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>React</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/157</guid>
      <comments>https://e-juhee.tistory.com/entry/React-createRoot-hydrateRoot#entry157comment</comments>
      <pubDate>Tue, 19 Mar 2024 01:29:13 +0900</pubDate>
    </item>
    <item>
      <title>[React] createPortal</title>
      <link>https://e-juhee.tistory.com/entry/React-createPortal</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모달 짱 쉽게 만들 수 있을 것만 같은 API.. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Docs&lt;br /&gt;  &lt;a href=&quot;https://react.dev/reference/react-dom/createPortal&quot;&gt;createPortal&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;/p&gt;
&lt;h1&gt;createPortal&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cratePortal을 사용하면 컴포넌트의 일부를 DOM의 다른 부분에 렌더링할 수 있다.&lt;/li&gt;
&lt;li&gt;포털을 생성하려면 createPortal을 호출하고 JSX와 렌더링할 DOM 노드를 전달한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;div&amp;gt;
  &amp;lt;SomeComponent /&amp;gt;
  {createPortal(children, domNode, key?)}
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포털은 DOM 노드의 &lt;b&gt;물리적 배치&lt;/b&gt;만 변경한다.&lt;/li&gt;
&lt;li&gt;포털에 렌더링하는 JSX도 이를 렌더링하는 React 컴포넌트의 자식 노드 역할을 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어, 자식은 부모 트리에서 제공하는 컨텍스트에 접근할 수 있고, 이벤트는 React 트리에 따라 자식에서 부모로 버블링된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Parameters&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;children&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React로 렌더링할 수 있는 모든 'JSX 조각, Fragment, string, number 또는 이들의 배열'&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;domNode&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;document.getElementById()&lt;/code&gt;가 반환하는 것과 같은 일부 DOM 노드&lt;/li&gt;
&lt;li&gt;노드는 이미 존재하는 것이어야 한다. (null이면 안됨)&lt;/li&gt;
&lt;li&gt;업데이트 중에 다른 DOM 노드를 전달하면 포털 콘텐츠가 다시 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;key (optional)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포털의 Key로 사용할 고유 문자열 or 숫자&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Returns&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JSX에 포함시키거나 React 컴포넌트에서 반환할 수 있는 React 노드를 반환한다.&lt;/li&gt;
&lt;li&gt;React가 렌더링 출력에서 이것을 만나면, 제공된 children을 제공된 domNode 안에 배치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;주의사항&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포털의 이벤트는 DOM 트리가 아닌 React 트리에 따라 전파된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어, 포털 내부를 클릭했을 때 포털이 &lt;code&gt;&amp;lt;div onClick&amp;gt;&lt;/code&gt;으로 감싸져 있으면 해당 &lt;code&gt;onClick&lt;/code&gt; 핸들러 이벤트가 실행된다.&lt;/li&gt;
&lt;li&gt;이로 인해 문제가 발생한다면, 포털 내부에서 이벤트 전파를 중지하거나 포털 자체를 React 트리에서 위로 옮겨야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;사용법&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1) DOM의 다른 부분으로 렌더링하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포털을 사용하면 컴포넌트 자식 중 일부를 DOM의 다른 위치에 렌더링할 수 있다.&lt;/li&gt;
&lt;li&gt;이를 통해 컴포넌트의 일부가 그 컨테이너에서 escape(탈출)할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어, 모달이나 툴팁을 페이지의 나머지 부분 위에 or 외부에 표시할 수 있다.&lt;/li&gt;
&lt;li&gt;포털을 생성하려면 createPortal의 결과를 일부 JSX와 함께 렌더링하고 포털이 있어야 할 DOM 노드를 지정한다.&lt;/li&gt;
&lt;li&gt;React는 사용자가 전달한 JSX에 대한 DOM 노드를 사용자가 제공한 DOM 노드 안에 배치한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시에서 포털이 없다면 &lt;code&gt;두 번째 p 태그&lt;/code&gt;는 테두리 있는 &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; 안에 배치되겠지만, 포털을 썼기 때문에 포털이 이를 &lt;code&gt;document.body&lt;/code&gt;로 텔레포트시킨다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { createPortal } from &quot;react-dom&quot;;

export default function MyComponent() {
  return (
    &amp;lt;div style={{ border: &quot;2px solid black&quot; }}&amp;gt;
      &amp;lt;p&amp;gt;첫 번째 p 태그&amp;lt;/p&amp;gt;
      {createPortal(&amp;lt;p&amp;gt;두 번째 p 태그&amp;lt;/p&amp;gt;, document.body)}
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cEE5Ox/btsFz9Py5vj/ehd7nvcs3yDMvg4zVZKYO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cEE5Ox/btsFz9Py5vj/ehd7nvcs3yDMvg4zVZKYO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cEE5Ox/btsFz9Py5vj/ehd7nvcs3yDMvg4zVZKYO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcEE5Ox%2FbtsFz9Py5vj%2Fehd7nvcs3yDMvg4zVZKYO1%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;846&quot; height=&quot;312&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;312&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;h2 data-ke-size=&quot;size26&quot;&gt;2) 모달 렌더링하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모달을 불러오는 컴포넌트가 &lt;code&gt;overflow: hidden&lt;/code&gt; 또는 이를 숨기는 어떤 다른 스타일이 지정되어 있더라도, 포털을 사용해서 나머지 페이지 위에 띄울 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시에서 포털에 렌더링된 모달은 DOM에서 부모 JSX 요소에 포함되지 않기 때문에, 부모 스타일의 영향을 받지 않고 잘 표시된다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// App
import PortalExample from './PortalExample';

export default function App() {
  return (
    &amp;lt;div className=&quot;clipping-container&quot;&amp;gt;
      &amp;lt;PortalExample /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

// PortalExample
import { useState } from 'react';
import { createPortal } from 'react-dom';
import ModalContent from './ModalContent.js';

export default function PortalExample() {
  const [showModal, setShowModal] = useState(false);
  return (
    &amp;lt;&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setShowModal(true)}&amp;gt;
        Show modal using a portal
      &amp;lt;/button&amp;gt;
      {showModal &amp;amp;&amp;amp; createPortal(
        &amp;lt;ModalContent onClose={() =&amp;gt; setShowModal(false)} /&amp;gt;,
        document.body
      )}
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들어, 키보드 사용자를 위해 포털 내외로 포커스를 자연스럽게 이동할 수 있도록 관리해야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 모달 창을 사용하는 경우 사용자가 키보드만으로 모달 안팎을 자유롭게 이동할 수 있어야 한다.&lt;/li&gt;
&lt;li&gt;즉, 사용자가 포털 내의 요소와 포털 외부의 다른 요소 사이에서 키보드 탐색을 할 때, 예상대로 포커스가 이동해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WHY? 스크린 리더와 같은 보조 기술을 사용하는 사람들을 위해&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모달을 만들 때는 &lt;a href=&quot;https://www.w3.org/WAI/ARIA/apg/#dialog_modal&quot;&gt;WAI-ARIA 모달 제작 사례&lt;/a&gt;를 따라야 한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WAI-ARIA(Web Accessibility Initiative - Accessible Rich Internet Applications) : 장애가 있는 사용자도 더 쉽게 이해하고 사용할 수 있도록 설계된 기술 표준&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3) React 컴포넌트를 non-React 서버 마크업으로 렌더링하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포털은 React 루트가 React로 빌드되지 않은 'static 페이지' 또는 'server-rendered 페이지'의 일부일 때 유용할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어, 페이지가 Rails와 같은 서버 프레임워크로 빌드된 경우, 사이드바와 같은 정적 영역 내에 상호작용 가능한 영역을 만들 수 있다.&lt;/li&gt;
&lt;li&gt;여러 개별 React 루트를 사용하는 것과 비교하여, 포털을 사용하면 앱의 일부가 DOM의 다른 부분에 렌더링되더라도, 공유 state를 가진 단일 React 트리로 처리할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;// index.html
&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
  &amp;lt;head&amp;gt;&amp;lt;title&amp;gt;My app&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;h1&amp;gt;Welcome to my hybrid app&amp;lt;/h1&amp;gt;
    &amp;lt;div class=&quot;parent&quot;&amp;gt;
      &amp;lt;div class=&quot;sidebar&quot;&amp;gt;
        This is server non-React markup
        &amp;lt;div id=&quot;sidebar-content&quot;&amp;gt;&amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
      &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;


// index.js
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App.js';
import './styles.css';

const root = createRoot(document.getElementById('root'));
root.render(
  &amp;lt;StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/StrictMode&amp;gt;
);


// App.js
import { createPortal } from 'react-dom';

const sidebarContentEl = document.getElementById('sidebar-content');

export default function App() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;MainContent /&amp;gt;
      {createPortal(
        &amp;lt;SidebarContent /&amp;gt;,
        sidebarContentEl
      )}
    &amp;lt;/&amp;gt;
  );
}

function MainContent() {
  return &amp;lt;p&amp;gt;This part is rendered by React&amp;lt;/p&amp;gt;;
}

function SidebarContent() {
  return &amp;lt;p&amp;gt;This part is also rendered by React!&amp;lt;/p&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;610&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOytWg/btsFAgHSGi2/W6JI5wkSqoOLgKzdv0jg71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOytWg/btsFAgHSGi2/W6JI5wkSqoOLgKzdv0jg71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOytWg/btsFAgHSGi2/W6JI5wkSqoOLgKzdv0jg71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOytWg%2FbtsFAgHSGi2%2FW6JI5wkSqoOLgKzdv0jg71%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;1716&quot; height=&quot;610&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;610&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;h2 data-ke-size=&quot;size26&quot;&gt;4) React 컴포넌트를 non-React DOM 노드로 렌더링하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React가 아닌 DOM 요소에 React 콘텐츠를 삽입할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React가 아닌 지도 위젯 안에 React 콘텐츠를 렌더링해보자!&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;우선 렌더링할 DOM 노드를 저장할 state 변수를 선언한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-js&quot;&gt;const [popupContainer, setPopupContainer] = useState(null);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;지도 위젯을 생성하고, 지도에 팝업을 추가한 후 팝업 DOM 노드를 state에 저장한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-js&quot;&gt;useEffect(() =&amp;gt; {
  if (mapRef.current === null) {
    const map = createMapWidget(containerRef.current); // 지도 위젯
    mapRef.current = map;
    const popupDiv = addPopupToMapWidget(map); // 팝업으로 사용될 DOM 노드를 반환 (지도 라이브러리에서 제공하는 기능)
    setPopupContainer(popupDiv);
  }
}, []);&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;DOM 요소(popupContainer)가 준비되면 (즉, null이 아니면) 이 요소 안에 React 컴포넌트를 렌더링한다.&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-js&quot;&gt;return (
  &amp;lt;div style={{ width: 250, height: 250 }} ref={containerRef}&amp;gt;
    {popupContainer !== null &amp;amp;&amp;amp;
      createPortal(&amp;lt;p&amp;gt;Hello from React!&amp;lt;/p&amp;gt;, popupContainer)}
  &amp;lt;/div&amp;gt;
);&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOHHXI/btsFzbz6nU9/EAGk33uwqfZXKZBrxg9QlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOHHXI/btsFzbz6nU9/EAGk33uwqfZXKZBrxg9QlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOHHXI/btsFzbz6nU9/EAGk33uwqfZXKZBrxg9QlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOHHXI%2FbtsFzbz6nU9%2FEAGk33uwqfZXKZBrxg9QlK%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;602&quot; height=&quot;550&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>React</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/156</guid>
      <comments>https://e-juhee.tistory.com/entry/React-createPortal#entry156comment</comments>
      <pubDate>Tue, 5 Mar 2024 13:45:00 +0900</pubDate>
    </item>
    <item>
      <title>[React] useFormState, useFormStatus</title>
      <link>https://e-juhee.tistory.com/entry/React-useFormState-useFormStatus</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form 액션의 결과에 기반하여 상태를 업데이트할 수 있게 해주는 Hook&lt;/li&gt;
&lt;li&gt;마지막 양식 제출의 status 정보를 제공하는 Hook&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Docs&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;a href=&quot;https://react.dev/reference/react-dom/hooks/useFormState&quot;&gt;useFormState&lt;/a&gt;&lt;br /&gt;  &lt;a href=&quot;https://react.dev/reference/react-dom/hooks/useFormStatus&quot;&gt;useFormStatus&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;useFormState&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  현재는 Canary와 experimental channel에서만 사용할 수 있다.&lt;br /&gt;  useFormState을 사용하는 이점을 얻으려면, React 서버 컴포넌트를 지원하는 프레임워크에서 사용해야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form 액션의 결과에 기반하여 상태를 업데이트할 수 있게 해주는 Hook이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pf&quot;&gt;&lt;code&gt;const [state, formAction] = useFormState(fn, initialState, permalink?);&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;1. Reference&lt;/h1&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;useFormState(action, initialState, permalink?) &lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트 최상위 레벨에서 useFormState를 호출해서, form 액션이 호출될 때 업데이트되는 state를 생성한다.&lt;/li&gt;
&lt;li&gt;useFormState에 form의 액션 함수와 초기값을 전달하면, form에 사용할 새로운 액션과 최신 state를 반환한다.&lt;/li&gt;
&lt;li&gt;최신 state는 제공한 함수에도 전달된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useFormState } from &quot;react-dom&quot;;

async function increment(previousState, formData) {
  return previousState + 1;
}

function StatefulForm({}) {
  const [state, formAction] = useFormState(increment, 0);
  return (
    &amp;lt;form&amp;gt;
      {state}
      &amp;lt;button formAction={formAction}&amp;gt;Increment&amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
  )
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form의 state는 form이 마지막으로 제출되었을 때 액션에 의해 반환된 값이다.&lt;/li&gt;
&lt;li&gt;form이 아직 제출되지 않았다면, 초기값을 가진다.&lt;/li&gt;
&lt;li&gt;서버 액션을 사용하면 form을 제출하고 나서 서버의 응답을 hydration이 완료되기 전에 보여줄 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-1. Parameters&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fn&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form이 제출될 때 호출할 함수&lt;/li&gt;
&lt;li&gt;초기 인자로 form의 이전 state를 받는다.&lt;/li&gt;
&lt;li&gt;그 다음에는 form 액션이 일반적으로 받는 인자들을 받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;initialState&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;State의 초기값&lt;/li&gt;
&lt;li&gt;처음 호출된 후에는 무시된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;permalink (optional)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form이 수정하는 고유 페이지 URL을 포함하는 문자열&lt;/li&gt;
&lt;li&gt;동적 콘텐츠를 포함하는 페이지(ex. 피드)에서 점진적 개선을 위해 사용된다.&lt;/li&gt;
&lt;li&gt;만약 fn이 &lt;b&gt;서버 액션&lt;/b&gt;이고, JS 번들이 로드되기 전에 폼이 제출되면, 브라우저는 현재 페이지의 URL이 아닌 &lt;b&gt;지정된 permalink URL로 이동&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;당연히,, 이 페이지에서도 동일한 form 컴포넌트가 렌더링되도록 해놔야하고, 동일한 액션 fn 및 permalink도 포함시켜야 한다.   React가 상태를 어떻게 전달해야 하는지 알 수 있게 하기 위함&lt;/li&gt;
&lt;li&gt;form이 hydrate되면, 이 매개변수는 더 이상 효과가 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-2. Returns&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 개의 값을 가지는 배열을 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;current state&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;액션에 의해 반환된 값&lt;/li&gt;
&lt;li&gt;첫 렌더링에서는 전달한 initialState&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;new action&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form 컴포넌트의 액션 속성으로, 또는 form 내의 버튼 컴포넌트의 formAction 속성으로 전달할 수 있는 새로운 액션&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-3. 주의사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 서버 컴포넌트와 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React 서버 컴포넌트를 지원하는 프레임워크와 사용하면, 클라이언트에서 JS가 실행되기 전에 form을 interactive하게 만들 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 서버 컴포넌트 없이 사용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컴포넌트의 로컬 state와 동일하다. (form의 상태가 컴포넌트 내에서만 유지되고 관리되며, form 상태와 관련된 로직은 클라이언트에서 JS가 로드되고 실행된 후에 처리된다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 함수 시그니처의 차이&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전달된 함수는 첫번째 인자로 이전 state를 받는다. (기존 form 함수는 보통 이벤트 객체만 받음)&lt;/li&gt;
&lt;li&gt;이는 함수가 useFormState 없이 직접 form 액션으로 사용될 때와 다른 시그니처(인자의 구성)을 갖게 만든다.&lt;/li&gt;
&lt;li&gt;이로인해 특정 조건 하에만 State를 업데이트하는 등 세밀한 제어가 가능해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 사용법&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;useFormState에서 반환 받은 formAction은 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;의 action prop으로 전달된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useFormState } from 'react-dom';
import { action } from './actions.js';

function MyComponent() {
  const [state, formAction] = useFormState(action, null);
  // ...
  return (
    &amp;lt;form action={formAction}&amp;gt;
      {/* ... */}
    &amp;lt;/form&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form이 제출되면 인자로 전달한 action 함수가 호출되고, 이 함수의 반환 값이 form의 새로운 state가 된다.&lt;/li&gt;
&lt;li&gt;제공한 액션은 첫 번째 인자로 currentState를 받고, 나머지 인자들은 useFormState를 사용하지 않았을 때와 동일하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function action(currentState, formData) {
  // ...
  return 'next state';
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;useFormStatus&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  현재는 Canary와 experimental channel에서만 사용할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마지막 양식 제출의 status 정보를 제공하는 Hook이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;const { pending, data, method, action } = useFormStatus();&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;1. Reference&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useFormStatus()&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래 예제에서처럼 status &lt;b&gt;정보를 얻을(이 hook을 호출할) 컴포넌트가 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 내부에 렌더링되어야&lt;/b&gt; 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useFormStatus } from &quot;react-dom&quot;;
import action from './actions';

function Submit() {
  const status = useFormStatus();
  return &amp;lt;button disabled={status.pending}&amp;gt;Submit&amp;lt;/button&amp;gt;
}

export default function App() {
  return (
    &amp;lt;form action={action}&amp;gt;
      &amp;lt;Submit /&amp;gt;
    &amp;lt;/form&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Parameters&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Returns&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) pending&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제출중이면 true&lt;/li&gt;
&lt;li&gt;아니면 false&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) data&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;form이 제출하는 데이터를 포함하는 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/FormData&quot;&gt;FormData 인터페이스&lt;/a&gt;를 구현한 객체&lt;/li&gt;
&lt;li&gt;활성화된 제출이 없거나 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt; 부모가 없으면 null이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3) method&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;get 또는 post (string)&lt;/li&gt;
&lt;li&gt;HTTP 메소드 중 어떤 것으로 제출되는지 알려준다.&lt;/li&gt;
&lt;li&gt;(기본적으로 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;은 GET 메소드를 사용하고, method 속성을 통해 지정할 수 있다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4) action&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;부모 &lt;code&gt;&amp;lt;form&amp;gt;&lt;/code&gt;의 action 속성에 전달된 함수에 대한 참조&lt;/li&gt;
&lt;li&gt;action 속성에 URI값이 제공되었거나 지정되지 않았으면 null이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 사용법&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. 대기 상태 표시하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;pending 꺼내와서 분기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function Submit() {
  const { pending } = useFormStatus();
  return (
    &amp;lt;button type=&quot;submit&quot; disabled={pending}&amp;gt;
      {pending ? &quot;Submitting...&quot; : &quot;Submit&quot;}
    &amp;lt;/button&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-2. 제출된 form 데이터 읽기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;data 꺼내와서 읽기&lt;/li&gt;
&lt;li&gt;data를 활용해서 이미 제출한 데이터를 보여줄 수 있다. 예시  &lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;import {useState, useMemo, useRef} from 'react';
import {useFormStatus} from 'react-dom';

export default function UsernameForm() {
  const {pending, data} = useFormStatus();

  const [showSubmitted, setShowSubmitted] = useState(false);
  const submittedUsername = useRef(null);
  const timeoutId = useRef(null);

  useMemo(() =&amp;gt; {
    if (pending) {
      submittedUsername.current = data?.get('username');
      if (timeoutId.current != null) {
        clearTimeout(timeoutId.current);
      }

      timeoutId.current = setTimeout(() =&amp;gt; {
        timeoutId.current = null;
        setShowSubmitted(false);
      }, 2000);
      setShowSubmitted(true);
    }
  }, [pending, data]);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;label&amp;gt;Request a Username: &amp;lt;/label&amp;gt;&amp;lt;br /&amp;gt;
      &amp;lt;input type=&quot;text&quot; name=&quot;username&quot; /&amp;gt;
      &amp;lt;button type=&quot;submit&quot; disabled={pending}&amp;gt;
        {pending ? 'Submitting...' : 'Submit'}
      &amp;lt;/button&amp;gt;
      {showSubmitted ? (
        &amp;lt;p&amp;gt;Submitted request for username: {submittedUsername.current}&amp;lt;/p&amp;gt;
      ) : null}
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>React</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/155</guid>
      <comments>https://e-juhee.tistory.com/entry/React-useFormState-useFormStatus#entry155comment</comments>
      <pubDate>Mon, 12 Feb 2024 02:19:27 +0900</pubDate>
    </item>
    <item>
      <title>왜 Hooks는 컴포넌트의 최상위 레벨에서 호출해야 하나요?</title>
      <link>https://e-juhee.tistory.com/entry/%EC%99%9C-Hooks%EB%8A%94-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%B5%9C%EC%83%81%EC%9C%84-%EB%A0%88%EB%B2%A8%EC%97%90%EC%84%9C-%ED%98%B8%EC%B6%9C%ED%95%B4%EC%95%BC-%ED%95%98%EB%82%98%EC%9A%94</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;hook을 작성할 때는 반드시 컴포넌트의 최상위 레벨 혹은 커스텀 훅 안에서만 작성할 수 있다.&lt;br /&gt;즉, 조건문, 반복문, 함수 내부에서는 hook을 호출할 수 없다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  참고한 글&lt;/p&gt;
&lt;figure id=&quot;og_1703851592502&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;React hooks: not magic, just arrays&quot; data-og-description=&quot;Untangling the rules around the proposal using diagrams&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e&quot; data-og-url=&quot;https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMMV0T/hyUXR6NoTb/s9Y1g4LkbKO0lNsluTJYGK/img.jpg?width=1200&amp;amp;height=940&amp;amp;face=0_0_1200_940&quot;&gt;&lt;a href=&quot;https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/@ryardley/react-hooks-not-magic-just-arrays-cd4f1857236e&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMMV0T/hyUXR6NoTb/s9Y1g4LkbKO0lNsluTJYGK/img.jpg?width=1200&amp;amp;height=940&amp;amp;face=0_0_1200_940');&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;React hooks: not magic, just arrays&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Untangling the rules around the proposal using diagrams&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;figure id=&quot;og_1703851579765&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;State: 컴포넌트의 메모리 &amp;ndash; React&quot; data-og-description=&quot;The library for web and native user interfaces&quot; data-og-host=&quot;react-ko.dev&quot; data-og-source-url=&quot;https://react-ko.dev/learn/state-a-components-memory#how-does-react-know-which-state-to-return&quot; data-og-url=&quot;https://react-ko.dev/learn/state-a-components-memory&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/glHIv/hyUXRFJbvt/G4KIAOS2v9onCmgcTQnjA1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/ED8s2/hyUXTKiLAE/pG49PKmjSBaOuakn1xzgFk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567&quot;&gt;&lt;a href=&quot;https://react-ko.dev/learn/state-a-components-memory#how-does-react-know-which-state-to-return&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://react-ko.dev/learn/state-a-components-memory#how-does-react-know-which-state-to-return&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/glHIv/hyUXRFJbvt/G4KIAOS2v9onCmgcTQnjA1/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567,https://scrap.kakaocdn.net/dn/ED8s2/hyUXTKiLAE/pG49PKmjSBaOuakn1xzgFk/img.png?width=1080&amp;amp;height=567&amp;amp;face=0_0_1080_567');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;State: 컴포넌트의 메모리 &amp;ndash; React&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The library for web and native user interfaces&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;react-ko.dev&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;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;nbsp;&lt;/div&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;useState를 호출하는 코드를 보면, 인자로 초기값만을 전달하고 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;const [index, setIndex] = useState(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;어떤 state 변수를 참조하는 지&lt;/b&gt;에 대한 정보는 받지 않는다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;code&gt;useState&lt;/code&gt;에 전달되는 &lt;b&gt;식별자&lt;/b&gt;가 없는데, 어떤 state 변수를 반환할지 어떻게 아는 것일까??  &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;hook은 동일한 컴포넌트의 모든 렌더링에서 안정적인 &lt;b&gt;호출 순서에 의존&lt;/b&gt;한다.&lt;br /&gt;최상위 수준에서만 hook을 호출한다는 규칙을 따르면 hook은 항상 같은 순서로 호출된다.&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;이렇게 매 렌더링마다 동일한 순서로 호출되는&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모든 hook은 컴포넌트마다 &lt;b&gt;배열&lt;/b&gt;로 관리된다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;hook이 호출되는 순서에 따라서 렌더링 간에 어떤 hook인지 파악한다.&lt;/h4&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;code&gt;useState&lt;/code&gt;가 내부적으로 어떻게 작동하는지 간략화한 함수이다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;let componentHooks = []; // 컴포넌트의 모든 훅을 저장하는 배열
let currentHookIndex = 0; // 현재 처리 중인 훅의 인덱스

function useState(initialState) {
  let pair = componentHooks[currentHookIndex];
  if (pair) {
    // 첫 번째 렌더링이 아닐 경우
    // pair를 반환하고 다음 훅 호출을 위해 index를 증가한다.
    currentHookIndex++;
    return pair;
  }

  // 첫 번째 렌더링이라면, componentHooks[0]에 상태 pair를 생성해서 저장한다.
  pair = [initialState, setState];

  function setState(nextState) {
    // 상태 변경을 요청하면, 새 값을 현재 pair에 저장한다.
    pair[0] = nextState;
    updateDOM();
  }

  // 다음 렌더링을 위해 현재 pair를 hooks 배열에 저장하고, 
  componentHooks[currentHookIndex] = pair;
  // 다음 훅 호출을 위해 index를 증가한다.
  currentHookIndex++;
  return pair;
}

function updateDOM() {
  // 현재 훅 인덱스를 리셋한다.
  currentHookIndex = 0;
  let output = Gallery();

  // DOM을 출력 내용과 일치하도록 업데이트한다.
  nextButton.onclick = output.onNextClick;
  header.textContent = output.header;
  moreButton.onclick = output.onMoreClick;
  // ...

}

let nextButton = document.getElementById('nextButton');
let header = document.getElementById('header');
let moreButton = document.getElementById('moreButton');
// ...

// UI를 초기 상태와 일치하게 만든다.
updateDOM();
&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;즉, 첫 번째 setState는 첫 번째 state 변수를 변경하고&lt;br /&gt;두 번째 setState는 두 번째 state 변수를 변경한다.&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;이런식으로 몇 번째에 호출된 hook인지에 따라서 값을 식별하므로 &lt;b&gt;순서가 보장되어야&lt;/b&gt; 하는 것!&lt;br /&gt;그리고 그 순서를 보장하기 위해 조건문이나 반복문, 중첩된 함수 내부에서 hook을 호출하지 않고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 최상위 레벨에서만 작성하는 것이다.  &lt;/p&gt;</description>
      <category>React</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/154</guid>
      <comments>https://e-juhee.tistory.com/entry/%EC%99%9C-Hooks%EB%8A%94-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%B5%9C%EC%83%81%EC%9C%84-%EB%A0%88%EB%B2%A8%EC%97%90%EC%84%9C-%ED%98%B8%EC%B6%9C%ED%95%B4%EC%95%BC-%ED%95%98%EB%82%98%EC%9A%94#entry154comment</comments>
      <pubDate>Fri, 29 Dec 2023 21:04:10 +0900</pubDate>
    </item>
    <item>
      <title>[React] useRef, forwardRef, useImperativeHandle</title>
      <link>https://e-juhee.tistory.com/entry/React-useRef</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;리렌더링을 일으키지 않고 정보를 &quot;기억하는&quot; 방법을 알아보자.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;컴포넌트가 특정 정보를 기억하도록 하고 싶지만&lt;br /&gt;그 정보가 새로운 렌더링을 촉발하지 않게 하려면 ref를 사용하면 된다!&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Docs&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://react.dev/reference/react/useRef&quot;&gt;useRef&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;  &lt;a href=&quot;https://react.dev/learn/referencing-values-with-refs&quot;&gt;Referencing Values with Refs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;  &lt;a href=&quot;https://react.dev/learn/manipulating-the-dom-with-refs&quot;&gt;Manipulating the DOM with Refs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;0. useRef&lt;/h1&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;useRef(initialValue) &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트의 최상위 레벨에서 useRef를 호출해서 ref를 선언한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

const ref = useRef(0);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef가 반환하는 객체&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;{ 
  current: 0 // The value you passed to useRef
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ref.current&lt;/code&gt; 속성을 통해 ref의 현재 값에 접근할 수 있다.&lt;/li&gt;
&lt;li&gt;이것은 React가 추적하지 않는 컴포넌트의 비밀 주머니 ㅋㅋㅋ 와 같다..&lt;/li&gt;
&lt;li&gt;ref는 React의 단방향 데이터 흐름에서 escape hatch가 된다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Parameters&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;initialValue&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ref 객체의 current 프로퍼티에 들어갈 초기 설정 값이다.&lt;/li&gt;
&lt;li&gt;어떤 유형의 값이든 지정할 수 있다.&lt;/li&gt;
&lt;li&gt;초기 렌더링 이후부터는 무시된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Returns&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단일 프로퍼티인 current를 가진 객체를 반환한다.&lt;/li&gt;
&lt;li&gt;current의 값은 처음에는 전달한 initialValue가 되고, 나중에 다른 값으로 바꿀 수 있다.&lt;/li&gt;
&lt;li&gt;반환 받은 이 객체 ref를 JSX 노드의 ref 속성으로 전달하면 JSX 노드가 current에 담긴다.&lt;/li&gt;
&lt;li&gt;다음 렌더링에서도 useRef는 동일한 객체를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 주의사항&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기화를 제외하고는 렌더링 중에 ref.current를 읽거나 쓰면 안된다. (컴포넌트의 동작을 예측할 수 없게 되기 때문)&lt;/li&gt;
&lt;li&gt;Stric Mode에서는 컴포넌트 함수가 두 번씩 호출되어 의도하지 않은 동작을 찾는 데 도움을 준다.&lt;br /&gt;ref 객체도 두 번 생성되고 그 중 하나는 버려진다. 컴포넌트 함수는 순수해야 하므로 이것이 로직에 영향을 미치지는 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. state와의 차이&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ref의 current 속성은 state와 달리 읽고 수정할 수 있는 일반 JS 객체이다.&lt;br /&gt;단, 렌더링에 사용되는 객체(ex. state의 일부)를 포함하는 경우에는 변이해서는 안된다.&lt;/li&gt;
&lt;li&gt;ref.current가 변경되어도 리렌더가 일어나지 않는다.&lt;/li&gt;
&lt;li&gt;리렌더링되어도 값이 유지되는 것은 state와 동일하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;refs&lt;/th&gt;
&lt;th&gt;state&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;사용법&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useRef()&lt;/code&gt;는 &lt;code&gt;{ current: initialValue }&lt;/code&gt;을 반환&lt;/td&gt;
&lt;td&gt;&lt;code&gt;useState()&lt;/code&gt;는 state 변수의 현재값과 state 설정자함수([value, setValue])를 반환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;리렌더링&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;변경 시 리렌더링을 촉발하지 않음&lt;/td&gt;
&lt;td&gt;변경 시 리렌더링을 촉발함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;변경 가능성&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Mutable &amp;mdash; 렌더링 프로세스 외부에서 current 값을 수정하고 업데이트할 수 있음&lt;/td&gt;
&lt;td&gt;Immutable &amp;mdash; setState를 사용해서 state 변수를 수정해 리렌더링을 대기열에 추가해야함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;값 접근&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;렌더링 중에는 current 값을 읽거나 쓰지 않아야 함&lt;/td&gt;
&lt;td&gt;언제든지 state를 읽을 수 있음. 각 렌더링에는 변경되지 않는 자체 state snapshot이 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  렌더링에 보여줘야 하는 값은 state로,&lt;br /&gt;렌더링에 사용되지 않는 값은(값이 바뀌어도 리렌더링될 필요 없는 경우) ref로 보관하는 것이 더 효율적일 수 있다.&lt;/p&gt;
&lt;h1&gt;3. 사용법&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-1. ref를 사용하면 다른점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;state VS ref&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ref는 변경되어도 리렌더를 촉발하지 않는다. 따라서 ref는 컴포넌트의 UI에 영향을 미치지 않는 정보를 저장하는 데 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일반 변수 VS ref&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;재설정&lt;/b&gt;되는 일반 변수와 달리 정보가 &lt;b&gt;유지&lt;/b&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;외부 변수 VS ref&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정보가 공유되는 외부 변수와 달리 각 컴포넌트에 로컬로 저장된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-2. ref에 올바르게 접근하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트는 순수 함수처럼 동작해야 하므로, 렌더링 중에 ref를 읽거나 쓰면 안된다!&lt;br /&gt;컴포넌트 최상위 레벨에서 ref에 접근하는 코드가 있으면 안 된다는 뜻&lt;br /&gt;(렌더링 중에 읽거나 써야만 한다면, ref 대신 state를 쓰자!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이렇게 하면 안됨&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;function MyComponent() {
  // ...
  //   Don't write a ref during rendering
  myRef.current = 123;
  // ...
  //   Don't read a ref during rendering
  return &amp;lt;h1&amp;gt;{myOtherRef.current}&amp;lt;/h1&amp;gt;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 &lt;b&gt;이벤트 핸들러&lt;/b&gt;나 &lt;b&gt;useEffect&lt;/b&gt; 안에서 읽거나 쓰자&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;function MyComponent() {
  // ...
  useEffect(() =&amp;gt; {
    // ✅ You can read or write refs in effects
    myRef.current = 123;
  });
  // ...
  function handleClick() {
    // ✅ You can read or write refs in event handlers
    doSomething(myOtherRef.current);
  }
  // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-3. DOM 조작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 DOM을 자동으로 업데이트하므로 컴포넌트가 DOM을 자주 조작할 필요는 없지만,&lt;br /&gt;때로 노드에 포커싱하거나 스크롤, 크기나 위치 측정을 위해 DOM 요소에 접근해야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에는 이러한 작업을 수행할 수 있는 내장 기능이 없으므로 DOM 노드에 대한 ref가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref로 DOM을 조작하는 것은 일반적이다. React에는 이를 위한 빌트인 기능도 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) 초기값이 null인 ref 객체를 생성한다.&lt;/h3&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { useRef } from 'react';

function MyComponent() {
  const inputRef = useRef(null);
  // ...&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 조작하려는 DOM 노드의 JSX에 ref를 전달한다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DOM 노드를 가져올 JSX 태그에 &lt;code&gt;ref&lt;/code&gt; 속성으로 참조를 전달한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;  return &amp;lt;input ref={inputRef} /&amp;gt;;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) 끝!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰면 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 inputRef.current는 null이 된다.&lt;br /&gt;React가 DOM 노드를 생성하고 화면에 배치한 후, ref 객체의 current 속성이 이 DOM 노드로 설정된다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;  function handleClick() {
    inputRef.current.focus();
  }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드가 화면에서 제거되면 ref의 current는 다시 null이 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ref 콜백&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref가 몇 개 필요한지 모를 때는 사용법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시는 목록의 각 항목에 ref를 전달하려 하고 있다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;ul&amp;gt;
  {items.map((item) =&amp;gt; {
    // Doesn't work!
    const ref = useRef(null);
    return &amp;lt;li ref={ref} /&amp;gt;;
  })}
&amp;lt;/ul&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;But, 훅은 컴포넌트의 최상위 레벨에서만 호출해야 하기 때문에 useRef를 반복문 안에서 호출할 수는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 ref 속성에 함수를 전달하는 ref 콜백을 사용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;import { useRef } from 'react';

export default function CatFriends() {
  const itemsRef = useRef(null);

  function scrollToId(itemId) {
    const map = getMap();
    const node = map.get(itemId);
    node.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest',
      inline: 'center'
    });
  }

  function getMap() {
    if (!itemsRef.current) {
      // Initialize the Map on first usage.
      itemsRef.current = new Map();
    }
    return itemsRef.current;
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;nav&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; scrollToId(0)}&amp;gt;
          Tom
        &amp;lt;/button&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; scrollToId(5)}&amp;gt;
          Maru
        &amp;lt;/button&amp;gt;
        &amp;lt;button onClick={() =&amp;gt; scrollToId(9)}&amp;gt;
          Jellylorum
        &amp;lt;/button&amp;gt;
      &amp;lt;/nav&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;ul&amp;gt;
          {catList.map(cat =&amp;gt; (
            &amp;lt;li
              key={cat.id}
              ref={(node) =&amp;gt; {
                const map = getMap();
                if (node) {
                  map.set(cat.id, node);
                } else {
                  map.delete(cat.id);
                }
              }}
            &amp;gt;
              &amp;lt;img
                src={cat.imageUrl}
                alt={'Cat #' + cat.id}
              /&amp;gt;
            &amp;lt;/li&amp;gt;
          ))}
        &amp;lt;/ul&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

const catList = [];
for (let i = 0; i &amp;lt; 10; i++) {
  catList.push({
    id: i,
    imageUrl: 'https://placekitten.com/250/200?image=' + i
  });
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-4. Ref의 내용 재생성 피하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React는 초기에 ref 값을 한 번 저장하고 다음 렌더링부터는 이를 무시한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function Video() {
  const playerRef = useRef(new VideoPlayer());
  // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드에서 &lt;code&gt;new VideoPlayer()&lt;/code&gt;의 결과는 초기 렌더링에만 사용되지만,&lt;br /&gt;&lt;b&gt;호출 자체는 이후의 모든 렌더링에서도 여전히 계속 이뤄진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef가 초기값을 첫 마운트 때만 사용한다는 것은, 이 부분(&lt;code&gt;new VideoPlayer()&lt;/code&gt;)이 호출되지 않는다는 뜻이 아니다!&lt;br /&gt;렌더링마다 실행은 매번 되어 인스턴스를 매번 생성한다.&lt;br /&gt;만들어놓고 쓰지는 않음..ㅋㅋ&lt;br /&gt;생성하는 데 비용이 발생하므로 좋지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하려면 아래와 같이 초기화하면 된다.&lt;/p&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;function Video() {
  const playerRef = useRef(null);
  if (playerRef.current === null) {
    playerRef.current = new VideoPlayer();
  }
  // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 이렇게 렌더링 중에 ref.current를 쓰거나 읽으면 안되지만,&lt;br /&gt;이 경우에는 결과가 항상 동일하고 초기화 중에만 조건이 실행되어 충분히 예측이 가능하므로 괜찮다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-5. ref의 작동 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 useState로 구현할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// Inside of React
function useRef(initialValue) {
  const [ref, unused] = useState({ current: initialValue });
  return ref;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;useRef는 항상 동일한 객체를 반환하기 때문에 state 설정자는 사용되지 않는다.&lt;br /&gt;즉, ref는 setter가 없는 일반 state 변수로 생각할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 useRef를 기본적으로 제공해주므로, 이딴식으로 쓰지 말고 useRef를 쓰도록 ^ㅁ^&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-6. 언제 쓰나요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 React의 범위를 벗어나서 외부 API와 통신할 때 사용한다.&lt;br /&gt;주로 컴포넌트의 모양에 영향을 주지 않는 브라우저 API 등과 통신할 때 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타임아웃 ID 저장&lt;/li&gt;
&lt;li&gt;DOM 요소 저장 or 조작&lt;/li&gt;
&lt;li&gt;JSX 계산에 필요하지 않은 다른 객체들 저장할 때 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트에 일부 값을 저장해야 하지만 렌더링 로직에는 영향을 미치지 않는 경우 ref를 쓰자.&lt;br /&gt;이러한 상황에서 React의 일반적인 데이터 흐름 내에서 정보를 추적하기보다는&lt;br /&gt;ref를 사용해 정보를 직접적으로 관리하는 것이 더 효율적일 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-7. ref 모범사례&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 원칙을 따라 컴포넌트의 예측 가능성을 높일 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1) ref를 escape hatch로 취급하자!&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱의 주요 로직과 데이터 흐름이 ref에 크게 의존한다면, 접근 방식을 재고해볼 필요가 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2) 렌더링 중에는 읽거나 쓰지 말자&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React는 ref.current가 언제 변경되는지 알 수 없기 때문에, 렌더링 중에 ref.current를 읽는 것은 컴포넌트의 동작을 예측하기 어렵게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외) 아래처럼 첫 렌더링 중에만 ref를 설정하는 것은 ㄱㅊ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;if (!ref.current) ref.current = new Thing()&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;state는 동기적으로 업데이트되지 않지만, ref는 값을 변이하면 즉시 변경된다.&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;ref.current = 5;
console.log(ref.current); // 5&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3) ref를 이용한 DOM 조작 모범 사례&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포커싱이나 스크롤처럼 비파괴적인 동작에는 문제가 없다.&lt;br /&gt;그러나 DOM을 수동으로 수정하려 하면 React가 수행하는 변경 사항과 충돌할 위험이 있다..!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, DOM 엘리먼트를 수동으로 제거(&lt;code&gt;remove()&lt;/code&gt;)하고 setState를 통해 다시 표시하려고 하는 경우 충돌이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 React가 관리하는 DOM 노드를 변경할 때는 주의가 필요하다.&lt;br /&gt;위 상황과 달리 React가 업데이트할 이유가 없는 DOM 요소는 안전하게 수정할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3-8. ref는 언제 첨부되나요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서 모든 업데이트는 두 단계로 나뉜다.&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;커밋: 커밋하는 동안 DOM에 변경 사항을 적용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 렌더링을 할 때는 DOM 노드들이 아직 생성되지 않았기 때문에 ref.current는 null이 된다.&lt;br /&gt;그리고 업데이트의 렌더링 중에는, DOM 노드들이 아직 업데이트되지 않았기 때문에 아직 읽기에는 너무 이르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 React는 commit 중에 ref.current를 설정한다!&lt;br /&gt;DOM을 업데이트한 후, 즉시 ref.current 값을 해당 DOM 노드로 설정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 이벤트 핸들러에서 ref에 접근하는데, 마땅한 특정 이벤트가 없다면 Effect가 필요할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;flushSync&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flushSync는 React의 DOM 렌더러 내에서 제공하는 API로, 동기적으로 상태 업데이트를 강제로 수행할 때 사용된다.&lt;br /&gt;일반적인 React의 상태 업데이트는 비동기적으로 동작하는데, 상태 변경을 즉시 반영하고 싶을 때 flushSync를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에서는 state 업데이트가 큐에 등록되므로 DOM을 즉시 업데이트하지 않아 종종 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목록에 새 항목을 추가하고 마지막 항목으로 스크롤하려면 아래 예시처럼 flushSync를 사용해서 동기적으로 업데이트를 해줘야 원하는 동작이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;flushSync(() =&amp;gt; {
  setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView();&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Troubleshooting&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다른 컴포넌트의 DOM 노드에 접근하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;&amp;lt;input/&amp;gt;&lt;/code&gt; 같은 브라우저 요소를 출력하는 빌트인 컴포넌트에 ref를 넣으면 잘 작동하지만,&lt;br /&gt;컴포넌트에 ref를 넣으려고 하면 null이 반환되고 원하는 동작을 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔에 이런 오류도 뜸  &lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

Check the render method of `MyForm`.
    at MyInput
    at MyForm (https://1e4ad8f7.sandpack-bundler-4bw.pages.dev/App.js:24:38)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유는&lt;br /&gt;기본적으로 컴포넌트가 다른 컴포넌트의 DOM 노드에 접근하는 것을 허용하지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신, 자신의 DOM 노드를 공개하고 싶은 컴포넌트는 자신의 자식 중 하나에게 ref를 전달한다고 &lt;code&gt;forwardRef&lt;/code&gt;로 명시할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴포넌트를 forwardRef를 사용해서 선언하면, props 다음의 두 번째 ref 인자로 전달 받은 ref를 받도록 설정된다.&lt;/p&gt;
&lt;pre class=&quot;verilog&quot;&gt;&lt;code&gt;import { forwardRef, useRef } from 'react';

const MyInput = forwardRef((props, ref) =&amp;gt; {
  return &amp;lt;input {...props} ref={ref} /&amp;gt;;
});

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;MyInput ref={inputRef} /&amp;gt;
      &amp;lt;button onClick={handleClick}&amp;gt;
        Focus the input
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;내부 DOM 노드에 대한 접근 제한하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부모로부터 받은 ref에 대한 사용자 정의 인터페이스 제공하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예시에서, 부모 컴포넌트가 MyInput에 focus()를 호출할 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 하면 부모 컴포넌트가 MyInput에 대해 다른 작업도 할 수 있게 된다.&lt;br /&gt;그렇지 못하게 제한하고 싶다면 &lt;code&gt;useImperativeHandle&lt;/code&gt;을 사용하면 된다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;const MyInput = forwardRef((props, ref) =&amp;gt; {
  const realInputRef = useRef(null);
  useImperativeHandle(ref, () =&amp;gt; ({
    // Only expose focus and nothing else
    focus() {
      realInputRef.current.focus();
    },
  }));
  return &amp;lt;input {...props} ref={realInputRef} /&amp;gt;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 부모 컴포넌트에 대한 ref 값으로 특별한 객체를 제공하도록 React에 지시해서&lt;br /&gt;focus 메서드만 가지게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 ref 핸들은 DOM 노드가 아닌, &lt;code&gt;useImperativeHandle&lt;/code&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;useImperativeHandle에 대해 자세히 알아보자 ㅎㅎ&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;useImperativeHandle&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref로 노출되는 핸들을 직접 정의할 수 있게 해주는 훅이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Parameters&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;ref&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;forwardRef 함수에서 두 번째 인자로 받은 ref&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;createHandle&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노출하려는 ref 핸들을 반환하는 함수&lt;/li&gt;
&lt;li&gt;인자는 없다.&lt;/li&gt;
&lt;li&gt;어떤 방식으로 작성하든 상관 없지만, 일반적으로 노출하려는 메서드가 있는 객체를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(optional) dependencies&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;createHandle 코드 내에서 참조하는 모든 반응형 값을 나열한 목록&lt;/li&gt;
&lt;li&gt;이 반응형 값은 props, state 및 컴포넌트 내에서 직접 선언한 모든 변수와 함수를 포함한다.&lt;/li&gt;
&lt;li&gt;이것도 역시나 린터가 올바르게 의존성을 지정했는지 확인해줌  &lt;/li&gt;
&lt;li&gt;이 의존성이 변경되거나 인수를 생략하면 createHandle 함수가 다시 실행되고 새로 생성된 핸들이 ref에 할당된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Returns&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;undefined를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Usage&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;부모 컴포넌트에 커스텀 ref 핸들 노출&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 컴포넌트는 자식 컴포넌트의 DOM 노드를 부모 컴포넌트에 노출하지 않는다.&lt;/li&gt;
&lt;li&gt;따라서 부모 컴포넌트가 자식의 DOM 노드에 접근하려면 forwardRef를 사용해서 선택적으로 참조에 포함시켜줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { forwardRef } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  return &amp;lt;input {...props} ref={ref} /&amp;gt;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위 코드에서는 ref로 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;의 DOM 노드를 받게 되는데, 커스텀한 값을 노출할 수 있다.&lt;/li&gt;
&lt;li&gt;이것을 커스텀하려면 useImperativeHandle을 사용하면 되는 것!&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { forwardRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  useImperativeHandle(ref, () =&amp;gt; {
    return {
      // ... your methods ...
    };
  }, []);

  return &amp;lt;input {...props} /&amp;gt;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 작성하면 이제 ref에 &lt;code&gt;&amp;lt;input&amp;gt;&lt;/code&gt;이 전달되지 않게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;focus와 scrollIntoView만 노출시켜보자&lt;br /&gt;그러기 위해서는 실제 DOM을 별도의 ref에 유지해야 한다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;import { forwardRef, useRef, useImperativeHandle } from 'react';

const MyInput = forwardRef(function MyInput(props, ref) {
  const inputRef = useRef(null);

  useImperativeHandle(ref, () =&amp;gt; {
    return {
      focus() {
        inputRef.current.focus();
      },
      scrollIntoView() {
        inputRef.current.scrollIntoView();
      },
    };
  }, []);

  return &amp;lt;input {...props} ref={inputRef} /&amp;gt;;
});&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;사용자 정의 명령 노출&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;imperative handle을 통해 노출하는 메서드가 DOM 메서드일 필요는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 Post 컴포넌트는 imperative handle을 통해 &lt;code&gt;ScrollAndFocusAddComment&lt;/code&gt; 메서드를 표시한다.&lt;/p&gt;
&lt;pre class=&quot;lisp&quot;&gt;&lt;code&gt;const Post = forwardRef((props, ref) =&amp;gt; {
  const commentsRef = useRef(null);
  const addCommentRef = useRef(null);

  useImperativeHandle(ref, () =&amp;gt; {
    return {
      scrollAndFocusAddComment() {
        commentsRef.current.scrollToBottom();
        addCommentRef.current.focus();
      }
    };
  }, []);

  return (
    &amp;lt;&amp;gt;
      &amp;lt;article&amp;gt;
        &amp;lt;p&amp;gt;Welcome to my blog!&amp;lt;/p&amp;gt;
      &amp;lt;/article&amp;gt;
      &amp;lt;CommentList ref={commentsRef} /&amp;gt;
      &amp;lt;AddComment ref={addCommentRef} /&amp;gt;
    &amp;lt;/&amp;gt;
  );
});&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Pitfall&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ref를 과도하게 사용하지 말 것!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ref는 props로 표현할 수 없는 필수적인 행동에만 써야 한다.&lt;br /&gt;ex) 특정 노드로 스크롤하기, 초점 맞추기, 애니메이션 촉발하기, 텍스트 선택하기 등&lt;/li&gt;
&lt;li&gt;props으로 표현할 수 있는 것은 ref를 사용하지 말아야 한다.&lt;br /&gt;ex) Modal 컴포넌트에서 {open, close}와 같은 imperative handle을 노출하는 대신 isOpen 등의 prop을 사용하고, Effect를 통해 명령형 동작을 노출할 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>React</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/153</guid>
      <comments>https://e-juhee.tistory.com/entry/React-useRef#entry153comment</comments>
      <pubDate>Tue, 12 Dec 2023 20:56:19 +0900</pubDate>
    </item>
    <item>
      <title>[KAIST 정글] 나만무 프로젝트 회고 (2) :: 씽잉러너 만들기</title>
      <link>https://e-juhee.tistory.com/entry/KAIST-%EC%A0%95%EA%B8%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-2-%EC%94%BD%EC%9E%89%EB%9F%AC%EB%84%88-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
      <description>&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP6brO/btsyTXhZIuU/x2PKRx9fWkNorK3vWoBKm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP6brO/btsyTXhZIuU/x2PKRx9fWkNorK3vWoBKm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP6brO/btsyTXhZIuU/x2PKRx9fWkNorK3vWoBKm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP6brO%2FbtsyTXhZIuU%2Fx2PKRx9fWkNorK3vWoBKm0%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;448&quot; height=&quot;252&quot; data-origin-width=&quot;448&quot; data-origin-height=&quot;252&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;노래만 부르면 아쉬우니까 여기에 아이템으로 공격을 할 수 있고 방어 아이템을 쓰거나 특정 행동으로 공격을 회피할 수도 있다.&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;그래픽 관련 기술을 새로 배워서 하기엔 시간이 너무 촉박할 것 같고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트에서 잘할 수 있는 걸 하고 싶었지 겨우겨우 해내는 걸 하고 싶지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 내 실력에서 어떻게 구현할 수 있을까, 3d 기술은 얼마나 공부해야 할 수 있는 건가를 많이 찾아봤다.&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;하지만 이걸 본 내 생각은, 이것도 어떻게 만드는지 모르겠는데..? 어려워 보이는데?ㅋㅋㅋ ㅠㅠㅠㅠ 잔뜩 쫄았음&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tkVDZ/btsyT3WPNHU/YGnqkKwNDPZbtkKbB1YowK/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tkVDZ/btsyT3WPNHU/YGnqkKwNDPZbtkKbB1YowK/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tkVDZ/btsyT3WPNHU/YGnqkKwNDPZbtkKbB1YowK/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtkVDZ%2FbtsyT3WPNHU%2FYGnqkKwNDPZbtkKbB1YowK%2Fimg.webp&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;540&quot; height=&quot;243&quot; data-origin-width=&quot;540&quot; data-origin-height=&quot;243&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;이전부터 눈독 들이던 ThreeJS로 3D 세상을 구현하는 &lt;a href=&quot;https://www.inflearn.com/course/3d-%EC%9D%B8%ED%84%B0%EB%9E%99%ED%8B%B0%EB%B8%8C-%EC%9B%B9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;강의&lt;/a&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;316&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HMt9J/btsyT0sf2OW/eaAXpu50m2VVJf2eguc0S1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HMt9J/btsyT0sf2OW/eaAXpu50m2VVJf2eguc0S1/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HMt9J/btsyT0sf2OW/eaAXpu50m2VVJf2eguc0S1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/HMt9J/btsyT0sf2OW/eaAXpu50m2VVJf2eguc0S1/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;316&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;316&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;  요런 느낌으로 가보기로 결정.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;IMG_6222.PNG&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dBvRF3/btsyTWi7zWr/R4e39NWb1LeXMrNz4MG2t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dBvRF3/btsyTWi7zWr/R4e39NWb1LeXMrNz4MG2t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dBvRF3/btsyTWi7zWr/R4e39NWb1LeXMrNz4MG2t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdBvRF3%2FbtsyTWi7zWr%2FR4e39NWb1LeXMrNz4MG2t0%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;282&quot; height=&quot;611&quot; data-filename=&quot;IMG_6222.PNG&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;2556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;초안 발표를 준비하며 보낸 일주일은 그렇게 소멸&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;기술적 챌린지도 4개로 늘어났다 (대충 쥐어짜 내서 고작 이게 챌린지인가 싶은 것도 욱여넣었다)&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;오락가락했다 ㅋㅋㅋ&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;재제출 기한이 아침 10시였나?&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;fileblock&quot; data-ke-align=&quot;alignCenter&quot;&gt;&lt;a href=&quot;https://blog.kakaocdn.net/dn/F4lOT/btsyRVeuRCP/oN6jzkMcgEr5gwaMPruomK/%E1%84%8A%E1%85%B5%E1%86%BC%E1%84%8B%E1%85%B5%E1%86%BC%E1%84%85%E1%85%A5%E1%84%82%E1%85%A5.pdf?attach=1&amp;amp;knm=tfile.pdf&quot; class=&quot;&quot;&gt;
    &lt;div class=&quot;image&quot;&gt;&lt;/div&gt;
    &lt;div class=&quot;desc&quot;&gt;&lt;div class=&quot;filename&quot;&gt;&lt;span class=&quot;name&quot;&gt;씽잉러너.pdf&lt;/span&gt;&lt;/div&gt;
&lt;div class=&quot;size&quot;&gt;2.00MB&lt;/div&gt;
&lt;/div&gt;
  &lt;/a&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;기획에 너무 시간을 많이 써서 잔뜩 지쳐버린 우리 팀은 그럼에도 불구하고 이대로 진행하기로 했다! 우리의 감을 믿어보자..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;화면 설계&lt;/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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;819&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RxJWd/btsyUdSGtIY/3NDOSuKAf4AKdOqDJrGKQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RxJWd/btsyUdSGtIY/3NDOSuKAf4AKdOqDJrGKQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RxJWd/btsyUdSGtIY/3NDOSuKAf4AKdOqDJrGKQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRxJWd%2FbtsyUdSGtIY%2F3NDOSuKAf4AKdOqDJrGKQ1%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;807&quot; height=&quot;819&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;819&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;/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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qJ8DQ/btsyUgWeRRs/11kQEHahZftQz4DwDJes10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qJ8DQ/btsyUgWeRRs/11kQEHahZftQz4DwDJes10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qJ8DQ/btsyUgWeRRs/11kQEHahZftQz4DwDJes10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqJ8DQ%2FbtsyUgWeRRs%2F11kQEHahZftQz4DwDJes10%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;1195&quot; height=&quot;662&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;662&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;796&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5MZM4/btsyXXg3rh2/IDybOpuYLmof9R3taQzy5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5MZM4/btsyXXg3rh2/IDybOpuYLmof9R3taQzy5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5MZM4/btsyXXg3rh2/IDybOpuYLmof9R3taQzy5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5MZM4%2FbtsyXXg3rh2%2FIDybOpuYLmof9R3taQzy5K%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;952&quot; height=&quot;796&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;796&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;818&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b04Oz9/btsyUMAuUCV/UJSsG69CqtpPljuLW1IcI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b04Oz9/btsyUMAuUCV/UJSsG69CqtpPljuLW1IcI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b04Oz9/btsyUMAuUCV/UJSsG69CqtpPljuLW1IcI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb04Oz9%2FbtsyUMAuUCV%2FUJSsG69CqtpPljuLW1IcI1%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;439&quot; height=&quot;405&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;818&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;ERD 설계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 내가 Three.js 강의를 듣는 동안 백엔드를 맡은 팀원들이 ERD를 설계했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB를 정글 과정에서 배우지 않기 때문에 노베이스에서 진행하다 보니 이것도 꽤 오래 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 배웠는데도 실제로 안 짜봐서 별 도움이 되지 못했음..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;619&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hb9Qy/btsyUJKA8np/Mx9mmgKmEcQI5z8Knm76TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hb9Qy/btsyUJKA8np/Mx9mmgKmEcQI5z8Knm76TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hb9Qy/btsyUJKA8np/Mx9mmgKmEcQI5z8Knm76TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHb9Qy%2FbtsyUJKA8np%2FMx9mmgKmEcQI5z8Knm76TK%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;649&quot; height=&quot;425&quot; data-origin-width=&quot;945&quot; data-origin-height=&quot;619&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;우리의 중간발표 목표는 '아이템전에서 2명이서 노래하면서 달리면서 물파리 공격'까지 하는 것으로 정했다 ㅋㅋㅋ&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVK3Rj/btsyYoTbKeq/OCWOiHCyeDdgW3nz0bY3E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVK3Rj/btsyYoTbKeq/OCWOiHCyeDdgW3nz0bY3E1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVK3Rj/btsyYoTbKeq/OCWOiHCyeDdgW3nz0bY3E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVK3Rj%2FbtsyYoTbKeq%2FOCWOiHCyeDdgW3nz0bY3E1%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;722&quot; height=&quot;286&quot; data-origin-width=&quot;722&quot; data-origin-height=&quot;286&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;h3 data-ke-size=&quot;size23&quot;&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;하지만 내가 보는 강의는 JavaScript에서 Three.js를 쓰는 강의였고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 쓰는 건 Next.js였고...&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_IMG_6225 중간.jpeg&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGDdAJ/btsyX0riYqf/SHXidNjieCIf8nDwzY3iO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGDdAJ/btsyX0riYqf/SHXidNjieCIf8nDwzY3iO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGDdAJ/btsyX0riYqf/SHXidNjieCIf8nDwzY3iO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGDdAJ%2FbtsyX0riYqf%2FSHXidNjieCIf8nDwzY3iO1%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;394&quot; height=&quot;336&quot; data-filename=&quot;edited_IMG_6225 중간.jpeg&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;336&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;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/441881225&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Yk9FH/hyUgF1G4FA/pR1zO7ovtkKJ41X0YOe8Qk/img.jpg?width=1080&amp;amp;height=1920&amp;amp;face=0_0_1080_1920,https://scrap.kakaocdn.net/dn/vkiPe/hyUgVcnGXn/lTcGYoMDki3DFoUHpNNzIK/img.jpg?width=1080&amp;amp;height=1920&amp;amp;face=0_0_1080_1920&quot; data-video-width=&quot;300&quot; data-video-height=&quot;533&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1529&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/441881225?service=daum_tistory&quot; width=&quot;300&quot; height=&quot;533&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;아이템 효과로 넣기로 한 빙그르르 돌아가는 것도&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;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/441881232&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/CnFVt/hyUgNFno9f/9n3SB1qFNPFGktMYzSsuQ1/img.jpg?width=1080&amp;amp;height=1920&amp;amp;face=0_0_1080_1920,https://scrap.kakaocdn.net/dn/lA3uC/hyUgRufIaS/NG1KTXFQStpkKkSQWLVWh1/img.jpg?width=1080&amp;amp;height=1920&amp;amp;face=0_0_1080_1920&quot; data-video-width=&quot;300&quot; data-video-height=&quot;533&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1529&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/441881232?service=daum_tistory&quot; width=&quot;300&quot; height=&quot;533&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;안개도 깔고 빛도 조정하고 이것저것 추가하고 나서&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;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/441881262&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/NQo1Q/hyUgMsX4Lu/zjLDrkkA5rO5kWgmkcZBF0/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920,https://scrap.kakaocdn.net/dn/DsjVj/hyUgLHz68j/ru5VcnkKecCJiJhuHQ6cf1/img.jpg?width=886&amp;amp;height=1920&amp;amp;face=0_0_886_1920&quot; data-video-width=&quot;300&quot; data-video-height=&quot;650&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1864&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/441881262?service=daum_tistory&quot; width=&quot;300&quot; height=&quot;650&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;노래와 채점을 붙이고 실제로 플레이를 처음 했을 때&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;h3 data-ke-size=&quot;size23&quot;&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;span&gt;&amp;nbsp;&lt;/span&gt;발표 연습은커녕 이틀 동안 잠도 30분 정도 잤나..? 혀 씹고 발음 꼬이고 난장판인 발표였다 ㅋㅋㅋ&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기능이 적다는 피드백에 적잖이 충격을 받았기 때문에&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;발표에 앞으로 구현할 화면들을 다 넣어서 일일이 하나하나 다 설명했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=J7Vf6Do1zzs&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/boA3lk/hyUgJXhN8U/XtPXuxFgI0JoXNAVAseT5k/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/J7Vf6Do1zzs&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중간발표 피드백&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코치님의 피드백&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임 특성상 설명을 앞에 할 수밖에 없을 것 같긴 한데 설명이 너무 지루할 수 있겠다&lt;/li&gt;
&lt;li&gt;게임이 둘이서 하는 건데 혼자서 하는 걸 보여주니까 솔로 플레이로 보일 수 있겠다&lt;/li&gt;
&lt;li&gt;실제로 만들어보면서 느꼈겠지만 재미없는 부분이 있다.  &lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 다른 코치님의 피드백&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;재밌을 것 같다!&lt;/li&gt;
&lt;li&gt;영상보다는 시연을 직접 하는 게 낫겠다&lt;/li&gt;
&lt;li&gt;혼자 하는 것보다 같이 하는 게 좋겠다&lt;/li&gt;
&lt;li&gt;아이템을 다양하게 보여주면 좋겠다&lt;/li&gt;
&lt;li&gt;발표할 때 시나리오를 재밌게 만들어라&lt;/li&gt;
&lt;/ul&gt;
&lt;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;h3 data-ke-size=&quot;size23&quot;&gt;최종 발표를 향하여&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간발표 때까지의 구현 속도가 빨랐던 것은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 보여주기만 하면 돼.. 였기 때문이었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 진짜를 만들어야 하는데, 이런저런 잦은 버그들을 잡는 것이 참 힘들었다..&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;눈사람으로 변한 캐릭터가 또 눈사람이 되어서 눈사람이 두 개가 되어버리고 평생 탈출하지 못하는 등의 버그들 ㅠㅠ&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;인게임 밖에서는 플레이하다가 통신이 끊기는 등의 문제들이 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마무리 발표 때는 게임이 시작을 안 해서 2분 정도 정적이 흐르는 대참사도 겪었다..ㅋㅋㅋㅋㅋ&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;같이 플레이하는 방식으로 시연을 변경했더니 이번에는 너무 정신없으니 줄이라는 피드백을 받기도 했다.  &lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;심지어 우리의 히든 아이템이라고 생각했던 소리를 질러서 탈출하는 아이템은, 청중들이 불쾌해할 거라는 조언도 있었지...&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;매 발표마다 큰 산을 넘는 심정으로 잠을 더더욱 줄여가며 우리의 씽잉러너를 발전시켜 나갔다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/441882391&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cmMJHs/hyUgGzwrzK/Y68yKp0dJ40tDVUiXe2GN0/img.jpg?width=1080&amp;amp;height=608&amp;amp;face=0_0_1080_608,https://scrap.kakaocdn.net/dn/b9ZcJW/hyUgPJ0mqe/1PDFK45E2Ur3qULuF1mk40/img.jpg?width=1080&amp;amp;height=608&amp;amp;face=0_0_1080_608&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/441882391?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&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;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=_YiVBs4rrZ4&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cDn87i/hyUgRug0La/5FvKGcMuNhEkz51k14ywGk/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/_YiVBs4rrZ4&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;발표 딱 끝나고 자리에 앉았는데 정글 선배 한 분이 오셔서&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왜 이렇게 잘했냐며 엄~청나게 칭찬을 해주셨다.&amp;nbsp;포스터 세션에도 몇 번이나 오셔서 이것저것 물어보시고 취업에 대한 조언도 해주시고,,,&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나뿐만 아니라 우리 팀원들도 다들 표정이 몽글몽글 했다 ㅋㅋ&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;그동안 힘들었던 거 다 보상받는 기분이었다.&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다른 협력사 분들도 '멋져요', '잘 봤어요' 등 칭찬을 많이 해주셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;옆 반에서도 몇몇 분들께서 오셔서 신기하다고 어떻게 구현했는지 물어보기도 하셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;교수님께서도 발표 잘 봤다고 말씀해 주셨다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;창업해서 출시할 생각 없냐고 하신 분도 있었다 ㅋㅋㅋㅋㅋㅋ  &lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;발표가 끝나고도 한동안 발표 영상을 보면 벅찼다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정글이 끝나서 팀원들이 하나 둘 떠날 때 너무 아쉽고 슬프고..&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다시는 안 보고 살 줄 알았는데&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다들 서로 가까이서 일하고 있어서 저번주에도 다 같이 만나서 술 마셨다. 내 눈물 돌려내&lt;/p&gt;</description>
      <category>회고</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/152</guid>
      <comments>https://e-juhee.tistory.com/entry/KAIST-%EC%A0%95%EA%B8%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-2-%EC%94%BD%EC%9E%89%EB%9F%AC%EB%84%88-%EB%A7%8C%EB%93%A4%EA%B8%B0#entry152comment</comments>
      <pubDate>Sun, 22 Oct 2023 05:13:14 +0900</pubDate>
    </item>
    <item>
      <title>[KAIST 정글] 나만무 프로젝트 회고 (1) :: 팀 형성, 초안 발표</title>
      <link>https://e-juhee.tistory.com/entry/KAIST-%EC%A0%95%EA%B8%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1-%ED%8C%80-%ED%98%95%EC%84%B1-%EC%B4%88%EC%95%88-%EB%B0%9C%ED%91%9C</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;너무너무 늦었지만 이제라도 작성하는 &lt;b&gt;나만의 무기를 갖기&lt;/b&gt; 회고 Start~!&lt;br /&gt;지금은 나만무 최종 발표(7/8)가 끝난 지 3개월이나 지났다 ㅎㅎ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 나만무에 대한 나의 생각부터 기록해 보자면..&lt;br /&gt;부트캠프를 하고 남는 것은 프로젝트뿐이라고 생각하였고,&lt;br /&gt;나에겐 무려 세 번째 부트캠프인 만큼 굉장한 프로젝트를 해내야 하고 해낼 수 있을 것이라는 자신감이 가득했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;리더 지원&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연하게도 리더에 지원했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;지원한 이유는&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 경험이 있다.&lt;/li&gt;
&lt;li&gt;원래 나서는 거 좋아함. 답답할 바에 내가 뛸래 !&lt;/li&gt;
&lt;li&gt;앞으로 사회에 나가면 리더가 되기까지 몇 년은 더 기다려야 한다. 내가 스스로 리더가 될 수 있는 마지막 기회니까&lt;/li&gt;
&lt;li&gt;애초에 크래프톤에서 발표하는 영상 보고 반해서 나도 하고싶어서 정글 지원했음&lt;/li&gt;
&lt;li&gt;취업에도 당연히 도움이 될 것 같음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;나만무가 끝나고 취업한 이후에 든 생각은&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확실히 프로젝트 경험이 나만무를 할 때 도움이 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 일정 수립, 기획 방향 설정, 우선순위 수립 등. 정글답게 이 모든 것을 알아서 해내야 했기에 프로젝트 경험이 없었다면 더 힘들었을 것 같다.&lt;br /&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;우선 팀원 모을 때도 우여곡절이 많았고, 운영진의 부정적인 피드백을 듣고 팀이 방황할 때, 팀원 간 소통이 잘 이루어지지 않을 때 정말 힘들었다.&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;나만 힘들었다는 게 아닌 걸 알았을 때 왠지 안심이 되었고, 같이 리더를 했던 다른 동기가 많이 위로해 줘서 힘이 되었다  &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;h4 data-ke-size=&quot;size20&quot;&gt;프로젝트 주제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리더가 되기 위해서는 프로젝트 주제를 가지고 면접을 봐서 선발되어야 했다.&lt;br /&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;br /&gt;그래프 알고리즘을 시각화해 주는 사이트 였다.&lt;br /&gt;DFS나 BFS, 다익스트라 같은 알고리즘을 눈으로 보면서 이해하면 더 좋을 것 같고, 프로젝트를 하면서 알고리즘에 대해서 더 잘 알게 될 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;막상 나만무 시작하고 나서는 노잼일 것 같아서 폐기된 주제이다. ㅋㅋㅠ&lt;br /&gt;다른 팀들도 리더를 지원할 때 가져간 주제로 나만무를 진행한 팀은 하나도 없는 듯?&lt;br /&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;h4 data-ke-size=&quot;size20&quot;&gt;지원서 작성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리더 지원 공지가 나오고 나서는 강의실 분위기가 아주 요란법석 하다.&lt;br /&gt;너 지원할 거야? 주제 정했어? 누구랑 할 거야?&lt;br /&gt;이런저런 이야기가 많이 오간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;도저히 지원서 작성에 집중이 되지 않아서 나는 핀토스를 잠시 내려놓고   카이스트 본원 근처에 있는 카페에 혼자 가서 지원서를 작성했다. 강의실에 남아있던 친구들도 이 날 여럿이서 편의점에서 맥주 파티를 벌였다고 한다. 리더를 지원하지 않는 친구들도 공부에 집중하기 힘든 시기였던 것 같다.&lt;br /&gt;지원서 내용은 리더로서 어떻게 할 건지에 관련된 질문이 있었던 것 같은데 (기억이 가물가물..)&lt;br /&gt;별로 특색 있는 질문은 아니고, 별 생각도 안 떠올라서 지피티한테 써달라고 했다.ㅋㅋㅋ&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;면접&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리 반에서 리더에 지원한 동기는 6명이고, 리더를 총 몇 명 선발할지는 미리 알려주지 않아서 참 심란했다. 내가 리더가 되지 않을 수도 있고, 리더가 몇 명인지 모르니 우리 팀이 4명이 될지, 5명? 6명이 될 지 예상할 수 없기 때문.ㅠㅠ&lt;br /&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;나까지 총 네 명이었던 우리 팀은 다른 팀원이 더 필요해졌다!!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;팀 형성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 형성은 stable marriage 알고리즘으로 결정된다.&lt;br /&gt;리더도 선호하는 팀원을 1위부터 마지막까지 전부 제출하고&lt;br /&gt;팀원도 선호하는 리더를 1위부터 마지막까지 작성해서 제출한다.&lt;br /&gt;어떻게 작성했는지는 공개되지 않기 때문에 정말 원하는 사람을 적으면 된다.&lt;br /&gt;아직 핀토스 마지막 프로젝트인 파일 시스템을 하고 있던 시기였고, 이미 나까지 네 명이서 함께 하기로 되어있던 상태라 신경을 덜 쓰고 있었는데, 순식간에 모든 리더들이 팀원 섭외를 완료했다.&lt;br /&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;쓸데없는 걱정도 마음 고생도 너무 많이 함..&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;br /&gt;다행히도 우리 팀은 함께 하기로 했던 4명이 무사히 합류했고, 예상치 못한 결과를 맞이한 팀도 있었다. 알고리즘의 장난인지 운영진분들의 조정이 있었는지는 모른다.&lt;br /&gt;그로 인해 우리 팀은 마지막 다섯 번째 멤버도 함께 하고 싶었지만 이미 다른 팀에 합류해 버려 아쉬웠던 친구가 들어오게 되었고, 미안하면서도 감사한 마음으로 팀 형성이 잘 마무리되었다. by 알고리즘 최대 수혜 팀  &lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;첫 회의&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 팀원도 모였겠다 어떤 프로젝트를 해나갈지 본격적으로 이야기를 나누기 시작했다.&lt;br /&gt;각자 선호하는 공부 스타일에 대해서 공유하고, 어떤 프로젝트를 하고 싶은지 의견을 나누었다.&lt;br /&gt;팀원 중에 아이디어 뱅크인 친구가 있어서 정말 많은 아이디어가 나왔다.&lt;br /&gt;규칙도 정했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7veQw/btsyV1jDRBf/Xb8ydnm1cRCtGBerIeQQqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7veQw/btsyV1jDRBf/Xb8ydnm1cRCtGBerIeQQqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7veQw/btsyV1jDRBf/Xb8ydnm1cRCtGBerIeQQqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7veQw%2FbtsyV1jDRBf%2FXb8ydnm1cRCtGBerIeQQqk%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;728&quot; height=&quot;277&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기술 스택&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트는 내가 해봤던 Next.js를 사용하기로 했고 백엔드는 프론트와 함께 코드 리뷰하기 편할 것이라 생각하여 js를 쓰는 Nest.js를 선택했다.&lt;br /&gt;그리고 통신도 내가 써본 GraphQL을 사용했다.&lt;br /&gt;사실 편하려고 선택한 건데 발표 후 포스터 세션과 취업 면접에서 기술 스택 관련된 질문과 관심을 꽤 받을 수 있었다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;'어떻게 이렇게 핫한 기술들을 선택하게 되셨나요?'
'왜 GraphQL을 사용했죠?'
'GraphQL이 뭔가요?'
'학습곡선이 높은데 힘들지 않았나요?'&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대기업을 지망하는 게 아니라면 프론트엔드는 Next.js 혹은 리액트&lt;br /&gt;백엔드는 Node.js 관련된 프레임워크를 선택하는 것이 나쁘지 않은 선택인 것 같다.&lt;br /&gt;GraphQL도 채용 공고의 우대사항에서 꽤 많이 보인다. 하지만 확실히 RestAPI가 더 많이 쓰이긴 하는 듯? 백엔드가 아니라서 잘 모르겠당..&lt;br /&gt;그리고 프론트에서는 상태 관리 툴을 무엇으로 할지 결정하는 것도 쉽지 않을 텐데 나는 이것도 내가 원래 써본 recoil을 사용했다. 이건 학습 곡선이 아예 없다고 봐도 무방할 정도인데, 지금 내가 회사에서 쓰고 있는 redux는 이것저것 알아야 할 게 꽤 있었다.&lt;br /&gt;사실 나는 다 써본 거라서 편하게 선택한 거고, 그냥 잘할 수 있는 거, 최대한 빨리 배울 수 있는 거 선택하는 게 최고인 듯!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;취업 때는 어차피 신입 채용이어서 어떤 라이브러리를 써봤다고 해서 대단한 대우를 해주지는 않는 것 같다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;초안 발표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나만무의 첫 번째 과제는 주제를 선정하는 것이다.&lt;br /&gt;주제를 굉장히 중요하게 생각하는 것 같다.&lt;br /&gt;5/31 저녁에 팀이 발표되었고, 6/6에 초안 발표를 해야 했다.&lt;br /&gt;뭔 기획하는 데 시간을 이렇게 많이 줘..?라고 생각했는데&lt;br /&gt;초안 발표를 최종 발표처럼 하는 것이었다. 프로젝트했다 치고~ 하는 것이다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아키텍처와 어떤 기술적 챌린지가 있었는지도 함께 발표에 넣어야 했기에&lt;br /&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;&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;+ 기술적 챌린지도 초안 발표에서는 딱히 관심 없는 것 같음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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;br /&gt;초안 발표에는 2~3개를 가져갈 수 있고, 우리 팀은 총 3개의 주제를 준비했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;우리 팀의 목표&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리팀의 프로젝트 주제 선정 기준은 누가 봐도 우와 소리가 나올만한 것을 하고 싶었다.&lt;br /&gt;그리고 실현 가능성을 확인하기 위해서&lt;br /&gt;가장 핵심이 되는 기능을 먼저 시도해 보고 간단하게라도 구현이 되는 아이디어를 초안 발표에 가져가기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아이디어가 굉장히 많이 나왔다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/f4228cbd-84f2-430a-b221-3528930a2eb4/image.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서 채택된 것은 아래 세 가지&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주제 1) 토끼풀&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo; 음성 파일의 필요한 부분만 남기고, 관련 정보를 함께 제공하는 서비스 &amp;rdquo;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=PkzsrwO3D88&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bgmv15/hyUgS00d3h/RDuAl5LFXUWek91Ppxo5TK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/PkzsrwO3D88&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;원래는 VREW를 보고 반해버려서 브루와 비슷한 프로젝트를 해보고 싶었다. 하지만 협력사에서도 발표를 보러 올 것이기 때문에 조금 망설여지기도 했고, 너무 어려워 보이기도 했다.&lt;br /&gt;음성 인식과 공백 구간을 잘라내는 기술은 가져가되, 여기에 요즘 핫한 AI 기술을 붙여보자 해서 나온 주제가 토끼풀이다. 이름은 대충 막 지은 건데 네이버의 클로바랑 비슷해서 토끼풀이 되었던 것 같다ㅋㅋ&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공백 Trim&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/019e4a99-447b-4d93-bd60-35558785329d/image.gif&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;관련 정보 제공&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/1f7c52b7-8115-42ab-8947-041ebaa28913/image.gif&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MVP&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음성 파일을 업로드하면 공백을 제거하는 것까지 구현해 봤다. by 오박사님 작품  &lt;br /&gt;음질이 저하되는 이슈가 있어서 많이 힘들었다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/7c45b74e-7d22-4feb-91dd-6d5e124b92c1/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주제 2) Singtopia&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo; 내 음역대 분석 서비스 &amp;rdquo;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=V3aWFkzBYQk&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/zH4rN/hyUgHrEq3w/0eizB2qYNazKAj9K3RkFNK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/V3aWFkzBYQk&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;이건 고기 먹다가 뜬금없이 나온 아이디어!&lt;br /&gt;처음 시작은 노래를 더 잘 부를 수 있게 만들어주는 어플이 있으면 좋겠다! 에서 시작했다.&lt;br /&gt;부르고 싶은 노래가 내 음역대에 맞는지, 아니라면 키를 얼마나 조정해야 하는지 등을 알려주는 기능이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실시간 채점 &amp;amp; 음역대 분석&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/b1aa6f1e-8ff9-414c-b477-cd25be57bcdf/image.png&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;내 음역대에서 가능한 노래 찾기&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/f9500e57-79d4-4e1f-921f-4348258ef4d2/image.png&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;내 음역대에 맞추어 키 조절하기&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/bc6c3df9-ad4d-4a04-8dc9-50f30ca6d479/image.gif&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 채택된다면 노래 부르는 스타일에 맞는 노래를 추천해 주는 기능도 넣고 싶었다.&lt;br /&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;br /&gt;개인적으로 너무너무 재밌을 것 같아서 정말 마음에 드는 아이템이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;하지만 마음에 드는 만큼 어려운 점이 많았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 저작권 문제.&lt;br /&gt;실시간 채점을 제공하다 보니 노래를 부를 수 있게 MR을 틀어줘야 하고, 유저의 목소리와 비교하기 위해 노래에서 가수의 목소리를 가져와서 변환하는 데 써야 했다.&lt;br /&gt;나만무는 최종 발표에서 시연을 해야 했기 때문에 음원을 사용하는 데 있어 저작권 이슈가 있을 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이게 해결되지 않으면 아무것도 진행하지 못할 것이라 생각해 우선 저작권 협회에 문의를 했다.&lt;br /&gt;우리에게 남은 시간이 너무 촉박해서 그 안에 답변이 올까 우려했지만, 다행히도 상업 목적이 아닌 경우에는 써도 된다는 답변을 받았다.&lt;br /&gt;또 상업 목적으로 사용하게 된다 하여도 일단 서비스를 만든 다음에 서비스 자체를 제출해야 심사를 해준다고 했다.&lt;br /&gt;결국 그냥 만들어도 된다는 것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&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&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/f3ea33ec-07b1-4f6f-9995-3aefa5926fad/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  받은 거&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/8d7b00a1-1f79-4f0c-a156-914ff12bb420/image.png&quot; alt=&quot;&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;br /&gt;음정 채점, 어렵긴 하겠지만 금방 될 거라 생각했는데 엉뚱한 결과만 자꾸 나와서 이 주제를 버려야 한다는 얘기마저 나왔다.ㅠㅠ&lt;br /&gt;결국 초안 발표 때까지 뚜렷한 결과는 얻지 못했지만, 아이디어가 너무 아까워서 일단 발표는 해보자로 합의를 봤다.&lt;br /&gt;일주일 만에 할 수 있으면 그게 뭐가 기술적 챌린지야.. 한 달 동안 매달리면 할 수 있을 거야!라고 생각함ㅋㅋㅋ&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주제 3) 뚜벅뚜벅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo; 뚜벅이를 위한 길 찾기 서비스 &amp;rdquo;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=f6zBVTuUiV0&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/Sobpv/hyUgR8UDox/bReAxBGJRuJnL6lcsgEGoK/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/f6zBVTuUiV0&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;우리 팀에 유독 길치가 많았기에 나온 아이디어&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;걸어서 갈 수 있는 지름길을 추천해 주는 기능&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/64bd3f86-e1b6-4330-b126-994f6be3b0aa/image.png&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;시간대별로 사람들이 많이 몰리는 곳이 어딘지 알 수 있게 해주는 기능&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/d3481d4e-534e-47f6-a3f2-59405adafef7/image.png&quot; alt=&quot;&quot; /&gt;&lt;/div&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;우리의 결론은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어플 백그라운드 상태에서 10m 이상 이동하면 이동 정보를 저장&lt;/li&gt;
&lt;li&gt;지도 위에 가상의 점들을 만들어서 사람들이 이동한 경로를 edge로 하는 그래프라고 생각하고 최단 경로를 찾기로 했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/09eb27dc-fb24-418e-a87f-c8b86f57f473/image.gif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;MVP&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP를 내가 만들기로 해서 어플 백그라운드에서 이동 경로를 저장할 수 있는지를 테스트했다.&lt;br /&gt;웹은 백그라운드 기능이 없어서 React Native를 사용했고, Expo를 통해서 구현했다.&lt;br /&gt;제대로 만든 건지 확인하기 위해서 새벽에 혼자 노트북 들고 문지캠퍼스를 걷기도 하고 뛰기도 하면서 테스트를 했다 ㅋㅋ 되는 거 보고 짜릿했다..&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/6bb9bc71-6a28-489d-98f4-b22bccdce1f2/image.png&quot; alt=&quot;&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;그리고 XCode에서 이동 테스트 기능을 제공한다는 것을 뒤늦게 깨달았지 ㅎ_ㅎ&lt;br /&gt;세상 참 좋아  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;kakaotv&quot; data-video-url=&quot;https://tv.kakao.com/v/441880492&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/bIiHFt/hyUgQPDlCN/YkpchNMCZkIa0oUIcpKnm1/img.jpg?width=1080&amp;amp;height=1920&amp;amp;face=0_0_1080_1920,https://scrap.kakaocdn.net/dn/kBpbd/hyUgLnhOyi/MrNEsxuBWyOZoZdpg9qk2K/img.jpg?width=1080&amp;amp;height=1920&amp;amp;face=0_0_1080_1920&quot; data-video-width=&quot;860&quot; data-video-height=&quot;1529&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;1529&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-play-service=&quot;daum_tistory&quot; data-original-url=&quot;&quot; data-video-title=&quot;&quot;&gt;&lt;iframe src=&quot;https://play-tv.kakao.com/embed/player/cliplink/441880492?service=daum_tistory&quot; width=&quot;860&quot; height=&quot;1529&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&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;아무튼 속력까지 엄청 세세하게 잘 나왔다.&lt;br /&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csF3cZ/btsyR8q7UAX/G6vefAVinwXT5Tm50pOGY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csF3cZ/btsyR8q7UAX/G6vefAVinwXT5Tm50pOGY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csF3cZ/btsyR8q7UAX/G6vefAVinwXT5Tm50pOGY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsF3cZ%2FbtsyR8q7UAX%2FG6vefAVinwXT5Tm50pOGY1%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;875&quot; height=&quot;392&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nOMo4/btsyX0risXV/qLTSN4PXze8bDQJC7ihYB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nOMo4/btsyX0risXV/qLTSN4PXze8bDQJC7ihYB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nOMo4/btsyX0risXV/qLTSN4PXze8bDQJC7ihYB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnOMo4%2FbtsyX0risXV%2FqLTSN4PXze8bDQJC7ihYB0%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;875&quot; height=&quot;392&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;초안 발표 피드백&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 가져간 주제들 전부 서비스 같지 않고, 기능에 가깝다는 피드백을 받았다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; (&lt;/span&gt;MVP 가져오라면서요..)&lt;/span&gt;&lt;br /&gt;우리가 어려울 것이라 생각한 것도 크게 기술적인 부분이 없어 보였던 것 같다.&lt;br /&gt;예를 들어 음정 채점은 과학고 애들이 과제로 할 수준이라는 말씀도 해주셨다.. (과학고 애들이 하는 거면 어려운 거 아닌가요..?)&lt;br /&gt;그리고 뚜벅뚜벅 같은 위치 기반 서비스는 데이터가 없으면 시연이 어려워서 힘들다고..&lt;br /&gt;지금 가져온 기획안들 중에서는 그나마 노래방이 나은 것 같고, 여기에 뭘 더 많이 붙여야 한다는 피드백을 한 분이 해주셨다. (다른 분들은 절레절레..ㅠ)&lt;br /&gt;긍정적인 피드백으로는 팀원들 에너지는 좋다고 하셨다..^^ 시연 영상을 재밌게 잘 만들어서 그런 말씀을 하신 것 같다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;싹 다 갈아엎기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상치 못한 부정적인 피드백에 발등에 불이 떨어진 우리 팀은 규칙이 바뀌었다.ㅠㅠ&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/c92f4805-dcdc-4398-8310-846fb6150237/image.png&quot; alt=&quot;&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;그나마 피드백이 긍정적이었던 노래 연습 Singtopia를 살리되 노래를 부르는 기능만으로는 부족하다고 했으니&lt;br /&gt;이것저것 다 갖다 붙이자! 에서 나온 것이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;노래를 부르면서 레이싱 경기를 하는 캐주얼 노래 배틀 게임&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/9618dfdc-663c-43ae-976c-f2cda9fdd8d4/image.jpeg&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노래 연습과 카트라이더를 합친 게임이다 ㅋㅋ&lt;br /&gt;이것이 씽잉러너의 시작&lt;/p&gt;</description>
      <category>회고</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/151</guid>
      <comments>https://e-juhee.tistory.com/entry/KAIST-%EC%A0%95%EA%B8%80-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-1-%ED%8C%80-%ED%98%95%EC%84%B1-%EC%B4%88%EC%95%88-%EB%B0%9C%ED%91%9C#entry151comment</comments>
      <pubDate>Sun, 22 Oct 2023 00:19:10 +0900</pubDate>
    </item>
    <item>
      <title>[Redux] Redux, React Redux, Redux DevTools</title>
      <link>https://e-juhee.tistory.com/entry/Redux-Redux-React-Redux-Redux-DevTools</link>
      <description>&lt;h1&gt;1. Redux&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://ko.redux.js.org/&quot;&gt;  Redux&lt;/a&gt;&lt;br&gt;&lt;a href=&quot;https://react-redux.js.org/tutorials/quick-start&quot;&gt;  React Redux&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;1-1. Installation&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install redux
npm install react-redux&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;1-2. Setting&lt;/h2&gt;
&lt;h3&gt;1) &lt;code&gt;&amp;lt;Provider /&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Redux Store를 이용 가능하게 해주는 컴포넌트인 &lt;code&gt;&amp;lt;Provider /&amp;gt;&lt;/code&gt;로 index.js의 &lt;code&gt;&amp;lt;App/ &amp;gt;&lt;/code&gt;을 감싼다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;Provider /&amp;gt;&lt;/code&gt;는 react-redux에서 가져온다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;store&lt;/code&gt;는 별도의 파일에서 만들어서 가져온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { BrowserRouter } from &amp;quot;react-router-dom&amp;quot;;
import { Provider } from &amp;quot;react-redux&amp;quot;;

~~~ 생략 ~~~

root.render(
      &amp;lt;Provider store={store}&amp;gt;
        &amp;lt;App /&amp;gt;
      &amp;lt;/Provider&amp;gt;
);

~~~ 생략 ~~~&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2) reducer 생성&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;store를 만들기 위해서는 reducer가 필요하므로, reducer를 먼저 만들어주자!&lt;/li&gt;
&lt;li&gt;src 폴더에 &lt;code&gt;/redux/reducer/reducer.js&lt;/code&gt;와 &lt;code&gt;/redux/store.js&lt;/code&gt;를 새로 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/7bf5c080-87d9-4681-9970-dbca086ecaed/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/redux/reducer/reducer.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let initialState = {
  count: 0,
};

function reducer(state = initialState, action) {}

export default reducer;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;3) store 생성&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;/src/redux/store.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createStore } from &amp;quot;redux&amp;quot;;
import reducer from &amp;quot;./reducer/reducer&amp;quot;;

let store = createStore(reducer);

export default store;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;4) reducer 여러개 만들기&lt;/h3&gt;
&lt;p&gt;기능별로 reducer를 따로 만들었다면, combineReducers를 이용해 하나로 묶어줘야 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/reducer/index.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { combineReducers } from &amp;quot;redux&amp;quot;;
import authenticateReducer from &amp;quot;./authenticateReducer&amp;quot;;
import productReducer from &amp;quot;./productReducer&amp;quot;;

export default combineReducers({
  auth: authenticateReducer,
  product: productReducer,
});&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;store.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createStore, applyMiddleware } from &amp;quot;redux&amp;quot;;
import thunk from &amp;quot;redux-thunk&amp;quot;;
import rootReducer from &amp;quot;./reducer&amp;quot;;
// reducer를 묶어준 파일명이 index.js이기 때문에 /reducer까지만 작성해도 자동으로 index.js를 가져온다.
// 이름은 rootReducer가 아니어도 상관 없음!

let store = createStore(rootReducer, applyMiddleware(thunk));

export default store;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이렇게 변경하고 나면 useSelector로 state를 불러올 때 combineReducers에서 지정해둔 키 이름으로 접근해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const productList = useSelector((state) =&amp;gt; state.product.productList);&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;1-3. 적용하기&lt;/h2&gt;
&lt;h3&gt;1) state 변경하기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;action을 전달하는 hook인 &lt;code&gt;useDispatch()&lt;/code&gt;를 이용한다.&lt;/li&gt;
&lt;li&gt;type과 payload로 이루어진 객체를 인자로 넣는다.&lt;br&gt;(payload는 필요한 정보를 담아서 보낼 수 있는 선택값!)&lt;/li&gt;
&lt;li&gt;useDispatch를 사용하면 reducer에서 받을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;~~~ 생략 ~~~

import { useDispatch } from &amp;quot;react-redux&amp;quot;;

const ReduxPage = () =&amp;gt; {
  const dispatch = useDispatch();

  const increase = () =&amp;gt; {
    dispatch({ type: &amp;quot;INCREASE&amp;quot; });
  };

~~~ 생략 ~~~&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;reducer가 새로운 값을 반환하면 반환된 값으로 state가 변경된다.&lt;/li&gt;
&lt;li&gt;새로운 객체(주소값)을 return해야 한다는 것에 주의하자!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;/redux/reducer/reducer.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let initialState = {
  count: 0,
};

function reducer(state = initialState, action) {
  switch (action.type) {
    case &amp;quot;INCREASE&amp;quot;:
      return { ...state, count: state.count + 1 };
    default:
      return { ...state };
  }
}

export default reducer;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;2) State 가져오기&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;store의 state를 가져오는 hook인 &lt;code&gt;useSelector()&lt;/code&gt;를 이용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { useSelector } from &amp;quot;react-redux&amp;quot;;

const count = useSelector((state) =&amp;gt; state.count);&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;

&lt;h3&gt;3) 컴포넌트와 state 연결하기 (mapStateToProps)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;connect&lt;/code&gt;를 이용하면 state의 값을 컴포넌트의 props와 병합해서 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mapStateToProps&lt;/code&gt;는 컴포넌트가 스토어에서 필요한 데이터만 선택할 수 있게 해준다.&lt;/li&gt;
&lt;li&gt;mapState라고도 부른다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;mapStateToProps&lt;/code&gt; 함수는 Redux Store가 업데이트 될 때마다 호출된다.&lt;/li&gt;
&lt;li&gt;컴포넌트가 필요로 하는 데이터를 객체로 반환해야 한다.&lt;/li&gt;
&lt;li&gt;state와 ownProps(optional)를 파라미터로 받는다.&lt;/li&gt;
&lt;li&gt;파라미터로 state만 받도록 선언하면 Store 상태가 변경될 때마다 호출된다.&lt;/li&gt;
&lt;li&gt;파라미터로 두 개의 파라미터를 받도록 선언하면, Store 상태가 변경되거나 래퍼 컴포넌트가 새 props를 받을 때마다 호출된다.&lt;/li&gt;
&lt;li&gt;반환되는 값이 변경되면 컴포넌트가 리렌더된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;function mapStateToProps(state) {
  const { todos } = state
  return { todoList: todos.allIds }
}

export default connect(mapStateToProps)(TodoList)&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;


&lt;h3&gt;4) 컴포넌트와 dispatch 연결하기 (mapDispatchToProps)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;connect&lt;/code&gt;의 두 번째 매개변수로 mapDispatchToProps를 전달하면 dispatch 하는 함수를 생성하고, 그 함수들을 컴포넌트의 props로 전달할 수 있다.&lt;/li&gt;
&lt;li&gt;store.dispatch를 호출하는 것이 가능하기는 하지만 React Redux에서는 컴포넌트가 Store와 직접적으로 소통하는 것을 피하고, connect를 통해 컴포넌트의 props로 연결하는 패턴을 주로 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;connect&lt;/code&gt;의 두 번째 인자를 지정하지 않으면, 컴포넌트는 기본적으로 dispatch를 받는다.&lt;/li&gt;
&lt;li&gt;mapDispatchToProps(&lt;code&gt;connect&lt;/code&gt;의 두 번째 인자)를 사용하면, props에 dispatch가 제공되지 않는다.&lt;/li&gt;
&lt;li&gt;mapDispatchToProps를 제공하면 컴포넌트가 어떤 액션을 dispatch하는지 지정할 수 있어 더 선언적이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 가지 형태(함수 형태와 객체 형태)가 있다.&lt;br&gt;공식 문서에서는 객체 형태를 권장한다.&lt;/p&gt;
&lt;h3&gt;4-1) 함수 형태&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;더 많은 커스터마이징이 가능하다.&lt;/li&gt;
&lt;li&gt;첫 번째 매개변수로 dispatch를 전달받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const mapDispatchToProps = (dispatch) =&amp;gt; {
  return {
    // dispatching plain actions
    increment: () =&amp;gt; dispatch({ type: &amp;#39;INCREMENT&amp;#39; }),
    decrement: () =&amp;gt; dispatch({ type: &amp;#39;DECREMENT&amp;#39; }),
    reset: () =&amp;gt; dispatch({ type: &amp;#39;RESET&amp;#39; }),
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;매개변수를 두 개를 받는다면(두 번째는 optional) 두 번째 매개변수로는 props를 전달받게 되고, 컴포넌트가 새로운 props를 받을 때마다 재호출되어 액션을 dispatch할 함수들을 최신 props를 바탕으로 새로 바인딩해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;render() {
  return &amp;lt;button onClick={() =&amp;gt; this.props.toggleTodo()} /&amp;gt;
}

const mapDispatchToProps = (dispatch, ownProps) =&amp;gt; {
  return {
    toggleTodo: () =&amp;gt; dispatch(toggleTodo(ownProps.todoId))
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;bindActionCreators&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;함수 래핑을 단순화하기 위한 함수이다.&lt;/li&gt;
&lt;li&gt;각 함수를 dispatch로 자동으로 래핑해준다.&lt;/li&gt;
&lt;li&gt;Redux를 모르는 컴포넌트에 액션 크리에이터를 전달하고 싶을 때 쓰면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { bindActionCreators } from &amp;#39;redux&amp;#39;

const increment = () =&amp;gt; ({ type: &amp;#39;INCREMENT&amp;#39; })
const decrement = () =&amp;gt; ({ type: &amp;#39;DECREMENT&amp;#39; })
const reset = () =&amp;gt; ({ type: &amp;#39;RESET&amp;#39; })

// binding an action creator
// returns (...args) =&amp;gt; dispatch(increment(...args))
const boundIncrement = bindActionCreators(increment, dispatch)

// binding an object full of action creators
const boundActionCreators = bindActionCreators(
  { increment, decrement, reset },
  dispatch
)
// returns
// {
//   increment: (...args) =&amp;gt; dispatch(increment(...args)),
//   decrement: (...args) =&amp;gt; dispatch(decrement(...args)),
//   reset: (...args) =&amp;gt; dispatch(reset(...args)),
// }&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;mapDispatchToProps와 함께 쓴 예시&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { bindActionCreators } from &amp;#39;redux&amp;#39;
// ...

function mapDispatchToProps(dispatch) {
  return bindActionCreators({ increment, decrement, reset }, dispatch)
}

// component receives props.increment, props.decrement, props.reset
connect(null, mapDispatchToProps)(Counter)&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4-2) 객체 형태&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;더 선언적이고 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const mapDispatchToProps = {
  increment,
  decrement,
  reset,
}&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h1&gt;2. Redux Middleware (redux-thunk)&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/reduxjs/redux-thunk&quot;&gt;  Redux Thunk&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Redux는 동기적인 업데이트만 가능하다.&lt;br&gt;비동기 작업이 필요한 API 요청 등은 Redux로 할 수 없다...&lt;/p&gt;
&lt;p&gt;그래서 action이 reducer로 전달되기 전에 중간에 가로채서 비동기 작업을 처리하는 Middleware를 사용한다.&lt;/p&gt;
&lt;p&gt;Redux의 Middleware에는 redux-saga와 redux-thunk가 있다.&lt;br&gt;redux-thunk를 사용해보자!!&lt;/p&gt;
&lt;h2&gt;2-1. Installation&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;npm install redux-thunk&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2-2. Setting&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;applyMiddleware와 thunk를 가져와서 createStore의 인자로 전달한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;import { createStore, applyMiddleware } from &amp;quot;redux&amp;quot;;
import thunk from &amp;quot;redux-thunk&amp;quot;;
import productReducer from &amp;quot;./reducer/productReducer&amp;quot;;

let store = createStore(productReducer, applyMiddleware(thunk));

export default store;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;2-3. 적용하기&lt;/h2&gt;
&lt;p&gt;react-thunk를 사용하지 않던 기존에는 (동기 방식) dispatch의 인자로 action과 payload를 바로 전달했다.&lt;/p&gt;
&lt;p&gt;비동기 작업을 하려면 우선 해당 비동기 작업을 반환하는 미들웨어 함수를 만들어둔다.&lt;br&gt;이 비동기 작업 로직 안에서 기존 방식대로 dispatch를 호출한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function getProducts(searchQuery) {
  return async (dispatch, getState) =&amp;gt; {
    let url = `https:...`;
    let response = await fetch(url);
    let data = await response.json();
    dispatch({ type: &amp;quot;GET_PRODUCT_SUCCESS&amp;quot;, payload: { data } });
  };
}

export const productAction = { getProducts };&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 미들웨어 함수를 호출할 때는 dispatch를 사용하는 것은 동일하지만 action과 payload가 아닌, 이 미들웨어 함수를 인자로 넣어주면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &amp;quot;react&amp;quot;;
import { productAction } from &amp;quot;.../redux/action/productAction&amp;quot;;
import { useDispatch, useSelector } from &amp;quot;react-redux&amp;quot;;

const ProductAll = () =&amp;gt; {
  const dispatch = useDispatch();
  const getProducts = () =&amp;gt; {
    dispatch(productAction.getProducts(&amp;quot;searchQuery&amp;quot;));
  };
  getProducts();

  return &amp;lt;div&amp;gt;&amp;lt;/div&amp;gt;;
};

export default ProductAll;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;reducer는 기존과 동일하다.&lt;/p&gt;
&lt;h1&gt;3. Redux DevTools&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/zalmoxisus/redux-devtools-extension&quot;&gt;  Redux DevTools&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;state의 변화를 편리하게 확인할 수 있게 도와주는 도구이다.&lt;/p&gt;
&lt;h2&gt;3-1. Installation&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=ko&quot;&gt;  크롬 확장 설치&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install --save redux-devtools-extension&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3-2. Setting&lt;/h2&gt;
&lt;p&gt;composeWithDevTools로 applyMiddleware를 감싸준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;store.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { createStore, applyMiddleware } from &amp;quot;redux&amp;quot;;
import thunk from &amp;quot;redux-thunk&amp;quot;;
import rootReducer from &amp;quot;./reducer&amp;quot;;
import { composeWithDevTools } from &amp;quot;redux-devtools-extension&amp;quot;;

let store = createStore(
  rootReducer,
  composeWithDevTools(applyMiddleware(thunk))
);

export default store;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;3-3. 활용&lt;/h2&gt;
&lt;p&gt;개발자 도구의 Redux 탭에서 state 변화를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/e_juhee/post/c80de170-775a-4709-9449-dab7ec729e7d/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;</description>
      <category>React</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/150</guid>
      <comments>https://e-juhee.tistory.com/entry/Redux-Redux-React-Redux-Redux-DevTools#entry150comment</comments>
      <pubDate>Tue, 3 Oct 2023 21:49:14 +0900</pubDate>
    </item>
    <item>
      <title>[React] Router (version 6)</title>
      <link>https://e-juhee.tistory.com/entry/React-Router-version-6</link>
      <description>&lt;h1&gt;1. Installation&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;npm install react-router-dom@6&lt;/code&gt;&lt;/pre&gt;&lt;br/&gt;

&lt;h1&gt;2. Setting&lt;/h1&gt;
&lt;h3&gt;1) src/index.js&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;BrowserRouter&amp;gt;&lt;/code&gt;로 App을 감싼다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { BrowserRouter } from &amp;quot;react-router-dom&amp;quot;;

const root = ReactDOM.createRoot(document.getElementById(&amp;quot;root&amp;quot;));
root.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;BrowserRouter&amp;gt;
      &amp;lt;App /&amp;gt;
    &amp;lt;/BrowserRouter&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;
);&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2) src/App.js&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Routes&amp;gt;&lt;/code&gt; 안에서 필요한 페이지들을 &lt;code&gt;&amp;lt;Route&amp;gt;&lt;/code&gt; 태그를 이용해 정의해둔다.&lt;br&gt;path 속성에 담은 값이 각 페이지의 경로가 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import &amp;quot;./App.css&amp;quot;;
import { Routes, Route } from &amp;quot;react-router-dom&amp;quot;;
import Homepage from &amp;quot;./page/Homepage&amp;quot;;
import Aboutpage from &amp;quot;./page/Aboutpage&amp;quot;;

function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Routes&amp;gt;
        {/* 각 페이지를 정의한다. */}
        &amp;lt;Route path=&amp;quot;/&amp;quot; element={&amp;lt;Home /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&amp;quot;/about&amp;quot; element={&amp;lt;About /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&amp;quot;/product&amp;quot; element={&amp;lt;Product /&amp;gt;} /&amp;gt;
      &amp;lt;/Routes&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h1&gt;3. Routing&lt;/h1&gt;
&lt;p&gt;페이지를 이동하는 방법은 두 가지가 있다.&lt;/p&gt;
&lt;h3&gt;1) &lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Link&amp;gt;&lt;/code&gt; 태그 이용하기&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;quot;react&amp;quot;;
import { Link } from &amp;quot;react-router-dom&amp;quot;;

const Home = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;
      &amp;lt;Link to=&amp;quot;/about&amp;quot;&amp;gt;About&amp;lt;/Link&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Home;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2) useNavigate()&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;useNavigate()&lt;/code&gt; hook 이용하기&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;quot;react&amp;quot;;
import { useNavigate } from &amp;quot;react-router-dom&amp;quot;;

const About = () =&amp;gt; {
  const navigate = useNavigate();
  const goToHomepage = () =&amp;gt; {
    navigate(&amp;quot;/&amp;quot;);
  };
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;About&amp;lt;/h1&amp;gt;
      &amp;lt;button onClick={goToHomepage}&amp;gt;Go to Homepage&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default About;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h1&gt;4. Parameter :: useParams()&lt;/h1&gt;
&lt;p&gt;path를 작성할 때 &lt;code&gt;:id&lt;/code&gt; 형태로 작성하면 매개변수가 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;function App() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Routes&amp;gt;
        &amp;lt;Route path=&amp;quot;/product/:id/:num&amp;quot; element={&amp;lt;ProductDetail /&amp;gt;} /&amp;gt;
      &amp;lt;/Routes&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;매개변수 읽어오기&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;quot;react&amp;quot;;
import { useParams } from &amp;quot;react-router-dom&amp;quot;;

const ProductDetail = () =&amp;gt; {
  const params = useParams();
  console.log(params);

~~~ 생략 ~~~

};

export default ProductDetail;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h1&gt;5. Query String :: useSearchParams()&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;q=pants&lt;/code&gt;라는 쿼리 스트링을 붙여서 product 페이지로 이동한다고 했을 때,&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;navigate(&amp;quot;/product?q=pants&amp;quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;product 페이지에서 쿼리를 읽는 방법&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import React from &amp;quot;react&amp;quot;;
import { useSearchParams } from &amp;quot;react-router-dom&amp;quot;;

const Product = () =&amp;gt; {
  let [query, setQuery] = useSearchParams();
  console.log(query.get(&amp;quot;q&amp;quot;));

~~~ 생략 ~~~

};

export default Product;&lt;/code&gt;&lt;/pre&gt;
&lt;br/&gt;

&lt;h1&gt;6. Redirect :: &lt;code&gt;&amp;lt;Navigate&amp;gt;&lt;/code&gt;&lt;/h1&gt;
&lt;p&gt;조건에 따라 페이지 분기하는 방법&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { Routes, Route, Navigate } from &amp;quot;react-router-dom&amp;quot;;
import { useState } from &amp;quot;react&amp;quot;;
import Login from &amp;quot;./page/Login&amp;quot;;
import User from &amp;quot;./page/User&amp;quot;;

function App() {
  const [authenticate, setAuthenticate] = useState(false);

  const PrivateRoute = () =&amp;gt; {
    return authenticate ? &amp;lt;User /&amp;gt; : &amp;lt;Navigate to=&amp;quot;/login&amp;quot; /&amp;gt;;
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Routes&amp;gt;
        &amp;lt;Route path=&amp;quot;/login&amp;quot; element={&amp;lt;Login /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&amp;quot;/user&amp;quot; element={&amp;lt;PrivateRoute /&amp;gt;} /&amp;gt;
      &amp;lt;/Routes&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>React</category>
      <author>e-juhee</author>
      <guid isPermaLink="true">https://e-juhee.tistory.com/149</guid>
      <comments>https://e-juhee.tistory.com/entry/React-Router-version-6#entry149comment</comments>
      <pubDate>Mon, 2 Oct 2023 18:18:24 +0900</pubDate>
    </item>
  </channel>
</rss>