<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Ideas Worth Spreading⭐</title>
        <link>https://velog.io/</link>
        <description>꾸준히, 깊게</description>
        <lastBuildDate>Sun, 15 Jun 2025 12:34:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Ideas Worth Spreading⭐</title>
            <url>https://velog.velcdn.com/images/minw0_o/profile/ca878713-3a80-40bd-8ace-76a3d540610b/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Ideas Worth Spreading⭐. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/minw0_o" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Terraform - IaC와 Terraform]]></title>
            <link>https://velog.io/@minw0_o/Terraform-IaC%EC%99%80-Terraform</link>
            <guid>https://velog.io/@minw0_o/Terraform-IaC%EC%99%80-Terraform</guid>
            <pubDate>Sun, 15 Jun 2025 12:34:13 GMT</pubDate>
            <description><![CDATA[<h2 id="🌱-iac">🌱 IaC</h2>
<p>인프라가 코드로 표현된다 </p>
<p>⇒ UI나 커맨드를 통한 수동 조작이 아닌 <strong>코드로 인프라를 관리</strong>하는 것</p>
<p>ex) Terraform에서는 하시코프 설정언어(HCL)를 사용 </p>
<hr>
<h2 id="🌱-인프라-자동화의-변화">🌱 인프라 자동화의 변화</h2>
<p>인프라 운영이 물리적인 데이터 센터(온 프레미스)에서부터 클라우드 환경에 이르기까지 형태가 변화하면서 운영하는 방식도 지속적으로 바뀌고 있다. </p>
<ol>
<li>문서로 관리</li>
</ol>
<ul>
<li>ex) 어떤 서비스가 어느 서버에서 실행되는 지 등을 엑셀에 기록</li>
<li>결국 사람이 실제 작업을 위한 명령어와 파일을 별도로 준비해야 함</li>
<li>재사용성이 낮음</li>
</ul>
<ol start="2">
<li>스크립트</li>
</ol>
<ul>
<li>반복되는 작업을 스크립트로 작성해 자동화</li>
<li>스크립트는 현재의 상태와 관계없이 순서대로 각 단계를 실행하기 때문에 실행 중인 최종 상태가 스크립트의 결과와 일치하지 않을 수 있다.</li>
</ul>
<ol start="3">
<li>가상 머신</li>
</ol>
<ul>
<li>미리 구성된 가상 머신 이미지는 템플릿화하여 저장하고 반복적으로 사용할 수 있음</li>
<li>하지만 이미지 변경을 위한 수동 작업은 여전히 존재</li>
</ul>
<ol start="4">
<li>클라우드 인프라 </li>
</ol>
<ul>
<li>상품화된 인프라와 데이터 센터 이용</li>
<li>확장성 뛰어남</li>
</ul>
<ol start="5">
<li>컨테이너</li>
</ol>
<ul>
<li>운영체제를 가상화한 환경 제공</li>
<li>컨테이너 오케스트레이션 환경을 사용한다면 특정 컴퓨팅 서버가 아닌 적절한 리소스가 있는 어딘가의 서버에 배포된다.</li>
</ul>
<hr>
<h2 id="🌱-iac-개념의-등장-배경">🌱 IaC 개념의 등장 배경</h2>
<p>이렇게 <strong>가상화 기술이 발전</strong>하면서, 여러 대의 서버를 더 많이 쉽게 만들 수 있게 됨</p>
<p>⇒ 늘어나는 서버들에 대한 프로비저닝과 운영에 대한 이슈 발생(인력을 무한정으로 늘릴 수 없음)</p>
<p>⇒ 서버 구축과 운영에 대한 자동화가 필요</p>
<p>⇒ 프로그래밍 코드로 인프라를 구축하고 운영할 수 있는 IaC 개념의 등장</p>
<hr>
<h2 id="🌱-iac의-장점">🌱 IaC의 장점</h2>
<ul>
<li><p>자동화</p>
<p>  수동으로 서버를 생성하는 게 아니라, 코드로 생성하기 때문에 서버 운명 및 관리를 모두 자동화할 수 있다. </p>
<p>  ex) <em>AWS에서 새로운 서버를 생성 하기 위해서 AWS 콘솔에 로그인할 필요없이 Terraform 코드를 실행시키기기만 하면 됩니다.</em></p>
</li>
<li><p>휴먼 에러 방지</p>
</li>
<li><p>문서화</p>
<ul>
<li>재사용성</li>
<li>여러 사람들 간 공유 가능</li>
<li>속도가 빠름 ⇒ 직접 cli에서 치는 것보다 문서화된 코드를 실행하는 게 훨씬 빠름</li>
</ul>
</li>
<li><p>형상관리</p>
</li>
<li><p>리뷰 및 테스트</p>
<p>  수동으로 서버 작업을 할 때는, 실제로 실행하기 전에 리뷰 하는 것이 굉장히 힘들수 밖에 없었습니다. 그래서 문제가 발견 되었을 때는 이미 프로덕션 애플리케이션에 영향을 주고 난 후인 경우가 많았는데, Terraform의 경우  코드 리뷰와 테스트를 통해  문제가 실제로 발생 되는 것을 어느 정도  예방할 수 있습니다.</p>
</li>
</ul>
<hr>
<h2 id="🌱-iac의-단점">🌱 IaC의 단점</h2>
<ul>
<li>코드 문법 학습</li>
<li>파이프라인 통합 ⇒ 기존 워크플로에 자동화를 위한 수고가 추가로 필요하다.</li>
<li>IaC 자체 지식과 더불어 관리 대상이 되는 인프라 지식이 함께 필요하다.</li>
</ul>
<hr>
<h2 id="🌱-테라폼의-특성">🌱 테라폼의 특성</h2>
<h3 id="테라폼의-3가지-철학">테라폼의 3가지 철학</h3>
<ul>
<li>workflow</li>
<li>IaC</li>
<li>실용주의</li>
</ul>
<blockquote>
<p>💡 workflow란?
어떤 기능 하나가 모든 문제를 해결해주지는 못한다
⇒ 테라폼은 개발자나 시스템 관리자가 일하는 방식과 유사한 “workflow”를 만들기 위한 도구로 설계되었다. 
⇒ workflow의 대상은 인프라 구성과 배포, 보안 구성, 계정 추가 작업 등이 될 수 있다.
⇒ 어떤 기술이 사용되더라도 workflow를 통해 환경의 변화에 크게 구애받지 않도록 설계되었다.
ex) 몇 년 전에 도입한 특정 인프라 환경이 회사 전략에 따라 새로운 환경으로 변경되어도 workflow 자체는 그대로 유지될 수 있다. </p>
</blockquote>
<hr>
<h2 id="🌱-프로바이더">🌱 프로바이더</h2>
<p>테라폼 자체만으로는 다양한 인프라와 서비스를 프로비저닝 할 수 없다. </p>
<p>⇒ 인프라와 서비스 사이에서 <strong>프로바이더</strong>가 인터페이싱을 해줘야한다. </p>
<ul>
<li>각 인프라와 서비스는 고유의 API를 가지고 있음</li>
<li>프로바이더는 각 API 명세를 테라폼 코드로 호출해 동작</li>
<li>마치 자바 코드에서 MySql을 연동할 때 해당 DB의 드라이버를 사용하는 것과 비슷한 원리</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/153d3021-091e-4d87-bb15-8f0e4f9bdfa3/image.jpeg" alt=""></p>
<p>테라폼은 인프라를 코드로 관리하는 도구(IaC: Infrastructure as Code)이다.  하지만 테라폼 자체는 아마존 웹 서비스(AWS), 구글 클라우드(GCP), 마이크로소프트 애저(Azure) 등 <strong>다양한 클라우드 플랫폼이나 SaaS(Software as a Service) 시스템의 동작 방식이나 API</strong>를 알지 못한다. </p>
<p>그래서 <strong>테라폼이 직접 인프라 리소스에 접근하고 생성하는 것이 아니라</strong>, “프로바이더(Provider)”를 통해 각 서비스와 통신하고 조작한다. </p>
<p>자바에서 MySQL을 연결할 때도, 자바 코드가 MySQL 프로토콜을 직접 이해하진 않는다. 대신 <strong>MySQL JDBC 드라이버</strong>가 자바의 명령을 MySQL에 맞게 번역해준다. </p>
<p>ex) API와 테라폼 코드 사이의 <strong>중개자 역할</strong></p>
<p>각 클라우드나 SaaS 서비스는 <strong>고유한 API</strong>를 제공한다. 예를 들어 AWS는 <code>ec2:RunInstances</code> 같은 API를 통해 EC2 인스턴스를 생성하고 이때 테라폼은 그 API를 직접 호출하지 않고, 대신 <strong>AWS 프로바이더</strong>에 다음과 같은 방식으로 명령을 내린다.</p>
<pre><code class="language-hcl">resource &quot;aws_instance&quot; &quot;example&quot; {
  ami           = &quot;ami-123456&quot;
  instance_type = &quot;t2.micro&quot;
}
</code></pre>
<p>이걸 받은 AWS 프로바이더는 내부적으로 해당 값을 AWS API로 변환해서 호출하고, 결과를 받아서 테라폼에게 알려준다. </p>
<hr>
<h2 id="🌱-테라폼-진행순서">🌱 테라폼 진행순서</h2>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/76082703-4fa6-4351-9c8f-a8a3291af5ad/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>단계</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><code>write</code></td>
<td>인프라 리소스를 정의하는 <strong>테라폼 코드(HCL)</strong> 작성</td>
</tr>
<tr>
<td><code>plan</code></td>
<td>코드대로 실행하면 <strong>무슨 일이 일어나는지 미리 확인</strong></td>
</tr>
<tr>
<td><code>apply</code></td>
<td>실제로 <strong>인프라를 생성하거나 변경</strong></td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[Terraform - state와 workspace]]></title>
            <link>https://velog.io/@minw0_o/Terraform-state-workspace</link>
            <guid>https://velog.io/@minw0_o/Terraform-state-workspace</guid>
            <pubDate>Sat, 14 Jun 2025 05:35:01 GMT</pubDate>
            <description><![CDATA[<h2 id="🌱state란">🌱state란?</h2>
<ul>
<li>테라폼 구성 파일은 기존 state와 실제 인프라 구성을 비교해 실행 계획에서 생성, 수정, 삭제 여부를 결정한다.</li>
<li><code>terraform plan</code>을 실행하면 암묵적으로 <strong>refresh 동작</strong>을 수행하면서 리소스 생성의 대상과 state를 기준으로 비교하는 과정을 거친다.</li>
<li><code>-refresh = false</code> 플래그를 사용해 리소스 생성 대상과의 동기화 과정을 생략하고 state를 기준으로 실행 계획을 생성할 수 있다.</li>
</ul>
<br>

<h3 id="terraform-plan-실행-시-기본-동작-과정">terraform plan 실행 시 기본 동작 과정</h3>
<ol>
<li><p><strong>현재 상태(state file)</strong> 로컬 또는 remote backend에서 읽어옴</p>
</li>
<li><p><strong>리프레시(refresh)</strong>: 현재 클라우드의 <strong>실제 리소스 상태</strong>를 API로 조회해서 <code>.tfstate</code>와 비교</p>
<p> ⇒ 만약 여기서 실제 리소스 상태와 state가 다르다면, 실제 리소스 상태에 맞춰서 state 파일을 동기화</p>
</li>
<li><p><strong>계획 생성(plan)</strong>: <code>.tf</code> 코드와 현재 상태를 비교해서 어떤 변경이 필요한지 계산</p>
</li>
</ol>
<br>

<h4 id="💡-refreshfalse란">💡 <code>refresh=false</code>란?</h4>
<pre><code class="language-bash">terraform plan -refresh=false</code></pre>
<p>이 명령은 👉 <strong>2번의 상태 리프레시를 건너뛰겠다는 뜻</strong>입니다. </p>
<blockquote>
<p>즉, Terraform은 현재 .tfstate 파일만 믿고 변경 사항을 계산합니다.</p>
<p>실제 클라우드 자원이 어떤 상태인지 <strong>조회하지 않습니다.</strong></p>
</blockquote>
</aside>

<hr>
<h3 id="plan과-apply에서-각-리소스에-발생할-수-있는-작업의-종류">Plan과 Apply에서 각 리소스에 발생할 수 있는 작업의 종류</h3>
<table>
<thead>
<tr>
<th>기호</th>
<th>의미</th>
<th>행동</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td>+</td>
<td>Create(add)</td>
<td>리소스 생성</td>
<td></td>
</tr>
<tr>
<td>-</td>
<td>Destroy</td>
<td>리소스 삭제</td>
<td></td>
</tr>
<tr>
<td>-/+</td>
<td>Replace(destroy &amp; add)</td>
<td>리소스 삭제 후 생성</td>
<td>lifecycle의 create_before_destroy 옵션을 통해 생성 후 삭제를 수행하도록 설정할 수 있다.</td>
</tr>
<tr>
<td>~</td>
<td>Update in-place(change)</td>
<td>리소스 수정</td>
<td></td>
</tr>
</tbody></table>
<br>

<h3 id="replace--vs-update-in-place-">Replace (<code>/+</code>) vs Update in-place (<code>~</code>)</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>Replace (<code>-/+</code>)</th>
<th>Update in-place (<code>~</code>)</th>
</tr>
</thead>
<tbody><tr>
<td>의미</td>
<td><strong>기존 리소스를 삭제하고 새로 생성</strong></td>
<td><strong>기존 리소스를 그대로 두고 속성만 변경</strong></td>
</tr>
<tr>
<td>리소스 ID</td>
<td>❌ 새 ID로 변경됨</td>
<td>✅ 기존 ID 유지됨</td>
</tr>
<tr>
<td>다운타임</td>
<td>있음 (삭제 → 생성)</td>
<td>없음 (대부분)</td>
</tr>
<tr>
<td>예시 속성</td>
<td>인스턴스 타입, 서브넷, VPC 변경 등</td>
<td>태그, 디스크 크기, 설명 등</td>
</tr>
<tr>
<td>트리거 조건</td>
<td>리소스 특성상 변경 불가능한 속성 변경 시</td>
<td>변경 가능한 속성 수정 시</td>
</tr>
<tr>
<td>로그 기호</td>
<td><code>-/+</code></td>
<td><code>~</code></td>
</tr>
</tbody></table>
<br>


<table>
<thead>
<tr>
<th>Replace (<code>-/+</code>)</th>
<th>변경이 안 되는 속성은 리소스를 삭제하고 새로 만듦 (ID 변경)</th>
</tr>
</thead>
<tbody><tr>
<td>Update (<code>~</code>)</td>
<td>리소스는 유지하면서 속성만 바꿈 (ID 유지)</td>
</tr>
</tbody></table>
<hr>
<h2 id="🌱-workspace란">🌱 workspace란?</h2>
<ul>
<li><p>state를 관리하는 논리적인 가상 공간을 의미합니다. 
  =&gt; 하나의 루트 모듈에서 다른 환경을 위한 리소스를 동일한 테라폼 구성으로 프로비저닝하고 관리할 수 있습니다.</p>
</li>
<li><p>새로운 워크스페이스가 생성되면 실행한 루트 모듈 디렉토리에 <code>terraform.tfstate.d</code> 디렉토리가 생성되고 하위에 생성한 워크스페이스 이름을 확인할 수 있습니다.</p>
<img src="https://velog.velcdn.com/images/minw0_o/post/d0c6cd65-3819-4788-b898-d324bd111dae/image.png" width="300" />


</li>
</ul>
<ul>
<li>테라폼 구성에서 terraform.workspace를 사용하여 workspace 이름을 읽으면 workspace 기준으로 문자열을 지정하거나 조건을 부여할 수 있습니다.
  workspace 이름을 조건으로 동일한 코드 구성으로 다른 조건의 리소스를 생성 하는 예시<pre><code class="language-hcl">resource &quot;aws_instance&quot; &quot;web&quot; {
  count = &quot;${terraform.workspace == &quot;default&quot; ? 5 : 1}&quot;
  ami = &quot;~&quot;
  instance_type = &quot;t3.micro&quot;
}</code></pre>
</li>
</ul>
<h2 id="🌱-workspace-관련-명령어-모음">🌱 workspace 관련 명령어 모음</h2>
<pre><code class="language-shell"># 새로운 워크스페이스 생성 =&gt; 자동으로 해당 워크스페이스로 전환됨 
$terraform workspace new &lt;workspsace name&gt;

# 사용 중인 워크스페이크 확인
$terraform workspace show

# 활성화된 워크스페이스 변경
$terraform workspace select &lt;workspsace name&gt;

# 워크스페이스 삭제
$terraform workspace delete &lt;workspsace name&gt;
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠버네티스란?]]></title>
            <link>https://velog.io/@minw0_o/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EB%9E%80</link>
            <guid>https://velog.io/@minw0_o/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EB%9E%80</guid>
            <pubDate>Mon, 03 Mar 2025 04:00:02 GMT</pubDate>
            <description><![CDATA[<hr>
<h2 id="🌱-쿠버네티스란">🌱 쿠버네티스란?</h2>
<p>공식문서에 나와있는 쿠버네티스의 정의는 다음과 같습니다. </p>
<blockquote>
<p>Kubernetes is a portable, extensible, open source platform for managing containerized workloads and services, that facilitates both declarative configuration and automation. It has a large, rapidly growing ecosystem. Kubernetes services, support, and tools are widely available.</p>
</blockquote>
<p>쿠버네티스란 컨테이너화된 워크로드와 서비스를 관리하기 위한 이식 및 확장이 가능하고 선언적 구성과 자동화를 지원하는 오픈소스 플랫폼입니다. </p>
<p>하지만, 위의 정의를 통해서는 쿠버네티스의 의미가 잘 와 닿지는 않습니다. 그렇다면 &quot;왜 쿠버네티스가 필요하게 되었는지, 이전의 플랫폼과는 어떤 차이점이 있는지&quot;를 알아보겠습니다.</p>
<hr>
<h2 id="🌱-배포-방법의-변화">🌱 배포 방법의 변화</h2>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/7819cbcd-8d6e-4dc6-b06b-dcae080d13fa/image.png" alt=""></p>
<p>배포 방법의 변화를 3단계로 구분해 살펴보겠습니다.</p>
<p><strong>1. 전통적인 배포 시대</strong>
초기 조직은 애플리케이션을 물리 서버에서 실행했었다. 한 물리 서버에서 여러 애플리케이션의 리소스 한계를 정의할 방법이 없었기에, 리소스 할당의 문제가 발생했다. 예를 들어 물리 서버 하나에서 여러 애플리케이션을 실행하면, 리소스 전부를 차지하는 애플리케이션 인스턴스가 있을 수 있고, 결과적으로는 다른 애플리케이션의 성능이 저하될 수 있었다. </p>
<p>이에 대한 해결책으로 서로 다른 여러 물리 서버에서 각 애플리케이션을 실행할 수도 있다. 그러나 이는 리소스가 충분히 활용되지 않는다는 점에서 확장 가능하지 않았으며, 조직이 많은 물리 서버를 유지하는 데에 높은 비용이 들었다.</p>
<p><strong>2. 가상화된 배포 시대</strong>
위 전통적인 배포의 문제점에 대한 해결책으로 <strong>가상화</strong>가 도입되었다. 이는 <strong>단일 물리 서버의 CPU에서 여러 가상 시스템 (VM)을 실행할 수 있게 한다.</strong> 가상화를 사용하면 VM간에 애플리케이션을 격리하고 애플리케이션의 정보를 다른 애플리케이션에서 자유롭게 액세스할 수 없으므로, 일정 수준의 보안성을 제공할 수 있다.</p>
<p>가상화를 사용하면 물리 서버에서 리소스를 보다 효율적으로 활용할 수 있으며, 쉽게 애플리케이션을 추가하거나 업데이트할 수 있고 하드웨어 비용을 절감할 수 있어 더 나은 확장성을 제공한다. 가상화를 통해 일련의 물리 리소스를 폐기 가능한(disposable) 가상 머신으로 구성된 클러스터로 만들 수 있다.</p>
<p>각 VM은 가상화된 하드웨어 상에서 <strong>자체 운영체제를 포함한 모든 구성 요소를 실행하는 하나의 완전한 머신</strong>이다.</p>
<p><strong>3. 컨테이너 개발 시대</strong>
컨테이너는 VM과 유사하지만 격리 속성을 완화하여 <strong>애플리케이션 간에 운영체제(OS)를 공유</strong>한다. 그러므로 컨테이너는 가볍다고 여겨진다. VM과 마찬가지로 컨테이너에는 자체 파일 시스템, CPU 점유율, 메모리, 프로세스 공간 등이 있다. 기본 인프라와의 종속성을 끊었기 때문에, 클라우드나 OS 배포본에 모두 이식할 수 있다.</p>
<blockquote>
<p>💡 VM과 Containter의 차이점
VM은 자체 운영체제를 가지고 있지만, 컨테이너는 별도의 운영체제가 필요하지 않습니다. </p>
</blockquote>
<p>컨테이너를 통한 배포 방식을 전통적인 배포 방식과 비교해보자면, 하나의 OS위에 프로그램을 여러개 구동시킨 것과 별 차이가 없어보이지만, 중요한 기술적인 차이점이 있습니다. </p>
<p>각 프로그램들이 “이 컴퓨터에서는 나만 구동되고 있다&quot; 라고 판단할 수 있도록, 실제로 두 프로그램간에 간섭을 일으킬 수 없는 장벽을 치고, 동시에 OS는 각 프로그램들이 사용할 수 있는 CPU, 메모리 등의 자원 또한 독립적으로 사용할 수 있도록 할당하고 관리합니다. </p>
<p>물론 OS관점에서 보자면 둘다 OS 상에서 구동되는 프로그램이긴 하지만요. <strong>이와 같은 컨테이너 동작 방식을 OS 커널을 공유하는 가상화</strong> 라고 하기도 합니다.</p>
<hr>
<h2 id="🌱-쿠버네티스의-필요성">🌱 쿠버네티스의 필요성</h2>
<p>도커의 등장으로 컨테이너 기반 배포 방식이 보편화되었고, 많은 서비스들이 도커를 활용하여 이미지로 관리되기 시작했습니다. <strong>이미지가 점점 많아지면서, 관리해야하는 컨테이너와 서버들 또한 많아지게 되었습니다.</strong></p>
<p>그래서 이러한 <strong>컨테이너들의 관리를 자동화할 도구(컨테이너 오케스트레이션 툴)</strong>의 필요성이 대두되었고, 쿠버네티스 역시 이러한 컨테이너 오케스트레이션 툴 중 하나로, 현재 가장 대중적으로 사용되는 툴입니다. 쿠버네티스는 분산 시스템을 탄력적으로 실행하기 위한 프레임 워크를 제공하며, 애플리케이션의 확장과 장애 조치를 처리하고, 배포 패턴 등을 제공합니다. </p>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<p><a href="https://velog.io/@holicme7/K8s-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80">https://velog.io/@holicme7/K8s-%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Iterable => "순회 가능한" ]]></title>
            <link>https://velog.io/@minw0_o/Iterable-%EC%88%9C%ED%9A%8C-%EA%B0%80%EB%8A%A5%ED%95%9C</link>
            <guid>https://velog.io/@minw0_o/Iterable-%EC%88%9C%ED%9A%8C-%EA%B0%80%EB%8A%A5%ED%95%9C</guid>
            <pubDate>Thu, 18 Apr 2024 14:15:07 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-이터레이션-프로토콜">📌 이터레이션 프로토콜</h2>
<p>ES6에서 도입된 <strong>Iteration Protocol</strong>은 iterable한, 즉 순회 가능한 데이터 컬렉션(자료구조)을 만들기 위해 ECMAScript 사양에 정의하여 미리 약속한 규칙입니다. </p>
<p>즉, 이터레이션 프로토콜을 지키는 자료구조를 만들면, 해당 자료구조는 순회가 가능합니다. </p>
<blockquote>
<p>💡 ES6,  ECMAScript 란?</p>
<p>ES6는 ECMAScript 2015의 줄임말입니다. ECMAScript는 JavaScript의 표준화된 버전을 가리키며, ES6는 ECMAScript의 6번째 버전을 의미합니다. ES6는 JavaScript의 새로운 기능과 문법을 도입하여 개발자들이 코드를 더욱 간결하고 효율적으로 작성할 수 있도록 도와주었습니다. 이전의 JavaScript 버전들보다 더 많은 기능과 개선 사항을 포함하고 있습니다. ES6는 let, const, 화살표 함수, 클래스, 템플릿 리터럴 등의 새로운 기능을 도입하여 JavaScript의 유연성과 표현력을 향상시켰습니다. 그러므로 현대적인 JavaScript 개발에서는 ES6를 기본적으로 사용하는 것이 일반적입니다.</p>
</blockquote>
<p>ES6에서는 순회 가능한 데이터 컬렉션을 <strong>이터레이션 프로토콜</strong>을 준수하는 <strong>이터러블</strong>로 통일하여 <code>for ... of</code>문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용할 수 있도록 일원화했습니다. </p>
<p>Iteration Protocol에는 2가지가 존재합니다. </p>
<ul>
<li><strong>iterable</strong> protocol =&gt; 이를 준수한 객체를 <strong>iterable</strong>라고 부릅니다 .</li>
<li><strong>iterator</strong> protocol =&gt; 이를 준수한 객체를 <strong>iterator</strong>라고 부릅니다.</li>
</ul>
<p>이에 대해서 자세히 알아보겠습니다. </p>
<br>

<h3 id="이터러블">이터러블</h3>
<p><strong>이터러블 프로토콜을 준수</strong>한 객체를 이터러블이라고 합니다. </p>
<p>이터러블 프로토콜을 준수하려면 아래 2가지 조건 중 하나를 만족해야 합니다. </p>
<ul>
<li><p><code>Symbol.iterator</code>를 프로퍼티 키로 사용한 메서드를 직접 구현</p>
</li>
<li><p><code>Symbol.iterator</code>를 프로토타입 체인을 통해 상속받은 객체</p>
<p>ex) 배열은 <code>Array.prototype</code>의 <code>Symbol.iterator</code> 메서드를 상속 받는 이터러블입니다. </p>
<pre><code class="language-js">const array = [1, 2, 3];
console.log(Symbol.iterator in array) // true</code></pre>
</li>
</ul>
<p>이터러블에는 다음과 같은 연산들이 가능합니다. </p>
<ul>
<li><code>for ... of</code>문으로 순회</li>
<li>스프레드 문법과 디스트럭처링 할당의 대상으로 사용</li>
</ul>
<p>위의 두 가지 조건을 모두 만족하지 않는, 즉 이터러블 프로토콜을 준수하지 않는 일반 객체는 <code>for ... of</code>문으로 순회할 수 없고, 스프레드 문법을 사용할 수 없습니다. </p>
<br>

<h3 id="이터레이터">이터레이터</h3>
<p>이터러블의 <code>Symbol.iterator</code> 메서드를 호출하면 이터레이터 프로토콜을 준수한 이터레이터를 반환합니다. 그리고 이 반환된 이터레이터는<code>next</code> 메서드를 갖습니다. </p>
<p>이터레이터의 <code>next</code> 메서드는 이터러블의 각 요소를 순회하기 위한 포인터의 역할을 합니다. 즉 <code>next</code> 메서드를 호출하면 이터러블을 순차적으로 한 단계씩 순회하며 순회 결과를 나타내는 이터레이터 리절트 객체를 반환합니다. </p>
<pre><code class="language-js">// 배열은 이터러블 프로토콜을 준수한 이터러블입니다.
const array = [1,2,3];

// 배열의 Symbol.iterator 메서드는 이터레이터를 반환합니다. 
const iterator = array[Symbol.iterator]();

// Symbol.iterator 메서드가 반환한 이터레이터는 next 메서드를 갖습니다. 
console.log(&#39;next&#39; in iterator); // true

console.log(iterator.next()); // {value: 1, done: false} 
console.log(iterator.next()); // {value: 2, done: false} 
console.log(iterator.next()); // {value: 3, done: false} 
console.log(iterator.next()); // {value: undefined, done: true} </code></pre>
<p>이터레이터의 <code>next</code> 메서드가 반환하는 이터레이터 리젙르 객체는 다음 2개의 프로퍼티를 가집니다. </p>
<ul>
<li><code>value</code> 프로퍼티 : 현재 순회 중인 이터러블의 값을 나타냅니다.  </li>
<li><code>done</code> 프로퍼티 : 이터러블의 순회 완료 여부를 나타냅니다.  </li>
</ul>
<br>

<h3 id="빌트인-이터러블">빌트인 이터러블</h3>
<p>js는 이터레이션 프로토콜을 준수한 객체인 빌트인 이터러블을 제공합니다. 표준 빌트인 객체인  <strong>Array, String, Map, Set, TypedArray, arguments, DOM 컬렉션</strong> 모두 빌트인 이터러블입니다. </p>
<blockquote>
<p>Map과 Set은 엄밀히 독립된 자료형으로, 객체나 배열이 아닙니다. 그럼에도 불구하고 <code>for ... of</code> 문으로 동작하는 이유는 인덱스로 접근하는게 아닌 이터러블 프로토콜을 따르고 있기 떄문입니다. </p>
</blockquote>
<p>즉, <code>Symbol.iterator</code>를 프로토타입 체인을 통해 상속받은 객체들이고 각 객체의<code>Symbol.iterator</code> 메서드를 호출해 이터레이터를 반환받을 수 있습니다. </p>
<hr>
<h2 id="📌-for--of-문">📌 for ... of 문</h2>
<p><code>for ... of</code> 문은 이터러블을 순회하면서 이터러블의 요소를 변수에 할당합니다. </p>
<pre><code class="language-js">for (변수 선언문 of 이터러블) { ... }

for(let num of numArr) { } </code></pre>
<p><code>for ... of</code> 문은 내부적으로 이터레이터의 next 메서드를 호출하여 이터러블을 순회하며 next메서드가 반환한 이터레이터 리절트 객체의 value 프로퍼티 값을 <code>for ... of</code> 문의 변수에 할당합니다. </p>
<p>이터레이터 리절트 객체의 done 프로퍼티 값이 false 이면 이터러블의 순회를 계속하고 true이면 이터러블의 순회를 중단합니다. </p>
<br>

<pre><code class="language-js">for(const item of [1,2,3]){
    console.log(item); // 1 2 3 
}</code></pre>
<p>위의 <code>for ... of</code> 문 코드의 내부 동작을 <code>for</code>문으로 표현하면 아래와 같습니다. </p>
<pre><code class="language-js">// 이터러블
const arr_iterable = [1,2,3];

// 이터러블의 Symbol.iterator 메서드를 호출하여 이터레이터를 생성한다. 
const iterator = arr_iterable[Symbol.iterator](); 

for(;;){
    const res = iterator.next();

    if(res.done) break;
    console.log(res.value);
}</code></pre>
<blockquote>
<p>💡 지금까지의 내용을 정리하면 다음과 같습니다. </p>
<ul>
<li>이터러블 프로토콜을 준수한다는 건 다음 2가지 조건 중 하나를 만족하는 것입니다.<ul>
<li><code>Symbol.iterator</code>를 프로퍼티 키로 사용한 메서드를 직접 구현</li>
<li><code>Symbol.iterator</code>를 프로토타입 체인을 통해 상속받은 객체</li>
</ul>
</li>
<li>이터러블 프로토콜을 준수한 객체를 이터러블이라고 합니다. </li>
<li>이터러블에는 <code>for .. of</code>, 스프레드 문법, 구조분해 할당을 사용할 수 있습니다.</li>
<li>이터러블은 <code>Symbol.iterator</code> 메서드를 통해 이터레이터를 반환할 수 있습니다.</li>
<li>이터레이터란 이터레이터 프로토콜을 준수한 객체를 말합니다. </li>
<li>이터레이터의 <code>next</code> 메서드를 통해 이터러블의 각 요소를 순회할 수 있습니다. </li>
</ul>
</blockquote>
<hr>
<h2 id="📌-이터러블과-유사-배열-객체">📌 이터러블과 유사 배열 객체</h2>
<p>유사 배열 객체는 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있고 length 프로퍼티를 갖는 객체를 말합니다. 유사 배열 객체는 length 프로퍼티를 갖기 때문에 for 문으로 순회할 수 있고, 인덱스를 나타내는 숫자 형식의 문자열을 프로퍼티 키로 가지므로 마치 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있습니다. </p>
<pre><code class="language-js">const arrayLike = {
    0: 1,
    1: 2,
    2:3,
    length: 3
};

for(let i=0;i&lt;arrayLike.length;i++){
    console.log(arrayLike[i]);
}</code></pre>
<p>하지만 위와 같은 배열은 이터러블이 아닌 일반 객체입니다. 따라서 유사 배열 객체에는 <code>Symbol.iterator</code> 메서드가 없기 때문에 <code>for ... of</code> 문으로 순회할 수 없습니다.</p>
<p>하지만 ES6에서 도입된 <code>Array.from</code> 메서드를 사용하면 위와 같은 유사 배열을 배열로 변환할 수 있습니다. </p>
<p><code>Array.from</code>은 유사 배열 객체 또는 이터러블을 인수로 전달받아 배열로 변환합니다. </p>
<pre><code class="language-js">const arrayLike = {
    0: 1,
    1: 2,
    2:3,
    length: 3
};

const arr = Array.from(arrayLike);

console.log(arr); // [1, 2, 3]</code></pre>
<hr>
<h2 id="📌-사용자-정의-이터러블">📌 사용자 정의 이터러블</h2>
<p>위의 내용을 정리하면, </p>
<ul>
<li>이터러블이란 이터레이터를 리턴하는 <code>[Symbol.iterator]()</code> 메서드를 가진 객체입니다.</li>
<li>이터레이터란 <code>{value : 값, done: true/false}</code> 형태의 이터레이터 객체를 리턴하는 <code>next()</code> 메서드를 가진 객체입니다. </li>
</ul>
<p>위 두 가지 내용을 바탕으로, 직접 이터러블 객체를 만들어보겠습니다. </p>
<pre><code class="language-js">let range = { // 1. 객체 생성
    from: 1,
    to: 5
}

range[Symbol.iterator] = function() { // 2. 객체에 새로운 key: value를 추가
    return { // 3. next() 메서드를 가지는 객체인 이터레이터를 리턴한다. 
        current: this.from,
        last: this.to,

        next(){ // 4. {value : 값, done: true/false} 형태의 이터레이터 리절트 객체를 리턴하는 메서드입니다. 
            if(this.current &lt;= this.last){
                return {done: false, value: this.current++};
            } else{
                return {done: true};
            }
        }   
    }
}

for(let num of range){
    console.log(num); // 1 2 3 4 5
}</code></pre>
<ul>
<li><p><code>range</code>는 이터러블 객체입니다. <code>Symbol.iterator</code> 메서드를 가지고 있기 때문입니다. </p>
</li>
<li><p><code>range</code>의 <code>Symbol.iterator</code> 메서드에서 리턴한 객체가 이터레이터입니다. <code>next()</code> 메서드를 가지는 객체이기 때문입니다. </p>
</li>
<li><p><code>range</code>는 이터러블 객체이므로 <code>for ... of</code> 문으로 순회할 수 있습니다. </p>
</li>
</ul>
<p>위 코드는 이터러블 객체를 정의하고, 그 이터러블 객체의 이터레이터를 외부에 정의해주었는데, 이터러블 객체이자 이터레이터 객체 역할을 모두 수행하는 <code>range</code> 객체를 선언해주는 방법도 있습니다. </p>
<pre><code class="language-js">let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() { // 생성자
    this.current = this.from;
    this.last = this.to;
    return this; // 자기 자신을 반환. 자기자신엔 next()메소드가 정의되어있으니, next()메소드에서의 this는 생성자로 생성된 객체를 가리키게 된다.
  },

  next() { // 아예 객체안에 next()메서드를 정의.
    if (this.current &lt;= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let num of range) {
  alert(num); // 1, 2, 3, 4, 5
}</code></pre>
<hr>
<h2 id="📌-이터레이션-프로토콜의-필요성">📌 이터레이션 프로토콜의 필요성</h2>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/b45c7741-9b3c-4b08-976f-f97e1317d981/image.png" alt=""></p>
<p>이터레이션 프로토콜은 다양한 데이터 공급자가 하나의 순회 방식을 갖도록 규정하여 데이터 소비자가 효율적으로 다양한 데이터 공급자를 사용할 수 있도록 데이터 소비자와 데이터 공급자를 연결하는 인터페이스의 역할을 합니다. </p>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<ul>
<li>모던 자바스크립트 Deep Dive - Ch 34 이터러블</li>
<li><a href="https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-%EC%9D%B4%ED%84%B0%EB%9F%AC%EB%B8%94-%EC%9D%B4%ED%84%B0%EB%A0%88%EC%9D%B4%ED%84%B0-%F0%9F%92%AF%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4">[JS] 📚 이터러블 &amp; 이터레이터</a></li>
</ul>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[JavaScript는 인터프리터 언어이지만 컴파일도 합니다!]]></title>
            <link>https://velog.io/@minw0_o/JavaScript%EB%8A%94-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0-%EC%96%B8%EC%96%B4%EC%9D%B4%EC%A7%80%EB%A7%8C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%8F%84-%ED%95%A9%EB%8B%88%EB%8B%A4</link>
            <guid>https://velog.io/@minw0_o/JavaScript%EB%8A%94-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0-%EC%96%B8%EC%96%B4%EC%9D%B4%EC%A7%80%EB%A7%8C-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%8F%84-%ED%95%A9%EB%8B%88%EB%8B%A4</guid>
            <pubDate>Thu, 11 Apr 2024 14:49:42 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>일반적으로 JavaScript는 고전적인 컴파일 언어와는 다르게 코드의 실행과 동시에 한 줄씩 해석하는 인터프리터 언어로 분류됩니다. 하지만 그렇다고해서 컴파일 과정을 거치지 않는 것은 아닙니다. 이에 대해 더 자세히 알아보겠습니다. </p>
</blockquote>
<hr>
<h2 id="📌-컴파일러와-인터프린터의-차이">📌 컴파일러와 인터프린터의 차이</h2>
<p>개발자가 작성한 소스코드를 기계어로 번역하는 방식은 크게 2가지가 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/61d522c5-9485-41de-8c5e-574d67babc75/image.png" alt=""></p>
<h3 id="컴파일러-방식">컴파일러 방식</h3>
<p>컴파일러는 프로그램 전체를 스캔하여 이를 모두 기계어로 번역하고 이를 디스크에 저장합니다. 전체를 스캔하기 때문에 대부분 컴파일러는 초기 스캔 시간이 오래 걸립니다. 하지만 전체 실행 시간만 따지고 보면 인터프리터 보다 빠릅니다. 왜냐하면 컴파일러는 초기 스캔을 마치면 실행파일을 만들어 놓고 다음에 실행할때 이전에 만들어 놓았던 실행파일을 실행하기 때문입니다. </p>
<p>하지만 단점도 존재합니다. 컴파일러는 고급언어로 작성된 소스를 기계어로 번역하고 이 과정에서 오브젝트 코드(Object Code)라는 파일을 만드는데 이 오브젝트 코드를 묶어서 하나의 실행 파일로 다시 만드는 링킹(Linking) 이라는 작업을 해야합니다. 따라서 컴파일러는 통상적으로 인터프리터 보다 많은 메모리를 사용해야 합니다. </p>
<p>또한 컴파일러는 오류 메시지를 생성할때 전체 코드를 검사한 후에 오류 메시지를 생성합니다. 그래서 실행 전에 오류를 발견 할 수 있습니다. 대표적인 언어로 C,C++,JAVA 등이 있습니다.</p>
<br>

<h3 id="인터프리터-방식">인터프리터 방식</h3>
<p>컴파일러와는 반대로 인터프리터는 프로그램 실행시 한 번에 한 문장씩 번역합니다. 그렇기 때문에 한번에 전체를 스캔하고 실행파일을 만들어서 실행하는 컴파일러보다 실행시간이 더 걸립니다. 한 문장 읽고 번역하여 실행시키는 과정을 반복하는게 만들어 놓은 실행파일을 한번 실행시키는 것보다 빠르긴 힘들 것입니다. </p>
<p>하지만 인터프리터는 메모리 효율이 좋습니다. 컴파일러처럼 목적코드를 만들지도 않고, 링킹 과정도 거치지 않기 때문입니다. 이 때문에 인터프리터는 메모리 사용에 컴파일러 보다 더 효율적입니다. </p>
<p>인터프리터는 오류 메시지 생성과정이 컴파일러와는 다릅니다. 인터프리터는 한 번에 한 문장씩 번역하기 때문에 프로그램을 실행시키고 한 문장씩 번역될때 오류를 만나게 되면 바로 프로그램을 중지합니다 그래서 프로그램을 실행해봐야지만 오류 발견이 가능합니다. 대표적인 언어로 Python, Ruby, Javascript 등이 있습니다. </p>
<hr>
<h2 id="📌-인터프리터-언어였던-javascript">📌 인터프리터 언어였던 JavaScript</h2>
<p>초기에 JavaScript가 개발될 때, 웹 브라우저 상에서 동적인 기능을 제공하기 위한 목적으로 설계되었습니다. 이는 사용자의 브라우저에서 바로 실행될 수 있어야 했기 때문에 인터프리터 방식이 적합했습니다.</p>
<p>위에서 설명했듯이 인터프린터란 한 번에 한 줄씩 번역하여 실행하는 방식입니다. 하지만 JS 코드는 결국 개발자가 작성한 코드이므로, JS 엔진의 인터프리터는 우리가 작성한 <code>console.log(&quot;Hello World!&quot;);</code> 같은 코드를 그 자체로 이해할 수는 없습니다. 자바스크립트가 인터프리터에 전해지기 위해서는 일련의 과정을 거쳐야 합니다. </p>
<h3 id="소스코드--바이트코드--기계어">소스코드 =&gt; 바이트코드 =&gt; 기계어</h3>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/46b3d334-933f-4ec9-b1e6-60bb18fc9add/image.png" alt=""></p>
<p>위와 같이 자바스크립트는 기계에게 전달되기전에 바이트 코드로 변환되어야 합니다. 이 바이트 코드는 가상머신에 의해 기계어로 변환되고 이를 기계가 해석하여 실행하는 것입니다.  </p>
<p>그렇다면 JS엔진이 소스 코드를 어떻게 바이트코드로 변환하는지 알아보겠습니다. </p>
<br>

<h3 id="소스코드--token--ast--바이트-코드">소스코드 =&gt; Token =&gt; AST =&gt; 바이트 코드</h3>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/0542cb4b-b11d-4bcd-8320-bec645240528/image.png" alt=""></p>
<p>위 과정을 간단히 말하면 JS 엔진이 소스 코드를 토크나이징, 파싱 과정을 거쳐 AST로 변환하고, 이 AST를 인터프리터가 바이트 코드로 변환합니다. </p>
<blockquote>
<p>💡 정리하면, </p>
<ul>
<li>초기 JS는 인터프리터 방식으로 번역되었습니다.</li>
<li>JS엔진이 소스 코드를 AST로 변환하고, AST를 인터프리터가 바이트 코드로 변환합니다. </li>
<li>가상 머신이 바이트 코드를 기계어로 번역해 기계가 이를 실행합니다.  </li>
</ul>
</blockquote>
<hr>
<h2 id="📌-컴파일-과정이-추가된-javascript">📌 컴파일 과정이 추가된 JavaScript</h2>
<p>초기 자바스크립트는 인터프리터 언어였지만, 점차 웹에서도 다양한 요구사항들이 추가되면서 더 많은 기능들을 갖추어야 했고, 이는 자바스크립트가 점차 성능상 무거워지는 계기가 되었습니다. 이를 통해 자바스크립트 언어에서도 실행 전에 내부적으로 <strong>컴파일하는 과정이 추가</strong>되었습니다. </p>
<h3 id="js엔진의-컴파일-과정">JS엔진의 컴파일 과정</h3>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/523c20d7-4808-48dd-bf55-16d241b28c3a/image.png" alt=""></p>
<p>위에서 살펴본 것 처럼, JS엔진과 인터프리터가 소스 코드를 바이트 코드로 변환한 후에 바이트 코드를 기계어로 변역해 실행합니다. 생성된 바이트 코드를 실행하는 방법에는 2가지 방법이 있습니다. </p>
<ol>
<li><p>인터프리터 모드 </p>
<p>초기 JS가 사용했던 방식으로, 인터프리터가 바이트 코드를 하나씩 읽어가며 실행합니다.</p>
</li>
<li><p>JIT 모드 </p>
<p>바이트 코드를 native code(기계어)로 컴파일하여 실행하는 방법입니다. </p>
<br>

</li>
</ol>
<h3 id="jit란">JIT란?</h3>
<ul>
<li>동적 컴파일 과정이라고 불립니다.</li>
<li>인터프리터 + 정적 컴파일 방식 (둘을 섞어놓은 느낌)</li>
<li>프로그램 실행 시점에서 인터프리터 방식으로 기계어 코드를 생성하면서 <strong>그 코드를 캐싱</strong>하여, 같은 함수가 여러  번 불릴 때마다 매번 기계어 코드를 생성하는 것을 방지합니다. </li>
<li>좀 더 구체적으로 말하면, <strong>실행시점에 바이트 코드를 기계어로 번역</strong>하는 역할을 합니다. 바이트코드에서 번역된 기계어는 <strong>캐시에 저장</strong>되기 때문에 재사용시 다시 번역할 필요가 없습니다. 따라서 코드가 반복된다면 다시 번역하는 과정 없이 재사용할 수 있으므로 시간이 단축됩니다.</li>
</ul>
<blockquote>
<p>정적 컴파일 방식처럼 미리 저장하는 것이 아니라 <strong>실행 중에 코드를 번역하고 저장</strong>합니다.</p>
</blockquote>
<br>

<h3 id="인터프리터-vs-jit">인터프리터 VS JIT</h3>
<blockquote>
<p>💡 그렇다면 JavaScript에서는 인터프리터 모드와  JIT 모드 중에서 어떤 것이 더 효율적일까요?</p>
</blockquote>
<p>일반적으로는 JIT 모드가 인터프리터 모드보다 더 효율적입니다. 직관적으로 생각해도 인터프리터 모드로 코드를 한 줄씩 번역해서 실행하는 것보다는 기계어로 <strong>미리 번역해둔 것을 수행하는게 더 빠를 것</strong>입니다.</p>
<p>정적 컴파일러라면 기계어를 생성하는 도중에 많은 최적화 알고리즘을 사용할 수 있어서 code quality가 높지만,  JIT는 컴파일 과정 자체가 실행 중에 발생하기 때문에 이 자체가 오버헤드가 되고, 따라서 컴파일에 많은 시간을 쓸 수 없습니다.</p>
<p>따라서 코드 전체를 읽어서 최적화하는 방식은 당연히 사용할 수 없고, 보통 최소한의 최적화만 적용하여 기계어를 생성합니다. 이렇게 해도 인터프리터 방식보다는 기계어의 수행 성능이 훨씬 낫습니다. 따라서 JIT에 오버헤드가 포함되더라도 인터프리터 모드보다 빠르게 수행되므로 Java VM에서는 JIT를 많이 사용합니다 .</p>
<p>따라서 일반적으로는 인터프리터 모드로 코드를 한줄 한줄 바로 번역해서 실행하는것보다는 JIT모드로 수행하는게 더 빠릅니다. </p>
<p>하지만 JavaScript에서는 그렇지 않습니다. 바로 다음 2가지 이유 때문입니다. </p>
<h3 id="1-js는-동적-타입-언어이다">1. JS는 동적 타입 언어이다.</h3>
<p>JS는 동적 타입 언어입니다. 즉, 변수의 타입이 실행 중에 변할 수 있고, 프로토타입 기반 방식을 사용하는 등 매우 동적인 특성을 가지기 때문에 JavaScript JIT 컴파일러는 모든 예외적인 케이스를 고려하여 코드를 생성해야합니다.</p>
<p>예를 들어, 2개의 변수를 서로 더하는 코드를 생각해보겠습니다. 이 때에도 모든 예외 케이스를 고려하면 상당히 많은 양의 native code가 생성됩니다. </p>
<p>=&gt; 변수가 모두 int형일 경우 / 하나라도 int형이 아닐 경우 / 더하고 나니 int 범위를 벗어나는 등 많은 예외 케이스가 존재합니다 .</p>
<p>이렇게 예외 케이스가 발생하게 되면 <strong>slow case</strong>로 점프하게 됩니다.</p>
<blockquote>
<p>💡 slow case란?</p>
<p>native code로 생성하기 어려운(native code로 표현하면 양이 많아지는) 동작들을 native code로 뽑아내는 대신 미리 엔진 내부에 C로 구현된 helper function을 호출하여 동작을 수행하는 경우를 의미합니다. </p>
</blockquote>
<p>그런데 이런 helper function들은 인터프리터 모드로 수행할 때와 동일한 코드를 사용하게 됩니다. JIT 컴파일러로 native code를 수행한다 해도 많은 부분이 인터프리터를 사용할때와 차이가 없게 되는 것입니다. 오히려 컴파일 오버헤드(native code를 생성해야하는)가 더해지므로 JavaScript에서 JIT는 Java에서보다 훨씬 비효율적입니다. </p>
<h3 id="2-js는-hotspot이-상대적으로-적다">2. JS는 hotspot이 상대적으로 적다.</h3>
<p>JavaScript는 주로 웹페이지의 layout을 조작하거나 사용자 입력에 반응하는 방식의 코드가 많습니다. 따라서, JS는 <strong>자주 반복되어서 수행되는 구간</strong>인 hotspot이 상대적으로 매우 적습니다.</p>
<p>이러한 경우 native code를 수행하는 시간에 비해, <strong>native code를 만드는 시간, 즉 컴파일 오버헤드가 상대적으로 커지게 되는 문제</strong>가 있다. 코드가 골고루 실행되기 때문에 컴파일해야하는 코드의 양이 많아지는 것입니다. </p>
<p>결과적으로 &quot;컴파일 오버헤드 + native code가 인터프리터보다 빠르다&quot;라는 JIT의 사용이유가 무의미해지는 상황입니다. (실제 JavaScript JIT가 성능 향상에 기여하는 바가 거의 없다는 연구도 진행된 바 있다고 합니다.)</p>
<blockquote>
<p>💡 정리하면, </p>
<p>JIT는 정적컴파일에 비해 최적화를 많이 할 수 없어서 정적컴파일보다 느립니다. 하지만 그래도 인터프리터보다 빠르기 때문에 Java에서는 JIT를 사용합니다. </p>
<p>하지만 JavaScript는 <strong>1. 동적언어 + 2. hotspot이 적다</strong>는 특성 때문에 JIT가 인터프리터보다 빠르다고 할 수 없습니다.</p>
<p>따라서, <strong>JavaScript 코드는 JIT 보다 인터프리터로 수행하는 것이 낫습니다.</strong></p>
</blockquote>
<p>하지만 이번 문단의 상단에서도 말했듯, 최근에는 JavaScript가 단순히 웹에서 이벤트 처리 용도로만 사용되는 것이 아니라 그 사용 용도가 다양해지면서 점차 연산이 많아지는 프로그램에도 충분히 사용되고 있기에, JIT 방식을 완전히 외면할 수는 없다고 합니다. </p>
<p>따라서 최근에는, 고전적인 방식의 JavaScript코드(hotspot이 많이 없는)와 연산 중심의 코드들의 수행 성능을 모두 만족시키는 방식인 <strong>Adaptive JIT</strong> 방식을 사용합니다. </p>
<hr>
<h2 id="📌-adaptive-jit">📌 Adaptive JIT</h2>
<h4 id="adaptive-jit-compilation">Adaptive JIT Compilation</h4>
<ul>
<li><p>실행 초기에는 인터프리터 방식으로 실행하고 <strong>자주 호출되는 메소드나 반복되는 코드를 검출하여 이러한 코드만 컴파</strong>일하는 방식</p>
</li>
<li><p>JIT 컴파일러의 단점을 보완하기 위한 방식으로, 코드가 사용되었을때 즉시 컴파일하는 것이 아니라 <strong>여러번 호출 된 후 지연시켜 컴파일을 수행하는데 이를 Lazy Compilation이라 합니다</strong>.</p>
</li>
<li><p>실행시간의 대부분을 소비하는 코드만 컴파일하여 효율적으로 실행속도를 향상시킬 수 있습니다.</p>
</li>
</ul>
<blockquote>
<p>JIT는 코드가 실행되면 이를 저장하지만 Adaptive Compiliation은 자주 사용되는 코드만 저장하는 것 </p>
</blockquote>
<p>기본적으로 모든 코드는 처음에 인터프리터로 해석합니다. 그러다가 <strong>자주 반복되는 부분</strong>(hotspot)이 발견되면, <strong>그 부분에 대해서만 JIT를 적용</strong>하여 native code로 변환합니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/00f62221-c0ed-4c3c-b45a-97a013585c40/image.png" alt=""></p>
<p>따라서, 인터프리터가 생성한 바이트 코드에 최적화된 컴파일을 진행하는 방식입니다. </p>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p><a href="https://meetup.nhncloud.com/posts/77">자바스크립트 엔진의 최적화 기법 (1) - JITC, Adaptive Compilation</a></p>
<p><a href="https://velog.io/@seungchan__y/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-Compiler-Interpreter-%EC%96%B8%EC%96%B4%EB%8B%A4">자바스크립트는 Compiler / Interpreter 언어다?</a></p>
<p><a href="https://velog.io/@jhur98/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%ACcompiler%EC%99%80-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0interpreter%EC%9D%98-%EC%B0%A8%EC%9D%B4">컴파일러(compiler)와 인터프리터(interpreter)의 차이</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 렌더링 !== 브라우저 렌더링]]></title>
            <link>https://velog.io/@minw0_o/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81</link>
            <guid>https://velog.io/@minw0_o/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80-%EB%A0%8C%EB%8D%94%EB%A7%81</guid>
            <pubDate>Tue, 19 Mar 2024 09:40:06 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>우리는 리액트를 이용해 웹을 개발하며 <strong>렌더링</strong>, <strong>리렌더링</strong>이라는 표현을 굉장히 자주 사용합니다. 하지만 리액트 <strong>컴포넌트의 렌더링</strong>과 <strong>브라우저의 렌더링</strong>은 분명히 서로 다릅니다. 이 글은 그 차이에 대해서 설명합니다.</p>
</blockquote>
<hr>
<h2 id="📌-브라우저에서의-렌더링">📌 브라우저에서의 렌더링</h2>
<blockquote>
<p>브라우저에서의 렌더링 과정을 설명해주세요.</p>
</blockquote>
<p>위 질문은 아마 프론트엔드에서 기본적인 면접 질문 중 하나일 것입니다. 아마 많은 분들이 이미 알고 계시듯이, 브라우저에서의 렌더링 과정은 다음과 같습니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/ca6335f5-3ca9-46af-9b6f-72a8a2c4d31d/image.png" alt=""></p>
<p>브라우저 렌더링 과정은 <strong>CRP(Critical Redering Path)</strong>라는 일련의 과정을 통해 진행됩니다. 그 과정을 요약하면 다음과 같습니다. </p>
<ol>
<li><p>HTML 파싱을 통한 DOM 생성</p>
</li>
<li><p>CSS 파싱을 통한 CSSOM 생성</p>
</li>
<li><p>DOM 트리와 CSSOM 트리를 결합해 렌더 트리 생성</p>
</li>
<li><p>layout 과정 실행</p>
</li>
<li><p>paint 및 composite 과정 실행</p>
</li>
</ol>
<hr>
<h2 id="📌-리액트에서의-렌더링">📌 리액트에서의 렌더링</h2>
<p>위에서 살펴본 브라우저의 렌더링과 리액트에서의 렌더링은 그 의미와 사용되는 상황이 사뭇 다릅니다. </p>
<p>일반적으로 우리가 <strong>&quot;렌더링&quot;</strong> 이라는 단어를 생각할 때, <strong>무엇인가를 화면에 그리는 작업</strong>을 떠올릴 수 있지만, React에서의 렌더링은 오히려 화면에 무엇인가를 그리기 전 <strong>어떤 과정을 시작</strong>하는 듯한 느낌에 가깝습니다. </p>
<blockquote>
<p>💡 React에서의 렌더링이란 
=&gt; 컴포넌트가 state와 props를 기반으로 어떻게 UI를 구성하고 이를 바탕으로 어떤 dom 결과를 브라우저에게 제공할 것인지를 계산하는 일련의 과정을 말합니다. </p>
</blockquote>
<p>React에서의 렌더링은 DOM 업데이트를 의미하지 않습니다. 즉, React에서의 렌더링은 실질적 화면 업데이트를 말하는 것이 아닙니다. </p>
<h3 id="리액트-렌더링-과정">리액트 렌더링 과정</h3>
<p>React가 UI를 어떻게 구성할 지 컴포넌트에 요청하는 과정, 즉 React에서의 렌더링 과정은 총 3단계로 나눌 수 있습니다.  </p>
<ol>
<li>Trigger - 렌더링을 트리거</li>
<li>Rendering - 컴포넌트를 렌더링</li>
<li>Commit - DOM에 커밋</li>
</ol>
<br>

<h3 id="1-trigger">1. Trigger</h3>
<p>React가 데이터가 변경되었는지를 확인하는 과정입니다. 컴포넌트의 state가 변경되었다면 렌더링이 Trigger됩니다. 이는 <code>Object.is()</code> 메서드를 통해 state 값이 불변성을 지키고 있는지를 확인하는 과정입니다.</p>
<p>렌더링을 Trigger하는 경우는 다음과 같습니다. </p>
<ol>
<li><p>state 변경</p>
<p>컴포넌트의 state가 변경될 때마다 해당 컴포넌트는 리렌더링됩니다.  <code>setState</code> 함수를 호출하여 state를 변경하면, React는 새로운 state를 이용해 컴포넌트를 리렌더링합니다.</p>
</li>
<li><p>props 변경</p>
<p>컴포넌트가 받는 props가 변경되면 해당 컴포넌트는 리렌더링됩니다. 이것은 부모 컴포넌트로부터 props를 받는 모든 자식 컴포넌트에 적용됩니다.</p>
</li>
<li><p>부모 컴포넌트 리렌더링</p>
<p>부모 컴포넌트가 리렌더링되면 그의 모든 자식 컴포넌트들도 리렌더링 됩니다. 부모 컴포넌트의 state나 props가 변경되면 부모 컴포넌트는 리렌더링되며, 이에 따라 모든 자식 컴포넌트들도 리렌더링 됩니다.</p>
</li>
<li><p>Context 변경</p>
<p>React Context API를 사용하면 컴포넌트 트리 내에서 전역적으로 데이터를 공유할 수 있습니다. Context에 변경이 생기면 그를 구독하는 모든 컴포넌트는 리렌더링됩니다.</p>
</li>
<li><p>Force Update</p>
<p> <code>forceUpdate</code> 메서드를 사용하면 컴포넌트는 강제로 리렌더링 될 수 있습니다. 하지만 이 방법은 가능한 한 피해야 합니다. 대부분의 경우 <code>state</code>나 <code>props</code>의 변경으로 리렌더링을 처리할 수 있기 때문입니다.</p>
</li>
</ol>
<br>

<h3 id="2-rendering">2. Rendering</h3>
<p>크게 Reconciliation과 Batching 과정으로 나뉩니다.</p>
<h4 id="reconciliation재조정">Reconciliation(재조정)</h4>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/d6e13dab-14ca-4c49-86a6-cbb81f4aca87/image.png" alt=""></p>
<p>렌더링이 Trigger되면, 화면에 표시될 새로운 가상 DOM을 만듭니다. 그리고 <strong>이전 가상 DOM</strong>과 <strong>새로운 가상 DOM</strong>을 비교해 달라진 부분을 찾아내 어떤 부분을 실제 DOM에 업데이트할 지 결정합니다.</p>
<p>이전 가상 DOM과 새로운 가상 DOM을 비교할 때, Diffing 알고리즘이 사용됩니다. Diffing 알고리즘은 아래와 같은 작업을 합니다. </p>
<ul>
<li>React Element의 타입(JSX 태그 종류) 비교</li>
<li>타입이 동일할 경우 속성(attribute) 비교</li>
<li>key 값 비교</li>
<li>재귀적으로 자식 Element 비교</li>
</ul>
<blockquote>
<p>💡 Diffing 알고리즘을 사용해 이전 가상 DOM과 새로운 가상 DOM을 비교하는 이유는 ?</p>
<p>=&gt; 굳이 리렌더링 되지 않아도 되는 부분은 제외시키고 변경되어야 하는 부분만 변경하기 위해서입니다. </p>
</blockquote>
<br>

<h4 id="batching">Batching</h4>
<p>Reconciliation에서 상태 업데이트로 인해 실제 DOM에 업데이트할 작업들을 결정한 후 이를 다음 단계인 Commit 단계로 넘길 때, 한 번에 묶어서 넘깁니다. </p>
<p>즉, React는 렌더링을 최소한으로 발생시키기 위해 작업을 한 번에 묶어서 <strong>Batching</strong>(일괄 처리) 합니다.  </p>
<blockquote>
<p>💡 Batching을 통해 얻을 수 있는 이점은?</p>
<p>Commit 단계 이후, 브라우저의 렌더링 과정이 이루어집니다. 브라우저의 렌더링 과정 중 layout, painting 과정은 요소가 변경되거나 레이아웃이 변경되었을 때 다시 실행되는 과정인데 이는 비용이 비싼 작업입니다.</p>
<p>layout이 다시 발생하는 것을 reflow라고 부르는데 이는 paint가 다시 일어나는 과정인 repaint보다 비싼 작업입니다. 따라서 만약 React에서 <code>setState</code>를 호출할 때마다 리렌더링이 일어나게 되면, repaint와 reflow가 계속해서 발생되고 이는 비효율적일 것입니다.</p>
<p>따라서, 연속적인 상태의 변화가 있을 때 Batching을 통해 리렌더링이 한 번만 일어나도록 하는 것입니다.</p>
</blockquote>
<br>

<h3 id="3-commit">3. Commit</h3>
<p>Rendering에서 재조정된 가상 DOM을 실제 DOM에 적용하고 라이프사이클을 실행하는 단계입니다.  </p>
<p>여기서도 <strong>DOM에 마운트 된다는 뜻이지 paint 된다는 뜻이 아닙니다</strong>. 이 단계는 항상 일관적 화면 업데이트를 위해 동기적으로 실행됩니다. 동기적으로 실행된다는 것은 콜 스택을 비우지 않고, DOM 조작을 Batching 처리 한다는 뜻입니다.</p>
<p>Commit 단계가 끝나면 그제서야 브라우저는 실제 DOM을 업데이트하며 화면을 그려줍니다. 즉, 이 과정이 바로 <strong>브라우저에서의 렌더링 과정</strong>입니다. </p>
<blockquote>
<p>💡 해석에 따라 Commit 단계 이후에 브라우저 렌더링 과정이 일어난다고 보기도 하고, Commit 단계 내에 브라우저 렌더링 과정을 포함해서 보는 관점도 있는 것 같습니다. </p>
</blockquote>
<blockquote>
<p>💡 리액트의 렌더링이 일어난다고 해서 무조건 브라우저 렌더링(DOM 엡데이트)가 일어나는 것은 아닙니다. </p>
</blockquote>
<ul>
<li>렌더링을 수행했으나 Commit 단계까지 갈 필요가 없다면, 즉 변경 사항을 계산했는데 아무런 변경 사항이 감지되지 않는다면 이 Commit 단계는 생략될 수 있습니다. </li>
<li>즉 리액트의 렌더링은 꼭 가시적인 변경이 일어나지 않아도 발생할 수 있습니다. 렌더링 과정 중 Render 단계에서 변경 사항을 감지할 수 없다면 Commit 단계가 생략되어 브라우저의 DOM 업데이트가 일어나지 않을 수 있습니다.</li>
</ul>
<br>

<p>즉, 이를 정리하면 다음과 같습니다. </p>
<blockquote>
<ul>
<li>리액트의 렌더링 과정에는 Trigger, Rendering, Commit 단계로 이루어져 있습니다.</li>
<li>state가 변경되어 DOM 업데이트가 필요한 작업만 Batching 처리되어 Commit 단계로 전달됩니다. </li>
<li><strong>Commit 후에 브라우저 렌더링이 일어납니다.</strong> </li>
</ul>
</blockquote>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p><a href="https://pozafly.github.io/react/declarative-meaning-of-react-rendering-process/">React 렌더링과정으로 알아보는 선언적이라는 의미</a></p>
<p><a href="https://velopert.com/3236">[번역] 리액트에 대해서 그 누구도 제대로 설명하기 어려운 것 – 왜 Virtual DOM 인가?</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 hook 총 정리]]></title>
            <link>https://velog.io/@minw0_o/%EB%A6%AC%EC%95%A1%ED%8A%B8-hook-%EC%B4%9D-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@minw0_o/%EB%A6%AC%EC%95%A1%ED%8A%B8-hook-%EC%B4%9D-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 07 Mar 2024 09:41:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>평소에 자주 사용해오던 리액트 Hook을 전체적으로 정리해본 적은 없어서 이번 기회에 한 번 전체적으로 정리해보려고 합니다🫠</p>
</blockquote>
<hr>
<h2 id="📌-리액트-훅이란-">📌 리액트 훅이란 ?</h2>
<p>React에서는 16.8 버전부터 <strong>Hook</strong>이란 개념이 새로 추가되었습니다. Hook은 함수형 컴포넌트에서 React state와 생명주기 기능을 연동할 수 있게 해주는 함수입니다. </p>
<p>리액트에서 제공하는 내장 훅(useState, useEffect, ... ) 과 사용자가 직접 정의할 수 있는 Custom Hooks가 있습니다.  </p>
<br>

<h3 id="어떤-문제를-해결할-수-있나요">어떤 문제를 해결할 수 있나요?</h3>
<p>함수형 컴포넌트에 Hook이 나오기 전까지는, 일반적으로 클래스형 컴포넌트를 많이 사용했습니다. 하지만 클래스형 컴포넌트에서 상태(state)를 사용하고, 생명주기 메서드를 사용하는 방식은  많은 문제들과 불편함을 가지고 있었습니다. </p>
<ol>
<li><p>컴포넌트 간의 상태 로직을 재사용하기 어렵다.</p>
</li>
<li><p>복잡한 컴포넌트들의 이해가 어렵다. </p>
<p>=&gt; render props나 HOC(Higher Order Component)와 같은 패턴들 </p>
</li>
<li><p>Class 문법 자체(ex) Class 함수 내에서의 this)가 어렵다. </p>
</li>
</ol>
<br>

<p>이러한 문제들을 해결할 수 있는 함수형 컴포넌트에서의 Hook이 등장했습니다! </p>
<ol>
<li>컴포넌트 간의 계층을 바꾸지 않고 상태 로직을 재사용할 수 있습니다.</li>
<li>하나의 컴포넌트를 생명주기가 아닌 기능을 기반으로 하여 작은 함수 단위로 나눌 수 있습니다. </li>
<li>Class 문법 없이도 React 기능을 사용할 수 있게끔 해준다. </li>
</ol>
<br>

<h3 id="훅-규칙">훅 규칙</h3>
<ol>
<li><p>같은 훅을 여러 번 호출할 수 있습니다. </p>
<pre><code class="language-js">function Form() {
  // useState 여러번 호출 가능
  const [name, setName] = useState(&#39;Mary&#39;);
  const [surname, setSurname] = useState(&#39;Poppins&#39;);

  // 이하 생략</code></pre>
</li>
<li><p>컴포넌트 <strong>최상위</strong>(at the top level)에서만 호출할 수 있습니다. 반복문, 조건문, 중첩된 함수 내에서 훅을 사용할 수 없습니다. </p>
<p>=&gt; 컴포넌트 최상위에서 훅을 호출하면, 컴포넌트가 렌더링 될 때마다 항상 동일한 순서로 Hook이 호출되는 것이 보장됩니다. </p>
<p>이는 React가 useState와 useEffect 등의 훅이 여러 번 호출되는 중에도 훅의 상태를 올바르게 유지할 수 있도록 해줍니다. </p>
<blockquote>
<p>훅의 호출 순서가 같아야 하는 이유는?</p>
<p>=&gt; React가 특정 state가 어떤 <code>useState</code> 호출에 해당하는지 알 수 있는 이유는 <strong>React가 Hook이 호출되는 순서에 의존</strong>하기 때문입니다. 모든 렌더링에서 Hook의 호출 순서는 같기 때문에 state를 구분할 수 있습니다. 더 자세한 내용은 <a href="https://ko.legacy.reactjs.org/docs/hooks-rules.html">여기</a>를 참고해주세요.</p>
</blockquote>
</li>
<li><p>Hook은 React 함수 내에서만 호출할 수 있습니다. 리액트 훅은 함수형 컴포넌트에서 호출해야하며, 추가적으로 custom hooks에서 또한 호출할 수 있습니다. </p>
</li>
<li><p>비동기 함수(async 키워드가 붙은 함수)는 콜백함수로 사용할 수 없습니다. </p>
<pre><code class="language-js">export default function App(){
  // useEffect Hook 내부에 비동기 함수가 들어가므로 에러 발생
  useEffect(async () =&gt; {
    await Promise.resolve(1)
  }, [])

  return {
    &lt;div&gt;
      &lt;div&gt;Test&lt;/div&gt;
    &lt;/div&gt;
  }</code></pre>
<blockquote>
<p>❓ 그 이유는?</p>
</blockquote>
</li>
</ol>
<hr>
<h2 id="📌-리액트-훅의-종류">📌 리액트 훅의 종류</h2>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/93b02857-3e81-4243-b4d6-ea026e29d344/image.png" alt="">리액트는 기본적으로 컴포넌트 상태를 관리할 수 있는 <code>useState</code>와 컴포넌트 생애주기에 개입할 수 있는 <code>useEffect</code> , 컴포넌트 간의 전역 상태를 관리하는 <code>useContext</code> 훅을 제공하고 있습니다. 추가로 제공하는 Hook은 기본 Hook의 동작 원리를 모방해서 만들어졌습니다. </p>
<p>추가적인 훅들의 기본적인 역할은 다음과 같습니다. </p>
<ul>
<li><code>useReducer</code> : 상태 업데이트 로직을 <code>reducer</code> 함수에 따로 분리합니다. </li>
<li><code>useRef</code> : 컴포넌트나 HTML 엘리먼트를 레퍼런스로 관리합니다. </li>
<li><code>useImperativeHandle</code> : <code>useRef</code>의 레퍼런스를 상위 컴포넌트로 전달합니다. </li>
<li><code>useMemo</code>, <code>useCallback</code> : 의존성 배열에 적힌 값이 변할 때만 값 또는 함수를 다시 정의합니다. </li>
<li><code>useLayoutEffect</code> : 모든 DOM 변경 후 브라우저가 화면을 그리기 이전 시점에 effect를 동기적으로 실행합니다.  </li>
<li><code>useDebugvalue</code> : 사용자 정의(custom) Hook의 디버깅을 도와줍니다. </li>
</ul>
<hr>
<h2 id="📌-usestate">📌 useState</h2>
<p>리액트 훅의 기본이자 가장 많이 사용하는 훅인 <code>useState</code>는,  컴포넌트의 상태(state)를 관리할 수 있는 훅입니다. </p>
<pre><code class="language-react">const [number, setNumber] = useState(1);</code></pre>
<p>위 코드에서는 <code>number</code> 라는 상태를 <code>useState</code> 훅을 통해 관리하고 있습니다. <code>useState</code> 훅의 인자로 전달된 <code>1</code> 이라는 값이 <code>number</code>의 초기값이 되고, 훅을 통해 반환되는 <code>[number, setNumber]</code>에서 <code>number</code>를 통해 상태의 값에 접근할 수 있고, <code>setNumber</code> 메서드를 호출함으로써 상태 값을 변경할 수 있습니다. </p>
<br>

<h3 id="상태값-변경시-리렌더링-발생">상태값 변경시 리렌더링 발생</h3>
<pre><code class="language-js">const [number, setNumber] = useState(1);
setNumber(2);</code></pre>
<p><code>useState</code> 에서 반환된 배열의 두번째 값인 setter 함수를 호출하면 상태 값을 변경할 수 있고, 상태 값이 변경되면 해당 컴포넌트는 다시 렌더링됩니다. </p>
<p>=&gt; <code>useState(초기값)</code>에서 인자로 전달된 값을 상태의 초기값으로 사용합니다. 하지만 이후 setter 함수에 의해 상태의 값이 변경되었다면, 다음 렌더링에서는 그 상태를 유지합니다. </p>
<blockquote>
<p>setState 호출  =&gt; 상태 변경 =&gt; 리렌더링(변경된 상태값 사용)</p>
</blockquote>
<blockquote>
<p><a href="https://velog.io/@minw0_o/useState-%EC%9D%B4%EA%B2%83%EB%A7%8C%EC%9D%80-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90">useState 이것만은 알고 쓰자</a>
=&gt; useState의 기본적인 사용법 외에도 useState 사용 시 주의할 점에 대해 자세히 정리한 글입니다. </p>
</blockquote>
<hr>
<h2 id="📌-useeffect">📌 useEffect</h2>
<p>컴포넌트 내의 상태의 변화가 있을 때, 이를 감지하여 특정 작업을 해줄 수 있는 훅입니다. </p>
<p>일반적으로 <strong>sideEffect</strong>의 처리를 위해서 사용된다고 말합니다.</p>
<blockquote>
<p>sideEffect란?</p>
<p>=&gt; 컴포넌트가 화면에 렌더링된 이후에 <strong>비동기</strong>로 처리되어야 하는 부수적인 효과들을 말합니다. 예를 들어 API를 호출하는 경우 데이터를 비동기적으로 가져와야 하는데, 만약 그렇지 않다면 데이터를 가져오는 시간 동안 렌더링이 지연될 수도 있기 때문입니다.</p>
<p>sideEffect란 다음과 같은 경우를 모두 포함합니다.</p>
<ul>
<li>함수에서 함수 안의 내용물만으로 결과값을 만드는 것 외에 다른 행위들 </li>
<li>함수의 output를 만들기 위해 외부에 값을 사용하는 것 </li>
<li>외부 변수를 함수 안에서 변경시키는 것  </li>
</ul>
</blockquote>
<br>

<h3 id="사용-방법">사용 방법</h3>
<pre><code class="language-js">useEffect(()=&gt;{
   // 
}, []) // 의존성 배열</code></pre>
<p><code>useEffect</code>의 두번째 인자로 어떤 값을 전달하는 지에 따라, 첫번째 인자로 전달된 콜백 함수(effect)가 언제 실행되는 지가 결정됩니다.</p>
<br>

<h4 id="1-아무것도-전달하지-않으면">1. 아무것도 전달하지 않으면</h4>
<pre><code class="language-js">useEffect(()=&gt;{

}) // 아무것도 전달 x</code></pre>
<p>기본적으로는 <strong>첫 렌더링(마운트)</strong>과 <strong>그 이후의 모든 업데이트</strong>에 대해서 effect를 수행하게 됩니다. 이는 클래스 컴포넌트의 <code>componentDidMount</code>, <code>componentDidUpdate</code>를 합쳐놓은 것과 같습니다.</p>
<p>=&gt; 불필요한 렌더링을 줄이고 싶다면 <code>useEffect</code>의 두 번째 인자인 <strong><em>의존성 배열</em></strong> 을 넣어주면 됩니다.</p>
<br>

<h4 id="2-빈-배열--전달-시">2. 빈 배열 <code>[]</code> 전달 시,</h4>
<pre><code class="language-js">useEffect(() =&gt; {
  console.log(&quot;Component Loaded&quot;);
}, []);
// 컴포넌트가 마운트 됐을때만 실행된다. (componentDidMount)</code></pre>
<p>맨 처음 컴포넌트 생성 시, 즉 <strong>마운트</strong> 될 때만 실행됩니다. </p>
<p>=&gt; 물론, 마운트 될 때 실행된다는 것의 의미는 마운트 시 발생하는 렌더링 이후에 실행된다는 의미입니다.</p>
<br>

<h4 id="3-변수-전달-시-count">3. 변수 전달 시, <code>[count]</code></h4>
<pre><code class="language-js">useEffect(() =&gt; {
  document.title = `You clicked ${count} times`;
}, [count]);
// count가 바뀔 때만 effect를 재실행</code></pre>
<p>맨 처음 마운트 되었을 때와, <code>count</code> state의 값이 바뀔 때만 실행됩니다.</p>
<br>

<h3 id="clean-up">clean up</h3>
<pre><code class="language-js">useEffect(()=&gt;{
    console.log(&quot;Component Loaded&quot;);
    const handleScroll = () =&gt; {
        console.log(window.scrollY);
    };

    document.addEventListener(&quot;scroll&quot;, handleScroll);

    return () =&gt; document.removeEventListener(&#39;scroll&#39;, handleScroll);
}, []);</code></pre>
<p><code>window</code>에 스크롤에 대한 event를 등록하고, 컴포넌트가 사라질 때, 이를 해제해줄 수 있습니다.</p>
<p>=&gt; useEffect 안의 첫 번째 인자인 <strong>콜백함수 내 return 문</strong>은 <strong>해당 컴포넌트가 제거될 때(unmount될 때) 실행</strong>됩니다.</p>
<blockquote>
<p>❓ 왜 컴포넌트가 제거 될 때 return 문이 실행될까?</p>
<p>=&gt; <code>useEffect</code>는 원래 컴포넌트가 살아있는 동안 컴포넌트 내 특정 변수나 상태가 변화될 때마다 실행되야 하므로, 이를 감지하기 위해서는 컴포넌트가 살아있는 동안 계속 감지하면서 있다가, 컴포넌트가 제거될 때 return 하는 건가 ? </p>
</blockquote>
<p>=&gt; 의존성 배열로 아무것도 전달하지 않던, <code>[]</code>를 전달하던, <code>[count]</code>를 전달하던, <code>useEffect</code> 내의 return 문은 무조건 컴포넌트가 제거(unmount) 될 때 한번만 실행됩니다.</p>
<br>

<h3 id="useeffect-실행-타이밍">useEffect 실행 타이밍</h3>
<p>useEffect로 전달된 함수는 <code>컴포넌트 렌더링 - 화면 업데이트 - useEffect 함수 실행</code> 순으로 실행됩니다. 즉, useEffect실행이 <strong><em>최초 렌더링 이후</em></strong> 에 된다는 것을 유의해야합니다.</p>
<p>만약 화면을 다 그리기 이전에 동기화 되어야 하는 경우에는, <code>useLayoutEffect()</code>를 활용하여 <code>컴포넌트 렌더링 - useLayoutEffect 실행 - 화면 업데이트</code> 순으로 effect를 실행시킬 수 있습니다. </p>
<hr>
<h2 id="📌-useref">📌 useRef</h2>
<p>2가지 경우에 useRef를 사용할 수 있습니다.</p>
<ol>
<li><p><strong>DOM에 직접 접근</strong>할 때 사용합니다.</p>
<p>=&gt;  리액트는 DOM으로의 직접 접근을 막고 있기 때문에 <code>ref</code> 를 통해 접근해야합니다.  </p>
</li>
<li><p>변수의 값이 변하더라도 리<strong>렌더링을 유발하고 싶지 않을 때</strong> 사용합니다.</p>
<p>=&gt; <strong>ref 안에 저장되어 있는 값이 변경되어도 컴포넌트는 다시 렌더링되지 않습니다</strong>. 또한, 컴포넌트가 아무리 렌더링 되어도 ref 안에 저장되어 있는 값은 변화되지 않고 그대로 유지됩니다. </p>
<p>=&gt; ref의 값은 컴포넌트의 전 생애주기를 통해 유지되기 때문입니다. 컴포넌트가 브라우저에 마운팅 된 시점부터 마운트가 해제될 때까지 같은 값을 계속해서 유지할 수 있습니다.</p>
<p>=&gt; <strong>변경 시 렌더링을 발생시키지 말아야 하는 값</strong>을 다룰 때 편리합니다.</p>
<blockquote>
<p>useState를 통해 생성된 state는 그 값이 변경될 때마다 다시 렌더링됩니다. </p>
<p>VS </p>
<p>useRef는 값이 변경되더라도 다시 렌더링되지 않습니다. </p>
</blockquote>
</li>
</ol>
<br>

<h4 id="1-dom에-접근">1. dom에 접근</h4>
<pre><code class="language-js">const inputRef = useRef();

&lt;input ref = {inputRef}/&gt;</code></pre>
<p>그럼 이제 <code>inputRef.current</code>로 <code>input</code> 태그에 접근할 수 있습니다. </p>
<br>

<p>하위 컴포넌트의 dom에 접근하고 싶다면</p>
<p>EX)</p>
<p><code>App</code> 컴포넌트에서 =&gt; 하위 컴포넌트인 <code>Input</code> 컴포넌트의 dom에 접근하고 싶다면,</p>
<p>=&gt; <code>App</code> 컴포넌트에서 <code>Input</code> 컴포넌트의 dom에 ref를 통해 접근할 수 있다. </p>
<p>=&gt; <code>forwardRef</code> 이용 </p>
<pre><code class="language-jsx">function App() {
    const inputRef = useRef(); // useRef 선언

    return (
        &lt;div&gt;
            &lt;Input ref={inputRef}/&gt; // ref 전달 
            &lt;button onClick={()=&gt;inputRef.current.focus()}&gt; Focus &lt;/button&gt;
        &lt;/div&gt;
    );
}</code></pre>
<pre><code class="language-jsx">const Input = React.forwardRef(( _, ref ) =&gt; {
    return (
    &lt;&gt;
        Input: &lt;input ref={ref} /&gt;
    &lt;/&gt;
    );
});

export default Input;</code></pre>
<h4 id="2-리렌더링-유발하지-않는-변수로-사용">2. 리렌더링 유발하지 않는 변수로 사용</h4>
<p><code>useRef</code> 함수는 <code>current</code> 속성을 가지고 있는 객체를 반환하는데, 인자로 넘어온 초기값을 <code>current</code> 속성에 할당합니다. 이 <code>current</code> 속성은 값을 변경해도 상태를 변경할 때 처럼 React 컴포넌트가 다시 랜더링되지 않습니다. React 컴포넌트가 다시 랜더링될 때도 마찬가지로 이 <code>current</code> 속성의 값이 유실되지 않습니다.</p>
<pre><code class="language-jsx">import React, { useState, useRef } from &quot;react&quot;;

function ManualCounter() {
  const [count, setCount] = useState(0);
  const intervalId = useRef(null);
  console.log(`랜더링... count: ${count}`);

  const startCounter = () =&gt; {
    intervalId.current = setInterval(
      () =&gt; setCount((count) =&gt; count + 1),
      1000
    );
    console.log(`시작... intervalId: ${intervalId.current}`);
  };

  const stopCounter = () =&gt; {
    clearInterval(intervalId.current);
    console.log(`정지... intervalId: ${intervalId.current}`);
  };

  return (
    &lt;&gt;
      &lt;p&gt;자동 카운트: {count}&lt;/p&gt;
      &lt;button onClick={startCounter}&gt;시작&lt;/button&gt;
      &lt;button onClick={stopCounter}&gt;정지&lt;/button&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>위의 예제에서는 <code>intervalId</code>라는 data가 렌더링 시마다 변하지 않도록 하고 싶습니다. 그래서 <code>ntervalId</code>를 <code>useRef</code> 훅을 이용해 초기화해주었습니다. </p>
<blockquote>
<p>❓ useRef 작동 원리 알아보기</p>
<p>useRef는 선언된 컴포넌트 내에서 그냥 변수에 값을 할당하는 게 아니라, 변수에 참조값을 할당하고, 그 참조값이 값을 가리키고 있어서, 즉 한 단계 더 거쳐서 저장해서 값이 변하더라도 참조값은 변하지 않으므로 그래서 렌더링이 일어나지 않는 것 아닐까</p>
<p>=&gt; <code>const intervalId = useRef(null)</code>에서 <code>IntervalId</code>가 참조값이고, <code>intervalId.current</code>를 통해 그 실제 값을 접근할 수 있는 것 같다 ! </p>
</blockquote>
<hr>
<h2 id="📌-usememo-reactmemo">📌 useMemo, React.memo</h2>
<h3 id="usememo">useMemo</h3>
<p>원래, 컴포넌트 내의 변수는 리렌더링 시에 다시 정의됩니다. 하지만 <code>useMemo</code>를 사용하면, 매 리렌더링 시마다 다시 정의되는 것이 아니라, 특정 값이 변할 때만 정의됩니다. </p>
<p>즉, <code>useMemo</code>는 <strong>변수의 최적화</strong>를 위한 훅입니다. </p>
<p>컴포넌트는 사실 함수입니다. 즉, 컴포넌트란 <strong>JSX를 반환하는 함수</strong>에 불과합니다. 컴포넌트가 렌더링 된다는 것은 누군가가 이 함수 컴포넌트를 호출해서 실행하는 것을 의미합니다. 이는 함수가 실행될 때마다 내부에 선언되어있던 <strong>변수, 함수 등이 매번 다시 선언</strong>되거나 다시 실행이 된다는 것을 의미합니다. </p>
<blockquote>
<p>컴포넌트가 리렌더링 되는 조건</p>
<ol>
<li><p>컴포넌트의 <strong>state</strong>가 변경</p>
</li>
<li><p>컴포넌트의 <strong>props</strong>가 변경</p>
</li>
<li><p>부모 컴포넌트 리렌더링</p>
<p>=&gt; 부모 컴포넌트가 리렌더링될 때 새로운 prop이 들어오지 않더라도 부모 컴포넌트가 리렌더링되면 자식 컴포넌트 역시 리렌더링 된다.</p>
</li>
</ol>
</blockquote>
<p>만약 컴포넌트를 최적화하지 않는다면, 부모 컴포넌트가 변경되기만 하더라도 리렌더링 될 수 있음을 의미합니다. 그리고 리렌더링 시, 연산이 많이 걸린다면 이는 비효율적일 것입니다. 따라서 이를 <code>useMemo</code>를 사용함으로써 해결이 가능합니다. </p>
<h3 id="사용-예시">사용 예시</h3>
<pre><code class="language-jsx">const ShowSum = ({label, n]}) =&gt; {
   // const result = sum(n);

   const result = useMemo(()=&gt;sum(n), [n]);

   return (
       &lt;span&gt;
           {label}: {result}
    &lt;/span&gt;
   )
}

export default ShowSum;</code></pre>
<p>기록해 둘 표현식인 <code>sum(n)</code> 을 적어두고, 어떤 값이 변할 때만 이를 다시 변경할 것인지를 <code>[ ]</code> 안에 넣어줌 =&gt; <code>[n]</code> =&gt; <code>n</code> 값이 변할 때만 다시 계산</p>
<blockquote>
<p>prop인 <code>label</code>의 값이 계속 바뀐다면, 바뀔 때마다 컴포넌트는 계속 리렌더링되지만, 대신 result는 렌더링 시마다 계산하지 않는 것</p>
<p>=&gt; 의존성 배열에 들어있는 <code>n</code> 이 변할 때만 다시 계산</p>
</blockquote>
<p>=&gt; 즉, <strong>특정 변수를 언제 다시 계산(정의)할 것인지</strong>를 지정해줄 수 있습니다. </p>
<blockquote>
<p><strong>그럼 useMemo를 이용한 변수는 state 인가 ?</strong></p>
<p><code>useMemo</code>를 사용하여 선언된 변수는 컴포넌트의 state와는 조금 다릅니다. <code>useMemo</code>는 메모이제이션된 값을 반환하는 훅으로, 특정 값이나 연산의 결과를 기억하여, 의존성이 변경되지 않으면 이전에 계산된 값을 재사용합니다.</p>
<p>컴포넌트의 상태(State)는 <code>useState</code> 훅을 사용하여 관리되며, 상태가 변경되면 컴포넌트가 리렌더링됩니다. <code>useMemo</code>는 주로 계산 비용이 높은 연산의 결과를 저장하고 싶을 때 사용됩니다. 이 때, 의존성 배열(dependency array)을 통해 해당 값이 의존하는 변수가 변경될 때만 다시 계산됩니다.</p>
<p>요약하면, <code>useMemo</code>로 선언된 변수는 상태(State)가 아니며, 컴포넌트의 리렌더링과 직접적인 관련이 없습니다. 대신, 특정 값을 메모이제이션하여 필요할 때만 계산을 수행하는 용도로 사용됩니다.</p>
</blockquote>
<h3 id="reactmemo">React.Memo</h3>
<p><code>useMemo</code> 훅과 비슷하지만 약간 다른 개념인 <code>React.memo</code>에 대해서도 알아보겠습니다. </p>
<p>위에서 컴포넌트가 리렌더링 되는 조건을 살펴봤습니다. </p>
<blockquote>
<p>컴포넌트가 리렌더링 되는 조건</p>
<ol>
<li>컴포넌트의 상태가 변경</li>
<li>부모로부터 받는 props가 변경</li>
<li>부모 컴포넌트의 상태가 변경 </li>
</ol>
</blockquote>
<p>위에서 알 수 있는 건, 부모 컴포넌트의 상태가 변경되기만 하더라도 자식 컴포넌트가 리렌더링 될 수 있다는 것입니다.  하지만, 부모 컴포넌트의 상태만 변경되고 자식 컴포넌트는 변경된 것이 없는데, 리렌더링된다면 이는 비효율적일 것입니다. </p>
<p>여기서 <code>React.memo</code>를 사용한다면, 부모 컴포넌트의 상태가 변경되더라도 자식 컴포넌트가 변경되지 않았을 경우 자식 컴포넌트의 리렌더링을 막을 수 있습니다. </p>
<h3 id="사용-예시-1">사용 예시</h3>
<p>단순히 함수 컴포넌트 식을 <code>React.memo()</code> 로만 감싸주면서 사용할 수 있습니다. </p>
<p><code>Box.jsx</code></p>
<pre><code class="language-jsx">const Box = React.memo(()=&gt;{
    console.log(&quot;Box 컴포넌트 렌더링&quot;);
    return &lt;div/&gt;
})

export default Box;</code></pre>
<p><code>App.jsx</code></p>
<pre><code class="language-jsx">function App(){
    const [count, setCount] = useState(0);

    return (
        &lt;div&gt;
            {count}
            &lt;button onClick={()=&gt;setCount(count+1)}&gt;+&lt;/button&gt;
            &lt;Box/&gt;
        &lt;/div&gt;
    )
}

export default App;</code></pre>
<p>=-&gt; 만약 <code>React.memo</code>를 사용하지 않았다면 <code>button</code> 클릭으로 인해 부모 컴포넌트의 상태인 <code>count</code> 값이 증가할 때마다, 자식 컴포넌트인 <code>Box</code> 컴포넌트가 리렌더링되어     <code>console.log(&quot;Box 컴포넌트 렌더링&quot;);</code>가 계속 찍혔을 것입니다. </p>
<hr>
<h2 id="📌-usecallback">📌 useCallback</h2>
<p>컴포넌트가 렌더링 될 때마다 함수가 재정의되지 않고, 의존성 배열 내의 값이 변할 때만 재정의되도록 만들어주는 훅입니다. </p>
<p>위에서도 말했듯이, 컴포넌트가 렌더링 된다는 것은 컴포넌트 함수가 다시 호출되는 것이고, 그 안의 함수도 다시 정의됩니다. </p>
<p>하지만, 함수가 재정의된다면 함수는 <strong>객체 타입</strong>이므로 그 참조값이 바뀔 것입니다. 이 때, 함수가 다시 정의되는 것을 막기 위해 <code>useCallback</code> 훅을 사용할 수 있습니다. </p>
<blockquote>
<p>함수가 재정의되는 것을 막아줌으로써, 이 함수가 하위 컴포넌트에게 <code>prop</code>으로 전달된다면 그 하위 컴포넌트의 리렌더링도 방지할 수 있습니다. </p>
<p>=&gt; 참조값이 변하지 않기 때문에 <code>prop</code>으로 전달되는 값도 변하지 않습니다.  </p>
</blockquote>
<h3 id="사용-예시-2">사용 예시</h3>
<p><code>CheckBox.jsx</code></p>
<pre><code class="language-jsx">const CheckBox = React.memo(({ label, on, onChange})) =&gt; {
  console.log(label, on);
  return (
    &lt;label&gt;
      {label}
      &lt;input type=&quot;checkbox&quot; defaultChecked={on} onChange={onChange}/&gt;
    &lt;/label&gt;
  )
}

export default CheckBox;</code></pre>
<p><code>App.jsx</code></p>
<pre><code class="language-jsx">function App(){
  const [foodOn, setFoodOn] = useState(false);
  const [clothesOn, setClothesOn] = useState(false);
  const [shelterOn, setShelterOn] = useState(false);

  const foodChange = useCallback((e)=&gt; setFoodOn(e.target.checked), []);
  const clothesChange = useCallback((e)=&gt; setClothesOn(e.target.checked), []);
  const shelterChange = useCallback((e)=&gt; setShelterOn(e.target.checked), []);

  return (
    &lt;div&gt;
      &lt;CheckBox label=&quot;Food&quot; on={foodOn} onChange={foodChange} /&gt;
      &lt;CheckBox label=&quot;Clothes&quot; on={clothesOn} onChange={clothesChange} /&gt;
      &lt;CheckBox label=&quot;Shelter&quot; on={shelterOn} onChange={shelterChange} /&gt;
    &lt;/div&gt;
  )
}

export default App;</code></pre>
<ul>
<li><p>Food check box의 체크를 눌렀을 때, <code>props</code>로 전달된 <code>onChange</code>인 <code>foodChange</code>가 실행되고, 이를 통해 <code>App</code> 컴포넌트의 state인 <code>foodOn</code>이 변경될 것입니다. </p>
</li>
<li><p><code>App</code> 컴포넌트의 state가 변경되었으므로, <code>App</code> 컴포넌트는 다시 렌더링 될 것입니다. 여기서 만약 <code>useCallback</code>으로 각 함수 <code>foodChange</code>, <code>clothesChange</code>, <code>shelterChange</code>를 감싸주지 않았다면, 이 함수들은 모두 재정의 될 것입니다. </p>
</li>
<li><p>또한, 이 함수들은 자식 컴포넌트들에게 props로 전달되고, 각 자식 컴포넌트 입장에서는 props가 변경된 것이기 때문에 자식 컴포넌트들도 리렌더링될 것입니다. </p>
<p>=&gt; <code>React.memo</code>로 감쌋지만, props가 변경되는 것이므로 렌더링됩니다. </p>
</li>
<li><p>하지만 <code>useCallback</code>으로 각 함수를 감싸줬기 때문에, <code>App</code> 컴포넌트 내의 함수들인  <code>foodChange</code>, <code>clothesChange</code>, <code>shelterChange</code>은 <code>App</code> 컴포넌트가 리렌더링되어도 재정의되지 않습니다.  </p>
</li>
</ul>
<blockquote>
<p>💡 </p>
<p>useCallback 을 사용할 때 , 2번째 인자에 defendency array 를 넘기고, setData 로 state를 초기화 해줄 때, <strong>함수형 업데이트</strong>로 초기화를 해주어야 합니다. </p>
<p>함수형 업데이트로 초기화를 해주어야, defenden array를 비워도 항상 최신의 state를 함수형 업데이트의 인자를 통해서 참조할 수 있게 됩니다. </p>
</blockquote>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p><a href="https://velog.io/@jaewoneee/React-React-Hooks-%EC%A0%95%EB%A6%AC">[React] React Hooks 정리</a></p>
<p><a href="https://velog.io/@gwak2837/React-Hooks%EC%9D%98-%EC%9D%B4%ED%95%B4">React Hooks 이해하기</a></p>
<p><a href="https://www.daleseo.com/react-hooks-use-ref/">React Hooks: useRef 사용법</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로토타입과 객체지향]]></title>
            <link>https://velog.io/@minw0_o/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EA%B3%BC-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5</link>
            <guid>https://velog.io/@minw0_o/%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EA%B3%BC-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5</guid>
            <pubDate>Fri, 09 Feb 2024 07:13:28 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-프로로타입이란">📌 프로로타입이란?</h2>
<p>프로토타입이란 무엇일까요? 프로토타입에 대한 일반적인 설명은 아마 다음과 같을 것입니다. </p>
<blockquote>
<p>프로토타입이란, </p>
<p>다른 객체의 원형이 되는 객체로서, 객체 간의 상속을 가능하게 합니다. </p>
</blockquote>
<p>하지만 위의 설명은 저에게 프로토타입은 무엇이고 왜 생겨났는지, JS는 왜 클래스가 아닌 프로토타입 기반으로 설계되었는지 등의 질문에 대한 명확한 대답이 되지 못했습니다. 여러 글을 찾아보다가, <a href="https://medium.com/@limsungmook/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C-997f985adb42">자바스크립트는 왜 프로로타입을 선택했을까</a> 라는 글을 읽게 되었고, 덕분에 위와 같은 질문에 대한 답을 나름대로 찾을 수 있었습니다. </p>
<p>프로토타입이 등장한 배경부터 그 내용까지 굉장히 잘 정리되어있는 글이기에, 직접 읽어보시는 것을 추천합니다. 다음은 위 글에서 핵심이라고 생각한 내용들을 간단히 정리한 것입니다.  </p>
<hr>
<h3 id="💡-자바스크립트는-왜-프로토타입을-선택했을까---요약">💡 자바스크립트는 왜 프로토타입을 선택했을까 - 요약</h3>
<p>플라톤이 주장했던 &quot;이데아&quot; 이론에 기반해, 공통된 속성을 기준으로 객체를 &quot;<strong>분류</strong>&quot;하는 사고 방식이 프로그래밍 언어에 자연스럽게 녹아든 것이 &quot;클래스 기반 객체 지향 프로그래밍 언어&quot;입니다. </p>
<blockquote>
<p>공통된 속성은 클래스의 프로퍼티가 되고, 프로퍼티가 유사한 객체가 있다면 일반화 과정을 통해 클래스로 추상화됩니다. </p>
</blockquote>
<br>
비트켄슈타인은 이러한 클래스의 "분류" 개념을 정면으로 반박하면서 2개의 이론을 주장했고, Rosch는 이를 바탕으로 프로로타입 이론을 주장합니다.

<ul>
<li><p>의미사용이론</p>
<p>단어의 진정한 본래의 의미는 존재하지 않고 오직 &quot;상황과 맥락&quot;에 의해서만 결정된다. </p>
</li>
<li><p>가족 유사성 이론</p>
<p>대상을 분류할 때, 모든 구성원이 공유하는 공통 속성이 아닌, 다수의 구성원이 공유하는 전형적인 특징을 통해 분류하는 이론</p>
</li>
</ul>
<br>
그리고 이 2개의 이론은 JS라는 언어에 다음과 같은 모습으로 드러납니다. 

<ul>
<li><p>의미 사용 이론 </p>
<p>=&gt; 변수의 의미는 그 <strong>어휘적인(Lexical)</strong>, <strong>맥락(Context)</strong>에서 평가됩니다. </p>
<p>=&gt; 실행 컨텍스트, 스코프 체인, this, 호이스팅, 클로저 등의 개념이 모두 프로토타입의 &quot;맥락&quot;을 표현하기 위한 것입니다. </p>
</li>
<li><p>가족 유사성 이론</p>
<p>=&gt; 객체는 정의로부터 분류되는 것이 아니라, 가장 좋은 보기(<strong>프로토타입</strong>)으로부터 범주화됩니다. </p>
<p>ex)</p>
<pre><code class="language-js">function 참새(){
    this.날개갯수 = 2;
    this.날수있나 = true;
}
const 참새1 = new 참새();

console.log(&quot;참새의 날개 갯수 : &quot;, 참새1.날개갯수); // 2

function 닭(){
  this.벼슬 = true;
}

닭.prototype = 참새1; // 오른쪽이 인스턴스인 점 주목
const 닭1 = new 닭();</code></pre>
<p><code>참새1</code> 을 프로토타입으로 갖는 <code>닭1</code>이 생겼습니다. 여기서 주목할 점은 오른쪽이 <code>참새</code>(함수)가 아니라 <code>참새1</code>(인스턴스) 인 점입니다. 프로토타입 이론은 이미 존재하는 사물을 통해 범주화한다는 점에서 일치합니다. </p>
</li>
</ul>
<br>

<p>위의 글을 정리해보면, 다음과 같이 요약할 수 있습니다. </p>
<blockquote>
<p>프로로타입 기반 언어인 JS는 </p>
<p>변수나 객체의 의미가 그 변수나 객체가 속한 &quot;<strong>맥락</strong>&quot;에 의해서만 평가될 수 있기 때문에, 공통된 속성을 기준으로 한 &quot;분류&quot;가 아닌, <strong>가장 좋은 보기(프로토타입)</strong>으로부터 객체를 범주화한다.  </p>
</blockquote>
<hr>
<h2 id="📌-프로로타입-기반-객체지향-프로그래밍">📌 프로로타입 기반 객체지향 프로그래밍</h2>
<h3 id="객체지향-프로그래밍이란">객체지향 프로그래밍이란?</h3>
<p>프로그램을 절차지향적 관점에서 벗어나 여러 객체의 집합으로 표현하는 방법입니다. </p>
<blockquote>
<p>객체란?
=&gt; 객체의 핵심은 <strong>묶는 것</strong></p>
<p>어떤 변수가 하나의 값을 가지는 것이 아니라, 여러 속성을 가지고 이 속성들을 한 곳에 묶어놓은 것이 객체가 되는 것  </p>
<p>일반적으로는 상태를 나타내는 데이터와 데이터를 조작할 수 있는 동작들을 묶어서 객체를 정의합니다. </p>
</blockquote>
<blockquote>
<p> JS는 객체 기반의 프로그래밍 언어이며 JS를 이루고 있는 거의 &quot;모든 것&quot;이 객체입니다. </p>
</blockquote>
<h3 id="프로토타입-기반-객체지향">프로토타입 기반 객체지향</h3>
<p>JS는 클래스 기반 OOP 언어보다 효율적이며 더 강력한 객체지향 프로그래밍 능력을 지니고 있는 <strong>프로토타입</strong> 기반의 객체지향 프로그래밍 언어입니다. </p>
<blockquote>
<p>ES6에서 JS에 클래스가 도입되었지만, 기존 프로토타입 기반 객체 지향 모델을 폐지하는 것은 아닙니다. 클래스도 결국 함수이며, 기존 프로토타입 기반 패턴의 symantic sugar라고 볼 수 있습니다.</p>
</blockquote>
<hr>
<h2 id="📌-프로토타입을-통한-상속--코드의-재사용">📌 프로토타입을 통한 상속 =&gt; 코드의 재사용</h2>
<p>상속은 어떤 객체의 프로퍼티 또는 메서드를 다른 객체가 상속받아 그대로 사용할 수 있는 것을 말합니다. JS는 <strong>프로토타입을 기반으로 상속을 구현</strong>하여 불필요한 중복을 제거합니다. </p>
<blockquote>
<p>즉, 각 인스턴스마다 동일한 메서드를 각각 가지고 있으면 메모리 상으로도 코드 상으로도 효율적이지 못하니까, 한 번만 정의하고 이를 재사용하는 것입니다. </p>
</blockquote>
<p>예를 들어, 원을 나타내는 객체를 정의한다고 생각해봅시다.
<img src="https://velog.velcdn.com/images/minw0_o/post/d1f553d3-90d7-4e14-af8e-0c610a7fcb1b/image.png" width="400" /></p>
<p>각 원 객체마다 반지름은 다를 수 있어도 반지름을 통해 너비를 구하는 방법은 같을 것입니다. 그렇다면 각 인스턴스가 이 <strong>너비를 구하는 메서드</strong>를 공유해서 사용할 수 있도록 <strong>프로토타입에 추가</strong>할 수 있습니다.</p>
<pre><code class="language-js">function Circle(radius){ // 원 객체를 생성하는 생성자 함수
    this.radius = radius;
}

Circle.prototype.getArea = function(){
    return Math.PI * this.radius * 2;
}

const circle1 = new Circle(1);
const circle2 = new Circle(2);

console.log(circle1.getArea()); // 프로로타입으로부터 상속받은 getArea 메서드를 사용합니다. </code></pre>
<p>위 코드에서,<code>Circle</code> 생성자 함수가 생성한 모든 인스턴스는 자신의 프로토타입 <code>Circle.prototype</code>의 모든 프로퍼티와 메서드를 상속받습니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/18e0cce4-b1a1-4e30-81db-44b16e76b5b4/image.png" alt=""></p>
<p>이렇듯 상속은 <strong>코드의 재사용</strong>이란 관점에서 매우 유용합니다. 이렇게 프로로타입에 프로퍼티나 메서드를 구현해두면, 각 인스턴스는 프로토타입에서 이를 상속받아 사용할 수 있습니다. </p>
<hr>
<h2 id="📌-프로토타입-객체-prototype">📌 프로토타입 객체 [[Prototype]]</h2>
<p>모든 객체는 <code>[[Prototype]]</code> 이라는 내부 슬롯을 가지며, 이 내부 슬롯의 값은 프로토타입의 참조입니다. </p>
<blockquote>
<p>좀 더 쉽게 설명하자면, 모든 객체는 <code>[[Prototype]]</code>이라는 직접 접근할 수는 없는 정보인 내부 슬롯을 가지고 있고, 이것이 바로 우리가 지금까지 설명한 객체의 프로토타입을 나타냅니다.</p>
</blockquote>
<p>내부 슬롯은 직접 접근할 수 없기 때문에, 다음과 같이 접근합니다.</p>
<pre><code class="language-js">const o = {};
o.[[Prototype]] // 내부 슬롯 =&gt; 접근 불가
o.__proto__ // =&gt; Object.prototype </code></pre>
<p><code>[[Prototype]]</code>에 저장되는 프로토타입은 객체가 생성될 때 그 방식에 의해 결정됩니다. </p>
<ul>
<li>객체 리터럴로 생성되었다면 <code>Object.prototype</code> 값을 가집니다.</li>
<li>생성자 함수를 통해 생성되었다면 생성자 함수의 <code>prototype</code> 프로퍼티에 바인딩되어 있는 객체 값을 가집니다. </li>
</ul>
<p><strong>모든 객체는 하나의 프로토타입을 갖습니다</strong>. 그리고 모든 프로토타입은 생성자 함수와 연결되어 있습니다. 즉, *<em>객체와 프로토타입과 생성자 함수는 다음 그림과 같이 서로 연결되어있습니다. *</em></p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/8169a568-28ad-43af-94cf-ce108c24fcc4/image.png" alt=""></p>
<ul>
<li><p>생성자 함수에서 prototype에 접근할 때는 <code>Circle.prototype</code>으로 접근할 수 있습니다. </p>
</li>
<li><p>반대로 prototype에서 생성자에 접근하려면<code>(Circle.prototype).constructor</code>로 접근할 수 있습니다. </p>
</li>
<li><p>객체에서 prototype에 접근할 때는 <code>__proto__</code> 접근자 프로퍼티를 통해 접근할 수 있습니다.</p>
</li>
</ul>
<br>

<h3 id="__proto__-접근자-프로퍼티"><code>__proto__</code> 접근자 프로퍼티</h3>
<p>모든 객체는 <code>__proto__</code> 접근자 프로퍼티를 통해 자신의 프로토타입, 즉 <code>[[Prototype]]</code> 내부 슬롯에 간접적으로 접근할 수 있습니다. </p>
<p>내부 슬롯은 프로퍼티가 아닙니다. 즉, 원칙적으로는 내부 슬롯의 값에 직접 접근할 수 없습니다. 하지만 <code>[[Prototype]]</code> 내부 슬롯은  <code>__proto__</code>라는 접근자 프로퍼티를 통해 간접적으로 접근할 수 있습니다.</p>
<blockquote>
<p>접근자 프로퍼티란?</p>
<p>자체적으로는 값 <code>[[Value]]</code> 프로퍼티 어트리뷰트를 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수인 <code>[[Get]]</code>, <code>[[Set]]</code> 프로퍼티 어트리뷰트로 구성된 프로퍼티입니다. </p>
<p>예시)</p>
<pre><code class="language-js">const person = {
    // 데이터 프로퍼티
    firstName: &#39;Minwoo&#39;
    lastName: &#39;Park&#39;,

    // fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
    // getter 함수
    get fullName(){
        return `${this.firstName} ${this.lastName}`;
    },


       // setter 함수    
    set fullName(name){ // 크기가 2인 배열인 name이 전달될 것임. 
        [this.firstName, this.lastName] = name.split(&quot; &quot;);
    }
};

// 데이터 프로퍼티를 통한 값 접근
console.log(person.firstName + &#39;&#39; + person.lastName); 


// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
person.fullName = &quot;minwoo park&quot;;
console.log(person.fullName); // minwoo parkd</code></pre>
</blockquote>
<p>그래서 <code>__proto__</code>를 통해 객체의 프로토타입 값에 접근하고, 그 값을 수정할 수 있습니다. </p>
<pre><code class="language-js">const obj = {};
const parent = { x : 1};

obj.__proto__;
obj.__proto__ = parent;
// setter 함수인 set __proto__가 호출되어 obj 객체의 프로토타입을 교체 

console.log(obj.x);; </code></pre>
<p>사실 <code>__proto__</code> 접근자 프로퍼티는 객체가 직접 소유하는 프로퍼티가 아니라 Object.prototype의 프로퍼티입니다. 모든 객체는 상속을 통해 <code>Object.prototype.__proto__</code> 접근자 프로퍼티를 사용할 수 있는 것입니다. </p>
<br>

<h3 id="함수-객체의-prototype-프로퍼티">함수 객체의 prototype 프로퍼티</h3>
<p>함수 객체만이 소유하는 prototype 프로퍼티는 생성자 함수가 생성할 인스턴스의 프로토타입을 가리킵니다. </p>
<pre><code class="language-js">// 함수 객체는 prototype 프로퍼티를 소유한다.
( function(){} ).hasOwnProperty(&#39;prototype&#39;); // =&gt; true

// 일반 객체는 prototype 프로퍼티를 소유하지 않는다.
({}).hasOwnPropery(&#39;prototype&#39;); // =&gt; false</code></pre>
<p>따라서 생성자 함수로 호출할 수 없는 함수, 즉 non-constructor인 화살표 함수와 ES6 메서드 축약 표현으로 정의한 메서드는 <strong>prototype 프로퍼티를 소유하지 않으며 프로토타입도 생성하지 않습니다.</strong> </p>
<p>생성자 함수로 호출하기 위해 정의하지 않은 일반 함수(함수 선언문, 함수 표현식)도 prototype 프로퍼티를 소유하지만 객체를 생성하지 않는 일반 함수의 prototype 프로퍼티는 아무런 의미가 없습니다. </p>
<h3 id="__proto__-vs-prototype"><code>__proto__</code> VS prototype</h3>
<p>위에서 살펴본 <code>__proto__</code> 프로퍼티와 <code>prototype</code> 프로퍼티에 대한 내용들을 정리해보면, 두 프로퍼티 모두 동일한 프로토타입을 가리키지만, 사용 주체와 목적 등에서 차이가 있습니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/3ec36898-75e6-4a14-899f-116afa350306/image.png" alt=""></p>
<hr>
<h2 id="📌-프로토타입의-생성-시점">📌 프로토타입의 생성 시점</h2>
<p>프로토타입은 <strong>생성자 함수가 생성되는 시점</strong>에 더불어 생성됩니다. 프로토타입과 생성자 함수는 단독으로 존재할 수 없고 언제나 쌍으로 존재하기 때문입니다. </p>
<p>생성자 함수는 다음과 같이 두 종류가 있습니다. </p>
<ul>
<li>사용자 정의 생성자 함수</li>
<li>js 기본 제공 빌트인 생성자 함수 <br>

</li>
</ul>
<h3 id="사용자-정의-생성자-함수">사용자 정의 생성자 함수</h3>
<p>사용자가 정의한 생성자 함수, 즉 constructor는 <strong>함수 정의가 평가되어 함수 객체를 생성하는 시점</strong>에 프로토타입도 더불어 생성됩니다. 그리고 이 프로토타입의 프로토타입은 언제나 <code>Object.prototype</code>이다.</p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/773ef5a3-e155-40de-8d47-a9ef7a6857be/image.png" alt=""></p>
<h3 id="빌트인-생성자-함수">빌트인 생성자 함수</h3>
<p>빌트인 생성자 함수(Object, String, Number, Function 등)도 일반 함수와 마찬가지로 빌트인 생성자 함수가 생성되는 시점에 프로토타입이 생성됩니다. </p>
<p>모든 빌트인 생성자 함수는 <strong>전역 객체가 생성되는 시점</strong>에 생성됩니다. </p>
<blockquote>
<p>전역 객체에는 많은 내장된 속성과 메서드가 있습니다. 이 중 일부는 빌트인 생성자 함수입니다. 예를 들어, <code>String</code>, <code>Number</code>, <code>Boolean</code>, <code>Object</code>, <code>Array</code>와 같은 생성자 함수들은 모두 전역 객체에 이미 존재하며, 코드 실행 이전에 JS 엔진에 의해 생성됩니다.</p>
</blockquote>
<br>

<p>프로토타입은 언제 생성되는가? 라고 물어보면 다음과 같이 정리할 수 있습니다. </p>
<ul>
<li>객체가 생성되기 이전에 생성자 함수와 프로토타입은 이미 객체화되어 존재합니다. </li>
<li>이후 생성자 함수 또는 리터럴 표기법으로 객체를 생성하면 프로토타입은 생성된 객체의 <code>[[Prototype]]</code> 내부 슬롯에 할당됩니다. </li>
</ul>
<blockquote>
<p>💡 생성자 함수가 만들어질 때 객체가 가지게 될 프로토타입이 미리 만들어지고, 나중에 생성자 함수를 통해 객체가 생성되면, 그 때 객체와 프로토타입이 연결되는 것입니다. </p>
</blockquote>
<hr>
<h2 id="📌-객체-생성-방식과-프로토타입의-결정">📌 객체 생성 방식과 프로토타입의 결정</h2>
<p>JS에서는 객체를 다양한 방법으로 생성할 수 있습니다. </p>
<ul>
<li>객체 리터럴</li>
<li>Object 생성자 함수</li>
<li>생성자 함수</li>
<li>Object.create 메서드`</li>
<li>클래스(ES6)</li>
</ul>
<p>여러가지 방법이 있지만, 이 방법들의 공통점은 <strong>추상 연산 OrdinaryObjectCreate</strong> 과정을 거친다는것입니다. </p>
<h3 id="추상-연산-ordinaryobjectcreate">추상 연산 OrdinaryObjectCreate</h3>
<ul>
<li>자신이 생성할 객체의 <strong>프로토타입</strong>을 인수로 받습니다. =&gt; (필수)</li>
<li>자신이 생성할 객체에 추가할 <strong>프로퍼티 목록</strong>을 인수로 받습니다. =&gt; (옵션)</li>
</ul>
<p>추상 연산이 이루어지는 과정은 다음과 같습니다. </p>
<ol>
<li>빈 객체를 생성</li>
<li>인수로 전달받은 프로퍼티 목록을 객체에 추가</li>
<li>인수로 전달받은 프로토타입을 객체의 <code>[[Prototype]]</code> 내부 슬롯에 할당 </li>
</ol>
<p>프로토타입은 추상 연산 OrdinaryObjectCreate에 전달되는 인수에 의해 결정되고, 이 인수는 객체가 생성되는 시점에 <strong>객체 생성 방식</strong>에 의해 결정됩니다. </p>
<h3 id="1-객체-리터럴">1. 객체 리터럴</h3>
<p>객체 리터럴에 의해 생성되는 객체의 프로토타입은 <code>Object.prototype</code>입니다. </p>
<pre><code class="language-js">const obj = {x:1};</code></pre>
<p>위 객체 리터럴이 평가되면 추상 연산 OrdinaryObjectCreate에 의해 다음과 같이 Object 생성자 함수와 Object.prototype과 생성된 객체 사이에 연결이 만들어진다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/c0a64873-4449-45f6-a918-e4942809456f/image.png" alt=""></p>
<h3 id="2-object-생성자-함수">2. Object 생성자 함수</h3>
<pre><code class="language-js">const obj = new Object();</code></pre>
<p>Object 생성자 함수에 의해 생성되는 객체의 프로토타입은 Object.prototype 입니다. </p>
<h3 id="3-생성자-함수">3. 생성자 함수</h3>
<p>추상 연산 OrdinaryObjectCreate에 전달되는 프로토타입은 생성자 함수의 prototype 프로퍼티에 바인딩되어 있는 객체입니다. </p>
<pre><code class="language-js">function Person(name){
    this.name = name;
}

const me = new Person(&quot;Lee&quot;);</code></pre>
<blockquote>
<p>정리하면</p>
</blockquote>
<ul>
<li>객체 리터럴 또는 Object 생성자 함수로 객체가 생성되었을 때는, 그 객체의 프로토타입은 <code>Object.prototype</code>입니다. </li>
<li>생성자 함수로 객체가 생성되었을 때는 그 생성자 함수의 고유한 프로토타입이 생성되고, 그 프로토타입의 프로토타입은 <code>Object.prototype</code>입니다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/420bef06-2e8f-40a0-9d4e-7227a42371a8/image.png" alt=""></p>
<hr>
<h2 id="📌-프로토타입-체인">📌 프로토타입 체인</h2>
<p>프로토타입의 프로토타입은 언제나 <code>Object.prototype</code> 입니다. </p>
<pre><code class="language-js">Object.getPrototypeOf(me) === Person.prototype; // true
Object.getPrototypeOf(Person.prototype) === Object.prototype; // true</code></pre>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/8847acb8-9766-4739-935a-8be55055cea7/image.png" alt=""></p>
<p>JS는 객체의 <strong>프로퍼티에 접근하려고 할 때</strong> 해당 객체에 접근하려는 프로퍼티가 없다면 <code>[[Prototype]]</code> 내부 슬롯의 참조를 따라 자신의 부모 역할을 하는 프로토타입의 프로퍼티를 순차적으로 검색합니다. 이를 프로토타입 체인이라고 합니다.</p>
<blockquote>
<p>프로토타입 체인은 JS가 객체지향 프로그래밍의 상속을 구현하는 메커니즘입니다.</p>
</blockquote>
<p>프로토타입 체인의 최상위에 위치하는 객체는 언제나 <code>Object.prototype</code>입니다. 프로토타입 체인의 종점인 <code>Object.prototype</code>에서도 프로퍼티를 검색할 수 없는 경우 <code>undefined</code>를 반환합니다. 이 때, 에러가 발생하지는 않습니다. </p>
<p>EX)</p>
<pre><code class="language-js">me.hasOwnPropertty(&#39;name&#39;);</code></pre>
<p>hasOwnProperty 메서드가 me 객체에도 없고, Person.prototype 에도 없으므로 계속 프로토타입 체인을 따라 올라가다가, Object.prototype는  hasOwnProperty 메서드를 가지고 있기 때문에, 여기서 멈추고 이를 참조합니다. </p>
<p>js엔진은 Object.prototype.hasOwnProperty 메서드를 다음과 같이 호출합니다. </p>
<pre><code class="language-js">Object.prototype.hasOwnProperty.call(me, &#39;name&#39;);</code></pre>
<p>=&gt; 메서드의 this에는 me가 바인딩됩니다. </p>
<p>이처럼 JS 엔진은 프로토타입 체인을 따라 프로퍼티/메서드를 검색합니다. 다시 말해, JS 엔진은 객체 간의 상속 관계로 이루어진 프로토타입의 계층적인 구조에서 객체의 프로퍼티를 검색합니다.
따라서** 프로토타입 체인은 상속과 프로퍼티 검색을 위한 메커니즘**이라고 할 수 있습니다. </p>
<h4 id="스코프-체인-vs-프로토타입-체인">스코프 체인 VS 프로토타입 체인</h4>
<p>이에 반해, 프로퍼티가 아닌 식별자는 스코프 체인에서 검색합니다. 다시 말해, JS 엔진은 함수의 중첩 관계로 이루어진 스코프의 계층적 구조에서 식별자를 검색합니다. 따라서 <strong>스코프 체인은 식별자 검색을 위한 메커니즘</strong>이라고 할 수 있습니다. </p>
<pre><code class="language-js">me.hasOwnProperty(&quot;name&quot;);</code></pre>
<p>위 예제의 경우, 먼저 스코프 체인에서 me 식별자를 검색합니다. me 식별자는 전역에서 선언되었으므로 전역 스코프에서 검색됩니다. me 식별자를 검색한 다음, me 객체의 프로토타입 체인에서 hasOwnProperty 메서드를 검색합니다. </p>
<p>이처럼 스코프 체인과 프로토타입 체인은 서로 연관없이 별도로 동작하는 것이 아니라, 서로 협력하여 식별자와 프로퍼티를 검색하는데 사용됩니다. </p>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p><a href="https://medium.com/@limsungmook/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%8A%94-%EC%99%9C-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85%EC%9D%84-%EC%84%A0%ED%83%9D%ED%96%88%EC%9D%84%EA%B9%8C-997f985adb42">자바스크립트는 왜 프로로타입을 선택했을까</a></p>
<p>모던 자바스크립트 Deep Dive CH19 - 프로토타입</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[전역 상태 관리 Recoil ]]></title>
            <link>https://velog.io/@minw0_o/%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-Recoil</link>
            <guid>https://velog.io/@minw0_o/%EC%A0%84%EC%97%AD-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC-Recoil</guid>
            <pubDate>Tue, 09 Jan 2024 12:48:18 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-상태-관리의-중요성">📌 상태 관리의 중요성</h2>
<p>리액트는 상태를 단방향으로 바인딩을 하는 라이브러리입니다. 부모에서 자식 방향으로만 <code>state</code>를 <code>props</code>로 전달할 수 있고, 자식의 <code>props</code>를 부모에게 전달하는 방법은 존재하지 않습니다. </p>
<p><strong>자식 component</strong>에서 <strong>부모 component</strong>의 <code>state</code>를 바꿀 수 있는 방법에는 2가지가 존재합니다. </p>
<blockquote>
<ol>
<li>자식에게 부모의 <code>state</code>를 변경할 수 있는 <code>setState</code> 함수를 <code>props</code>로 넘겨준다. </li>
<li>상태 관리 도구(redux, recoil 등)을 사용한다.</li>
</ol>
</blockquote>
<p>하지만 1번 방법은 서비스의 규모가 조금이라도 커지게 된다면 상태 관리에 어려움을 겪을 수 있습니다. 즉, 자식을 내려보내는 depth가 조금이라도 깊어지면 1번과 같은 방법은 효율적이지 않습니다.</p>
<br>

<p>React 자체를 통해서도 상태 관리가 가능하지만, 다음과 같은 한계가 존재합니다. </p>
<ul>
<li>컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 합니다. </li>
<li>Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값들의 집합을 담을 수는 없습니다. </li>
<li>위 두 가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 말단(state가 사용되는 곳)까지의 코드 분할을 어렵게 합니다. </li>
</ul>
<blockquote>
<p>전역 상태 관리 도구인 <strong>Recoil</strong>을 사용하면 atoms(공유 상태)에서 selectors(순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 있습니다. </p>
</blockquote>
<hr>
<h2 id="📌-atom">📌 atom</h2>
<p>Recoil에서 atom이란, 상태의 단위이고 업데이트와 구독이 가능합니다. </p>
<ul>
<li><p>상태의 단위이고, 값의 업데이트가 가능합니다. </p>
<p>=&gt; <strong>useState</strong> 훅과 비슷한 느낌이라고 생각할 수 있습니다. </p>
</li>
<li><p>구독이 가능합니다. </p>
<p>=&gt; 모든 컴포넌트가 전역적으로 상태를 공유할 수 있습니다. </p>
<p>=&gt; atom이 업데이트 되면 이를 구독한 각각의 컴포넌트는 새로운 값을 반영하여 리렌더링이 일어나게 됩니다. </p>
<p>=&gt; 동일한 atom을 여러 컴포넌트에서 구독하고 있다면 모든 컴포넌트는 그 상태를 공유합니다. </p>
</li>
<li><p>atom은 런타임에 생성될 수도 있고, React의 로컬 컴포넌트의 상태 대신 사용할 수 있습니다. </p>
</li>
</ul>
<br>


<h3 id="atom-정의-및-사용-예시">atom 정의 및 사용 예시</h3>
<p>다음은 atom을 정의하는 예시입니다. </p>
<pre><code class="language-ts">import { atom } from &#39;recoil&#39;;

// atom 생성 
export const todoListState = atom({ 
    key: &#39;todoListState&#39;,
    default: []
});

export const todoListFilter = atom({
    key: &#39;todoListFilterState&#39;,
    default: &#39;Show All&#39;,
})</code></pre>
<ul>
<li><p><code>atom</code> 이라는 함수를 사용해서 atom을 생성할 수 있습다. </p>
</li>
<li><p><code>atom</code> 함수에 인자로 <code>key</code>와 상태가 가질 기본 값 <code>default</code>를 넣어줘야 합니다. </p>
</li>
<li><p><code>atom</code>의 키는 전역적으로 고유해야 합니다. </p>
<p>=&gt; 2개의 atom이 같은 키를 가지면 오류를 일으킵니다.  </p>
</li>
</ul>
<br>
다음은 atom을 사용하는 예시입니다. 

<pre><code class="language-ts">// atom을 사용하는 컴포넌트 파일 
import { todoListState } from &#39;store/atom&#39;

const TodoList = (props) =&gt; {
    const [todoList, setTodoList] = useRecoilState(todoListState); // atom 구독

    const handleClickSetTodoList = (newList) =&gt; {
        setTodoList(newList);
    };

    return (
        &lt;&gt;
            {todoList.map((todoItem) =&gt; (
                &lt;TodoItem key={todoItem.id} item={todoItem}/&gt;
            ))}
        &lt;/&gt;
    );
};</code></pre>
<p>위의 코드의 useRecoilState 훅처럼 atom의 값에 접근하고 변경하기 위해 사용할 수 있는 러가지 hook들을 알아보겠습니다. </p>
<h3 id="userecoilstate">useRecoilState</h3>
<pre><code class="language-js">const [todoList, setTodoList] = useRecoilState(todoListState); </code></pre>
<p><code>atom</code>으로 정의된 state를 읽고 쓰기 위해서는 <code>useRecoilState</code>라는 훅을 사용할 수 있습니다.</p>
<ul>
<li><p><code>useState</code> 처럼 사용하고</p>
<p>=&gt; 인자에 atom 혹은 selector를 넣어 <code>[state, setState]</code>같은 튜플에 할당한다.</p>
</li>
<li><p>반환되는 배열의 첫 번째 인자 <code>todoList</code>는 값을 읽을 때 사용하는 값이고 </p>
</li>
<li><p>두번째 인자 <code>setTodoList</code>는 atom 값을 변경할 수 있는 함수이다.</p>
</li>
</ul>
<br>

<h3 id="userecoilvalue">useRecoilValue</h3>
<pre><code class="language-js">const todoList = useRecoilValue(todoListState);</code></pre>
<p>만약 상태를 사용하는 컴포넌트에서 atom이나 selector의 상태는 변경하지 않고 값을 읽는 용도로만 사용한다면 <code>useRecoilValue</code> 훅을 사용할 수 있습니다. </p>
<blockquote>
<p>이 hook은 상태를 읽을 수만 있게 하고 싶을 때 사용하는 것을 추천합니다.</p>
</blockquote>
<br>

<h3 id="usesetrecoilstate">useSetRecoilState</h3>
<pre><code class="language-js">const setTodoList = useSetRecoilState(todoListState);</code></pre>
<p>상태의 setter 함수만을 활용하기 위해 사용됩니다. 선언된 변수에 setter 함수를 할당하여 사용합니다.</p>
<blockquote>
<p>💡 useRecoilState를 사용하면 될텐데 useSetRecoilState가 필요한 이유 ??</p>
<pre><code class="language-tsx">const [todoList, setTodoList] = useRecoilState(todoListState);</code></pre>
<p>위와 같이 useRecoilState를 사용해 다음과 같이 받아오면 <code>todoList</code>까지 같이 받아오기 때문에 이 atom을 구독하게 됩니다. 따라서 <code>todoList</code>의 값이 바뀌면 이 컴포넌트는 다시 렌더링됩니다. </p>
<pre><code class="language-tsx">const setTodoList = useSetRecoilState(todoListState);</code></pre>
<p>위와 같이 useSetRecoilState를 사용하면 해당 atom을 구독하지 않고 해당 atom을 업데이트만 할 수 있어서, 불필요한 렌더링을 일으키지 않습니다. </p>
</blockquote>
<br>

<h3 id="useresetrecoilstate">useResetRecoilState</h3>
<pre><code class="language-js">const resetTodo = useResetRecoilState(todoListState);</code></pre>
<p>전역상태를 초기(deafult) 값으로 reset하기 위해서 사용됩니다. 선언된 함수 변수에 할당하여 사용합니다. </p>
<hr>
<h2 id="📌-selector">📌 selector</h2>
<ul>
<li><p>selector는 기존의 상태인 <strong>atom이나 selector에서 파생될 수 있는 data</strong>에 접근할 수 있게 해줍니다. </p>
</li>
<li><p>selector는 atom나 다른 selector를 입력으로 받아들이는 <strong>순수 함수</strong>입니다. </p>
</li>
<li><p>상위에 있는 atom 또는 selector가 업데이트 되면 하위의 selector 함수도 다시 실행됩니다. </p>
</li>
<li><p>selector를 atom과 마찬가지로 구독할 수 있고 selector가 변경될 때도 컴포넌트는 리렌더링됩니다. </p>
</li>
</ul>
<br>

<h3 id="selector를-사용하는-이유">selector를 사용하는 이유</h3>
<p>=&gt; 최소한의 상태 단위만 atoms에 저장하고 그 상태를 기반으로 파생되는 모든 데이터들은 selector에 명시한 함수를 통해서 효율적으로 계산함으로써 <strong>쓸모없는 상태의 보존</strong>을 방지합니다. </p>
<blockquote>
<p>EX)</p>
<p><strong>todoList</strong>도 필요하고, <strong>특정 filter가 적용된 todoList</strong>도 필요하다면</p>
<p>=&gt; 두 개의 state를 atom으로 각각 가지고 있는 것보다는, todoList라는 atom을 한 번만 선언하고 filter가 적용된 todoList를 계산해서 반환해주는 selector를 정의하는 쪽이 더 효과적입니다. </p>
<p>단, 지금 상황처럼 특정 filter가 적용된 todoList가 todoList에서 <strong>파생</strong>될 수 있기 때문에 selector를 사용하는 것이 가능한 것입니다. </p>
</blockquote>
<br>

<h3 id="사용-예시">사용 예시</h3>
<p>기존에 존재하는 atom에 접근해 이로부터 파생된 새로운 state에 접근할 수 있는 selector를 만들어보겠습니다. </p>
<pre><code class="language-js">// atom 선언 
export const todoListState = atom({
    key: &#39;todoListState&#39;,
    default: []
});

export const todoListFilter = atom({
    key: &#39;todoListFilterState&#39;,
    default: &#39;Show All&#39;,
})</code></pre>
<pre><code class="language-js">export const filteredTodoListState = selector({
    key: &#39;filteredTodoListState&#39;,
    get: ({get}) =&gt; {
        const filter = get(todoListFilterState);
        const list = get(todoListState); 

        switch(filter){
            case &#39;A&#39;: 
                return list.filter((item)=&gt;item.isComplete);
            case &#39;B&#39;:
                return list.filter((item)=&gt;!item.isComplete);
            default:
                return list;
        }
    }
})</code></pre>
<ul>
<li><p><code>selector</code> 함수를 호출해 인자로 <code>key</code>와 <code>get</code>을 넣어서 selector를 생성합니다. </p>
</li>
<li><p><code>key</code> : selector 마다 고유한 값으로 설정해줘야 합니다. </p>
</li>
<li><p><code>get</code> </p>
<ul>
<li><p>함수를 정의합니다. 여기서 <strong>정의해주는 함수의 리턴값</strong>이 실제로 이 selector를 호출했을 때 계산되어서 접근할 수 있는 값이 됩니다.</p>
</li>
<li><p>함수의 인자로 <code>{get}</code>을 받아 이 <code>get</code> 함수를 사용해 다른 <code>atom</code>과 <code>selector</code>에 접근할 수 있습니다. 이렇게 참조했던 다른 atom 혹은 selector가 업데이트 되면 이 <code>get</code> 함수도 다시 실행됩니다. </p>
<pre><code class="language-ts">const filter = get(todoListFilterState); // atom에 접근 
const list = get(todoListState); // atom에 접근 </code></pre>
</li>
<li><p>위 예시의 <code>filteredTodoListState</code> 라는 selector는 <code>todoListFilterState</code>와 <code>todoListState</code>라는 두개의 atom에 의존성을 가지고 있습니다.</p>
</li>
</ul>
</li>
<li><p>이렇게 정의된 selector의 값에 접근하기 위해서는 <code>useRecoilValue</code> 훅을 사용할 수 있습니다.</p>
<pre><code class="language-ts">const filteredTodoList = useReocilValue(filteredTodoListState);</code></pre>
<blockquote>
<p> 현재 <code>filteredTodoListState</code> 라는 selector는 writable하지 않기 때문에 <code>useRecoilState</code>를 사용하지 않았습니다.</p>
<p> selector를 정의할 때, <code>get</code> 함수만 정의한다면 selector는 읽기만 가능한 RecoilValueReadOnly 객체를 반환합니다. 그렇기 때문에 읽기 전용 훅은 <code>useRecoilValue</code>를 사용한 것이고, 만약 selector에서 set 함수를 정의한다면 쓰기도 가능한 <code>useRecoilState</code> 훅을 사용할 수 있습니다.</p>
</blockquote>
</li>
</ul>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p><a href="https://medium.com/feelslikemmmm/%EC%83%81%ED%83%9C%EA%B4%80%EB%A6%AC-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-recoil-f91f7ffa38b1">Recoil 알아보기</a>
<a href="https://velog.io/@juno7803/Recoil-Recoil-200-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0">Recoil 200% 활용하기</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[변수 선언 과정과 호이스팅]]></title>
            <link>https://velog.io/@minw0_o/%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8-%EA%B3%BC%EC%A0%95%EA%B3%BC-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85</link>
            <guid>https://velog.io/@minw0_o/%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8-%EA%B3%BC%EC%A0%95%EA%B3%BC-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85</guid>
            <pubDate>Tue, 09 Jan 2024 08:36:48 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="https://velog.io/@minw0_o/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%BD%94%EB%93%9C%EC%9D%98-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8">자바스크립트 코드의 실행 과정 - 실행 컨텍스트</a>에서 변수를 선언할 때의  3가지 단계(선언, 초기화, 할당)를 실행 컨텍스트와 렉시컬 환경을 통해서 설명했습니다. 이번 글에서는 이러한 변수 선언 과정을 거치면서 JS 내부에서는 어떤 일이 발생하는 지, 호이스팅이 발생하는 이유는 무엇인지에 대해 더욱 자세하게 알아보겠습니다.</p>
</blockquote>
<hr>
<h2 id="📌-변수의-선언">📌 변수의 선언</h2>
<blockquote>
<p> 변수를 선언했을 때, 변수 이름은 어디에 등록되는가?</p>
</blockquote>
<p>변수 이름을 비롯한 모든 식별자는 <strong>실행 컨텍스트</strong>에 등록됩니다. 실행 컨텍스트란, 간단히 말하면 프로그램이 실행되는 동안 변수와 함수가 존재하는 장소입니다. 변수 이름과 변수 값은 실행 컨텍스트 내에 키/값 형식인 객체로 등록되어 관리됩니다.</p>
<p>JS 엔진은 소스코드를 한 줄씩 순차적으로 실행하기에 앞서 먼저 소스코드의 평가 과정을 거칩니다. 이 때 변수 선언을 포함한 모든 선언문을 소스코드에서 찾아내 먼저 실행합니다. 그리고 평가 과정이 끝나면 비로소 선언문을 제외한 소스 코드를 한 줄씩 순차적으로 실행합니다. </p>
<blockquote>
<p>코드의 실행 과정 : 평가 =&gt; 실행</p>
</blockquote>
<p>=&gt; 이로 인해, 변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 JS 고유의 특징을 <strong>변수 호이스팅</strong>이라고 합니다. </p>
<blockquote>
<p>호이스팅이 발생하는 이유 ?</p>
<p>=&gt; 코드를 순차적으로 한줄 한줄 실행하기 전에 소스 코드를 평가하면서 변수 선언문이 먼저 실행되기 때문입니다. </p>
</blockquote>
<p>사실 변수 뿐만 아니라 함수, 클래스 등의 모든 식별자가 호이스팅 됩니다. 모든 선언문은 런타임 이전 단계에서 먼저 실행되기 때문입니다. </p>
<hr>
<h2 id="📌-값의-할당">📌 값의 할당</h2>
<pre><code class="language-JS">var score = 80;</code></pre>
<p>다음과 같은 코드는 변수의 선언과 값의 할당을 하나의 문으로 단축 표현한 것이며, 이는 실제로 아래와 같이 2개의 문으로 나누어 각각 실행됩니다. </p>
<pre><code class="language-js">var score; // 변수의 선언 
score = 80; // 값의 할당</code></pre>
<p>이 때 주의 할 점은 변수 선언과 값의 할당의 실행 시점이 다르다는 것이다. </p>
<ul>
<li>변수 선언 : 런타임 이전</li>
<li>값의 할당 : 런타임</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/e344ace6-5644-40e0-bf62-902df8473dfb/image.png" alt=""></p>
<p>값의 할당이 일어나면 <code>undefined</code> 값이 있던 메모리에 새로운 값인 <code>80</code>을 덮어 쓰는 것이 아니라, 80을 위한 새로운 메모리를 할당하고, score는 그 메모리를 가리키게 됩니다. </p>
<p>이는  <code>undefined</code> 값은 이제 더 이상 필요하지 않다는 것을 의미합니다. 이러한 불필요한 값들은 <code>garbage collector</code>에 의해 메모리에서 자동 해제됩니다. 단, 메모리에서 언제 해제될지는 예측할 수 없습니다. </p>
<pre><code class="language-js">console.log(score); // undefined

score = 80;
var score;

console.log(score); // 80</code></pre>
<hr>
<h2 id="📌-변수-선언의-3가지-단계">📌 변수 선언의 3가지 단계</h2>
<pre><code class="language-js">var variable1 = &quot;변수&quot;;
let variable2 = &quot;123&quot;;</code></pre>
<p>위와 같이 변수를 선언하고 값을 할당하는 코드가 실행되면 다음 3가지 단계로 나누어 실행됩니다.</p>
<ol>
<li>변수의 <strong>고유식별자(변수명)을 생성</strong>하고</li>
<li><strong>메모리에 주소를 할당</strong>하고 </li>
<li>생성한 주소에 <strong>값</strong>을 넣는다.</li>
</ol>
<p>이를 실제 실행 컨텍스트 관점에서 다시 표현해보면 다음과 같이 표현할 수 있습니다. </p>
<ol>
<li><p>선언 단계 </p>
<p>변수를 Execution Context의 <strong>Lexical Environment</strong>에 등록합니다. </p>
</li>
<li><p>초기화 단계  </p>
<p><strong>Lexical Environment</strong>에 등록되어 있는 변수를 위하여 메모리를 할당하는 단계로, 여기서 변수는 undefined로 초기화됩니다. </p>
</li>
<li><p>할당 단계 </p>
<p>변수에 실제로 값이 할당되는 단계입니다. (undefined → 특정한 값)</p>
</li>
</ol>
<br>

<p>우리가 선언한 변수나 상수는 값이 아닌 <strong>메모리 주소</strong>를 바라보고 있습니다.</p>
<pre><code class="language-js">let variable = 126;
let variable2 = variable;</code></pre>
<p>이렇게 새로운 변수에 기존 변수를 대입한다면, <code>variable2</code>가 <code>variable</code>의 메모리 주소를 참조하게 됩니다. </p>
<br>

<p>여기서 만약 다음과 같이 기존 변수의 값을 변경한다면 어떻게 될까요 ? </p>
<pre><code class="language-js">variable = variable + 1</code></pre>
<p>=&gt; 기존 <code>variable</code>이 가리키던 메모리 주소가 아닌, 새로운 메모리 주소를 할당 받고, 그곳에 값을 넣게 됩니다. </p>
<p>=&gt; 이는 <strong>JS에서 원시 타입은 값의 변경이 불가능</strong>하기 때문입니다. 특정 메모리에 값이 할당이 되면, 그 메모리에 다른 값은 할당이 불가능합니다. Garbage collector가 이를 해제한 후에야 가능합니다.</p>
<p>=&gt; 즉, 원시 타입 변수의 값이 변경될 때는 항상 메모리가 새로 할당됩니다. </p>
<hr>
<h2 id="📌-호이스팅과-var-let-const">📌 호이스팅과 var, let, const</h2>
<h3 id="호이스팅이란">호이스팅이란?</h3>
<p><strong>변수 및 함수 선언문이 스코프 내의 최상단으로 끌어올려지는 것처럼 동작하는 현상</strong>을 말합니다. </p>
<p>호이스팅이 발생하는 이유는 JS 엔진은 소스코드를 순차적으로 한줄 한줄 실행하기 전에 소스 코드를 평가하면서 변수와 함수 선언문을 먼저 실행하고, 이를 렉시컬 환경에 등록한 다음에 코드를 실행하기 때문입니다.</p>
<p>실제로 선언문이 코드 상에서 최상단으로 끌어올려지는 것은 아닙니다. 렉시컬 환경에 변수 및 함수가 등록되어 있기 때문에 코드 상에서 변수 선언문 이전에 변수를 참조하는 코드가 나와도, 해당 변수를 참조할 수 있으므로 최상단으로 끌어올려지는 것처럼 느끼는 것 뿐입니다. </p>
<h3 id="var-vs-let-const">var vs let, const</h3>
<p>let, const와 var을 통해 변수를 선언할 때 <strong>변수 선언의 3가지 단계</strong>를 거치는 부분에 차이가 발생하는데, var의 경우 1번과 2번이 동시에 진행되는데 let, const는 1번만 먼저 진행됩니다.</p>
<p>Execution Context의 생성 과정 중 첫 번째 페이즈인 Creation Phase에서</p>
<ul>
<li><strong>Variable Environment</strong>에서는 var로 선언된 변수를 변수 선언 1, 2단계 모두 진행하기에 메모리에 매핑되고 undefined로 초기화까지 마치게 됩니다. </li>
<li>반면에 <strong>Lexical Environment</strong>의 경우는 let, const로 선언된 변수를 1단계만 진행하기에 <strong>Variable Environment</strong>와는 동작 방식에 차이가 있습니다. </li>
</ul>
<p>이를 좀 더 자세히 정리하면, </p>
<ul>
<li><p>var 키워드로 선언한 변수는 <strong>선언 단계와 초기화 단계가 한번에</strong> 이뤄집니다. 즉, 스코프에 변수를 등록하고 메모리에 변수를 위한 공간을 확보한 후, undefined로 초기화합니다. 따라서 변수 선언문 이전에 변수에 접근하여도 스코프에 변수가 존재하기 때문에 참조 에러가 발생하지 않습니다. 다만 undefined를 반환한다. 이후 변수 할당문에 도달하면 비로소 값이 할당됩니다. </p>
</li>
<li><p>let 키워드로 선언된 변수는 <strong>선언 단계와 초기화 단계가 분리</strong>되어 진행됩니다. 즉, 스코프에 변수를 등록하지만 <strong>초기화 단계는 변수 선언문에 도달했을 때(코드 실행 후)</strong> 이뤄집니다.따라서 초기화 이전에 변수에 접근하려고 하면 참조 에러가 발생합니다. 이는 아직 변수가 초기화되지 않았기 때문입니다. 즉, 변수를 위한 메모리 공간이 아직 확보되지 않았기 때문입니다. </p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/0eb32015-f7ce-401c-a67a-9a736a85a168/image.png" alt=""></p>
<p>따라서 스코프의 시작 지점부터 초기화 시작 지점까지는 변수를 참조할 수 없습니다. 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 ‘일시적 사각지대(Temporal Dead Zone; TDZ)’라고 부릅니다.</p>
<p>=&gt; 즉, let은 호이스팅 시 선언 단계만 진행됩니다.  </p>
<hr>
<h2 id="➕-렉시컬-환경">➕ 렉시컬 환경</h2>
<p>렉시컬 환경이란 <strong>식별자-변수 맵핑 데이터 구조</strong>입니다. 여기서 식별자는 변수/함수의 이름을 참조하고, 변수는 실제 객체 [포함된 함수 객체] 또는 원시값을 참조합니다.  </p>
<p>렉시컬 환경의 컨셉은 다음과 같습니다.</p>
<pre><code class="language-null">LexicalEnvironment = {
  Identifier:  &lt;value&gt;,
  Identifier:  &lt;function object&gt;
}</code></pre>
<p>다시 말해, 렉시컬 환경은 <strong>프로그램이 실행되는 동안 변수와 함수가 존재하는 장소</strong>입니다. </p>
<blockquote>
<p>undefined VS &quot;초기화되지 않은 상태&quot;</p>
<p>undefined</p>
<pre><code class="language-js">lexicalEnvironment = {
a: undefined
}</code></pre>
<p>초기화되지 않은 상태 </p>
<pre><code class="language-javascript">lexicalEnvironment = {
a: &lt;uninitialized&gt;
}</code></pre>
</blockquote>
<br>

<p>호이스팅 함수 선언 시 렉시컬 환경의 변화</p>
<pre><code class="language-javascript">helloWorld();  // &#39;Hello World!&#39;가 콘솔에 찍힌다
function helloWorld(){
  console.log(&#39;Hello World!&#39;);
}</code></pre>
<p>컴파일 단계에서 함수 선언이 메모리에 추가된다는 것을 이미 알고 있기 때문에 실제 함수 선언 전에 우리 코드로 그 선언에 접근할 수 있습니다. </p>
<pre><code class="language-javascript">lexicalEnvironment = {
  helloWorld: &lt; func &gt;
}</code></pre>
<p>자바스크립트 엔진이 <code>helloWorld()</code> 호출을 접하게 되면, 렉시컬 환경 내부를 살펴보고 함수를 찾은 후에 실행 시킬 수 있습니다. </p>
<p>=&gt; 하지만 함수 표현식은 var, let, const로 선언하기 때문에 호이스팅 되어도 <code>undefiend</code> 또는 <code>uninitialized</code> 상태로 렉시컬 환경에 저장되기 때문에 선언문 이전에 접근 시 에러가 발생합니다.  </p>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p>모던 자바스크립트 Deep Dive </p>
<p><a href="https://developer.mozilla.org/ko/docs/Glossary/Hoisting">mdn 호이스팅</a></p>
<p><a href="https://hanamon.kr/javascript-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EC%9D%B4%EB%9E%80-hoisting/">호이스팅이란</a></p>
<p><a href="https://velog.io/@bisu8018/%ED%95%A8%EC%88%98-%ED%91%9C%ED%98%84%EC%8B%9D-VS-%ED%95%A8%EC%88%98-%EC%84%A0%EC%96%B8%EC%8B%9D">함수 표현식 vs 함수 선언식</a></p>
<p><a href="https://velog.io/@bisu8018/hoisting-in-modern-javascript">함수 호이스팅</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔥세션스토리지 훅을 이용한 자동 저장 및 이어서 작성하기]]></title>
            <link>https://velog.io/@minw0_o/%EC%84%B8%EC%85%98%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%ED%9B%85%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9E%90%EB%8F%99-%EC%A0%80%EC%9E%A5-%EB%B0%8F-%EC%9D%B4%EC%96%B4%EC%84%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minw0_o/%EC%84%B8%EC%85%98%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80-%ED%9B%85%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%9E%90%EB%8F%99-%EC%A0%80%EC%9E%A5-%EB%B0%8F-%EC%9D%B4%EC%96%B4%EC%84%9C-%EC%9E%91%EC%84%B1%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 05 Jan 2024 15:20:53 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>올해도 아좌좌 프로젝트를 진행하며 <strong>계획 이어서 작성하기</strong> 기능을 담당했고, 이를 useSessionStorage 훅을 정의해 구현하는 과정을 담은 글입니다. </p>
</blockquote>
<hr>
<h2 id="📌-계획-작성-페이지">📌 계획 작성 페이지</h2>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/6443fc0b-a2b7-4257-968f-5c652aee0be5/image.png" alt=""></p>
<p>계획 작성 페이지는 계획을 작성할 수 있는 페이지입니다. 위의 파란색 박스에서 보듯이, 계획 작성 과정은 총 4단계로 구성되어 있습니다. <code>/create</code> 라는 url을 가지는 하나의 페이지 안에  총 4개의 단계가 진행될 수 있고, 각 단계를 나타내는 컴포넌트가 한 페이지 안에서 교체되면서 렌더링되는 구조입니다. </p>
<p>유저는 이 페이지에서 계획을 작성할 수 있습니다. 하지만 그 단계가 4단계나 되는 만큼, 유저가 중간에 실수 또는 고의로 페이지를 이탈할 수 있기 때문에, 다시 작성 페이지로 돌아왔을 때 이전 data를 사용해 계획을 <strong>이어서 작성할 수 있는 기능</strong>이 필요하다고 생각했습니다.<br><br></p>
<hr>
<h2 id="📌-세션-스토리지와-usesesseionstorage-hook">📌 세션 스토리지와 useSesseionStorage hook</h2>
<p>계획 이어서 작성하기 기능을 구현하기 위해서는, 유저가 이전에 작성하던 data를 어딘가에 저장해야했습니다. 그래야지만 유저가 계획을 이어서 작성하려고 할 때, 이전 data를 가져와 사용할 수 있기 때문입니다. </p>
<p>저희 팀이 선택한 저장소는 <strong>세션 스토리지</strong>입니다. 사실 데이터를 서버에 저장할 수도, 로컬 스토리지에 저장할 수도 있지만 세션 스토리지를 선택한 이유는 다음과 같습니다. </p>
<ul>
<li>유저가 계획 작성 페이지에서 작성을 하다가 실수로 다른 페이지로 이동했을 때, 다시 작성 페이지로 돌아와서 이어서 작성할 수 있도록 하고 싶었습니다.</li>
<li>위의 상황만 만족시켜주면 되겠다고 생각했기에, 브라우저를 아예 닫거나 다른 탭에서 계획 작성 페이지에 접근했을 때 data를 유지시켜줄 필요는 없다고 생각했습니다.</li>
</ul>
<br>

<p>그래서 컴포넌트 내의 상태들을 세션 스토리지에도 저장하고 컴포넌트의 상태가 변경될 때마다 이를 세션 스토리지와 동기화시켜주기 위해, <strong>useSessionStorage</strong> 훅을 정의하였습니다.</p>
<pre><code class="language-typescript">interface SessionStorageProps&lt;T&gt; {
  key: string;
  initialValue: T;
  setSessionValueAtFirst?: boolean; 
}

export const useSessionStorage = &lt;T&gt;({ // T: 저장하려는 data의 type
  key,
  initialValue,
  setSessionValueAtFirst = false,
}: SessionStorageProps&lt;T&gt;) =&gt; {
  const [getItem, setStoredItem] = useState&lt;T&gt;(() =&gt; {
    if (typeof window !== &#39;undefined&#39;) {
      const item = sessionStorage.getItem(key);
      if (item) {
        // sessionStorage에 값이 있다면 그 값을 사용
        return JSON.parse(item);
      } else {
        // session에 값이 없을 때 초기값으로 사용
        if (setSessionValueAtFirst) {
          // 초기값을 sessionStorage에 저장
          sessionStorage.setItem(key, JSON.stringify(initialValue));
        }
        return initialValue;
  });

  const setItem = (value: T) =&gt; {
    setStoredItem(value); // 1. 자체 state 변경   
    sessionStorage.setItem(key, JSON.stringify(value)); // 2. 세션 스토리지 내 data 변경
  };

  return [getItem, setItem] as const;
};</code></pre>
<p><code>useSessionStorage</code> hook을 간단히 설명하자면 다음과 같습니다. </p>
<ul>
<li><p>state의 초기값</p>
<ul>
<li><p>세션스토리지에 해당 key에 대한 data가 이미 존재한다면, 그 값을 초기값으로 사용합니다. </p>
<blockquote>
<p>이는 유저가 페이지를 이탈한 후, 다시 페이지에 접근했을 때 세션스토리지에 남아있던 data를 계속 사용할 수 있도록 하기 위함입니다. </p>
</blockquote>
</li>
<li><p>세션스토리지에 해당 ket에 대한 data가 존재하지 않는다면, 인자로 받은 <code>initialValue</code>를 초기값으로 사용합니다. </p>
</li>
</ul>
</li>
<li><p>state에 접근</p>
<p><code>useSessionStorage</code> hook 내 자체 state 값을 반환합니다. </p>
</li>
<li><p>state의 변경</p>
<ol>
<li><p><code>useSessionStorage</code> hook 내 자체 state를 변경시켜주고</p>
</li>
<li><p>세션스토리지의 data도 변경시켜줍니다. </p>
<p>=&gt; 이를 통해 컴포넌트 내 state와 세션스토리지 내 data가 항상 같은 값을 유지하는 것을 보장할 수 있습니다.</p>
</li>
</ol>
</li>
</ul>
<br>

<p>결국 이를 정리하면, <code>useState</code> hook을 통해 컴포넌트의 state를 관리하는 방법과 거의 동일하지만, state가 세션 스토리지에도 저장되는 것입니다.  </p>
<pre><code class="language-typescript">const [number, setNumber] = useState(1);</code></pre>
<p>다음과 같이 useState hook을 사용해 상태를 관리한다면, </p>
<pre><code class="language-typescript">const [원하는 getter 이름, 원하는 setter 이름] = useSessionStorage({key, 1});</code></pre>
<p>이렇게 비슷한 형식으로 useSessionStorage hook을 사용할 수 있습니다.  </p>
<br>

<hr>
<h2 id="📌-자동저장-및-이어서-작성하기-적용-예시">📌 자동저장 및 이어서 작성하기 적용 예시</h2>
<p>실제 코드 상에서 위 hook을 사용해 어떻게 자동저장 및 이어서 작성하기 기능을 구현했는지 살펴보겠습니다. </p>
<h3 id="자동-저장">자동 저장</h3>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/b8224c84-22fc-4410-b15e-f7e9e0798054/image.png" alt=""></p>
<p>유저가 처음 계획 작성 페이지에 접근하게 되면, 1단계인 <strong>계획 아이콘 정하기</strong> 단계에 진입하게 됩니다. 그러면 위 이미지의 초록색 박스에 해당하는 계획 아이콘 컴포넌트에서 <code>useSessionStorage</code> hook을 이용해 <strong>계획 아이콘 data</strong>를 관리합니다. </p>
<br>

<p><code>CreatePlanIcon.tsx</code></p>
<pre><code class="language-typescript">const [iconNumber, setIconNumber] = useSessionStorage&lt;number | null&gt;({
  key: SESSION_STORAGE_KEY.STEP_1,
  initialValue: null,
});</code></pre>
<p>1단계에 해당하는 key인 상수 <code>SESSION_STORAGE_KEY.STEP_1</code>를 인자로 전달해주고, 맨 처음에는 선택된 아이콘이 없기 때문에 초기값은 <code>null</code>로 전달해줍니다. </p>
<p>그리고 이 hook을 통해 반환한 getter, setter 역할을 하는 <code>iconNumber</code>와 <code>setIconNumber</code>를 각각 필요한 부분에 연결해줍니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/c3d3d017-a645-4659-9811-da25cf7c9c3b/image.png" alt=""></p>
<p>다음과 같이 아이콘 선택 모달에서 유저가 특정 아이콘을 클릭했을 때, 컴포넌트 내의 <strong>계획 아이콘 data</strong>가 변경되어야 합니다. 따라서, 아이콘 선택 모달 컴포넌트에 <code>setIconNumber</code> 메서드를 인자로 넘겨주고, 아이콘 선택 모달 컴포넌트 내 각 이미지의 클릭 리스너에 <code>setIconNumber</code> 메서드를 등록해줍니다. </p>
<p><code>CreatePlanIcon.tsx</code></p>
<pre><code class="language-tsx">&lt;Modal&gt;
&lt;ModalSelectIcon
  setIconNumber={setIconNumber}
  closeModal={() =&gt; {
    setIsSelectIconModalOpen(false); // 아이콘 선택 모달 컴포넌트에 setIconNumber 메서드를 인자로 넘겨줌
  }}
/&gt;
&lt;/Modal&gt;</code></pre>
<br>

<p><code>ModalSelectIcon.tsx</code></p>
<pre><code class="language-tsx">&lt;Image
    src={`/animal/${planIcons[iconNumber]}.png`}
    width={40}
    height={40}
    alt=&quot;example plan icon&quot;
    className={classNames(&#39;select-icon-modal__icon-image&#39;)}
    onClick={() =&gt; { // 클릭 리스너에 setIconNumber 메서드를 등록
          setIconNumber(iconNumber);
          closeModal();
    }}
/&gt;</code></pre>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/358d8b71-fdcf-4014-bee8-f0e64cb7b222/image.gif" alt=""></p>
<p>따라서, 아이콘이 클릭되었을 때 아이콘 data는 클릭된 아이콘에 해당하는 number로 변경되고, 세션 스토리지의 정보도 동시에 변경되는 것을 볼 수 있습니다. 즉, 컴포넌트 내 state가 세션 스토리지에도 자동으로 저장되는 것입니다. </p>
<br>

<h3 id="이어서-작성하기">이어서 작성하기</h3>
<p>또한, 아이콘을 선택한 후 다른 페이지로 갔다가 다시 계획 작성 페이지로 왔을 때 이전 data를 사용해 이어서 작성할 수 있습니다. </p>
<p>계획 작성 페이지에 다시 접근한다면, 계획 아이콘 작성 컴포넌트가 다시 mount 되고, 컴포넌트 내 state는 다시 초기화될 것입니다. 즉 아래의 코드가 다시 실행될 것입니다. </p>
<pre><code class="language-typescript">const [iconNumber, setIconNumber] = useSessionStorage&lt;number | null&gt;({
  key: SESSION_STORAGE_KEY.STEP_1,
  initialValue: null,
});</code></pre>
<p>하지만 지금은 이미 이전에 사용한 data가 세션스토리지에 남아있기 때문에, inivialValue로 전달된 <code>null</code>이 아니라 세션 스토리지의 값을 초기값으로 사용합니다. 따라서 다음과 같이 이어서 작성하기가 가능합니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/d3754c76-b868-41f1-be04-1557477d402a/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[useState 이것만은 알고 쓰자!]]></title>
            <link>https://velog.io/@minw0_o/useState-%EC%9D%B4%EA%B2%83%EB%A7%8C%EC%9D%80-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90</link>
            <guid>https://velog.io/@minw0_o/useState-%EC%9D%B4%EA%B2%83%EB%A7%8C%EC%9D%80-%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90</guid>
            <pubDate>Thu, 14 Dec 2023 01:29:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>리액트에서 가장 많이 사용하는 hook은 높은 확률로 useState일 것입니다. 기본적인 hook인 만큼, 사용하면서 주의해야할 점들을 정리해보았습니다💡</p>
</blockquote>
<hr>
<h2 id="📌-상태값-변경시-리렌더링-발생">📌 상태값 변경시 리렌더링 발생</h2>
<p><code>useState</code>에서 반환된 배열의 두번째 값인 setter 함수를 호출하면 상태 값을 변경할 수 있고, 상태 값이 변경되면 해당 컴포넌트는 다시 <strong>렌더링</strong>됩니다. </p>
<p>=&gt; 컴포넌트가 마운트되었을 때는, 기본적으로 <code>useState(초기값)</code>에서 인자로 전달된 값을 상태의 초기값으로 사용합니다. 하지만 이후 setter 함수에 의해 상태의 값이 변경되었다면, 다음 렌더링에서는 그 상태를 유지합니다. </p>
<blockquote>
<p>setState 호출  =&gt; 상태 변경 =&gt; 리렌더링(변경된 상태값 사용)</p>
</blockquote>
<hr>
<h2 id="📌-setstate는-비동기로-동작한다-그-이유는-">📌 setState는 비동기로 동작한다! 그 이유는 ?</h2>
<p><code>useState</code>의 setter 함수를 호출한 이후 바로 다음 줄에서 해당 state값을 참조하면 아직 바뀌기 이전 state가 참조됩니다. 이는 useState의 setter 함수가 <strong>비동기적으로 동작</strong>하기 때문입니다. </p>
<pre><code class="language-js">function App() {
  let [count, setCount] = useState(0);
  let [age, setAge] = useState(20);

  return (
    &lt;div&gt;
      &lt;div&gt;나이: {age}&lt;/div&gt;
      &lt;button
        onClick={() =&gt; {
          setCount(count + 1); // 나중 실행
          if (count &lt; 3) { // 먼저 실행 
            setAge(age + 1);
          }    
        }}
      &gt;
        카운트: {count}
      &lt;/button&gt;
    &lt;/div&gt;
  );
}</code></pre>
<p>위 코드 상에서 버튼을 누르면 다음과 같은 일이 발생합니다. </p>
<ol>
<li>버튼을 누르면 count를 +1 해줍니다. </li>
<li>그리고 만약에 count가 3보다 적으면 age도 +1 해줍니다.</li>
</ol>
<p>따라서 버튼을 계속 클릭하면, age는 20에서 22가 되면 더 이상 증가하지 않고 멈춰야합니다. 하지만 실제로는 age는 23까지 증가합니다. 즉, count가 3일 때에도 age +1를 해주고 있는 것입니다. </p>
<p>이는 <strong>state 변경함수가 비동기적으로 동작하는 함수</strong>이기 때문입니다. <code>setCount(count+1)</code> 동작으로 인해 count가 3이 되기 전에 <code>if(count &lt; 3) {}</code>에 먼저 진입하기 때문에 age가 23까지 증가하는 것입니다.  </p>
<br>

<h3 id="비동기로-동작하는-이유는">비동기로 동작하는 이유는?</h3>
<p>만약 한 컴포넌트 안에서 여러 state 값을 연속으로 변경하는 일이 생긴다면 여러 번 비교하고 다시 그리는 알고리즘이 실행(렌더링이 계속 일어남)됩니다.</p>
<p>만약 이 과정이 동기적으로 실행된다면 바뀌지 않아도 되는 불필요한 리렌더링이 다수 발생해 비효율적이고 성능도 안 좋을 것입니다.</p>
<blockquote>
<p>❓ 따라서 한번에 모아서 batch 처리를 해주기 위해 setState가 비동기적으로 동작한다는 건가?</p>
</blockquote>
<br>

<h3 id="setstate-함수는-비동기적으로-동작하지만-동기함수이다">setState 함수는 비동기적으로 동작하지만, 동기함수이다?</h3>
<p>리액트의 setState가 동기적 함수이고 마치 비동기 함수처럼 보이는 이유는 리엑트의 리렌더링 원리가 비동기적으로 작동하기 때문입니다.</p>
<p>리엑트는 렌더링 함수를 호출하여 가상 돔을 업데이트합니다.</p>
<p>setState가 비동기 함수처럼 보이는 이유는 setState 함수 그 자체가 비동기 함수여서가 아니라 리액트가 가상돔을 사용하게 설계되어있기 때문입니다.</p>
<blockquote>
<p>❓ 그렇다면, setState 함수 자체는 동기함수이므로, 호출 자체는 동기적으로 되는데, 그 변경으로 인한 가상돔을 통한 리렌더링이 비동기적으로 이루어져서 setState가 비동기적으로 작동하는 것처럼 보이는 것????</p>
<p>더 알아보기</p>
<p><a href="https://velog.io/@jay/setStateisnotasync">https://velog.io/@jay/setStateisnotasync</a></p>
</blockquote>
<br>

<h3 id="setstate-이후-특정-작업을-순차적으로-실행해주고-싶다면-">setState 이후 특정 작업을 순차적으로 실행해주고 싶다면 ?</h3>
<p>=&gt; <code>useEffect</code>와 useEffect의 <strong>의존성 배열</strong>을 활용</p>
<p>=&gt; 어떤 상태값이 바뀐 후 동작해야하는 코드가 있다면, useEffect를 사용해 해당 값을 감시하다가 상태가 변하면 동작하도록 해줄 수 있습니다. </p>
<hr>
<h2 id="📌-짧은-시간안에-setter-함수를-연속적으로-호출-시-렌더링은-1번만-진행">📌 짧은 시간안에 setter 함수를 연속적으로 호출 시 렌더링은 1번만 진행</h2>
<p>만약 다음과 같이 setter 함수를 연속적으로 호출 했을 경우 렌더링은 1번만 진행됩니다. </p>
<pre><code class="language-javascript">&quot;use client&quot;

import { useEffect, useState } from &quot;react&quot;;

export default function Page() {
  const [number, setNumber] = useState(0);

  useEffect(() =&gt; {
    setNumber(prev =&gt; prev + 1);
    setNumber(prev =&gt; prev + 1);
    setNumber(prev =&gt; prev + 1);
    setNumber(prev =&gt; prev + 1);
    setNumber(prev =&gt; prev + 1);
  }, []);

  useEffect(() =&gt; {
    console.log(`[${performance.now()}] 렌더링 완료됨. number =&gt; ${number}`);
  }, [number]);

  return (
    &lt;&gt;
      { 
        (function() {
          console.log(`[${performance.now()}] component return 됨.`);
          return &lt;&gt;&lt;/&gt;;
        })() 
      }
      &lt;div className=&quot;w-full relative&quot;&gt;
        현재 number 값 : { number }
      &lt;/div&gt;
    &lt;/&gt;
  );
}</code></pre>
<p>리액트에서는 배치 상태 업데이트라고 하여 짧은 시간 이내에 setter 함수가 연속적으로 호출 되었을 경우에는 호출 된 값 또는 업데이터 함수들을 큐에 집어 넣어 놓고 대기합니다. </p>
<p>그리고 그 짧은 시간이 지난 후 큐에 집어 넣은 것들을 순차적으로 일괄적으로 호출(적용) 하기 시작합니다. 이러한 배치 상태 업데이트 방식을 채택하고 있으므로, setter 함수를 연속으로 호출해도 렌더링은 1번만 발생시키는 것이 가능합니다. </p>
<hr>
<h2 id="📌-업데이터-함수로-상태를-변경해야-하는-경우">📌 업데이터 함수로 상태를 변경해야 하는 경우</h2>
<p>setter 함수의 인자에는 값을 넣을 수도 있지만, <strong>업데이터 함수</strong>를 넣을 수도 있습니다. 업데이터 함수는 인자에 prev 값이 넘어오면서 next state 값이 반환되게 되어 있도록 정의된 함수입니다. 업데이터 함수를 사용해야 하는 경우는 바로 이전 값을 참조해야 하는 경우입니다.</p>
<pre><code class="language-javascript">const [number, setNumber] = useState(0);

useEffect(() =&gt; {
  setNumber(prev =&gt; prev + 1);
  setNumber(prev =&gt; prev + 1);
  setNumber(prev =&gt; prev + 1);
  setNumber(prev =&gt; prev + 1);
  setNumber(prev =&gt; prev + 1);
}, []);</code></pre>
<p>위 코드를 실행해보면 number 는 5로 바뀌어 있는 것을 확인 할 수 있습니다. 업데이터 함수 자체도 인자에 이전 상태 값을 받아올 수 있지만, 업데이터 함수들 끼리도 이전에 호출된 업데이터 함수에서 반환된 상태 값을 이전 상태 값으로 참조할 수 있습니다. 즉 이전 값이 반드시 필요한 상태 업데이트 같은 경우에는 업데이터 함수를 사용해야 합니다. </p>
<hr>
<h2 id="📌-state의-불변성을-지켜야한다">📌 state의 불변성을 지켜야한다.</h2>
<p>리액트에서 state의 불변성을 지킨다는 것은, 상태 변경 시 기존의 상태 객체나 배열을 직접 수정하지 않고, <strong>새로운 객체나 배열을 생성하여 상태를 업데이트</strong>하는 것을 의미합니다. </p>
<blockquote>
<p>불변성을 지켜야 하는 이유는 무엇인가요?</p>
</blockquote>
<p>리액트가 상태를 업데이트 할 때, <strong>얕은 비교</strong>를 수행하기 때문입니다.</p>
<p>예를 들어 상태가 배열이라면, 배열의 요소 하나하나를 다 비교하는 것이 아니라, <strong>이전 배열의 참조값과 현재 배열의 참조값만을 비교하는 것</strong>입니다.</p>
<p>따라서 배열에 특정 요소가 push 되었더라도, 배열의 참조값이 바뀌지 않으면 리액트는 상태 변화를 감지하지 못합니다. 따라서 새로운 배열을 생성하여 상태를 업데이트 해줘야 합니다.</p>
<p>예를 들어, <code>useEffect</code> 훅은 의존성 배열의 원소들 중 하나라도 이전 렌더링 시의 그것과 다른 값을 가질 경우 콜백을 실행합니다. </p>
<p>여기서 <code>useEffect</code> 훅의 의존성 배열 원소들의 비교는 <code>Object.is</code> 메서드를 사용합니다. 원소들이 객체일 경우, 참조값이 서로 같으면 변경되지 않았다고 판단합니다. </p>
<p>따라서 state의 불변성을 지킴으로써 리액트는 상태변화를 감지할 수 있는 것입니다.</p>
<br>

<h3 id="불변성을-유지하는-예시">불변성을 유지하는 예시</h3>
<p>새로운 객체나 배열을 생성함으로써 불변성을 유지할 수 있습니다. <code>...</code>, <code>map</code>, <code>filter</code>, <code>slice</code>, <code>reduce</code> 등등 새로운 배열을 반환하는 메소드들을 활용하면 됩니다.</p>
<pre><code class="language-jsx">const [item, setItems] = useState([&#39;item1&#39;, &#39;item2&#39;]);
const addItem = newItem =&gt; {
    setItems([...items, newItem]); // 새로운 객체를 생성 ?
}</code></pre>
<hr>
<h2 id="📌-여러-data를-객체로-묶어서-state-관리하기">📌 여러 data를 객체로 묶어서 state 관리하기</h2>
<p>관리해야 할 data가 여러개라면, 비슷한 data 끼리는 객체로 묶고, 공통점이 없는 data들은 분리해서 각 상태의 <strong>책임</strong>을 분산하는 것이 좋습니다. 이렇게 한다면, 모듈화도 쉬워지고 불필요한 큰 객체 생성도 방지할 수 있습니다. </p>
<p>여기서의 책임은 <strong>변화</strong>를 의미합니다. 서로 같이 업데이트되는 상태끼리는 하나의 객체로 묶어도 된다는 의미입니다. 예를 들어 <code>마우스의  x좌표, y좌표</code> 같은 상태는 별도로 관리하는 것보다는 하나의 객체로 묶는 것이 더 효율적일 것입니다. </p>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p><a href="https://codingapple.com/unit/react-setstate-async-problems/">state 변경함수 사용할 때 주의점 : async</a></p>
<p><a href="https://velog.io/@samkong/setState">setState가 내 마음처럼 동작하지 않는 이유(feat.비동기)</a></p>
<p><a href="https://funveloper.tistory.com/192">useState 에 대해 알아봅시다</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔥Next.js middleware, Tanstack-query staleTime, 디바운스를 이용한 API 호출 최적화]]></title>
            <link>https://velog.io/@minw0_o/API-%ED%98%B8%EC%B6%9C-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@minw0_o/API-%ED%98%B8%EC%B6%9C-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Tue, 12 Dec 2023 02:15:11 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="">올해도 아좌좌 프로젝트</a>를 진행하며 서비스 내 API 호출 횟수를 최적화하기 위해 어떤 방법들을 사용했는지 그 과정을 담은 글입니다.</p>
</blockquote>
<hr>
<h2 id="📌-api-호출-최적화의-필요성">📌 API 호출 최적화의 필요성</h2>
<p><a href="">올해도 아좌좌 프로젝트</a>를 시작할 때부터, 저희 팀의 목표는 <strong>실제 사용자들의 문제를 해결할 수 있는 서비스를 만들고, 꾸준한 도메인 분석과 사용자들의 피드백을 바탕으로 서비스를 지속적으로 발전시켜나가는 것</strong>이었습니다. </p>
<p>그렇기 때문에 서비스를 실제로 온라인 상에 배포할 생각이었습니다. 따라서 <strong>많은 API 호출은 곧 비용을 의미했고</strong>, 누군가가 악의적으로 많은 양의 API 호출을 하거나 꼭 필요하지 않은 API가 호출되는 상황은 최대한 방지하자는 마음 가짐으로 서비스를 개발했습니다. </p>
<p>그래서 API 호출 최적화를 위해 다음과 같은 방법들을 사용했습니다. </p>
<ol>
<li><p>Next.js middleware 기능을 이용한 페이지 요청 리다이렉트 </p>
</li>
<li><p>Tanstack-query의 staleTime 설정 및 쿼리 무효화</p>
</li>
<li><p>토글 버튼에 디바운스 처리 적용 </p>
</li>
</ol>
<hr>
<h2 id="📌-nextjs-middleware-기능을-이용한-페이지-요청-리다이렉트">📌 Next.js middleware 기능을 이용한 페이지 요청 리다이렉트</h2>
<p>저희 서비스에는 로그인을 해야만 접근할 수 있는 페이지들이 있습니다. 내 계획 페이지, 계획 작성 페이지, 마이 페이지가 그 예시입니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/e9bb76ec-b59f-4101-89e3-03519a2b9585/image.png" alt=""></p>
<p>만약 로그인하지 않은 유저가 해당 페이지에 접근하려고 하는 경우, 페이지를 응답하기 전 nextjs <strong>middleware</strong>에서 로그인 여부를 검사해 로그인 하지 않았다면 로그인 페이지로 리다이렉트 시켜주었습니다. </p>
<blockquote>
<h4 id="middleware란">middleware란?</h4>
<p>유저가 보낸 request와 이에 대한 response 사이에 있는 소프트웨어입니다. 즉, 특정 요청이 완료되기 전 그 요청을 가로채 원하는 작업을 할 수 있습니다. </p>
<p>페이지를 렌더링하기 전에 서버 측에서 실행되는 함수이기 때문에, 요청에 대한 검증의 용도로 사용될 수 있습니다. 요청에 대한 응답을 rewriting, redirecting 등을 통해 수정할 수 있습니다. </p>
<p>더 자세한 내용은 <a href="https://nextjs.org/docs/app/building-your-application/routing/middleware">공식문서</a>를 참고하는 게 좋을 것 같습니다. </p>
</blockquote>
<p>로그인 하지 않은 유저가 위의 페이지들에 접근하는 것을 막는 것은 기능적으로도 자연스러웠고, 만약 특정 페이지에 접근해 그 페이지 안에서 로그인 여부를 검사해 리다이렉트 처리를 한다면, 이는 페이지 응답이라는 API 호출과 페이지에 사용되는 data를 불러오는 API 호출을 추가적으로 요구하는 작업이기에 비효율적이라고 생각했습니다. </p>
<p>따라서, 유저에게 특정 페이지를 응답하기 이전에 middleware에서 조건을 검사해 <strong>불필요한 페이지 응답 및 API 호출</strong>을 막을 수 있었습니다.</p>
<br>

<p>다음은 실제 middleware 코드의 일부분입니다.  </p>
<p><code>middleware.ts</code></p>
<pre><code class="language-ts">import { NextResponse } from &#39;next/server&#39;;
import type { NextRequest } from &#39;next/server&#39;;

export function middleware(request: NextRequest) {
  const cookies = request.cookies; // 1번 
  const hasAuthCookies = cookies.has(&#39;auth&#39;); // 2번

  if (request.nextUrl.pathname === &#39;/&#39;) {
    if (hasAuthCookies) {
      return NextResponse.redirect(new URL(&#39;/home&#39;, request.url));
    }
    return NextResponse.redirect(new URL(&#39;/login&#39;, request.url));
  } else if (request.nextUrl.pathname === &#39;/home&#39;)  // 3번
    if (!hasAuthCookies) {
      return NextResponse.redirect(new URL(&#39;/login&#39;, request.url));
    }
  } else if (request.nextUrl.pathname === &#39;/login&#39;) {
    if (hasAuthCookies) {
      return NextResponse.redirect(new URL(&#39;/home&#39;, request.url));
    }
  }

  ...</code></pre>
<ol>
<li>페이지 요청에 담겨있는 <code>cookies</code>에 접근합니다. </li>
<li><code>cookies</code>에 <code>auth</code>라는 key에 대한 값이 있는지 여부를 <code>hasAuthCookies</code>라는 변수에 저장합니다. 만약 로그인이 되어있는 유저의 요청이라면 <code>auth</code>라는 key에 대해 토큰 data가 값으로 들어있을 것입니다.</li>
<li>예를 들어 내 계획 페이지(<code>/home</code>)에 접근했을 경우, 만약 로그인되지 않은 유저라면로그인 페이지(<code>/login</code>)로 리다이렉트 시킵니다. </li>
</ol>
<hr>
<h2 id="📌-tanstack-query-staletime-설정-및-쿼리-무효화">📌 Tanstack-Query staleTime 설정 및 쿼리 무효화</h2>
<p>data가 변경될 가능성이 거의 없는 페이지가 있다면, 이 페이지에 접근할 때마다 data를 서버에서 새로 받아오는 것은 불필요한 API를 호출하는 것일 수 있습니다. 그래서 저는 이를 해결하기 위해 Tanstack-Query의 staleTime과 쿼리 무효화 기능을 사용했습니다. </p>
<p>이를 위해 다음과 같은 단계를 거쳤습니다. </p>
<ol>
<li>data를 받아오는 get 요청들에 대한 useQuery의 쿼리 키를 구조화합니다. </li>
<li>data가 변경될 가능성이 많이 없는 get 요청들에 대한 useQuery의 staleTime을 <code>infinity</code>로 설정합니다. </li>
<li>data를 변경하는 useMutation 요청이 실행됐을 때, 각 data에 해당하는 쿼리 키를 무효화합니다.</li>
<li>쿼리 키가 무효화된 useQuery에 해당하는 data는 새로운 데이터로 업데이트됩니다.  </li>
</ol>
<br>

<p>data를 변경하는 useMutation 함수들이 호출 될 때, <strong>어떤 get 요청을 무효화시켜줘야 하는지</strong>, <strong>get 요청에 해당되는 쿼리 키는 무엇인지</strong> 팀 기술 문서에 표로 정리했습니다.</p>
<p>이를 통해 data를 받아오는 요청과 업데이트 하는 요청 간의 관계를 한 눈에 파악할 수 있었습니다.  </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/d913bb3a-42c0-4a0b-9ab4-f6465482d475/image.png" alt=""></p>
<p>이러한 과정을 통해, 특정 data에 접근 시 <strong>무조건 서버로부터 data를 받아오는 것이 아니라, data가 변경이 되었을 때만</strong> 서버에서 새로운 data를 받아옴으로써 불필요한 API 호출을 줄였습니다. </p>
<br>

<h3 id="예시---내-계획-페이지">예시 - 내 계획 페이지</h3>
<p>위의 과정을 실제로 페이지 내에서 어떻게 적용했는지 살펴보겠습니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/8f7da569-e8b6-4213-a3ff-b8c6e61ac9eb/image.png" alt=""></p>
<p>위 페이지는 <strong>내 계획 페이지</strong>입니다. 이 페이지에서는 본인이 생성한 계획들을 보여주는데, 이 data는 본인이 계획을 생성하거나 삭제하기 전까지는 변경될 가능성이 없습니다. </p>
<p>따라서, staleTime을 기본적으로 <code>infinity</code>로 설정해주고, 계획 생성 API와 계획 삭제 API가 호출될 경우에만 쿼리를 무효화해주었습니다. </p>
<p>자세한 과정은 다음과 같습니다. </p>
<ol>
<li><p>내 계획을 불러오는 get 요청에 대한 useQuery의 <strong>staleTime</strong>을 <code>infinity</code>로 설정합니다. </p>
<pre><code class="language-ts">export const useGetMyPlansQuery = () =&gt; {
  const { data } = useSuspenseQuery({
    queryKey: [QUERY_KEY.MY_PLANS],
    queryFn: getMyPlans,
    staleTime: Infinity,
  });

  return { myPlans: data! };
};</code></pre>
</li>
<li><p>계획을 생성했을 때, 즉 계획을 생성하는 mutation 함수의 요청이 성공했을 때, 계획을 불러오는 query를 무효화합니다. </p>
<pre><code class="language-ts">export const usePostNewPlanMutation = () =&gt; {
  const queryClient = useQueryClient

  return useMutation({
    mutationFn: postNewPlan,
    onSuccess: () =&gt; {
      queryClient.invalidateQueries({
        queryKey: [QUERY_KEY.MY_PLANS],
      });
    },
  });
};</code></pre>
<blockquote>
<pre><code class="language-ts">queryClient.invalidateQueries({
  queryKey: [QUERY_KEY.MY_PLANS],
});</code></pre>
<p>이 부분에서 쿼리키를 기반으로 특정 쿼리만 무효화시켜주었음을 확인할 수 있습니다. </p>
</blockquote>
</li>
<li><p>이를 통해 <strong>내 계획 data</strong>를 페이지에 접근할 때마다 서버로부터 받아오는 것이 아니라 내 계획 data가 바뀌는 상황인 <strong>계획 생성과 계획 삭제에 성공했을 때만</strong> 새로 서버에서 data를 받아왔습니다.</p>
</li>
</ol>
<blockquote>
<p>Tanstack-query의 staleTime 및 쿼리 무효화 기능에 대한 자세한 설명 및 원리는 따로 <a href="https://velog.io/@minw0_o/tanstack-query-staleTime-invalidQueries%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-data-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC">TanStack-query staleTime &amp; invalidQueries를 이용한 data 상태관리</a>에 상세하게 정리해두었습니다. </p>
</blockquote>
<hr>
<h2 id="📌-토글-버튼에-디바운스-처리-적용">📌 토글 버튼에 디바운스 처리 적용</h2>
<p>저희 서비스에는 계획 공개 여부, 알림 여부 등의 상태를 toggle 할 수 있는 토글 버튼이 여러 페이지에 존재합니다. 아래 그림에서 파란색으로 감싸져있는 버튼들이 모두 토글 버튼입니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/d396f0ac-14d4-46a4-b3d8-d27d36f069f2/image.png" alt=""></p>
<p>이러한 토글 버튼의 특성 상, 유저는 버튼을 연속으로 누를 수 있습니다. 만약 유저가 짧은 시간 동안 버튼을 굉장히 여러 번 누른다면 이는 그 횟수 만큼의 API 호출을 의미할 것입니다. 그래서 토글 버튼을 눌렀을 때 발생하는 API 호출에 대해 <strong>디바운스</strong>를 적용해 위와 같은 상황을 방지했습니다. </p>
<blockquote>
<p>디바운스란 ?</p>
<p>이벤트를 그룹화하여 특정 시간이 지난 후 <strong>하나의 이벤트만 발생하도록 하는 기술</strong>입니다. </p>
</blockquote>
<p>만약 유저가 짧은 시간 동안 특정 버튼을 연속해서 누른다면, 여러 번의 요청 중 마지막 요청만 서버에 전달하도록 했습니다. 유저가 자주 누를 가능성이 있는 <code>계획 공개 버튼</code>, <code>리마이드 알림 여부 변경 버튼</code>의 토글 버튼에 디바운스 처리를 해줌으로써 잠재적인 API 호출 횟수를 줄일 수 있었습니다.  </p>
<hr>
<h2 id="📌-결론">📌 결론</h2>
<p><strong>불필요한 API 호출을 줄여보자!</strong> 라는 생각으로 여러가지 방법들을 고민해보았고, 결론적으로는 다음과 같은 방법들을 적용했습니다. </p>
<ol>
<li><p>Next.js middleware 기능을 이용한 페이지 요청 리다이렉트 </p>
</li>
<li><p>Tanstack-query의 staleTime 설정 및 쿼리 무효화</p>
</li>
<li><p>토글 버튼에 디바운스 처리 적용 </p>
</li>
</ol>
<p>앞으로도 API 호출을 더 효율적으로 할 수 있는 방법들에 대해 고민해보고, 현재 저희 서비스에 적용할 수 있는 상황이 된다면 적용하고 또 글로 정리해보려고 합니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🔥페이지 내 state와 prop이 너무 많다면?]]></title>
            <link>https://velog.io/@minw0_o/%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%82%B4-state%EC%99%80-prop%EC%9D%B4-%EB%84%88%EB%AC%B4-%EB%A7%8E%EB%8B%A4%EB%A9%B4</link>
            <guid>https://velog.io/@minw0_o/%ED%8E%98%EC%9D%B4%EC%A7%80-%EB%82%B4-state%EC%99%80-prop%EC%9D%B4-%EB%84%88%EB%AC%B4-%EB%A7%8E%EB%8B%A4%EB%A9%B4</guid>
            <pubDate>Tue, 12 Dec 2023 02:14:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="">올해도 아좌좌 프로젝트</a>를 진행하며 하나의 페이지 내에서 관리되는 state와 하위 컴포넌트로 전달되는 prop이 너무 많아지는 문제를 경험했고, 이를 해결하는 과정을 담은 글입니다. </p>
</blockquote>
<hr>
<h2 id="📌-하나의-페이지-내-여러-개의-state">📌 하나의 페이지 내 여러 개의 state</h2>
<p>저는 <a href="">올해도 아좌좌 프로젝트</a>에서 <strong>계획 작성 페이지</strong>의 구현을 담당했습니다.  </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/bd7e72db-e451-498e-bd1c-5916b7e93638/image.png" alt=""></p>
<p>다음과 같은 페이지인데, 한 눈에 보기에도 페이지 내에서 관리되는 data들이 많아 보입니다😅</p>
<p>계획 작성 페이지의 역할은 다음과 같습니다.  </p>
<ul>
<li>사용자로부터 계획 작성에 필요한 data를 입력 받습니다. </li>
<li>계획 생성 버튼을 눌렀을 때 계획 작성 API를 호출해 계획을 생성합니다. </li>
</ul>
<p>페이지 level에서 계획 작성 API를 호출하기 위해서는 결국 모든 data들이 한 번에 request body에 담겨져 서버에 전송이 되어야 하므로, <strong>계획 작성에 필요한 모든 data는 페이지 level에서 정의하고, 하위 컴포넌트에 prop으로 넘겨주는 방식</strong>으로 구현하기로 결정했습니다. </p>
<p>이렇게 결정한 후, 페이지 내 각 컴포넌트를 나눠보니 페이지 내 가장 하위 컴포넌트에 해당하는 <code>RemindInput</code>에 state가 전달되기까지 총 4개의 컴포넌트를 거쳐야 하는 <strong><em>prop drilling</em></strong> 현상이 발생했습니다. </p>
<p>아래 그림에서 파란색 직사각형으로 표시된 컴포넌트가 최하위 컴포넌트인 <code>RemindInput</code>입니다.
<img src="https://velog.velcdn.com/images/minw0_o/post/e27d6cd9-a280-44cc-be7a-4b5cc58e35f0/image.png" alt=""></p>
<p>사실 prop의 전달이 4개의 컴포넌트를 거치는 것이 depth가 엄청 깊은 편은 아니라고 생각했지만, 하위로 전달되어야 하는 모든 data와 메서드들이 CreatePage에서 관리되었기 때문에 그 수가 너무 많았습니다. </p>
<pre><code class="language-tsx">export default function CreatePage() {
  const router 
  const [title, setTitle] = useState
  const [description, setDescription] = useState(&#39;&#39;);
  const [tags, setTags] = useState&lt;string[]&gt;([]);
  const [isPublic, setPublic] = useState(true);
  const toggleIsPublic = () =&gt; {
    setPublic(!isPublic);
  };

  const [remindOptions, setRemindOptions] = useState&lt;RemindOptionType&gt;({
    TotalPeriod: 12,
    Term: 1,
    Date: 1,
    Time: 9,
  });

  const [remindMessageList, setRemindMessageList] = useState&lt;RemindItemType[]&gt;(
    [],
  );

  const handleChangeRemindOption = () =&gt; {};
  const handleChangeRemindMessage = () =&gt; {};
  const fixRemindOptions = () =&gt; {};
  const makeAllRemindMessageSame = () =&gt; {};    </code></pre>
<p>작성 페이지에서 관리되어야 하는 state와 이 state를 다루는 메서드들은 대략 위와 같았습니다. 이렇게 많은 state에 대한 prop drilling을 통해 다음과 같은 문제들이 발생했습니다. </p>
<ul>
<li>길어지는 코드로 인해 코드의 <strong>가독성</strong>이 매우 나빠집니다. </li>
<li>중간 컴포넌트에 <strong>불필요한 프로퍼티가 전달</strong>되어 불필요한 복잡성을 초래할 수 있습니다.</li>
<li>state 변경 시 props 전달 과정에 관여된 컴포넌트들에게 <strong>불필요한 리렌더링</strong>이 발생할 수 있습니다.</li>
</ul>
<p>그래서 위와 같은 문제점들을 인지한 후, 좀 더 효율적인 방법으로 페이지 내의 data들을 관리할 수 없을까 고민을 시작했습니다. </p>
<hr>
<h2 id="📌-recoil-contextapi">📌 Recoil? ContextAPI?</h2>
<p>prop으로 상태를 하위에 전달하는 방법의 문제점들을 해결하기 위해, Recoil과 ContextAPI의 사용을 고려하게 되었습니다. </p>
<p>상태 관리 도구인 Recoil과 ContextAPI 모두, state를 하위 컴포넌트에 prop으로 전달하는 방식이 아니라 <strong>원하는 위치에서 data를 불러와 사용</strong>할 수 있기 때문에 위에서 언급한 코드의 가독성 문제와 불필요한 프로퍼티 전달 문제 모두 해결할 수 있는 것처럼 보였습니다. </p>
<p>또한, Recoil을 사용했을 때에도 여전히 발생할 수 있는 <strong>불필요한 렌더링 문제</strong>를 막기 위해 <strong>atom 객체에 각 속성에 대한 selector의 get과 set을 정의</strong>하는 방법을 생각해 팀원분에게 이를 제안하기도 했습니다. </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/7c15ee48-bc0f-43f5-a07a-2440440f2e6f/image.png" alt=""></p>
<p>하지만, prop으로 data를 전달하는 방식의 문제점을 해결해주는 Recoil과 ContextAPI를 사용했을 때에도, 새로운 문제가 있었습니다. </p>
<blockquote>
<p>💡 컴포넌트에서 전역 상태를 구독하는 순간, 그 컴포넌트는 재사용이 어려워진다. </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/d022f311-7672-4c0b-8b79-efab128ba02e/image.png" alt=""></p>
<p>위의 그림에서 보듯이 왼쪽의 <strong>계획 작성 페이지</strong>와 오른쪽의 <strong>계획 수정 페이지</strong>는 한 눈에 보기에도 그 구조가 상당히 유사합니다. 그래서 이 두개의 페이지에서는 공통으로 사용되는 <code>WritableRemind</code> 컴포넌트와 <code>WritableRemindItem</code> 컴포넌트가 있습니다. </p>
<p>하지만 작성 페이지와 수정 페이지는 서로 역할이 다른 페이지이고, 호출되어야 하는 API가 다릅니다. 따라서, 필요한 data의 구조와 성격이 조금씩 다릅니다. 그렇기 때문에 만약 Recoil을 사용한다면, 각각 서로 다른 atom을 구독해야하고 이렇게 되면 <strong>두 페이지에서 공통으로 쓰이는 컴포넌트는 각 페이지에서 사용되는 서로 다른 두 개의 atom을 구독하고 있어야 하나,,?</strong> 라는 생각이 들었습니다. 이렇게 되면 그 컴포넌트가 다루어야 하는 data의 수도 많아질 것이고, 자칫하면 컴포넌트를 분리해야 할 수도 있습니다. </p>
<p>prop으로 data를 전달하는 방식의 문제를 해결하기 위해 전역 상태 관리를 사용하려고 했지만, 특정 컴포넌트가 전역 상태를 구독하는 순간 그 컴포넌트의 재사용성이 줄어드는 문제가 예상되었고, 이는 적절한 해결방법이 아닌 것 같아 도입하지 않았습니다. </p>
<hr>
<h2 id="📌-prop-방식-사용-but-리액트-훅-최적화하기">📌 prop 방식 사용, but 리액트 훅 최적화하기!</h2>
<p>문제를 해결하기 위한 다른 방법들도 생각해보았습니다. </p>
<ul>
<li>React-hook-form 사용</li>
<li>페이지의 분리 =&gt; 계획 작성을 여러 단계로 </li>
</ul>
<p>구현 중이었던 계획 작성 페이지는 사실상 사용자로부터 data를 입력받아, 이를 조합해 서버로 전송하는 폼의 역할을 했기 때문에 <strong>React-hook-form</strong>이라는 라이브러리를 사용하면 좀 더 쉽게 상태 관리를 할 수 있지 않을까라는 생각도 들었습니다. </p>
<p>또한, 하나의 페이지 안에 애초부터 너무 많은 data와 로직이 담기도록 설계한 것은 아닌가 하는 생각도 들어서 <strong>페이지를 분리</strong>해 계획 작성 단계를 분리하는 방향도 생각해보았습니다.  </p>
<blockquote>
<p><strong>하지만 결론적으로는, 두 방식 모두 도입하지 않았습니다.</strong> </p>
</blockquote>
<p>그 이유는, 두 가지 방법 모두 &quot;시간&quot; 이라는 추가적 비용이 상대적으로 많이 발생하는 방법이었고 <strong>프로젝트 완성</strong>이라는 목표를 생각했을 때, 좋은 해결 방법이 아니었기 때문입니다. </p>
<p>두 가지 방법 모두 기술적으로는 더 뛰어난 선택이었을 수도 있지만 당시 저는 마감 기간이 정해져 있는 프로젝트를 팀원들과 함께 진행 중이었고, 프로젝트의 가장 최우선 목표는 기술적으로 뛰어난 구현이 아니라, <strong>마감 기간 내의 프로젝트 완성</strong>이었습니다. </p>
<p>그래서 결국 저는 기존처럼 prop으로 data를 하위로 전달하는 방식으로 state를 관리하되, 불필요한 렌더링이 발생하지 않도록 <strong>리액트 훅 최적화</strong>를 진행하고 <strong>프로젝트 완성 이후 페이지를 분리</strong>하는 방식으로 진행하기로 결정했습니다.</p>
<br>

<p>리액트 훅을 최적화했던 예시는 다음과 같습니다. </p>
<h3 id="usememo">useMemo()</h3>
<pre><code class="language-ts">const filteredTermOptions = useMemo(() =&gt; {
  return TERM_OPTIONS.filter(
    (option) =&gt; option.value &lt;= remindOptions.TotalPeriod,
  );
}, [remindOptions.TotalPeriod]);</code></pre>
<pre><code class="language-js">const isRemindMessageEmpty = useMemo(() =&gt; {
  return remindMessage.length === 0;
}, [remindMessage]);</code></pre>
<p>다음과 같이 렌더링 시마다 재선언 될 필요가 없는 변수는 특정 data가 변할 때만 재선언 될 수 있도록 해주었습니다.     </p>
<h3 id="reactmemo">React.Memo()</h3>
<pre><code class="language-ts">export default React.memo(function WritableRemindItem({
  remindMonth,
  remindDay,
  remindMessage,
  handleChangeRemindMessage,
  makeAllRemindMessageSame,
  classNameList = [],
}: WritableRemindItemProps) {
// 이하 생략</code></pre>
<p>작성 페이지에서는 prop으로 data가 전달되는 depth가 최대 4번까지 있었기에, 자식 컴포넌트가 불필요하게 리렌더링 될 수 있는 상황이 있었습니다. </p>
<p>그래서 작성 페이지 컴포넌트나 상위 컴포넌트의 상태가 변경되어 리렌더링 될 때, 하위 컴포넌트의 상태나 prop이 변경되지 않았다면 하위 컴포넌트는 불필요한 리렌더링이 일어나지 않도록 <code>React.memo()</code>를 사용해주었습니다. </p>
<h3 id="usecallback">useCallback()</h3>
<pre><code class="language-ts">const handleChangeRemindOption = useCallback(
  (optionKey: string, newOptionValue: number) =&gt; {
    setRemindOptions
      ...remindOptions,
      [optionKey]: newOptionValue,
    });
  },
  [remindOptions, setRemindOptions],
);</code></pre>
<p>리액트에서 컴포넌트가 리렌더링 될 때, 컴포넌트 내 정의된 함수도 재정의되기 때문에 만약 이 함수가 자식 컴포넌트에게 전달된다면 자식 컴포넌트로 전달되는 prop이 변하는 것이기 때문에 자식 컴포넌트도 리렌더링 될 것입니다.</p>
<p>위의 <code>handleChangeRemindOption</code> 메서드는 하위 컴포넌트로 전달되는 함수였기에, 렌더링 될 때마다 하위 컴포넌트가 계속 리렌더링 될 것입니다.  </p>
<p>그래서 <code>useCallback</code>을 사용해 연관된 data가 변하지 않는다면 함수를 재정의하지 않도록 해주어 하위 컴포넌트의 불필요한 리렌더링을 방지했습니다. </p>
<hr>
<h2 id="📌-결론">📌 결론</h2>
<p>위의 글을 정리해보자면, 다음과 같습니다. </p>
<ol>
<li>페이지 내에서 관리해야 할 state와 prop이 너무 많다는 문제를 마주했습니다. </li>
<li>이를 해결하기 위해 여러가지 방법을 고려해보았습니다. <ul>
<li>Recoil</li>
<li>ContextAPI</li>
<li>React-hook-form</li>
<li>페이지의 분리 </li>
</ul>
</li>
<li>페이지에서 state와 prop으로 data를 관리하되, 리액트 훅을 최적화하는 방법으로 문제를 해결했습니다. </li>
</ol>
<blockquote>
<p>앞으로도 개발을 하며 크고 작은 다양한 문제들을 만날텐데, 그 때마다 이를 해결하기 위한 여러 가지 방법들의 장점과 비용을 계산해보고, 이를 바탕으로 근거 있고 합리적인 선택을 하는 개발자가 되려고 합니다!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[리액트 공통 컴포넌트의 책임에 대해 고민하기]]></title>
            <link>https://velog.io/@minw0_o/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B3%B5%ED%86%B5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%97%AD%ED%95%A0%EA%B3%BC-%EC%B1%85%EC%9E%84%EC%97%90-%EB%8C%80%ED%95%B4-%EA%B3%A0%EB%AF%BC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@minw0_o/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B3%B5%ED%86%B5-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%9D%98-%EC%97%AD%ED%95%A0%EA%B3%BC-%EC%B1%85%EC%9E%84%EC%97%90-%EB%8C%80%ED%95%B4-%EA%B3%A0%EB%AF%BC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Sat, 09 Dec 2023 05:25:20 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p><a href="">올해도 아좌좌 프로젝트</a> 초기에 공통 컴포넌트를 설계하며 컴포넌트의 책임에 대해서 고민해보았고, 그 생각의 과정을 담은 글입니다. </p>
</blockquote>
<hr>
<h2 id="📌-공통-컴포넌트의-책임">📌 공통 컴포넌트의 책임</h2>
<p>일반적으로 공통 컴포넌트는, 다양한 페이지와 컴포넌트에서 사용할 수 있는 가장 기본적인 단위의 컴포넌트를 말합니다. 저는 이번 프로젝트에서 <code>Icon</code>, <code>Dropdown</code>, <code>Input</code>, <code>CircleProgressBar</code> 등의 공통 컴포넌트를 담당했습니다.  </p>
<p>가장 작은 단위의 컴포넌트인 공통 컴포넌트를 만들면서, <strong>공통 컴포넌트의 책임을 어디까지로 정의해야할 지</strong> 고민되기 시작했습니다. </p>
<p>만약 컴포넌트에 필요한 data가 있다면 </p>
<ul>
<li>이를 컴포넌트 내에서 <strong>컴포넌트 자체의 state</strong>로 정의해야 할지</li>
<li><strong>외부에서 prop으로 넘겨받아 사용해야 할지</strong></li>
</ul>
<p>를 결정해야 했습니다. </p>
<p>예를 들어 사용자로부터 text를 입력받는 Input 컴포넌트를 만든다면, 다음과 같이 고민할 수 있습니다. </p>
<h4 id="1-input-컴포넌트-내-자체-state를-정의-o">1. Input 컴포넌트 내 자체 state를 정의 O</h4>
<ul>
<li>외부에서 text를 나타내는 <strong>state</strong>와 이 state의 값을 변경하는 <strong>setState</strong> 메서드를 prop으로 전달받습니다. </li>
<li>전달받은 state값을 초기값으로 Input 컴포넌트 내 자체 state(앞으로 편의상 <strong>state_input</strong>로 지칭)를 정의하고, 이를 Input 컴포넌트 내 input 태그와 연결해 사용자가 입력하는 text가 바뀔 때마다 state_input이 업데이트 되도록 해줍니다.</li>
<li>state_input이 업데이트 될 때마다 setState함수를 실행해 컴포넌트 외부의 state도 동기화되도록 해줍니다.</li>
</ul>
<h4 id="2-input-컴포넌트-내-자체-state-정의-x">2. Input 컴포넌트 내 자체 state 정의 X</h4>
<ul>
<li>외부에서 text를 나타내는 <strong>state</strong>와 이 state의 값을 변경하는 <strong>setState</strong> 메서드를 prop으로 전달받습니다. </li>
<li>input 태그에 입력되는 사용자 입력이 변경될 때마다, setState를 실행해 외부의 state를 업데이트하고, 이 값이 다시 Input 컴포넌트에 prop으로 전달되면서 Input 컴포넌트가 전달받는 state값이 변경됩니다.</li>
</ul>
<p>이렇게 공통 컴포넌트가 자체 state를 가져야 할지의 고민으로부터 시작해 컴포넌트의 책임에 대해서 여러 글들을 찾아보며 고민해보았습니다. </p>
<p>그 결과 이는 상황에 따라 다르다 (Case By Case😭) 라는 결론을 내렸습니다. 결국 상황과 조건에 따라 판단해야한다는 뜻인데, <strong>이를 판단할 때 어떠한 점들을 고려할 수 있는지</strong>에 대해서 알게 되었습니다. </p>
<hr>
<h2 id="📌-역할과-중복">📌 역할과 중복</h2>
<h3 id="1-역할">1. 역할</h3>
<p>첫 번째로, 컴포넌트의 역할입니다. </p>
<p>모든 컴포넌트는 그 종류와 범위는 다를 수 있어도 각자 <strong>고유한 역할</strong>이 있어야합니다.</p>
<p>이번에는 toggleButton 컴포넌트를 예시로 들어보겠습니다.</p>
<img src="https://velog.velcdn.com/images/minw0_o/post/eb16fc83-d8c5-4269-8a27-876e3a8e48b2/image.png" width="400">

<p>toggleButton에 대해서 생각하면 대부분 위와 같은 이미지를 떠올리실 겁니다.  </p>
<p>이러한 이미지에서도 직관적으로 알 수 있듯이 toggleButton이란 말 그대로 토글을 해주는 역할을 하는 버튼입니다. 이는 즉, <strong>특정 값을 계속 switch 해주는 동작</strong>을 해야한다고 생각할 수 있습니다.  </p>
<p>이렇게 컴포넌트의 <strong>고유한 역할</strong>을 생각해보았을 때, 값을 토글하는 <code>EX) setState(!state)</code> 같은 로직은 토글 버튼의 고유한 기능이므로 컴포넌트 안에 정의되어야 하는 것이 바람직하다고 생각할 수 있습니다. </p>
<p>즉, toggleButton의 고유한 역할인 <strong>값을 toggle 한다</strong>라는 측면에서 생각해보았을 때 toggleButton 컴포넌트는 자체 state를 가지는 것이 자연스럽다고 판단할 수 있습니다. </p>
<h3 id="2-중복">2. 중복</h3>
<p>두 번째로는 중복입니다. </p>
<p>위에서 말했듯이 toggleButton은 특정 값을 toggle 해주는 역할을 담당할 것이고, 이 로직을 간단히 코드로 나타내면 다음과 비슷할 것입니다. </p>
<pre><code class="language-js">const toggleState = (state, setState) =&gt; {
    setState(!state);
}</code></pre>
<p>만약 toggleButton 내부에 위와 같은 로직을 작성하지 않고, 컴포넌트 외부에서 이를 작성해서 넣어준다면, toggleButton을 사용하는 모든 곳에서 위와 같은 <code>toggleState</code> 함수를 정의해서 넣어줘야 할 것입니다. </p>
<p>이를 중복의 측면에서 생각해보면, toggleButton이 사용되는 페이지가 많아질 수록 위 로직을 계속 정의해줘야 할 것이고, 결국 이는 불필요한 중복이라고 생각할 수 있습니다. </p>
<p>따라서 이러한 경우에는 toggleButton 내에 자체 <code>state</code>를 정의하고 그 <code>state</code>를 이용해 toggle 하는 로직까지 정의하는게 적절하다고 생각했습니다. </p>
<p>또한, 실제로 toggle 하려고 하는 외부의 state를 변경하는 <code>setState</code> 메서드만 외부에서 toggleButton 컴포넌트로 넘겨주고 필요할 때마다 이를 호출해 실제 state와 컴포넌트 내부의 state를 동기화 시켜줄 수도 있을 것입니다. </p>
<p>=&gt; 이렇게 컴포넌트의 고유한 역할에 대한 로직을 내부에 정의함으로써 <strong>외부로 노출할 필요가 없는 부분은 캡슐화가 되는 효과</strong> 또한 얻을 수 있습니다. </p>
<hr>
<h2 id="📌-컴포넌트-정의-예시---debouceswitchbutton">📌 컴포넌트 정의 예시 - DebouceSwitchButton</h2>
<p>그러면 위와 같이 컴포넌트의 역할과 중복을 고려해 실제 프로젝트에서 공통 컴포넌트를 어떻게 구현했는지 보여드리겠습니다. </p>
<h3 id="debounceswitchbutton">DebounceSwitchButton</h3>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/a30a4494-0526-43ba-91fc-c03e7cdef716/image.gif" alt=""></p>
<p>위에 보이는 버튼이 DebounceSwitchButton 입니다. toggle 버튼과 비슷하게 보이지만, 단순히 값을 toggle 하는 기능과 더불어 다른 책임들도 가지고 있습니다. </p>
<ul>
<li>클릭될 때마다 컴포넌트 자체의 상태는 계속 toggle 되어 사용자 입장에서는 값이 바뀐 것처럼 보여야 합니다. </li>
<li>실제 서버 상태의 toggle을 일으키는 api 호출은 디바운스를 이용해 처리하고, 그 처리 자체도 원래 기존값과 다를 때만 호출해줍니다. (<strong>값이 toggle되기 때문에 컴포넌트의 상태가 변하지 않는다면 서버의 상태도 변하지 않아야 하므로</strong>)<blockquote>
<p>예를 들어, 사용자가 빠르게 두번 클릭했을 때는 사용자 입장에서는 값이 바뀌었다가 다시 원래대로 돌아온 것 처럼 느껴야하므로 클라이언트의 상태를 2번 toggle 해줍니다. 하지만, 디바운스를 적용한 서버 api 호출은, 결국은 2번 toggle 되어 값이 바뀌지 않았으므로 호출해줄 필요가 없습니다.  </p>
</blockquote>
</li>
</ul>
<p>이러한 책임을 가진 DebounceSwichButton은 값을 toggle 해주는 자신의 고유한 역할도 담당할 뿐 아니라, debounce를 적용한 서버 api 호출을 통해 api 호출을 최소화하는 역할도 담당합니다. 실제로 위 동영상을 통해 빠르게 버튼을 2번 클릭했을 때는, 서버 api를 호출하는 콘솔이 출력되지 않는 것을 확인할 수 있습니다. </p>
<h3 id="구현-방법">구현 방법</h3>
<p>DebounceSwichButton을 코드 상에서 어떻게 구현했는지 살펴보겠습니다. </p>
<h4 id="props">props</h4>
<pre><code class="language-ts">interface DebounceSwitchButtonProps {
  defaultIsOn: boolean; // 실제로 toggle 하려는 상태의 초기값  
  submitToggleAPI: () =&gt; void; // 실제 서버의 값을 toggle하는 API 함수  
  toggleName: ToggleName; // toogleButton 사용 용도 
}</code></pre>
<p>DebounceSwichButton은 다음과 같은 props를 외부에서 전달받습니다. </p>
<ul>
<li><code>defaultIsOn</code> : toggle하려는 서버 상태의 초기값</li>
<li><code>submitToggleAPI()</code> : 실제 서버 상태의 값을 toggle하는 API 통신 함수  </li>
<li><code>toggleName</code> : toggleButton의 사용 용도를 나타내는 string literal 값 </li>
</ul>
<blockquote>
<p>toggle 상태의 초기값과 toggle API 통신 함수는 컴포넌트 호출 시 매번 달라져야하는 값이므로 <code>defaultIsOn</code>과 <code>submitToggleAPI</code>는 props로 정의해 외부에서 전달받도록 했습니다. 또한, <code>toggleName</code>도 외부에서 전달받도록 하고, 컴포넌트 내부에서 <code>toggleName</code>에 따른 분기처리를 통해 렌더링 해줄 버튼의 text와 icon 등을 결정했습니다.
=&gt; 즉, 위와 같은 3개의 값 모두 컴포넌트 자체에서 판단할 수 있는 data가 아니고, 호출 시마다 달라져야 하는 값이기 때문에 컴포넌트의 고유한 역할에서 제외된다고 생각해 prop으로 정의했습니다. </p>
</blockquote>
<h4 id="state">state</h4>
<pre><code class="language-ts">const [isOn, setIsOn] = useState(defaultIsOn); // 컴포넌트에서 toggle 되는 state
const [originalIsOn, setOriginalIsOn] = useState(defaultIsOn); // 

const toggleIsOn = () =&gt; { // 컴포넌트 자체 상태 toggle 함수
  setIsOn(!isOn);
};

const submitIfReallyChanged = () =&gt; {
  if (isOn !== originalIsOn) {
    submitToggleAPI();
    setOriginalIsOn(isOn);
  }
};

useDebounce(submitIfReallyChanged, 500, [isOn]); // 디바운스 적용</code></pre>
<ul>
<li><code>[isOn, setIsOn]</code> : <code>defaultIsOn</code>을 초기값으로 하는 컴포넌트에서 toggle 되는 컴포넌트 자체 state </li>
<li><code>[originalIsOn, setOriginalIsOn]</code> : 값이 여러번 toggle 되었을 때 값이 실제로 변경되었을 때만 서버 api를 호출해주기 위해 <code>isOn</code>과 비교할 state </li>
<li><code>toggleIsOn()</code> : 컴포넌트 자체 상태를 toggle 함수</li>
<li><code>submitIfReallyChanged()</code> : 값이 여러 번 toggle 되었을 때 값이 실제로 변경되었을 때만 서버 api를 호출해주는 함수 </li>
<li><code>useDebounce()</code> : 컴포넌트 내부에서 디바운스 적용 </li>
</ul>
<blockquote>
<p>컴포넌트 자체 toggle state, toggle 함수, 값이 여러번 toggle 되었을 때 값이 실제로 변경되었을 때만 서버 api를 호출해주는 함수, 디바운스 등은 컴포넌트 내부에서 정의해주었습니다. 
이러한 값들은 외부에서 어떤 초기값, 서버 API 통신 함수를 전달받더라도, _<strong>동일하게 사용될 수 있는 data</strong>_들이기 때문에 DebounceSwitchButton 컴포넌트의 <strong>고유한 역할</strong>이 될 수 있고, <strong>중복 측면</strong>에서도 컴포넌트 내부에 정의하는 것이 좋겠다고 생각했습니다. </p>
</blockquote>
<h3 id="호출-예시-및-전체-코드">호출 예시 및 전체 코드</h3>
<p>다음은 실제로 DebounceSwitchButton 컴포넌트를 호출하는 예시 및 전체 코드입니다.</p>
<h4 id="호출-예시">호출 예시</h4>
<pre><code class="language-tsx"> &lt;DebounceSwitchButton
        defaultIsOn={serverIsOn} // toggle하려는 서버 상태의 기본값이 on or off
        submitToggleAPI={submitToggleAPI} 
        toggleName=&quot;remind&quot;
      /&gt;</code></pre>
<h4 id="전체-코드">전체 코드</h4>
<pre><code class="language-tsx">
type ToggleName = &#39;public&#39; | &#39;ajaja&#39; | &#39;remind&#39;;

const toggleText = {
  public: {
    on: &#39;계획 공개&#39;,
    off: &#39;계획 비공개&#39;,
  },
  ajaja: {
    on: &#39;월요일 18:00 마다\n응원 메세지 알림 활성화&#39;,
    off: &#39;응원 메세지 알림 비활성화&#39;,
  },
  remind: {
    on: &#39;리마인드 알림 활성화&#39;,
    off: &#39;리마인드 알림 비활성화&#39;,
  },
};

interface DebounceSwitchButtonProps {
  defaultIsOn: boolean;
  submitToggleAPI: () =&gt; void;
  toggleName: ToggleName;
}

export default function DebounceSwitchButton({
  defaultIsOn,
  submitToggleAPI,
  toggleName,
}: DebounceSwitchButtonProps) {
  const [isOn, setIsOn] = useState(defaultIsOn);
  const [originalIsOn, setOriginalIsOn] = useState(defaultIsOn);

  const toggleIsOn = () =&gt; {
    setIsOn(!isOn);
  };

  const submitIfReallyChanged = () =&gt; {
    if (isOn !== originalIsOn) {
      submitToggleAPI();
      setOriginalIsOn(isOn);
    }
  };

  useDebounce(submitIfReallyChanged, 500, [isOn]);

  return (
    &lt;div className={classNames(&#39;debounce-switch&#39;)}&gt;
      &lt;IconSwitchButton
        onIconName={toggleName === &#39;public&#39; ? &#39;PLAN_OPEN&#39; : &#39;NOTIFICATION_ON&#39;}
        offIconName={
          toggleName === &#39;public&#39; ? &#39;PLAN_CLOSE&#39; : &#39;NOTIFICATION_OFF&#39;
        }
        onClick={toggleIsOn}
        isActive={isOn}
      /&gt;
      &lt;span className={classNames(&#39;debounce-switch__text&#39;)}&gt;
        {isOn ? toggleText[toggleName].on : toggleText[toggleName].off}
      &lt;/span&gt;
    &lt;/div&gt;
  );
}</code></pre>
<hr>
<h2 id="📌-맺음말">📌 맺음말</h2>
<blockquote>
<p>공통 컴포넌트의 책임에 대해 고민하면서, 컴포넌트의 역할과 중복을 고려하려 책임을 결정할 수 있다는 사실을 알게 되었습니다. 여기서 끝나는 것이 아니라, <strong>&quot;리액트 컴포넌트를 잘 설계한다는 것은 무엇일까?&quot;</strong> 라는 고민으로 확장시켜 컴포넌트에 대해 더욱 본질적으로 공부해볼 계획입니다🔥 </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[TanStack-Query staleTime & invalidQueries를 이용한 data 상태관리]]></title>
            <link>https://velog.io/@minw0_o/tanstack-query-staleTime-invalidQueries%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-data-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@minw0_o/tanstack-query-staleTime-invalidQueries%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-data-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Tue, 05 Dec 2023 06:28:23 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>TanStack-Query에서는 useQuery를 이용해 서버의 data를 받아올 수 있고, data의 staleTime과 gcTime을 설정해줌으로써 서버 data의 상태를 관리할 수 있다. 이러한 staleTime, gcTime과  invalidQueries를 활용해 어떻게 서버 data의 상태 관리를 효율적으로 할 수 있는지 알아보자. </p>
</blockquote>
<hr>
<h2 id="📌-staletime">📌 staleTime</h2>
<ul>
<li><p>캐시된 data가 <strong>신선한 상태(fresh)</strong>로 남아있는 시간을 말한다.</p>
</li>
<li><p>특정 data에 대해 설정해준 stale time이 지나게되면, 그 data는 <strong>신선하지 않은 상태(stale)</strong>로 간주된다.  </p>
</li>
<li><p>따로 stale time의 값을 설정해주지 않으면 기본값은 <code>0</code>이다. </p>
<p>=&gt; data를 fetch 해오자마자 data를 신선하지 않다고 간주하는 것 ! </p>
</li>
<li><p>특정 쿼리 키에 대한 data를 다시 fetch 해와야 하는 상황일 때,  </p>
<ul>
<li>해당하는 data가 <strong>fresh</strong>한 상태라면, API 호출 없이 캐싱된 data가 다시 사용된다. </li>
<li>해당하는 data가 <strong>stale</strong>한 상태라면, API 호출을 통해 신선한 data를 다시 fetch해오고, 이 data를 cache에 저장한다. </li>
</ul>
</li>
</ul>
<p>=&gt; 즉, 서버로부터 새로 data를 받아오지 않고 캐시된 data를 최신의 data로 간주하고 사용하기 위해 설정해주는 조건이다. </p>
<hr>
<h2 id="📌-gctime">📌 gcTime</h2>
<ul>
<li><p>메모리에 저장된 캐싱 데이터가 유효한 시간(캐시 메모리에 남아있는 시간)을 의미한다.</p>
</li>
<li><p>쿼리를 사용하는 모든 컴포넌트가 언마운트되었을 때, 쿼리는 비활성화(inactive 상태)되고 비활성화된 데이터는 <code>gcTime</code>이 지난 후, 캐시에서 삭제된다. </p>
<blockquote>
<p>쿼리를 사용하는 컴포넌트가 언마운트 되지 않고 계속 마운트된 상태로 화면에 계속 보여진다면, cache data는 계속 남아있다. </p>
<p>=&gt; 쿼리를 사용하는 모든 컴포넌트가 <strong>언마운트 된 이후부터 <code>gcTime</code>이 지나야</strong> 캐시 데이터가 삭제되기 때문이다.</p>
</blockquote>
</li>
<li><p>캐싱 데이터가 없다면, 특정 query key에 해당하는 쿼리 호출 시, 다시 API 요청을 통해 data를 받아온다. </p>
</li>
<li><p>따로 gcTime의 값을 설정해주지 않으면 기본값은 <code>5분</code>이다. </p>
</li>
</ul>
<hr>
<h2 id="📌-staletime-gctime에-따른-data-fetch">📌 staleTime, gcTime에 따른 data fetch</h2>
<p>그렇다면 staleTime과 gcTime가 어떻게 설정되어있는지에 따라 data fetch가 어떻게 달라지는지 각 상황을 통해 알아보자. </p>
<p>특정 data를 fetch 할 때</p>
<ol>
<li><p><strong>staleTime 아직 안 지남, gcTime 아직 안 지남</strong></p>
<p>=&gt; API 요청 X, 캐시 데이터 사용</p>
</li>
<li><p><strong>staleTime 아직 안 지남, gcTime 지남</strong></p>
<p>=&gt; staleTime은 아직 지나지 않아서 cache data를 최신의 data로 간주하고 사용하고 싶은데 gcTime이 지나서 cache에 data가 없는 상황</p>
<p>=&gt; API 요청으로 새로운 data 받아와야 함 </p>
<blockquote>
<p>사실 이런 경우는 해당 쿼리가 포함된 페이지나 컴포넌트가 새로 다시 mount되는 경우일 것이다. </p>
<p>=&gt; 만약 unmount 되지 않고 계속 해당 쿼리가 활성화 상태라면 cache에 data가 계속 있는 상태였을 것이므로 </p>
<p>=&gt; unmount 되었다가 다시 mount되어 다시 data fetch 하는 경우일 듯</p>
<p>EX) </p>
<pre><code class="language-ts">export const useGetContentList = (type: string, title: string, page: number) =&gt; {
  const { data } = useQuery(
    [{ scope: &quot;ContentList&quot;, type, title, page }],
    () =&gt; {
      console.log(`fetch Data: ${type},${title}, ${page}`);
      return getContentList(type, title, page);
    },
    {
      staleTime: 1000000,
      gcTime: 100,
      select: (data) =&gt; {
        return data?.contentList;
      },
    }
  );

  return {
    contentListData: data,
  };
};</code></pre>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/2800f5aa-df38-48dd-99b1-c1fc24fdc533/image.gif" alt="">위와 같이 <code>gcTime</code>을 엄청 짧게 설정해, 쿼리가 inactive된 후 바로 삭제되도록 했을 때, 다시 해당 쿼리가 불렸을 때 다시 data를 fetch해오고 이를 cache에 추가하는 것을 확인할 수 있었다.</p>
</blockquote>
</li>
<li><p><strong>staleTime 지남, gcTime 아직 안 지남</strong></p>
<p>=&gt; API 호출로 새로운 신선한 data를 받아오는데, <strong>받아오기 전까지는 cache data를 보여줌</strong></p>
<blockquote>
<p>여기서 해당 useQuery에 대한 <strong><code>isLoading</code> 값은 계속 false</strong>이기 때문에 사용자는 loading 화면을 보고 있지 않아도 된다.</p>
</blockquote>
</li>
<li><p><strong>staleTime 지남, gcTime 지남</strong></p>
<p>=&gt; API 호출로 새로운 신선한 data를 받아오고, 받아오기 전까지는 보여줄 data 없음  </p>
</li>
</ol>
<br>

<p>위의 각 상황들을 정리해보면 !</p>
<blockquote>
<p>fetch나 refetch를 통해 data를 불러올 때</p>
<ul>
<li>staleTime 안 지나면 cache data 사용<ul>
<li>gcTime 지나서 cache data 없으면 =&gt; refetch </li>
</ul>
</li>
<li>staleTime 지나면 무조건 새로운 data 받아오는 것<ul>
<li>새로운 data 받아오는 동안, cache data가 있으면 이를 보여주다가 새로운 data로 교체해주는 것</li>
</ul>
</li>
</ul>
</blockquote>
<hr>
<h2 id="📌-invalidqueries">📌 invalidQueries</h2>
<p>tanstack query는 <strong>특정 쿼리를 무효화</strong>해주는 <code>invalidQueries</code> 메서드를 제공한다. </p>
<pre><code class="language-js">// 캐시의 모든 쿼리를 무효화함
queryClient.invalidateQueries();


// `todos`로 시작하는 쿼리키를 가지는 모든 쿼리를 무효화함
queryClient.invalidateQueries({ queryKey: [&#39;todos&#39;] });</code></pre>
<p>쿼리를 무효화한다는 것은 무슨 뜻일까 ? <code>invalidateQueries</code> 메서드를 통해 특정 쿼리를 무효화하면, 다음과 같은 두 가지 현상이 발생한다. </p>
<ul>
<li>해당 쿼리의 상태를 stale로 바꾼다. stale된 상태는 <code>useQuery</code> 또는 관련 훅에서 사용 중인 모든 <code>staleTime</code> 구성에 오버라이드한다.</li>
<li>만약 <code>useQuery</code> 나 관련 훅을 통해 쿼리가 렌더링되고 있다면 백그라운드에서도 <code>refetch</code>한다.</li>
</ul>
<p>=&gt; 그래서 <code>invalidQueries</code> 메서드를 이용해 특정 쿼리를 무효화하면, 해당 쿼리의 data를 신선하지 않은 상태로 간주해 이후 data를 fetch 할 때 캐시의 data가 아닌 서버로부터 새로운 data를 받아올 수 있도록 하는 것이다. </p>
<hr>
<h2 id="📌-querykeys">📌 queryKeys</h2>
<h4 id="querykeys-란">queryKeys 란?</h4>
<ol>
<li>React Query에서는 query keys를 기반으로 해서 <strong>쿼리 캐싱을 관리한다.</strong></li>
<li>query key는 <code>문자열</code>, <code>문자열의 배열</code> 혹은 <code>중첩된 객체(nested object)</code>로 지정 가능하다.</li>
<li>query keys는 <strong>query data에 고유</strong>하고 <strong>직렬화</strong>하여 사용해야한다.</li>
</ol>
<br>

<h4 id="querykey에-queryfunction의-변수-포함하기">queryKey에 queryFunction의 변수 포함하기</h4>
<p>공식 문서에서는 다음과 같이 쿼리 키를 지정하도록 권장한다.</p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/652fd69b-1b4a-46f7-a7f6-d2ca147deb44/image.png" alt=""></p>
<p>쿼리 키는 쿼리 function에 대한 dependencies의 역할을 한다. 쿼리 function이 의존하고 있는 변수들을 쿼리 키 배열에 넣음으로써, <strong>각 쿼리 키에 해당하는 쿼리들이 독립적으로 cache</strong>되고, 변수가 변할 때마다 각 쿼리들이 자동적으로 refetch 되도록 할 수 있다. </p>
<hr>
<h2 id="📌-쿼리가-refetch-되는-조건">📌 쿼리가 refetch 되는 조건</h2>
<ul>
<li>새로운 Query Instance가 마운트 될 때 ( 페이지를 이동했다가 왔을 경우 )</li>
<li>브라우저 화면을 이탈했다가 다시 Focus 할 때</li>
<li>네트워크가 다시 연결될 때</li>
<li>설정한 refetch interval에 의한 경우</li>
<li>queryKey에 특정 state가 포함되고,  state값이 변경될 경우</li>
</ul>
<br>

<h4 id="staletime--refetch">staleTime &amp;&amp; refetch</h4>
<p>staleTime이 <code>0</code>으로 설정되어있다면 data를 fetch 하는 즉시 <code>stale</code>한 상태로 변하게 되고, <strong><code>refetch</code>가 일어나는 조건과 일치하게 되면 데이터가 패치됩니다.</strong></p>
<blockquote>
<p>즉, staleTime이 지나 data가 stale 한 상태가 되더라도, 바로 refetch가 일어나는 건 아니다.</p>
<p>만약 그렇다면 기본 staleTime은 0인데 페이지에 그대로 있어도 data가 무한으로 계속 refetch 될 것이다. </p>
<p>=&gt; <strong>staleTime이 지나고</strong> &amp;&amp; <strong>refetch가 일어나는 조건에 해당</strong>될 때 refetch가 일어나는 것</p>
</blockquote>
<br>

<p>refetch의 전제조건은 데이터가 <code>stale</code>한 상태여야 한다.</p>
<p>⇒ 다른 페이지 갔다가 와서 새로 mount 되더라도, data가 <code>fresh</code> 하다면 refetch 하지 않는다. (ex) 작성하고 다시 홈페이지가면 업데이트 x)</p>
<p>=&gt; <strong>대신 새로고침할 때는 무조건 refetch</strong> 된다. ( refetch라고 부르는 것이 올바르지 않은 듯)</p>
<hr>
<h2 id="📌-staletime-invalidqueries를-이용한-data-상태-관리">📌 staleTime, InvalidQueries를 이용한 data 상태 관리</h2>
<blockquote>
<p> 위에서 살펴본 queryKeys,  staleTime, InvalidQueries의 개념을 서로 연결해 data의 상태를 어떻게 효율적으로 관리할 수 있는지 알아보자 ! </p>
</blockquote>
<p>특정 data가 자주 변경되지 않고, 특정 상황에서만 변경이 된다면 다음과 같은 방법으로 data를 관리할 수 있다. </p>
<ol>
<li>해당 query의 <strong>staleTime</strong>을 <code>infinity</code>로 설정</li>
<li>data가 변경되는 상황일 때, 해당 data에 해당하는 쿼리 키 기반으로 <strong>쿼리 무효화</strong> <code>invalidQueries</code> 실행</li>
<li>data가 stale 해졌으므로, 서버에서 data를 새로 받아온다. </li>
</ol>
<p>이를 통해 , 특정 페이지에 접근할 때마다 <strong>무조건 서버로부터 data를 받아오는 것이 아니라, data가 변경이 되었을 때만</strong> 서버에서 새로운 data를 받아옴으로써 불필요한 API 호출을 줄일 수 있다. </p>
<br>

<p>실제 나의 <a href="">프로젝트</a>에서 적용했던 예시를 살펴보면,</p>
<ol>
<li>내 계획을 불러오는 get 요청에 대한 useQuery의 <strong>staleTime</strong>을 <code>infinity</code>로 설정해주고</li>
</ol>
<pre><code class="language-ts">export const useGetMyPlansQuery = () =&gt; {
  const { data } = useSuspenseQuery({
    queryKey: [QUERY_KEY.MY_PLANS],
    queryFn: getMyPlans,
    staleTime: Infinity,
  });

  return { myPlans: data! };
};
</code></pre>
<ol start="2">
<li>계획을 생성했을 때, 즉 계획을 생성하는 mutation 함수가 성공했을 때, 계획을 불러오는 query를 무효화 시켜줬다. </li>
</ol>
<pre><code class="language-ts">export const usePostNewPlanMutation = () =&gt; {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: postNewPlan,
    onSuccess: () =&gt; {
      queryClient.invalidateQueries({
        queryKey: [QUERY_KEY.MY_PLANS],
      });
    },
  });
};</code></pre>
<blockquote>
<p>여기서 쿼리키를 기반으로 특정 쿼리만 무효화시켜주었음을 확인할 수 있다. </p>
<pre><code class="language-ts">queryClient.invalidateQueries({
     queryKey: [QUERY_KEY.MY_PLANS],
   });</code></pre>
</blockquote>
<ol start="3">
<li>이를 통해 <strong>내 계획 data</strong>를 페이지에 접근할 때마다 서버로부터 받아오는 것이 아니라 내 계획 data가 바뀌는 상황인 <strong>계획 생성에 성공했을 때만</strong> 새로 서버에서 data를 받아올 수 있었다. </li>
</ol>
<hr>
<h2 id="🙇🏻♂️-참고">🙇🏻‍♂️ 참고</h2>
<blockquote>
<p><a href="https://ttaerrim.tistory.com/53">리액트 쿼리의 staleTime과 cacheTime</a></p>
<p><a href="https://tanstack.com/query/v4/docs/react/guides/query-keys">공식문서 - query keys</a></p>
<p><a href="https://velog.io/@rgfdds98/React-Query-queryKeys">queryKey 작성방법 및 특징</a></p>
<p><a href="https://hjk329.github.io/react/react-query-query-invalidation/">React query : Query Invalidation</a></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[✏️ 23.10 MIL]]></title>
            <link>https://velog.io/@minw0_o/23.10-MIL</link>
            <guid>https://velog.io/@minw0_o/23.10-MIL</guid>
            <pubDate>Mon, 06 Nov 2023 13:14:27 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>10월은 중간 프로젝트를 통해 배우고 느낀 점들을 바탕으로 최종 프로젝트를 준비하고 시작하는 한 달이었다. 그 과정에서 있었던 일을 KPT 방식으로 회고해보고자 한다. </p>
</blockquote>
<hr>
<h2 id="📌-k-keep">📌 K: Keep</h2>
<h3 id="nextjs를-기술-스택으로-선택한-것">next.js를 기술 스택으로 선택한 것</h3>
<p>최종 프로젝트를 앞두고 팀원들끼리 next.js 를 사용할 지 말지에 대해 오랜 시간 고민했다. 모두가 다 한 번도 사용해보지 않았기에 사용하는 것에 대한 부담감과 두려움이 컸다. 하지만 단순히 그러한 이유 때문에 특정 기술의 사용 여부를 결정하는 것은 좋지 않다고 생각했다. </p>
<p>그래서 팀원들 모두 프로젝트 시작 전, 각자 미니 프로젝트를 진행하며 next.js를 공부하고 사용해보며 next.js를 통해 얻을 수 있는 장점과 사용하는 이유에 대해 깊게 고민해보았다.</p>
<p>나의 경우, 우선 <a href="https://velog.io/@minw0_o/SPA-vs-MPA-CSR-vs-SSR">웹 페이지 구성 방식과 렌더링 방법</a>부터 자세히 공부했고 <a href="https://velog.io/@minw0_o/Nextjs%EC%97%90%EC%84%9C%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EB%B0%A9%EC%8B%9D-CSR-vs-SSR-vs-SSG-vs-ISR">next.js가 제공하는 렌더링 방식</a>에 대해서도 공부하며 어떤 상황에서 어떤 렌더링 방식을 사용하는 것이 좋을지에 대해서 고민해보았다. </p>
<p>그래서 결론적으로는 특정 페이지와 컴포넌트에 따라 이에 맞는 렌더링 방식을 선택할 수 있는 next.js를 사용하기로 결정했다. </p>
<br>

<h3 id="부족한-부분을-공부한-것">부족한 부분을 공부한 것</h3>
<p>중간 프로젝트를 하면서 많은 것을 배웠지만, 동시에 나의 부족한 부분도 알게 되었다. 기능 구현에 급급해 특정 기술을 고민 없이 얕게만 사용했던 부분도 있다. 그래서 최종 프로젝트를 본격적으로 시작하기 전까지 부족한 부분들에 대해 학습했다. </p>
<p>러닝 타입 스크립트 책을 읽으며 공부한 내용을 챕터별로 <a href="https://velog.io/@minw0_o/series/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8">기록</a>을 남기고 있고, 중간 프로젝트 때 react- query를 사용하면서 헷갈렸던 개념인 <strong>stale-time</strong>, <strong>cache-time</strong>에 대한 내용을 시작으로 더 깊게 학습하고자 노력했고, <strong>쿼리 키</strong>에 대한 중요성도 많이 느낄 수 있었다. </p>
<p>이를 바탕으로 최종 프로젝트에는 기술을 더욱 더 깊게 이해하고 사용해보려고 노력할 것이다. </p>
<br>


<hr>
<h2 id="📌-p-problem">📌 P: Problem</h2>
<h2 id="프로젝트-진행-상황이-느린-것">프로젝트 진행 상황이 느린 것</h2>
<p>처음으로 정했던 프로젝트 주제가 <strong>불분명한 문제 상황,</strong> <strong>서비스화의 어려움</strong>, 팀원들간의 맞지 않는 관심사 등의 이유로 반려당했다. 그 주제로 와이어프레임 및 플로우차트까지 작성하고 있던 상황에서 주제를 바꿔야했고, 이 때문에 현재 프로젝트는 처음의 예상보다는 느리게 진행되고 있다.  </p>
<p>또한, React를 기반, emotion-styled 방식으로 스타일을 적용했던 중간 프로젝트와 달리 최종 프로젝트에서는 next.js 기반, SCSS 방식으로 스타일을 적용하고 있다. css-in-js인 emotion-styled 방식으로 스타일을 적용할 때는 prop을 넘겨주어 이에 맞는 스타일을 쉽게 적용할 수 있었다. </p>
<p>하지만 지금은 SCSS를 사용하면서 각 컴포넌트의 <code>class</code> 이름을 통해 특정 스타일을 적용해주고 있는데, 이 과정에서 <strong>어떤 구조가 효율적일지 고민하고 이를 서로 통일하는 과정</strong>에서 생각보다 많은 고민과 시간이 걸리는 것 같다. </p>
<hr>
<h2 id="📌-t-try">📌 T: Try</h2>
<h3 id="속도보다는-방향">속도보다는 방향</h3>
<p>위에서 언급한 Problem인 프로젝트 진행 상황이 느리다는 점과 더불어 최종 프로젝트의 볼륨이 너무 작다는 점도 사실 걱정 중 하나였다. 하지만 프로젝트 볼륨이 작은 것을 걱정하기보다는, 오히려 볼륨이 크지 않기에 코드의 품질을 높이고, 구조를 더 효율적으로 짜보고, 사용 기술에 대해 더 깊게 고민해볼 수 있지 않을까라는 생각도 든다.  </p>
<p>진행 상황이 너무 느려서도 안 되겠지만, 언젠가는 짚고 넘어가야 할 문제들에 대해서 고민하고, 이를 통일하려고 노력하는 과정이라면, 결국 지금 시간이 조금 걸리더라도 확실하게 잡고 가야할 것이라고 생각한다. </p>
<p><strong>&quot;속도보다는 방향&quot;</strong> 이라는 말이 있다. 남은 기간 동안 프로젝트를 진행하면서, 어떤 구조가 더 좋을지 고민하고, 이 기술을 왜 사용하는 지, 적절하게 사용하고 있는지 고민하고, 주어진 기간안에 서비스를 완성하는 것을 목표로 최선을 다해봐야겠다 !  </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🪄 러닝 타입스크립트 CH5 - 함수]]></title>
            <link>https://velog.io/@minw0_o/%EB%9F%AC%EB%8B%9D-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-CH5-%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@minw0_o/%EB%9F%AC%EB%8B%9D-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-CH5-%ED%95%A8%EC%88%98</guid>
            <pubDate>Thu, 26 Oct 2023 02:33:43 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-매개변수-타입">📌 매개변수 타입</h2>
<h3 id="필수-매개변수">필수 매개변수</h3>
<p>JS에서는 인수의 수와 상관없이 함수를 호출할  수 있다. 하지만 <strong>TS는 함수에 선언된 모든 매개변수가 필수라고 가정한다.</strong> 함수가 잘못된 수의 인수로 호출되면 TS는 타입 오류의 형태로 의의를 제기한다. </p>
<blockquote>
<p>함수의 인수가 잘못되면 오류는 표시하지만 실행은 되는 듯 ?</p>
</blockquote>
<br>

<h3 id="선택적-매개변수">선택적 매개변수</h3>
<p>함수에서 사용되는 모든 선택적 매개변수는 마지막 매개변수여야 한다. 필수 매개변수 전에 선택적 매개변수를 위치시키면 다음과 같이 TS 구문 오류가 발생한다. </p>
<pre><code class="language-TS">function announceSinger(singer?: string, song: string){

}</code></pre>
<br>

<h3 id="기본-매개변수">기본 매개변수</h3>
<p>JS에서 선택적 매개변수를 선언할 때 <code>=</code>와 값이 포함된 기본값을 제공할 수 있다. </p>
<p>즉, 선택적 매개변수에는 기본적으로 값이 제공되기 때문에 해당 ts 타입에는 암묵적으로 함수 내부에 <code>| undefined</code> 유니언 타입이 추가된다. </p>
<pre><code class="language-ts">function rateSong(song: string, rating = 0){
    console.log(`${song} gets ${rating}/5 stars !`;
}

rateSong(&quot;Photograph&quot;);
rateSong(&quot;Set Fire to the Rain&quot;, 5);</code></pre>
<p>다음 rateSong 함수에서 rating은 <code>number</code> 타입으로 유추되지만, 함수를 호출하는 코드에서는 선택적 <code>number | undefined</code>가 된다.</p>
<br>

<h3 id="나머지-매개변수">나머지 매개변수</h3>
<p><code>...</code> 스프레드 연산자 이용</p>
<pre><code class="language-ts">function singAllTheSongs(singer: string, ...songs: string[]){
    for(const song of songs){
        console.log(`${song}, by ${singer}`);
    }
}</code></pre>
<hr>
<h2 id="📌-반환-타입">📌 반환 타입</h2>
<p> ts는 함수가 반환할 수 있는 모든 가능한 값을 이해해 함수의 반환타입을 알려고 노력한다. </p>
<p>따라서 일반적으로 함수의 반환 타입을 명시하지 않는 것이 좋지만, 특정 상황에서는 반환 타입을 명시하는 방식이 유용할 때가 있다.</p>
<ul>
<li>가능한 반환 값이 많은 함수가 항상 동일한 타입을 반환하도록 강제</li>
<li>재귀 함수의 반환타입을 통해 타입을 유추하는 것을 거부한다. </li>
<li>수백 개 이상의 ts 파일이 있는 큰 프로젝트에서는 ts 타입 검사 속도를 높일 수 있다 .</li>
</ul>
<pre><code class="language-ts">function A(songs: string[], count=0): number {
   // ~ 
}

const A = (songs: string[], count=0): number =&gt; {

}</code></pre>
<hr>
<h2 id="📌-함수-타입">📌 함수 타입</h2>
<p>화살표 함수와 비슷하게 <code>=&gt;</code>를 이용해 함수의 타입을 표현한다. </p>
<pre><code class="language-ts">let nothingInGivesString: () =&gt; string;</code></pre>
<p>다음 변수 타입은 매개변수가 없고 string 타입을 반환하는 함수를 뜻한다. </p>
<br>

<p>함수를 매개변수로 가지는 함수 </p>
<pre><code class="language-ts">function runOnSongs(getSongAt: (index: number) =&gt; string){
    // ~ ~
}

function getSongAt(index: number){
    return `${index}th song`;
}</code></pre>
<br>

<p>다음 함수의 반환타입 : <code>string을 반환하는 함수</code> or <code>undefined</code></p>
<pre><code class="language-ts">let maybeReturnsString: (() =&gt; string) | undefined;</code></pre>
<hr>
<h2 id="📌-매개변수-타입-추론">📌 매개변수 타입 추론</h2>
<pre><code class="language-tsx">let singer: (song: string) =&gt; string;

singer = function(song) {
    // song: string 타입으로 유추된다. 
    return &#39;Singing: ${song.toUpperCase()}!&#39;;
}</code></pre>
<p>다음 singer 변수는 string 타입의 매개변수를 갖는 함수이므로 나중에 singer가 할당되는 함수 내의 song 매개변수는 string으로 예측된다.</p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/301111bc-4b6c-43f1-8395-11b271cb6d44/image.png" alt=""></p>
<hr>
<h2 id="📌-함수-타입-별칭">📌 함수 타입 별칭</h2>
<pre><code class="language-ts">type StringToNumber = (input: string) =&gt; number;
let stringToNumber: StringToNumber;</code></pre>
<p>타입 별칭은 특히 함수 타입에 유용하다. 타입 별칭을 이용하면 반복적으로 작성하는 매개변수와 반환 타입을 갖는 코드 공간을 많이 절약할 수 있다. </p>
<hr>
<h2 id="📌-void-never-반환-타입">📌 void, never 반환 타입</h2>
<h4 id="void">void</h4>
<ul>
<li><p>어떤 값도 반환하지 않는 함수. return 문이 없거나 <strong>값을 반환하지 않는 return 문</strong>(<code>return ;</code>)을 가진 함수</p>
</li>
<li><p>함수 타입 선언 시 void를 사용하면 함수에서 반환되는 모든 값은 무시된다. </p>
<p>=&gt; 사실 js 함수는 실제값이 반환되지 않으면 기본으로 모두 <code>undefined</code>를 반환하지만 <code>void</code>는 <code>undefined</code>와 동일하지 않다. </p>
<blockquote>
<ul>
<li>void : 함수의 반환 타입이 무시된다는 것을 의미한다. </li>
<li>undefined : 반환되는 리터럴 값으로, 정의되지 않은 값이 반환된다는 뜻이다.  </li>
</ul>
</blockquote>
</li>
</ul>
<pre><code class="language-ts">function returnsVoid(){
    return;
}

let lazyValue: string | undefined;

lazyValue = returnsVoid();
// lazyValue는 undefined를 반환하는 타입인데 여기에 void 타입의 값을 넣으려고 하니 오류 발생 </code></pre>
<p>=&gt; <code>undefined</code>를 포함하는 대신 void 타입의 값을 할당하려고 하면 타입 오류가 발생한다. </p>
<br>

<p>void를 반환하도록 선언된 타입 위치에 전달된 함수가 반환된 모든 값을 무시하도록 설정할 수 있다.</p>
<p>예를 들어, 배열의 내장 forEach 메서드는 void를 반환하는 콜백을 받는다. forEach에 제공되는 함수는 원하는 모든 값을 반환할 수 있다. 다음 saveRecords 함수의 <code>records.push(record)</code>는 <code>number</code>(배열의 .push()에서 반환된 값)를 반환하지만, 여전히 <code>newRecords.forEach</code>에 전달된 화살표 함수에 대한 반환값이 허용된다. </p>
<pre><code class="language-ts">const records: string[] = [];

function saveRecords(newRecords: string[]){
    newRecords.forEach(record =&gt; records.push(record));
}

saveRecords([&#39;21&#39;, &#39;Come On Over&#39;, &#39;The Bodyguard&#39;])</code></pre>
<br>

<h4 id="never-반환-타입">never 반환 타입</h4>
<p>never 반환 함수는 (의도적으로) 항상 <strong>오류를 발생</strong>시키거나 <strong>무한 루프를 실행</strong>하는 함수이다.  </p>
<p>함수를 절대 반환하지 않도록 의도하려면 명시적 <code>: never</code> 타입 애너테이션을 추가해 해당 함수를 호출한 후 모든 코드가 실행되지 않음을 나타낸다. </p>
<p>다음 fail 함수는 오류만 발생시키므로 param의 타입을 string으로 좁혀서 타입스크립트의 제어 흐름 분석을 도와준다. </p>
<pre><code class="language-ts">function fail(message: string): never {
    throw new Error(&#39;Invariant failure: ${message}&#39;);
}

function workWithUnsafeParam(param: unknown){
    if(typeof param !== &#39;string&#39;){
        fail(`param should be a string, not ${typeof param}`)
    }

    param.toUpperCase();
}</code></pre>
<hr>
<h2 id="📌-함수-오버로드">📌 함수 오버로드</h2>
<p>하나의 최종 <strong>구현 시그니처</strong>와 그 함수의 본문 앞에 서로 다른 버전의 함수 이름, 매개변수, 반환 타입인 <strong>오버로드 시그니처</strong>를 여러번 선언함으로써  다양한 매개변수들로 호출되는 함수를 정의할 수 있다. </p>
<pre><code class="language-ts">function createDate(timestamp: number): Date;
function createDate(month: number, day: number, year: number): Date;
function createDate(monthOrTimeStamp: number, day?: number, year?: number){
    return day === undefined || year === undefined 
        ? new Date(monthOrTimeStamp)
        : new Date(year, monthOrTimeStamp, day);
}

createDate(554356800); // ok
createDate(7, 27, 1987); // ok

createDate(4, 1) // Error</code></pre>
<blockquote>
<p>함수 오버로드는 복잡하고 설명하기 어려운 함수 타입에 사용하는 최후의 수단이다. 함수를 단순하게 유지하고 가능하면 함수 오버로드를 사용하지 않는 것이 좋다.</p>
</blockquote>
<hr>
<h2 id="🪄-함수-정의-시-타입을-나타내는-가장-일반적인-2가지-방법">🪄 함수 정의 시 타입을 나타내는 가장 일반적인 2가지 방법</h2>
<ol>
<li><p>일반 함수</p>
<pre><code class="language-ts">function A(a: string, b: number): number {
    // string과 number를 받아서 number를 반환하는 함수
}</code></pre>
</li>
<li><p>화살표 함수</p>
<pre><code class="language-ts">const A = (a: string, b: number):number =&gt; {
    // string과 number를 받아서 number를 반환하는 함수
}</code></pre>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[🪄 러닝 타입스크립트 CH4 - 객체]]></title>
            <link>https://velog.io/@minw0_o/%EB%9F%AC%EB%8B%9D-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-CH4-%EA%B0%9D%EC%B2%B4</link>
            <guid>https://velog.io/@minw0_o/%EB%9F%AC%EB%8B%9D-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-CH4-%EA%B0%9D%EC%B2%B4</guid>
            <pubDate>Thu, 26 Oct 2023 02:06:29 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-객체-타입-선언">📌 객체 타입 선언</h2>
<p>객체 타입은 객체 리터럴과 유사하게 보이지만 <strong>필드 값</strong> 대신 <strong>타입</strong>을 사용한다. </p>
<pre><code class="language-ts">let poetLater: {
    born: number;
    name: string;
}

poetLater = {
    born: 1935,
    name: &quot;Mary Oliver&quot;
}</code></pre>
<hr>
<h2 id="📌-별칭-객체-타입">📌 별칭 객체 타입</h2>
<p>각 객체 타입을 계속 작성하지 않고, <strong>각 객체 타입에 타입 별칭을 할당</strong>하는 방법이 더 일반적이다. </p>
<pre><code class="language-ts">type Poet = {
    born: number;
    name: string;
}

let poetLater: Poet;

poetLater = {
    born: 1935,
    name: &quot;Mary Oliver&quot;
}</code></pre>
<br>

<h3 id="구조적-타이핑">구조적 타이핑</h3>
<p>타입을 충족하는 모든 값을 해당 값으로 사용할 수 있다 ? </p>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/bbda1bca-167d-4502-808d-e0c89ee348c1/image.png" alt=""></p>
<p><code>hasBoth</code> 멤버는 명시적으로 선언되지 않았음에도 두 개의 별칭 객체 타입을 모두 가지므로 두 개의 별칭 객체 타입 내에 선언된 변수를 모두 제공할 수 있습니다. </p>
<br>

<h2 id="초과-속성-검사">초과 속성 검사</h2>
<p>이해 안됨 =&gt; 나중에 다시 읽어보기 </p>
<hr>
<h2 id="📌-객체-타입-유니언">📌 객체 타입 유니언</h2>
<h3 id="유추된-객체-타입-유니언">유추된 객체 타입 유니언</h3>
<p>변수에 여러 객체 타입 중 하나가 될 수 있는 초깃값이 주어지면 TS는 해당 타입을 객체 타입 유니언으로 유추한다. </p>
<pre><code class="language-ts">const poem = Math.random &gt; 0.5 
    ? { name: &quot;MinwooP&quot;, pages: 7 }
    : { name: &quot;MinwooP&quot;, rhymes: true };</code></pre>
<p>위의 poem 변수는 아래와 같은 객체 타입 유니언으로 유추된다.</p>
<pre><code class="language-ts">{
    name: string,
    pages: number,
    ryhmes?: undefined
}
| 
{
    name: string,
    pages?: undefined,
    ryhmes: boolean
}</code></pre>
<br>

<h3 id="명시된-객체-타입-유니언">명시된 객체 타입 유니언</h3>
<pre><code class="language-TS">type PoemWithPages = {
    name: string,
    pages: number
}

type PoemWithRhymes = {
    name: string,
    ryhmes: boolean
}

type Poem = PoemWithPages | PoemWithRhymes;

const poem: Poem = Math.random &gt; 0.5 
    ? { name: &quot;MinwooP&quot;, pages: 7 }
    : { name: &quot;MinwooP&quot;, rhymes: true };

poem.name; // ok

poem.pages // error
poem.rhymes // error</code></pre>
<p><code>poem.pages</code>나 <code>poem.rhymes</code>  처럼 잠재적으로 존재하지 않는 객체의 멤버에 대한 접근을 제한한다. <code>pages</code>나 <code>rhymes</code>는 항상 존재한다는 보장이 없기 때문.</p>
<br>

<p><code>if</code>문을 통해 객체 타입 내로잉을 할 수 있고, 이를 통해 특정 속성에 접근할 수 있다.</p>
<pre><code class="language-ts">if(&quot;pages&quot; in poem){ // poem은 PoemWithPages로 좁혀짐
    poem.pages; 
}else{ // poem은 PoemWithRhymes로 좁혀짐
    poem.rhymes;
}</code></pre>
<br>

<h3 id="판별된-유니언">판별된 유니언</h3>
<p><strong>유니온 타입을 구성하는 타입들의 공통된 &quot;리터럴 타입&quot; 필드를 통해서 구분하는것</strong></p>
<pre><code class="language-ts">type PoemWithPages = {
    type: &#39;pages&#39;; // 리터럴 타입 ? 
}</code></pre>
<p>다음과 같이 객체의 속성에 <code>type</code> 필드를 추가하고 그 속성 값에 type명을 나타낼 수 있다.</p>
<pre><code class="language-ts">type PomeWithPages = {
    name: string;
    number: number
    type: &#39;pages&#39;;
}

type PoemWithRhymes = {
    name: string;
    rhymes: boolean;
    type: &#39;rhymes&#39;;
}

type Poem = PoemWithPages | PoemWithRhymes;

const poem: Poem = Math.random &gt; 0.5 
    ? { name: &quot;MinwooP&quot;, pages: 7, type: &quot;pages&quot; }
    : { name: &quot;MinwooP&quot;, rhymes: true, type: &quot;rhymes&quot; };

if(poem.type === &quot;pages&quot;){
   // pages 타입
}else{
   // rhymes 타입  
}</code></pre>
<br>

<p>switch case 문을 통해서도 각 type을 구분할 수 있다.</p>
<pre><code class="language-ts">switch(poem.type){
    case &quot;pages&quot;: 
        // ~ 
        break;    
    case &quot;rhymes&quot;: 
        // ~ 
        break;
}</code></pre>
<hr>
<h2 id="📌-교차-타입">📌 교차 타입</h2>
<p>여러 기존 객체 타입을 별칭 객체 타입으로 결합해 새로운 타입을 생성할 수 있다. </p>
<br>

<p>유니언 타입과 결합해 사용할 수도 있다. </p>
<pre><code class="language-ts">type ShortPoem = { author: string } &amp; (
    | { kigo: string; type: &quot;haiku&quot;; }
    | { metor: number; type: &quot;villanelle&quot;; }
);</code></pre>
<p>위의 ShortPoem 타입은 항상 <code>author</code> 속성을 가지며 하나의 type 속성으로 판별된 유니언 타입이다. </p>
<br>

<p> ts가 해당 이름을 출력하도록(할당 가능성 오류 때 타입 검사기가 출력하는 메시지) 타입을 일련의 별칭으로 된 객체 타입으로 분할하면 읽기가 훨씬 쉬워진다.</p>
<pre><code class="language-ts">type ShortPoemBase = { author: string };
type Haiku = ShortPoemBase &amp; { kigo: string; type: &quot;haiku&quot;; }
type Villanelle = { metor: number; type: &quot;villanelle&quot;};
type ShortPoem = Haiku | Villanelle;</code></pre>
<br>

<p>두 개의 원시 타입을 교차 시도하면 never 키워드로 표시되는 never 타입이 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[🪄 러닝 타입스크립트 CH3 - 유니언과 리터럴]]></title>
            <link>https://velog.io/@minw0_o/%EB%9F%AC%EB%8B%9D-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-CH3-%EC%9C%A0%EB%8B%88%EC%96%B8%EA%B3%BC-%EB%A6%AC%ED%84%B0%EB%9F%B4-f7d9zvs7</link>
            <guid>https://velog.io/@minw0_o/%EB%9F%AC%EB%8B%9D-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-CH3-%EC%9C%A0%EB%8B%88%EC%96%B8%EA%B3%BC-%EB%A6%AC%ED%84%B0%EB%9F%B4-f7d9zvs7</guid>
            <pubDate>Mon, 23 Oct 2023 16:06:44 GMT</pubDate>
            <description><![CDATA[<ul>
<li><p>유니언 : 값에 허용된 타입을 두 개 이상의 가능한 타입으로 <strong>확장하는 것</strong></p>
</li>
<li><p>내로잉 : 값에 허용된 타입이 하나 이상의 가능한 타입이 되지 않도록 <strong>좁히는 것</strong> </p>
</li>
</ul>
<p>=&gt; 코드 정보에 입각한 추론 </p>
<hr>
<h2 id="📌-유니언">📌 유니언</h2>
<ul>
<li><p>여러 개의 타입을 가질 수 있을 때</p>
<blockquote>
<p>쉽게 말해서 <strong>이거 아님 저거</strong></p>
</blockquote>
</li>
</ul>
<p>값이 유니언 타입일 때, 유니언으로 선언한 <strong>모든 타입에 존재하는 멤버 속성에만</strong> 접근할 수 있다. </p>
<p>ex) 다음 코드에서 <code>physicist</code>는 <code>string | number</code> 타입이므로 두 개의 타입에 모두 존재하는 <code>toString()</code> 메서드는 사용할 수 있지만 <code>toUpperCase()</code>는 string 타입에만 존재하기 때문에 사용할 수 없다.</p>
<pre><code class="language-ts">let physicist = Math.random() &gt; 0.5 
    ? &quot;Marie Curie&quot;
    : 84; // string | number

physicist.toString(); // ok

physicist.toUpperCase(); // error! </code></pre>
<p>이를 해결하려면 코드에서 값이 보다 구체적인 타입 중 하나라는 것을 TS에게 알리는 <strong>내로잉</strong>이 필요하다. </p>
<hr>
<h2 id="📌-내로잉">📌 내로잉</h2>
<p>값이 정의, 선언 혹은 이전에 유추된 것보다 더 구체적인 타입임을 코드에서 유추하는 것이다. </p>
<p>타입을 좁히는 데 사용할 수 있는 논리적 검사를 <strong>타입 가드</strong>라고 한다. </p>
<h4 id="값-할당을-통한-내로잉">값 할당을 통한 내로잉</h4>
<p>변수에 직접 값을 할당하면 TS는 변수의 타입을 할당된 값이 타입으로 좁힌다. </p>
<pre><code class="language-ts">let person: number | string;

person = &quot;MinwooP&quot;;

person.toUpperCase(); // ok</code></pre>
<br>

<h4 id="if문-조건-검사를-통한-내로잉">if문 조건 검사를 통한 내로잉</h4>
<pre><code class="language-TS">let person = Math.random() &gt; 0.5
    ? &quot;MinwooP&quot;
    : 51;

if(person === &quot;MinwooP&quot;){
    person.toUpperCase(); // ok =&gt; string으로 좁혀졌기 때문
}

person.toUpperCase(); // error</code></pre>
<br>

<h4 id="typeof-검사를-통한-내로잉">typeof 검사를 통한 내로잉</h4>
<pre><code class="language-ts">let person = Math.random() &gt; 0.5
    ? &quot;MinwooP&quot;
    : 51;

if(typeof person === string){
    person.toUpperCase(); // ok =&gt; string으로 좁혀졌기 때문
}

person.toUpperCase(); // error</code></pre>
<hr>
<h2 id="📌-리터럴-타입">📌 리터럴 타입</h2>
<pre><code class="language-ts">const philosopher = &quot;MinwooP&quot;;</code></pre>
<ul>
<li><p>위 변수의 타입은, <code>string</code> 타입이지만 단지 string 타입이 아닌 기술적으로 더 구체적인 <code>&quot;MinwooP&quot;</code>이다.</p>
</li>
<li><p>변수를 const로 선언하고 직접 리터럴 값을 할당하면 TS는 해당 변수를 할당된 리터럴 값으로 유추한다. </p>
<blockquote>
<p><code>const</code>로 선언되어있으니 변경이 불가능하므로 리터럴 값으로 유추 ! </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/minw0_o/post/6ff29e44-0b29-4f90-bc1e-277f3516174b/image.png" alt=""></p>
</li>
</ul>
<blockquote>
<pre><code class="language-ts">let philosopher = &quot;MinwooP&quot;; </code></pre>
<p>그럼  이렇게 let으로 선언하면 변경가능하니 string 타입이고</p>
<pre><code class="language-ts">const philosopher = &quot;MinwooP&quot;;</code></pre>
<p>const로 선언하면 리터럴 타입  </p>
</blockquote>
<ul>
<li><p>리터럴 타입은 유니언 타입 애너테이션에서도 사용됨</p>
<pre><code class="language-ts">let lifespan: number | &quot;ongoing&quot; | &quot;uncertain&quot;</code></pre>
</li>
</ul>
<hr>
<h2 id="📌-엄격한-null-검사">📌 엄격한 null 검사</h2>
<p>TS 컴파일러 옵션 중, <code>strictNullChecks</code>를 비활성화하면, 코드의 모든 타입에 <code>| null | undefined</code> 를 추가해야 모든 변수가 null 또는 undefined를 할당할 수 있다. </p>
<p><code>strictNullChecks</code> 를 활성화한 경우, string 타입인 value에 null을 할당하면 타입 오류가 발생합니다.</p>
<blockquote>
<p>stringNullChecks를 비활성화해도, 무조건 null을 할당할 수 있는게 아니라 타입에 <code>| null | undefined</code>를 추가해야 이를 할당할 수 있는 것 ?</p>
</blockquote>
<br>

<p>false, 0, &quot;&quot;, null, undefined, NaN 처럼 <strong>falsy</strong>로 정의된 값을 제외한 모든 값은 모두 참이다. </p>
<hr>
<h2 id="📌-타입-별칭">📌 타입 별칭</h2>
<p>재사용하는 타입에 더 쉬운 이름을 할당</p>
<pre><code class="language-ts">type RawData = boolean | number | string ;

let rawDataFirst: RawData;
let rawDataSecond: RawData | null | undefined;</code></pre>
<blockquote>
<p>이러한 타입 별칭은 타입 애너테이션처럼 js로 컴파일되지 않는다. ts 타입 시스템에만 존재하는 것</p>
<p>=&gt; </p>
<p>따라서 타입 별칭은 런타임 코드에서 참조할 수 없음</p>
<pre><code class="language-ts">type someType = string | undefined;

console.log(SomeType); // Error</code></pre>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>