<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>효효 개발공부로그</title>
        <link>https://velog.io/</link>
        <description>효효 개발공부로그</description>
        <lastBuildDate>Tue, 24 Jun 2025 01:42:17 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. 효효 개발공부로그. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyohyo__" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[모노레포를 프로젝트에 적용해보기]]></title>
            <link>https://velog.io/@hyohyo__/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EB%A5%BC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@hyohyo__/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC%EB%A5%BC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-%EC%A0%81%EC%9A%A9%ED%95%B4%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Tue, 24 Jun 2025 01:42:17 GMT</pubDate>
            <description><![CDATA[<h1 id="📱모노레포란">📱모노레포란?</h1>
<ul>
<li>여러 프로젝트의 코드와 자산을 1개의 레포지토리에서 저장하는 구조이다</li>
</ul>
<p><strong><code>모노레포 구조 예시</code></strong></p>
<pre><code>/root
├── apps/
│   ├── user-app/       # 사용자용 웹앱 (www.xxx.com)
│   └── admin-app/      # 어드민용 웹앱 (admin.xxx.com)
├── packages/
│   ├── ui/             # 공통 UI 컴포넌트 (shadcn 확장)
│   ├── hooks/          # 공통 hooks
│   ├── types/          # API/도메인 공통 타입
│   └── utils/          # 공통 유틸리티 함수
├── turbo.json          # Turborepo 설정
├── package.json
└── tsconfig.base.json</code></pre><h2 id="✔️-멀티레포와의-차이점">✔️ 멀티레포와의 차이점</h2>
<ul>
<li>멀티레포는 모노레포와 상반된 개념으로 프로젝트를 레포지토리 별로 저장하는 구조이다</li>
<li>도메인별로 독립성을 보장할 수 있고 책임 경계를 분명하게 할 수 있다</li>
<li>서비스별 릴리즈가 유연하고 구조가 단순해서 빠르게 시작할 수 있다  </li>
<li>작업물이나 이슈 처리 결과 등의 공유와 찾는 과정이 비교적 협업이 오래 걸린다</li>
<li>새 프로젝트 생성의 비용이 크다</li>
</ul>
<p><strong><code>멀티레포 구조 예시</code></strong></p>
<pre><code>/user-app-repo
/user-admin-repo
/backend-api-repo</code></pre><h2 id="✔️-장점">✔️ 장점</h2>
<ul>
<li>코드 공유, 변경 추적을 1개의 레포지토리에서 할 수 있기 때문에 <strong>협업에 용이</strong>하다</li>
<li>공통 로직을 여러 프로젝트에서 사용하기 때문에 한 번의 커밋으로 여러 프로젝트에 반영할 수 있어 <strong>코드의 유지보수에 용이</strong>하다</li>
<li>여러 프로젝트들이 동일한 도구와 프로세스를 사용하여 빌드하고 테스트할 수 있기 때문에 <strong>프로젝트 환경 세팅이 쉬워진다</strong></li>
</ul>
<h2 id="✔️-단점">✔️ 단점</h2>
<ul>
<li>규모가 커질수록 <strong>코드 관리가 어려워질 수 있다</strong></li>
<li>멀티레포보다 비교적 코드 베이스가 커지기 때문에 빌드 최적화 툴을 사용하지 않으면 <strong>빌도 속도가 느려진다</strong></li>
<li>어려 프로젝트를 사용하기 때문에 <strong>코드 간의 충돌</strong>이 발생할 가능성이 있다</li>
</ul>
<h2 id="✔️-프론트엔드와-백엔드도-같은-레포지토리에-넣는-게-좋은가">✔️ 프론트엔드와 백엔드도 같은 레포지토리에 넣는 게 좋은가</h2>
<ul>
<li><strong>언어가 같거나 협업이 긴밀할 때만</strong> 레포지토리를 분리하지 않는 것을 추천!</li>
<li>서로 다른 언어라면 일관되고 재사용 가능한 체계로 통일하기 어려움 (빌드 도구, 환경 설정 따로 관리, 타입 변환 도구 필요)</li>
<li>분리하지 않은 만큼 코드베이스가 커져 불필요한 부분까지 매번 다 빌드되기 때문에 빌드 속도가 느려져 이점보다 단점이 커짐</li>
</ul>
<h2 id="✔️-어떤-프로젝트에서-사용하는-게-좋은가">✔️ 어떤 프로젝트에서 사용하는 게 좋은가?</h2>
<ul>
<li>협업의 효율이 좋아하는 프로젝트일 때 (스타트업/소규모팀)</li>
<li>(프론트엔드 기준) 다른 도메인이지만 일관된 UI를 사용하고 싶을 때</li>
<li>여러 프로젝트의 변화를 한 눈에 파악해야 할 때</li>
<li>공통 코드가 많고 빠르게 코드를 수정하고 프로덕트에 반영해야 되는 프로젝트</li>
</ul>
<h2 id="🔧-모노레포-툴-정리">🔧 모노레포 툴 정리</h2>
<ul>
<li>모노레포를 실무에 도입할 때 많이 사용하는 주요 툴은 아래와 같다</li>
</ul>
<h3 id="turborepo">Turborepo</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>개발사</strong></td>
<td><a href="https://turbo.build/">Vercel</a></td>
</tr>
<tr>
<td><strong>언어 지원</strong></td>
<td>JS, TS 기반 (React/Next.js에 최적화)</td>
</tr>
<tr>
<td><strong>의존성 관리</strong></td>
<td>Yarn/NPM/PNPM Workspaces와 함께 사용</td>
</tr>
<tr>
<td><strong>캐시 지원</strong></td>
<td>로컬 + 원격 캐시 모두 가능</td>
</tr>
<tr>
<td><strong>빌드 전략</strong></td>
<td>Task 기반 DAG 빌드, 병렬 실행, 변경 감지</td>
</tr>
<tr>
<td><strong>CI/CD 통합</strong></td>
<td>GitHub Actions 등과 원활하게 연동됨</td>
</tr>
</tbody></table>
<h3 id="nx">Nx</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>개발사</strong></td>
<td><a href="https://nx.dev">Nrwl</a> (ex-Google Angular core dev)</td>
</tr>
<tr>
<td><strong>언어 지원</strong></td>
<td>JS/TS 기반이지만, plugins 통해 다양한 언어 확장</td>
</tr>
<tr>
<td><strong>의존성 관리</strong></td>
<td>자체 CLI + Yarn/PNPM Workspaces</td>
</tr>
<tr>
<td><strong>캐시 지원</strong></td>
<td>로컬 + 원격 캐시 + 분산 빌드</td>
</tr>
<tr>
<td><strong>빌드 전략</strong></td>
<td>고급 DAG 분석, affected기반 작업만 수행</td>
</tr>
<tr>
<td><strong>시각화 도구</strong></td>
<td>nx graph로 의존성 시각화 가능</td>
</tr>
</tbody></table>
<h3 id="lerna">Lerna</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>개발사</strong></td>
<td>Community (현재 유지 중단 상태였다가 Turborepo에 흡수된 느낌)</td>
</tr>
<tr>
<td><strong>언어 지원</strong></td>
<td>JS/TS</td>
</tr>
<tr>
<td><strong>의존성 관리</strong></td>
<td>Yarn/NPM Workspaces 필요</td>
</tr>
<tr>
<td><strong>빌드 전략</strong></td>
<td>기본은 단순한 <code>run</code> 순차 실행, 복잡한 빌드 최적화는 없음</td>
</tr>
<tr>
<td><strong>캐시 지원</strong></td>
<td>없음 (따로 설정 필요)</td>
</tr>
</tbody></table>
<h3 id="yarn--npm-workspacesv">Yarn / npm Workspacesv</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>개발사</strong></td>
<td>Meta (Yarn), npm (OpenJS Foundation)</td>
</tr>
<tr>
<td><strong>기능</strong></td>
<td>의존성 연결, 중복 제거 (<code>hoisting</code>)</td>
</tr>
<tr>
<td><strong>빌드 전략</strong></td>
<td>자체 없음 (Turborepo/Nx와 함께 사용)</td>
</tr>
<tr>
<td><strong>캐시 지원</strong></td>
<td>의존성 캐시 (<code>node_modules</code>)만 제공</td>
</tr>
</tbody></table>
<h3 id="bazel">Bazel</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td><strong>개발사</strong></td>
<td><a href="https://bazel.build">Google</a></td>
</tr>
<tr>
<td><strong>언어 지원</strong></td>
<td>거의 모든 언어 (Java, Go, Rust, JS 등)</td>
</tr>
<tr>
<td><strong>특징</strong></td>
<td>빌드 시스템 자체를 구성하는 툴</td>
</tr>
<tr>
<td><strong>캐시 지원</strong></td>
<td>고급 분산 캐시 시스템</td>
</tr>
<tr>
<td><strong>CI/CD 통합</strong></td>
<td>복잡하지만, 강력한 확장성</td>
</tr>
</tbody></table>
<h3 id="전체-비교-요약표">전체 비교 요약표</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Turborepo</th>
<th>Nx</th>
<th>Lerna</th>
<th>Yarn Workspaces</th>
<th>Bazel</th>
</tr>
</thead>
<tbody><tr>
<td>언어 지원</td>
<td>JS/TS</td>
<td>JS/TS (+ 확장)</td>
<td>JS/TS</td>
<td>JS/TS</td>
<td>대부분</td>
</tr>
<tr>
<td>변경 추적</td>
<td>✅</td>
<td>✅ (nx affected)</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>원격 캐시</td>
<td>✅</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>의존성 관리</td>
<td>Workspaces</td>
<td>자체 + Workspaces</td>
<td>Workspaces</td>
<td>Workspaces</td>
<td>자체</td>
</tr>
<tr>
<td>시각화/분석</td>
<td>❌</td>
<td>✅</td>
<td>❌</td>
<td>❌</td>
<td>✅</td>
</tr>
<tr>
<td>러닝 커브</td>
<td>낮음</td>
<td>중~높음</td>
<td>낮음</td>
<td>낮음</td>
<td>매우 높음</td>
</tr>
<tr>
<td>추천 대상</td>
<td>React/Next.js</td>
<td>대규모/기업</td>
<td>소규모/단기</td>
<td>최소 구성</td>
<td>초대형 멀티언어 시스템</td>
</tr>
</tbody></table>
<h1 id="💻-프로젝트에-적용하기">💻 프로젝트에 적용하기</h1>
<h2 id="✔️-서비스-요약">✔️ 서비스 요약</h2>
<ul>
<li>서비스 종류 : 커뮤니티</li>
<li>도메인 종류 : 어드민, 사용자로 분리</li>
<li>페이지 수 : 10개 내외</li>
</ul>
<h2 id="✔️-기술스택">✔️ 기술스택</h2>
<h3 id="프론트엔드">프론트엔드</h3>
<ul>
<li>언어 : JavaScript</li>
<li>프레임워크 : TypeScript</li>
<li>라이브러리 : React</li>
<li>스타일링(CSS) : Tailwind css, shadcn/ui</li>
<li>배포 : vercel</li>
</ul>
<h3 id="백엔드">백엔드</h3>
<ul>
<li>언어 : java</li>
<li>프레임워크 : spring boot 3.5</li>
<li>라이브러리: jpa</li>
<li>서비스 환경: docker, kafka</li>
<li>db: postgreSQL, redis</li>
<li>CI/CD: git / Jenkins</li>
<li>AI 개발 환경 : MCP [cursor, claude etc...]</li>
<li>api 문서화 : 스웨거</li>
</ul>
<h2 id="✔️-왜-모노레포를-사용하는가">✔️ 왜 모노레포를 사용하는가?</h2>
<ul>
<li>짧은 기간과 작은 규모의 프로젝트로 빠른 협업과 생산성이 필요함</li>
<li>2개의 도메인(사용자, 어드민)에 공통 로직이 많고 일관된 UI를 구현하기 위함</li>
<li>많은 기업에서 사용하는 모노레포에 대한 경험을 쌓기 위해서</li>
</ul>
<h2 id="⚙️-설정-계획">⚙️ 설정 계획</h2>
<blockquote>
<p><strong>Turborepo와 pnpm을 사용</strong></p>
</blockquote>
<h3 id="turborepo-모노레포-툴-사용-이유">Turborepo 모노레포 툴 사용 이유</h3>
<ul>
<li>낮은 러닝 커브로 빠른 세팅 가능</li>
<li>vercel 배포에 최적화</li>
<li>JS, TS 기반으로 기술스택에 최적화</li>
<li>Github Actions에 원활하게 연동됨</li>
<li>변경된 사항만 빌드되기 때문에 빌드 최적화 가능</li>
</ul>
<h3 id="pnpm-패키지-매니저-사용-이유">pnpm 패키지 매니저 사용 이유</h3>
<ul>
<li>중복된 패키지를 제거해 줌</li>
<li>성능이 좋아 속도가 빠름</li>
<li>모노레포에 최적화됨</li>
</ul>
<h2 id="배포-계획">배포 계획</h2>
<ul>
<li>vercel의 무료 사용을 위해 조직 소유의 모노 레포지토지를 만든 후에 개인 레포지토리로 fork함</li>
<li>어드민, 사용자 도메인별로 vercel 프로젝트도 2개로 나눠서 fork 해야 함</li>
</ul>
<h1 id="📑참고-자료">📑참고 자료</h1>
<ul>
<li><a href="https://green-labs.github.io/monorepo-microfrontend">greenlabs 기술 블로그, 모노레포 - 마이크로 아키텍처를 지향하며</a></li>
<li><a href="https://team-pairy.medium.com/%EB%AA%A8%EB%85%B8%EB%A0%88%ED%8F%AC-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-67f4087763d0">Team Pairy 테크 블로그, 모노레포 알아보기</a></li>
<li><a href="https://www.youtube.com/watch?v=Ix9gxqKOatY">FECONF 2022 [B2] 일백개 패키지 모노레포 우아하게 운영하기</a></li>
<li><a href="https://d2.naver.com/helloworld/0923884">네이버 D2 기술 블로그, 모던 프론트엔드 프로젝트 구성 기법 - 모노레포 개념 편
</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트] 모던 리액트 딥다이브 15장]]></title>
            <link>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-15%EC%9E%A5</link>
            <guid>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-15%EC%9E%A5</guid>
            <pubDate>Sat, 28 Dec 2024 10:20:37 GMT</pubDate>
            <description><![CDATA[<h1 id="15장-마치며">15장 마치며</h1>
<h2 id="151-리액트-프로젝트를-시작할-때-고려해야-할-사항">15.1 리액트 프로젝트를 시작할 때 고려해야 할 사항</h2>
<h3 id="1511-유지보수-중인-서비스라면-리액트-버전을-최소-1686에서-최대-1702로-올려두자">15.1.1 유지보수 중인 서비스라면 리액트 버전을 최소 16.8.6에서 최대 17.0.2로 올려두자</h3>
<ul>
<li>리액트 16.8 이후로 훅이 소개되면서 함수 컴포넌트의 개념이 정립되어 중요한 업데이트가 이뤄졌다.</li>
<li>리액트 17 버전은 새루온 기능 출시 및 호환성이 깨지는 변경 사항을 최소한으로 맞춘 업데이트이므로 가능하다면 17버전으로 가는 것도 좋다.</li>
</ul>
<h4 id="클래스-컴포넌트를-함수-컴포넌트로-리팩토링-해야할까">클래스 컴포넌트를 함수 컴포넌트로 리팩토링 해야할까?</h4>
<ul>
<li><strong>굳이 그럴 필요 없다</strong></li>
<li>리액트 팀에서 클래스 컴포넌트를 제거할 계획이 없다고 했다.</li>
</ul>
<h3 id="1512-인터넷-익스플로러-11지원을-목표한다면-각별히-더-주의를-기한다">15.1.2 인터넷 익스플로러 11지원을 목표한다면 각별히 더 주의를 기한다</h3>
<p>IE11을 지원하는 애플리케이션은 각별히 라이브러리 설치에 주의해야 한다.</p>
<h4 id="인터넷-익스플로러-11을-지원하지-않는-대표적인-라이브러리">인터넷 익스플로러 11을 지원하지 않는 대표적인 라이브러리</h4>
<ul>
<li>리액트 : 18버전부터 IE11을 지원하지 않기로 했다</li>
<li>Next.js : 13버전부터 공식적으로 IE11을 지원하지 않기로 했다</li>
<li>query-string : 주소의 쿠리 문자열을 다루는 대표적인 라이브러리로, 6.x 버전부터 IE를 지원하지 않는다</li>
</ul>
<h3 id="1513-서버-사이드-렌더링-애플리케이션을-우선적으로-고려한다">15.1.3 서버 사이드 렌더링 애플리케이션을 우선적으로 고려한다</h3>
<ul>
<li>기본 HTML에 온전히 자바스크립트로 렌더링과 라우팅을 수행하는 싱글 페이지 애플리케이션은 대부분의 경우 라이트하우스와 WebPageTest, 구글 개발자 도구에서 좋은 결과를 얻기 어렵다.</li>
<li>많은 사용자를 감당해야 된다면 서버 사이드 렌더링을 고려하는 것이 좋다.</li>
<li><strong>싱글 페이지 애플리케이션을 추천하는 경우</strong><ul>
<li>제한적인 사용자에게 빠르게 웹페이지를 제공해야 하는 경우</li>
<li>콘텐츠와 인터렉션이 매우 제한적인 소규모 웹페이지를 만드는 경우</li>
<li>서버를 준비해야 할 상황이 여의치 않은 경우</li>
</ul>
</li>
</ul>
<h3 id="1514-상태-관리-라이브러리는-꼭-필요한-때만-사용한다">15.1.4 상태 관리 라이브러리는 꼭 필요한 때만 사용한다</h3>
<ul>
<li>과거에는 리액트에서 리덕스를 무조건 설치해서 사용했었다</li>
<li>하지만 현재는 리액트 Context API와 훅의 등장으로 prop drilling 문제를 겪지 않고도 하위 컴포넌트에 원하는 상태값을 전달할 수 있게 됐다</li>
<li>문서 편집기 같이 관리해야 할 상태가 많고, 여러 상태를 합성해서 또 새로운 상태를 파생하는 등 상태에 대한 여러 가지 필요성이 많은 애플리케이션이라면 상태 관리를 사용하는 것이 좋다</li>
<li>비교적 정적인 애플리케이션에서는 상태 관리 라이브러리가 굳이 필요하지 않다</li>
</ul>
<h3 id="1515-리액트-의존성-라이브러리-설치를-조심한다">15.1.5 리액트 의존성 라이브러리 설치를 조심한다</h3>
<ul>
<li>리액트에 의존적인 라이브러리는 대부분 react-** 같은 이름을 가지고 있으며, 아래와 같은 의존성을 가지고 있다</li>
</ul>
<pre><code>{
    &quot;peerDependencies&quot;: {
        &quot;react&quot;: &quot;^16.8.6 || ^17.0.0&quot;,
        &quot;react-dom&quot;: &quot;^16.8.6 || ^17.0.0&quot;
    }
}</code></pre><ul>
<li>이때 반드시 이 peerDenpendencies가 설치하고자 하는 프로젝트의 리액트 버전과 맞는지 확인해야 한다</li>
<li>이 버전이 맞지 않다면 정상적인 방법으로 설치가 불가능하고, 설령 억지로 설치한다 하더라도 향후 문제를 일으킬 가능성이 크다</li>
<li>특히 리액트 18에서는 외부 상태를 관리하는 방법이 변경됐기 때문에 잘못 설치했다면 버그를 야기할 가능성이 크다</li>
</ul>
<h2 id="152-언젠가-사라질-수도-있는-리액트">15.2 언젠가 사라질 수도 있는 리액트</h2>
<h3 id="1521-리액트는-그래서-정말-완벽한-라이브러리인가">15.2.1 리액트는 그래서 정말 완벽한 라이브러리인가?</h3>
<ul>
<li>리액트는 가장 널리 쓰이는 프론트엔드 라이브러리는 맞지만 여전히 리액트를 반대하는 사람들은 많다.</li>
<li>리액트를 반대하는 사람들의 의견을 아래와 같다</li>
</ul>
<h4 id="클래스-컴포넌트에서-함수-컴포넌트로-넘어오면서-느껴지는-혼란">클래스 컴포넌트에서 함수 컴포넌트로 넘어오면서 느껴지는 혼란</h4>
<ul>
<li>리액트 공식 홈페이지에는 클래스 컴포넌트에 대한 설명도 작성되어 있어서 입문자를 혼란스럽게 한다</li>
<li>이에 반해 Svelte와 Vue는 문서가 깔끔하고 잘 정리돼 있는 편이다. 무엇보다 함수 컴포넌트와 클래스 컴포넌트와 같은 큰 패러다임의 차이도 없기 때문에 초보자가 훨씬 이해하기 편하다</li>
</ul>
<h4 id="너무-방대한-자유가-주는-혼란">너무 방대한 자유가 주는 혼란</h4>
<ul>
<li>리액트에서 스타일을 입힐 수 있는 방법은 다양하다<ul>
<li>외부 스타일시트 임포트</li>
<li>인라인 스타일</li>
<li>CSS Module 기법 적용</li>
<li>styled-components</li>
<li>emotion</li>
</ul>
</li>
<li>상태 관리 또한 다양한 옵션이 있다<ul>
<li>Redux</li>
<li>MobX</li>
<li>Jotai</li>
<li>Zustand</li>
<li>React Tracked</li>
<li>Valtio</li>
</ul>
</li>
<li>자칫 파편화된 리액트 기술 스택은 새로운 리액트 개발자에게 장애물이 될 수 있다</li>
</ul>
<h3 id="1522-오픈소스-생태계의-명과-암">15.2.2 오픈소스 생태계의 명과 암</h3>
<h4 id="페이스북-라이선스-이슈">페이스북 라이선스 이슈</h4>
<ul>
<li>리액트는 BSD+Parents 라이선스를 사용하고 있었다.</li>
<li>이 라이선스는 &#39;이 라이선스를 적용한 소프트웨어에 대해서 특정한 사건이 발생한다면 라이선스가 통지 없이 종료될 수 있다&#39;라는 조항이 있다</li>
<li>2017년 7월, 아파치 재단에서 BSD+Parents 라이선스를 사용하는 것을 금지한다고 밝히면서 격론이 일어났고 결국 MIT 라이선스로 변경되었다</li>
<li>이 라이선스 사건은 한바탕 소동으로 끝났지만 개발자들에게는 자신이 오픈소스로 만든 소프트웨어에 대한 권리를 한순가에 잃어버릴 수 있다는 경각심을 갖게 하는 계기가 됐다</li>
</ul>
<h4 id="오픈소스는-무료로-계속-제공될-수-있는가-colorjs-fakerjs-그리고-바벨">오픈소스는 무료로 계속 제공될 수 있는가? color.js, faker.js, 그리고 바벨</h4>
<ul>
<li>오픈소스인 바벨은 풀타임 개발자들을 고용해 급여를 주고 있지만 재정난이 있어 모금을 계속하고 있다고 밝혔다</li>
<li>color.js는 고의로 무한 루프를 삽입한 코드를 커밋해 1.4.1을 배포해 버렸다</li>
<li>faker.js은 5.5.3 버전에서 갑자기 6.6.6으로 넘어갔는데, 이 6.6.6 버전에는 아무런 코드가 남아있지 않았다.</li>
<li>우리는 npm 오픈소스 생태계 덕분에 700여 개의 라이브러리를 모두 무료로 사용하고 있지만 위와 같은 사례가 발생할 수 있다</li>
</ul>
<h3 id="1523-제이쿼리-angular-js-리액트-그리고-다음은-무엇인가">15.2.3 제이쿼리, Angular JS, 리액트, 그리고 다음은 무엇인가?</h3>
<ul>
<li>과거에는 서버와 웹페이지가 함께 있는 프레임워크가 주를 이뤘다면 현재로 오면서 점차 프론트엔드 단독 프레임워크가 증가하는 것을 볼 수 있다</li>
<li>계속해서 새로운 프레임워크가 등장하고 있지만 그중에서도 리액트는 꾸준히 사랑받고 있다.</li>
<li>하지만 상태 관리 라이브러리를 중심으로 한 다양한 옵션에 대한 피로감, 훅에 대한 거부감과 어려움 등으로 인해 점차 부정적인 의견이 조금씩 나타나고 있다</li>
</ul>
<h3 id="1524-웹-개발자로서-가져야-할-유연한-자세">15.2.4 웹 개발자로서 가져야 할 유연한 자세</h3>
<ul>
<li>어떤 프레임워크가 라이브러리를 사용하든 변하지 않은 사실은 HTML과 CSS, 자바스크립트가 웹 페이지를 구성하는 기초 기술이라는 사실이다</li>
<li>하지만 이 생태계가 계속 이 수준에서 머물러 있을 것이라는 뜻은 아니다. 최근에는 느린 자바스크립트를 대신할 하나의 방안으로 웹어셈블리가 떠오르고 있다</li>
<li>웹어셈블리는 C, C++, 러스트와 같은 시스템 프로그래밍 언어로 작성된 프로그램을 웹에서 사용할 수 있게 해준다</li>
<li>자바 스크립트보다 훨씬 더 빠른 언어로 작성돼 있기 때문에 일반적으로 웹에서 자바스크립트 기반으로 처리하기 어려운 작업을 웹어셈블리를 활용해 처리할 수 있다</li>
<li>하지만 웹 어셈블리는 자바스크립트에 대체하는 것이 아닌 함께 상호 보완적으로 실행되는 도구라고 보는 것이 적절하다</li>
<li>이 밖에도 Next.js에서 살펴본 SWC, 터보팩 모두 러스트로 작성되어 자바스크립트로 실행되는 바벨과 웹팩을 속도 측면에서 개선하려는 시도도 이뤄지고 있다</li>
<li>그렇다고 당장 리액트를 버리고 다른 프레임워크나 라이브러리를 배워야 하는 것은 아니다</li>
<li>중요한 것은 웹 개발이 HTML, CSS, 자바스크립트라는 토대 위에 세워졌음을 깨닫고 자바스크립트에 대해 깊게 공부하는 것을 추천한다</li>
<li>또한 11억 개가 넘는 웹사이트가 존재하는 인터넷에는, 리액트가 아닌 것들이 많다. 그렇기 때문에 리액트를 넘어 다양한 자바스크립트를 세계를 맛본다면 더욱 유연한 개발자가 될 수 있다</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트] 모던 리액트 딥다이브 12장]]></title>
            <link>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-12%EC%9E%A5</link>
            <guid>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-12%EC%9E%A5</guid>
            <pubDate>Sat, 21 Dec 2024 16:16:36 GMT</pubDate>
            <description><![CDATA[<h1 id="12장-모든-웹-개발자가-관심을-가져야-할-핵심-웹-지표">12장 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표</h1>
<h2 id="121-웹사이트와-성능">12.1 웹사이트와 성능</h2>
<h4 id="사용자가-웹사이트에-접속했을-때-기대하는-사항">사용자가 웹사이트에 접속했을 때 기대하는 사항</h4>
<ol>
<li>방문한 목적을 쉽게 달성할 수 있어야 함</li>
<li>방문한 목적을 달성하는데 시간이 짧아야 함</li>
<li>웹사이트에서 개인정보가 누출되는 등의 사고 없이 보안에 철저해야 함</li>
</ol>
<h2 id="122-핵심-웹-지표란">12.2 핵심 웹 지표란?</h2>
<p>구글에서 만든 지표로, 웹사이트에서 뛰어난 사용자 경험을 제공하는 데 필수적인 지표를 일컫는 용어이다</p>
<h4 id="핵심-웹-지표-3가지">핵심 웹 지표 3가지</h4>
<ol>
<li>최대 콘텐츠 풀 페인트(LCP)</li>
<li>최초 입력 지연(FID)</li>
<li>누적 레이아웃 이동(CLS)</li>
</ol>
<h4 id="특정-문제를-진단하는-데-사용할-수-있는-2가지-지표">특정 문제를 진단하는 데 사용할 수 있는 2가지 지표</h4>
<ol>
<li>최초 바이트까지의 시간(TTFB)</li>
<li>최초 콘텐츠풀 시간(FCP)</li>
</ol>
<h2 id="123-최대-콘텐츠-풀-페인트lcp">12.3 최대 콘텐츠 풀 페인트(LCP)</h2>
<h3 id="1231-정의">12.3.1 정의</h3>
<p>페이지가 처음으로 로드를 시작한 시점부터 뷰포트 내부에서 가장 큰 이미지 또는 텍스트를 렌더링하는 데 걸리는 시간을 말한다</p>
<h4 id="뷰포트-내부에서-큰-이미지와-텍스트">뷰포트 내부에서 &#39;큰 이미지와 텍스트&#39;</h4>
<ol>
<li><code>&lt;img&gt;</code></li>
<li><code>&lt;svb&gt;</code> 내부의 <code>&lt;img&gt;</code></li>
<li>poster 속성을 사용하는 <code>&lt;video&gt;</code></li>
<li>url()을 통해 불러온 배경 이미지가 있는 요소</li>
<li>텍스트와 같이 인라인 텍스트 요소를 포함하고 있는 블록 레벨 요소<ul>
<li>이 블록 레벨 요소에는 <code>&lt;p&gt;</code>, <code>&lt;div&gt;</code> 등이 포함된다</li>
</ul>
</li>
</ol>
<p>이미지와 텍스트가 각각 사용자의 시점에 언제 노출됐는지를 확인하는 정확한 시점은 <a href="https://w3c.github.io/element-timing/#sec-element-timing">W3C 문서</a>에 나와 있다.</p>
<h3 id="1232-의미">12.3.2 의미</h3>
<ul>
<li>웹페이지가 로딩이 완료되어 사용자에게 노출되기까지 걸리는 시간의 기준을 <code>DOMCotentLoaded</code> 이벤트가 호출되는 시간으로 잡으면 안 된다</li>
<li><code>DOMCotentLoaded</code>는 HTML 문서를 완전히 불러오고 파싱했을 때 발생하는 이벤트로, 페이지의 document를 대상으로 일어나며 단 한 번만 호출된다. 이 이벤트는 스타일 시트, 이미지, 하위 프레임의 로딩은 기다리지 않는다.</li>
<li>사용자는 화면에 노출되는 부분만 로딩돼 있다면 사용자는 페이지 로딩이 완료되었다고 느끼기 때문에 뷰포트에 메인 콘텐츠가 화면에 완전히 전달되는 속도를 기준으로 한다.</li>
<li>따라서 사용자에게 페이지의 정보를 화면에 전달하는 속도를 객관적으로 판단하기 위한 지표로 만들어진 것이 바로 최대 콘텐츠풀 페인트이다.</li>
</ul>
<h3 id="1233-예제">12.3.3 예제</h3>
<ul>
<li>생략</li>
<li>LCP는 페이지 로딩에 따라 변화하는 지표이다. 사용자가 이용하는 디바이스의 크기에 따라 그리고 그것이 이미지와 같이 크기가 큰 리소스라면 실제로 로딩에 필요한 시간에 따라 LCP의 값이 달라질 수 있다</li>
</ul>
<h3 id="1234-기준-점수">12.3.4 기준 점수</h3>
<ul>
<li>기준 점수를 측정하는 방법은 자바스크립트 API를 호출하는 방법과 다른 도구를 활용하는 방법(더 많이 사용)이 있다</li>
<li>해당 지표가 <strong>2.5초 내로 응답이 오는 것</strong>이 좋은 점수이다. 4초 이내로 응답이 온다면 보통, 그 이상이 걸리면 나쁨으로 판단한다</li>
</ul>
<h3 id="1235-개선-방안">12.3.5 개선 방안</h3>
<h4 id="텍스트는-언제나-옳다">텍스트는 언제나 옳다</h4>
<ul>
<li>가장 확실한 방법이다.</li>
<li>이미지를 최적화해도 텍스트 노출이 훨씬 더 빠르다.</li>
</ul>
<h4 id="이미지는-어떻게-불러올-것인가">이미지는 어떻게 불러올 것인가?</h4>
<ul>
<li><p>이미지 노출하는 방법으론 아래와 같이 여러 가지가 있다</p>
</li>
<li><p><code>&lt;img&gt;</code></p>
<pre><code class="language-html">&lt;img src=&quot;lcp.jpg&quot; ... /&gt;</code></pre>
<ul>
<li>브라우저의 <a href="https://yceffort.kr/2022/06/preload-scanner#pre-load-scanner%C3%AB%C5%BE%E2%80%A2-%C3%AB%C2%AC%C2%B4%C3%AC%C5%A0%E2%81%84%C3%AC%C5%A1%C2%B8%C3%AA%C2%B0%E2%80%A2">프리로드 스캐너</a>에 의해서 먼저 발견되어 빠르게 요청이 일어난다.</li>
<li>프리로드 스캐너란 HTML을 파싱하는 단계를 차단하지 않고 이미지와 같이 빠르게 미리 로딩하면 좋은 리소스를 먼저 찾아 로딩하는 브라우저의 기능이다</li>
<li>HTML 파싱이 미처 완료되지 않더라도 프리로드 스캐너가 병렬적으로 리소스를 다운로드하므로 LCP 요소를 불러오기에 <strong>적절한 방법</strong>이다</li>
</ul>
</li>
<li><p><code>&lt;svg&gt; 내부의 &lt;img&gt;</code></p>
<pre><code class="language-html">&lt;svg xmls=&quot;http://www.w3.org/1000/svg&quot;&gt;
  &lt;image href=&quot;lcp.jpg&quot; /&gt;
&lt;/svg&gt;</code></pre>
<ul>
<li>모든 리소스를 다 불러온 이후에 이미지를 불러온다</li>
<li>따라서 프리로드 스캐너에 의해 발견되지 않아 병렬적으로 다운로드가 일어나지 않는다. 이는 결국 최대 LCP 점수에도 <strong>악영향을 미쳐 권장하지 않는다</strong></li>
</ul>
</li>
<li><p><code>&lt;video&gt;의 poster</code></p>
<pre><code class="language-html">&lt;video poster=&quot;lcp.jpg&quot;&gt;&lt;/video&gt;</code></pre>
<ul>
<li><a href="https://developer.mozilla.org/ko/docs/Web/HTML/Element/Video">poster</a>는 사용자가 video 요소를 재생하거나 탐색하기 전까지 노출되는 요소다</li>
<li>프리로드 스캐너에 의해 조기에 발견되어 <strong>적절한 방법</strong>이다</li>
<li>향후 poster가 없는 video의 경우 video를 실제로 로딩해 첫 번째 프레임을 해당 poster 리소스로 대체할 예정이기 때문에 poster를 반드시 넣어주는 것이 좋다</li>
</ul>
</li>
<li><p><code>background-image</code></p>
<pre><code class="language-html">&lt;div style=&quot;backgroud-image: url(lcp.jpg)&quot;&gt;...&lt;/div&gt;</code></pre>
<ul>
<li>CSS에 있는 리소스는 항상 느리다. 이러한 리소스는 브라우저가 해당 리소스를 필요로 하는 DOM을 그릴 준비가 될 때까지 리소스 요청을 뒤로 미루기 때문에 <strong>사용하지 않는 것이 좋다.</strong></li>
</ul>
</li>
</ul>
<h4 id="그-밖에-조심해야-할-사항">그 밖에 조심해야 할 사항</h4>
<ul>
<li><strong>이미지 무손실 압축</strong> : 이미지는 가능한 한 무손실 형식으로 압축해 최소한의 용량으로 서비스하는 것이 좋다</li>
<li><strong>loading=lazy 주의</strong> : loading=lazy는 리소스를 중요하지 않음으로 표시하고 필요할 때만 로드하는 전략이기 때문에 LCP의 이미지에 사용하면 로딩 속도를 늦춰서 사용하지 않는 것이 좋다</li>
<li><strong>fadein과 같은 각종 애니메이션</strong> : 이미지가 그냥 뜨는 것보다 fadein을 쓰면 LCP도 늦어진다</li>
<li><strong>클라이언트에서 빌드하지 말 것</strong> : 서버에서 빌드해온 HTML을 프리로드 스캐너가 바로 읽어서 최대 콘텐츠풀 페인트로 빠르게 가져가는 것이 최덕의 시나리오이다</li>
<li><strong>최대 콘텐츠풀 리소스는 직접 호스팅</strong> : 이미 연결이 맺어진 현재 출처가 아니라 완전 새로운 출처의 경우에는 네트워크 커넥션부터 다시 수행하기 때문에 권장하지 않는다</li>
</ul>
<h2 id="124-최초-입력-지연fid">12.4 최초 입력 지연(FID)</h2>
<h3 id="1241-정의">12.4.1 정의</h3>
<ul>
<li>사용자가 페이지와 처음 상호 작용할 때(예: 링크를 클릭하거나 버튼을 탭하거나 사용자 지정 JavaScript 기반 컨트롤을 사용할 때)부터 해당 상호 작용에 대한 응답으로 브라우저가 실제로 이벤트 핸들러 처리를 시작하기까지의 시간을 측정한다</li>
<li>쉽게 말해 사용자가 최초의 입력을 할 때 얼마나 빠르게 웹페이지와의 상호작용에 대한 응답을 받을 수 있는지를 측정하는 지표이다</li>
</ul>
<h3 id="1242-의미">12.4.2 의미</h3>
<ul>
<li>대부분 웹사이트 내부의 이벤트가 반응이 늦어지는 이유는 해당 입력을 처리해야 하는 브라우저의 메인 스레드가 바쁘기 때문이다.</li>
<li>메인 스레드가 바쁜 이유는 대규모 렌더링이 일어나고 있거나 다른 작업을 처리하는 데 리소스를 할애하고 있기 때문이다.</li>
<li>즉, 이벤트가 발생하는 시점에 최대한 메인 스레드가 다른 작업을 처리할 수 있도록 여유를 만들어 둬야 사용자에게 빠른 반응성을 보장할 수 있다.</li>
</ul>
<h4 id="구글에서-정의한-사용자-경험-4가지rail">구글에서 정의한 사용자 경험 4가지(RAIL)</h4>
<ul>
<li><strong>Response</strong>: 사용자의 입력에 대한 반응 속도를 50ms 미만으로 이벤트를 처리할 것 (FID에 해당당)</li>
<li><strong>Animation</strong>: 애니메이션의 각 프레임을 10ms 이하로 생성할 것</li>
<li><strong>Idle</strong>: 유휴 시간을 극대화해 페이지가 50ms 이내에 사용자 입력에 응답하도록 할 것</li>
<li><strong>Load</strong>: 5초 이내에 콘텐츠를 전달하고 인터렉션을 준비할 것</li>
</ul>
<h4 id="정리">정리</h4>
<ul>
<li>화면이 최초에 그려지고 난 뒤, 사용자가 웹페이지에서 클릭 등 상호작용을 수행했을 때 메인 스레드가 이 이벤트에 대한 반응을 할 수 있을 때까지 걸리는 시간을 의미한다.</li>
<li>이 사간은 메인 스레드가 처리해야 하는 다른 작업이 많을수록 느리다</li>
</ul>
<h3 id="1243-예제">12.4.3 예제</h3>
<ul>
<li>크롬으로 디버깅하는 것은 이벤트가 처리되는 것이 얼마나 지연되는지만 알 수 있다</li>
<li>즉, 최초 이벤트 발생으로부터 해당 이벤트의 핸들러가 실행되는 순간까지 사이의 기간만 측정한다</li>
<li>만약 이벤트 핸들러의 실행 시간을 측정하고 싶다면 Event Timing API를 사용하는 것이 좋다</li>
</ul>
<h3 id="1244-기준-점수">12.4.4 기준 점수</h3>
<ul>
<li>좋은 점수의 기준은 <strong>100ms 이내로 응답</strong>이 와야 하며, 300ms 이내인 경우 보통, 그 이후에는 나쁨으로 처리된다</li>
</ul>
<h3 id="1245-개선-방안">12.4.5 개선 방안</h3>
<h4 id="실행에-오래-걸리는-긴-작업을-분리">실행에 오래 걸리는 긴 작업을 분리</h4>
<ul>
<li><p>만약 실행에 오래 걸리는 작업이 있다면 몇 가지 대안을 연구해야 한다</p>
</li>
<li><p><strong>꼭 웹페이지에서 해야 하는 작업인가?</strong><br>꼭 웹페이지에서 해야 되는 작업이 아니라면 서버로 옮겨서 처리하는 것이 좋다. 서버에서 처리하면 상대적으로 빠르고 쾌적한 환경에서 처리해 브라우저의 메인 스레드를 오래 점유하지 않게 할 수 있다.</p>
</li>
<li><p><strong>긴 작업을 여러 개로 분리하기</strong><br>하나의 긴 작업이 메인 스레드를 계속 점유할수록 사용자는 페이지에서 응답을 받지 못하고 있을 가능성이 크다. 웹페이지 최초 로딩에 필요하지 않은 내용을 나중에 불러오는 것도 포함해서 분리하는 것이 좋다</p>
</li>
</ul>
<h4 id="자바스크립트-코드-최소화">자바스크립트 코드 최소화</h4>
<ul>
<li>현재 사용하지 않는 코드 중에서는 특정 이벤트에 따라 실행되는 코드, 예기치 못한 상황에서 실행될 코드 등 다양한 것들이 존재할 수 있다</li>
<li>이러한 코드들은 당장에 급하지 않은 코드로 간주해서 앞서 언급한 지연 로딩 기법, 사용자가 필요로 하는 순간에 불러오거나 우선순위를 낮춰서 불러오는 것이 좋다</li>
<li><strong>폴리필 확인하기</strong><br>폴리필이란 브라우저에서 지원하지 않는 기능을 사용하기 위해 웹페이지에서 직접 구현하고 집어넣는 코드를 의미한다<br>한 메서드에 들어가는 폴리필의 크기가 제법 크기 때문에 폴리필을 넣기 전에 반드시 2가지를 확인해 봐야 한다<ol>
<li><strong>폴리필이 필요한 환경인가?</strong> : 만약 구현 브라우저 환경을 지원하지 않기로 결심했다면 폴리필을 집어 넣을 필요가 없다</li>
<li><strong>꼭 필요한 폴리필인가?</strong> : 애플리케이션의 여러 군데에서 자주 사용하는 코드인지 반드시 확인해 봐야 한다. 만약 사용하는 곳이 몇 군데 되지 않는다면 직접 저수준 자바스크립트 코드를 작성해 구현하는 편이 도움이 될 수 있다</li>
</ol>
</li>
</ul>
<h4 id="타사-자바스크립트-코드-실행의-지연">타사 자바스크립트 코드 실행의 지연</h4>
<ul>
<li>Google Analytics나 Firebase와 같이 웹페이지의 통계 집계를 위해 제3자가 만든 타사 스크립트를 넣는 경우도 있다. 이러한 코드의 실행으로 메인 스레드가 점유될 수 있다.</li>
<li>이러한 타사 스크립트는 대부분 웹 페이지 로드에 중요한 자원이 아니므로 <code>&lt;script&gt;</code>의 async와 defer를 이용해 지연 불러오기를 하는 것이 좋다.<ul>
<li><code>defer</code><br>먼저 해당 스크립트를 다른 리소스와 함께 병렬로 다운로드한다.<br>다운로드 중에도 HTML 파싱 등의 메인 스레드 작업은 멈추지 않는다.<br>다운로드가 완료됐다 하더라도 이 스크립트의 실행은 페이지가 완전히 로딩된 이후에 맨 마지막에 실행된다</li>
<li><code>async</code><br>해당 스크립트를 다른 리소스와 함께 병렬로 다운로드한다.<br>리소스의 다운로드가 완료되어 버리면 다른 리소스의 다운로드가 완료되는 것을 기다리지 않고 바로 실행한다.<br>따라서 async 리소스의 실행 순서는 다운로드가 완료된 순서대로 실행된다</li>
<li><code>둘 다 없는 경우</code><br>script를 만나는 순간 다운로드가 우선되며, 다운로드가 완료되면 코드 실행이 우선된다. 다른 작업은 다운로드와 실행이 끝날 때까지 미뤄진다.</li>
</ul>
</li>
<li>따라서 타사 스크립트는 가능하면 async를, 더 가능하면 defer로 지연하는 것이 성능에 도움이 된다</li>
</ul>
<h2 id="125-누적-레이아웃-이동cls">12.5 누적 레이아웃 이동(CLS)</h2>
<h3 id="1251-정의">12.5.1 정의</h3>
<ul>
<li>페이지의 생명주기 동안 발생하는 모든 예기치 않은 이동에 대한 지표를 개선하는 것이다.</li>
<li>다른 지표와 마찬가지로 이 지표가 낮을수록, 즉 사용자가 겪는 예상치 못한 레이아웃 이동이 적을수록 더 좋은 웹사이트이다.</li>
</ul>
<h3 id="1252-의미">12.5.2 의미</h3>
<ul>
<li>누적 레이아웃 이동은 사용자의 가시적인 콘텐츠에 영향을 미쳐야 하기 때문에 뷰포트 내부의 요소에 대해서만 측정한다.</li>
<li>최초 렌더링이 시작된 위치에서 만약 레이아웃의 이동이 발생한다면 누적 레이아웃 이동 점수로 기록하게 된다.</li>
<li>단순히 요소가 추가된다고 해서 무조건 누적 레이아웃 이동으로 간주되는 것은 아니다. 요소가 추가됐다 하더라도 다른 요소의 시작 위치에 영향을 미치지 않았다면 레이아웃 이동으로 간주되지 않는다.</li>
<li>사용자 액션으로 인해 발생한 레이아웃 이동은 점수에 포함되지 않는다. - 이 점수를 계산할 때 포함되는 내용은 다음과 같다<ul>
<li>영향분율: 레이아웃 이동이 발생한 요소의 전체 높이와 뷰포트 높이의 비율을 의미한다</li>
<li>거리분율: 레이아웃 이동이 발생한 요소가 뷰포트 대비 얼마나 이동했는지를 의미한다</li>
</ul>
</li>
</ul>
<h3 id="1253-예제">12.5.3 예제</h3>
<ul>
<li>생략</li>
</ul>
<h3 id="1254-기준-점수">12.5.4 기준 점수</h3>
<ul>
<li>좋은 점수는 <strong>0.1 이하</strong>인 경우, 0.25 이하인 경우 보통이며 그 외에는 개선이 필요한 나쁜 점수로 보고된다</li>
</ul>
<h3 id="1255-개선-방안">12.5.5 개선 방안</h3>
<h4 id="삽입이-예상되는-요소를-위한-추가적인-공간-확보">삽입이 예상되는 요소를 위한 추가적인 공간 확보</h4>
<ul>
<li>useEffect의 내부에서 요소에 영향을 미치는 작업, 특히 뷰포트 내부에서 노출될 확률이 높은 작업은 최소화하는 것이 좋다.</li>
<li>스켈레톤 UI처럼 미리 무언가 동적으로 뜰 것으로 예상되는 공간을 미리 확보해 두는 것도 좋은 방법이다. 레이아웃 이동을 막으면서 클라이언트 시점에 정해지는 콘텐츠를 안정적으로 보여줄 수 있다</li>
<li>가장 좋은 방법은 서버 사이드 렌더링이다. 서버에서 이러한 동적인 요소의 유무를 사전에 판단해 클라이언트에 HTML을 미리 제공해 준다면 클라이언트에서는 이러한 고민을 할 필요 없이 깔끔하게 처리할 수 있다.</li>
</ul>
<h4 id="폰트-로딩-최적화">폰트 로딩 최적화</h4>
<ul>
<li><p>폰트로 인해 레이아웃 이동이 발생할 수 있는 문제는 아래 2가지이다</p>
<ul>
<li>FOUT: HTML 문서에서 지정한 폰트가 보이지 않고 대체 기본 폰트로 보이다가 뒤늦게 폰트가 적용되는 현상</li>
<li>FOIT: HTML 문서에서 지정한 폰트가 보이지 않고, 기본 폰트도 없어서 텍스트가 없는 채로 있다가 뒤늦게 폰트가 로딩되면서 페이지에 렌더링되는 현상</li>
</ul>
</li>
<li><p>사용자 기기의 기본 폰트 이외에 다른 폰트로 웹페이지를 보여주고 싶다면 아래와 같은 점을 유념해야 한다</p>
<ul>
<li><strong><code>&lt;link&gt;</code>의 preload 사용</strong><ul>
<li><code>&lt;link&gt;</code> 요소의 rel=preload는 페이지에서 즉시 필요로 하는 리소스를 명시하는 기능이다.</li>
<li>preload로 지정된 요소는 웹페이지의 생명주기에서 초기에 불러와야 하는 중요한 리소르로 간주되므로 브라우저는 리소를 더 빠르게 사용할 수 있도록 준비해 준다.</li>
<li>따라서 rel=preload로 스타일이나 폰트를 지정하면 페이지의 렌더링을 가로막거나 레이아웃을 방해할 가능성이 줄어든다.</li>
</ul>
</li>
<li><strong>font-family</strong><ul>
<li>폰트를 불러올 수 있는 방법은 크게 5개로 나뉜다</li>
<li>auto(기본값): 브라우저가 폰트를 불러오는 방법을 결정한다.</li>
<li>block: 폰트가 로딩되기 전까지 렌더링 중단(최대 3초), 웹 폰트의 로딩이 완료되면 비로스 폰트 적용</li>
<li>swap: 앞서 언급한 FOUT 방식, 폴백 폰트로 렌더링한 다음, 웹 폰트의 로딩이 완료되면 웹 폰트 적용</li>
<li>fallback: 100ms간 텍스트가 보이지 않고 그 이후에 폴백 폰트로 렌더링, 3초 안에 폰트가 로딩되면 해당 웹 폰트로 전환하고, 그렇지 않으면 폴백 폰트 유지</li>
<li>optional: fallback과 유사, 100ms 동안 보이지 않고 폴백 폰트로 렌더링한다는 점은 동일하나 네트워크 상태를 파악해 0.1초 이내로 폰트가 다운로드되지 않으면 해당 폰트를 보여주지 않음</li>
</ul>
</li>
</ul>
</li>
<li><p>요약하자면 최대한 중요한 폰트의 다운로드를 우선순위에 밀어넣고, 이 우선순위를 활용했음에도 빠르게 로딩하는데 실패했다면 다음을 기약하고 기본 폰트를 노출하는 것이다.</p>
</li>
</ul>
<h4 id="적절한-이미지-크기-설정">적절한 이미지 크기 설정</h4>
<ul>
<li><p>많은 웹사이트가 반응형 웹사이트를 구현하면서 누적 레이아웃 이동이 커지는 결과를 낳게 되었다</p>
</li>
<li><p><code>width:100% height:auto</code>로 지정하면 이미지의 높이에 자동으로 비례해서 auto를 설정해준다. 하지만 이미지가 완전히 다운로드되기 전까지는 높이를 알 수 없기 때문에 이미지의 높이를 높게 잡아 뒀다가 레이아웃 이동이 크게 발생한다</p>
</li>
<li><p>적절한 이미지 크기를 설정하기 위한 방법은 아래와 같다</p>
<ul>
<li><p><strong>width, height 지정</strong></p>
<ul>
<li>width:100% height:auto; 와 함께 width, height를 원하는 비율로 지정하면 브라우저가 이미지를 로딩하기 전에 적절한 가로세로 비율을 계산해 이미지가 표시되는 만큼 면적을 할당해 둔다.</li>
<li>이는 aspect-ratio 속성 덕분인데, 이 속성은 브라우저의 유저 에이전트 스타일시트에 포함돼 있으며, 이미지의 가로세로 비율을 자동으로 맞춰주는 역할을 한다</li>
</ul>
<pre><code class="language-js">import &quot;./style.css&quot;; // width: 100%, height: auto;

export default function App() {
  return (
    &lt;div className=&quot;App&quot;&gt;
      &lt;img src=&quot;/image.jpg&quot; alt=&quot;이미지&quot; width=&quot;1600&quot; height=&quot;900&quot; /&gt;
    &lt;/div&gt;
  );
}</code></pre>
<ul>
<li><strong>만약 뷰포트 너비에 맞춰서 다른 이미지를 제공하고 싶은 경우 srcset 속성을 사용하는 것이 좋다</strong></li>
</ul>
<pre><code class="language-html">&lt;img
  width=&quot;1000&quot;
  height=&quot;1000&quot;
  src=&quot;image-1000.jpg&quot;
  srcset=&quot;image-1000.jpg 1000w, image-2000.jpg 2000w, image-3000.jpg 3000w&quot;
  alt=&quot;이미지&quot;
/&gt;</code></pre>
</li>
</ul>
</li>
</ul>
<h3 id="1256-핵심-웹-지표는-아니지만-성능-확인에-중요한-지표들">12.5.6 핵심 웹 지표는 아니지만 성능 확인에 중요한 지표들</h3>
<h4 id="최초-바이트까지의-시간time-to-first-byte-ttfb">최초 바이트까지의 시간(Time To First Byte, TTFB)</h4>
<ul>
<li>페이지를 요청했을 때 요청이 완전히 완료되는 데 걸리는 시간을 측정하는 것이 아니라 최초의 응답이 오는 바이트까지가 얼마나 걸리는지를 측정하는 지표이다.</li>
<li>이 지표는 600ms 이상 걸릴 경우 개선이 필요한 것으로 간주된다</li>
<li>최초 바이트까지의 시간을 개선하기 위한 고려사항<ul>
<li>서버 사이드 렌더링을 수행하고 있다면 로직와 API 호출을 최적화해서 페이지를 빨리 준비 시켜야 한다</li>
<li>웹페이지의 주된 방문객의 국적을 파악해 해당 국적과 가깝게 서버를 위치시키는 것이 좋다</li>
<li>리액트 서버 사이드 렌더링이라면 renderToNodeStream, renderToStaticNodeStream과 같은 스트리밍 API를 사용하는 것이 좋다</li>
</ul>
</li>
</ul>
<h4 id="최초-콘텐츠풀-페인트fcp">최초 콘텐츠풀 페인트(FCP)</h4>
<ul>
<li>웹사이트에 접속한 순간부터 페이지에 뭐라도 뜨기 시작한 시점까지의 시간이다</li>
<li>일반적으로 좋은 점수는 <strong>1.8초 이내</strong>이며, 3.0초 이내는 보통, 그 이후는 개선이 필요하다</li>
<li>FCP 개선방법<ul>
<li>TTFB를 개선한다. 렌더링을 최대한 빠르게 하기 위해 최초 바이트까지의 시간을 단축해야 한다</li>
<li>렌더링을 가로막는 리소스를 최적화한다</li>
<li>페이지 리다이렉트를 최소화한다.</li>
<li>DOM이 복잡하고 크다면 렌더링되는데 시간이 오래 걸리기 때문에 크기를 최적화한다</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트] 모던 리액트 딥다이브 10장]]></title>
            <link>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-10%EC%9E%A5</link>
            <guid>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-10%EC%9E%A5</guid>
            <pubDate>Sun, 15 Dec 2024 03:33:12 GMT</pubDate>
            <description><![CDATA[<h1 id="10장-리액트-17과-18의-변경-사항-살펴보기">10장 리액트 17과 18의 변경 사항 살펴보기</h1>
<p>대부분의 리액트 프로젝트들이 최소한 훅과 함수 컴포넌트를 사용하기 위해 16.8 버전을 기반으로 개발됐고, 이것이 고착화되어 더 이상 버전 업하지 않고 운영되고 있다. 그래서 리액트 버전 17과 18 버전은 비교적 많이 사용하고 있진 않다.</p>
<h2 id="101-리액트-17-버전-살펴보기">10.1 리액트 17 버전 살펴보기</h2>
<ul>
<li>16 버전과 다르게 새롭게 추가된 기능이 없으며 호환성이 깨지는 변경 사항, 즉 기존에 사용하던 코드의 수정을 필요로 하는 변경 사항을 최소화했다는 점을 가장 큰 특징으로 꼽는다.</li>
<li>그래서 16 버전에서 17 버전의 버전 업은 큰 부담없이 할 수 있는 작업이라 볼 수 있다.</li>
</ul>
<h3 id="1011-리액트의-점진적인-업그레이드">10.1.1 리액트의 점진적인 업그레이드</h3>
<h4 id="16버전까지의-업그레이드-전략">16버전까지의 업그레이드 전략</h4>
<ul>
<li>리액트의 16버전까지는 유의적 버전 전략에 맞춰 업데이트되고 있었다. 그래서 새로운 주 버전이 릴리즈되면 이전 버전에서의 API 제공을 완전히 중단해 버리고, 전체 애플리케이션을 새롭게 업그레이드하기를 요구하고 있었다.</li>
<li>따라서 레거시 애플리케이션을 관리하는 개발자는 전체 웹 서비스가 새로운 버전으로 완전히 넘어가버리거나, 계속 현재(과거 버전)에 머물러 있어야 한다.</li>
</ul>
<h4 id="17버전-이후부터의-업그레이드-전략">17버전 이후부터의 업그레이드 전략</h4>
<ul>
<li>17버전부터는 점진적인 업그레이드가 가능해진다. 즉, 두 개 이상의 리액트 버전이 웹 애플리케이션에 존재할 수 있다.</li>
<li>2개 이상의 버전이 있는 것이 이상적인 방법은 아니지만, 한 번에 버전을 올리기에 부담이 되는 규모가 큰 애플리케이션의 경우에는 충분히 고려해 볼만한 선택지가 될 수 있다.</li>
</ul>
<h3 id="1012-이벤트-위임-방식의-변경">10.1.2 이벤트 위임 방식의 변경</h3>
<p>리액트는 이벤트 핸들러를 해당 이벤트 핸들러를 추가한 각각의 DOM 요소에 부탁하는 것이 아니라, 이벤트(click, change) 당 하나의 핸들러를 루트에 부착한다. 이를 <code>이벤트 위임</code>이라고 한다. 이벤트 위임을 이해하려면 이벤트가 어떤 단계로 구성돼 있는지 먼저 알아야 한다.</p>
<h4 id="이벤트의-단계">이벤트의 단계</h4>
<ol>
<li><strong>캡처</strong><br>이벤트 핸들러가 트리 최상단 요소에 부터 시작해서 실제 이벤트가 발생한 타깃 요소까지 내려가는 것을 의미한다.</li>
<li><strong>타깃</strong>
이벤트 핸들러가 타깃 노드에 도달하는 단계다. 이 단계에서 이벤트가 호출된다.</li>
<li><strong>버블링</strong>
이벤트가 발생한 요소에서부터 시작해 최상위 요소까지 다시 올라간다.</li>
</ol>
<pre><code>&lt;ul&gt;
    &lt;li /&gt;
    &lt;li /&gt;
    &lt;li /&gt;
    &lt;li /&gt;
    &lt;!-- ... --&gt;
&lt;/ul&gt;</code></pre><ul>
<li>만약 모든 li 요소에 이벤트가 필요다면 이 li에 이벤트를 다 추가할 수 있지만 ul에만 추가해서 이벤트를 위임한다면 ul의 자식에 li가 추가 또는 삭제되더라도 이벤트 핸들러도 똑같이 추가 수정할 필요도 없고, 이벤트 추가를 한 번만 하면 되므로 좀 더 효율적으로 관리할 수 있게 된다.</li>
<li>리액트는 최초 릴리스부터 이러한 이벤트 위임을 적극적으로 사용했다. 앞서 언급했듯이 리액트는 이벤트 핸들러를 각 요소가 아닌 document에 연결해서 이벤트를 좀 더 효율적으로 관리한다.</li>
</ul>
<h4 id="17버전부터-바뀐-이벤트-위임-요소">17버전부터 바뀐 이벤트 위임 요소</h4>
<ul>
<li>리액트 17부터는 이러한 이벤트 위임이 모두 document가 아닌 리액트 컴포넌트 최상단 트리, 즉 루트 요소로 바뀌었다.</li>
<li>그 이유는 점진적인 업그레이드를 지원하고, 바닐라 자바스크립트 코드 또는 jQuery 등이 혼재돼 있는 경우 혼란을 방지하기 위해서다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/eb44c45c-8f57-4dfa-962e-b66defd92db8/image.png" alt=""></p>
<h3 id="1013-import-react-from-react가-더-이상-필요없다새로운-jsx-transform">10.1.3 import React from &#39;react&#39;가 더 이상 필요없다:새로운 JSX transform</h3>
<h4 id="16버전까지는-import-react-from-react가-필요한-리액트">16버전까지는 import React from &#39;react&#39;가 필요한 리액트</h4>
<ul>
<li>JSX는 브라우저가 이해할 수 있는 코드가 아니므로 바벨이나 타입스크립트를 활용해 JSX를 실행하기 위해 일반적으로 자바스크립트로 변환하는 과정이 꼭 필요하다.</li>
<li>이 변환 작업이 개발자에게 보이지 않는 리액트 단에서 이뤄지고 있다. 16버전까지는 이러한 JSX 변환을 사용하기 위해 코드 내에서 React를 사용하는 구문이 없더라도 import React from &#39;react&#39;가 필요했다.</li>
</ul>
<pre><code class="language-js">const Component = (
  &lt;div&gt;
    &lt;span&gt;hello world&lt;/span&gt;
  &lt;/div&gt;
);

// 리액트 16에서는 이렇게 변환된다
var Component = React.createElement(
  &quot;div&quot;,
  null,
  React.craeteElement(&quot;span&quot;, null, &quot;hello world&quot;)
);</code></pre>
<ul>
<li>변환 결과를 보면 왜 import 문이 필요한지 알 수 있다. JSX 코드를 변경은 해주지만 React.createElement를 수행할 때 필요한 import문까지는 추가해주지 않기 때문이다.</li>
</ul>
<h4 id="17버전-이후부터는-import-문이-필요없음">17버전 이후부터는 import 문이 필요없음</h4>
<ul>
<li>리액트 17부터는 바벨과 협력해 import 구문 없이도 JSX를 변환할 수 있게 됐다.</li>
<li>이 새로운 변환 식은 import문이 필요 없다는 장점 외에도, 불필요한 import 구문을 삭제해 번들링 크기를 약간 줄일 수 있고, 컴포넌트 작성을 더욱 간결하게 해준다.</li>
<li>이전 코드는 17버전에서는 아래와 같이 변환된다</li>
</ul>
<pre><code class="language-js">&quot;use strict&quot;;

var _jsxRuntime = require(&quot;react/jsx-runtime&quot;);

var Component = (0, _jsxRuntime.jsx)(&quot;div&quot;, {
  children: (0, _jsxRuntime.jsx)(&quot;span&quot;, {
    children: &quot;hello world&quot;,
  }),
});</code></pre>
<ul>
<li>React.createElement가 제거되고, require() 구문이 추가되었다.</li>
<li>JSX를 변환할 때 필요한 모듈인 react/jsx-runtime을 불러오는 require 구문도 같이 추가되므로 import 문을 작성하지 않아도 된다</li>
</ul>
<h3 id="1014-그-밖의-주요-변경-사항">10.1.4 그 밖의 주요 변경 사항</h3>
<h4 id="이벤트-풀링-제거">이벤트 풀링 제거</h4>
<ul>
<li>리액트 16에서는 이른바 이벤트 풀링이라 불리는 기능이 있었다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/38594144-5623-4194-aa58-b035b84b3160/image.png" alt=""></p>
<ul>
<li><p><strong>이벤트 풀링 시스템 순서</strong></p>
<ol>
<li>이벤트 핸들러가 이벤트를 발생시킨다</li>
<li>합성 이벤트 풀에서 합성 이벤트 객체에 대한 참조를 가져온다</li>
<li>이 이벤트 정보를 합성 이벤트 객체에 넣어준다</li>
<li>유저가 지정한 이벤트 리스너가 실행된다</li>
<li>이벤트 객체가 초기화되고 다시 이벤트 풀로 돌아간다</li>
</ol>
</li>
<li><p>언뜻 보기에는 이벤트 풀에 잇는 합성 이벤트를 반복적으로 사용할 수 있어서 효과적으로 보이지만 풀에서 이벤트를 받아오고, 이벤트가 종료되자마자 다시 초기화하는 방식은 분명 사용하는 쪽에서 직관적이지 않았다</p>
</li>
<li><p>비동기 코드로 이벤트 핸들러를 접근하기 위해서는 별도 메모리 공간에 합성 이벤트 객체를 할당해야 한다는 점, 그리고 모던 브라우저에서는 이와 같은 방식이 성능 향상에 크게 도움이 안 된다는 점 때문에 이벤트 풀링 개념이 삭제됐다.</p>
</li>
</ul>
<h4 id="useeffect-클린업-함수의-비동기-실행">useEffect 클린업 함수의 비동기 실행</h4>
<ul>
<li>리액트의 useEffect에 있는 클린업 함수는 리액트 16 버전까지는 동기적으로 처리됐다. 동기적으로 실행되기 때문에 다른 작업을 실행할 수 없어 불필요한 성능 저하로 이어지는 문제가 존재했다.</li>
<li>리액트 17버전부터는 화면이 완전히 업데이트된 이후에 클린업 함수가 비동기적으로 실행된다. 명확히 말하면 클린업 함수는 컴포넌트의 커밋 단계가 완료될 때까지 지연된다.</li>
</ul>
<h4 id="컴포넌트의-undefined-반환에-대한-일괄적인-처리">컴포넌트의 undefined 반환에 대한 일괄적인 처리</h4>
<ul>
<li>리액트 16과 17버전은 컴포넌트 내부에서 undefined를 반환하면 오류가 발생한다. 이는 의도치 않게 잘못된 반환으로 이한 실수를 방지하기 위해서다.</li>
<li>하지만 리액트 16에서 forwardRef나 memo에서 undefined를 반환하는 경우에는 별다른 에러가 발생하지 않는 문제가 있고, 리액트 17부터는 에러가 정상적으로 발생한다</li>
</ul>
<h2 id="102-리액트-18-버전-살펴보기">10.2 리액트 18 버전 살펴보기</h2>
<p>리액트 18에서는 리액트 17에서 하지 못했던 다양한 기능들이 추가됐다. 그중 가장 큰 변경점은 <code>동시성 지원</code>이다.</p>
<h3 id="1021-새로-추가된-훅-살펴보기">10.2.1 새로 추가된 훅 살펴보기</h3>
<h4 id="useid"><a href="https://ko.react.dev/reference/react/useId">useId</a></h4>
<ul>
<li>컴포넌트별로 유니크한 값을 생성하는 새로운 훅이다.</li>
<li>기존에는 컴포넌트 내부에서 사용할 수 있는 유니크한 값을 생성하는 것은 어려운 일이었다.<ul>
<li>하나의 컴포넌트가 여러 군데에서 재사용되는 경우도 고려해야 하며, 리액트 컴포넌트 트리에서 컴포넌트가 가지는 모든 값이 겹치지 않고 다 달라야 한다는 제약도 있다.</li>
<li>또한 서버 사이드 렌더링 환경에서 하이드레이션이 일어날 때도 서버와 클라이언트에서 동일한 값을 가져야 에러가 발생하지 않으므로 이러한 점도 고려해야 한다.</li>
</ul>
</li>
<li>useId를 사용하면 클라이언트와 서버에서 불일치를 피하면서 컴포넌트 내부의 고유한 값을 생성할 수 있게 됐다.<ul>
<li>같은 컴포넌트 임에도 서로 인스턴스가 다르면 다른 랜덤한 값을 만들어 내며, 이 값들이 모두 유니크한다.</li>
<li>또한 서버 사이드와 클라이언트 간에 동일한 값이 생성되어 하이드레이션 이슈도 발생하지 않는 것을 알 수 있다.</li>
</ul>
</li>
</ul>
<h4 id="usetransition"><a href="https://ko.react.dev/reference/react/useTransition">useTransition</a></h4>
<ul>
<li>UI 변경을 가로막지 않고 상태를 업데이트할 수 있는 리액트 훅이다.</li>
<li>상태 업데이트를 긴급하지 않는 것으로 간주해 무거운 렌더링 작업을 조금 미룰 수 있으며, 사용자에게 조금 더 나은 사용자 경험을 제공할 수 있다.</li>
<li>리액트 18의 변경 사항의 핵심 중 하나인 <code>동시성</code>을 다룰 수 있는 새로운 훅이다.<ul>
<li>과거 리액트의 모든 렌더링은 동기적으로 작동해 느린 렌더링 작업이 있을 경우 애플리케이션 전체적으로 영향을 끼쳤다</li>
<li>useTransistion과 같은 동시성을 지원하는 기능을 사용하면 느린 렌더링 과정에서 로딩 화면을 보여주거나 혹은 지금 진행 중인 렌더링을 버리고 새로운 상태값으로 다시 렌더링하는 등의 작업을 할 수 있게 된다.</li>
</ul>
</li>
<li>예시</li>
</ul>
<pre><code class="language-js">import { useState, useTransition } from &#39;react&#39;
// ...

export default function TabContainer() {
  // isPending : 상태 업데이트가 진행 중인지 확인할 수 있는 boolean 값
  // startTransition : 긴급하지 않은 상태 업데이트로 간주할 set 함수를 넣어둘 수 있는 함수를 인수로 받음
  const [isPending, startTransition] = useTransition()
  const [tab, setTab] = useState&lt;Tab&gt;(&#39;about&#39;);

  function selectTab(nextTab:Tab) {
    // 경우에 따라 여러 개의 setter를 넣어줄 수도 있다
    startTransittion(()=&gt; {
      setTab(nextTab)
    })
  }

  return (
    &lt;&gt;
    {/*...*/}
    {isPending ? (
      &#39;로딩 중&#39;
    ):(
      &lt;&gt;
        {tab === &#39;about&#39; &amp;&amp; &lt;About /&gt;}
        {tab === &#39;posts&#39; &amp;&amp; &lt;Posts /&gt;}
        {tab === &#39;context&#39; &amp;&amp; &lt;Context /&gt;}
      &lt;/&gt;
    )}
  )
}</code></pre>
<ul>
<li><strong>사용 시 주의점</strong><ol>
<li>startTransition 내부는 반드시 setState와 같은 상태를 업데이트하는 함수와 관련된 작업만 넘길 수 있다.</li>
<li>startTransition으로 넘겨주는 상태 업데이트는 다른 모든 동기 업데이트로 인해 실행이 지연될 수 있다.</li>
<li>startTransition으로 넘겨주는 함수는 반드시 동기 함수여야 한다.</li>
</ol>
</li>
</ul>
<h4 id="usedeferredvalue"><a href="https://ko.react.dev/reference/react/useDeferredValue">useDeferredValue</a></h4>
<ul>
<li>리액트 컴포넌트 트리에서 리렌더링이 급하지 않은 부분을 지연할 수 있게 도와주는 훅이다.</li>
<li>디바운스 기법과 비슷하지만 고정된 지연 시간을 필요로 하는 디바운스와 달리 고정된 지연 시간없이 첫 번째 렌더링이 완료된 이후에 이 useDeferredValue로 지연된 렌더링을 수행한다는 장점이 있다</li>
<li>useTransition과의 차이점</li>
</ul>
<table>
<thead>
<tr>
<th><strong>차이점</strong></th>
<th><strong>useTransition</strong></th>
<th><strong>useDeferredValue</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>사용 방식</strong></td>
<td><code>state</code> 값을 업데이트하는 함수를 감싸서 사용</td>
<td><code>state</code> 값 자체만을 감싸서 사용</td>
</tr>
<tr>
<td><strong>사용하는 상황</strong></td>
<td>낮은 우선순위로 처리해야 할 작업에 대해 상태를 직접 업데이트할 수 있는 코드에 접근할 수 있을 때 사용</td>
<td>컴포넌트의 <code>props</code>처럼 상태 업데이트에 관여하지 않고 값만 받아야 하는 상황에서 사용</td>
</tr>
</tbody></table>
<h4 id="usesyncexternalstore"><a href="https://ko.react.dev/reference/react/useSyncExternalStore">useSyncExternalStore</a></h4>
<ul>
<li>외부 store를 구독할 수 있는 훅이다</li>
<li>애플리케이션 코드에 직접적으로 사용할 일은 많지 않지만 사용 중인 관리 라이브러리가 외부에서 상태를 관리하고 있다면 이 useSyncExternalStore를 통해 외부 데이터 소스의 변경을 추적하고 있는지 반드시 확인해야 한다</li>
</ul>
<h4 id="useinsertioneffect"><a href="https://ko.react.dev/reference/react/useInsertionEffect">useInsertionEffect</a></h4>
<ul>
<li>CSS-in-js 라이브러리를 위한 훅으로, layout Effects 가 실행되기 전에 전체 요소를 DOM 에 주입한다</li>
<li>실제 애플리케이션 코드를 작성할 때는 사용될 일이 거의 없으며 라이브러리를 작성하는 경우가 아니라면 참고만 하는 것이 좋다</li>
<li>CSS의 추가 및 수정은 브라우저에서 렌더링하는 작업 대부분을 다시 계산해 작업해야 하는데, 이는 매우 무거운 작업이다. 클라이언트 렌더링 시에 이러한 작업이 발생하지 않도록 서버 사이드에서 스타일 코드를 삽입을 할 수 있도록 도와주는 훅이다</li>
</ul>
<h3 id="1022-react-domclient">10.2.2 react-dom/client</h3>
<p>클라이언트에서 리액트 트리를 만들 때 사용되는 API가 변경됐다. 만약 리액트 18 이하 버전에서 만든 create-react-app으로 프로젝트를 유지보수 중이라면 리액트 18로 업그레이드할 때 반드시 index.{t|j}sx에 있는 내용을 변경해야 한다</p>
<h4 id="creatroot">creatRoot</h4>
<ul>
<li>기존의 react-dom에 있던 render 메서드를 대체할 새로운 메서드다. 리액트 18의 기능을 사용하고 싶다면 craeteRoot와 render를 함께 사용해야 한다</li>
</ul>
<h4 id="hydrateroot">hydrateRoot</h4>
<ul>
<li>서버 사이드 렌더링 애플리케이션에서 하이드레이션을 하기 위한 새로운 메서드다.</li>
</ul>
<h3 id="1023-react-domserver">10.2.3 react-dom/server</h3>
<p>클라이언트의 변화와 마찬가지로 서버에서도 컴포넌트를 생성하는 API에 변경이 있었다.</p>
<h4 id="rendertopipeablestream">renderToPipeableStream</h4>
<ul>
<li>리액트 컴포넌트를 HTML로 렌더링하는 메서드다.</li>
<li>스트림을 지원하는 메서드로, HTML을 점진적으로 렌더링하고 클라이언트에서는 중간의 script를 삽입하는 등의 작업을 할 수 있다.</li>
<li>이를 통해 서버에서는 Suspense를 사용해 빠르게 렌더링이 필요한 부분을 먼저 렌더링할 수 있고, 값비싼 연산으로 구성된 부분은 이후에 렌더링되게끔 할 수 있다.</li>
</ul>
<h4 id="rendertoreadablestream">renderToReadableStream</h4>
<ul>
<li>서버 환경이 아닌 클라우드플레어나 디노 같은 웹 스트림을 사용하는 모던 엣지 런타임 환경에서 사용되는 메서드다</li>
<li>실제로 웹 애플리케이션을 개발하는 경우에는 이 메서드를 사용할 일이 거의 없다</li>
</ul>
<h3 id="1024-자동-배치automatic-batching">10.2.4 자동 배치(Automatic Batching)</h3>
<ul>
<li><p>자동 배치는 리액트가 여러 상태 업데이트를 하나의 리렌더링으로 묶어서 성능을 향상시키는 방법을 의미한다</p>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/2d203f05-4d8e-4a9e-8453-47e9f5af4cd5/image.png" alt=""></p>
</li>
</ul>
<ul>
<li>위 사진은 리액트 17과 18버전에서 버튼 클릭 한 번에 두 개 이상의 state를 동시에 업데이트하는 코드를 실행한 결과이다</li>
<li>17에서는 자동 배치가 되지 않아 두 번의 리렌더링이 일어났지만 18에서는 자동 배치 덕분에 리렌더링이 단 한 번만 일어났다</li>
</ul>
<h3 id="1025-더욱-엄격해진-엄격-모드">10.2.5 더욱 엄격해진 엄격 모드</h3>
<h4 id="리액트의-엄격-모드란">리액트의 엄격 모드란?</h4>
<ul>
<li>리액트에서 제공하는 컴포넌트 중 하나로, 리액트 애플리케이션에서 발생할 수도 있는 잠재적인 버그를 찾는 데 도움이 되는 컴포넌트다.</li>
<li>리액트에서 널리 알려져 있는 Fragment나 Suspense와 마찬가지로 컴포넌트 형태로 선언해서 사용할 수 있다</li>
</ul>
<pre><code>import { StrictMode } from &#39;react&#39;
import { craeteRoot } from &#39;react-dom/client&#39;

const root = createRoot(document.getElementById(&#39;root&#39;))

root.render(
    &lt;StrictMode&gt;
        &lt;App /&gt;
    &lt;/StrictMode&gt;,
)</code></pre><ul>
<li>이 엄격 모드에서 수행하는 모드는 모두 개발자 모드에서만 작동하고, 프로덕션 모드에서는 작동하지 않는다.</li>
</ul>
<h4 id="리액트-엄격-모드에서-하는-작업">리액트 엄격 모드에서 하는 작업</h4>
<ol>
<li><p><strong>더이상 안전하지 않은 특정 생명주기를 사용하는 컴포넌트에 대한 경고</strong></p>
<ul>
<li>리액트 클래스 컴포넌트에서 사용되는 생명주기 메서드 중 일부는 더 이상 사용할 수 없게 됐다.</li>
<li>이러한 메서드는 16.3부터 UNSAFE_가 붙게 됐고, 이후 16 버전에서는 UNSAFE_가 붙지 않는 생명주기 메서드를 사용하게 되면 경고로그가 기록되고(?) =&gt; * UNSAFE_가 붙은 생명주기 메서드는 경고가 발생하지 않는건가?*,<ul>
<li><code>UNSAFE_가 없는 메서드</code>: 리액트 16.3 이후 모든 환경(엄격 모드 여부와 상관없이)에서 경고가 발생한다</li>
<li><code>UNSAFE_가 붙은 메서드</code>: 동작은 가능하나, StrictMode에서만 경고가 발생한다</li>
</ul>
</li>
<li>17 버전부터는 UNSAFE_가 붙은 세 메서드만 남고 나머지는 다 삭제됐다. 그럼에도 불구하고 UNSAFE_가 붙은 생명주기 메서드를 사용하면서 엄격 모드를 켜게 된다면 경고 메시지가 발생한다</li>
</ul>
</li>
<li><p><strong>문자열 ref 사용 금지</strong></p>
</li>
</ol>
<ul>
<li>과거 리액트에서는 레거시 문자열 ref라 해서 createRef가 없어도 컴포넌트 내부에서 문자열로 ref를 생성하고, 이를 사용해 DOM 노드를 참조하는 것이 가능했다.</li>
<li>하지만 아래와 같은 문제점이 있어 사용이 금지됐다
① 문자열로 값을 주는 것은 여러 컴포넌트에 걸쳐 사용될 수 있으므로 충돌의 여지가 있다
② 앞의 코드에서도 볼 수 있듯이 단순히 문자열로만 존재하기 때문에 실제로 어떤 ref에서 참조되고 있는지 파악하기 어렵다
③ 리액트가 계속해서 현재 렌더링되고 있는 컴포넌트의 ref의 값을 추적해야 하기 때문에 성능 이슈가 있다</li>
</ul>
<ol start="3">
<li><strong>findDOMNode에 대한 경고 출력</strong></li>
</ol>
<ul>
<li>findDOMNode는 클래스 컴포넌트 인스턴스에서 실제 DOM 요소에 대한 참조를 가져올 수 있는 현재는 사용하는 것이 권장되지 않는 메서드다</li>
</ul>
<ol start="4">
<li><strong>구 Context API 사용 시 발생하는 경고</strong></li>
</ol>
<ul>
<li>childContextType와 getChildContext를 사용하는 구 리액트 Context API를 사용하면 엄격 모드에서는 경고를 발생시킨다</li>
</ul>
<ol start="5">
<li><strong>예상치 못한 부작용(side-effects) 검사</strong></li>
</ol>
<ul>
<li>리액트 엄격 모드 내부에서 다음 내용을 의도적으로 이중으로 호출한다<ul>
<li>클래스 컴포넌트의 constructor, render, shouldComponentUpdate, getDerivedStateFromProps</li>
<li>클래스 컴포넌트의 setState의 첫 번째 인수</li>
<li>함수 컴포넌트의 body</li>
<li>useState, useMemo, useReducer에 전달되는 함수</li>
</ul>
</li>
<li>이중으로 호출하는 이유는 함수형 프로그래밍의 원칙에 따라 리액트의 모든 컴포넌트는 항상 순수하다고 가정하기 때문에 항상 순수한 결과를 내고 있는 개발자에게 확인시켜주기 위함이다</li>
</ul>
<h4 id="리액트-18에서-추가된-엄격모드">리액트 18에서 추가된 엄격모드</h4>
<ul>
<li>향후 리액트에서는 컴포넌트가 마운트 해제된 상태에서도 컴포넌트 내부의 상태값을 유지할 수 있는 기능을 제공할 예정이라고 리액트 팀에서 밝혔다.</li>
<li>예를 들어 사용자가 뒤로가기를 했다가 다시 현재 화면으로 돌아왔을 때 리액트가 즉시 이전의 상태를 그대로 유지해 표시할 준비를 하는 기능이 추가되는 것이다.</li>
<li>이러한 기능을 지원하기 위해 컴포넌트가 최초에 마운트될 때 자동으로 모든 컴포넌트를 마운트 해제하고 두 번째 마운트에서 이전 상태를 복원하는 기능을 엄격모드의 개발모드에 도입했다</li>
</ul>
<h3 id="1026-suspense-기능-강화">10.2.6 Suspense 기능 강화</h3>
<ul>
<li>Suspense는 리액트 16.6 버전에서 실험 버전으로 도입된 기능으로 컴포넌트를 동적으로 가져올 수 있게 도와주는 기능이다</li>
</ul>
<h4 id="리액트-18-버전에서-suspense가-변경된-내용">리액트 18 버전에서 Suspense가 변경된 내용</h4>
<ul>
<li>아직 마운트되기 직전임에도 effect가 빠르게 실행되는 문제가 수정됐다. 이제 컴포넌트가 실제로 화면에 노출될 때 effect가 실행된다</li>
<li>Suspense로 인해 컴포넌트가 보이거나 사라질 때도 effect가 정상적으로 실행된다.</li>
<li>Suspense를 이제 서버에서도 실행할 수 있게 된다. 서버에서는 일단 fallback 상태의 트리를 클라이언트에 제공하고, 불러올 준비가 된다면 자연스럽게 렌더링된다</li>
<li>Suspense 내에 스트롤링이 추가됐다. 화면이 너무 자주 업데이트되어 시각적으로 방해받는 것을 방지하기 위해 리액트는 다음 렌더링을 보여주기 전에 잠시 대기한다. 즉, 중첩된 Suspense의 fallback이 있다면 자동으로 스트롤되어 최대한 자연스럽게 보여주기 위해 노력한다</li>
</ul>
<h3 id="1027-인터넷-익스플로러-지원-중단에-따른-추가-폴리필-필요">10.2.7 인터넷 익스플로러 지원 중단에 따른 추가 폴리필 필요</h3>
<ul>
<li>이제 리액트를 사용하는 코드에서 아래와 같은 최신 자바스크립트 기능을 사용할 수 있다는 가정하에 배포된다<ul>
<li>Promise : 비동기 연산이 종료된 이후에 실패 또는 결괏값을 확인할 수 있는 객체</li>
<li>Symbol : 자바스크립트의 새로운 데이터 형식으로, 익명의 객체 속성을 만들 수 있는 특성을 가진 객체</li>
<li>Object : 객체의 열거 가능한 모든 속성을 다른 객체로 붙여 넣는 메서드</li>
</ul>
</li>
<li>이러한 세 기능을 지원하지 않는 브라우저에서 서비스해야 한다면 이 세 가지 기능을 위한 폴리필을 반드시 추가해야 한다.</li>
</ul>
<h3 id="1028-그-밖에-알아두면-좋은-변경사항">10.2.8 그 밖에 알아두면 좋은 변경사항</h3>
<ul>
<li>이제 컴포넌트에서 undefined를 반환해도 에러가 발생하지 않는다. undefined 반환은 null 반환과 동일하게 처리된다</li>
<li>이와 마찬가지로 <code>&lt;Suspense fallback={undefined}&gt;</code>도 null과 동일하게 처리한다</li>
<li>renderToNodeStream이 지원 중단됐다. 그 대신 renderToPipealbeStream을 사용하는 것이 권장된다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[모던 리액트 딥다이브 9장]]></title>
            <link>https://velog.io/@hyohyo__/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-9%EC%9E%A5</link>
            <guid>https://velog.io/@hyohyo__/%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-9%EC%9E%A5</guid>
            <pubDate>Sat, 07 Dec 2024 14:48:26 GMT</pubDate>
            <description><![CDATA[<h1 id="9장-모던-리액트-개발-도구로-개발-및-배포-환경-구축하기">9장 모던 리액트 개발 도구로 개발 및 배포 환경 구축하기</h1>
<p>빈 폴더만 준비돼 있는 환경에서 가장 각광받는 라이브러리인 리액트와 Next.js로 실무에서도 쓸 수 있는 웹서비스를 구축해보기</p>
<h2 id="91-nextjs로-리액트-개발-환경-구축하기">9.1 Next.js로 리액트 개발 환경 구축하기</h2>
<ul>
<li>리액트와 Next.js에서 어떠한 설정이 필요한지 알기 위해서 package.json부터 하나씩 살펴보자</li>
<li>create-react-app은 더 이상 유지보수되지 않을 가능성이 큼</li>
<li>여러 리액트 기반 프레임워크를 제안하는 런처 형태로 변경될 예정이기 때문에 아무것도 없는 상태에서 리액트 프레임워크를 구축하는 방법을 공부해 둘 필요가 있다.</li>
</ul>
<h3 id="911-create-next-app-없이-하나씩-구축하기">9.1.1 create-next-app 없이 하나씩 구축하기</h3>
<h4 id="1-packagejson-만들기">1. package.json 만들기</h4>
<ul>
<li><p><code>npm init</code>을 실행하면 package.json을 만드는 CLI를 실행할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/2d0a5846-1a7f-4652-82e3-19cd2a335c68/image.png" alt=""></p>
</li>
</ul>
<h4 id="2-react-react-dom-next-설치">2. react, react-dom, next 설치</h4>
<ul>
<li>Next.js 프로젝트를 실행하는데 필요한 핵심 라이브러리를 설치한다</li>
</ul>
<pre><code class="language-bash">npm i react react-dom next</code></pre>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/2c16c843-33dd-43f0-ac9d-b602c0c3077b/image.png" alt=""></p>
<h4 id="3-devdependencies에-필요한-패키지-설치">3. devDependencies에 필요한 패키지 설치</h4>
<ul>
<li>typescript, @types/react, @types/react-dom, @types/node, eslint, eslint-config-next를 설치한다</li>
<li>그러면 기본적인 패키지 설치는 모두 완료된다</li>
</ul>
<pre><code class="language-bash">npm i typescript @types/react @types/react-dom @types/node eslint eslint-config-next</code></pre>
<h3 id="912-tsconfigjson-작성하기">9.1.2 tsconfig.json 작성하기</h3>
<ul>
<li>이제 타입스크립트 코드를 작성하기 위한 준비를 해야한다</li>
<li>타입스크립트 설정은 tsconfig.json에 기록한다.</li>
</ul>
<h4 id="tsconfigjson-작성-방법">tsconfig.json 작성 방법</h4>
<ul>
<li>본격적으로 작성하기 전에 JSON 최상단에 $schema 키와 아래와 같은 값을 넣어야 한다</li>
</ul>
<pre><code>{
  &quot;$schema&quot;: &quot;https://json.schemastore.org/tsconfig.json&quot;
}</code></pre><ul>
<li><code>$schema</code>란?<ul>
<li>schemaStore에서 제공해 주는 정보로, 해당 JSON 파일이 무엇을 의미하는지, 또 어떤 키와 어떤 값이 들어갈 수 있는지 알려주는 도구다.</li>
<li>올바른 값이 선언돼 있다면 IDE에서 자동 완성이 가능해진다</li>
</ul>
</li>
</ul>
<pre><code>{
  &quot;$schema&quot;: &quot;https://json.schemastore.org/tsconfig.json&quot;,
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es5&quot;,
    &quot;lib&quot;: [&quot;dom&quot;, &quot;dom.iterable&quot;, &quot;esnext&quot;],
    &quot;allowJs&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;strict&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;noEmit&quot;: true,
    &quot;esModuleInterop&quot;: true,
    &quot;module&quot;: &quot;esnext&quot;,
    &quot;moduleResolution&quot;: &quot;node&quot;,
    &quot;resolveJsonModule&quot;: true,
    &quot;isolatedModules&quot;: true,
    &quot;jsx&quot;: &quot;preserve&quot;,
    &quot;incremental&quot;: true,
    &quot;baseUrl&quot;: &quot;src&quot;,
    &quot;paths&quot;: {
      &quot;#pages/*&quot;: [&quot;pages/*&quot;],
      &quot;#hooks/*&quot;: [&quot;hooks/*&quot;],
      &quot;#types/*&quot;: [&quot;types/*&quot;],
      &quot;#components/*&quot;: [&quot;components/*&quot;],
      &quot;#utils/*&quot;: [&quot;utils/*&quot;]
    }
  },
  &quot;include&quot;: [&quot;next-env.d.ts&quot;, &quot;**/*.ts&quot;, &quot;**/*.tsx&quot;],
  &quot;exclude&quot;: [&quot;node_modules&quot;]
}</code></pre><ul>
<li>tsconfig.json에서 사용하는 옵션은 <a href="https://www.typescriptlang.org/tsconfig">공식 홈페이지</a>에서 확인할 수 있다</li>
</ul>
<h3 id="913-nextconfigjs-작성하기">9.1.3 next.config.js 작성하기</h3>
<ul>
<li>next.config.js가 제공하는 설정 파일은 버전별로 조금씩 다르다</li>
<li>자신이 사용하고 있는 next.config.js에서 사용 가능한 옵션을 확인하고 싶다면 <a href="https://github.com/vercel/next.js/blob/v12.3.1/packages/next/server/config-shared.ts">깃허브 저장소</a>를 방문해 확인할 수 있다.</li>
<li>책에서는 아래와 같이 옵션만 추가했다</li>
</ul>
<pre><code class="language-js">/** @type {import(&#39;next&#39;).NextConfig} */
const nextConfig = {
  // 리액트의 엄격모드를 활성화한다
  reactStrictMode: true,
  // 일반적으로 보안 취약점으로 취급되는 X-Powered-By 헤더를 제거한다
  poweredByHeader: false,
  // 빌드 시에 ESLint를 무시한다
  eslint: {
    ignoreDuringBuilds: true,
  },
};

module.exports = nextConfig;</code></pre>
<h3 id="914-eslint와-prettier-설정하기">9.1.4 ESLint와 Prettier 설정하기</h3>
<ul>
<li>eslint-config-next를 설치했지만 코드의 스타일링을 정의해주지 않는다.</li>
<li>코드 스타일링등 eslint-config-next가 해주지 않는 일반적인 ESLint 작업을 수행하기 위해 가장 설치 및 설정이 쉬운 @titicaca/eslint-config-triple을 설치해 사용한다</li>
</ul>
<pre><code class="language-bash">npm i @titicaca/eslint-config-triple --save-dev</code></pre>
<ul>
<li>@titicaca/eslint-config-triple에 대한 설정은 <a href="https://github.com/titicacadev/triple-config-kit#eslint-config-triple">깃허브 저장소</a>에 정리돼 있다</li>
<li>eslint-config-next와 eslint-config-triple이 함께 작동하게 하려면 .eslintrc.ts 파일에 다음과 같은 별도의 설정이 필요하다</li>
</ul>
<pre><code class="language-js">const path = require(&quot;path&quot;);

const createConfig = require(&quot;@titicaca/eslint-config-triple/create-config&quot;);

const { extends: extendConfigs, overrides } = createConfig({
  type: &quot;frontend&quot;,
  project: path.resolve(__dirname, &quot;./tsconfig.json&quot;),
});

module.exports = {
  // next/core-web-vitals 를 추가하면 두 가지 설정이 모두 적용된다
  extends: [...extendConfigs, &quot;next/core-web-vitals&quot;],
  overrides,
};</code></pre>
<h3 id="915-스타일-설정하기">9.1.5 스타일 설정하기</h3>
<ul>
<li>styled-components를 사용해서 애플리케이션에 스타일 적용해보기</li>
</ul>
<pre><code class="language-bash">npm i styled-components</code></pre>
<ul>
<li>위 명령어로 설치한 후 swc에 styled-components를 사용한다는 것을 알리기 위해 next.config.js에 <code>styledComponents: true</code>을 추가한다</li>
<li>추가적으로 pages/_document.tsx의 Head에 styled-components를 사용하기 위한 ServerStyleSheet를 추가한다</li>
</ul>
<h3 id="916-애플리케이션-코드-작성">9.1.6 애플리케이션 코드 작성</h3>
<ul>
<li>Next.js 애플리케이션의 폴더 구조는 src/pages 하단에 실제 페이지 라우팅과 관련된 파일을 기재하는 컨벤션만 지키면 된다</li>
<li>프로젝트 실행, 빌드, 린트와 관련된 명령어를 package.json에 기재하면 모든 준비를 마치게 된다</li>
</ul>
<h3 id="효율적으로-프로젝트-세팅하기">효율적으로 프로젝트 세팅하기</h3>
<h4 id="1-템플릿-저장소-생성하는-방법">1. 템플릿 저장소 생성하는 방법</h4>
<ul>
<li>프로젝트를 세팅하는 과정을 줄이기 위해 깃허브에서는 템플릿 리포지토리를 만들 수 있다</li>
<li><a href="https://docs.github.com/ko/repositories/creating-and-managing-repositories/creating-a-template-repository">템플릿 리포지토리</a>는 기존 리포지토리를 템플릿으로 만들어서 자신과 다른 사용자가 동일한 디렉터리 구조, 분기 및 파일을 사용하여 새 리포지토리를 생성하도록 할 수 있는 기능이다</li>
</ul>
<h4 id="2-나만의-create--app-구현하기">2. 나만의 create-***-app 구현하기</h4>
<ul>
<li>cli 패키지로 만들어서 사용자의 입력을 받아 서로 다른 패키지를 만들 수 있다</li>
</ul>
<h2 id="92-깃허브-100-활용하기">9.2 깃허브 100% 활용하기</h2>
<p>깃허브에서 제공하는 다양한 서비스를 활용하면 애플리케이션을 개발하는데 많은 도움을 얻을 수 있다</p>
<h3 id="921-깃허브-액션으로-ci-환경-구축하기">9.2.1 깃허브 액션으로 CI 환경 구축하기</h3>
<h4 id="ci란">CI란?</h4>
<ul>
<li>코드의 변화를 모으고 관리하는 코드 중앙 저장소에서, 여러 기여자가 기여한 코드를 지속적으로 빌드하고 테스트해 코드의 정합성을 확인하는 과정</li>
<li>CI의 핵심은 저장소에서 코드의 변화가 있을 때마다 전체 소프트웨어의 정합성을 확인하기 위한 작업(테스트, 빌드, 정적 분석, 보안 취약점 분석 등) 을 자동으로 실행해야 한다</li>
</ul>
<h4 id="깃허브-액션이란">깃허브 액션이란?</h4>
<ul>
<li>깃허브에서 출시한 SaaS로, 깃허브 저장소를 기반으로 깃허브에서 발생하는 다양한 이벤트를 트리거 삼아 다양한 작업을 할 수 있게 도와주는 도구이다</li>
<li>깃허브 액션이 수행하는 작업<ul>
<li>어떤 브랜치에 푸시가 발생하면 빌드를 수행한다</li>
<li>특정 브랜치가 메인 브랜치를 대상으로 풀 리퀘스트가 열리면 빌드, 테스트, 정적 분석을 수행한다</li>
</ul>
</li>
<li>이러한 작업이 CI/CD 솔루션을 대체할 수 있다</li>
<li>CI/CD 배포 뿐만 아니라 프로젝트에서 필요한 자동화 처리, 빌드 확인, 테스트 등을 자동화할 수 있다</li>
</ul>
<h4 id="깃허브-액션의-기본-개념">깃허브 액션의 기본 개념</h4>
<ul>
<li><p><strong>러너(runner)</strong><br>파일로 작성된 깃허브 액션이 실행되는 서버를 의미한다. 특별히 지정하지 않으면 공용 깃허브 액션 서버를 이용하며, 별도의 러너를 구축해 자체적으로 운영할 수도 있다</p>
</li>
<li><p><strong>액션(action)</strong><br>러너에서 실행되는 하나의 작업 단위를 의미한다. yaml 파일로 작성된 내용을 하나의 액션으로 볼 수 있다</p>
</li>
<li><p><strong>이벤트(event)</strong><br>깃허브 액션이 일으키는 이벤트를 의미한다. 개발자의 필요에 따라 한 개 이상의 이벤트를 지정할 수 있다. 또한 특정 브랜치를 지정하는 이벤트도 가능하다.</p>
</li>
<li><p><strong>잡(jobs)</strong><br>하나의 러너에서 실행되는 여러 스텝의 모음을 의미한다. 하나의 액션에서 여러 잡을 생성할 수 있으며, 특별히 선언한 게 없다면 내부 가상머신에서 각 잡은 병렬로 실행된다</p>
</li>
<li><p><strong>스텝(steps)</strong><br>잡 내부에서 일어나는 하나하나의 작업을 의미한다. 셀 명령어나 다른 액션을 실행할 수도 있다. 이 작업은 병렬로 일어나지 않는다.</p>
</li>
</ul>
<h4 id="깃허브-액션-작성하기">깃허브 액션 작성하기</h4>
<ul>
<li>./github/workflow 폴더를 생성하고 내부에 파일을 작성한다. 파일명에 제한이 없고, 확장자를 .yml 또는 .yaml로 지정하면 된다</li>
<li>yaml 파일을 구성하는 문법은 github <a href="https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions">공식 문서</a>에서 확인할 수 있다</li>
</ul>
<h4 id="브랜치-보호-규칙">브랜치 보호 규칙</h4>
<ul>
<li>브랜치 보호 규칙을 활용하면 테스트, 빌드와 같은 CI가 성공한 코드만 push되도록 설정할 수 있다</li>
<li>설정 방법: [Settings] → [Code and automation] → [Branches] → [Add branch protection rule] 클릭</li>
<li>브랜치 보호 규칙의 종류는 <a href="https://docs.github.com/ko/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches">공식 문서</a>에서 확인할 수 있다</li>
</ul>
<h3 id="922-직접-작성하지-않고-유용한-액션과-깃허브-앱-가져다-쓰기">9.2.2 직접 작성하지 않고 유용한 액션과 깃허브 앱 가져다 쓰기</h3>
<ul>
<li>깃허브에서는 <a href="https://github.com/marketplace?type=actions">Marketplaces</a>라는 서비스를 제공해 여러 사용자가 만들어 놓은 액션을 손쉽게 가져다 쓸 수 있도록 운영하고 있다</li>
</ul>
<h4 id="깃허브에서-제공하는-기본-액션">깃허브에서 제공하는 기본 액션</h4>
<ul>
<li><p><strong>actions/checkout</strong><br>깃허브 저장소를 체크아웃하는 액션이다. 저장소를 기반으로 작업을 해야 한다면 반드시 필요하다.</p>
</li>
<li><p><strong>actions/setup-node</strong><br>Node.js를 설치하는 액션이다. Node.js를 사용하는 프로젝트라면 반드시 필요하다. 설치할 Node.js 버전을 지정할 수도 있다.</p>
</li>
<li><p><strong>actions/github-script</strong><br>Github API가 제공하는 기능을 사용할 수 있도록 도와주는 액션이다. GitHub API를 이용하면 기서브에서 할 수 있는 대부분의 작업을 수행할 수 있으므로 한 번쯤 API 문서를 보는 것을 추천한다.</p>
</li>
<li><p><strong>actions/stale</strong><br>오래된 이슈나 PR을 자동으로 닫거나 더 이상 커뮤니케이션하지 못하도록 닫는다. 저장소가 오래되어 과거에 생성된 이슈나 풀 리퀘스트가 너무 많을 경우 정리하는 데 도움이 된다.</p>
</li>
<li><p><strong>actions/dependency-review-action</strong><br>의존성 그래프에 대한 변경, 즉 package.json, package-lock.json, pnpm-lock.yaml 등의 내용이 변경됐을 때 실행되는 액션으로, 의존성을 분석해 보안 또는 라이센스가 문제가 있다면 이를 알려준다.</p>
</li>
<li><p><strong>github/codeql-action</strong><br>깃허브의 코드 분석 솔루션이 code-ql을 활용해 저장소 내 코드의 취약점을 분석해 준다. languages에 javascript만 설정해 두면 자바스크립트와 타입스크립트를 모두 검사하므로 특정 스케줄을 맞춰서 실행하거나 CI로 활용할 수 있다.</p>
</li>
</ul>
<h3 id="923-깃허브-dependabot으로-보안-취약점-해결하기">9.2.3 깃허브 Dependabot으로 보안 취약점 해결하기</h3>
<h4 id="dependabot이란">Dependabot이란?</h4>
<ul>
<li>의존성에 문제가 있다면 이에 대해 문제를 알려주고 가능하다면 해결할 수 있는 풀 리퀘스트를 열어주는 깃허브에서 제공하는 기능이다</li>
</ul>
<h4 id="packagejson의-dependencies-이해하기">package.json의 dependencies 이해하기</h4>
<ul>
<li><p>먼저 package.json에서 빼놓을 수 없는 의존성과 버전에 대해서 이해해야 한다</p>
</li>
<li><p><strong>버전</strong></p>
<ul>
<li>버전은 주.부.수로 구성돼 있으며 각각의 정의는 아래와 같다</li>
</ul>
<ol>
<li>주(主)버전 : 기존 버전과 호환되지 않게 API가 바뀌면 올린다. 주 버전0은 초기 개발을 위해 쓰며, 실험 버전이라고 생각하면 된다.</li>
<li>부(部) 버전 : 기존 버전과 호환되면서 새로운 기능을 추가할 때 올린다.</li>
<li>수(修) 버전 : 기존 버전과 호환되면서 버그를 수정할 때 올린다</li>
</ol>
<ul>
<li>이 외에도 특정 버전으로 패키지를 배포하고 나면 그 버전의 내용은 절대 변경하지 말야야 한다</li>
<li>한 가지 염두에 둬야 할 점은 유의적 버전은 어디까지나 개발자들 간의 약속일 뿐, 정말로 해당 API의 버전이 이 유의적 버전에 맞춰 구현돼 있는지는 알 수 없다. 그래서 유의적 버전만 신뢰하고 버전을 변경하면 안된다.</li>
</ul>
</li>
<li><p><strong>npm 버전 규칙</strong></p>
<ul>
<li><a href="mailto:react@16.0.0">react@16.0.0</a>: 버전 앞에 아무런 특수 기호가 없다면 정확히 해당 버전에 대해서만 의존하고 있다는 뜻이다</li>
<li>react@^16.0.0: 16.0.0과 호환되는 버전을 의미한다. 호환된다는 뜻은 0보다 높은 부 버전에 대해서는 호환된다는 가정 하에 상위 버전을 설치할 수 있다는 것을 뜻한다. 즉, 여기서 가능한 버전은 16.0.0부터 17.0.0 미만의 모든 버전이다</li>
<li>react@~16.0.0: 패치 버전에 대해서만 호환되는 버전을 의미한다. 즉, 여기서 가능한 버전은 16.0.0부터 16.1.0 미만의 모든 버전이다.</li>
</ul>
</li>
<li><p><strong>의존성</strong></p>
<ul>
<li>npm 프로젝트를 운영하는데 필요한 자신 외의 npm 라이브러리를 정의해 둔 목록이다. JSON 형식으로 작성돼 있으며, dependencies와 devDependencies로 구성돼 있다.<ul>
<li>dependencies: <code>npm insatll 패키지명</code>을 실행하면 package.json에 설치되는 의존성이다. 해당 프로젝트를 실행하는데 꼭 필요한 패키지가 선언된다 (예- react,react-dom, next)</li>
<li>devDependencies: <code>npm install 패키지명 --save-dev</code>를 실행하면 package.json에 설치되는 의존성이다. 해당 프로젝트를 실행하는 데는 필요하지 않지만 개발 단계에서 필요한 패키지들을 여기에 선언한다. (예-eslint, jest, typescript)</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="dependabot으로-취약점-해결하기">Dependabot으로 취약점 해결하기</h4>
<ul>
<li>취약점이 있는 애플리케이션을 구현한 후 원격 저장소에 푸시하면 아래와 같이 Dependabot이 저장소의 의존성에 여러 가지 문제가 있다고 알려준다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/ca48122f-a3fc-416f-a4e2-23e6e394efd0/image.png" alt=""></p>
<ul>
<li>그리고 배너의 &#39;See Dependabot alerts&#39;를 누르면 구체적으로 어떤 의존성에 문제가 있는지 확인할 수 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/e9db6edf-9084-4b42-83c0-b2b4c65db4a9/image.png" alt=""></p>
<ul>
<li><p><strong>개별 취약점 살펴보기</strong></p>
<ul>
<li>깃허브의 Dependabot은 취약점을 Critical, High, Moderate, Low의 4단계로 나눈다</li>
<li>상세 화면을 살펴보면 아래와 같다
<img src="https://velog.velcdn.com/images/hyohyo__/post/7ee61e6e-831e-42eb-ac9f-b71a745ab472/image.png" alt=""></li>
</ul>
<ol>
<li>취약점을 발견한 파일의 경로다</li>
<li>취약점을 바로 수정할 수 있는 경우 표시되는 버튼이다</li>
<li>보안 취약점의 심각도를 나타낸다</li>
<li>취약점의 자세한 정보를 나타낸다</li>
</ol>
<ul>
<li>문제가 되는 패키지인 shell-quote가 어디에 설치되어 있는지 확인하기 위해서는 <code>npm ls shell-quote</code> 명령어를 입력하면 확인할 수 있다</li>
<li>문제가 되는 패키지가 버전에 문제가 없다면 어떻게 사용하고 있는지를 확인하면 된다</li>
</ul>
</li>
<li><p><strong>취약점 해결하기</strong></p>
<ul>
<li>가장 쉽게 해결할 수 있는 것은 깃허브 Dependabot이 풀 리퀘스트를 열어준 경우에 그 풀 리퀘스트를 머지하는 것 이다</li>
<li>풀 리퀘스트를 열어줬다는 것은 이미 취약점을 해결한 패치가 존재한다는 것을 의미한다</li>
</ul>
</li>
</ul>
<h2 id="93-리액트-애플리케이션-배포하기">9.3 리액트 애플리케이션 배포하기</h2>
<p>리액트 애플리케이션을 빠르게 배포할 수 있게 도와주는 3가지 SaaS 서비스의 특징 비교해보기</p>
<h3 id="931-netilfy">9.3.1 <a href="https://www.netlify.com/">Netilfy</a></h3>
<ul>
<li>2014년부터 시작하여, Heroku와 함께 가장 널리 알려진 정적 웹사이트 배포 서비스다</li>
</ul>
<h3 id="932-vercel">9.3.2 <a href="https://vercel.com/">Vercel</a></h3>
<ul>
<li>Next.js를 비롯한 Turboreop, SWC를 만든 회사이며, Next.js를 등에 업고 많은 사용자를 모으고 있다.</li>
</ul>
<h3 id="933-digitalocean">9.3.3 <a href="https://www.digitalocean.com/">DigitalOcean</a></h3>
<ul>
<li>미국의 클라우드 호스팅, 호스팅 플랫폼 업체이다. 다른 업체와 다르게 다양한 리소스에 대한 문서화가 매우 상세하게 돼 있고, 자체 블로그도 운영하고 있다.</li>
</ul>
<h3 id="비교표">비교표</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>Netlify</th>
<th>Vercel</th>
<th>DigitalOcean</th>
</tr>
</thead>
<tbody><tr>
<td><strong>주요 용도</strong></td>
<td>정적 웹사이트 및 JAMstack 프로젝트 배포</td>
<td>Next.js와 프론트엔드 프레임워크를 중심으로 한 서버리스 배포</td>
<td>가상 서버(VPS)를 기반으로 한 애플리케이션 및 웹사이트 배포</td>
</tr>
<tr>
<td><strong>배포 방식</strong></td>
<td>Git 연동(자동 배포), CLI 지원</td>
<td>Git 연동(자동 배포), CLI 지원</td>
<td>수동 서버 설정 또는 Git 연동, Terraform 지원</td>
</tr>
<tr>
<td><strong>사용자 친화성</strong></td>
<td>직관적 대시보드, 간단한 설정</td>
<td>Next.js 최적화, 서버리스 함수 및 빌드 간소화</td>
<td>사용자가 서버를 직접 설정해야 하므로 높은 학습 곡선</td>
</tr>
<tr>
<td><strong>성능 최적화</strong></td>
<td>CDN 기반 정적 파일 최적화, 글로벌 Edge 네트워크 제공</td>
<td>글로벌 Edge 네트워크, Next.js 서버리스 함수 최적화</td>
<td>사용자가 원하는 네트워크 및 성능 구성 가능</td>
</tr>
<tr>
<td><strong>확장성</strong></td>
<td>Jamstack 및 정적 웹사이트에 적합</td>
<td>Next.js 기반 프로젝트에 특화, 서버리스 함수로 동적 확장 가능</td>
<td>VM/VPS 기반으로 직접 스케일링 관리 가능</td>
</tr>
<tr>
<td><strong>호환성</strong></td>
<td>모든 정적 사이트 생성기(Gatsby, Hugo 등)에 적합</td>
<td>Next.js 및 React 프로젝트에 최적화</td>
<td>모든 언어 및 프레임워크 지원</td>
</tr>
<tr>
<td><strong>장점</strong></td>
<td>- 빠른 배포<br>- 간단한 설정<br>- 무료 CDN 포함</td>
<td>- Next.js 및 React에 최적화<br>- 자동 빌드 및 서버리스 함수<br>- 글로벌 Edge 네트워크</td>
<td>- 완전한 서버 제어 가능<br>- 다양한 언어/프레임워크 지원<br>- 비용 효율적</td>
</tr>
<tr>
<td><strong>단점</strong></td>
<td>- 동적 콘텐츠 처리 한계<br>- 서버리스 함수가 기본적</td>
<td>- 비Next.js 프로젝트에서 일부 기능 제한적<br>- 무료 플랜 제한</td>
<td>- 높은 기술 요구 사항<br>- 설정 및 유지보수 시간 소요</td>
</tr>
<tr>
<td><strong>추천 대상</strong></td>
<td>정적 웹사이트, Jamstack 개발자</td>
<td>Next.js 및 React 기반 프론트엔드 개발자</td>
<td>서버에 대한 직접 제어가 필요한 DevOps 및 풀스택 개발자</td>
</tr>
</tbody></table>
]]></description>
        </item>
        <item>
            <title><![CDATA[[리액트] 모던 리액트 딥다이브 8장 ]]></title>
            <link>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-8%EC%9E%A5</link>
            <guid>https://velog.io/@hyohyo__/%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%AA%A8%EB%8D%98-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%94%A5%EB%8B%A4%EC%9D%B4%EB%B8%8C-8%EC%9E%A5</guid>
            <pubDate>Sun, 01 Dec 2024 03:09:33 GMT</pubDate>
            <description><![CDATA[<h1 id="8장-좋은-리액트-코드-작성을-위한-환경-구축하기">8장 좋은 리액트 코드 작성을 위한 환경 구축하기</h1>
<p>ESLint를 활용한 정적 분석과 리액트 테스트 라이브러리를 충분히 활용한다면 웹 서비스가 서비스되기 전에 여러가지 문제를 점검하고 확인할 수 있다.</p>
<h2 id="81-eslint를-활용한-정적-코드-분석">8.1 ESLint를 활용한 정적 코드 분석</h2>
<p>ESLint는 자바스크립트 생태계에서 가장 많이 사용되는 정적 코드 분석 도구이다.</p>
<h3 id="811-eslint-살펴보기">8.1.1 ESLint 살펴보기</h3>
<h4 id="eslint의-코드-분석-방법">ESLint의 코드 분석 방법</h4>
<ol>
<li><p>자바스크립트 코드를 문자열로 읽는다</p>
</li>
<li><p>자바스크립트 코드를 분석할 수 있는 파서로 코드를 구조화한다.</p>
<ul>
<li>파서는 여러 종류가 있는데 ESLint는 기본값으로 espree를 사용한다</li>
<li>espree는 코드의 정확한 위치와 같은 세세한 정보도 분석해 알려준다</li>
</ul>
</li>
<li><p>2번에서 구조화한 트리플 AST라 하며, 이 구조화된 트리를 기준으로 각종 규칙과 대조한다</p>
<ul>
<li>espree로 코드를 분석한 결과를 바탕으로, 어떤 코드가 잘못된 코드이며 어떻게 수정해야 할지도 정한다. 이를 ESLint 규칙이라고 하며, 특정한 규칙의 모음을 plugins라고 한다.</li>
<li>ESLint는 <a href="https://eslint.org/docs/latest/rules">공식 홈페이지</a>에서 기본적으로 몇 가지 규칙을 제공한다.</li>
</ul>
</li>
<li><p>규칙과 대조했을 때 이를 위반한 코드를 알리거나 수정한다</p>
</li>
</ol>
<h3 id="812-eslint-plugin과-eslint-config">8.1.2 eslint-plugin과 eslint-config</h3>
<h4 id="eslint-plugin">eslint-plugin</h4>
<ul>
<li>eslint 규칙을 모아놓은 패키지다</li>
<li>예) eslint-plugin-import는 자바스크립트에서 다른 모듈을 불러오는 import와 관련된 규칙을 제공한다</li>
</ul>
<h4 id="eslint-config">eslint-config</h4>
<ul>
<li>eslint-plugin을 한데 묶어서 완벽하게 한 세트로 제공하는 패키지다</li>
<li>내가 원하는 규칙들을 한데 모아서 설치하고 적용하는 것보다 이미 존재하는 eslint-config를 설치해서 빠르게 적용할 수 있다</li>
</ul>
<h4 id="네이밍-규칙">네이밍 규칙</h4>
<ol>
<li>eslint-plugin, eslint-config라는 접두사를 준수해야 한다</li>
<li>반드시 한 단어로 구성해야 한다<ul>
<li>가능 : <code>eslint-plugin-naver</code></li>
<li>불가능 : <code>eslint-plugin-naver-financials</code></li>
</ul>
</li>
<li>특정 스코프가 앞에 붙는 것 까지는 가능하다<ul>
<li>가능 : <code>@titicaca/eslint-config-triple</code></li>
<li>불가능 : <code>@titicaca/eslint-config-triple-rules</code></li>
</ul>
</li>
</ol>
<h4 id="it기업에서-잘-만든-대표적인-eslint-config">IT기업에서 잘 만든 대표적인 eslint-config</h4>
<ul>
<li><p>eslint-config-airbnb</p>
<ul>
<li>리액트 기반 프로젝트의 대표적인 eslint-config다</li>
<li>에어비앤드 개발자뿐만 아니라 500여 명의 수많은 개발자가 유지보수하고 있는 가장 유명한 eslint-config다.</li>
</ul>
</li>
<li><p>@titicaca/eslint-config-kit</p>
<ul>
<li>한국 커뮤니티에서 운영되는 eslint-config 중 많이 쓰이는 eslint-config다</li>
<li>대부분의 eslint-config는 eslint-config-airbnb를 기반으로 약간의 룰을 수정해 배포되고 있는 것과 다르게 해당 패키지는 자체적으로 정의한 규칙을 기반으로 운영되고 있다.</li>
<li>외부로 제공하는 규칙에 대한 테스트 코드가 존재한다</li>
<li>CI/CD 환경, 카나리 배포 등 일반적인 npm 라이브러리 구축 및 관리를 위한 시스템이 잘 구축되어 있다.</li>
<li>별도의 frontend 규칙도 제공하고 있어 Node.js 환경 또는 리액트 환경에 맞는 규칙을 적용할 수 있다.</li>
</ul>
</li>
<li><p>eslint-config-next</p>
<ul>
<li>리액트 기반 Next.js 프레임워크를 사용하고 있는 프로젝트에서 사용할 수 있는 eslint-config다</li>
<li>페이지나 컴포넌트에서 반환하는 JSX 구문 및 _app, _document에서 작성돼 있는 HTML 코드 또한 정적 분석 대상으로 분류해 제공한다</li>
<li>웹 서비스 성능에 영향을 미칠 수 있는 요소들을 분석해 제공하는 기능도 있다</li>
</ul>
</li>
</ul>
<h3 id="813-나만의-eslint-규칙-만들기">8.1.3 나만의 ESLint 규칙 만들기</h3>
<p>ESLint 규칙을 생성해 관리하면 개발자가 수동으로 수정하는 것보다 훨씬 더 빠르고 휴먼 에러도 방지할 수 있다.</p>
<h4 id="이미-존재하는-규칙을-커스터마이징해서-적용하기-import-react를-제거하기-위한-eslint-규칙-만들기">이미 존재하는 규칙을 커스터마이징해서 적용하기: import React를 제거하기 위한 ESLint 규칙 만들기</h4>
<ul>
<li>리액트 17 버전부터는 import React 구문이 필요 없어졌다. 이에 따라 import React를 삭제하면 번들러의 크기를 줄일 수 있게 된다</li>
<li>이미 존재하는 규칙인 no-restricted-imports 규칙을 사용해서 import React를 금지할 수 있다. 이 규칙은 어떠한 모듈을 import하는 것을 금지하기 위해 만들어진 규칙이다</li>
</ul>
<pre><code class="language-js">// .eslintrc.js 파일
module.exports = {
  rules: {
    &quot;no-restricted-imports&quot;: [
      &quot;error&quot;,
      {
        // path에 금지시킬 모듈을 추가한다
        paths: [
          {
            // 모듈명
            name: &quot;react&quot;,
            // 모듈의 이름
            // default export만 금지시켜야 올바르게 필터링 가능
            importNames: [&quot;default&quot;],
            // 경고 메시지
            message:
              &quot;import React from &#39;react&#39;는 react 17부터 더 이상 필요하지 않습니다&quot;,
          },
        ],
      },
    ],
  },
};</code></pre>
<h4 id="완전히-새로운-규칙-만들기-new-date를-금지시키는-규칙">완전히 새로운 규칙 만들기: new Date를 금지시키는 규칙</h4>
<ul>
<li>한국 시간을 반환하기 위해 기기에 종속된 현재 시간인 new Date()을 사용하지 않고 ServerDate()를 만들어 이 함수만 사용하도록 규칙을 만들어야 한다</li>
<li>이 규칙을 만들기 전에 new Date() 코드를 작성한 후 espree에서 AST를 어떻게 만드는지 확인해야 한다</li>
<li>AST로 type, calleename 등을 확인한 후 ESLint의 create 함수를 통해 규칙을 만들어 보자</li>
</ul>
<pre><code class="language-js">// AST 기반으로 만든 ESLint 규칙
module.exports = {
    // 해당 규칙과 관련된 정보를 나타내는 필드
    meta: {
        type: &#39;suggestion&#39;,
        docs: {
            description: &#39;disallow use of the new Date()&#39;,
            recommended: false,
        },
        fixable: &#39;code&#39;,
        schema: [],
        messages: {
            message:
                &#39;new Date()는 클라이언트에서 실행 시 해당 기기의 시간에 의존적이라 정확하지 않습니다. ...&quot;
        },
    },
    // 코드 스멜을 감지할 선택자나 이벤트명 선언 가능
    create: function(context) {
        return {
            NewExpression: function (node) {
                if(node.callee.name === &quot;Date&quot; &amp;&amp; node.arguments.lenght === 0) {
                    context.report({
                        node:node,
                        messageId: &#39;message&#39;,
                        fix: function (fixer) {
                            return fixer.replaceText(node, &#39;ServerDate()&#39;)
                        }
                    })
                }
            }
        }
    }
}</code></pre>
<ul>
<li>위와 같이 규칙을 만들면 eslint-plugin 형태로 규칙을 묶음으로 배포하면 된다. 규칙은 하나씩 배포하는 것은 불가능하다</li>
</ul>
<h3 id="814-주의할-점">8.1.4 주의할 점</h3>
<h4 id="prettier와의-충돌">Prettier와의 충돌</h4>
<p>Prettier는 코드의 포매팅과 관련된 작업(줄바꿈, 들여쓰기, 작은따옴표, 큰따옴표 등)을 담당한다<br>ESLint에서도 포매팅과 관련된 작업을 처리할 수 있기 때문에 두 가지 모두를 자바스크립트 코드에서 실행한다면 서로 충돌하는 규칙으로 에러가 발생할 수 있다</p>
<ul>
<li>해결 방법<ol>
<li>서로 규칙이 충돌되지 않게끔 규칙을 잘 선언한다</li>
<li>자바스크립트나 타입스크립트는 ESLint에, 그 외의 파일은 모두 Prettier에 맡긴다</li>
</ol>
</li>
</ul>
<h4 id="규칙에-대한-예외-처리">규칙에 대한 예외 처리</h4>
<p>만약 일부 코드에서 특정 규칙을 임시로 제외시키고 싶다면 eslint-disable-주석을 사용하면 된다</p>
<pre><code class="language-js">// 특정 줄만 제외
console.log(&quot;hello world&quot;); // eslint-disable-line no-console

// 다음 줄 제외
// eslint-disable-next-line no-console
console.log(&quot;hello world&quot;);

// 특정 여러 줄 제외
/* eslint-disable no-console */
console.log(&quot;JavaScript debug log&quot;);
console.log(&quot;eslint is disabled now&quot;);
/* eslint-disable no-console */

// 파일 전체에서 제외
/* eslint-disable no-console */
console.log(&quot;hello world&quot;);</code></pre>
<h4 id="react-hooksno-exhaustive-deps">react-hooks/no-exhaustive-deps</h4>
<p>이 규칙은 useEffect나 useMemo와 같이 의존 배열이 필요한 훅에 의존성 배열을 제대로 선언했는지 확인하는 역할을 한다. 이 규칙을 예외처리 하면 잠재적인 버그를 야기할 수 있다.</p>
<ul>
<li>괜찮다고 임의로 판단한 경우에는 해당 변수를 어디서 어떻게 선언할지 다시 고민해봐야 한다</li>
<li>의존성 배열이 너무 긴 경우에는 useEffect를 분리해서 의존성 배열의 가독성과 안정성을 확보해야 한다</li>
<li>마운트 시점에 한 번만 실행하고 싶은 경우에 의도적으로 []로 모든 의존성을 제거하는데 과거 클래스 컴포넌트에서 사용되던 생명주기 형태의 접근 방법으로 함수 컴포넌트의 패러다임과는 맞지 않을 가능성이 있다. 또한 컴포넌트의 상태값과 별개의 부수 효과가 되어 컴포넌트의 상태와 불일치가 일어날 수 있게 된다.</li>
</ul>
<h4 id="eslint-버전-충돌">ESLint 버전 충돌</h4>
<ul>
<li>간혹 최신 버전의 eslint-config를 설치하면 react-scripts와 eslint-config-triple의 ESLint 의존성이 맞지 않아 에러가 발생한다.</li>
<li>이러한 에러를 방지하기 위해서 eslint-config, eslint-plugin이 지원하는 ESLint 버전을 확인하고, 또 설치하고자 하는 프로젝트의 ESLint 버전을 어떻게 지원하고 있는지 살펴봐야 한다</li>
</ul>
<h2 id="82-리액트-팀이-권장하는-리액트-테스트-라이브러리">8.2 리액트 팀이 권장하는 리액트 테스트 라이브러리</h2>
<h4 id="테스트란">테스트란?</h4>
<ul>
<li>개발자가 만든 프로그램이 코딩을 한 의도대로 작동하는지 확인하는 일련의 작업이다</li>
<li>사용자에게 버그가 최소화된 안정적인 서비스를 제공할 수 있는 원동력이 된다</li>
<li>테스트 코드와 QA의 차이점</li>
</ul>
<table>
<thead>
<tr>
<th><strong>항목</strong></th>
<th><strong>테스트 코드</strong></th>
<th><strong>QA</strong></th>
</tr>
</thead>
<tbody><tr>
<td><strong>목적</strong></td>
<td>기능과 동작이 코드 설계대로 작동하는지 확인</td>
<td>최종 사용자 관점에서 제품의 품질을 보장하고 경험을 최적화</td>
</tr>
<tr>
<td><strong>책임자</strong></td>
<td>개발자</td>
<td>QA 팀 또는 전담 테스터, 가끔 개발자가 함께 진행</td>
</tr>
<tr>
<td><strong>범위</strong></td>
<td>특정 함수, 컴포넌트, 혹은 사용자 흐름</td>
<td>전체 애플리케이션의 기능, UI, UX, 성능</td>
</tr>
<tr>
<td><strong>방식</strong></td>
<td>자동화된 테스트 코드 실행</td>
<td>사람 중심의 수동 테스트 또는 자동화된 도구 사용</td>
</tr>
<tr>
<td><strong>환경</strong></td>
<td>개발 환경</td>
<td>실제 사용자 환경을 재현 (다양한 브라우저, 디바이스 등 포함)</td>
</tr>
<tr>
<td><strong>속도</strong></td>
<td>빠르고 반복적인 실행 가능</td>
<td>시간이 오래 걸릴 수 있으며 반복 작업이 많음</td>
</tr>
<tr>
<td><strong>발견할 수 있는 문제</strong></td>
<td>코드 상의 버그, 특정 로직 오류</td>
<td>사용자 경험 문제, 엣지 케이스, 성능 문제 등</td>
</tr>
</tbody></table>
<h4 id="프론트엔드에서의-테스트">프론트엔드에서의 테스트</h4>
<ul>
<li>백엔드의 테스트는 일반적으로 화이트 박스 테스트로, 작성한 코드가 의도대로 작동하는지 확인해야 하며, AUI에서 수행해야 한다</li>
<li>프론트엔드는 블랙박스 형태로 테스트가 이뤄지며, 사용자와 동일하거나 유사한 환경에서 수행한다. 코드가 어떻게 됐든 의도한 대로 작성하는데 초점이 맞춰져 있다.</li>
<li>HTML, CSS와 같이 디자인 요소뿐만 아니라 사용자의 인터랙션, 의도치 않은 작동 등 브라우저에서 발생할 수 있는 다양한 시나리오를 고려해야 하기 때문에 일반적으로 테스팅하기가 매우 번거롭다</li>
</ul>
<h3 id="821-react-testing-libraty란">8.2.1 React Testing Libraty란?</h3>
<ul>
<li>DOM Testing Libraty를 기반으로 만들어진 테스팅 라이브러리로, 리액트를 기반으로 한 테스트를 수행하기 위해 만들어졌다</li>
<li>DOM Testing Libraty는 jsdom을 기반으로 한다. jsdom은 자바스크립트로만 작성된 라이브러리로, HTML이 없는 자바스크립트만 존재하는 환경에서 HTML과 DOM을 사용할 수 있도록 해주는 라이브러리다.</li>
<li>jsdom을 사용하면 자바스크립트 환경에서도 HTML을 사용할 수 있으므로 이를 기반으로 DOM Testing Library에서 제공하는 API를 사용해 테스트를 수행할 수 있다.</li>
</ul>
<pre><code class="language-js">const jsdom = require(&quot;jsdom&quot;);

const { JSDOM } = jsdom;

const dom = new JSDOM(`&lt;!DOCTYPE html&gt;&lt;p&gt;Hello world&lt;/p&gt;`);

console.log(dom.window.document.querySelector(&quot;p&quot;).textContent); // &quot;Hello world&quot;</code></pre>
<ul>
<li>위 예제처럼 jsdom을 사용하면 마치 HTML이 있는 것처럼 DOM을 불러오고 조작할 수 있다</li>
<li>리액트 테스팅 라이브러리를 활용하면 실제로 리액트 컴포넌트를 렌더링하지 않고도, 리액트 컴포넌트가 원하는 대로 렌더링되고 있는지 확인할 수 있다.</li>
</ul>
<h3 id="822-자바스크립트-테스트의-기초">8.2.2 자바스크립트 테스트의 기초</h3>
<h4 id="기본적인-테스트-코드를-작성하는-방식">기본적인 테스트 코드를 작성하는 방식</h4>
<ol>
<li>테스트할 함수나 모듈을 선정한다</li>
<li>함수나 모듈이 반환하길 기대하는 값을 적는다</li>
<li>함수나 모듈의 실제 반환 값을 적는다</li>
<li>3번의 기대에 따라 2번의 결과가 일치하는지 확인한다</li>
<li>기대하는 결과를 반환한다면 테스트는 성공이며, 만약 기대와 다른 결과를 반환하면 에러를 던진다</li>
</ol>
<h4 id="assert-모듈">assert 모듈</h4>
<ul>
<li>Node.js에서 기본적으로 제공하며, 테스트 코드를 작성하면 이 코드의 성공 여부에 따라 테스트 통과 또는 실패를 반환하는 모듈이다</li>
<li>사용 예시</li>
</ul>
<pre><code class="language-js">const assert = require(&quot;assert&quot;);

function sum(a, b) {
  return a + b;
}

assert.equal(sum(1, 2), 3); // 성공
assert.equal(sum(2, 2), 4); // 성공
assert.equal(sum(1, 2), 4); // 실패 - AssertionError [ERR_ASSERTION] [ERR_ASSERTION]: 3==4</code></pre>
<ul>
<li>이처럼 테스트 결과를 확인할 수 있도록 도와주는 라이브러리를 어설션(assertion) 라이브러리라고 한다</li>
</ul>
<h4 id="테스팅-프레임워크">테스팅 프레임워크</h4>
<ul>
<li>테스트 코드는 가능한 한 사람이 읽기 쉽게, 그리고 테스트의 목적이 분명하게 작성되는 것이 중요하다</li>
<li>테스팅 프레임워크는 어설션을 기반으로 테스트를 수행하며, 여기에 추가로 테스트 코드 작성자에게 도움이 될 만한 정보를 알려주는 역할도 함께 수행한다</li>
<li>자바스크립트에서 유명한 테스팅 프레임워크는 Jest, Mocha, Karma, Jasmine 등이 있다</li>
<li>리액트에서는 Jest가 널리 쓰인다.</li>
</ul>
<h4 id="jest">Jest</h4>
<pre><code class="language-js">// 예제 코드
function sum(a, b) {
  return a + b;
}

module.exports = {
  sum,
};</code></pre>
<pre><code class="language-js">// 테스트 코드

const { sum } = require(&quot;./math&quot;);

test(&quot;두 인수가 덧셈이 되어야 한다&quot;, () =&gt; {
  expect(sum(1, 2)).toBe(3);
});

test(&quot;두 인수가 덧셈이 되어야 한다&quot;, () =&gt; {
  expect(sum(1, 2)).toBe(3); // 에러
});</code></pre>
<ul>
<li>assert과 다르게 테스트를 실행하는 콘솔에서 볼 수 있는 테스크 관련 정보가 한층 다양해진다</li>
<li>무엇을 테스트했는지, 소요된 시간, 무엇을 성공하고 실패했는지, 전체 결과는 어떤지에 대한 자세한 정보를 확인할 수 있다</li>
</ul>
<h3 id="823-리액트-컴포넌트-테스트-코드-작성하기">8.2.3 리액트 컴포넌트 테스트 코드 작성하기</h3>
<ul>
<li>리액트 컴포넌트 테스트 순서</li>
</ul>
<ol>
<li>컴포넌트를 렌더링한다</li>
<li>필요하다면 컴포넌트에서 특정 액션을 수행한다</li>
<li>컴포넌트 렌더링과 2번의 액션을 통해 기대하는 결과와 실제 결과를 비교한다</li>
</ol>
<h4 id="프로젝트-생성">프로젝트 생성</h4>
<ul>
<li>create-react-app에는 이미 react-testing-library가 포함돼 있으므로 별도로 설치할 필요가 없다</li>
</ul>
<pre><code>npx craete-react-app react-test --template typescript</code></pre><ul>
<li>위 명령어로 생성된 프로젝트에는 App.text.tsx 파일이 생성되어 있다</li>
</ul>
<pre><code class="language-js">import React from &quot;react&quot;;
import { render, screen } from &quot;@testing-library/react&quot;;
import App from &quot;./App&quot;;

test(&quot;renders learn react link&quot;, () =&gt; {
  render(&lt;App /&gt;); // 1
  const linkElement = screen.getByText(/learn react/i); // 2
  expect(linkElement).toBeInTheDocument(); // 3
});</code></pre>
<ul>
<li>위 코드 내용은 아래와 같다<ol>
<li><code>&lt;App /&gt;</code>을 렌더링한다</li>
<li>렌더링하는 컴포넌트 내부에서 &quot;learn react&quot;라는 문자열을 가진 DOM 요소를 찾는다</li>
<li>expect(linkElement).toBeInTheDocument()라는 어설션을 활용해 2번에서 찾은 요소가 document 내부에 있는지 확인한다</li>
</ol>
</li>
</ul>
<h4 id="html-요소-여부를-확인하는-메소드">HTML 요소 여부를 확인하는 메소드</h4>
<ul>
<li>일반적으로 리액트 컴포넌트 테스트 시나리오는 HTML 요소가 있는지 확인한다. 확인하는 방법 3가지는 아래와 같다<ol>
<li>getBy...: 인수의 조건에 맞는 요소를 반환하며, 해당 요소가 없거나 두 개 이상이면 에러를 발생시킨다. 복수 개를 찾고 싶다면 getAllBy...를 사용하면 된다</li>
<li>findBy...: getBy...와 거의 유사하나 한 가지 큰 차이점은 Promise를 반환한다는 것이다. 즉, 비동기로 찾는다는 것을 의미하며, 기본값으로 1000ms의 타임아웃을 가지고 있다. 마찬가지로 두 개 이상이면 에러를 발생시키지만 복수 개를 찾고 싶다면 findAllBy...를 사용하면 된다. 이러한 특징 때문에 findBy는 비동기 액션 이후에 요소를 찾을 때 사용한다</li>
<li>queryBy...: 인수의 조건에 맞는 요소를 반환하는 대신, 찾지 못한다면 null을 반환한다. getBy...와 findBy...는 찾지 못하면 에러를 발생시키기 때문에 찾지 못해도 에러를 발생시키지 않고 싶다면 queryBy..를 사용하면 된다. 마찬가지로 복수 개를 찾았을 때는 에러를 발생시키며, 복수 개를 찾고 싶다면 queryAllBy...를 사용하면 된다</li>
</ol>
</li>
</ul>
<h4 id="정적-컴포넌트">정적 컴포넌트</h4>
<ul>
<li>별도의 상태가 존재하지 않아 항상 같은 결과를 반환하는 컴포넌트인 정적 컴포넌트는 테스트를 원하는 컴포넌트를 렌더링한 다음, 테스트를 원하는 요소를 찾아 원하는 테스트를 수행하면 된다.</li>
</ul>
<h4 id="동적-컴포넌트">동적 컴포넌트</h4>
<ul>
<li>리액트 테스팅 라이브러리에서 사용자의 입력을 흉내 내고, 또 state의 변화에 따른 컴포넌트의 변화를 테스트할 수 있다</li>
</ul>
<h4 id="비동기-이벤트가-발생하는-컴포넌트">비동기 이벤트가 발생하는 컴포넌트</h4>
<ul>
<li>jest 등을 활용해서 fetch를 모킹하면 서버에서 오는 응답 시나리오를 모두 테스트하기에는 fetch가 할 수 있는 다양한 일을 일일이 모킹해야 하므로 테스트 코드가 길어지고 유지보수도 어렵다</li>
<li>이러한 문제를 해결하기 위해 MSW(Mock Service Worker)를 사용한다</li>
<li>MSW는 모킹 라이브러리로, 브라우저에서는 서비스 워커를 활용해 실제 네트워크 요청을 가로채는 방식이고, Node.js에서는 https나 XMLHttpRequest의 요청을 가로채는 방식으로 작동한다.</li>
</ul>
<h3 id="824-사용자-정의-훅-테스트">8.2.4 사용자 정의 훅 테스트</h3>
<ul>
<li>훅이 들어가 있는 컴포넌트를 만들 경우 : 테스트 코드 작성 외에 작업이 더 추가된다</li>
<li>훅이 들어있는 컴포넌트에 대해 별도로 훅에 대한 테스트를 만들 경우 : 해당 훅이 모든 테스트 케이스를 커버하지 못할 경우 또 다른 테스트 가능한 컴포넌트를 찾아야 한다</li>
<li>react-hooks-testing-library를 활용하면 훅을 편리하게 테스트할 수 있다</li>
<li>renderHook 함수에서 훅을 편리하게 테스트파기 위한 rerender, unmount 등의 함수도 제공하고 있으므로 사용자 정의 훅을 테스트하고 싶다면 꼭 한번 사용해보자</li>
</ul>
<h3 id="825-테스트를-작성하기에-앞서-고려해야-할-점">8.2.5 테스트를 작성하기에 앞서 고려해야 할 점</h3>
<h4 id="테스트-커버리지를-맹신하면-안된다">테스트 커버리지를 맹신하면 안된다</h4>
<ul>
<li>흔히들 알고 있는 사실 중 하나는 테스트 커버리지가 높을 수록 좋고 꾸준히 테스트 코드를 작성해야 한다</li>
<li>하지만 테스트 커버리지는 단순히 얼마나 많은 코드가 테스트되고 있는지를 나타내는 지표일 뿐, 테스트가 잘되고 있는지를 나타내는 것은 아니다. 그러므로 절대 테스트 커버리지를 맹신해서는 안 된다</li>
</ul>
<h4 id="실무에서는-테스트-코드를-운영할-만큼-여유롭지-않다">실무에서는 테스트 코드를 운영할 만큼 여유롭지 않다</h4>
<ul>
<li>그리고 테스트 커버리지를 100%까지 끌어올릴 수 있는 상황은 생각보다 드물다</li>
<li>TDD라고 하는 개발 방법론을 차용해서 테스트를 우선시하더라도 서버 코드와는 다르게 프론트엔드 코드는 사용자의 입력이 매우 자유롭기 때문에 이러한 모든 상황을 커버해 테스트를 작성하기란 불가능하다</li>
<li>테스트를 QA에 의존해 개발을 빠르게 진행해야 할 수도 있고, 이후에 또 개발해야 할 기능이 산적해 있을 수도 있다</li>
</ul>
<h4 id="애플리케이션에서-가장-취약하거나-중요한-부분을-파악해야-한다">애플리케이션에서 가장 취약하거나 중요한 부분을 파악해야 한다</h4>
<ul>
<li>테스트 코드를 작성하기 전에 생각해 봐야 할 최우선 과제는 애플리케이션에서 가장 취약하거나 중요한 부분을 파악하는 것이다</li>
<li>테스트 코드는 반드시 사용자의 작업과 최대한 유사하게 작성돼야 한다. 예를 들어 결제를 위해 사용자가 입력하는 절차, 장바구니, 주소 입력, 결제까지의 과정을 모두 사용자와 최대한 비슷한 입장에서 테스트를 작성하는 것이 필요하다</li>
<li>이처럼 애플리케이션에서 가장 핵심이 되는 부분부터 먼저 테스트 코드를 하나씩 작성해 나가는 것이 중요하다</li>
<li>테스트 코드는 소프트웨어 품질에 대한 확신을 얻기 위해 작성하는 것이다</li>
</ul>
<h3 id="826-그-밖에-해볼-만한-여러-가지-테스트">8.2.6 그 밖에 해볼 만한 여러 가지 테스트</h3>
<ul>
<li>유닛 테스트 : 각각의 코드나 컴포넌트가 독립적으로 분리된 환경에서 의도된 대로 정확히 작동하는지 검증하는 테스트</li>
<li>통합 테스트 : 유닛 테스트를 통과한 여러 컴포넌트가 묶여서 하나의 기능으로 정상적으로 작동하는지 확인하는 테스트</li>
<li>엔드 투 엔드(End to End Test) : 흔히 E2E 테스트라 하며, 실제 사용자처럼 작동하는 로봇을 활용해 애플리케이션의 전체적인 기능을 확인하는 테스트</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React] 커스텀 스크롤바 구현하기]]></title>
            <link>https://velog.io/@hyohyo__/React-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%8A%A4%ED%81%AC%EB%A1%A4%EB%B0%94-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyohyo__/React-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%8A%A4%ED%81%AC%EB%A1%A4%EB%B0%94-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 24 Oct 2024 08:56:55 GMT</pubDate>
            <description><![CDATA[<p>프로젝트에서 실시간 채팅을 구현하면서 기본 스크롤바가 전반적인 UI에 너무 안 어울려서 수정해야될 필요성을 느꼈다. 또한, 기본 스크롤바에 조사해보니 아래와 같은 단점들이 있었다.</p>
<blockquote>
<h4 id="기본-스크롤바가-적용된-ui">기본 스크롤바가 적용된 UI</h4>
<ul>
<li>전체 레이아웃이 왼쪽으로 밀리는 현상이 발생함</li>
<li>전반적인 UI의 어울리지 않는 스크롤바 사용으로 UI 일관성을 저해시킴</li>
<li>스크롤하지 않는 상태에서 스크롤바가 보여 비교적 주요 정보에 대한 집중력을 떨어트림
<img src="https://velog.velcdn.com/images/hyohyo__/post/b27b8116-cfbb-42c0-93fd-ba23aa62f103/image.png" alt=""></li>
</ul>
</blockquote>
<p>그리고 단순히 CSS만 바꾸는 게 아니라 카카오톡을 참고해서 다양한 기능을 넣을 수 있도록 구현해야겠다는 생각이 들어서 자료조사를 시작했다</p>
<hr>
<h1 id="커스텀-스크롤바를-구현하는-목적">커스텀 스크롤바를 구현하는 목적</h1>
<h2 id="1-ui-안정성--및-일관성-개선">1. <strong>UI 안정성  및</strong> <strong>일관성 개선</strong></h2>
<ul>
<li>기본 스크롤바를 사용할 경우, 스크롤바의 너비가 화면 너비에 포함되어 <strong>전체 레이아웃이 왼쪽으로 이동하는 현상</strong>을 발생하는 이슈를 개선할 수 있음</li>
<li>커스텀 스크롤바로 <strong>필요할 때만 나타나도록 설정</strong>하여 시각적 복잡도를 줄이고 사용자가 중요한 정보에 더 집중할 수 있음</li>
<li>웹사이트의 <strong>디자인 테마나 브랜드 아이덴티티</strong>에 맞춰 스크롤바의 색상, 모양, 크기 등을 커스텀하여 UI 전반에 <strong>일관성</strong>을 부여할 수 있음</li>
</ul>
<h2 id="2-성능-최적화">2. 성능 최적화</h2>
<ul>
<li>스크롤이 없는 상태에서 스크롤바가 숨겨져 있으면 <strong>불필요한 렌더링 리소스를 줄일 수 있어</strong> 성능 최적화에도 기여할 수 있음</li>
</ul>
<hr>
<h1 id="작업-내용">작업 내용</h1>
<ul>
<li>기술스택 : <code>React</code> <code>TypeScript</code> <code>TailwindCSS</code> <code>Framer motion</code></li>
<li>기본 스크롤바는 보이지 않도록 한다</li>
<li>커스텀 스크롤바를 공용 컴포넌트로 만들어 다양한 컴포넌트에서 사용할 수 있도록 한다</li>
<li>커스텀 스크롤바 기능<ul>
<li>스크롤바의 기본 기능을 구현한다</li>
<li>스크롤이 없는 상태에서는 보이지 않게 한다</li>
<li>다양한 컴포넌트에서 사용할 수 있도록 스크롤바의 className을 변경할 수 있도록 한다</li>
</ul>
</li>
</ul>
<hr>
<h1 id="구현한-코드">구현한 코드</h1>
<h4 id="기본-스크롤바는-보이지-않도록-한다">기본 스크롤바는 보이지 않도록 한다</h4>
<ul>
<li>tailwind css에서 스크롤바를 선택하는데 어려움이 있으므로 css 파일을 만든 후 <code>display : none</code>을 하면 스크롤바가 없어진다<pre><code class="language-css">/* CustomScrollbar.css */
.scrollbar-custom {
overflow-y: auto;
}
</code></pre>
</li>
</ul>
<p>.scrollbar-custom::-webkit-scrollbar {
  display: none;
}</p>
<pre><code>
#### 스크롤바 기능 구현
- 아래 코드는 지정한 요소에 스크롤을 하면 작동하는 함수이다
- 아래 순서로 진행된다
  ① 스크롤될 콘텐츠의 길이에 따라 스크롤바의 길이가 변함
  ② 스크롤할 때마다 스크롤의 위치가 변경
  ③ 스크롤이 없는 상태에서는 보이지 않게 함
```js
const handleScroll = () =&gt; {
    if (containerRef.current) {
      {/* 스크롤바 크기 조정 */}

      // 스크롤될 컨텐츠 요소 높이 계산
      // 무한 스크롤링이 될 수 있기 때문에 스크롤할 때마다 연산 필요
      const contentHeight = containerRef.current.children[0].clientHeight;

      const scrollbarHeightCalc =
        (containerHeight / contentHeight) * containerHeight;
      setScrollbarHeight(scrollbarHeightCalc);

      {/* 스크롤바 위치 조정 */}
      // 스크롤바가 요소의 끝을 넘지 않도록 제한
      const maxScrollTop = containerHeight - scrollbarHeightCalc;
      const newScrollTop =
        scrollYProgress.get() * (containerHeight - scrollbarHeightCalc);


      setScrollPercentage(Math.min(newScrollTop, maxScrollTop));

      {/* 스크롤을 할 때만 스크롤바가 보임 */}
      if (hideScrollbar) {
        setShowScrollbar(true)

        if (timeoutRef.current) {
          clearTimeout(timeoutRef.current);
        }

        timeoutRef.current = setTimeout(() =&gt; {
          setShowScrollbar(false);
        }, 1000);
      }
    }
  };</code></pre><h4 id="전체-코드">전체 코드</h4>
<ul>
<li>그외에도 옵셔널 파라미터를 통해서 커스텀을 할 수 있도록 했다<pre><code class="language-js">// CustomScrollbar.tsx
import { useState, useEffect, useRef } from &#39;react&#39;;
</code></pre>
</li>
</ul>
<p>import { useScroll } from &#39;framer-motion&#39;;</p>
<p>import { cn } from &#39;@/utils/cn&#39;;
import &#39;@/styles/CustomScrollbar.css&#39;;</p>
<p>type CustomScrollbarProps = {
  children: React.ReactNode;
  containerClassName?: string;
  scrollbarClassName?: string;
  hideScrollbar?: boolean;
};</p>
<p>const CustomScrollbar = ({
  children,
  containerClassName = &#39;&#39;,
  scrollbarClassName = &#39;&#39;,
  hideScrollbar = true,
}: CustomScrollbarProps) =&gt; {
  const [showScrollbar, setShowScrollbar] = useState(
    hideScrollbar ? false : true,
  );
  const [scrollPercentage, setScrollPercentage] = useState(0);
  const [scrollbarHeight, setScrollbarHeight] = useState(0);
  const [containerHeight, setContainerHeight] = useState<number>(0);
  const containerRef = useRef<HTMLDivElement>(null);
  const timeoutRef = useRef&lt;NodeJS.Timeout&gt;();</p>
<p>  const { scrollYProgress } = useScroll({ container: containerRef });</p>
<p>  useEffect(() =&gt; {
    // 사용자가 보고 있는 요소 높이 계산
    // 고정된 크기이기 때문에 렌더링 때 한 번만 연산
    if (containerRef.current) {
      setContainerHeight(containerRef.current.clientHeight);
    }
  }, [containerRef]);</p>
<p>  const handleScroll = () =&gt; {
    if (containerRef.current) {
      {/* 스크롤바 크기 조정 */}</p>
<pre><code>  // 스크롤될 컨텐츠 요소 높이 계산
  // 무한 스크롤링이 구현할 수 있기 때문에 스크롤할 때마다 연산 필요
  const contentHeight = containerRef.current.children[0].clientHeight;

  const scrollbarHeightCalc =
    (containerHeight / contentHeight) * containerHeight;
  setScrollbarHeight(scrollbarHeightCalc);

  {/* 스크롤바 위치 조정 */}
  // 스크롤바가 요소의 끝을 넘지 않도록 제한
  const maxScrollTop = containerHeight - scrollbarHeightCalc;
  const newScrollTop =
    scrollYProgress.get() * (containerHeight - scrollbarHeightCalc);


  setScrollPercentage(Math.min(newScrollTop, maxScrollTop));

  {/* 스크롤을 할 때만 스크롤바가 보임 */}
  if (hideScrollbar) {
    setShowScrollbar(true)

    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    timeoutRef.current = setTimeout(() =&gt; {
      setShowScrollbar(false);
    }, 1000);
  }
}</code></pre><p>  };</p>
<p>  useEffect(() =&gt; {
    // 타이머 클린업 함수
    return () =&gt; {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, []);</p>
<p>  return (
    &lt;div className={cn(&#39;relative&#39;, containerClassName)}&gt;
      <div
        ref={containerRef}
        onScroll={handleScroll}
        className="scrollbar-custom h-full"
      >
        {children}
      </div>
      &lt;div
        className={cn(
          &#39;absolute right-0 top-0 h-full w-3 bg-transparent transition-opacity duration-300&#39;,
          showScrollbar ? &#39;opacity-100&#39; : &#39;opacity-0&#39;,
        )}
      &gt;
        &lt;div
          className={cn(
            &#39;absolute w-full rounded-full bg-kt-gray-2&#39;,
            scrollbarClassName,
          )}
          style={{
            top: <code>${scrollPercentage}px</code>,
            height: containerRef.current ? <code>${scrollbarHeight}px</code> : &#39;20%&#39;,
          }}
        /&gt;
      </div>
    </div>
  );
};</p>
<p>export default CustomScrollbar;</p>
<p>```</p>
<h1 id="커스텀-스크롤바-적용-화면">커스텀 스크롤바 적용 화면</h1>
<ul>
<li>스크롤바에 absolute을 적용하여 왼쪽으로 밀리는 현상을 개선함</li>
<li>전반적인 UI와 어울리는 스크롤바로 개선</li>
<li>스크롤하지 않는 상태에서는 스크롤바가 보이지 않아 사용자가 주요 정보에 집중할 수 있도록 함
<img src="https://velog.velcdn.com/images/hyohyo__/post/6d334dec-58ee-4c89-9024-c193390aa142/image.gif" alt=""></li>
</ul>
<p>이제 채팅창 무한 스크롤을 구현하면서 성능에도 더 신경써서 미흡한 부분은 고쳐봐야겠다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바스크립트] 브라우저의 렌더링 과정]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95</link>
            <guid>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95</guid>
            <pubDate>Mon, 21 Oct 2024 06:59:35 GMT</pubDate>
            <description><![CDATA[<p>대부분의 프로그래밍 언어는 운영체제나 가상 머신 위에서 실행되지만 웹 애플리케이션의 클라이언트 사이드 자바스크립트는 브라우저에서 HTML, CSS와 함께 실행된다. 따라서 브라우저 환경을 고려할 때 더 효율적인 클라이언트 사이드 자바스크립트 프로그래밍이 가능하다.</p>
<hr>
<h1 id="브라우저-렌더링-과정">브라우저 렌더링 과정</h1>
<hr>
<p>브라우저는 다음과 같은 과정을 거쳐 렌더링을 수행한다</p>
<p>1️⃣ 브라우저는 HTML, CSS, 자바스크립트, 이미지, 폰트 파일 등 렌더링에 필요한 리소스를 요청하고 서버로부터 응답을 받는다</p>
<blockquote>
<p>렌더링은 HTML, CSS, 자바스크립트로 작성된 문서를 파싱하여 브라우저에 시각적으로 출력하는 것을 말한다</p>
</blockquote>
<p>2️⃣ 브라우저의 렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 <strong>파싱</strong>하여 DOM과 CSSOM을 생성하고 이들을 결합하여 렌더 트리를 생성한다</p>
<blockquote>
<p>파싱(구문 분석)은 프로그래밍 언어의 문법에 맞게 작성된 텍스트 문서를 읽어 들여 실행하기 위해 텍스트 문서의 문자열을 토큰으로 분해하고, 토큰에 문법적 의미와 구조를 반영하여 트리 구조의 자료구조인 파스 트리를 생성하는 일련의 과정을 말한다. 일반적으로 파싱이 완료된 후에는 파스 트리를 기반으로 중간 언어인 바이트코드를 생성하고 실행된다. </p>
</blockquote>
<p>3️⃣ 브라우저의 자바스크립트 엔진은 서버로부터 응답된 자바스크립트를 파싱하여 AST를 생성하고 바이트코드로 변환하여 실행한다. 이때 자바스크립트는 DOM API를 통해 DOM이나 CSSOM을 변경할 수 있다. 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합된다.
4️⃣ 렌더 트리를 기반으로 HTML 요소의 레이아웃을 계산하고 브라우저 화면에 HTML 요소를 페인팅한다</p>
<hr>
<h1 id="요청과-응답">요청과 응답</h1>
<hr>
<ul>
<li>브라우저의 핵심 기능은 필요한 리소스를 서버에 요청하고 서버로부터 응답받아 브라우저에 시각적으로 렌더링하는 것이다. 즉, 렌더링에 필요한 리소스는 모두 서버에 존재하므로 필요한 리소스를 서버에 요청하고 서버가 응답한 리소스를 파싱하여 렌더링하는 것이다.</li>
<li>서버에 요청을 전송하기 위해 브라우저는 주소창을 제공한다. 브라우저의 주소창에 URL을 입력하고 엔터 키를 누르면 URL의 호스트 이름이 DNS를 통해 IP 주소로 변환되고 IP 주소를 갖는 서버에게 요청을 전송한다<blockquote>
<p>예를 들어, 브라우저의 주소창에 <code>https://poiemaweb.com</code>을 입력하고 엔터 키를 누르면 루트 요청(/, 스킴과 호스트만으로 구성된 URI에 의한 요청)이 poiemaweb.com 서버로 전송된다. 루트 요청에는 명확히 리소스를 요청하는 내용이 없지만 일반적으로 서버는 루트 요청에 대해 암묵적으로 index.html을 응답하도록 기본 설정이 되어 있다. 
따라서 서버는 루트 요청에 대해 서버의 루트 폴더에 존재하는 정적 파일 index.html을 클라이언트로 응답한다.</p>
</blockquote>
</li>
<li>반드시 브라우저의 주소창을 통해 서버에게 정적 파일만을 요청할 수 있는 것은 아니다. 자바스크립트를 통해 동적으로 서버에 정적/동적 데이터를 요청할 수도 있다.<blockquote>
<p>브라우저의 개발자도구 &gt; 네트워크 탭을 보면 index.html 뿐만 아니라 CSS, 자바스크립트, 이미지, 폰트 파일들도 응답된 것을 확인할 수 있다. 이는 브라저의 렌더링 엔진이 HTML을 파싱하는 도중에 외부 리소스를 로드하는 태그, 즉 CSS 파일을 로드하는 link 태그, 이미지 파일을 로드하는 img 태그, 자바스크립트를 로드하는 script 태그 등을 만나면 HTML의 파싱을 일시 중단하고 해당 리소스 파일을 서버로 요청하기 때문이다.</p>
</blockquote>
</li>
</ul>
<hr>
<h1 id="http-11과-http-20">HTTP 1.1과 HTTP 2.0</h1>
<hr>
<p>HTTP는 웹에서 브라우저와 서버가 통신하기 위한 프로토콜이다.</p>
<h3 id="http11">HTTP/1.1</h3>
<ul>
<li>HTTP/1.1은 기본적으로 커넥션 당 하나의 요청과 응답만 처리한다. 리소스의 동시 전송이 불가능한 구조이므로 요청할 리소스의 개수에 비례하여 응답 시간도 증가하는 단점이 있다</li>
</ul>
<h3 id="http2">HTTP/2</h3>
<ul>
<li>HTTP/2는 커넥션 당 여러 개의 요청과 응답, 즉 다중 요청/응답이 가능하다. 따라서 HTTP/2.0은 여러 리소스의 동시 전송이 가능하므로 HTTP/1.1에 비해 페이지 로드 속도가 약 50% 정도 빠르다고 알려져 있다.</li>
<li>HTTP/2에 자세한 내용은 구글의 <a href="https://developers.google.com/web/fundamentals/performance/http2?hl=ko">HTTP/2 소개</a>를 참고</li>
</ul>
<hr>
<h1 id="html-파싱과-dom-생성">HTML 파싱과 DOM 생성</h1>
<hr>
<p>브라우저의 요청에 의해 서버가 응답한 HTML 문서는 문자열로 이루어진 순수한 텍스트다. 순수한 텍스트인 HTML 문서를 브라우저에 시각적인 픽셀로 렌더링하려면 HTML 문서를 브라우저가 이해할 수 있는 자료구조(객체)로 변환하여 메모리에 저장해야 한다.</p>
<h3 id="html-파싱과-dom-생성-과정">HTML 파싱과 DOM 생성 과정</h3>
<p>1️⃣ 서버에 존재하던 HTML 파일이 브라우저의 요청에 의해 응답된다. 이때 서버는 브라우저가 요청한 HTML 파일을 읽어 들여 메모리에 저장한 다음 메모리에 저장된 바이트(2진수)를 인터넷을 경유하여 응답한다
2️⃣ 브라우저는 서버가 응답한 HTML 문서를 바이트(2진수) 형태로 응답받는다. 그리고 응답된 바이트 형태의 HTML 문서는 meta 태그의 charset 어트리뷰트에 의해 지정된 인코딩 방식을 기준으로 문자열로 변환된다. 참고로 meta 태그의 charset 어트리뷰트에 선언된 인코딩 방식은 응답 헤더에 담겨 응답된다. 브라우저는 이를 확인하여 문자열로 변환한다
3️⃣ 문자열로 변환된 HTML 문서를 읽어 들여 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해한다
4️⃣ 각 토큰들을 객체로 변환하여 노드들을 생성한다. 토큰의 내용에 따라 문서 노드, 요소 노드, 어트리뷰트 노드, 텍스트 노드가 생성된다. 노드는 이후 DOM을 구성하는 기본 요소가 된다
5️⃣ HTML 문서는 HTML 요소들의 집합으로 이루어지며 HTML 요소는 중첩 관계를 갖는다. HTML 요소의 콘텐츠 영역(시작 태그와 종료 태그 사이)에는 텍스트뿐만 아니라 다른 HTML 요소도 포함될 수 있다. 이때 HTML 요소 간에는 중첩 관계에 의해 부자 관계가 형성된다. 이러한 HTML 요소 간의 부자 관계를 반영하여 모든 노드들을 <strong>트리 자료구조</strong>로 구성한다. 이 노드들로 구성된 트리 자료구조를 <strong>DOM</strong>이라 부른다.</p>
<p>** 즉, DOM은 HTML 문서를 파싱한 결과물이다**</p>
<hr>
<h1 id="css-파싱과-cssom-생성">CSS 파싱과 CSSOM 생성</h1>
<hr>
<ul>
<li>렌더링 엔진은 DOM을 생성해 나가다가 CSS를 로드하는 link 태그나 style 태그를 만나면 <strong>DOM 생성을 일시 중단</strong>한다.</li>
<li>그리고 link 태그의 href 어트리뷰트에 지정된 CSS 파일을 서버에 요청하여 로드한 CSS 파일이나 style 태그 내의 CSS를 HTML과 동일한 파싱 과정(바이트 → 문자 → 토큰 → 노드 → CSSOM)을 거치며 해석하여 <strong>CSSOM을 생성</strong>한다. </li>
<li>이후 CSS 파싱을 완료하면 HTML 파싱이 중단된 지점부터 다시 HTML을 파싱하기 시작하여 <strong>DOM 생성을 재개</strong>한다.</li>
</ul>
<hr>
<h1 id="렌더-트리-생성">렌더 트리 생성</h1>
<hr>
<ul>
<li><p>렌더링 엔진은 서버로부터 응답된 HTML과 CSS를 파싱하여 각각 DOM과 CSSOM을 생성한다. 그리고 DOM과 CSSOM은 렌더링을 위해 렌더 트리로 결합된다.</p>
</li>
<li><p>렌더 트리는 렌더링을 위한 트리 구조의 자료구조다. 따라서 브라우저 화면에 렌더링되지 않는 노드와 CSS에 의해 비표시되는 노드들은 포함되지 않는다.</p>
</li>
<li><p>이후 완성된 렌더 트리는 각 HTML 요소의 레이아웃(위치와 크기)를 계산하는 데 사용되며 브라우저 화면에 픽셀을 렌더링하는 페인팅 처리에 입력된다</p>
</li>
<li><p>지금까지 살펴본 브라우저의 렌더링 과정은 반복해서 실행될 수 있다. 예를 들어, 다음과 같은 경우 반복해서 레이아웃 계산과 페인팅이 재차 실행된다</p>
<blockquote>
<ul>
<li>자바스크립트에 의한 노드 추가 또는 삭제  </li>
<li>브라우저 창의 리사이징에 의한 뷰포트 크기 변경</li>
<li>HTML 요소의 레이아웃에 변경을 발생시키는 width/height, margin, padding, border, display, position, top/right/bottom/left 등의 스타일 변경</li>
</ul>
</blockquote>
</li>
<li><p>레이아웃 계산과 페인팅을 다시 실행하는 리렌더링은 비용이 많이 드는, 성능에 악영향을 주는 작업이다. 따라서 가급적 리렌더링이 빈번하게 발생하지 않도록 주의할 필요가 있다.</p>
</li>
</ul>
<hr>
<h1 id="자바스크립트-파싱과-실행">자바스크립트 파싱과 실행</h1>
<hr>
<ul>
<li>HTML 문서를 파싱한 결과물로서 생성된 DOM은 HTML 문서의 구조와 정보뿐만 아니라 HTML 요소와 스타일 등을 변경할 수 있는 프로그래밍 인터페이스로서 DOM API를 제공한다. 즉, DOM API를 사용하면 이미 생성된 DOM을 동적으로 조작할 수 있다.</li>
<li>CSS 파싱 과정과 마찬가지로 렌더링 엔진은 HTML을 한 줄씩 순차적으로 파싱하며 DOM을 생성해 나가다가 자바스크립트 파일을 로드하는 script 태그를 만나면 DOM 생성을 일시 중단한다.</li>
<li>그리고 script 태그의 자바스크립트 코드를 파싱하기 위해 자바스크립트 엔진에 제어권을 넘긴다. 이후 자바스크립트 파싱과 실행이 종료되면 렌더링 엔진으로 다시 제어권을 넘겨 HTML 파싱이 중단된 지점부터 다시 HTML 파싱을 시작하여 DOM 생성을 재개한다.</li>
<li>자바스크립트 파싱과 실행은 브라우저의 렌더링 엔진이 아닌 자바스크립트 엔진이 처리한다. 자바스크립트 엔진은 자바스크립트 코드를 파싱하여 CPU가 이해할 수 있는 저수준 언어로 변환하고 실행하는 역할을 한다. 자바스크립트 엔진은 구글 크롬과 Node.js의 V8, 파이어폭스의 SpiderMonkey, 사파리의 JavaScriptCore 등 다양한 종류가 있으며, 모든 자바스크립트 엔진은 ECMAScript 사양을 준수한다.</li>
<li>렌더링 엔진으로부터 제어권을 넘겨받은 자바스크립트 엔진은 자바스크립트 코드를 파싱하기 시작한다. 자바스크립트 엔진은 자바스크립트를 해석하여 AST(추상적 구문 트리)를 생성한다. 그리고 AST를 기반으로 인터프리터가 실행할 수 있는 중간 코드인 바이트코드를 생성하여 실행한다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/48160a8b-2500-4464-9f1b-e873accf7309/image.png" alt="자바스크립트 파싱과 실행"></p>
<blockquote>
<h4 id="토크나이징">토크나이징</h4>
<p>단순한 문자열인 자바스크립트 코드를 어휘 분석하여 문법적 의미를 갖는 코드의 최소 단위인 토큰들로 분해한다. 이 과정을 렉싱이라고 부르기도 하지만 토크나이징과 미묘한 차이가 있다</p>
<h4 id="파싱">파싱</h4>
<p>토큰들의 집합을 구문 분석하여 <code>AST</code>를 생성한다. <code>AST</code>는 토큰에 문법적 의미와 구조를 반영한 트리 구조의 자료구조다. <code>AST</code>는 인터프리터나 컴파일러만이 사용하는 것은 아니다. <code>AST</code>를 사용하면 TypeScript, Babel, Prettier 같은 트랜스파일러를 구현할 수도 있다. <a href="https://astexplorer.net">AST Explorer 웹사이트</a>에 방문하면 다양한 오픈소스 자바스크립트 파서를 사용하여 AST를 생성해 볼 수 있다.</p>
<h4 id="바이트코드-생성과-실행">바이트코드 생성과 실행</h4>
<p>파싱의 결과물로서 생성된 AST는 인터프리터가 실행할 수 있는 중간 코드인 바이트코드로 변환되고 인터프리터에 의해 실행된다. 참고로 V8 엔진의 경우 자주 사용되는 코드는 터보팬이라 불리는 컴파일러에 의해 최적화된 머신 코드로 컴파일되어 성능을 최적화한다. 만약 코드의 사용 빈도가 적어지면 다시 디옵티마이징하기도 한다.</p>
</blockquote>
<hr>
<h1 id="리플로우와-리페인트">리플로우와 리페인트</h1>
<hr>
<p>만약 자바스크립트 코드에 DOM이나 CSSOM을 변경하는 DOM API가 사용된 경우 DOM이나 CSSOM이 변경된다. 이때 변경된 DOM과 CSSOM은 다시 렌더 트리로 결합되고 변경된 렌더 트리를 기반으로 레이아웃과 페인트 과정을 거처 브라우저의 화면에 다시 렌더링된다. 이를 리플로우, 리페인트라고 한다.</p>
<blockquote>
<h4 id="리플로우">리플로우</h4>
</blockquote>
<ul>
<li>레이아웃을 다시 계산하는 것</li>
</ul>
<blockquote>
<h4 id="리페인트">리페인트</h4>
</blockquote>
<ul>
<li>노드 추가/삭제, 요소의 크기/위치 변경, 윈도우 리사이징 등 레이아웃에 영향을 주는 변경이 발생한 경우에 한하여 실행된다. 리페인트는 재결합된 렌더 트리를 기반으로 다시 페인트 되는 것을 말함</li>
</ul>
<p>리플로우와 리페인트가 반드시 순차적으로 동시에 실행되는 것은 아니다. 레이아웃에 영향이 없는 변경은 리플로우 없이 리페인트만 실행된다.</p>
<hr>
<h1 id="자바스크립트-파싱에-의한-html-파싱-중단">자바스크립트 파싱에 의한 HTML 파싱 중단</h1>
<hr>
<p>렌더링 엔진과 자바스크립트 엔진은 병렬적으로 파싱을 실행하지 않고 직렬적으로 파싱을 수행한다. 이것은 script 태그의 위치에 따라 HTML 파싱이 블로킹되어 DOM 생성이 지연될 수 있다는 것을 의미한다. 따라서 script 태그의 위치는 중요한 의미를 갖는다.</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html&gt;
 &lt;head&gt;
   &lt;meta charset=&quot;UTF-8&quot;&gt;
   &lt;link rel=&quot;stylesheet&quot; href=&quot;style.css&quot;&gt;
   &lt;script&gt;
    cosnt $apple = document.getElementById(&#39;apple&#39;);
    $apple.style.color = &#39;red&#39;;
    &lt;/script&gt;
&lt;/head&gt;
  &lt;body&gt;
    &lt;ul&gt;
        &lt;li id=&#39;apple&#39;&gt;Apple&lt;/li&gt;
        ...
    &lt;/ul&gt;
  &lt;/body&gt;
&lt;/html&gt;</code></pre>
<ul>
<li><code>document.getElementById(&#39;apple&#39;)</code>은 DOM에서 id가 <code>apple</code>인 HTML 요소를 취득한다. 하지만 id가 <code>apple</code>인 HTML 요소를 파싱하지 않았기 때문에 정상적으로 동작하지 않는다</li>
<li>이러한 문제를 피하기 위해서 <strong>body 요소 가장 아래에 script 태그를 위치하는 것</strong>이 좋다</li>
</ul>
<hr>
<h1 id="script-태그의-asyncdefer-어트리뷰트">script 태그의 async/defer 어트리뷰트</h1>
<hr>
<p>자바스크립트 파싱에 의한 HTML 파싱 중단 문제를 해결하기 위해 HTML5부터 script 태그에 async와 defer 어트리뷰트가 추가되었다.
async와 defer 어트리뷰트는 src 어트리뷰트를 통해 외부 자바스크립트 파일을 로드하는 경우에만 사용할 수 있다. 즉, src 어트리뷰트가 없는 인라인 자바스크립트에는 사용할 수 없다.</p>
<pre><code class="language-HTML">&lt;script async src=&quot;extern.js&quot;&gt;&lt;/script&gt;
&lt;script defer src=&quot;extern.js&quot;&gt;&lt;/script&gt;</code></pre>
<p>async와 defer 어트리뷰트를 사용하면 HTML 파싱과 외부 자바스크립트 파일의 로드가 <strong>비동기적으로 동시에 진행</strong>된다. 하지만 자바스크립트의 실행 시점에 차이가 있다.</p>
<h4 id="async-어트리뷰트">async 어트리뷰트</h4>
<ul>
<li>자바스크립트의 파싱과 실행은 자바스크립트 파일의 로드가 완료된 직후 진행되며, 이때 HTML 파싱이 중단된다.
<img src="https://velog.velcdn.com/images/hyohyo__/post/e3f8edbb-313f-4f08-bf43-8bdcc4c3668e/image.png" alt=""></li>
<li>여러 개의 script 태그에 async 어트리뷰트를 지정하면 script 태그의 순서와는 상관없이 로드가 완료된 자바스크립트부터 먼저 실행되므로 순서가 보장되지 않는다. 따라서 순서 보장이 필요한 script 태그에는 async 어트리뷰트를 지정하지 않아야 한다. async 어트리뷰트는 IE 10 이상에서 지원된다.</li>
</ul>
<h4 id="defer-어트리뷰트">defer 어트리뷰트</h4>
<ul>
<li>자바스크립트의 파싱과 실행은 HTML 파싱이 완료된 직후, 즉 DOM 생성이 완료된 직후 진행된다. 따라서 DOM 생성이 완료된 이후 실행되어야 할 자바스크립트에 유용하다. defer 어트리뷰트는 IE 10 이상에서 지원한다. 
<img src="https://velog.velcdn.com/images/hyohyo__/post/ce7c2f16-db93-4532-985a-679dcc2428d0/image.png" alt=""></li>
</ul>
<blockquote>
<h4 id="참고">참고</h4>
<p>모던 자바스크립트 딥다이브 책</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[자바스크립트] Babel과 Webpack]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Babel%EA%B3%BC-Webpack</link>
            <guid>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-Babel%EA%B3%BC-Webpack</guid>
            <pubDate>Fri, 11 Oct 2024 06:32:14 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="babel">Babel</h1>
</blockquote>
<h2 id="babel이란">Babel이란?</h2>
<ul>
<li>추상화 수준을 유지한 채로 코드를 변화시키는 트랜스파일러이다</li>
<li>바벨을 통해 ES6+의 자바스크립트 코드는 하위 버전으로 변환되고, 그로 인해 IE나 다른 브라우저에서 동작할 수 있게 만든다</li>
</ul>
<h3 id="설치-방법">설치 방법</h3>
<pre><code># 프로젝트 폴더 생성
$ mkdir esnext-project &amp;&amp; cd esnext-project
# package.json 생성
$ npm init -y
# bable-core, babel-cli 설치
$ npm install --save-dev @babel/core @babel/cli</code></pre><h3 id="프리셋-설치와-babelconfigjson-설정-파일-작성">프리셋 설치와 babel.config.json 설정 파일 작성</h3>
<ul>
<li>Babel을 사용하려면 <code>@babel/preset-env</code>를 설치해야 한다</li>
<li>프리셋은 <code>@babel/preset-env</code>과 함께 사용되어야 하는 Babel 플러그인을 모아둔 것이다</li>
<li><code>@babel/preset-env</code>는 필요한 플러그인들을 프로젝트 지원 환경에 맞춰 동적으로 결정해 준다. 프로젝트 지원 환경은 Browserslist 형식으로 .browerslistrc 파일에 상세히 설정할 수 있다.<pre><code># @babel/preset-env 설치
$ npm install --save-dev @babel/preset-env</code></pre></li>
<li>설치가 완료되면 프로젝트 루트 폴더에 <code>babel.config.json</code> 설정 파일을 생성하고 아래와 같이 작성한다<pre><code class="language-json">{
&quot;presets&quot;:[&quot;@babel/preset-env&quot;]
}</code></pre>
</li>
</ul>
<h3 id="트랜스파일링">트랜스파일링</h3>
<ul>
<li>Babel CLI 명령어도 있지만 npm scripts에 Babel CLI 명령어를 등록해서 사용할 수 있다</li>
<li>package.json 파일에 scripts를 추가한다. 완성된 package.json파일은 다음과 같다<pre><code class="language-json">{
&quot;name&quot;:&quot;esnext-project&quot;,
&quot;version&quot;:&quot;1.0.0&quot;,
&quot;scripts&quot;:{
  &quot;build&quot;: &quot;babel src/js -w -d dist/js&quot;
},
&quot;devDependencies&quot;:{
  &quot;@babel/cli&quot;: &quot;^7.10.3&quot;,
  &quot;@babel/core&quot;: &quot;^7.10.3&quot;,
  &quot;@babel/preset-env&quot;: &quot;^7.10.3&quot;,
}
}</code></pre>
</li>
<li><code>-w</code>는 타깃 폴더에 있는 모든 자바스크립트 파일들의 변경을 감지하여 자동으로 트랜스파일한다</li>
<li><code>-d</code>는 트랜스파일링된 결과물이 저장될 폴더를 지정한다. 만약 지정된 폴더가 존재하지 않으면 자동 생성한다</li>
<li>이제 트랜스파일링을 테스트하기 위해 ES6+/ES.NEXT 사양의 자바스크립트 파일을 작성해보자. 프로젝트 루프 폴더에 src/js 폴더를 생성한 후 lib.js와 main.js를 추가한다 <pre><code class="language-js">// src/js/lib.js
export const pi = Math.PI; // ES6 모듈
</code></pre>
</li>
</ul>
<p>export function power(x,y) {
  return x ** y;
}</p>
<p>// ES6 클래스
export class Foo {
  #private = 10; </p>
<p>  foo() {
    const {a,b, ...x} = {...{a:1,b:2},c:3,d:4};
    return {a,b,x};
  }</p>
<p>  bar() {
      return this.#private;
  }
}</p>
<pre><code>
```js
// src/js/main.js
import * from &#39;./lib&#39;;

console.log(pi);
console.log(power(pi,pi));

const f = new Foo();
console.log(f.foo());
console.log(f.bar());</code></pre><ul>
<li>터미널에서 아래와 같이 명령어를 입력하여 트랜스파일링을 실행한다<pre><code>npm run build</code></pre></li>
<li>명령어를 실행하면 에러가 발생한다. @babel/preset-env가 현재 제안 단계에 있는 사양에 대한 플러그인을 지원하지 않기 때문에 별도의 플러그인을 설치해야 한다</li>
</ul>
<h3 id="플러그인-설치">플러그인 설치</h3>
<ul>
<li><a href="https://babeljs.io/">Babel 공식사이트</a>에서 원하는 플러그인을 검색할 수 있다</li>
<li>검색된 플러그인 중에서 public/private 클래스 필드를 지원하는 @babel/plugin-proposal-class-properties를 설치하자<pre><code>$ npm install --save-dev @babel/plugin-proposal-class-properties</code></pre></li>
<li>설치한 플러그인은 babel.config.json 설정 파일에 추가해야 한다. <code>babel.config.json</code> 설정 파일을 아래와 같이 수정한다<pre><code class="language-json">{
&quot;presets&quot;:[&quot;@babel/preset-env&quot;],
&quot;plugins&quot;:[&quot;@babel/plugin-proposal-class-properties&quot;]
}</code></pre>
</li>
<li>다시 <code>npm run build</code>를 입력하여 트랜스파일링을 실행하면 성공할 수 있다.</li>
</ul>
<h3 id="브라우저에서-모듈-로딩-테스트">브라우저에서 모듈 로딩 테스트</h3>
<ul>
<li>지금까지 예제의 모듈 기능은 Node.js 환경에서 동작한 것이고 Babel이 모듈을 트랜스파일링한 것도 Node.js가 기본 지원하는 CommonJS 방식의 모듈 로딩 시스템에 따른 것이다.</li>
<li>브라우저는 CommonJS 방식의 require 함수를 지원하지 않으므로 위 예제로 트랜스파일링된 파일을 브라우저에서 실행하면 에러가 발생한다.</li>
<li>브라우저의 ESM을 사용하도록 Babel을 설정할 수도 있으나 앞서 설명한 바와 같이 ESM을 사용하는 것은 문제가 있고, 이러한 문제를 Webpack에서 해결할 수 있다</li>
</ul>
<blockquote>
<h1 id="webpack">Webpack</h1>
</blockquote>
<h2 id="webpack이란">Webpack이란?</h2>
<ul>
<li>의존 관계에 있는 자바스크립트, CSS, 이미지 등의 리소스들을 하나의 파일로 번들링하는 모듈 번들러다.</li>
<li>의존 모듈이 파일로 번들링되므로 별도의 모듈 로더가 필요 없다.</li>
<li>여러 개의 자바스크립트 파일을 하나로 번들링하므로 HTML 파일에서 script 태그로 여러 개의 자바스크립트 파일을 로드해야 하는 번거로움도 사라진다</li>
</ul>
<h3 id="설치-방법-1">설치 방법</h3>
<pre><code>$ npm insatll --save-dev webpack webpack-cli</code></pre><h3 id="babel-loader-설치">babel-loader 설치</h3>
<ul>
<li>Webpack이 모듈을 번들링할 때 Babel을 사용하여 ES6+/ES.NEXT 사양의 소스코드를 ES5 사양의 소스코드로 트랜스파일링하도록 babel-loader를 설치한다<pre><code>$ npm install --save-dev babel-loader</code></pre></li>
<li>npm scripts를 변경하여 Babel 대신 Webpack을 실행하도록 수정한다<pre><code class="language-json">{
&quot;name&quot;:&quot;esnext-project&quot;,
&quot;version&quot;:&quot;1.0.0&quot;,
&quot;scripts&quot;:{
  &quot;build&quot;: &quot;webpack -w&quot;
},
&quot;devDependencies&quot;:{
  &quot;@babel/cli&quot;: &quot;^7.10.3&quot;,
  &quot;@babel/core&quot;: &quot;^7.10.3&quot;,
  &quot;@babel/preset-env&quot;: &quot;^7.10.3&quot;,
  &quot;webpack&quot;:&quot;^4.43.0&quot;,
  &quot;webpack-cli&quot;:&quot;^3.3.12&quot;
}
}</code></pre>
</li>
</ul>
<h3 id="webpackconfigjs-설정-파일-작성">webpack.config.js 설정 파일 작성</h3>
<ul>
<li>webpack.config.js는 Webpack이 실행될 때까지 참조하는 설정파일이다. 프로젝트 루트 폴더에 webpack.config.js 파일을 생성하고 다음과 같이 작성한다<pre><code class="language-js">//webpack.config.js
</code></pre>
</li>
</ul>
<p>const path = require(&quot;path&quot;);</p>
<p>module.exports = {
  entry: &quot;./src/js/main.js&quot;,
  output: {
    path: path.resolve(<strong>dirname, &quot;dist&quot;),
    filename: &quot;js/bundle.js&quot;,
  },
  module: {
    rule: [
      {
        test: /.js$/,
        include: [path.resolve(</strong>dirname, &quot;src/js&quot;)],
        exclude: /node_modules/,
        use: {
          loader: &quot;babel-loader&quot;,
          options: {
            presets: [&quot;@babel/preset-env&quot;],
            plugins: [&quot;@babel/plugin-proposal-class-properties&quot;],
          },
        },
      },
    ],
  },
  devtool: &quot;source-map&quot;,
  mode: &quot;development&quot;,
};</p>
<pre><code>- `npm run build` 명령어를 통해 Webpack을 실행하면 dist/js 폴더에 bundle.js가 생성되었다. 이 파일을 main.js, lib.js 모듈이 하나로 번들링된 결과물이다. index.html을 다음과 같이 수정하고 브라우저에서 실행해보자
```js
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
    &lt;script src=&quot;dist/js/bundle.js&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><ul>
<li>main.js, lib.js 모듈이 하나로 번들링된 bundle.js가 브라우저에서 문제없이 실행된 것을 확인할 수 있다</li>
</ul>
<h3 id="babel-polyfill-설치">babel-polyfill 설치</h3>
<ul>
<li>Babel을 사용하여 ES9+ES.NEXT의 사양의 소스코드를 ES5 사양의 소스코드로 트랜스파일링해도 브라우저가 지원하지 않는 코드가 남아 있을 수 있다.</li>
<li>src/js/main.js를 다음과 같이 수정하여 Promise, Object.assign, Array.from 등이 어떻게 트랜스파일링 되는지 확인해보자.<pre><code class="language-js">//src/js/main.js
import { pi, power, Foo } from &quot;./lib&quot;;
</code></pre>
</li>
</ul>
<p>console.log(pi);
console.log(power(pi, pi));</p>
<p>const f = new Foo();
console.log(f.foo());
console.log(f.bar());</p>
<p>// polyfill이 필요한 코드
console.log(
  new Promise((resolve, resject) =&gt; {
    setTimeout(() =&gt; resolve(1), 100);
  })
);</p>
<p>// polyfill이 필요한 코드
console.log(Object.assign({}, { x: 1 }, { y: 2 }));</p>
<p>// polyfill이 필요한 코드
console.log(Array.from([1, 2, 3], (v) =&gt; v + v));</p>
<pre><code>다시 트랜스파일링과 번들링을 실행한 다음, dist/js/bundle.js를 확인해보자.
```js
console.log(new Promise(function (resolve, reject) {
    setTimeout(function ()
        return resolve(1);
    }, 100);
})); // polyfill이 필요한 코드

// polyfill이 필요한 코드
console.log(Object.assign({}, {
 x: 1
}, {
 y: 2
}));

// polyfill이 필요한 코드
console.log(Array.from([1, 2, 3], function (v) {
    return v + v;
}));</code></pre><ul>
<li><p>이처럼 Promise, Object.assign, Array.from 등과 같이 ES5 사양으로 대체할 수 없는 기능은 트랜스파일링 되지 않는다. 따라서 구형 브라우저에서도 Promise, Object.assign, Array.from 같은 객체나 메서드를 사용하기 위해서는 @babel/polyfill을 설치해야 한다.</p>
<pre><code>$npm install @babel/polyfill</code></pre></li>
<li><p>@babel/polyfill은 개발 환경에서만 사용해야하는 것이 아니라 실제 운영 환경에서도 사용해야한다. 따라서 —save-dev 옵션을 지정하지 않는다.</p>
</li>
<li><p>ES6의 import를 사용하는 경우에는 진입점의 선두에서 먼저 폴리필을 로드하도록 한다.</p>
<pre><code class="language-js">import &quot;@babel/polyfill&quot;;
import { pi, power, Foo } from &quot;./lib&quot;;
...</code></pre>
</li>
<li><p>Webpack을 사용하는 경우에는 위 방법 대신 webpack.config.js 파일의 entry 배열에 폴리필을 추가한다.</p>
<pre><code class="language-js">const path = require(&#39;path&#39;);
</code></pre>
</li>
</ul>
<p>module.exports = {
    ...
    entry: [&#39;@babel/polyfill&#39;, &#39;./src/js/main.js&#39;],
    ...
}</p>
<p>```</p>
<ul>
<li>위와 같이 폴리필을 반영한 뒤 Webpack을 실행한 후, dist/js/bundle.js를 확인해보면 폴리필이 추가된 것을 확인할 수 있다.</li>
</ul>
<h4 id="--출처--모던-자바스크립트-딥다이브-책">- 출처 : 모던 자바스크립트 딥다이브 책</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 모듈]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AA%A8%EB%93%88</link>
            <guid>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AA%A8%EB%93%88</guid>
            <pubDate>Fri, 11 Oct 2024 03:40:08 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="모듈이란">모듈이란?</h1>
</blockquote>
<ul>
<li>애플리케이션을 구성하는 개별적인 요소로서 재사용 가능한 코드 조각</li>
</ul>
<blockquote>
<h1 id="모듈의-특징">모듈의 특징</h1>
</blockquote>
<h4 id="모듈이-성립하려면-모듈은-자신만의-파일-스코프를-가질-수-있어야-함">모듈이 성립하려면 모듈은 자신만의 파일 스코프를 가질 수 있어야 함</h4>
<ul>
<li>자신만의 파일 스코프를 갖는 모듈의 자산은 기본적으로 비공개 상태다. 다시 말해, 자신만의 파일 스코프를 갖는 모듈의 모든 자산은 캡슐화되어 다른 모듈에서 접근할 수 없다. 즉, 모듈은 개별적 존재로서 애플리케이션과 분리되어 존재한다</li>
</ul>
<h4 id="모듈은-애플리케이션이나-다른-모듈에-의해-재사용되어야-한다">모듈은 애플리케이션이나 다른 모듈에 의해 재사용되어야 한다</h4>
<ul>
<li>모듈은 공개가 필요한 자산에 한정하여 명시적으로 선택적 공개가 가능하다. 이를 <code>export</code>라 한다</li>
<li>공개된 모듈의 자산은 다른 모듈에서 재사용할 수 있다. 이때 공개된 모듈의 자산을 사용하는 모듈을 모듈 사용자라 한다. 모듈 사용자는 모듈이 공개한 자산 중 일부 또는 전체를 선택해 자신의 스코프 내로 불러들어 재사용할 수 있다. 이를 <code>import</code>라 한다.</li>
</ul>
<h4 id="일반적으로-기능별로-분리되어-파일-단위로-분리한다">일반적으로 기능별로 분리되어 파일 단위로 분리한다</h4>
<ul>
<li>코드의 단위를 명확히 분리하여 애플리케이션을 구성할 수 있고, 재사용성이 좋아 개발 효율성과 유지보수성을 높일 수 있다</li>
</ul>
<blockquote>
<h1 id="자바스크립트에서의-모듈">자바스크립트에서의 모듈</h1>
</blockquote>
<h2 id="자바스크립트는-모듈-시스템파일-스코프-import-export을-지원하지-않는다">자바스크립트는 모듈 시스템(파일 스코프, import, export)을 지원하지 않는다</h2>
<hr>
<ul>
<li>자바스크립트는 웹페이지의 단순한 보조 기능을 처리하기 위한 제한적인 용도를 목적으로 생겼기 때문에 C언어, 자바와 같이 모듈 기능을 가지고 있지 않다</li>
<li>script 태그를 사용해서 외부의 자바스크립트 파일을 로드할 수 있지만 파일마다 독립적인 파일 스코프를 갖지 않는다</li>
<li>즉, 모든 자바스크립트 파일은 하나의 전역을 공유해서 전역 변수가 중복되는 등의 문제가 발생할 수 있다</li>
<li>자바스크립트에서 모듈 시스템을 사용하기 위해 CommonJS와 AMD 라는 모듈 로더 라이브러리가 나왔다</li>
</ul>
<hr>
<h2 id="es6-모듈esm">ES6 모듈(ESM)</h2>
<hr>
<ul>
<li>이러한 상황에서 ES6에서 클라이언트 사이드 자바스크립트에서도 동작하는 모듈 기능을 추가했다 (IE를 제외한 대부분의 브라우저 사용 가능)</li>
</ul>
<h3 id="ems-모듈-사용법">EMS 모듈 사용법</h3>
<ul>
<li>script 태그에 type=&quot;module&quot; 어트리뷰트를 추가하면 로드된 자바스크립트 파일은 모듈로서 동작한다. </li>
<li>일반적인 자바스크립트 파일이 아닌 ESM임을 명확하기 위해서 mjs 파일 확장자를 사용하는 것을 권장한다<pre><code class="language-html">&lt;script type=&quot;module&quot; src=&quot;app.mjs&quot;&gt;&lt;/script&gt;</code></pre>
</li>
</ul>
<h3 id="ems-모듈-스코프">EMS 모듈 스코프</h3>
<ul>
<li>EMS은 파일 자체의 <code>독자적인 모듈 스코프를 제공</code>한다. 따라서 모듈 내에서 var 키워드로 선언한 변수는 더는 전역 변수가 아니며 window 객체의 프로퍼티도 아니다</li>
<li>모듈 내에서 선언한 식별자는 모듈 외부에서 참조할 수 없다. 모듈 스코프가 다르기 때문이다<pre><code class="language-js">// foo.mjs
var x = &#39;foo&#39;;
var y = &#39;foo2&#39;;
console.log(x); // foo
console.log(window.x); // undefined</code></pre>
<pre><code class="language-js">// bar.mjs
var x = &#39;bar&#39;;
console.log(x); // bar
console.log(window.x); // undefined
</code></pre>
</li>
</ul>
<p>console.log(y); // ReferenceError</p>
<pre><code>```html
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;body&gt;
  &lt;script type=&quot;module&quot; src=&quot;foo.mjs&quot;&gt;&lt;/script&gt;
  &lt;script type=&quot;module&quot; src=&quot;bar.mjs&quot;&gt;&lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;  </code></pre><h3 id="ems-export-키워드">EMS export 키워드</h3>
<ul>
<li>export 키워드는 선언문 앞에 사용한다.</li>
<li>변수, 함수, 클래스 등 모든 식별자를 export할 수 있다.<pre><code class="language-js">// lib.mjs
export const pi = Math.PI;
</code></pre>
</li>
</ul>
<p>export function square(x) {
  return x * x;
}</p>
<p>export class Person {
  constructor(name) {
    this.name = name;
  }
}</p>
<pre><code>- export할 대상을 하나의 객체로 구성하여 한 번에 export할 수도 있다
```js
const pi = Math.PI;

function square(x) {
  return x * x;
}

class Person {
  constructor(name) {
    this.name = name;
  }
}

// 변수, 함수 클래스를 하나의 객체로 구성하여 공개
export { pi, square, Person };</code></pre><h3 id="ems-import-키워드">EMS import 키워드</h3>
<ul>
<li>EMS는 import할 때 파일 확장자를 생략할 수 없다<pre><code class="language-js">// app.mjs
import { pi, square, Person } from &quot;./lib.mjs&quot;;
</code></pre>
</li>
</ul>
<p>console.log(pi);
console.log(square(10));
console.log(new Person(&#39;Lee&#39;)); </p>
<pre><code>```html
&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;body&gt;
    &lt;script type=&quot;module&quot; src=&quot;app.mjs&quot;&gt;&lt;/script&gt;
  &lt;body&gt;
&lt;html&gt;</code></pre><ul>
<li>모듈이 export한 식별자 이름을 일일이 지정하지 않고 한 번에 import할 수 있다. 이때 import되는 식별자는 as 뒤에 지정한 이름의 객체에 프로퍼티로 할당된다.<pre><code class="language-js">// app.mjs
import * as lib from &#39;./lib.mjs&#39;;</code></pre>
</li>
<li>모듈이 export한 식별자 이름을 변경하여 import할 수도 있다<pre><code class="language-js">import { pi as PI, square as sq, Person as P } from &#39;./lib.mjs&#39;;</code></pre>
</li>
<li>모듈에서 하나의 값만 export한다면 defalut 키워드를 사용할 수 있다. default 키워드를 사용하는 경우 기본적으로 이름 없이 하나의 값을 export한다<pre><code class="language-js">// lib.mjs
export default x =&gt; x * x;</code></pre>
</li>
<li>default 키워드를 사용하는 경우 var, let, const 키워드는 사용할 수 없다.<pre><code class="language-js">export default const foo = () =&gt; {}; // SyntaxError</code></pre>
</li>
<li>defalut 키워드와 함께 export한 모듈을 {} 없이 임의의 이름으로 import한다<pre><code class="language-js">import square from &#39;./lib.mjs&#39;;</code></pre>
</li>
</ul>
<h4 id="--출처--모던-자바스크립트-딥다이브-책">- 출처 : 모던 자바스크립트 딥다이브 책</h4>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 프로젝트 3주차 ]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-3%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 22 Sep 2024 05:16:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyohyo__/post/f0fe5be0-1154-4c91-8511-ebb4100e8f17/image.png" alt=""></p>
<p>프로젝트를 진행하면서 UI에 대해 자료조사한 내용을 작성해본다
프론트엔드 개발자는 UI의 트렌드에 대한 지식도 필요하다고 생각한다</p>
<blockquote>
<h1 id="참고할만한-사이트">참고할만한 사이트</h1>
</blockquote>
<h2 id="1-behance"><strong>1. Behance</strong></h2>
<ul>
<li><strong>다양한 디자이너들의 포트폴리오가 모여 있는 플랫폼</strong></li>
<li><strong>URL: <a href="https://www.behance.net/">behance.net</a></strong></li>
</ul>
<h2 id="2-dribbble"><strong>2. Dribbble</strong></h2>
<ul>
<li><strong>디자이너들이 자신의 작업을 공유하는 사이트</strong></li>
<li><strong>URL: <a href="https://www.dribbble.com/">dribbble.com</a></strong></li>
</ul>
<h2 id="3-awwwards"><strong>3. Awwwards</strong></h2>
<ul>
<li><strong>독창적이고 창의적인 웹사이트를 소개하는 플랫폼으로, 여러 테마의 웹사이트 디자인을 참고할 수 있습니다.</strong></li>
<li><strong>URL: <a href="https://www.awwwards.com/">awwwards.com</a>T</strong></li>
</ul>
<h2 id="4-hemeforest"><strong>4. hemeForest</strong></h2>
<ul>
<li><strong>다양한 웹사이트 템플릿을 판매하는 사이트.</strong></li>
<li><strong>URL: <a href="https://themeforest.net/">themeforest.net</a></strong></li>
</ul>
<blockquote>
<h1 id="사진과-애니메이션이-많으면-좋은-장점">사진과 애니메이션이 많으면 좋은 장점</h1>
</blockquote>
<h3 id="1-브랜드-이미지-강화">1. <strong>브랜드 이미지 강화</strong></h3>
<ul>
<li><strong>사진</strong>은 기업의 역사와 문화를 시각적으로 보여주며, 브랜드 아이덴티티를 강화하는 데 기여합니다. 오래된 기업일수록 각 시대를 대표하는 사진이 기업의 변천사를 효과적으로 전달할 수 있습니다.</li>
<li><strong>애니메이션</strong>을 사용하면, 기업의 혁신적이고 현대적인 이미지를 강화할 수 있습니다. 애니메이션은 브랜드가 기술적이고 창의적이라는 인상을 줄 수 있습니다.</li>
</ul>
<h3 id="2-사용자의-몰입도-향상">2. <strong>사용자의 몰입도 향상</strong></h3>
<ul>
<li>사진과 애니메이션은 단순한 텍스트보다 훨씬 더 많은 관심을 끌고, 사용자가 웹페이지에 더 오래 머무르게 만듭니다. 이를 통해 연혁 페이지에서 기업의 중요한 이정표나 성과를 효과적으로 전달할 수 있습니다.</li>
<li>애니메이션을 통해 페이지 전환이나 콘텐츠 등장 시 부드러운 효과를 제공하면, 사용자는 더 몰입감 있는 경험을 하게 됩니다.</li>
</ul>
<h3 id="3-정보-전달의-효율성-증가">3. <strong>정보 전달의 효율성 증가</strong></h3>
<ul>
<li><strong>사진</strong>을 통해 기업의 중요한 순간을 한눈에 보여줄 수 있으며, 긴 텍스트를 읽는 대신 이미지를 통해 빠르게 정보를 파악할 수 있습니다.</li>
<li><strong>애니메이션</strong>은 복잡한 연혁을 단계별로 나타내거나 주요 이정표가 부각되도록 시각적 강조를 할 수 있습니다. 예를 들어, 타임라인이 스크롤에 따라 동적으로 변화하면서 주요 사건이 자연스럽게 강조되는 효과를 제공할 수 있습니다.</li>
</ul>
<h3 id="4-역동적이고-기억에-남는-사용자-경험">4. <strong>역동적이고 기억에 남는 사용자 경험</strong></h3>
<ul>
<li>시각적 자료는 사용자의 기억에 오래 남습니다. 기업 연혁을 사진과 애니메이션으로 표현하면 단순히 연도와 사건을 나열하는 것보다 훨씬 더 흥미롭고 기억에 남는 경험을 제공합니다.</li>
<li>인터랙티브 애니메이션을 통해 사용자가 특정 연도나 사건에 대해 더 알아보는 방식으로 웹 페이지와 상호작용할 수 있습니다. 예를 들어, 사용자가 특정 연혁 항목을 클릭하면 그와 관련된 사진이나 동영상이 나타나는 방식이 있을 수 있습니다.</li>
</ul>
<h3 id="5-더-나은-모바일-및-다양한-디바이스-경험">5. <strong>더 나은 모바일 및 다양한 디바이스 경험</strong></h3>
<ul>
<li><strong>사진과 애니메이션</strong>은 특히 모바일 사용자들에게 시각적으로 더 큰 임팩트를 줍니다. 작은 화면에서 긴 텍스트를 읽는 것은 어려울 수 있지만, 이미지를 통해 더 효과적으로 정보를 전달할 수 있습니다.</li>
<li>애니메이션은 모바일 기기에서도 부드럽고 자연스러운 사용자 경험을 제공할 수 있습니다. 적절한 애니메이션과 이미지 최적화를 통해 성능 문제를 최소화할 수 있습니다.</li>
</ul>
<h3 id="6-복잡한-정보를-쉽게-해석-가능">6. <strong>복잡한 정보를 쉽게 해석 가능</strong></h3>
<ul>
<li>기업의 연혁은 보통 많은 정보를 포함합니다. 여러 사건이나 성과를 나열하는 방식은 사용자에게 혼란을 줄 수 있지만, 사진과 애니메이션을 적절히 배치하면 연혁을 보다 간결하고 명확하게 정리할 수 있습니다.</li>
<li>예를 들어, 중요한 사건들을 사진과 함께 시간 순서대로 배치하고, 애니메이션으로 흐름을 연결하면 복잡한 정보도 직관적으로 이해하기 쉬워집니다.</li>
</ul>
<h3 id="7-seo-및-웹-트래픽-증가">7. <strong>SEO 및 웹 트래픽 증가</strong></h3>
<ul>
<li>시각적 요소와 애니메이션이 포함된 페이지는 사용자의 참여도를 높이며, 이는 검색 엔진 최적화(SEO)에도 긍정적인 영향을 미칠 수 있습니다. 사람들이 페이지에서 오래 머물고 더 많은 페이지를 탐색하게 되면, 검색 엔진은 해당 페이지를 더 긍정적으로 평가할 수 있습니다.</li>
</ul>
<h3 id="8-스토리텔링-강화">8. <strong>스토리텔링 강화</strong></h3>
<ul>
<li>사진과 애니메이션은 기업 연혁을 단순한 연도별 사건의 나열이 아닌, 하나의 스토리로 만들 수 있습니다. 시각적 자료와 움직임을 통해 기업의 성장을 드라마틱하게 표현하여 사용자가 더 큰 감정적 공감을 느낄 수 있습니다.</li>
<li>예를 들어, 특정 시점에서의 어려움이나 도전, 성과 등을 애니메이션으로 표현하면 더욱 생생하게 기업의 이야기를 전달할 수 있습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 프로젝트 1주차]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-1%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 08 Sep 2024 02:58:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyohyo__/post/d842cee3-559f-40d2-8f2c-8fd98df5e78b/image.png" alt=""></p>
<p>이번 주는 프로젝트를 진행하면서 사용했던 라이브러리인 Framer Motion에서 사용했던 useInView에 대해 정리해본다</p>
<blockquote>
<h1 id="framer-motion이란">Framer Motion이란?</h1>
</blockquote>
<p>Framer가 제공하는 리액트용 애니메이션 라이브러리이다.</p>
<h2 id="장점">장점</h2>
<ul>
<li>타입 스크립트를 지원한다</li>
<li>최적화된 성능 및 풍부한 API가 있다</li>
<li>애니메이션을 직관적인 코드를 통해 손쉽게 제작해준다</li>
<li>프로토타이핑 툴인 Framer 간에 적용이 용이하다</li>
</ul>
<blockquote>
<h1 id="useinview">useInView</h1>
</blockquote>
<p>요소가 <a href="https://developer.mozilla.org/ko/docs/Glossary/Viewport">뷰포트</a>에 있는지 확인하는 훅이다.</p>
<h2 id="사용-방법-공식-홈페이지">사용 방법 <a href="https://www.framer.com/motion/use-in-view/">(공식 홈페이지)</a></h2>
<h3 id="import">import</h3>
<pre><code>import { useInView } from &quot;framer-motion&quot;</code></pre><ul>
<li><p>모든 HTML 요소의 가시성을 추적할 수 있습니다. ref를 사용하면 객체를 전달할 수 있습니다</p>
<pre><code>function Component() {
const ref = useRef(null)
const isInView = useInView(ref)

return &lt;div ref={ref} /&gt;
}</code></pre></li>
<li><p>요소가 뷰포트 밖에 있는 동안 false를 반환하고 뷰 내부로 이동하면 구성 요소를 다시 렌더링하고 true를 반환합니다.</p>
</li>
</ul>
<h3 id="useeffect">useEffect</h3>
<ul>
<li><p>변경 사항이 있을 때마다 함수를 실행 하려면 useEffect에 전달하기만 하면 됩니다.</p>
<pre><code class="language-ts">useEffect(() =&gt; {
console.log(&quot;Element is in view: &quot;, isInView)
}, [isInView])</code></pre>
<h3 id="옵션">옵션</h3>
</li>
<li><p>루트 : RefObject<HTMLElement>
기본적으로, useInView요소가 창 뷰포트에 들어오거나 나갈 때 요소의 가시성을 추적합니다. root스크롤 가능한 부모의 참조로 설정하면 대신 해당 요소를 뷰포트로 사용합니다.</p>
</li>
</ul>
<pre><code class="language-ts">function Carousel() {
  const container = useRef(null)
  const ref = useRef(null)
  const isInView = useInView({ root: container })

  return (
    &lt;div ref={container} style={{ overflow: &quot;scroll&quot; }}&gt;
      &lt;div ref={ref} /&gt;
    &lt;/div&gt;
  )
}</code></pre>
<ul>
<li><p>margin : string
요소 또는 창 뷰포트 root에 적용할 하나 이상의 여백입니다. 이는 요소가 뷰포트 내부로 간주되는 지점을 확장하거나 축소할 수 있습니다.
여백은 %또는 px로 정의되어야 합니다 </p>
<pre><code class="language-ts">const isInView = useInView({
margin: &quot;0px 100px -50px 0px&quot;
})</code></pre>
<p>참고: 브라우저 보안상의 이유로, <code>root</code>가 명시적으로 정의되지 않은 한 margin 교차 출처 iframe에는 적용되지 않습니다 .</p>
</li>
<li><p>once : boolean
요소가 뷰포트에 들어오면 useInView가 요소를 관찰하는 것을 멈추고 항상 true를 반환합니다. </p>
<pre><code class="language-ts">// 애니메이션이 한 번만 실행됩니다.
const isInView = useInView(ref, { once: true })</code></pre>
</li>
<li><p>amount : &quot;some&quot; | &quot;all&quot; | number
true를 반환하기 위해 뷰포트에 들어가야 하는 요소의 양입니다.
0과 1사이의 숫자로 정의되며, 여기서 0는 원소의 일부 또는 전부이고, 1는 원소의 전부입니다.</p>
</li>
</ul>
<blockquote>
<h1 id="내가-짠-코드">내가 짠 코드</h1>
</blockquote>
<ul>
<li>내가 필요한 애니메이션은 스크롤을 내리다가 이미지 요소가 화면 안에 들어왔을 때를 감지하여 이미지의 opacity가 0에서 1로 천천히 올라가는 것으로 구현했다</li>
</ul>
<pre><code class="language-ts">import { useEffect, useRef } from &#39;react&#39;;

import { motion, useAnimation, useInView } from &#39;framer-motion&#39;;

import img from &#39;이미지 경로&#39;;

const Components = () =&gt; {
  const controls = useAnimation();
  const ref = useRef(null);
  // 애니메이션이 한 번만 실행된다
  const isInView = useInView(ref, { once: false });

  useEffect(() =&gt; {
    if (isInView === false) {
        // 지정된 요소가 뷰포트에 없다면 opacity가 0이 된다
      controls.set({ opacity: 0 });
    } else {
      // 지정된 요소가 뷰포트에 있다면 opacity가 1이 된다
      controls.start({ opacity: 1 });
    }
  }, [isInView, controls]);

  return (
    &lt;section className=&quot;relative h-screen&quot;&gt;
      &lt;motion.img
        ref={ref} // 애니메이션을 실행할 객체 지정
        src={img}
        initial={{ opacity: 0 }} // 초기값
        animate={controls} // 애니메이션 컨트롤러
        transition={{ duration: 1, delay: 1 }} 
        // duration으로 서서히 이미지가 나타나는 효과를 준다
        // 사용자가 천천히 스크롤을 내릴 것을 감안하여 delay를 1초로 설정한다
      /&gt;
    &lt;/section&gt;
  );
};
export default Components;
</code></pre>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://www.framer.com/motion">공식문서</a></li>
<li><a href="https://handhand.tistory.com/306">https://handhand.tistory.com/306</a></li>
</ul>
<p>본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 과정(B-log) 리뷰로 작성 되었습니다.</p>
<p>#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #리액트프로젝트 #프론트엔드개발자양성과정 #개발자교육과정 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 사전직무교육 2주차 후기]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%EC%82%AC%EC%A0%84%EC%A7%81%EB%AC%B4%EA%B5%90%EC%9C%A1-2%EC%A3%BC%EC%B0%A8-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%EC%82%AC%EC%A0%84%EC%A7%81%EB%AC%B4%EA%B5%90%EC%9C%A1-2%EC%A3%BC%EC%B0%A8-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Sun, 01 Sep 2024 12:03:09 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyohyo__/post/3929a430-58cd-4f2e-b48d-1eac353198d9/image.png" alt=""></p>
<blockquote>
<h1 id="배운-내용">배운 내용</h1>
</blockquote>
<h3 id="리액트-훅">리액트 훅</h3>
<ul>
<li>useEffect</li>
<li>useLayoutEffect</li>
<li>useID</li>
<li>useTransition</li>
<li>useDeferredValue</li>
<li>useTransition vs useDeferredValue</li>
</ul>
<h3 id="리액트-메모이제이션">리액트 메모이제이션</h3>
<ul>
<li>useCallback</li>
<li>useMemo</li>
<li>React.memo</li>
<li>useReducer</li>
</ul>
<h3 id="리액트-컨텍스트">리액트 컨텍스트</h3>
<ul>
<li>Context API</li>
<li>useContext</li>
<li>zustand</li>
</ul>
<h3 id="리액트-라우터">리액트 라우터</h3>
<ul>
<li>react-router-dom</li>
<li>Outlet</li>
</ul>
<h3 id="데이터-통신">데이터 통신</h3>
<ul>
<li>fetch API</li>
<li>Suspense와 ErrorBoundary</li>
<li>Tanstack Query</li>
</ul>
<h3 id="그외-실무에서-많이-사용하는-기법">그외 실무에서 많이 사용하는 기법</h3>
<ul>
<li>이미지를 미리 로딩하는 기법</li>
<li>디바운스 기법</li>
</ul>
<blockquote>
<h1 id="outlet">Outlet</h1>
</blockquote>
<p>이번주에 배운 개념 중 React Router의 Outlet에 대해 정리해보고자 한다.</p>
<ul>
<li>공식 홈페이지 : <a href="https://reactrouter.com/en/main/components/outlet">https://reactrouter.com/en/main/components/outlet</a></li>
<li><strong>외부 레이아웃을 쉽게 구성해주는 리액트 라우터 돔의 기능</strong>이다.</li>
<li>Outlet은 아래처럼 레이아웃 컴포넌트를 선언해준 후 <Outlet />으로 작성하면 children으로 선언한 컴포넌트가 자동으로 들어간다.<pre><code class="language-ts">// DefaultLayout.tsx (레이아웃으로 사용할 컴포넌트)
import { Link, Outlet } from &quot;react-router-dom&quot;;
</code></pre>
</li>
</ul>
<p>const DefaultLayout = () =&gt; {
  return (
    &lt;&gt;
      <header>
        <h1>header area</h1>
        <ul>
          <li>
            <Link to="/">Home</Link>
          </li>
        </ul>
      </header>
      <main>
        <h1>main area</h1>
        {/* 중간에 컨텐츠를 Outlet로 선언하면 child처럼 들어간다 */}
        <Outlet />
      </main>
      <footer>
        <h1>footer area</h1>
      </footer>
    &lt;/&gt;
  );
};
export default DefaultLayout;</p>
<pre><code>```ts
// router.tsx (라우터만 모아놓은 파일)

import { createBrowserRouter } from &quot;react-router-dom&quot;;
import HomeView from &quot;../views/HomeView&quot;;
import AboutView from &quot;../views/AboutView&quot;;
import BlogView from &quot;../views/BlogView&quot;;
import DefaultLayout from &quot;../layout/DefaultLayout&quot;;
import BlogLayout from &quot;../layout/BlogLayout&quot;;

export const router = createBrowserRouter([
  {
    element: &lt;DefaultLayout /&gt;,
    // 아래와 같이 children으로 선언한 컴포넌트가 Outlet 위치에 들어간다.
    children: [{ path: &quot;/&quot;, element: &lt;HomeView /&gt; }],
  },
  {
    element: &lt;BlogLayout /&gt;,
    children: [
      {
        path: &quot;/blog/:blogId&quot;,
        element: &lt;BlogView /&gt;,
      },
    ],
  },
  {
    path: &quot;/&quot;,
    element: &lt;HomeView /&gt;,
  },
  {
    path: &quot;/about&quot;,
    element: &lt;AboutView /&gt;,
  },
  {
    path: &quot;/blog/:blogId&quot;,
    element: &lt;BlogView /&gt;,
  },
]);
</code></pre><pre><code class="language-ts">// main.tsx (최상위 컴포넌트)

import { StrictMode } from &quot;react&quot;;
import { createRoot } from &quot;react-dom/client&quot;;
import &quot;./index.css&quot;;
import { RouterProvider } from &quot;react-router-dom&quot;;
import { router } from &quot;./router/index.tsx&quot;;

createRoot(document.getElementById(&quot;root&quot;)!).render(
  &lt;StrictMode&gt;
    &lt;RouterProvider router={router} /&gt;
  &lt;/StrictMode&gt;
);
</code></pre>
<p>이전까지는 Outlet에 대한 내용을 몰라서 컴포넌트마다 레이아웃 컴포넌트를 불러왔다. 다음주부터 들어가는 프로젝트에서는 Outlet 기능을 사용해서 더 가독성이 좋은 코드를 작성해야겠다.</p>
<blockquote>
<h1 id="회고-및-교육-후기">회고 및 교육 후기</h1>
</blockquote>
<h3 id="리액트-실무역량이-확실히-발전된-것-같다">리액트 실무역량이 확실히 발전된 것 같다</h3>
<ul>
<li>리액트, 타입스크립트로 프로젝트를 해보았지만 교육을 들으면서 새로 알게 된 내용도 있어서 아직 많이 부족하다는 것을 느꼈다.</li>
<li>여러 리액트 훅에 대해 공부했지만 프로젝트에서는 어떻게 사용해야 되는지 감이 안 잡혔는데 다양한 실습을 해보면서 어떻게 프로젝트에 적용해야 할지 알 수 있어서 좋았다</li>
<li>책으로 공부하는 편이 내용을 다시 찾아볼 수 있고 메모하기에도 효율적이고 더 좋다고 생각했는데 강의도 강사님이 좋은 분이면 더 이해하기 쉽고 효율적으로 공부할 수 있겠다는 생각이 들었다</li>
</ul>
<h3 id="교육시설과-운영진분들이-좋았다">교육시설과 운영진분들이 좋았다</h3>
<ul>
<li>넓고 쾌적한 강의시설에서 무료로 강의를 듣고 있다는 것에 감사함을 느꼈다
<img src="https://velog.velcdn.com/images/hyohyo__/post/cf92e694-055f-40b1-ad5c-4f977e81ab91/image.png" alt=""></li>
<li>운영진분들께 질문을 하면 바로 답변해주시고, 친절하게 상세히 설명해주셔서 가끔 감동받았다</li>
</ul>
<h3 id="교육내용이-알차고-좋았다">교육내용이 알차고 좋았다</h3>
<ul>
<li>부트캠프 강의나 MDN, 다양한 블로그에서 리액트 훅에 대한 설명을 들어도 잘 이해가 가지 않는 경우가 많았는데 강사님께서 쉽게 설명해주시는 편이라 이해가 잘 되었다. </li>
<li>강사님께서 실무적인 내용을 위주로 알려주셔서 참여하길 잘했다는 생각이 들었다.</li>
<li>취업에 대한 내용도 많이 해주셔서 좋았다.</li>
</ul>
<p>본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 과정(B-log) 리뷰로 작성 되었습니다.</p>
<p>#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #리액트프로젝트 #프론트엔드개발자양성과정 #개발자교육과정</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트의 배열]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%B0%B0%EC%97%B4</link>
            <guid>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%9D%98-%EB%B0%B0%EC%97%B4</guid>
            <pubDate>Sat, 31 Aug 2024 08:21:56 GMT</pubDate>
            <description><![CDATA[<blockquote>
<h1 id="배열이란">배열이란?</h1>
</blockquote>
<ul>
<li>배열은 여러 개의 값을 순차적으로 나열한 자료구조다. 배열은 사용 빈도가 매우 높은 가장 기본적인 자료구조다.</li>
</ul>
<pre><code class="language-js">const arr = [&quot;apple&quot;, &quot;banana&quot;, &quot;orange&quot;];</code></pre>
<ul>
<li>배열이 가지고 있는 값을 요소라고 부른다. 즉, 원시값은 물론 객체, 함수, 배열 등 자바스크립트에서 값으로 인정하는 모든 것은 배열의 요소가 될 수 있다.</li>
<li>배열의 요소는 배열에서 자신의 위치를 나타내는 0 이상의 정수인 인덱스를 갖는다. 인덱스는 배열의 요소에 접근할 때 사용한다.</li>
<li>요소에 접근할 때는 대괄호 표기법을 사용한다 대괄호 내에는 접근하고 싶은 요소의 인덱스를 지정한다.<pre><code class="language-js">arr[0] // apple
arr[1] // banana
arr[2] // orange</code></pre>
</li>
<li>배열은 요소의 개수, 즉 배열의 길이를 나타내는 length 프로퍼티를 갖는다.<pre><code class="language-js">arr.length // 3</code></pre>
</li>
<li>배열은 인덱스와 length 프로퍼티를 갖기 때문에 for문을 통해 순차적으로 요소에 접근할 수 있다.<pre><code class="language-js">for (let i = 0; i &lt; arr.length; i++){
  console.log(arr[i]);
}</code></pre>
</li>
</ul>
<blockquote>
<h1 id="자바스크립트의-배열-특징">자바스크립트의 배열 특징</h1>
</blockquote>
<h3 id="1-자바스크립트에-배열이라는-타입은-존재하지-않는다">1. 자바스크립트에 배열이라는 타입은 존재하지 않는다.</h3>
<ul>
<li>자바스크립트의 배열은 일반적인 배열의 동작은 흉내 낸 특수한 Array 객체다. </li>
<li>객체 타입이긴 하지만 일반 객체와 달리 자바스크립트의 배열은 &quot;값의 순서&quot;와 &quot;length 프로퍼티&quot;가 있다. 그래서 반복문을 통해 순차적으로 값에 접근하기 적합한 자료구조다.</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>객체</th>
<th>배열</th>
</tr>
</thead>
<tbody><tr>
<td>구조</td>
<td>프로퍼티 키와 프로퍼티 값</td>
<td>인덱스와 요소</td>
</tr>
<tr>
<td>값의 참조</td>
<td>프로퍼티 키</td>
<td>인덱스</td>
</tr>
<tr>
<td>값의 순서</td>
<td>✕</td>
<td>○</td>
</tr>
<tr>
<td>Length 프로퍼티</td>
<td>✕</td>
<td>○</td>
</tr>
</tbody></table>
<ul>
<li>위의 예제처럼 자바스크립트 배열은 인덱스를 나타내는 문자열을 프로퍼티 키로 가지며, length 프로퍼티를 갖는 특수한 객체다. 자바스크립트 배열의 요소는 사실 프로퍼티 값이다.</li>
</ul>
<pre><code class="language-js">console.log(Object.getOwnPropertyDesciptors([1, 2, 3]));
/*
{
 &#39;0&#39;: {value: 1, writable: true, enumerable: true, configurable: true}
 &#39;1&#39;: {value: 2, writable: true, enumerable: true, configurable: true}
 &#39;2&#39;: {value: 3, writable: true, enumerable: true, configurable: true}
 length: {value: 13 writable: true, enumerable: false, configurable: false}
}
*/</code></pre>
<h3 id="2-밀집-배열이-아닌-희소-배열이다">2. 밀집 배열이 아닌 희소 배열이다.</h3>
<ul>
<li>자바스크립트에서 사용할 수 있는 모든 값은 객체의 프로퍼티 값이 될 수 있으므로 어떤 타입의 값이라도 배열의 요소가 될 수 있다.</li>
</ul>
<pre><code class="language-js">const arr = [
  &quot;string&quot;,
  10,
  true,
  null,
  undefined,
  NaN,
  Infinity,
  [],
  {},
  function () {},
];</code></pre>
<ul>
<li>length 프로퍼티 값은 요소의 개수, 즉 배열의 길이를 바탕으로 결정되지만 임의의 숫자 값을 명시적으로 할당할 수도 있다.</li>
<li>현재 length 프로퍼티 값보다 작은 숫자 값을 할당하면 배열의 길이가 줄어든다.<pre><code class="language-js">const arr = [1, 2, 3, 4, 5];
</code></pre>
</li>
</ul>
<p>// 현재 length 프로퍼티 값보다 작은 숫자 값을 할당하면 배열의 길이가 줄어든다.
arr.length = 3;</p>
<p>// 배열의 길이가 5에서 3으로 줄어든다.
console.log(arr); // [1, 2, 3]</p>
<pre><code>- 주의할 것은 현재 length 프로퍼티 값보다 큰 숫자 값을 할당하는 경우다. 이때 length 프로퍼티 값은 변경되지만 배열의 길이가 늘어나지는 않는다.
```js
const arr = [1];

// 현재 length 프로퍼티 값인 1보다 큰 숫자 값 3을 length 프로퍼티에 할당
arr.length = 3;

// length 프로퍼티 값은 변경되지만 실제로 배열의 길이가 늘어나지는 않는다.
console.log(arr.length); // 3
console.log(arr); // [1, empty * 2]</code></pre><ul>
<li>위 예제의 출력 결과에서 empty * 2는 실제로 추가된 배열의 요소가 아니다. 즉, arr[1]과 arr[2]에는 값이 존재하지 않는다.</li>
<li>이처럼 현재 length 프로퍼티 값보다 큰 숫자 값을 length 프로퍼티에 할당하는 경우 length 프로퍼티 값은 성공적으로 변경되지만 실제 배열에는 아무런 변함이 없다. 값 없이 비어 있는 요소를 위해 메모리 공간을 확보하지 않으며 빈 요소를 생성하지도 않는다.
```js
console.log(Object.getOwnPropertyDesciptors(arr));
/*
{
&#39;0&#39;: {value: 1, writable: true, enumerable: true, configurable: true}
length: {value: 3 writable: true, enumerable: false, configurable: false}
}</li>
<li>/
```</li>
<li>자바스크립트의 배열은 지금까지 살펴본 자료구조에서 말하는 일반적인 의미의 배열과 다르다. 즉, 배열의 요소를 위한 각각의 메모리 공간은 동일한 크기를 갖지 않아도 되며, 연속적으로 이어져 있지 않을 수도 있다. 배열의 요소가 연속적으로 이어져 있지 않은 배열을 <strong>희소 배열</strong>이라 한다.</li>
<li>자바스크립트는 문법적으로 희소 배열을 허용하지만 <strong>희소 배열은 사용하지 않는 것이 좋다.</strong> 의도적으로 희소 배열을 만들어야 하는 상황은 발생하지 않는다. 희소 배열은 연속적으로 값의 집합이라는 배열의 기본적인 개념과 맞지 않으며, 성능에도 좋지 않은 영향을 준다. 최적화가 잘 되어 있는 모던 자바스크립트 엔진은 요소의 타입이 일치하는 배열을 생성할 때 일반적인 의미의 배열처럼 연속된 메모리 공간을 확보하는 것으로 알려져 있다.</li>
</ul>
<h3 id="3-배열-요소에-접근은-느리지만-삽입삭제는-빠르다">3. 배열 요소에 접근은 느리지만 삽입/삭제는 빠르다.</h3>
<ul>
<li>일반적인 배열은 인덱스로 요소에 빠르게 접근할 수 있다. 하지만 요소를 삽입 또는 삭제하는 경우에는 효율적이지 않다.</li>
<li>자바스크립트 배열을 해시 테이블로 구현된 객체이므로 인덱스로 요소에 접근하는 경우 일반적인 배열보다 성능적인 면에서 느릴 수 밖에 없는 구조적인 단점이 있다. 하지만 요소를 삽입 또는 삭제하는 경우에은 일반적인 배열보다 빠른 성능을 기대할 수 있다.</li>
<li>인덱스로 배열 요소에 접근할 때 일반적인 배열보다 느릴 수 밖에 없는 구조적인 단점을 보완하기 위해 대부분의 모던 자바스크립트 엔진은 배열을 일반 객체와 구별하여 좀 더 배열처럼 동작하도록 최적화하여 구현했다. 다음과 같이 일반 객체의 성능을 테스트해 보면 배열이 일반 객체보다 약 2배 정도 빠르다는 것을 알 수 있다.</li>
</ul>
<pre><code class="language-js">const arr = [];

console.time(&quot;Array Performance Test&quot;);

for (let i = 0; i &lt; 10000000; i++) {
  arr[i] = i;
}
console.timeEnd(&quot;Array Performance Test&quot;);
// 약 340ms

const obj = {};

console.time(&quot;Object Performance Test&quot;);

for (let i = 0; i &lt; 10000000; i++) {
  obj[i] = i;
}

console.timeEnd(&quot;Object Performance Test&quot;);
// 약 600ms</code></pre>
<blockquote>
<h1 id="배열-생성">배열 생성</h1>
</blockquote>
<h3 id="배열-리터럴로-생성">배열 리터럴로 생성</h3>
<ul>
<li>가장 일반적이고 간편한 배열 생성 방식은 배열 리터럴을 사용하는 것이다.</li>
<li>배열 리터럴은 0개 이상의 요소를 쉼표로 구분하여 대괄호([])로 묶는다. 배열 리터럴은 객체 리터럴과 달리 프로퍼티 키가 없고 값만 존재한다.<pre><code class="language-js">const arr = [1,2,3];
console.log(arr.length); // 3</code></pre>
</li>
<li>배열 리터럴에 요소를 하나도 추가하지 않으면 배열의 길이, 즉 length 프로퍼티 값이 0인 빈 배열이 된다.<pre><code class="language-js">const arr = [];
console.log(arr.length); // 0</code></pre>
<h3 id="array-생성자-함수로-생성">Array 생성자 함수로 생성</h3>
</li>
<li>Object 생성자 함수를 통해 객체를 생성할 수 있듯이 Array 생성자 함수를 통해 배열을 생성할 수도 있다. Array 생성자 함수는 전달된 인수의 개수에 따라 다르게 동작하므로 주의가 필요하다.</li>
<li>전달된 인수가 1개이고 숫자인 경우 length 프로퍼티 값이 인수인 배열을 생성한다.  <pre><code class="language-js">const arr = new Array(10);
</code></pre>
</li>
</ul>
<p>console.log(arr); // [empty * 10]
console.log(arr.length) // 10</p>
<pre><code>- 이때 생성된 배열은 희소 배열이다. length 프로퍼티 값은 0이 아니지만 실제로 배열의 요소는 존재하지 않는다.  
- 전달된 인수가 2개 이상이거나 숫자가 아닌 경우 인수를 요소로 갖는 배열을 생성한다.
```js
// 전달된 인수가 2개 이상이면 인수를 요소로 갖는 배열을 생성한다.
new Array(1, 2, 3); // [1, 2, 3]

// 전달된 인수가 1개지만 숫자가 아니면 인수를 요소로 갖는 배열을 생성한다.
new Array({}); // [{}]</code></pre><ul>
<li>Array 생성자 함수는 new 연산자와 함께 호출하지 않더라도, 즉 일반 함수로서 호출해도 배열을 생성하는 생성자 함수로 동작한다. 이는 Array 생성자 함수 내부에서 new.target을 확인하기 때문이다.<pre><code class="language-js">Array(1, 2, 3); // [1, 2, 3]</code></pre>
</li>
</ul>
<h3 id="arrayof-로-생성">Array.of 로 생성</h3>
<ul>
<li>ES6에서 도입된 Array.of 메서드는 전달된 인수를 요소로 갖는 배열을 생성한다. Array.of는 Array 생성자 함수와 다르게 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열을 생성한다.</li>
</ul>
<pre><code class="language-js">// 인수가 없을 때는 빈배열을 생성한다.
Array.of(); // []

// 전달된 인수가 1개이고 숫자이더라도 인수를 요소로 갖는 배열을 생성한다.
Array.of(1); // [1]

Array.of(1, 2, 3); // [1, 2, 3]

Array.of(&quot;string&quot;); // [&#39;string&#39;]</code></pre>
<h3 id="arrayfrom로-생성">Array.from로 생성</h3>
<ul>
<li>ES6에서 도입된 Array.from 메서드는 유사 배열 객체 또는 이터러블 객체를 인수로 전달받아 배열로 변환하여 반환한다.</li>
</ul>
<pre><code class="language-js">// 유사 배열 객체를 변환하여 배열을 생성한다.
Array.from({ length: 2, 0: &quot;a&quot;, 1: &quot;b&quot; }); // [&#39;a&#39;, &#39;b&#39;]

// 이터러블을 변환하여 배열을 생성한다. 문자열은 이터러블이다.
Arrray.from(&quot;Hello&quot;); // [&#39;H&#39;, &#39;e&#39;, &#39;l&#39;, &#39;l&#39;, &#39;o&#39;]</code></pre>
<ul>
<li>Array.from을 사용하면 두 번째 인수로 전달한 콜백 함수를 통해 값을 만들면서 요소를 채울 수 있다. Array.from 메서드는 두 번째 인수로 전달한 콜백 함수에 첫 번째 인수에 의해 생성된 배열의 요소값과 인덱스를 순차적으로 전달하면서 호출하고, 콜백 함수의 반환값으로 구성된 배열을 반환한다.</li>
</ul>
<pre><code class="language-js">// Array.from에 length만 존재하는 유사 배열 객체를 전달하며 undefined를 요소로 채운다.
Array.from({ length: 3 }); // [undefined, undefined, undefined]

// Array.from은 두 번째 인수로 전달한 콜백 함수의 반환값으로 구성된 배열을 반환한다.
Array.from({ length: 3 }, (_, i) =&gt; i); // [0, 1, 2]</code></pre>
<h4 id="유사-배열-객체와-이터러블-객체">유사 배열 객체와 이터러블 객체</h4>
<ul>
<li>유사배열 객체는 마치 배열처럼 프로퍼티 값에 접근할 수 잇고 length 프로퍼티를 갖는 객체를 말한다. 유사 배열 개체는 마치 배열처럼 for문으로 순회할 수 있다</li>
</ul>
<pre><code class="language-js">const arrayLike = {
  &#39;0&#39;: &quot;apple&quot;,
  &#39;1&#39;: &quot;banana&quot;,
  &#39;2&#39;: &quot;orange&quot;,
  length: 3,
};</code></pre>
<ul>
<li>이터러블 객체는 Symbol.iterator 메서드를 구현하여 for...of 문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭처링 할당의 대상으로 사용할 수 있는 객체를 말한다. ES6에서 제공하는 빌트인 이터러블은 Array, String, Map, Set, DOM 컬렉션, arguments 등이 있다.</li>
</ul>
<blockquote>
<h1 id="배열-요소의-참조추가갱신삭제">배열 요소의 참조/추가/갱신/삭제</h1>
</blockquote>
<h3 id="참조">참조</h3>
<ul>
<li>배열의 요소를 참조할 때는 대괄호([]) 표기법을 사용한다. 대괄호 안에는 인덱스가 와야 한다. 정수로 평가되는 표현식이라면 인덱스 대신 사용할 수 있다. 인덱스는 값을 참조할 수 있다는 의미에서 객체의 프로퍼티 키와 같은 역할을 한다.</li>
</ul>
<pre><code class="language-js">const arr = [1, 2];

// 인덱스가 0인 요소를 참조
console.log(arr[0]); // 1
// 인덱스가 1인 요소를 참조
console.log(arr[1]); // 2</code></pre>
<ul>
<li>존재하지 않는 요소에 접근하면 undefined가 반환된다.</li>
</ul>
<pre><code class="language-js">const arr = [1, 2];

// 인덱스가 2인 요소를 참조, 배열 arr에는 인덱스가 2인 요소가 존재하지 않는다.
console.log(arr[2]); // undefined</code></pre>
<ul>
<li>배열은 사실 인덱스를 나타내는 문자열을 프로퍼티 키로 갖는 객체다. 따라서 존재하지 않는 프로퍼티 키로 객체의 프로퍼티에 접근했을 때 undefined를 반환하는 것처럼 배열도 존재하지 않는 요소를 참조하면 undefined를 반환한다.</li>
<li>같은 이유로 희소 배열의 존재하지 않는 요소를 참조해도 undefined가 반환된다.</li>
</ul>
<pre><code class="language-js">// 희소 배열
const arr = [1, , 3];

// 배열 arr에는 인덱스가 1인 요소가 존재하지 않는다.
console.log(Object.getOwnPropertyDesciptors(arr));
/*
{
 &#39;0&#39;: {value: 1, writable: true, enumerable: true, configurable: true}
 &#39;2&#39;: {value: 3, writable: true, enumerable: true, configurable: true}
 length: {value: 3 writable: true, enumerable: false, configurable: false}
}
*/

// 존재하지 않는 요소를 참조하면 undefined가 반환된다.</code></pre>
<h3 id="추가-및-갱신">추가 및 갱신</h3>
<ul>
<li>객체에 프로퍼티를 동적으로 추가할 수 있는 것처럼 배열에도 요소를 동적으로 추가할 수 있다. 존재하지 않는 인덱스를 사용해 값을 할당하면 새로운 요소가 추가된다. 이때 length 프로퍼티 값은 자동 갱신된다.</li>
</ul>
<pre><code class="language-js">const arr = [0];

// 배열 요소의 추가
arr[1] = 1;

console.log(arr); // [0, 1]
console.log(arr.length); // 2</code></pre>
<ul>
<li>만약 현재 배열의 length 프로퍼티 값보다 큰 인덱스로 새로운 요소를 추가하면 희소 배열이 된다.</li>
</ul>
<pre><code class="language-js">arr[100] = 100;

console.log(arr); // [0, 1, empty * 98, 100]
console.log(arr.length); // [101]</code></pre>
<ul>
<li>이때 인덱스로 요소에 접근하여 명시적으로 값을 할당하지 않은 요소는 생성되지 않는다는 것에 주의하자</li>
</ul>
<pre><code class="language-js">console.log(Object.getOwnPropertyDesciptors(arr));
/*
{
 &#39;0&#39;: {value: 0, writable: true, enumerable: true, configurable: true}
 &#39;1&#39;: {value: 1, writable: true, enumerable: true, configurable: true}
 &#39;100&#39;: {value: 100, writable: true, enumerable: true, configurable: true}
 length: {value: 3 writable: true, enumerable: false, configurable: false}
}
*/</code></pre>
<ul>
<li>이미 요소가 존재하는 요소에 값을 재할당하면 요소값이 갱신된다.</li>
</ul>
<pre><code class="language-js">// 요소값의 갱신
arr[1] = 10;

console.log(arr); // [0, 10 empty * 98, 100]</code></pre>
<ul>
<li>인덱스는 요소의 위치를 나타내므로 반드시 0 이상의 정수(또는 정수 형태의 문자열)를 사용해야 한다. 만약 정수 이외의 값을 인덱스처럼 사용하면 요소가 생성되는 것이 아니라 프로퍼티가 생성된다. 이때 추가된 프로퍼티는 length 프로퍼티 값에 영향을 주지 않는다.</li>
</ul>
<pre><code class="language-js">const arr = [];

// 배열 요소 추가
arr[0] = 1;
arr[&quot;1&quot;] = 2;

// 프로퍼티 추가
arr[&quot;foo&quot;] = 3;
arr.bar = 4;
arr[1.1] = 5;
arr[-1] = 6;

console.log(arr); // [1, 2, foo:3, bar:4, &#39;1.1&#39;: 5, &#39;-1&#39;:6]

// 프로퍼티는 length에 영향을 주지 않는다.
console.log(arr.length); // 2</code></pre>
<h3 id="삭제">삭제</h3>
<ul>
<li>배열은 사실 객체이기 때문에 특정 요소를 삭제하기 위해 delete 연산자를 사요할 수 있다.</li>
</ul>
<pre><code class="language-js">const arr = [1,2,3];

// 배열 요소의 삭제
delete.arr[1];
console.log(arr); // [1, empty, 3]

// length 프로퍼티에 영향을 주지 않는다. 즉, 희소 배열이 된다.
console.log(arr.length); // 3</code></pre>
<ul>
<li>delete 연산자는 객체의 프로퍼티를 삭제한다. 따라서 위 예제의 delete arr[1]은 arr에서 프로퍼티 키가 &#39;1&#39;인 프로퍼티를 삭제한다. 이때 배열은 희소 배열이 되며 length 프로퍼티 값은 변하지 않는다. 따라서 희소 배열을 만드는 delete 연산자는 사용하지 않는 것이 좋다.</li>
<li>희소 배열을 만들지 않으면서 배열의 특정 요소를 완전히 삭제하려면 splice 메서드를 사용한다.</li>
</ul>
<pre><code class="language-js">const arr = [1, 2, 3];

// splice(삭제를 시작할 인덱스, 삭제할 요소 수)
arr.splice(1, 1);
console.log(arr); // [1,3]

// length가 자동 갱신된다.
console.log(arr.length); // 2</code></pre>
<blockquote>
<h1 id="많이-쓰는-배열-메서드">많이 쓰는 배열 메서드</h1>
</blockquote>
<h2 id="push">push()</h2>
<ul>
<li>배열의 끝에 하나 이상의 요소를 추가한다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;apple&#39;, &#39;banana&#39;];
fruits.push(&#39;orange&#39;);
console.log(fruits); // [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;]</code></pre>
<h2 id="pop">pop()</h2>
</li>
<li>배열의 마지막 요소를 제거하고 그 요소를 반환한다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;];
let lastFruit = fruits.pop();
console.log(lastFruit); // &#39;orange&#39;
console.log(fruits);    // [&#39;apple&#39;, &#39;banana&#39;]</code></pre>
<h2 id="shift">shift()</h2>
</li>
<li>배열의 첫 번째 요소를 제거하고 그 요소를 반환한다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;];
let firstFruit = fruits.shift();
console.log(firstFruit); // &#39;apple&#39;
console.log(fruits);     // [&#39;banana&#39;, &#39;orange&#39;]</code></pre>
<h2 id="unshift">unshift()</h2>
</li>
<li>배열의 앞에 하나 이상의 요소를 추가하고 변경되 length 프로퍼티 값을 반환한다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;banana&#39;, &#39;orange&#39;];
const result = fruits.unshift(&#39;apple&#39;);
console.log(result); // 3
console.log(fruits); // [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;]</code></pre>
<h2 id="splice">splice()</h2>
</li>
<li>배열의 요소를 추가하거나 제거한다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;];
fruits.splice(1, 1, &#39;grape&#39;);
console.log(fruits); // [&#39;apple&#39;, &#39;grape&#39;, &#39;orange&#39;]</code></pre>
</li>
<li>이 예시에서 splice(1, 1, &#39;grape&#39;)는 인덱스 1에서 1개의 요소(&#39;banana&#39;)를 제거하고, 그 자리에 &#39;grape&#39;를 추가한다.</li>
</ul>
<ol start="6">
<li>slice()</li>
</ol>
<ul>
<li>배열의 일부를 추출하여 새로운 배열을 반환한다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;, &#39;grape&#39;];
let citrus = fruits.slice(1, 3);
console.log(citrus); // [&#39;banana&#39;, &#39;orange&#39;]</code></pre>
0 이 예시에서 slice(1, 3)는 인덱스 1에서 시작해서 인덱스 3 이전까지의 요소를 반환한다.<h2 id="concat">concat()</h2>
</li>
<li>두 개 이상의 배열을 결합하여 새로운 배열을 만듭니다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;apple&#39;, &#39;banana&#39;];
let moreFruits = [&#39;orange&#39;, &#39;grape&#39;];
let allFruits = fruits.concat(moreFruits);
console.log(allFruits); // [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;, &#39;grape&#39;]</code></pre>
<h2 id="foreach">forEach()</h2>
</li>
<li>배열의 각 요소에 대해 한 번씩 제공된 함수를 실행한다.</li>
<li>예시<pre><code class="language-js">let fruits = [&#39;apple&#39;, &#39;banana&#39;, &#39;orange&#39;];
fruits.forEach(fruit =&gt; console.log(fruit));
</code></pre>
</li>
</ul>
<p>// apple
// banana
// orange</p>
<pre><code>## map()
- 배열의 각 요소에 대해 제공된 함수를 호출한 결과를 모아 새로운 배열을 만든다.
- 예시
```js
let numbers = [1, 2, 3];
const doubled = numbers.map(number =&gt; number * 2);

console.log(doubled); // [2, 4, 6]</code></pre><h2 id="filter">filter()</h2>
<ul>
<li>제공된 함수의 조건을 만족하는 모든 요소를 모아 새로운 배열을 만든다.</li>
<li>예시<pre><code class="language-js">let numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter(number =&gt; number % 2 === 0);
</code></pre>
</li>
</ul>
<p>console.log(evenNumbers); // [2, 4]</p>
<pre><code>## reduce()
- 자신을 호출한 배열을 모든 요소를 순회하며 인수로 전달받은 콜백 함수를 반복 호출한다.
- 예시
```js
let numbers = [1, 2, 3, 4];

numbers.reduce((누산값, 현재요소값, 현재요소의index, 현재배열) =&gt; {
  return 다음누산값;
}, 초기누산값);

const sum = numbers.reduce((acc, number) =&gt; acc + number, 0);

console.log(sum); // 10</code></pre><h4 id="참고">참고</h4>
<ol>
<li>모던 자바스크립트 딥다이브 책</li>
<li><a href="https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array">MDN - Array</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 사전직무교육 1주차 내용]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%EC%82%AC%EC%A0%84%EC%A7%81%EB%AC%B4%EA%B5%90%EC%9C%A1-1%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9</link>
            <guid>https://velog.io/@hyohyo__/%EC%9C%A0%EB%8D%B0%EB%AF%B8x%EC%8A%A4%EB%82%98%EC%9D%B4%ED%8D%BC%ED%8C%A9%ED%86%A0%EB%A6%AC-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%BA%A0%ED%94%84-React-2%EA%B8%B0-%EC%82%AC%EC%A0%84%EC%A7%81%EB%AC%B4%EA%B5%90%EC%9C%A1-1%EC%A3%BC%EC%B0%A8-%EB%82%B4%EC%9A%A9</guid>
            <pubDate>Sun, 25 Aug 2024 01:55:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyohyo__/post/71950fc9-ba00-4d63-9ba9-9564c3ded6ae/image.png" alt=""></p>
<blockquote>
<h1 id="배운내용">배운내용</h1>
</blockquote>
<h3 id="자바스크립트">자바스크립트</h3>
<ul>
<li>필수 자바스크립트 문법 : 템플릿 문자열, 화살표 함수, 비구조화 할당<h3 id="타입스크립트">타입스크립트</h3>
</li>
<li>타입 : 기본 타입, 함수 타입</li>
<li>타입 오퍼레이터</li>
<li>인터페이스</li>
<li>타입별칭</li>
<li>이넘</li>
<li>제너릭<h3 id="리액트">리액트</h3>
</li>
<li>NPM, NPX, YARN의 차이</li>
<li>바벨과 웹팩</li>
<li>js, jsx, tsx 차이</li>
<li>클래스 컴포넌트, 함수형 컴포넌트</li>
<li>컴포넌트 CSS 스타일링</li>
<li>Tailwind CSS</li>
<li>폰트 적용 방법</li>
<li>컴포넌트 : 컴포넌트 이벤트, Props, Children, 조건부 렌더링, 반복 렌더링, 이미지 렌더링</li>
<li>리액트 훅 : useState, useRef</li>
</ul>
<blockquote>
<h1 id="수행한-과제">수행한 과제</h1>
</blockquote>
<h3 id="819-월">8/19 (월)</h3>
<ol>
<li>타입스크립트로 문장에서 가장 긴 단어를 반환하는 함수 만들기</li>
<li>타입스크립트로 암스토롱 수 구하는 함수 만들기<h3 id="821-수">8/21 (수)</h3>
</li>
</ol>
<ul>
<li>프로필 카드 컴포넌트 만들기<h3 id="822-목">8/22 (목)</h3>
</li>
<li>로그인 컴포넌트 만들기<h3 id="823-금">8/23 (금)</h3>
</li>
</ul>
<ol>
<li>여행 사이트 만들기</li>
<li>이미지 삭제/복구 버튼 만들기</li>
</ol>
<blockquote>
<h1 id="과제-상세">과제 상세</h1>
</blockquote>
<p>8/23 (금)에 수행했던 여행 사이트 만들기 과제 내용을 풀어보고자 한다. </p>
<h3 id="과제-내용">과제 내용</h3>
<ul>
<li>리액트와 타입스크립트를 이용하여 아래와 같이 버튼 클릭 시 하단 이미지가 변하는 사이트 만들기
<img src="https://velog.velcdn.com/images/hyohyo__/post/142d86fe-c400-4013-9aff-e21262e2bffc/image.gif" alt=""></li>
</ul>
<h3 id="수행-과정">수행 과정</h3>
<ul>
<li>정적 언어인 타입스크립트를 최대한 활용하고 재사용성을 높이고자 id와 navTitle을 enum으로 정의한 후 타입 정의하였다.<pre><code class="language-ts">enum CityId {
SEOUL = &quot;서울&quot;,
LONDON = &quot;런던&quot;,
PARIS = &quot;파리&quot;,
NEWYORK = &quot;뉴욕&quot;,
}
</code></pre>
</li>
</ul>
<p>enum NavTitle {
  SEOUL = &quot;Seoul&quot;,
  LONDON = &quot;London&quot;,
  PARIS = &quot;Paris&quot;,
  NEWYORK = &quot;NewYork&quot;,
}</p>
<p>interface TImgState {
  id: CityId;
  src: string;
  navTitle: NavTitle;
}</p>
<pre><code>- 재사용성과 코드의 가독성을 위해 JSX 코드에 일일히 수동으로 이미지를 넣지 않도록 이미지와 이미지 제목을 객체 배열로 만들었다.
```ts
  const ImgInfo: TImgState[] = [
    {
      id: CityId.SEOUL,
      src: seoul,
      navTitle: NavTitle.SEOUL,
    },
    {
      id: CityId.LONDON,
      src: london,
      navTitle: NavTitle.LONDON,
    },
    {
      id: CityId.PARIS,
      src: paris,
      navTitle: NavTitle.PARIS,
    },
    {
      id: CityId.NEWYORK,
      src: newyork,
      navTitle: NavTitle.NEWYORK,
    },
  ];</code></pre><ul>
<li>클릭할 때마다 이미지 사진이 변함으로 이미지를 useState로 정의하였다.<pre><code class="language-ts">const [viewImg, setViewImg] = useState&lt;TImgState&gt;({
  id: CityId.SEOUL,
  src: seoul,
  navTitle: NavTitle.SEOUL,
});</code></pre>
</li>
<li>상단 nav를 이미지 객체 배열을 map 메서드를 이용해 보여주도록 하였고, 이미지의 id와 nav의 id가 일치하면 굵게 표시될 수 있도록 하였다.<pre><code class="language-ts">{ImgInfo.map((item) =&gt; (
            &lt;li key={item.id} onClick={() =&gt; onChangeViewImg(item.id)}&gt;
              {item.id === viewImg.id ? (
                &lt;strong&gt;
                  &lt;a href=&quot;#&quot;&gt;{item.navTitle}&lt;/a&gt;
                &lt;/strong&gt;
              ) : (
                &lt;a href=&quot;#&quot;&gt;{item.navTitle}&lt;/a&gt;
)}</code></pre>
</li>
<li>상단 nav 클릭 시 이미지가 변경되는 함수는 아래와 같다.<ol>
<li>클릭한 nav의 id가 이미지 객체 배열 중 id가 일치하는 이미지 값을 찾아 반환해준다.</li>
<li>그럴 일은 없지만 객체 배열에서 id가 일치하는 값을 못찾았을 경우를 대비하여, if문으로 분기처리를 해준다.<pre><code class="language-ts">const onChangeViewImg = (id: CityId) =&gt; {
const selectedImg = ImgInfo.find((item) =&gt; item.id === id);
if (selectedImg) {
 setViewImg(selectedImg);
}
};</code></pre>
</li>
</ol>
</li>
</ul>
<h3 id="전체-코드">전체 코드</h3>
<pre><code class="language-ts">import travel from &quot;../../assets/images/travel.png&quot;;
import seoul from &quot;../../assets/images/seoul.jpg&quot;;
import london from &quot;../../assets/images/london.jpg&quot;;
import paris from &quot;../../assets/images/paris.jpg&quot;;
import newyork from &quot;../../assets/images/newyork.jpg&quot;;
import { useState } from &quot;react&quot;;

enum CityId {
  SEOUL = &quot;서울&quot;,
  LONDON = &quot;런던&quot;,
  PARIS = &quot;파리&quot;,
  NEWYORK = &quot;뉴욕&quot;,
}

enum NavTitle {
  SEOUL = &quot;Seoul&quot;,
  LONDON = &quot;London&quot;,
  PARIS = &quot;Paris&quot;,
  NEWYORK = &quot;NewYork&quot;,
}

interface TImgState {
  id: CityId;
  src: string;
  navTitle: NavTitle;
}

export default function Travel() {
  const [viewImg, setViewImg] = useState&lt;TImgState&gt;({
    id: CityId.SEOUL,
    src: seoul,
    navTitle: NavTitle.SEOUL,
  });

  const ImgInfo: TImgState[] = [
    {
      id: CityId.SEOUL,
      src: seoul,
      navTitle: NavTitle.SEOUL,
    },
    {
      id: CityId.LONDON,
      src: london,
      navTitle: NavTitle.LONDON,
    },
    {
      id: CityId.PARIS,
      src: paris,
      navTitle: NavTitle.PARIS,
    },
    {
      id: CityId.NEWYORK,
      src: newyork,
      navTitle: NavTitle.NEWYORK,
    },
  ];

  const onChangeViewImg = (id: CityId) =&gt; {
    const selectedImg = ImgInfo.find((item) =&gt; item.id === id);
    if (selectedImg) {
      setViewImg(selectedImg);
    }
  };

  return (
    &lt;&gt;
      // ...생략...
          &lt;ul className=&quot;flex items-center antialiased justify-around w-full&quot;&gt;
            {ImgInfo.map((item) =&gt; (
              &lt;li key={item.id} onClick={() =&gt; onChangeViewImg(item.id)}&gt;
                {item.id === viewImg.id ? (
                  &lt;strong&gt;
                    &lt;a href=&quot;#&quot;&gt;{item.navTitle}&lt;/a&gt;
                  &lt;/strong&gt;
                ) : (
                  &lt;a href=&quot;#&quot;&gt;{item.navTitle}&lt;/a&gt;
                )}
              &lt;/li&gt;
            ))}
          &lt;/ul&gt;
          &lt;div&gt;
            &lt;img src={viewImg.src} alt={viewImg.id} /&gt;
          &lt;/div&gt;
    // ...생략...
    &lt;/&gt;
  );
}
</code></pre>
<h3 id="수행하면서-어려웠거나-궁금한-점">수행하면서 어려웠거나 궁금한 점</h3>
<ol>
<li>아래 클릭 이벤트 함수의 매개변수인 id를 CityId로 타입 정의를 했기 때문에 selectedImg가 undefined가 될 일이 없는데 vsCode에서는 계속 selectedImg가 undefind가 될 수 있다며 if문 없이는 setViewImg(selectedImg)를 못하게 했다<ul>
<li>배열은 코드의 다른 부분에서 변형될 수 있다. 예를 들어, ImgInfo 배열이 동적으로 변경되거나 비워질 가능성이 존재한다면, find 메서드는 undefined를 반환할 수 있기 때문이다. TypeScript는 이런 동적인 상황을 미리 예측할 수 없기 때문에, undefined 처리를 요구한다.<pre><code class="language-ts">const onChangeViewImg = (id: CityId) =&gt; {
const selectedImg = ImgInfo.find((item) =&gt; item.id === id);
if (selectedImg) {
 setViewImg(selectedImg);
}
};</code></pre>
</li>
</ul>
</li>
<li>이미지 src도 enum 으로 만들어서 쓸수는 없을까?<pre><code class="language-ts">import seoul from &quot;../../assets/images/seoul.jpg&quot;;
import london from &quot;../../assets/images/london.jpg&quot;;
import paris from &quot;../../assets/images/paris.jpg&quot;;
import newyork from &quot;../../assets/images/newyork.jpg&quot;;
</code></pre>
</li>
</ol>
<p>// 아래와 같이 정의하면 오류남
enum ImgSrc {
  SEOUL = seoul,
  LONDON = london,
  PARIS = paris,
  NEWYORK = newyork,
}</p>
<pre><code>- enum 대신 객체 리터럴 사용하면 된다. 하지만 코드가 길어지고 복잡해지는 단점이 있는 것 같다.
- 별도의 파일로 관리하거나 수정이 많이 필요한 로직일 때 사용해야겠다.
```ts
import seoul from &quot;../../assets/images/seoul.jpg&quot;;
import london from &quot;../../assets/images/london.jpg&quot;;
import paris from &quot;../../assets/images/paris.jpg&quot;;
import newyork from &quot;../../assets/images/newyork.jpg&quot;;

// 객체 리터럴로 이미지 소스를 정의
const ImgSrc = {
  SEOUL: seoul,
  LONDON: london,
  PARIS: paris,
  NEWYORK: newyork,
} as const;

type ImgSrcType = keyof typeof ImgSrc;

// 사용 예시
function getImageSrc(city: ImgSrcType): string {
  return ImgSrc[city];
}

// 사용
const seoulImgSrc = getImageSrc(&quot;SEOUL&quot;);
</code></pre><p>본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 과정(B-log) 리뷰로 작성 되었습니다.</p>
<p>#유데미 #udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프 #React #리액트프로젝트 #프론트엔드개발자양성과정 #개발자교육과정 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 this 개념 정리]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-this-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-this-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 14 Aug 2024 05:21:31 GMT</pubDate>
            <description><![CDATA[<hr>
<h1 id="this란-무엇인가">this란 무엇인가?</h1>
<hr>
<ul>
<li>자신이 속한 객체 또는 자신이 생성할 인스턴스를 가리키는 자기 참조 변수이다.</li>
<li>this는 &quot;지금 내가 가리키고 있는 것&quot;이다. 어떤 객체인지에 따라 다르게 가리킨다. (this 바인딩)</li>
<li>객체에서의 this 사용 예시<pre><code class="language-js">const dog = {
name: &quot;코코&quot;,
sayName: function() {
  console.log(this.name);
}
};
</code></pre>
</li>
</ul>
<p>dog.sayName(); // 코코</p>
<pre><code>- 생성자 함수에서의 this 사용 예시
```js
function Dog(name) {
  this.name = name;
}

const myDog = new Dog(&quot;초코&quot;);
console.log(myDog.name); // 초코</code></pre><ul>
<li>예를 들어, 집에 두 마리의 강아지가 있을 때 한 마리의 이름은 &quot;코코&quot;이고, 다른 한 마리는 &quot;초코&quot;이다. 각 강아지에게 &quot;이름을 말해봐!&quot;라고 말하면, 코코는 &quot;내 이름은 코코야!&quot;라고 말할 것이고, 초코는 &quot;내 이름은 초코야!&quot;라고 말한다. 이때, &quot;내&quot;라는 말은 this와 비슷하다. 코코가 &quot;내 이름&quot;이라고 말할 때 this는 코코를 가리키고, 초코가 &quot;내 이름&quot;이라고 말할 때 this는 초코를 가리킨다.</li>
</ul>
<hr>
<h1 id="왜-this를-사용하는가">왜 this를 사용하는가?</h1>
<hr>
<ul>
<li>this가 필요한 이유는 <strong>자바스크립트에서 객체 내부의 메서드가 그 객체의 프로퍼티이나 다른 메서드에 쉽게 접근할 수 있도록 하기 위해서</strong>다. 만약 this가 없다면, 자신이 속한 객체를 가리키는 식별자를 재귀적으로 참조하고 매번 객체의 이름을 명시적으로 사용해야 한다.</li>
<li>this를 사용하지 않고 객체 리터럴 방식으로 생성한 객체의 경우 메서드 내부에서 메서드 자신이 속한 객체를 가리키는 식별자를 재귀적으로 참조할 수 있다.<pre><code class="language-js">const circle = {
// 프로퍼티: 객체 고유의 상태 데이터
radius: 5,
// 메서드: 상태 데이터를 참조하고 조작하는 동작
getDiameter() {
  // 이 메서드가 자신이 속한 객체의 프로퍼티나 다른 메서드를 참조하려면
  // 자신이 속한 객체인 circle을 참조할 수 있어야 한다.
  return 2 * circle.radius;
},
};
</code></pre>
</li>
</ul>
<p>console.log(circle.getDiameter()); // 10</p>
<pre><code>- 하지만 이러한 방법은 **객체가 변경되거나, 이름이 바뀌거나, 메서드가 다른 객체에 복사될 때 문제가 생길 수 있기 때문에 코드의 유지보수성과 재사용성을 저하**시킨다.
- 만약 this를 사용하지 않고 객체를 가리키는 식별자를 재귀적으로 참조했을 때 메서드가 다른 객체에 복사될 경우, 아래와 같은 문제가 발생할 수 있다.
```js
const circle = {
  radius: 5,
  getDiameter() {
    return 2 * circle.radius;
  },
};

const sphere = {
  radius: 10,
};

// 메서드를 다른 객체에 복사함
sphere.getDiameter = circle.getDiameter;

// getDiameter 메서드는 circle.radius를 참조하고 있으므로,
// 20(sphere.radius*2)이 아닌 10(circle.radius*2)이 출력된다.
console.log(sphere.getDiameter()); // 10</code></pre><ul>
<li>또한, 객체 리터럴에서는객체 리터럴 방식에서는 객체가 정의되자마자 메모리에 생성되고, 그 객체를 가리키는 식별자가 바로 사용 가능하기 때문에 재귀적으로 참조가 가능하지만 생성자 함수에서는 생성자 함수는 새로운 객체(인스턴스)를 생성하기 위한 함수이기 때문에 생성자 함수가 호출되기 전까지는 그 함수가 생성할 인스턴스는 존재하지 않아 재귀적으로 참조할 수 없어 this가 필요하다.</li>
<li>결론 : <strong>this는 메서드가 속한 객체를 동적으로 참조할 수 있게 해준다.</strong> 즉, 객체가 변경되거나, 메서드가 다른 객체에 복사되더라도, this를 사용하면 항상 현재 메서드가 속한 객체를 참조할 수 있기 때문에, 위에서 언급한 문제들을 자연스럽게 해결할 수 있다.</li>
</ul>
<hr>
<h1 id="this는-어떻게-사용하는가">this는 어떻게 사용하는가?</h1>
<hr>
<ul>
<li><p>함수를 호출하면 arguments 객체와 this가 암묵적으로 함수 내부에 전달된다. 함수 내부에서 arguments 객체를 지역 변수처럼 사용할 수 있는 것처럼 this도 지역 변수처럼 사용할 수 있다.</p>
</li>
<li><p>클래스 기반 언어에서 this는 언제나 클래스가 생성하는 인스턴스를 가리킨다. 하지만 자바스크립트의 this는 함수가 호출되는 방식에 따라 this에 바인딩될 값, 즉 this 바인딩이 동적으로 결정된다. 또한 strict mode(엄격 모드) 역시 this 바인딩에 영향을 준다.  즉 <strong>this 바인딩은 함수 호출 방식에 의해 동적으로 결정</strong>된다.</p>
</li>
<li><p>this는 코드 어디에서든 참조 가능하다. </p>
<h2 id="일반-함수의-this">일반 함수의 this</h2>
</li>
<li><p>일반 함수에서 this는 <strong>전역 객체가 바인딩</strong>된다.</p>
</li>
<li><p>this는 객체의 프로퍼티나 메서드를 참조하기 위한 <strong>자기 참조 변수이므로 객체를 생성하지 않는 일반 함수에서 this는 의미가 없다.</strong></p>
<pre><code class="language-js">function foo() {
console.log(&quot;foo&#39;s this: &quot;, this); // window
function bar() {
  console.log(&quot;bar&#39;s this: &quot;, this); // window
}
bar();
}
foo();</code></pre>
</li>
<li><p>strict mode가 적용된 일반 함수 내부의 this는 undefind가 바인딩된다.</p>
<pre><code class="language-js">function foo() {
&quot;use strict&quot;;

console.log(&quot;foo&#39;s this: &quot;, this); // undefined
function bar() {
  console.log(&quot;bar&#39;s this: &quot;, this); // undefined
}
bar();
}
foo();</code></pre>
</li>
<li><p>전역 함수, 중첩 함수(함수 안에 함수), 콜백함수라도 일반 함수로 호출하면 함수 내부의 this에는 모두 전역 객체가 바인딩된다.</p>
</li>
<li><p>하지만 중첩 함수, 콜백 함수는 외부 함수를 돕는 헬퍼 함수의 역할을 하므로 외부 함수의 일부 로직을 대신하는 경우가 대부분이다. 그렇기 때문에 메서드의 this 바인딩과 일치시키기 위한 방법이 존재한다.</p>
<pre><code class="language-js">var value = 1;
</code></pre>
</li>
</ul>
<p>const obj = {
  value: 100,
  foo() {
    // this 바인딩(obj)을 변수 that에 할당한다.
    const that = this;</p>
<pre><code>// 콜백 함수 내부에서 this 대신 that을 참조한다.
setTimeout(function () {
  console.log(that.value); // 100
}, 100);</code></pre><p>  }
};</p>
<p>obj.foo();</p>
<pre><code>- 위 방법 말고도 자바스크립트는 this를 명시적으로 할당할 수 있는 apply, call, bind 메소드를 제공한다.
## Function.prototype.apply/call/bind 메서드

- apply, call, bind 메서드는 Function.prototype의 메서드다. 즉, 모든 함수가 상속받아 사용할 수 있다.
- apply, call 메서드는 this로 사용할 객체와 인수 리스트를 인수로 전달받아 함수를 호출한다.

```js
function getThisBinding() {
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

console.log(getThisBinding()); // window

// getThisBinding 함수를 호출하면서 인수로 전달한 객체를 getThisBinding 함수의 this에 바인딩한다.
console.log(getThisBinding.apply(thisArg)); // {a:1}
console.log(getThisBinding.call(thisArg)); // {a:1}</code></pre><ul>
<li>apply 메서드는 호출할 함수의 인수를 배열로 묶어 전달한다. call 메서드는 호출할 함수의 인수를 쉼표로 구분한 리스트 형식으로 전달한다. 2개의 메서드는 호출할 함수에 인수를 전달하는 방식만 다를 뿐 this로 사용할 객체를 전달하면서 함수를 호출하는 것은 동일하다.</li>
<li>2개의 메서드의 대표적인 용도는 arguments 객체와 같은 유사 배열 객체에 배열 메서드를 사용하는 경우다.</li>
</ul>
<pre><code class="language-js">function converArgsToArray() {
  console.log(arguments);

  // arguments 객체를 배열로 반환
  // Array.prototype.slice를 인수 없이 호출하면 배열의 복사본을 생성한다.
  const arr = Array.prototype.slice.call(arguments);
  // const arr = Array.prototype.slice.apply(arguments);
  console.log(arr);

  return arr;
}

convertArgsToArray(1, 2, 3); // [1, 2, 3];</code></pre>
<ul>
<li>bind 메서드는 apply와 call 메서드와 달리 함수를 호출하지 않는다. 다만 첫 번째 인수로 전달한 값으로 this 바인딩이 교체된 함수를 새롭게 생성해 반환한다.</li>
</ul>
<pre><code class="language-js">function getThisBinding() {
  return this;
}

// this로 사용할 객체
const thisArg = { a: 1 };

// bind 메서드는 첫 번째 인수로 전달한 thisArg로 this 바인딩이 교체된
// getThisBinding 함수를 새롭게 생성해 반환한다.
console.log(getThisBinding.bind(thisArg)); // getThisBinding
// bind 메서드는 함수를 호출하지는 않으므로 명시적으로 호출해야 한다.
console.log(getThisBinding.bind(thisArg)); // {a:1}</code></pre>
<ul>
<li>bind 메서드는 메서드의 this와 메서드 내부의 중첩 함수 또는 콜백 함수의 this가 불일치하는 문제를 해결하기 위해 유용하게 사용된다.</li>
</ul>
<pre><code class="language-js">const person = {
    name: &#39;Lee&#39;;
    foo(callback) {
        // bind 메서드로 callback 함수 내부의 this 바인딩을 전달
        setTimeout(callback.bind(this), 100);
        // 아래와 같이 호출하면 아래 person.foo의 console.log는 window.name이 출력된다.
        // setTimeout(callback, 100);
    }
};

person.foo(funciton () {
    console.log(`Hi my name is ${this.name}`); // Hi my name is Lee
})</code></pre>
<h2 id="메서드의-this">메서드의 this</h2>
<ul>
<li>메서드 내부의 this는 메서드를 소유한 객체가 아닌 <strong>메서드를 호출한 객체에 바인딩</strong>된다.</li>
</ul>
<pre><code class="language-js">const person = {
  name: &quot;Lee&quot;,
  getName() {
    // 메서드 내부의 this는 메서드를 호출한 객체에 바인딩된다.
    return this.name;
  },
};

// 메서드 getName을 호출한 객체는 person이다.
console.log(person.getName()); // Lee</code></pre>
<ul>
<li>person 객체의 getName 프로퍼티가 가리키는 함수 객체는 person 객체에 포함된 것이 아니라 독립적으로 존재하는 별도의 객체다. getName 프로퍼티가 함수 객체를 가리키고 있을 뿐이다.</li>
<li>따라서 getName 프로퍼티가 가리키는 함수 객체, 즉 getName 메서드는 다른 객체의 프로퍼티에 할당하는 것으로 다른 객체의 메서드가 될 수도 있고 일반 변수에 할당하여 일반 함수로 호출될 수도 있다.</li>
</ul>
<pre><code class="language-js">const anotherPerson = {
  name: &quot;Kim&quot;,
};

// getName 메서드를 anotherPerson 객체의 메서드로 할당
anotherPerson.getName = person.getName;

// getName 메서드를 호출한 객체는 anotherPerson이다.
console.log(anotherPerson.getName()); // Kim

// getName 메서드를 변수에 할당
const getName = person.getName;

// getName 메서드를 일반 함수로 호출
console.log(getName()); // &#39;&#39;
// 일반 함수로 호출된 getName 함수 내부의 this.name은 브라우저 환경에서 window.name과 같다.
// 브라우저 환경에서 window.name은 브라우저 창의 이름을 나타내는 빌트인 프로퍼티이며 기본값은 &#39;&#39;이다.
// Node.js 환경에서 this.name은 undefined이다.</code></pre>
<ul>
<li>프로토타입 메서드 내부에서 사용된 this도 일반 메서드와 마찬가지로 해당 메서드를 호출한 객체에 바인딩된다.</li>
</ul>
<pre><code class="language-js">function Person(name) {
  this.name = name;
}

Person.prototype.getName = function () {
  return this.name;
};

const me = new Person(&quot;Lee&quot;);

// getName 메서드를 호출한 객체는 me다.
console.log(me.getName()); // Lee

Person.prototype.name = &quot;Kim&quot;;

// getName 메서드를 호출한 객체를 Person.prototype이다.
console.log(Person.prototype.getName()); // Kim</code></pre>
<h2 id="생성자-함수의-this">생성자 함수의 this</h2>
<ul>
<li>생성자 함수 내부의 this에는 <strong>생성자 함수가 (미래에) 생성할 인스턴스가 바인딩</strong>된다.</li>
</ul>
<pre><code class="language-js">// 생성자 함수
function Circle(radius) {
  // 생성자 함수 내부의 this는 생성자 함수가 생성할 인스턴스를 가리킨다.
  this.radius = raduis;
  this.getDiameter = function () {
    return 2 * this.radius;
  };
}

// 반지름이 5인 Circle 객체를 생성
const circle1 = new Circle(5);
// 반지름이 10인 Circle 객체를 생성
const circle2 = new Circle(10);

console.log(circle1.getDiameter()); // 10
console.log(circle2.getDiameter()); // 20</code></pre>
<ul>
<li>출처 : (책)모던자바스크립트 딥다이브</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[자바스크립트 프로토타입 개념 정리/퀴즈]]></title>
            <link>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC%ED%80%B4%EC%A6%88</link>
            <guid>https://velog.io/@hyohyo__/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%ED%94%84%EB%A1%9C%ED%86%A0%ED%83%80%EC%9E%85-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC%ED%80%B4%EC%A6%88</guid>
            <pubDate>Sat, 10 Aug 2024 09:14:47 GMT</pubDate>
            <description><![CDATA[<h2 id="프로토타입의-이해">프로토타입의 이해</h2>
<h3 id="what-프로토타입은-무엇인가">(What) 프로토타입은 무엇인가?</h3>
<ul>
<li>자바스크립트의 모든 객체는 자신의 부모 역할을 하는 객체와 연결되어있고 이 부모 객체를 프로토타입이라고 한다.</li>
<li>객체지향 프로그래밍의 근간을 이루는 객체 간 상속을 구현하기 위해 사용된다. 프로토타입은 어떤 객체의 상위(부모) 객체의 역할을 하는 다른 객체에 공유 프로퍼티를 제공한다. </li>
<li>프로토타입을 상속받은 하위(자식) 객체는 상위 객체의 프로퍼티를 자신의 프로퍼티처럼 자유롭게 사용할 수 있다.</li>
</ul>
<h3 id="who-누가-프로토타입을-사용하는가">(Who) 누가 프로토타입을 사용하는가?</h3>
<ul>
<li>개발자: 객체 지향 프로그래밍(OOP) 개념을 자바스크립트에서 활용하는 개발자들이 프로토타입을 자주 사용한다.<h3 id="when-언제-프로토타입을-사용하는가">(When) 언제 프로토타입을 사용하는가?</h3>
</li>
<li>반복되는 기능이 필요할 때: 프로토타입을 사용하는 가장 흔한 상황은 여러 객체에 동일한 메서드나 속성이 필요할 때이다. 예를 들어, 게임에서 여러 캐릭터가 서로 다른 특성을 가지고 있지만, 모두 &quot;이동&quot;이라는 공통된 동작을 해야 할 때, 이 &quot;이동&quot; 동작을 프로토타입에 정의할 수 있다.</li>
<li>메모리 효율성을 고려할 때: 다수의 객체가 동일한 메서드를 가지게 될 때 메모리 사용을 최적화하고자 할 때도 프로토타입을 사용한다.<h3 id="where-어디서-프로토타입을-사용하는가">(Where) 어디서 프로토타입을 사용하는가?</h3>
</li>
<li>객체를 생성하는 모든 곳에서: 프로토타입은 객체를 생성하는 모든 곳에서 사용된다. 특히 라이브러리나 프레임워크, 또는 복잡한 애플리케이션에서 자주 사용된다.<h3 id="why-왜-프로토타입을-사용하는가">(Why) 왜 프로토타입을 사용하는가?</h3>
</li>
</ul>
<ol>
<li>메모리 효율성<ul>
<li>생성자 함수를 이용해 객체를 만들면, 그 함수 안에 있는 모든 메서드(기능)가 새롭게 만들어지는 객체마다 복사된다. 즉, 같은 기능을 가진 메서드가 여러 객체에 반복적으로 만들어지게 된다. 이것은 메모리를 비효율적으로 사용하게 된다.</li>
<li>프로토타입을 사용하면 같은 기능을 여러 객체에서 공유할 수 있기 때문에, 메모리를 절약할 수 있다. 하나의 메서드를 여러 객체가 공통으로 사용하게 되어 메모리 낭비가 줄어든다.</li>
</ul>
</li>
<li>일관성<ul>
<li>프로토타입에 정의된 메서드를 수정하면, 그 메서드를 사용하는 모든 객체에 즉시 반영된다. 이것은 코드의 일관성을 유지하는 데 도움이 된다.</li>
</ul>
</li>
<li>유지보수의 용이성<ul>
<li>프로토타입을 사용하면, 하나의 메서드만 수정하거나 확장하면 되기 때문에, 코드 유지보수가 훨씬 간편하다. 여러 곳에 똑같은 메서드를 수정할 필요가 없다.<h3 id="how-어떻게-프로토타입을-사용하는가">(How) 어떻게 프로토타입을 사용하는가?</h3>
</li>
</ul>
</li>
</ol>
<ul>
<li>프로토타입에 메서드 추가하는 방법 : 프로토타입에 메서드를 추가하여 모든 인스턴스에서 공통적으로 사용할 수 있게 한다. 예를 들어, Array.prototype에 사용자 정의 메서드를 추가하면, 모든 배열에서 해당 메서드를 사용할 수 있다.<pre><code class="language-js">function Animal(name, sound) {
  this.name = name;
  this.sound = sound;
}
</code></pre>
</li>
</ul>
<p>// 프로토타입에 메서드 추가
Animal.prototype.makeSound = function() {
    console.log(this.name + &quot; says &quot; + this.sound);
};</p>
<p>var dog = new Animal(&quot;Dog&quot;, &quot;Woof&quot;);
var cat = new Animal(&quot;Cat&quot;, &quot;Meow&quot;);</p>
<p>dog.makeSound(); // &quot;Dog says Woof&quot;
cat.makeSound(); // &quot;Cat says Meow&quot;</p>
<pre><code>- 프로토타입 취급 및 할당 : Object.getPrototypeOf와 Object.setPrototypeOf 메서드를 사용한다. \_\_proto\_\_로 객체가 상속받는 프로토타입 객체를 참조할 수 있지만, 모든 객체가 \_\_proto\_\_ 접근자 프로퍼티를 사용할 수 있는 것은 아니기 때문에 사용하지 않는 게 좋다.
```js
// obj는 프로토타입 체인의 종점이다. 따라서 Object.__proto__를 상속받을 수 없다.
const obj = Object.create(null);

// obj는 Object.__proto__를 상속받을 수 없다.
console.log(obj.__protp__); // undefined

// 따라서 __proto__보다 Object.getPrototypeOf 메서드를 사용하는 편이 좋다.
console.log(Object.getPrototypeOf(obj)); // null</code></pre><ul>
<li>프로토타입 체인: 자바스크립트의 프로토타입 체인을 이용하여 상속을 구현한다. 객체가 특정 속성이나 메서드를 찾지 못하면, 그 객체의 프로토타입 체인을 따라가면서 최상위 부모 객체까지 속성이나 메서드를 검색한다.</li>
</ul>
<hr>
<h2 id="프로토타입-퀴즈">프로토타입 퀴즈</h2>
<hr>
<h4 id="아래-코드의-obj2의-프로토타입은-무엇인가요">아래 코드의 obj2의 프로토타입은 무엇인가요?</h4>
<pre><code class="language-js">var obj = {
  x: 10,
  y: 20,
};
var obj2 = Object.create(obj);</code></pre>
<details><summary>정답</summary>
  <li><b>obj</b></li>
  <li>Object.create(obj)객체를 생성하고 obj의 프로토타입으로 정의한다. 따라서 obj2의 프로토타입은 obj 다.</li>
</details>

<h4 id="아래-코드는-console에-어떤-로그를-남길까요">아래 코드는 console에 어떤 로그를 남길까요?</h4>
<pre><code class="language-js">var obj = { x: 10, y: 20 };
var obj2 = { x: 20 };
var obj3 = Object.create(obj2);
console.log(obj3.y);</code></pre>
<details><summary>정답</summary>
  <li><b>undefined</b></li>
  <li>obj3의 프로토타입은 obj2이기 때문에 y값이 정의되지 않아 undefined가 출력된다.</li>
</details>

<h4 id="아래-코드의-출력-내용은-무엇인가요">아래 코드의 출력 내용은 무엇인가요?</h4>
<pre><code class="language-js">const obj = {
  greet() {
    console.log(&quot;Hello&quot;);
  }
};

console.log(obj.greet.prototype);</code></pre>
<details><summary>정답</summary>
  <li><b>undefined</b></li>
  <li>ES6 메서드 축약 표현으로 정의된 함수는 non-constructor이므로 prototype을 가지지 않는다.</li>
</details>

<h4 id="아래-코드의-출력-내용은-무엇인가요-1">아래 코드의 출력 내용은 무엇인가요?</h4>
<pre><code class="language-js">const obj = {
    a: 1,
};

Object.prototype.b = 2;

console.log(&quot;a&quot; in obj);
console.log(&quot;b&quot; in obj);
console.log(obj.hasOwnProperty(&quot;a&quot;));
console.log(obj.hasOwnProperty(&quot;b&quot;));</code></pre>
<details><summary>정답</summary>
  <li><b>true true true false</b></li>
  <li>in 연산자는 확인 대상 객체의 프로퍼티 뿐만 아니라 확인 대상 객체가 상속받은 모든 프로토타입의 프로퍼티를 확인하기 때문에 주의해야 한다.</li>
  <li>Object.prototype.hasOwnProperty 메서드는 객체의 고유의 프로퍼티 키인 경우에는 true를 반환하기 때문에 in 연산자보다 안전하다</li>
</details>

<h4 id="아래-코드의-문제점은-무엇인가요">아래 코드의 문제점은 무엇인가요?</h4>
<pre><code class="language-js">function Item(name, price) {
  this.name = name;
  this.price = price;
}
Item.__proto__.displayInfo = function () {
  console.log(this.name + &quot; is for $ &quot; + this.price);
};
var cake = new Item(&quot;Chocolate Cake&quot;, 10);
cake.displayInfo();</code></pre>
<details><summary>정답</summary>
  <li>__proto__은 객체가 상속받은 프로토타입 속성을 가리키기 때문에 Item에는 displayInfo이 정의되어 있지 않아 오류가 발생한다.</li>
  <li> cake에 들어간 값은 { name: 'Chocolate Cake', price: 10 } 이다.
  <li>생성된 객체들이 공통으로 사용하는 메서드나 속성을 정의할 때는 __proto__이 아닌 prototype을 사용해야 한다.</li>
</details>

]]></description>
        </item>
        <item>
            <title><![CDATA[SPA 프레임워크/라이브러리 비교 분석]]></title>
            <link>https://velog.io/@hyohyo__/SPA-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90-%EB%B6%84%EC%84%9D</link>
            <guid>https://velog.io/@hyohyo__/SPA-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC-%EB%B9%84%EA%B5%90-%EB%B6%84%EC%84%9D</guid>
            <pubDate>Sat, 20 Jul 2024 07:28:35 GMT</pubDate>
            <description><![CDATA[<p>SPA 프레임워크 또는 라이브러리에 대해서 알아보고 차이점을 분석하여, 이후 프로젝트를 진행할 때 참고하고자 정리해본다.</p>
<h3 id="요즘-어떤-게-제일-인기가-많은가">요즘 어떤 게 제일 인기가 많은가?</h3>
<ul>
<li>리액트가 가장 압도적으로 사용률이 많음</li>
<li>왜 리액트가 가장 인기가 많은걸까?
<img src="https://velog.velcdn.com/images/hyohyo__/post/426aafc7-920b-456c-a031-2e2b12a09e75/image.png" alt=""></li>
</ul>
<hr>
<h1 id="angular">Angular</h1>
<hr>
<p>구글에서 지원하는 TypeScript 기반의 프레임워크 (2010년 출시)</p>
<h3 id="특징-공식-문서">특징 (<a href="https://www.angular.kr/guide/what-is-angular">공식 문서</a>)</h3>
<ul>
<li>확장 가능한 컴포넌트 구조로 웹 애플리케이션을 만드는 프레임워크입니다</li>
<li>라우팅, 폼 관리, 클라이언트-서버 통신 등 웹 개발에 필요한 라이브러리를 조화롭게 통합한 모음집입니다</li>
<li>애플리케이션 개발, 빌드, 테스트, 수정에 필요한 개발자 도구를 제공합니다</li>
</ul>
<h3 id="장점">장점</h3>
<ul>
<li>타입스크립트 기반이기 때문에 <strong>정적 타이핑</strong>으로 컴파일 단계에서 에러를 예방할 수 있음</li>
<li>구글이 지원하기 때문에 <strong>공식 문서와 커뮤니티</strong>가 잘되어 있음 </li>
<li>웹과 모바일 프로젝트의 생성부터 테스팅, 빌드, 배포까지 <strong>다양한 기능을 제공함</strong></li>
</ul>
<h3 id="단점">단점</h3>
<ul>
<li>다양한 기능 제공과 프레임워크의 특성 상 <strong>러닝커브가 높음</strong></li>
<li>다른 프레임워크보다 <strong>초기로딩 속도가 느림</strong></li>
<li>검색 엔진이 웹 페이지를 크롤링 할 때 JavaScript를 실행하지 않고 있는 그대로 긁어가는 문제가 발생해서 <strong>검색엔진 최적화 (SEO)가 어려움</strong></li>
</ul>
<h3 id="어떤-프로젝트에-사용하면-좋은가">어떤 프로젝트에 사용하면 좋은가?</h3>
<ul>
<li>장기적으로 운영될 프로젝트</li>
<li>규모가 크고 다양한 기능을 제공하는 프로젝트</li>
<li>웹 뿐만 아니라 앱을 동시에 개발해야 되는 프로젝트</li>
</ul>
<hr>
<h1 id="svelte">Svelte</h1>
<hr>
<p>2016년 출시한 자바스크립트 프레임워크</p>
<h3 id="특징공식문서">특징(<a href="https://svelte.dev/">공식문서</a>)</h3>
<ul>
<li>적은 양의 코드를 사용함</li>
<li>가상 돔이 아닌 실제 돔에 반영함 (앱을 실행 시점에서 해석하지 않고 빌드 시점에서 Vanilla JavaScript Bundle로 컴파일)</li>
<li>별도의 상태 관리 라이브러리 없이 변수나 표현식이 변경될 때마다 자동으로 업데이트되는 코드를 작성할 수 있음</li>
</ul>
<h3 id="장점-1">장점</h3>
<ul>
<li>적은 양의 코드를 사용하기 때문에 <strong>가독성</strong>이 좋고 <strong>용량이 간소화</strong>됨</li>
<li>상태 변경 시 컴파일 시점에서 반응형 업데이트를 처리하기 때문에 <strong>상태 관리가 편함</strong></li>
<li>Vanilla JavaScript Bundle로 컴파일하기 때문에 <strong>성능이 좋음</strong> (빠름)</li>
</ul>
<h3 id="단점-1">단점</h3>
<ul>
<li><strong>빌드 속도가 느림</strong></li>
<li>비교적 <strong>커뮤니티가 작고</strong> 공식문서는 <strong>한글화</strong>가 되어 있지 않음</li>
<li>비교적 <strong>라이브러리와 같은 도구가 적음</strong></li>
<li>프레임워크에 대한 변화가 잦아 <strong>최신 정보를 따라가기 어려울 수 있음</strong></li>
</ul>
<h3 id="어떤-프로젝트에-사용하면-좋은가-1">어떤 프로젝트에 사용하면 좋은가?</h3>
<ul>
<li>성능이 중요한 작은 프로젝트에 적합</li>
</ul>
<hr>
<h1 id="vuejs">Vue.js</h1>
<hr>
<p>앵귤러와 React의 장점을 살리고 단점을 보완하여 만든 자바스크립트 프레임워크 (2014년 출시)</p>
<h3 id="특징-공식-문서-1">특징 (<a href="https://v3-docs.vuejs-korea.org/">공식 문서</a>)</h3>
<ul>
<li>HTML,CSS, 자바스크립트 표준을 기반으로, 쓰기 편한 API와 최고 수준의 문서를 제공합니다.</li>
<li>제대로된 반응성, 컴파일러 수준에서 최적화된 렌더링 시스템은 대부분의 경우 개발자가 직접 최적화를 수행할 필요가 없습니다.</li>
<li>라이브러리에서 부터 모든 기능을 갖춘 프레임워크 까지 확장되는 풍부하고 점진적으로 채택 가능한 에코시스템을 제공합니다.<h3 id="장점-2">장점</h3>
</li>
<li>공식 문서가 잘되어 있고 직관적인 API로 <strong>러닝커브가 낮음</strong></li>
<li>가상 DOM을 사용하여 DOM 조작을 최소화하여 <strong>성능을 향상</strong></li>
<li><strong>자체 라이브러리가 지속적으로 개발</strong>되고 있어 별도로 설치하지 않아도 되어 편리함(라우터, 상태관리 등)</li>
</ul>
<h3 id="단점-2">단점</h3>
<ul>
<li>앵귤러와 리액트에 비해서는 <strong>작은 생태계</strong></li>
<li>자바스크립트 번들 크기가 커서 기능이 많을수록 <strong>초기 로딩 속도가 느려짐</strong></li>
</ul>
<h3 id="어떤-프로젝트에-사용하면-좋은가-2">어떤 프로젝트에 사용하면 좋은가?</h3>
<ul>
<li>중소규모 프로젝트</li>
<li>빠른 개발이 필요한 프로젝트</li>
</ul>
<hr>
<h1 id="react">React</h1>
<hr>
<p>2013년 페이스북에 의해 출시된 자바스크립트 라이브러리</p>
<h3 id="특징공식-문서">특징(<a href="https://react.dev/">공식 문서</a>)</h3>
<ul>
<li>손쉬운 컴포넌트 기반의 개발이 가능함</li>
<li>풍부한 라이브러리가 있음</li>
<li>JSX를 사용하여 HTML과 JavaScript를 결합</li>
<li>단방향 데이터 흐름<h3 id="장점-3">장점</h3>
</li>
<li>손쉽게 컴포넌트를 만들 수 있기 때문에 프로젝트의 <strong>일관성과 확장성</strong>이 좋음</li>
<li>공식 문서가 잘 되어 있고, <strong>커뮤니티가 거대함</strong></li>
<li>많은 라이브러리가 있고 <strong>기능을 선택적으로 추가</strong>할 수 있음</li>
<li>가상 DOM을 사용하여 DOM 조작을 최소화하여 <strong>성능을 향상</strong></li>
<li>단방향 데이터 흐름으로 데이터의 흐름을 예측 가능하게 하고, <strong>디버깅에 용이</strong>함</li>
</ul>
<h3 id="단점-3">단점</h3>
<ul>
<li>프레임워크가 아니기 때문에 라우팅, 상태 관리 등은 <strong>별도의 라이브러리를 사용</strong>해야 함</li>
<li>JSX 문법을 공부해야 되기 때문에 <strong>러닝커브가 조금 높음</strong></li>
</ul>
<h3 id="어떤-프로젝트에-사용하면-좋은가-3">어떤 프로젝트에 사용하면 좋은가?</h3>
<ul>
<li>고성능 SPA (Single Page Application)</li>
<li>재사용 가능한 UI 컴포넌트가 많은 애플리케이션</li>
</ul>
<hr>
<h2 id="왜-리액트가-인기가-많은가">왜 리액트가 인기가 많은가?</h2>
<ul>
<li>비교적 다른 프레임워크보다는 유연성과 재사용성이 높기 때문인 것 같다</li>
<li>프론트엔드 개발의 역사가 길어지면서 유지보수성의 중요성을 깨달았고, 재사용성이 높은 리액트를 많이 사용하는 것 같다</li>
<li>러닝커브도 앵귤러만큼 어렵지 않고, 뷰보다 생태계가 넓고, 페이스북이라는 대기업의 망하지 않을 것 같은 안정감이 있어서 가장 인기가 많은 것 같다</li>
</ul>
<h3 id="참고">참고</h3>
<ul>
<li><a href="https://www.angular.kr/">https://www.angular.kr/</a></li>
<li><a href="https://www.elancer.co.kr/blog/view?seq=217">https://www.elancer.co.kr/blog/view?seq=217</a></li>
<li><a href="https://www.samsungsds.com/kr/insights/svelte.html">https://www.samsungsds.com/kr/insights/svelte.html</a></li>
<li><a href="https://namu.wiki/w/Svelte">https://namu.wiki/w/Svelte</a></li>
<li><a href="https://www.elancer.co.kr/blog/view?seq=171">https://www.elancer.co.kr/blog/view?seq=171</a></li>
<li><a href="https://pso62.tistory.com/entry/Vuejs%EB%9E%80-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EB%8B%A8%EC%A0%90-%EC%A0%84%EB%A7%9D">https://pso62.tistory.com/entry/Vuejs%EB%9E%80-%EC%9E%A5%EC%A0%90%EA%B3%BC-%EB%8B%A8%EC%A0%90-%EC%A0%84%EB%A7%9D</a></li>
<li><a href="https://modulabs.co.kr/blog/react-library/">https://modulabs.co.kr/blog/react-library/</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[React+TypeScript] 이미지 경로를 찾지 못할 때 오류 해결 방법]]></title>
            <link>https://velog.io/@hyohyo__/ReactTypeScript-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EA%B2%BD%EB%A1%9C%EB%A5%BC-%EC%B0%BE%EC%A7%80-%EB%AA%BB%ED%95%A0-%EB%95%8C-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@hyohyo__/ReactTypeScript-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EA%B2%BD%EB%A1%9C%EB%A5%BC-%EC%B0%BE%EC%A7%80-%EB%AA%BB%ED%95%A0-%EB%95%8C-%EC%98%A4%EB%A5%98-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Wed, 24 Apr 2024 06:05:15 GMT</pubDate>
            <description><![CDATA[<p>React+TypeScript 환경에서 개발 중 반복되는 svg 이미지가 있어 컴포넌트화하여 사용하고 싶다.</p>
<p>컴포넌트화하여 사용하고자 하는데 올바른 이미지 경로로 작성했지만 아래와 같이 오류가 발생한다. </p>
<p><img src="https://velog.velcdn.com/images/hyohyo__/post/6cd36947-241f-4437-96cb-04086575c1b5/image.png" alt=""></p>
<h2 id="원인">원인</h2>
<p>확장자를 타입스크립트가 받아드리지 못했음 </p>
<h2 id="해결방법">해결방법</h2>
<ul>
<li>프로젝트 최상위 경로에 @types/global/index.d.ts 파일 생성</li>
<li>파일 내용</li>
</ul>
<pre><code class="language-ts">declare module &#39;*.svg&#39;;</code></pre>
<ul>
<li>tsconfig.json 의 compilerOptions 안에 내용 추가<pre><code class="language-ts">{
  &quot;compilerOptions&quot;: {
  ...
      &quot;typeRoots&quot;: [&quot;./node_modules/@types&quot;, &quot;@types&quot;],
  }
}</code></pre>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[github] 내 작업 브랜치 가져와서 작업 후 공동브랜치(main)에 병합하기]]></title>
            <link>https://velog.io/@hyohyo__/github-%EB%82%B4-%EC%9E%91%EC%97%85-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EA%B0%80%EC%A0%B8%EC%99%80%EC%84%9C-%EC%9E%91%EC%97%85-%ED%9B%84-%EA%B3%B5%EB%8F%99%EB%B8%8C%EB%9E%9C%EC%B9%98main%EC%97%90-%EB%B3%91%ED%95%A9%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyohyo__/github-%EB%82%B4-%EC%9E%91%EC%97%85-%EB%B8%8C%EB%9E%9C%EC%B9%98-%EA%B0%80%EC%A0%B8%EC%99%80%EC%84%9C-%EC%9E%91%EC%97%85-%ED%9B%84-%EA%B3%B5%EB%8F%99%EB%B8%8C%EB%9E%9C%EC%B9%98main%EC%97%90-%EB%B3%91%ED%95%A9%ED%95%98%EA%B8%B0</guid>
            <pubDate>Tue, 16 Apr 2024 07:51:49 GMT</pubDate>
            <description><![CDATA[<p>github로 협업 시 내 작업 브랜치에 작업 후 공동브랜치(main)에 병합 후 업로드하는 과정을 정리하였다. 꼭 처음에 --single-branch로 가져 오지 않아도 되지만 난 잘 몰라서 내 작업 브랜치만 가져와서 main 브랜치를 별도로 추가하였다. </p>
<p>꼭 중간중간에 git status로 현재 브랜치의 상태를 확인하면서 명령을 실행해야 오류가 나지 않는다.</p>
<ol>
<li><p>내 작업 브랜치 가져오기</p>
<pre><code>git clone -b [브랜치명] --single-branch [깃허브 주소]</code></pre></li>
<li><p>작업 후 내 브랜치와 변경된 내용 확인</p>
<pre><code>git status 
</code></pre></li>
</ol>
<pre><code>
3. 특정 파일만 현재 브랜치에 올리기
  (git status로 중간중간에 상태 확인하면서 진행하기!)</code></pre><p>git add [경로/파일명] [경로/파일명]
git commit -m [커밋 메세지]
git push [리모트 저장소] [브랜치명]</p>
<pre><code>
4. main 브랜치 내 로컬에 가져오기</code></pre><p>git  remote  set-branches  --add  [&#39;리모트 저장소&#39;]  [&#39;main&#39;]
git  fetch  [&#39;리모트 저장소&#39;]
git  checkout  --track  [&#39;리모트 저장소/main&#39;]</p>
<pre><code>
5. 내 브랜치에 있던 작업 내용 main 브랜치에 병합하기</code></pre><p>git status =&gt; (main 브랜치인지 확인)
git merge [내 브랜치]</p>
<pre><code>
6. main 브랜치 내용 원격저장소에 올리기</code></pre><p>git status =&gt; (main 브랜치에 작업 내용 commit 되었는지 확인)
git push [리모트저장소] main</p>
<pre><code>
[참고 자료]
  * https://velog.io/@burningjeong/fatal-originmain-is-not-a-commit-and-a-branch-main-cannot-be-created-from-it</code></pre>]]></description>
        </item>
    </channel>
</rss>