<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>fe_sunmin.log</title>
        <link>https://velog.io/</link>
        <description>웹 프론트엔드</description>
        <lastBuildDate>Wed, 24 Jul 2024 13:56:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>fe_sunmin.log</title>
            <url>https://velog.velcdn.com/images/fe_sunmin/profile/14ac2e73-43cc-4c4b-ac4b-e01a2498bec5/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. fe_sunmin.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/fe_sunmin" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[2024.07.24] TIL 66일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.24-TIL-66%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.24-TIL-66%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 24 Jul 2024 13:56:27 GMT</pubDate>
            <description><![CDATA[<p><strong>[ 소셜 로그인 닉네임 작성 로직 추가 ]</strong></p>
<ol>
<li>구글/카카오 각각 callback 페이지를 만든다.<ul>
<li>callback 페이지에는 로그인한 사용자의 닉네임 여부에 따라, 
메인페이지로 갈지 nickname 페이지 갈지를 정한다.</li>
</ul>
</li>
<li>nickname 작성 페이지를 만든다.</li>
<li>구글/카카오 로그인 로직<ul>
<li>store에서는 로그인 provider로 이동하는 로직과, </li>
<li>redirecTo를 옵션으로 추가해 로그인 성공 시 callback 페이지로 넘어가는 로직을 구현</li>
</ul>
</li>
</ol>
<p><strong>[ 카카오 로그인 구현 ]</strong></p>
<ol>
<li><p><strong>supabase 세팅</strong>
 <img src="https://velog.velcdn.com/images/fe_sunmin/post/e2c226a8-52fa-4e4b-809b-3fa4a7eeac66/image.png" alt=""></p>
<ul>
<li>필요한건 아래 kakao developers 세팅 과정에서 얻기 </li>
</ul>
</li>
<li><p><strong>kakao developers 세팅</strong></p>
</li>
</ol>
<ul>
<li><p>내 어플리케이션 추가
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/b3a9f343-f424-4ae8-a001-b3175bc15e44/image.png" alt=""></p>
</li>
<li><p>앱 키 얻기
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/2e8c05af-eb0f-47a6-831a-53886ef09af4/image.png" alt=""></p>
</li>
<li><p>Redirect Url 삽입 (supabase callback url)
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/37c56eea-4808-482c-886b-b04d2db6c97f/image.png" alt=""></p>
</li>
<li><p>개인정보 동의항목 심사 신청
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/2a9f0e5e-a2d4-4d88-8bfe-26164eca57c1/image.png" alt=""></p>
</li>
<li><p>앱 아이콘 업로드 하기
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/cbd5ccd5-e5e0-469f-af42-2358f45a2a70/image.png" alt="">
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/994b572d-32ca-466b-8507-65d580a813f4/image.png" alt=""></p>
</li>
<li><p>비즈니스 -&gt; 개인 개발자 비즈 앱 전환
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/955d93be-d5bd-4c88-a149-892b1b1f0f8a/image.png" alt=""></p>
</li>
<li><p>개인정보 동의 항목 (모두 동의 (선택동의 가능))
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/bb7e9445-1eb3-4ebe-9300-40891e6241c4/image.png" alt=""></p>
</li>
<li><p>client secret 얻기
  <img src="https://velog.velcdn.com/images/fe_sunmin/post/3e526ece-72b6-4064-b7c7-10f608c7021a/image.png" alt=""></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.07.23] TIL 65일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.23-TIL-65%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.23-TIL-65%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 23 Jul 2024 15:31:11 GMT</pubDate>
            <description><![CDATA[<p><strong>[ 로그인 확인 ]</strong></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/372c5e8c-0f39-41d4-b756-edb05911e97e/image.png" alt=""></p>
<p><strong>[ 유효성 검사 ]</strong></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/8fd57fa7-b642-48d5-b715-00a6b72f1f03/image.png" alt=""></p>
<p><strong>[ 비밀번호 일치 여부 ]</strong></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/00dcef2d-7086-423e-9354-e09a3d22308d/image.png" alt=""></p>
<p><strong>[ 개인정보 수집 동의 ]</strong></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/8e2a6cea-567d-4a3c-a1a9-e56b39edb03b/image.png" alt=""></p>
<p><strong>[ 이메일 중복 검사 ]</strong></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/b2653298-7c63-4751-8d1a-0724a9bc3930/image.png" alt=""></p>
<p><strong>[ 구글 로그인 ]</strong></p>
<p><strong>1. supabase 세팅</strong>
    <img src="https://velog.velcdn.com/images/fe_sunmin/post/f45e7153-bb83-42ae-aea2-798a2d9c8ada/image.png" alt=""></p>
<ul>
<li>Clinet Id &amp; Client Secret은 Google Clouds에서 받아오기</li>
</ul>
<br />

<p><strong>2. Google API 세팅</strong></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/4f945c82-4a41-4d5a-8313-c78759d1efe7/image.png" alt=""> </p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/a7ee8c07-cf03-4296-80e7-f3ffcec48453/image.png" alt=""></p>
<ul>
<li>Google Cloud에서 프로젝트 생성</li>
<li>사용자 인증 정보 만들기 추가</li>
<li>supabase callback url 삽입</li>
</ul>
<p><strong>[ 구글 로그인 구현 중 생긴 에러 ]</strong></p>
<ul>
<li>구글 로그인은 성공<ul>
<li>구글 로그인 버튼 클릭 시 → 구글 redirect url로 넘어감</li>
<li>로그인 이후에 메인 페이지로 이동</li>
<li>supabase user 테이블 데이터 들어오는거 확인</li>
</ul>
</li>
<li>문제는 사용자 nickname을 받아올 수 없음<ul>
<li>시도 1 ) 구글 로그인에 성공하면 메인페이지가 아니라 nickname 생성하는 페이지로 callback 설정 (진행중)</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.07.22] TIL 64일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.22-TIL-64%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.22-TIL-64%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 23 Jul 2024 15:20:27 GMT</pubDate>
            <description><![CDATA[<ul>
<li><strong>회원가입 1차 ui 구현</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/5729ed4f-4a4e-4d92-9a83-e43842e37e49/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/a504196f-8a30-4132-9e79-34ab0c8fd76b/image.png" alt=""></p>
<ul>
<li><strong>비밀번호 일치 여부 확인</strong></li>
</ul>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/2c85545f-b6e6-41a8-8610-59ec30f0bd69/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/14e314ab-4e4c-47c5-b07d-5d612f9a36fe/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.07.19] TIL 63일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.19-TIL-63%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.19-TIL-63%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 23 Jul 2024 15:18:11 GMT</pubDate>
            <description><![CDATA[<h3 id="이메일-회원가입-기능-구현">이메일 회원가입 기능 구현</h3>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/21193950-6d3a-4639-a17c-e8c4b848d73b/image.png" alt=""></p>
<ul>
<li>중간에 에러가 발생했는데<ul>
<li>genTypes를 yarn으로 업데이트 하면서 바뀐 테이블명이 생겼는데</li>
<li>그 부분을 사용하는 코드에서 바뀐 테이블명을 적용 시키지 않아서 에러 발생</li>
</ul>
</li>
<li>해당 부분 수정하니 회원가입 정상 작동</li>
</ul>
<h3 id="회의-내용">회의 내용</h3>
<ul>
<li>Code Convention<ul>
<li>fetch or axios 선택</li>
<li>핸들러 네이밍 맞추기</li>
</ul>
</li>
<li>진도 관련해서 회의<ul>
<li>첫 주는 기획. 디자인에 집중</li>
<li>둘째 주 월요일 배포 관련 얘기 다시하기</li>
</ul>
</li>
<li>산책하기 페이지에서 1대1 채팅 및 신청 수락 로직을 어떻게 할지<ul>
<li>useFlow에 수정 사항 반영<ul>
<li>메이트 모집 글에 채팅 버튼 누르면 글쓴이와 메이트 신청인의 1대1 채팅방 생성</li>
<li>그 안에서 대화 후 모집이 완료되면 글쓴이가 모집중을 모집완료로 바꿈</li>
</ul>
</li>
</ul>
</li>
<li>그 외<ul>
<li>디자이너님과의 회의<ul>
<li>useFlow 체크</li>
<li>우리 서비에 대한 키워드 얘기하기</li>
<li>메인 컬러 - 원하는 느낌</li>
<li>와이어프레임 (컴포넌트) 에 대한 회의<ul>
<li>우선순위 정하기</li>
<li>문구에 대한 의견 받기</li>
<li>커뮤니티 와이어프레임에 대한 의견 받기</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.07.18] TIL 62일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.18-TIL-62%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.18-TIL-62%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Fri, 19 Jul 2024 01:11:57 GMT</pubDate>
            <description><![CDATA[<h1 id="최종-프로젝트-3일차">최종 프로젝트 3일차</h1>
<h3 id="이메일-회원가입-기능-구현">이메일 회원가입 기능 구현</h3>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/690b6d18-597b-4558-abd3-0b832f0396d3/image.png" alt=""></p>
<ul>
<li>중간에 에러가 발생했는데<ul>
<li>genTypes를 yarn으로 업데이트 하면서 바뀐 테이블명이 생겼는데</li>
<li>그 부분을 사용하는 코드에서 바뀐 테이블명을 적용 시키지 않아서 에러 발생</li>
</ul>
</li>
<li>해당 부분 수정하니 회원가입 정상 작동</li>
</ul>
<h3 id="회의-내용">회의 내용</h3>
<ul>
<li>Code Convention<ul>
<li>fetch or axios 선택</li>
<li>핸들러 네이밍 맞추기</li>
</ul>
</li>
<li>진도 관련해서 회의<ul>
<li>첫 주는 기획. 디자인에 집중</li>
<li>둘째 주 월요일 배포 관련 얘기 다시하기</li>
</ul>
</li>
<li>산책하기 페이지에서 1대1 채팅 및 신청 수락 로직을 어떻게 할지<ul>
<li>useFlow에 수정 사항 반영<ul>
<li>메이트 모집 글에 채팅 버튼 누르면 글쓴이와 메이트 신청인의 1대1 채팅방 생성</li>
<li>그 안에서 대화 후 모집이 완료되면 글쓴이가 모집중을 모집완료로 바꿈</li>
</ul>
</li>
</ul>
</li>
<li>그 외<ul>
<li>디자이너님과의 회의<ul>
<li>useFlow 체크</li>
<li>우리 서비에 대한 키워드 얘기하기</li>
<li>메인 컬러 - 원하는 느낌</li>
<li>와이어프레임 (컴포넌트) 에 대한 회의<ul>
<li>우선순위 정하기</li>
<li>문구에 대한 의견 받기</li>
<li>커뮤니티 와이어프레임에 대한 의견 받기</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/84edebcf-833c-4d53-ab75-3139cabd0456/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.07.17] TIL 61일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.17-TIL-61%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.17-TIL-61%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 17 Jul 2024 16:56:15 GMT</pubDate>
            <description><![CDATA[<h1 id="-최종-프로젝트-2일차-">[ 최종 프로젝트 2일차 ]</h1>
<p><strong>커뮤니티 탭 세분화</strong>
<strong>[ 온보딩 넣기 ]</strong></p>
<ul>
<li>회원가입 -&gt; 온보딩 페이지 -&gt; 로그인 -&gt; (마이페이지로 정보 전달)</li>
<li>희귀동물, 소동물까지 포함해서 동물 범위 넓히기</li>
</ul>
<p><strong>[ 헤더바 ]</strong></p>
<ul>
<li>헤더바로 넣고, 반응형으로 줄어들면 왼쪽 탭으로 들어가게</li>
</ul>
<p><strong>[ 산책 메이트 ]</strong></p>
<ul>
<li>강아지종</li>
<li>강아지 몸무게</li>
<li>위치</li>
<li>시간</li>
<li>메이트 신청 버튼</li>
</ul>
<p><strong>[ 병원 기능 ]</strong></p>
<ul>
<li>선택구현으로 아예 빼고, 필수 구현 끝나면 구현하는걸로</li>
</ul>
<p><strong>[ auth ]</strong></p>
<ul>
<li>auth 및 소셜 supabase 세팅</li>
<li>기능 구현</li>
</ul>
<p><strong>[ community ]</strong></p>
<ul>
<li>테이블 제작
<img src="https://velog.velcdn.com/images/fe_sunmin/post/3f1677de-8213-4a0b-9f21-5ee978990db0/image.png" alt="">
<img src="https://velog.velcdn.com/images/fe_sunmin/post/5fb64fab-65bd-4a73-a075-9998ef300215/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.07.16] TIL 60일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.16-TIL-60%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.16-TIL-60%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 16 Jul 2024 14:44:52 GMT</pubDate>
            <description><![CDATA[<h1 id="최종-프로젝트-시작">최종 프로젝트 시작</h1>
<h3 id="-오늘-회의-내용-">[ 오늘 회의 내용 ]</h3>
<img src="https://velog.velcdn.com/images/fe_sunmin/post/af9cd378-48a4-4561-9c5b-a8fb75ea5ee5/image.png" height="100px" width="300px">

<ul>
<li>프로젝트 주제 정하기 (아래 내용 참고)</li>
<li>피그마 : 아이디어 회의  |  피그잼 : user flow 제작</li>
<li>팀명 : Next Level</li>
<li>5분 기록보드 작성 여부 : O</li>
<li>기술 스택<ul>
<li>React</li>
<li>Typescript</li>
<li>Next.js</li>
<li>Tailwind Css</li>
<li>Supabase</li>
<li>Zustand</li>
<li>ReactQuery(TanstackQuery)</li>
<li>NextUI</li>
</ul>
</li>
<li>스케줄러 작성 ( 노션에 작성 ) : O</li>
<li>폴더 구조 세팅 및 프로젝트 세팅 : O</li>
<li>User Flow 마무리 : O</li>
<li>일감 분배<ul>
<li>메인 페이지 : 추후 구현</li>
<li>로그인 / 회원가입 : 김선민</li>
<li>마이 페이지 : 손지훈</li>
<li>커뮤니티 : 윤새라</li>
<li>병원 정보 : 김민수</li>
<li>장소 추천 : 조현경</li>
</ul>
</li>
<li>기획서 작성 : O</li>
</ul>
<h3 id="-프로젝트-주제-">[ 프로젝트 주제 ]</h3>
<ul>
<li>반려동물 사이트 ( Pet Coco )<ul>
<li>마이페이지</li>
<li>메인 페이지</li>
<li>로그인/회원가입 페이지</li>
<li>동물 병원 추천 페이지</li>
<li>반려동물 커뮤니티 페이지</li>
<li>애견 동반 장소 추천 페이지</li>
</ul>
</li>
</ul>
<h3 id="-user-flow-제작-">[ User Flow 제작 ]</h3>
<img src="https://velog.velcdn.com/images/fe_sunmin/post/ddb16bfb-0782-4132-ab9a-260cf3f73d8e/image.png" height="100px" width="700px">

<img src="https://velog.velcdn.com/images/fe_sunmin/post/43266c56-9fff-4d71-a8a0-c29f7c5a43c4/image.png" height="100px" width="700px">







]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.07.01] TIL 49일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.07.01-TIL-49%EC%9D%BC%EC%B0%A8-tl3f0t5o</link>
            <guid>https://velog.io/@fe_sunmin/2024.07.01-TIL-49%EC%9D%BC%EC%B0%A8-tl3f0t5o</guid>
            <pubDate>Tue, 16 Jul 2024 14:25:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="스터디-새-프로젝트-시작">스터디 새 프로젝트 시작</h4>
</blockquote>
<p><strong>[ 프로젝트 세팅 ]</strong></p>
<ol>
<li><p>repo clone 하기
<code>git clone [레포지토리 주소]</code></p>
</li>
<li><p>todoList 폴더로 이동
<code>cd todoList/</code></p>
</li>
<li><p>react, typescript, next.js, tailwind css 프로젝트 세팅
<code>git config pull.rebase false</code></p>
<p>What is your project named? my-app
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use src/ directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/)? Yes
What import alias would you like configured? @/</p>
</li>
<li><p>cd 3번에서만든본인프로젝트명/</p>
</li>
<li><p>supabase 세팅</p>
<ul>
<li><p>supabase 프로젝트 생성
<img src="https://velog.velcdn.com/images/fe_sunmin/post/7a610b00-2eeb-4b76-9418-3a7a6203af28/image.png" alt=""></p>
</li>
<li><p>프로젝트 세팅 &gt; API 로 들어가서 url, key 받아오기
<img src="https://velog.velcdn.com/images/fe_sunmin/post/4a73f3d5-6397-4c89-9042-368b45dc06c3/image.png" alt=""></p>
</li>
<li><p><code>.env.local</code> 파일 만들고 url과 key 넣기</p>
</li>
</ul>
</li>
</ol>
<pre><code>      NEXT_PUBLIC_SUPABASE_URL = &quot;&quot;
      NEXT_PUBLIC_SUPABASE_ANON_KEY = &quot;&quot;</code></pre><ol start="6">
<li>src &gt; app &gt; shared &gt; supabase.ts 에 아래 내용 넣기</li>
</ol>
<pre><code>    import { createClient } from &quot;@supabase/supabase-js&quot;;
    import { Database } from &quot;@/app/shared/supabase&quot;;

    const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL as string;
    const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY as string;

    export const supabase = createClient&lt;Database&gt;(SUPABASE_URL, SUPABASE_ANON_KEY);</code></pre><pre><code>- 주의 : 아직 Database type 지정을 안해서 오류가 발생한다. (추후 추가 예정)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.25] TIL 45일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.25-TIL-45%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.25-TIL-45%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 01 Jul 2024 15:44:30 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="usememo">useMemo</h4>
</blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/551d632f-499d-4eff-b992-c27abb85c804/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/4b2d0ac7-f6d7-4601-b63f-29a94c843063/image.png" alt=""></p>
<ul>
<li>2번째 사진 코드에서 컴포넌트를 memo로 묶지 않으면</li>
<li><code>changeTodo</code>와 <code>changeSomethingElse</code> 어느 버튼을 누르던 i am child가 불림</li>
<li>memo를 사용하면 <code>changeSomethingElse</code>를 눌렀을때 호출되지 않음</li>
</ul>
<ul>
<li>&quot;i am Child&quot;는 todo의 값이 바뀔때만 호출하면 되는 컴포넌트이다</li>
<li>그렇기 때문에 todo가 아닌 something의 값이 바꼈을때는 호출 필요 X</li>
<li>memo를 사용하면 이러한 불필요한 호출 및 렌더링을 막아줌</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.24] TIL 44일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.20-TIL-41%EC%9D%BC%EC%B0%A8-0w546n77</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.20-TIL-41%EC%9D%BC%EC%B0%A8-0w546n77</guid>
            <pubDate>Mon, 01 Jul 2024 15:24:16 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>useRef</strong></p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/41236b1e-b906-41a8-a8ff-d95779af986d/image.png" alt=""></p>
<ul>
<li><p>여기서 useRef는 현재 input에 들어있는 값을 잠시 홀딩해주고 </p>
</li>
<li><p>검색 버튼을 누를때 홀딩해둔 기존 input 값과 비교하여 </p>
</li>
<li><p>같으면 재렌더링 안해주고, 다르면 새로 렌더링하여 검색 결과 보여주도록 구현</p>
</li>
<li><p><strong>결론 :</strong> useRef는 필요한 값을 홀딩해서 불필요한 렌더링을 줄여준다</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.21] TIL 43일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.21-TIL-42%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.21-TIL-42%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Fri, 28 Jun 2024 14:14:41 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h2 id="발표-당일">발표 당일</h2>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/cfadaa2c-a233-4ef1-b81d-112f72ee7dff/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/e8bf71f2-f756-4faf-93e0-7eacdccf671b/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/850bc902-a5e4-4250-b3f9-595e3b4f7cb7/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/d4179c43-3f0c-417a-8b83-78cbc17d2f46/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/284c2a08-3d20-4048-9489-13591bf28fab/image.png" alt=""></p>
</blockquote>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/4e9fa618-b8fb-4805-be54-5d8c621871b6/image.png" alt=""></p>
</blockquote>
<ul>
<li>배포 링크 : <a href="https://outsourcing-project-two.vercel.app/">https://outsourcing-project-two.vercel.app/</a></li>
</ul>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/40482a6d-5d82-4c9e-b54a-483a1ae236c4/image.png" alt=""></p>
</blockquote>
<ul>
<li>저희가 프로젝트를 진행하면서 가장 크게 막혔던 부분은 크롤링 작업중에 생긴 문제였습니다</li>
<li>기획상으로 크롤링 작업은 보이시는 세 파일에서 필요했지만, 다음과 같은 문제가 발생하여 
다른 방안으로 대체 했습니다.</li>
</ul>
<blockquote>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/5f903226-6aaa-4e10-8393-0ee873ae3940/image.png" alt=""></p>
</blockquote>
<p>일단 저희가 생각한 기본 로직은…</p>
<blockquote>
</blockquote>
<ol>
<li>크롤링을 할 웹사이트를 열어 필요한 데이터의 태그나 클래스 등을 선택하여 데이터를 추출하고,</li>
<li>추출한 json데이터를 csv형식으로 변환시킵니다.</li>
<li>변환한 csv파일을 ../csv 폴더 안에 생성하는데 이때 ../csv 폴더가 없으면 해당 폴더를 생성하고 변환한 csv파일을 저장합니다</li>
<li>최종적으로는 크롤링한 데이터를 사용자가 접속했을때 보여주고, 
 만약 사용자가 최신 데이터를 가져오기 위해 새로고침을 하면,최신 데이터를 크롤링하고, 
 supabase에 있는 데이터들도 자동으로 클롤링된 데이터로 업데이트 시켜주는 것을 생각했습니다.<blockquote>
</blockquote>
그래서 저희는 필요한 정보를 크롤링 해오는 것 까지는 성공을 했었는데
마지막으로 크롤링한 데이터를 가져오는 부분에서  express와 같은 서버를 이용하여 만들어야 한다는 것을 알게되었고,
저희가 배우지 않은 부분이라 한계가 있었습니다.<blockquote>
</blockquote>
그래서 현재는 csv파일을 supabase에서 import csv를 통해 직접 업로드하고 있고
추후에 next.js를 배우고 구현을 시도하기로 마무리 되었습니다<blockquote>
</blockquote>
저희는 이 프로젝트를 진행하며 다양한 기술을 사용해볼 수 있어 좋았습니다.
그리고 협업 시에 
깃허브를 어떻게 사용해야 하는지,
소통은 어떤식으로 해야하는지에 대해 많이 배우게 되었던 것 같습니다!</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.20] TIL 42일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.20-TIL-41%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.20-TIL-41%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Fri, 28 Jun 2024 13:52:33 GMT</pubDate>
            <description><![CDATA[<h2 id="팀프로젝트-마무리">팀프로젝트 마무리</h2>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/54f8b308-cead-407b-ac32-1b06145a9748/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/3cfeea1c-e7bb-448a-b7c2-bf1ba1bccce4/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/a8fb0fa1-2a51-4315-a577-0c71883ac73e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/fe_sunmin/post/99503302-6c30-49c0-b358-e4eb21cd15e7/image.png" alt=""></p>
<blockquote>
<p><strong>정리</strong>
이번 팀 프로젝트에는 총 3개의 페이지로 구성이 되어있다</p>
<blockquote>
<p><strong>페이지 구성</strong></p>
</blockquote>
</blockquote>
<ul>
<li><strong>메인 페이지</strong><blockquote>
<blockquote>
</blockquote>
</blockquote>
<ul>
<li>최근 경기 결과</li>
<li>팀 순위</li>
<li>각 구단의 하이라이트 영상</li>
<li>경기 매치표 및 날씨 정보</li>
<li>야구 추천 유튜버<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
</li>
<li><strong>검색 페이지</strong><blockquote>
<blockquote>
</blockquote>
</blockquote>
<ul>
<li>검색 결과</li>
<li>태그 검색<blockquote>
<blockquote>
</blockquote>
</blockquote>
</li>
</ul>
</li>
<li><strong>구단별 페이지</strong><blockquote>
<blockquote>
</blockquote>
</blockquote>
<ul>
<li>구단 관련 영상</li>
<li>input 검색 및 태그 검색 기능</li>
<li>페이지네이션</li>
<li>응원 댓글 작성 기능<blockquote>
<blockquote>
<p><strong>사용한 스택</strong></p>
</blockquote>
</blockquote>
</li>
</ul>
</li>
<li>React</li>
<li>JavaScript</li>
<li>Tailwind Css</li>
<li>Supabase</li>
<li>유튜브 API</li>
<li>날씨 API</li>
</ul>
<blockquote>
<p><strong>느낀점</strong></p>
<blockquote>
<p>이번 프로젝트는 시작할때 계획도 확실히 짜고 역할 분담도 정확히 한 후 시작했더니
확실히 효율도 높아지고 여유롭게 끝낼 수 있었던 것 같다.
아직 익숙하지 않은 부분들도 있지만, 이번 프로젝트를 계기로 supabase나 api 등에 조금 익숙해졌다.
내가 짠 부분이 아니여도 다른 팀원분들이 짠 코드나 발생한 에러를 같이 확인하고 고쳐나가면서 자연스럽게 공부하게 되어서 좋은 경험이었다.</p>
</blockquote>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.19] TIL 41일차 ]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.19-TIL-41%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.19-TIL-41%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 19 Jun 2024 14:15:11 GMT</pubDate>
            <description><![CDATA[<h2 id="usecontext">useContext</h2>
<blockquote>
<p><strong>useContext 란?</strong></p>
</blockquote>
<ul>
<li><p><code>context</code>를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.</p>
</li>
<li><p>Context란?</p>
<ul>
<li>전역적인 데이터를 전달하기에 편리함</li>
<li>App 안에서 전역적으로 사용되는 데이터들을 여러 컴포넌트들끼리 쉽게 공유할 수 있는 방법        제공</li>
<li>Props를 일일이 전해주는 것이 아니라 상위 컴포넌트에서 필요한 컴포넌트에게 전해줄 수 있게됨</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Context를 사용할 때 Props가 필요한 이유</strong></p>
</blockquote>
<ul>
<li><p>Context를 사용하면 컴포넌트 재사용하기 어려워질 수 있다.</p>
<ul>
<li>Context는 꼭 필요할때만 사용해야 함     </li>
<li>Props drilling을 피하기 위한 목적이라면 Component Composition(컴포넌트 합성)을 먼저 고려</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Context 세가지 개념</strong></p>
</blockquote>
<ul>
<li><p>contextAPI를 사용하기 위해서는 <code>Provider</code>, <code>Consumer</code>, <code>createContext</code> 개념을 알고있어야 한다.</p>
<ul>
<li><p><strong>createContext</strong> : context 객체를 생성한다.</p>
<ul>
<li><p>export const ThemeContext = createContext(초기값);</p>
<ul>
<li><p>createContext 내부는 초기값이 들어간다.</p>
<ul>
<li>상위에서 Provider로 감싸주지 않았다면 초기값을 받아와서 사용하는 것이다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>- **Provider** : 생성한 context를 하위 컴포넌트에게 전달하는 역할을 한다.
    - Children을 감싸고 전달하고자 하는 값을 value로 넣어준다.

- **Consumer** : context의 변화를 감시하는 컴포넌트이다.</code></pre><blockquote>
<p><strong>참조</strong></p>
</blockquote>
<p> <strong>useContext(SomeContext))</strong></p>
<ul>
<li><p>컴포넌트 최상위 레벨에서 useContext를 호출하여 context를 읽고 구독한다.</p>
<pre><code>    import { useContext } from &#39;react&#39;;

    function MyComponent() {
      const theme = useContext(ThemeContext);
      // ...</code></pre></li>
</ul>
<blockquote>
<p><strong>매개변수</strong></p>
</blockquote>
<ul>
<li><strong>SomeContext</strong> : 이전에 createContext로 생성한 context이다.</li>
<li>context 자체는 정보를 보유하지 않으며, 컴포넌트에서 제공하거나 읽을 수 있는 정보의 종류를 나타낸다.</li>
</ul>
<blockquote>
<p><strong>반환값</strong></p>
</blockquote>
<ul>
<li>useContext는 호출하는 컴포넌트에 대한 context 값을 반환한다.</li>
<li>이 값은 호출한 컴포넌트에서 트리상 위에 있는 가장 가까운 SomeContext에 전달된 value     이다.</li>
<li>이러한 provider가 없는 경우 반환되는 값은 해당 context에 대해 createContext에 전달한 defaultValue가 된다.</li>
<li>반환 값은 항상 최신값이다.</li>
<li>React는 context가 변경되면 context를 읽는 컴포넌트를 자동으로 리렌더링한다.</li>
</ul>
<blockquote>
<p><strong>주의사항</strong></p>
</blockquote>
<ol>
<li><p>컴포넌트의 useContext() 호출은 동일한 컴포넌트에서 반환된 provider의 영향을 받지 않는다.</p>
<ul>
<li>해당 &lt;Context.Provider&gt;는 반드시 useContext() 호출을 수행하는 컴포넌트 위에          있어야 함</li>
</ul>
</li>
<li><p>React는 변경된 value를 받는 provider부터 시작해서 해당 context를 사용하는 자식들에 대해서까지 전부 자동으로 리렌더링 한다.</p>
<ul>
<li>이전 값과 다음 값은 Object.js로 비교함</li>
<li>memo로 리렌더링을 건너뛰어도 새로운 context 값을 수신하는 자식들을 막지는 못함</li>
</ul>
</li>
<li><p>빌드 시스템이 출력 결과에 중복 모듈을 생성하는 경우 context가 손상될 수 있다.</p>
<ul>
<li>context를 통해 무언가 전달하는 것은 <code>===</code> 비교에 의해 결정되는 것처럼 context를         제공하는 데 사용하는 SomeContext와 context를 읽는 데 사용하는 SomeConetext        가 정확하세 동일한 객체인 경우에만 작동함</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>사용법</strong></p>
</blockquote>
<blockquote>
<p><strong>트리 깊숙이 데이터 전달하기</strong></p>
</blockquote>
<ul>
<li>컴포넌트의 최상위 레벨에서 useContext를 호출하여 context를 읽고 구독한다.<pre><code>  import { useContext } from &#39;react&#39;;
&gt;
  function Button() {
    const theme = useContext(ThemeContext);
    // ...</code></pre></li>
<li>useContext는 전달한 context에 대한 context 값을 반환한다.</li>
<li>context 값을 결정하기 위해 React는 컴포넌트 트리를 검색하고 특정 context에 대해 위에서 가장 가까운 context provider를 찾는다.<blockquote>
</blockquote>
<pre><code>  function MyPage() {
    return (
      &lt;ThemeContext.Provider value=&quot;dark&quot;&gt;
        &lt;Form /&gt;
      &lt;/ThemeContext.Provider&gt;
    );
  }
&gt;
  function Form() {
    // ... renders buttons inside ...
  }</code></pre></li>
<li>provider와 Button 사이에 얼마나 많은 컴포넌트 레이어가 있는지는 중요하지 않다.</li>
<li>From 내부의 Button이 useContext(ThemeContext)를 호출하면 &quot;dark&quot;를 값으로 받는다.</li>
</ul>
<blockquote>
<p><strong>context를 통해 전달된 데이터 업데이트하기</strong></p>
</blockquote>
<ul>
<li>시간이 지남에 따라 context가 변경되기를 원하는 경우가 종종 있다</li>
<li>context를 업데이트하려면 state와 결합해야 한다.</li>
<li>부모 컴포넌트에 state 변수를 선언하고 현재 state를 context 값으로 provider에 전달한다.<blockquote>
</blockquote>
<pre><code>  function MyPage() {
    const [theme, setTheme] = useState(&#39;dark&#39;);
    return (
      &lt;ThemeContext.Provider value={theme}&gt;
        &lt;Form /&gt;
        &lt;Button onClick={() =&gt; {
          setTheme(&#39;light&#39;);
        }}&gt;
          Switch to light theme
        &lt;/Button&gt;
      &lt;/ThemeContext.Provider&gt;
    );
  }</code></pre></li>
<li>이제 provider 내부에 모든 Button은 현재 theme 값을 받게된다</li>
<li>provider에게 전달한 theme 값을 업데이트하기 위해 setTheme를 호출하면 모든 Button 컴포넌트가 새로운 light 값으로 리렌더링된다.</li>
</ul>
<blockquote>
<p><strong>fallback 기본값 지정하기</strong></p>
</blockquote>
<ul>
<li>React가 부모 트리에서 특정 context의 provider들을 찾을 수 없는 경우, useContext()가 반환하는 context 값은 해당 context를 생성할 때 지정한 기본값과 동일하다.<pre><code>  const ThemeContext = createContext(null);</code></pre><blockquote>
</blockquote>
</li>
<li>기본값은 절대 변경되지 않는다<ul>
<li>context를 업데이트하려면 앞서 설명된 대로 state를 사용해야 함<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>null 대신 기본값으로 사용할 수 있는 더 의미 있는 값이 있는 경우가 많다.<pre><code>  const ThemeContext = createContext(&#39;light&#39;);</code></pre></li>
<li>이렇게 하면 실수로 해당 provider 없이 일부 컴포넌트를 렌더링해도 중단되지 않는다.<ul>
<li>또한 테스트 환경에서 많은 provider를 설정하지 않고도 컴포넌트가 테스트 환경에서 잘 작동하는 데 도움이 된다.</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>트리 일부에 대한 context 재정의하기</strong></p>
</blockquote>
<ul>
<li>트리 일부분을 다른 값의 provider로 감싸 해당 부분에 대한 context를 재정의할 수 있다.<pre><code>  &lt;ThemeContext.Provider value=&quot;dark&quot;&gt;
    ...
    &lt;ThemeContext.Provider value=&quot;light&quot;&gt;
      &lt;Footer /&gt;
    &lt;/ThemeContext.Provider&gt;
    ...
  &lt;/ThemeContext.Provider&gt;
``
</code></pre></li>
</ul>
<blockquote>
<p><strong>객체 및 함수 전달 시 리렌더링 최적화</strong></p>
</blockquote>
<ul>
<li>context를 통해 객체와 함수를 포함한 모든 값을 전달할 수 있다.<pre><code>  function MyApp() {
    const [currentUser, setCurrentUser] = useState(null);
&gt;
    function login(response) {
      storeCredentials(response.credentials);
      setCurrentUser(response.user);
    }
&gt;
    return (
      &lt;AuthContext.Provider value={{ currentUser, login }}&gt;
        &lt;Page /&gt;
      &lt;/AuthContext.Provider&gt;
    );
  }</code></pre></li>
<li>여기서 context 값은 두 개의 프로퍼티를 가진 Js 객체이며, 그 중 하나는 함수다.</li>
<li>MyApp이 리렌더링할 때마다, 이것은 다른 함수를 가리키는 다른 객체가 될 것이므로 React는 useContext(AuthContext)를 호출하는 트리 깊숙한 곳의 모든 컴포넌트도 리렌더링해야 한다.<blockquote>
</blockquote>
</li>
<li>소규모 앱에서는 문제가 되지 않는다.<ul>
<li>그러나 currentUser와 같은 기초 데이터가 변경되지 않았다면 리렌더링할 필요가 없다</li>
<li>React는 이 사실을 활용할 수 있도록 login 함수를 useCallback으로 감싸고 객체 생성은 useMemo로 감싸면 된다.</li>
<li>이것은 성능 최적화를 위한 것이다<blockquote>
</blockquote>
<pre><code>import { useCallback, useMemo } from &#39;react&#39;;
&gt;
function MyApp() {
const [currentUser, setCurrentUser] = useState(null);
&gt;
const login = useCallback((response) =&gt; {
  storeCredentials(response.credentials);
  setCurrentUser(response.user);
}, []);
&gt;
const contextValue = useMemo(() =&gt; ({
  currentUser,
  login
}), [currentUser, login]);
&gt;
return (
  &lt;AuthContext.Provider value={contextValue}&gt;
    &lt;Page /&gt;
  &lt;/AuthContext.Provider&gt;
);
}</code></pre></li>
</ul>
</li>
<li>이 변경으로 인해 MyApp이 리렌더링해야 하는 경우에도 currentUser가 변경되지 않는 한 useContext(AuthProvider)를 호출하는 컴포넌트는 리렌더링할 필요가 없다</li>
</ul>
<p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.18] TIL 40일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.18-TIL-40%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.18-TIL-40%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 18 Jun 2024 14:08:50 GMT</pubDate>
            <description><![CDATA[<h2 id="usecontext">useContext</h2>
<blockquote>
<p><strong>useContext 란?</strong></p>
</blockquote>
<ul>
<li><p><code>context</code>를 이용하면 단계마다 일일이 props를 넘겨주지 않고도 컴포넌트 트리 전체에 데이터를 제공할 수 있다.</p>
</li>
<li><p>Context란?</p>
<ul>
<li>전역적인 데이터를 전달하기에 편리함</li>
<li>App 안에서 전역적으로 사용되는 데이터들을 여러 컴포넌트들끼리 쉽게 공유할 수 있는 방법        제공</li>
<li>Props를 일일이 전해주는 것이 아니라 상위 컴포넌트에서 필요한 컴포넌트에게 전해줄 수 있게됨</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Context를 사용할 때 Props가 필요한 이유</strong></p>
</blockquote>
<ul>
<li><p>Context를 사용하면 컴포넌트 재사용하기 어려워질 수 있다.</p>
<ul>
<li>Context는 꼭 필요할때만 사용해야 함     </li>
<li>Props drilling을 피하기 위한 목적이라면 Component Composition(컴포넌트 합성)을 먼저 고려</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>Context 세가지 개념</strong></p>
</blockquote>
<ul>
<li><p>contextAPI를 사용하기 위해서는 <code>Provider</code>, <code>Consumer</code>, <code>createContext</code> 개념을 알고있어야 한다.</p>
<ul>
<li><p><strong>createContext</strong> : context 객체를 생성한다.</p>
<ul>
<li><p>export const ThemeContext = createContext(초기값);</p>
<ul>
<li><p>createContext 내부는 초기값이 들어간다.</p>
<ul>
<li>상위에서 Provider로 감싸주지 않았다면 초기값을 받아와서 사용하는 것이다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>- **Provider** : 생성한 context를 하위 컴포넌트에게 전달하는 역할을 한다.
    - Children을 감싸고 전달하고자 하는 값을 value로 넣어준다.

- **Consumer** : context의 변화를 감시하는 컴포넌트이다.</code></pre><blockquote>
<p><strong>참조</strong></p>
</blockquote>
<p> <strong>useContext(SomeContext))</strong></p>
<ul>
<li><p>컴포넌트 최상위 레벨에서 useContext를 호출하여 context를 읽고 구독한다.</p>
<pre><code>    import { useContext } from &#39;react&#39;;

    function MyComponent() {
      const theme = useContext(ThemeContext);
      // ...</code></pre></li>
</ul>
<blockquote>
<p><strong>매개변수</strong></p>
</blockquote>
<ul>
<li><strong>SomeContext</strong> : 이전에 createContext로 생성한 context이다.</li>
<li>context 자체는 정보를 보유하지 않으며, 컴포넌트에서 제공하거나 읽을 수 있는 정보의 종류를 나타낸다.</li>
</ul>
<blockquote>
<p><strong>반환값</strong></p>
</blockquote>
<ul>
<li>useContext는 호출하는 컴포넌트에 대한 context 값을 반환한다.</li>
<li>이 값은 호출한 컴포넌트에서 트리상 위에 있는 가장 가까운 SomeContext에 전달된 value     이다.</li>
<li>이러한 provider가 없는 경우 반환되는 값은 해당 context에 대해 createContext에 전달한 defaultValue가 된다.</li>
<li>반환 값은 항상 최신값이다</li>
<li>React는 context가 변경되면 context를 읽는 컴포넌트를 자동으로 리렌더링한다.</li>
</ul>
<p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2023.06.17] TIL 39일차]]></title>
            <link>https://velog.io/@fe_sunmin/2023.06.17-TIL-39%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2023.06.17-TIL-39%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Mon, 17 Jun 2024 12:52:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>useLayoutEffect란?</strong></p>
</blockquote>
<ul>
<li>useLayoutEffect는 브라우저가 화면을 다시 채우기 전에 실행되는 useEffect 한 버전이다</li>
</ul>
<blockquote>
<p><strong>useLayoutEffect 기본 형태</strong></p>
</blockquote>
<p><strong>useLayoutEffect(setup, dependencies?)</strong></p>
<ul>
<li><strong>setup</strong> : Effect의 주요 로직을 포함 /  컴포넌트가 DOM에 추가되거나, 의존성이 변경될 때 실행</li>
<li><strong>dependencies</strong> : 배열 형태이며, 배열 안에는 검사하고자 하는 특정 값 or 빈 배열</li>
</ul>
<ul>
<li>브라우저가 화면을 다시 그리기 전에 useLayoutEffect를 호출하여 레이아웃을 측정</li>
</ul>
<p>&lt;예시 코드&gt;</p>
<pre><code>    import { useState, useRef, useLayoutEffect } from &#39;react&#39;;

    function Tooltip() {
      const ref = useRef(null);
      const [tooltipHeight, setTooltipHeight] = useState(0);

      useLayoutEffect(() =&gt; {
        const { height } = ref.current.getBoundingClientRect();
        setTooltipHeight(height);
      }, []);
      // ...</code></pre><blockquote>
<p><strong>매개변수</strong></p>
</blockquote>
<ol>
<li><p><code>setup</code> : Effect의 로직이 포함된 함수</p>
<ul>
<li><p>동작</p>
<ul>
<li>컴포넌트가 DOM에 추가되기 전에 실행됩니다.</li>
<li>의존성이 변경될 때마다 다시 실행됩니다.</li>
<li>선택적으로 클린업 함수를 반환할 수 있습니다.</li>
</ul>
</li>
<li><p>cleanup 함수</p>
<ul>
<li>새 setup 함수가 실행되기 전에 이전 setup 함수의 클린업 함수가 실행</li>
<li>컴포넌트가 DOM에서 제거되기 전에 실행됩니다</li>
</ul>
</li>
</ul>
</li>
<li><p><code>optional deps</code> : setup 코드 내에서 참조된 모든 반응형 값의 목록 </p>
<ul>
<li>props, state, 컴포넌트 내부에서 선언된 변수와 함수</li>
<li>일정한 수의 항목을 가져야 하며, [dep1, dep2, dep3] 형식으로 인라인 작성해야 함<ul>
<li>React는 Object.is를 사용하여 각 의존성을 이전 값과 비교</li>
</ul>
</li>
<li>이 인수를 생략하면 컴포넌트가 다시 렌더링될 때마다 Effect가 다시 실행</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>반환값</strong></p>
</blockquote>
<ul>
<li>useLayoutEffect는 undefined를 반환한다.</li>
</ul>
<blockquote>
<p><strong>주의사항</strong></p>
</blockquote>
<ol>
<li><p><strong>최상위 레벨에서 호출</strong></p>
<ul>
<li><p>useLayoutEffect는 훅이므로 컴포넌트의 최상위 레벨 또는 자체 훅에서만 호출 가능</p>
</li>
<li><p>반복문이나 조건문 내부에서는 호출할 수 없다.</p>
<ul>
<li>필요하다면 컴포넌트를 추출하고 Effect를 그곳으로 이동</li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>Strict Mode에서의 동작</strong></p>
<ul>
<li>Strict Mode가 켜져 있으면 첫 번째 실제 셋업 전에 추가로 개발 전용 셋업+클린업 사이클이 실행<ul>
<li>이는 클린업 로직이 셋업 로직을 정확히 반영하는지 확인하는 테스트</li>
<li>이로 인해 문제가 발생하면 클린업 함수를 구현</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>의존성 관리</strong></p>
<ul>
<li>컴포넌트 내부에 정의된 객체 또는 함수가 의존성인 경우, Effect가 불필요하게 자주 실행될 수 있다<ul>
<li>불필요한 객체 및 함수 의존성을 제거하고, state 업데이트와 비반응형 로직을 Effect 외부로 추출</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>사용법</strong></p>
</blockquote>
<blockquote>
<p><strong>브라우저에서 화면을 다시 그리기 전 레이아웃 측정하기</strong></p>
</blockquote>
<ul>
<li>대부분의 컴포넌트는 위치와 크기를 몰라도 JSX만 반환하고 브라우저가 레이아웃을 계산한다.</li>
<li>하지만 툴팁처럼 마우스오버 시 정확한 위치에 렌더링해야 하는 경우가 있다.</li>
<li>툴팁의 올바른 위치를 결정하려면 툴팁의 높이를 알아야 한다.<blockquote>
</blockquote>
</li>
<li>이를 위해 두 번의 패스로 렌더링 해야 한다.<blockquote>
</blockquote>
</li>
<li><em>1. 툴팁을 임의의 위치에 렌더링*</em><ul>
<li>초기 위치는 잘못될 수 있음<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><em>2. 툴팁의 높이를 측정하고 위치 결정*</em><ul>
<li>툴팁의 실제 높이를 측정<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><em>3. 툴팁을 올바른 위치에 다시 렌더링*</em><ul>
<li>측정된 높이를 바탕으로 툴팁을 정확한 위치에 재렌더링<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>이 모든 작업은 브라우저가 화면을 다시 그리기 전에 이루어져야 한다.<ul>
<li>사용자가 툴팁이 움직이는 것을 보지 않기를 원하기 때문에</li>
<li>브라우저가 화면을 다시 그리기 전에 useLayoutEffect를 호출하여 레이아웃 측정을 수행<blockquote>
</blockquote>
<pre><code>function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); 
// 아직 실제 height 값을 모릅니다.
&gt;
useLayoutEffect(() =&gt; {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); 
// Re-render now that you know the real height
// 실제 높이를 알았으니 이제 리렌더링 합니다.
}, []);
&gt;
// ...아래에 작성될 렌더링 로직에 tooltipHeight를 사용합니다...
}</code></pre></li>
</ul>
</li>
</ul>
<ol>
<li>Tooltip은 초기 tooltipHeight = 0으로 렌더링됩니다<ul>
<li>따라서 툴팁의 위치가 잘못 지정될 수 있음</li>
</ul>
</li>
<li>React는 이를 DOM에 배치하고 useLayoutEffect에서 코드를 실행</li>
<li>useLayoutEffect는 툴팁 콘텐츠의 높이를 측정하고 즉시 다시 렌더링을 촉발</li>
<li>Tooltip이 실제 tooltipHeight로 다시 렌더링<ul>
<li>따라서 툴팁이 올바르게 배치</li>
</ul>
</li>
<li>React가 DOM에서 이를 업데이트하면 브라우저에 툴팁이 최종적으로 표시<blockquote>
</blockquote>
</li>
</ol>
<ul>
<li>Tooltip 컴포넌트가 두 번의 패스로 렌더링되어야 하지만, (먼저 tooltipHeight를 0으로 초기화한 다음 실제 측정된 높이) 최종 결과만 볼 수 있다는 점에 유의 </li>
</ul>
<blockquote>
<p><strong>useLayoutEffect는 브라우저가 다시 그리는 것을 차단</strong></p>
</blockquote>
<ul>
<li>useLayoutEffect를 사용하면 브라우저가 화면을 다시 그리기 전에 코드와 상태 업데이트가 처리되도록 보장한다.<blockquote>
</blockquote>
<ul>
<li>이를 통해 툴팁을 렌더링하고, 측정한 후, 다시 렌더링할 수 있음</li>
<li>이렇게 하면 첫 번째 렌더링을 사용자가 눈치채지 못하게 됨</li>
<li>useLayoutEffect는 브라우저의 페인팅을 차단<blockquote>
</blockquote>
</li>
</ul>
</li>
<li><em>1. import 및 상태를 초기화*</em><pre><code>   import { useRef, useLayoutEffect, useState } from &#39;react&#39;;
   import { createPortal } from &#39;react-dom&#39;;
   import TooltipContainer from &#39;./TooltipContainer.js&#39;;</code></pre></li>
<li><em>2. Tooltip 컴포넌트 생성*</em><pre><code>   export default function Tooltip({ children, targetRect }) {
     const ref = useRef(null);
     const [tooltipHeight, setTooltipHeight] = useState(0);
&gt;
     useLayoutEffect(() =&gt; {
       const { height } = ref.current.getBoundingClientRect();
       setTooltipHeight(height);
     }, []);</code></pre></li>
<li>ref를 사용해 툴팁의 DOM 요소를 참조한다</li>
<li>tooltipHeight 상태를 초기화한다</li>
<li>useLayoutEffect로 툴팁의 높이를 측정하여 tooltipHeight 상태를 업데이트한다</li>
<li><em>3. 툴팁 위치 계산*</em><pre><code>     let tooltipX = 0;
     let tooltipY = 0;
     if (targetRect !== null) {
       tooltipX = targetRect.left;
       tooltipY = targetRect.top - tooltipHeight;
       if (tooltipY &lt; 0) {
         tooltipY = targetRect.bottom;
       }
     }</code></pre></li>
<li>targetRect가 존재하면 툴팁의 X, Y 좌표를 계산합니다.</li>
<li>툴팁이 위쪽에 맞지 않으면 아래쪽에 배치합니다.<blockquote>
</blockquote>
</li>
<li><em>4. 툴팁 렌더링*</em><pre><code>     return createPortal(
       &lt;TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}&gt;
         {children}
       &lt;/TooltipContainer&gt;,
       document.body
     );
   }</code></pre></li>
<li>createPortal을 사용하여 툴팁을 document.body에 렌더링합니다.</li>
<li>TooltipContainer에 위치와 내용(자식 요소)을 전달합니다.<blockquote>
</blockquote>
</li>
<li><em>5. 정리*</em></li>
</ul>
<ol>
<li>Tooltip 컴포넌트는 useLayoutEffect를 사용해 툴팁의 높이를 측정 </li>
<li>이를 기반으로 툴팁의 위치를 계산하여 올바른 위치에 다시 렌더링 </li>
<li>이 과정에서 첫 번째 렌더링을 사용자가 눈치채지 못하게 브라우저의 페인팅을 차단<blockquote>
</blockquote>
</li>
</ol>
<p><strong>6.useLayoutEffect Vs useEffect</strong></p>
<ul>
<li>useLayoutEffect<blockquote>
</blockquote>
<ul>
<li>동기적 실행: useLayoutEffect는 DOM 업데이트 직후 즉시 실행 <ul>
<li>이는 화면을 다시 그리기 전에 코드가 실행되어, 화면에 보이기 전에 필요한 작업들(예: 레이아웃 측정)을 처리할 수 있음을 의미</li>
<li>따라서 사용자가 화면에서 변경 사항을 볼 때까지 대기하지 않고 바로 업데이트된 내용을 보여줄 수 있음<blockquote>
</blockquote>
</li>
</ul>
</li>
</ul>
</li>
<li>useEffect<blockquote>
</blockquote>
<ul>
<li>비동기적 실행: useEffect는 렌더링 후 비동기적으로 실행 </li>
<li>이는 DOM 업데이트 이후에 발생하므로, 화면에 바로 반영되지 않을 수 있음 </li>
<li>따라서 useEffect를 사용할 경우, 초기 렌더링 시에 UI가 제대로 조정되기 전에 사용자가 변경된 내용을 일시적으로 볼 수 있을 수 있음</li>
</ul>
</li>
</ul>
<p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.13] TIL 38일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.13-TIL-38%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.13-TIL-38%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Thu, 13 Jun 2024 14:06:24 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>useReducer란?</strong></p>
</blockquote>
<ul>
<li>useReducer는 useState와 같은 상태 관리, 상태 업데이트 훅이다.</li>
<li>변경할 값이 많을 때, 즉 상태 관리 할 데이터가 많아질 때 사용을 고민해야 한다.</li>
</ul>
<blockquote>
<p><strong>useReducer 기본 형태</strong></p>
</blockquote>
<p><strong>[ const [state, dispatch ] = useReducer(reducer, initialState); ]</strong></p>
<ul>
<li><strong>state</strong> : 상태 이름 (컴포넌트에서 사용할 상태)</li>
<li><strong>dispatch</strong> : 상태를 변경 시 필요한 정보를 전달하는 함수</li>
<li><strong>reducer</strong> : dispatch를 확인해서 state를 변경해주는 함수</li>
<li><strong>initialState</strong> : state에 전달할 초기 값</li>
</ul>
<p>&lt;예시 코드&gt;</p>
<pre><code>  import { useReducer } from &#39;react&#39;;

  function reducer(state, action) {
    // ...
  }

  function MyComponent() {
    const [state, dispatch] = useReducer(reducer, { age: 42 });
    // ...</code></pre><blockquote>
<p><strong>매개변수</strong></p>
</blockquote>
<ol>
<li><p><code>reducer</code> : reducer 함수는 상태가 업데이트되는 방식을 지정</p>
<ul>
<li><p>순수 함수여야 한다</p>
<ul>
<li>동일 인자에 대해 항상 동일한 결과를 반환해야 함<ul>
<li>외부 상태를 변경하거나 부작용을 일으키면 안됨</li>
</ul>
</li>
</ul>
</li>
<li><p>state와 action을 인자로 받아야 한다.</p>
</li>
<li><p>다음 상태를 반환해야 한다.</p>
</li>
<li><p>state와 action은 어떤 유형이든 가능하다.</p>
</li>
</ul>
</li>
<li><p><code>initialArg</code> : 초기 상태가 계산되는 값</p>
<ul>
<li><p>모든 유형의 값이 될 수 있다.</p>
</li>
<li><p>초기 상태를 계산하는 데 사용된다.</p>
<ul>
<li>구체적인 계산 방법은 <code>init</code> 인자에 의해 결정됨</li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>선택적 <code>init</code> : 초기 상태를 계산하는 초기화 함수</p>
<ul>
<li><p>초기 상태를 반환해야 한다.</p>
</li>
<li><p>사용 방식</p>
<ul>
<li><code>init</code> 함수가 지정되지 않는 경우, 초기 상태는 <code>initialArg</code> 값으로 설정    </li>
<li><code>init</code> 함수가 지정된 경우, 초기 상태는 <code>init(initialArg)</code> 호출 결과로 설정</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>반환값</strong></p>
</blockquote>
<ul>
<li><p>useReducer는 정확히 두 개의 값을 가진 배열을 반환한다.</p>
<ul>
<li>현재 state, 첫번째 렌더링 중에는 <code>init(initialArg)</code> 또는 <code>initialArg</code>로 설정</li>
<li>state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 dispatch function</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>주의사항</strong></p>
</blockquote>
<ol>
<li><p><strong>useReducer 호출 위치</strong></p>
<ul>
<li><p>useReducer는 훅이므로 다음 위치에서만 호출할 수 있다.</p>
<ul>
<li>컴포넌트의 최상위 레벨</li>
<li>사용자 정의 훅</li>
</ul>
</li>
<li><p>반복문이나 조건문 내부에서는 useReducer를 호출할 수 없다.</p>
<ul>
<li>만약 반복문이나 조건문 내부에서 상태를 관리해야 한다면, 
  새 컴포넌트를 만들어서 그 안으로 옮겨야 함</li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>Strict Mode에서의 동작</strong></p>
<ul>
<li><p>Strict Mode에서는 React가 의도치 않은 불순물(부작용)을 찾기 위해 reducer와 
  초기화 함수를 두 번 호출한다.</p>
<ul>
<li>이 동작은 개발 모드에서만 발생하며, 상용 환경에서는 발생하지 않음</li>
</ul>
</li>
<li><p>reducer와 초기화 함수가 순수 함수라면, 이 동작이 로직에 영향을 미치지 않는다.</p>
<ul>
<li>두 번 호출된 결과 중 하나는 무시됨</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>사용법</strong></p>
</blockquote>
<blockquote>
<p><strong>컴포넌트에 추가하기</strong></p>
</blockquote>
<ul>
<li>컴포넌트 최상위 레벨에서 useReducer를 호출하여 reducer를 state 관리한다.<pre><code>  import { useReducer } from &#39;react&#39;;
&gt;
  function reducer(state, action) {
    // ...
  }
&gt;
  function MyComponent() {
    const [state, dispatch] = useReducer(reducer, { age: 42 });
    // ...</code></pre></li>
<li>useReducer는 정확히 두 개의 항목이 있는 배열을 반환한다.</li>
</ul>
<ol>
<li>이 state 변수는 현재 state -&gt; 처음에 제공한 초기 state로 설정됨</li>
<li>상호작용에 반응하여 이를 변경할 수 있는 dispatch 함수</li>
<li>화면에 표시되는 내용을 업데이트 하려면 사용자가 수행한 작업을 나타내는 객체, 
 즉, 액션을 사용하여 dispatch를 호출<pre><code> function handleClick() {
   dispatch({ type: &#39;incremented_age&#39; });
 }</code></pre></li>
<li>React는 현재 state와 액션을 reducer 함수에 전달한다.</li>
<li>Reducer는 다음 state를 계산하고 반환한다.</li>
<li>React는 다음 state를 저장하고, 컴포넌트를 렌더링하고, UI를 업데이트 한다.<pre><code> import { useReducer } from &#39;react&#39;;
&gt;
 function reducer(state, action) {
   if (action.type === &#39;incremented_age&#39;) {
     return {
       age: state.age + 1
     };
   }
   throw Error(&#39;Unknown action.&#39;);
 }
&gt;
 export default function Counter() {
   const [state, dispatch] = useReducer(reducer, { age: 42 });
&gt;
   return (
     &lt;&gt;
       &lt;button onClick={() =&gt; {
         dispatch({ type: &#39;incremented_age&#39; })
       }}&gt;
         Increment age
       &lt;/button&gt;
       &lt;p&gt;Hello! You are {state.age}.&lt;/p&gt;
     &lt;/&gt;
   );
 }</code></pre></li>
</ol>
<blockquote>
<p><strong>reducer 함수 작성하기</strong></p>
</blockquote>
<ul>
<li>Reducer 함수는 다음과 같이 선언된다.<pre><code>  function reducer(state, action) {
    // ...
  }</code></pre></li>
<li>그런 다음 state를 계산하고 반환할 코드를 입력해야 한다.<ul>
<li>관례상 switch 문으로 작성하는 것이 일반적이다.</li>
<li>switch의 각 case에 대해 다음 state를 계산하고 반환해야 한다.<pre><code>function reducer(state, action) {
switch (action.type) {
  case &#39;incremented_age&#39;: {
    return {
      name: state.name,
      age: state.age + 1
    };
  }
  case &#39;changed_name&#39;: {
    return {
      name: action.nextName,
      age: state.age
    };
  }
}
throw Error(&#39;Unknown action: &#39; + action.type);
}</code></pre></li>
</ul>
</li>
<li>액션이 어떤 형태든 가질 수 있다.     <ul>
<li>관례상 액션을 식별하는 type 프로퍼티가 있는 객체를 전달하는 것이 일반적이다.</li>
<li>여기에는 reducer가 다음 state를 계산하는데 필요한 최소한의 필수 정보가 포함되어야 한다.<pre><code>function Form() {
const [state, dispatch] = useReducer(reducer, { name: &#39;Taylor&#39;, age: 42 });
&gt;  
function handleButtonClick() {
  dispatch({ type: &#39;incremented_age&#39; });
}
&gt;
function handleInputChange(e) {
  dispatch({
    type: &#39;changed_name&#39;,
    nextName: e.target.value
  });
}
// ...</code></pre></li>
</ul>
</li>
<li>액션 유형 이름은 컴포넌트에 로컬로 지정된다<ul>
<li>각 액션은 아무리 많은 데이터를 변경하게 되더라도 오직 하나의 상호작용만을 기술</li>
<li>state의 모양은 임의적이지만 일반적으로 객체나 배열이 될 것 같다</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>초기 state 재생성 방지하기</strong></p>
</blockquote>
<ul>
<li>React는 초기 state를 한 번 저장하고 다음 렌더링에서 이를 무시한다.<pre><code>  function createInitialState(username) {
    // ...
  }
  &gt;
  function TodoList({ username }) {
    const [state, dispatch] = useReducer(reducer, createInitialState(username));
    // ...</code></pre></li>
<li>createInitialState(username)의 결과는 초기 렌더링에서만 사용되지만, 이후의 모든 렌더링에서도 여전히 이 함수를 호출하게 된다<ul>
<li>이는 큰 배열을 만들거나 값비싼 계산을 수행하는 경우 낭비가 될 수 있다.<blockquote>
</blockquote>
</li>
</ul>
</li>
<li>이 문제를 해결하려면 useReducer 세번 째 인수에 초기화 함수를 전달할 수 있다.<pre><code>  function createInitialState(username) {
    // ...
  }
&gt;
  function TodoList({ username }) {
    const [state, dispatch] = useReducer(reducer, username, createInitialState);
    // ...</code></pre></li>
<li>함수를 호출한 결과인 createInitialState()가 아니라 함수 자체 createInitialState를 전달하고 있다는 점에 유의해야 한다.<ul>
<li>이렇게 하면 초기화 후에는 초기 state가 다시 생성되지 않는다.</li>
</ul>
</li>
<li>위의 예에서 createInitialState는 username 인수를 받는다.<ul>
<li>초기화 함수가 초기 state를 계산하는 데 아무런 정보가 필요 하지 않는 경우, useReducer의 두번째 인수로 null을 전달할 수 있다.</li>
</ul>
</li>
</ul>
<p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.12] TIL 37일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.12-TIL-37%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.12-TIL-37%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 12 Jun 2024 14:10:22 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><strong>useReducer란?</strong></p>
</blockquote>
<ul>
<li>useReducer는 useState와 같은 상태 관리, 상태 업데이트 훅이다.</li>
<li>변경할 값이 많을 때, 즉 상태 관리 할 데이터가 많아질 때 사용을 고민해야 한다.</li>
</ul>
<blockquote>
<p><strong>useReducer 기본 형태</strong></p>
</blockquote>
<p><strong>[ const [state, dispatch ] = useReducer(reducer, initialState); ]</strong></p>
<ul>
<li><strong>state</strong> : 상태 이름 (컴포넌트에서 사용할 상태)</li>
<li><strong>dispatch</strong> : 상태를 변경 시 필요한 정보를 전달하는 함수</li>
<li><strong>reducer</strong> : dispatch를 확인해서 state를 변경해주는 함수</li>
<li><strong>initialState</strong> : state에 전달할 초기 값</li>
</ul>
<p>&lt;예시 코드&gt;</p>
<pre><code>  import { useReducer } from &#39;react&#39;;

  function reducer(state, action) {
    // ...
  }

  function MyComponent() {
    const [state, dispatch] = useReducer(reducer, { age: 42 });
    // ...</code></pre><blockquote>
<p><strong>매개변수</strong></p>
</blockquote>
<ol>
<li><p><code>reducer</code> : reducer 함수는 상태가 업데이트되는 방식을 지정</p>
<ul>
<li><p>순수 함수여야 한다</p>
<ul>
<li>동일 인자에 대해 항상 동일한 결과를 반환해야 함<ul>
<li>외부 상태를 변경하거나 부작용을 일으키면 안됨</li>
</ul>
</li>
</ul>
</li>
<li><p>state와 action을 인자로 받아야 한다.</p>
</li>
<li><p>다음 상태를 반환해야 한다.</p>
</li>
<li><p>state와 action은 어떤 유형이든 가능하다.</p>
</li>
</ul>
</li>
<li><p><code>initialArg</code> : 초기 상태가 계산되는 값</p>
<ul>
<li><p>모든 유형의 값이 될 수 있다.</p>
</li>
<li><p>초기 상태를 계산하는 데 사용된다.</p>
<ul>
<li>구체적인 계산 방법은 <code>init</code> 인자에 의해 결정됨</li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="3">
<li><p>선택적 <code>init</code> : 초기 상태를 계산하는 초기화 함수</p>
<ul>
<li><p>초기 상태를 반환해야 한다.</p>
</li>
<li><p>사용 방식</p>
<ul>
<li><code>init</code> 함수가 지정되지 않는 경우, 초기 상태는 <code>initialArg</code> 값으로 설정    </li>
<li><code>init</code> 함수가 지정된 경우, 초기 상태는 <code>init(initialArg)</code> 호출 결과로 설정</li>
</ul>
</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>반환값</strong></p>
</blockquote>
<ul>
<li><p>useReducer는 정확히 두 개의 값을 가진 배열을 반환한다.</p>
<ul>
<li>현재 state, 첫번째 렌더링 중에는 <code>init(initialArg)</code> 또는 <code>initialArg</code>로 설정</li>
<li>state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 dispatch function</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong>주의사항</strong></p>
</blockquote>
<ol>
<li><p><strong>useReducer 호출 위치</strong></p>
<ul>
<li><p>useReducer는 훅이므로 다음 위치에서만 호출할 수 있다.</p>
<ul>
<li>컴포넌트의 최상위 레벨</li>
<li>사용자 정의 훅</li>
</ul>
</li>
<li><p>반복문이나 조건문 내부에서는 useReducer를 호출할 수 없다.</p>
<ul>
<li>만약 반복문이나 조건문 내부에서 상태를 관리해야 한다면, 
  새 컴포넌트를 만들어서 그 안으로 옮겨야 함</li>
</ul>
</li>
</ul>
</li>
</ol>
<ol start="2">
<li><p><strong>Strict Mode에서의 동작</strong></p>
<ul>
<li><p>Strict Mode에서는 React가 의도치 않은 불순물(부작용)을 찾기 위해 reducer와 
  초기화 함수를 두 번 호출한다.</p>
<ul>
<li>이 동작은 개발 모드에서만 발생하며, 상용 환경에서는 발생하지 않음</li>
</ul>
</li>
<li><p>reducer와 초기화 함수가 순수 함수라면, 이 동작이 로직에 영향을 미치지 않는다.</p>
<ul>
<li>두 번 호출된 결과 중 하나는 무시됨</li>
</ul>
</li>
</ul>
</li>
</ol>
<p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.11] TIL 36일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.11-TIL-37%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.11-TIL-37%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 11 Jun 2024 12:09:54 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h4 id="useeffect란">useEffect란?</h4>
</blockquote>
<ul>
<li>리액트 컴포넌트가 렌더링 될 때마다 특정 작업을 실행할 수 있도록 하는 Hook</li>
<li>useEffect는 컴포넌트가 
  <strong>mount 됐을 때,</strong>
  <strong>컴포넌트가 unmount 됐을 때,</strong>
  <strong>컴포넌트가 update 됐을 때,</strong> 특정 작업을 처리할 수 있다.</li>
</ul>
<blockquote>
<p><strong>useEffect의 기본 형태</strong></p>
</blockquote>
<p><strong>[ useEffect( function, deps ) ]</strong></p>
<ul>
<li><p><strong>function</strong> : 수행하고자하는 작업</p>
</li>
<li><p><strong>deps</strong> : 배열 형태이며, 배열 안에는 검사하고자 하는 특정 값 or 빈 배열</p>
</li>
<li><p>useEffect의 기본 return 값은 <code>undefined</code>이다.</p>
</li>
<li><p>컴포넌트의 최상위 레벨에서 useEffect를 호출하여 Effect를 선언한다.</p>
<pre><code>  import { useEffect } from &quot;react&quot;;</code></pre></li>
</ul>
<p><strong>[ deps 여부의 차이점 ]</strong></p>
<p><strong>1. 의존성 배열을 생략한 경우</strong></p>
<pre><code>    useEffect(() =&gt; {
        console.log(&#39;컴포넌트가 렌더링 될 때마다 실행&#39;);
    });</code></pre><p><strong>2. 빈 배열을 전달한 경우</strong></p>
<pre><code>    function ExampleComponent() {
      useEffect(() =&gt; {
          console.log(&#39;컴포넌트가 처음 마운트 될 때만 실행&#39;);
      }, []);

      return &lt;div&gt;안녕하세요!&lt;/div&gt;
    }</code></pre><p><strong>3. 특정 값들이 포함된 배열을 전달한 경우</strong></p>
<pre><code>    function ExampleComponent() {
      const [count, setCount] = useState(0);

      useEffect(() =&gt; {
          console.log(&#39;업데이트 될 때 실행&#39;)
        console.log(`count 값이 변경되었습니다: ${count}`);
      }, [count]);

      return (
        &lt;div&gt;
          &lt;p&gt;{count} 번 클릭했습니다&lt;/p&gt;
          &lt;button onClick={() =&gt; setCount(count + 1)}&gt;클릭&lt;/button&gt;
        &lt;/div&gt;
      );
    }</code></pre><blockquote>
<p><strong>useEffect 매개변수</strong></p>
</blockquote>
<ul>
<li><p><strong><code>setup</code> 함수</strong></p>
<ul>
<li><p>setup은 Effect의 로직에 담긴 함수</p>
</li>
<li><p>이 함수는 컴포넌트가 DOM에 추가될 때 실행되고, 선택적으로 cleanup 함수를 반환 가능</p>
<ul>
<li><strong>컴포넌트가 DOM에 추가될 때</strong> 
  React가 <code>setup</code> 함수를 실행</li>
<li><strong>의존성이 변경되어 컴포넌트가 다시 렌더링될 때</strong>
  React가 이전 값으로 <code>cleanup</code> 함수를 실행 후 새로운 값으로 <code>setup</code> 함수를 다시 실행</li>
<li><strong>컴포넌트가 DOM에서 제거될 때</strong> 
  React가 마지막으로 <code>cleanup</code> 함수를 실행</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><p><strong><code>optional dependencies</code> 함수 (선택적 의존성)</strong></p>
<ul>
<li><p>deps는 setup 코드 내에서 참조되는 모든 반응형 값의 목록</p>
<ul>
<li><p>props, state, 그리고 컴포넌트 내부에서 선언된 모든 변수와 함수가 포함</p>
<ul>
<li><strong>목록 작성</strong>
의존성 목록은 [dep1, dep2, dep3]과 같은 고정된 수의 항목을 가진 배열 형태로         작성</li>
<li><strong>변화 감지</strong>
의존성 목록의 각 항목을 이전 값과 비교하여 변화가 있을때만 <code>setup</code> 함수를 실행</li>
<li><strong>린터 검사</strong>
React용으로 구성된 린터는 모든 반응형 값이 의존성 배열에 바르게 지정되었는지 확인</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>              function ExampleComponent() {
                const [count, setCount] = useState(0);

                // useEffect 안에 전달된 함수가 바로 setup 함수입니다.
                useEffect(() =&gt; {
                  console.log(`count 값이 변경되었습니다: ${count}`);

                  // 이 함수는 cleanup 함수로, setup 함수가 실행되기 전에 실행됩니다.
                  return () =&gt; {
                    console.log(&#39;클린업 중...&#39;);
                  };
                }, [count]); // 이 부분은 의존성 배열입니다.

                return (
                  &lt;div&gt;
                    &lt;p&gt;{count} 번 클릭했습니다&lt;/p&gt;
                    &lt;button onClick={() =&gt; setCount(count + 1)}&gt;클릭&lt;/button&gt;
                  &lt;/div&gt;
                );
              }</code></pre><blockquote>
<p><strong>주의사항</strong></p>
</blockquote>
<ol>
<li><p>useEffect는 컴포넌트의 최상위 레벨이나 자체적인 훅에서만 호출할 수 있다.</p>
<ul>
<li>반복문이나 조건문 내에서는 사용할 수 없음 </li>
<li>이럴 때는 새로운 컴포넌트를 추출하고 상태를 이동시켜야 함</li>
</ul>
</li>
<li><p>외부 시스템과의 동기화를 위한 목적이 아니라면, 보통 useEffect가 필요하지 않을 수 있다.</p>
</li>
<li><p>Strict 모드가 켜져있을 때, 첫번째 실제 셋업 전에 개발용 셋업 및 클린업 사이클을 한번 더 실행한다.</p>
<ul>
<li>이는 클린업 로직이 셋업 로직과 일치하는지 확인,</li>
<li>셋업이 수행한 작업을 중지하거나 취소하는지 확인하는 스트레스 테스트</li>
</ul>
</li>
<li><p>컴포넌트 내부에 정의된 객체나 함수가 의존성으로 사용될 경우, 불필요하게 useEffect가 더 자주 다시 실행될 수 있다.</p>
<ul>
<li>이를 해결하기 위해 불필요한 객체나 함수 의존성을 제거하거나, </li>
<li>Effect 외부로 상태 업데이트와 반응하지 않는 로직을 추출</li>
</ul>
</li>
<li><p>Effect가 사용자 상호작용으로 인해 발생하지 않았다면, React는 보통 브라우저가 화면을 업데이트 한 후에 Effect를 실행한다.</p>
<ul>
<li>만약 Effect가 시각적인 작업을 수행하고 있고 지연이 눈에 띄면 useEffect 대신 useLayoutEffect로 대체해야 함</li>
</ul>
</li>
<li><p>심지어 Effect가 사용자 상호작용으로 인해 발생했더라도, 브라우저는 상태 업데이트를 처리하기 전에 화면을 다시 그릴 수 있다.</p>
<ul>
<li>이것은 보통 원하는 동작이지만, 화면을 다시 그리지 않도록 브라우저를 차단해야 할 경우
useEffect를 useLayoutEffect로 대체해야 함</li>
</ul>
</li>
<li><p>Effects는 클라이언트에서만 실행되며, 서버 렌더링 중에는 실행되지 않는다.</p>
</li>
</ol>
<blockquote>
<p><strong>사용법</strong></p>
</blockquote>
<blockquote>
<p><strong>외부 시스템 연결하기</strong></p>
</blockquote>
<ul>
<li>때로는 컴포넌트가 페이지에 표시되는 동안 네트워크, 일부 브라우저 API 또는 타사 라이브러리에 연결 상태를 유지해야 할 수도 있다.</li>
<li>이러한 시스템은 React에서 제어되지 않으므로 외부(external)라고 한다.<pre><code>import { useEffect } from &#39;react&#39;;
import { createConnection } from &#39;./chat.js&#39;;
&gt;
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState(&#39;https://localhost:1234&#39;);
&gt;
useEffect(() =&gt; {
    const connection = createConnection(serverUrl, roomId);
  connection.connect();
&gt;
    return () =&gt; {
    connection.disconnect();
    };
}, [serverUrl, roomId]);
// ...
}</code></pre><blockquote>
</blockquote>
</li>
</ul>
<ol>
<li><strong>createConnection 함수를 사용하여 서버와의 연결을 생성한다.</strong><ul>
<li>이 함수는 서버 URL과 채팅방 ID를 인자로 받아서 해당 채팅방에 연결을 설정</li>
<li>그리고 이 연결 객체를 connection 변수에 저장</li>
</ul>
</li>
<li><strong>연결 객체를 만든 후에는 connect 메서드를 호출하여 서버와의 연결을 수립한다.</strong></li>
<li><strong>여기서의 cleanup 함수인 disconnect 메서드를 호출하여 서버와의 연결을 종료한다.</strong><ul>
<li>이 함수는 컴포넌트가 제거되거나 업데이트되기 전에 실행됨</li>
<li>이렇게 함으로써 메모리 누수를 방지하고 불필요한 리소스를 해제할 수 있음</li>
</ul>
</li>
<li><strong>마지막으로 useEffect의 의존성 배열을 확인한다.</strong><ul>
<li>serverUrl과 roomId가 포함</li>
<li>이는 useEffect가 실행될 조건을 결정하는데, 만약 이 값들이 변경되면 새로운 연결을 
설정해야 하기 때문</li>
</ul>
</li>
</ol>
<blockquote>
<p><strong>커스텀 훅으로 Effect 감싸기</strong></p>
</blockquote>
<ul>
<li>Effect를 수동으로 작성해야 하는 경우가 자주 발생한다면 이는 컴포넌트가 의존하는 일반적인 동작에 대한 커스텀 훅을 추출해야 한다는 신호일 수 있다.</li>
<li>예를 들어, 이 useChatRoom 커스텀 훅은 Effect의 로직을 보다 선언적인 API 뒤에 숨긴다.<pre><code>function useChatRoom({ serverUrl, roomId }) {
useEffect(() =&gt; {
  const options = {
    serverUrl: serverUrl,
    roomId: roomId
  };
  const connection = createConnection(options);
  connection.connect();
  return () =&gt; connection.disconnect();
}, [roomId, serverUrl]);
}</code></pre></li>
</ul>
<ol>
<li><strong>useChatRoom 훅은 서버와 채팅방의 연결을 설정하고 관리하는 역할을 한다.</strong><ul>
<li>useChatRoom은 serverUrl과 roomId라는 두 개의 매개변수를 받음</li>
<li>serverUrl : 연결할 서버의 URL </li>
<li>roomId : 채팅방의 ID</li>
</ul>
</li>
<li><strong>useEffect 훅은 의존성 배열에 있는 값이 변경될 때마다 실행</strong><ul>
<li>컴포넌트가 처음 마운트될 때와 serverUrl 또는 roomId가 변경될 때마다 실행</li>
</ul>
</li>
<li><strong>const option 부분은 연결을 설정한다.</strong><ul>
<li>createConnection 함수를 호출하여 서버와의 연결 객체를 생성</li>
<li>connection.connect()를 호출하여 연결을 수립</li>
</ul>
</li>
<li><strong>useEffect는 cleanup 함수를 반환한다.</strong><ul>
<li>이 함수는 컴포넌트가 언마운트되거나 의존성 배열의 값이 변경될 때 호출되어 연결을 해제합니다.<blockquote>
</blockquote>
<pre><code>function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState(&#39;https://localhost:1234&#39;);
&gt;
useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...</code></pre><blockquote>
</blockquote>
</li>
</ul>
</li>
<li><strong>ChatRoom 컴포넌트는 UI를 제공하고 useChatRoom 훅을 사용하여 채팅방 기능을 구현한다.</strong></li>
<li>** serverUrl 상태를 설정한다.**<ul>
<li>초기값은 &#39;<a href="https://localhost:1234&#39;">https://localhost:1234&#39;</a></li>
</ul>
</li>
<li><strong>useChatRoom 훅을 호출하여 서버와 채팅방의 연결을 설정하고 관리한다.</strong><ul>
<li>roomId와 serverUrl을 인자로 전달</li>
</ul>
</li>
</ol>
<p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.10] TIL 35일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024.06.10-TIL-35%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024.06.10-TIL-35%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Tue, 11 Jun 2024 00:40:49 GMT</pubDate>
            <description><![CDATA[<h2 id="-본캠프-35일차-기록-">[ 본캠프 35일차 기록 ]</h2>
<h3 id="🖥️-오늘-공부한-내용-🖥️">🖥️ 오늘 공부한 내용 🖥️</h3>
<blockquote>
<h4 id="-새로운-주차-시작"># 새로운 주차 시작</h4>
</blockquote>
<p>오늘은 새로운 개인 과제가 나갔고, Redux와 axios를 다뤄야하는 과제이다.
이번 과제는 주어진 시간이 짧아서 강의 먼저 빠르게 듣고 화요일부터 과제 수행에 들어가야겠다. 
그리고 오늘은 개인 일정으로 스터디 참여를 못해서 스터디 TIL 작성을 건너뛰지만 내일 모두 정리해서 올려야한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[2024.06.05] TIL 34일차]]></title>
            <link>https://velog.io/@fe_sunmin/2024-06-05-TIL-34%EC%9D%BC%EC%B0%A8</link>
            <guid>https://velog.io/@fe_sunmin/2024-06-05-TIL-34%EC%9D%BC%EC%B0%A8</guid>
            <pubDate>Wed, 05 Jun 2024 14:16:29 GMT</pubDate>
            <description><![CDATA[<h3 id="-useeffect"># useEffect</h3>
<blockquote>
<p><strong>반응형 의존성 지정</strong></p>
</blockquote>
<ul>
<li>Effect의 의존성을 &quot;선택&quot;할 수 없다는 점에 유의해야 한다.</li>
<li>Effect의 코드에서 사용되는 모든 반응형 값은 의존성으로 선언해야 한다.</li>
<li>Effect의 의존성 목록은 주변 코드에 의해 결정된다.</li>
<li><code>serverUrl</code> 또는 <code>roomId</code>가 변경되면 Effect는 새 값을 사용하여 채팅에 다시 연결한다.</li>
</ul>
<pre><code>     // This is a reactive value
    function ChatRoom({ roomId }) { 
       // This is a reactive value too
      const [serverUrl, setServerUrl] = useState(&#39;https://localhost:1234&#39;); 

      useEffect(() =&gt; {
           // This Effect reads these reactive values
        const connection = createConnection(serverUrl, roomId); 
        connection.connect();

        return () =&gt; connection.disconnect();

        // ✅ So you must specify them as dependencies of your Effect
      }, [serverUrl, roomId]); 
      // ...
    }</code></pre><br />

<ul>
<li>반응형 값에는 props와 컴포넌트 내부에서 직접 선언된 모든 변수, 함수가 포함된다.</li>
<li><code>roomId</code>와 <code>serverUrl</code>은 반응형 값이기 때문에 의존성 목록에서 제거할 수 없다.</li>
<li>만약 이 값을 생략하려고 할 때 린터가 React용으로 올바르게 구성되어 있다면, 린터는 이를 수정해야 하는 실수로 표시해준다.</li>
</ul>
<pre><code>    function ChatRoom({ roomId }) {
      const [serverUrl, setServerUrl] = useState(&#39;https://localhost:1234&#39;);

      useEffect(() =&gt; {
        const connection = createConnection(serverUrl, roomId);
        connection.connect();
        return () =&gt; connection.disconnect();
      }, []); // 🔴 React Hook useEffect has missing dependencies: &#39;roomId&#39; and &#39;serverUrl&#39;
      // ...
    }</code></pre><br />

<ul>
<li>의존성을 제거하려면, 의존성이어야 할 필요가 없음을 린터에게 <strong>증명</strong>해야 한다.</li>
<li>예를 들어, <code>serverUrl</code>을 컴포넌트 밖으로 이동시킴으로써 반응형이 아니며 리렌더링시에도 
변경되지 않음을 증명할 수 있다.</li>
</ul>
<pre><code>    const serverUrl = &#39;https://localhost:1234&#39;; // Not a reactive value anymore

    function ChatRoom({ roomId }) {
      useEffect(() =&gt; {
        const connection = createConnection(serverUrl, roomId);
        connection.connect();
        return () =&gt; connection.disconnect();
      }, [roomId]); // ✅ All dependencies declared
      // ...
    }</code></pre><ul>
<li>이제 <code>serverUrl</code>은 반응형 값이 아니므로 의존성이 될 필요가 없다.</li>
<li>Effect의 코드가 반응형 값을 사용하지 않는다면 의존성 목록은 비어 있어야 한다.([]);</li>
<li>빈 의존성이 있는 Effect는 컴포넌트의 props나 state가 변경되어도 다시 실행되지 않는다.</li>
</ul>
<pre><code>    const serverUrl = &#39;https://localhost:1234&#39;; // Not a reactive value anymore
    const roomId = &#39;music&#39;; // Not a reactive value anymore

    function ChatRoom() {
      useEffect(() =&gt; {
        const connection = createConnection(serverUrl, roomId);
        connection.connect();
        return () =&gt; connection.disconnect();
      }, []); // ✅ All dependencies declared
      // ...
    }</code></pre><br />

<blockquote>
<p><strong>Pitfall | 함정</strong></p>
</blockquote>
<ul>
<li>기존 코드베이스가 있는 경우 이와 같이 린터를 억제하는 Effect가 있을 수 있다.</li>
</ul>
<pre><code>    useEffect(() =&gt; {
      // ...
      // 🔴 Avoid suppressing the linter like this:
      // eslint-ignore-next-line react-hooks/exhaustive-deps
    }, []);</code></pre><ul>
<li>의존성이 코드와 일치하지 않으면 버그가 발생할 위험이 높다.</li>
<li>린터를 억제하는 것은 곧 Effect가 의존하는 값에 대해 React에 &quot;거짓말&quot;을 하는 것이다.</li>
<li>대신 의존성들이 불필요하다는 것을 증명해야 한다.</li>
</ul>
<br />

<blockquote>
<p><strong>반응형 의존성 전달 예시</strong></p>
</blockquote>
<ul>
<li>의존성 배열 전달하기<ul>
<li>의존성을 지정하면서 Effect는 초기 렌더링 후 및 변경된 의존성으로 다시 렌더링한 후에 실행된다.<pre><code>useEffect(() =&gt; {
// ...
}, [a, b]); // Runs again if a or b are different</code></pre><br /></li>
</ul>
</li>
<li></li>
<li>아래 예시에서 <code>serverUrl</code>과 <code>roomId</code>는 반응형 값이므로 둘 다 의존성으로 지정해야 한다. </li>
<li>따라서 드롭다운에서 다른 방을 선택하거나 서버 URL 입력을 수정하면 채팅이 다시 연결된다</li>
<li>하지만 <code>message</code>는 Effect에서 사용되지 않으므로 메시지를 편집해도 채팅에 다시 연결되지 않는다.<pre><code>// App.js
.
  import { useState, useEffect } from &#39;react&#39;;
  import { createConnection } from &#39;./chat.js&#39;;
.
  function ChatRoom({ roomId }) {
    const [serverUrl, setServerUrl] = useState(&#39;https://localhost:1234&#39;);
    const [message, setMessage] = useState(&#39;&#39;);
.
    useEffect(() =&gt; {
      const connection = createConnection(serverUrl, roomId);
      connection.connect();
      return () =&gt; {
        connection.disconnect();
      };
    }, [serverUrl, roomId]);
.
    return (
      &lt;&gt;
        &lt;label&gt;
          Server URL:{&#39; &#39;}
          &lt;input
            value={serverUrl}
            onChange={e =&gt; setServerUrl(e.target.value)}
          /&gt;
        &lt;/label&gt;
        &lt;h1&gt;Welcome to the {roomId} room!&lt;/h1&gt;
        &lt;label&gt;
          Your message:{&#39; &#39;}
          &lt;input value={message} onChange={e =&gt; setMessage(e.target.value)} /&gt;
        &lt;/label&gt;
      &lt;/&gt;
    );
  }
.
  export default function App() {
    const [show, setShow] = useState(false);
    const [roomId, setRoomId] = useState(&#39;general&#39;);
    return (
      &lt;&gt;
        &lt;label&gt;
          Choose the chat room:{&#39; &#39;}
          &lt;select
            value={roomId}
            onChange={e =&gt; setRoomId(e.target.value)}
          &gt;
            &lt;option value=&quot;general&quot;&gt;general&lt;/option&gt;
            &lt;option value=&quot;travel&quot;&gt;travel&lt;/option&gt;
            &lt;option value=&quot;music&quot;&gt;music&lt;/option&gt;
          &lt;/select&gt;
          &lt;button onClick={() =&gt; setShow(!show)}&gt;
            {show ? &#39;Close chat&#39; : &#39;Open chat&#39;}
          &lt;/button&gt;
        &lt;/label&gt;
        {show &amp;&amp; &lt;hr /&gt;}
        {show &amp;&amp; &lt;ChatRoom roomId={roomId}/&gt;}
      &lt;/&gt;
    );
  }</code></pre><pre><code>// Chat.js
.
  export function createConnection(serverUrl, roomId) {
    // A real implementation would actually connect to the server
    return {
      connect() {
        console.log(&#39;✅ Connecting to &quot;&#39; + roomId + &#39;&quot; room at &#39; + serverUrl + &#39;...&#39;);
      },
      disconnect() {
        console.log(&#39;❌ Disconnected from &quot;&#39; + roomId + &#39;&quot; room at &#39; + serverUrl);
      }
    };
  }</code></pre></li>
</ul>
<p><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></p>
]]></description>
        </item>
    </channel>
</rss>