diff --git "a/week-02/\352\271\200\353\257\274\355\230\201.md" "b/week-02/\352\271\200\353\257\274\355\230\201.md" new file mode 100644 index 0000000..c059a65 --- /dev/null +++ "b/week-02/\352\271\200\353\257\274\355\230\201.md" @@ -0,0 +1,526 @@ +# JSX + +## JSX란? + +JSX(JavaScript XML)는 자바스크립트 안에서 HTML과 유사한 마크업을 작성할 수 있게 해주는 자바스크립트 확장 문법이다. + +- JSX는 브라우저가 직접 이해하는 문법이 아니므로 **트랜스파일러를 거쳐 자바스크립트 코드로 변환**되어야 한다. +- JSX의 목적은 트리 구조의 UI 표현을 자바스크립트 코드로 변환하는 데 있다. +- React에서는 JSX를 통해 컴포넌트의 UI 구조를 선언적으로 작성할 수 있다. + +--- + +## JSX 구성 요소 + +### JSXElement + +`JSXElement`는 JSX를 구성하는 가장 기본적인 요소이다. + +- `JSXOpeningElement` + - 요소의 시작 태그 + - 예: `
` + +- `JSXClosingElement` + - 요소의 종료 태그 + - 예: `
` + +- `JSXSelfClosingElement` + - 스스로 시작과 종료를 모두 처리하는 태그 + - 예: `
` + +- `JSXFragment` + - 별도의 DOM 요소 없이 여러 요소를 감싸는 문법 + - 예: `<>` + - 단, 닫는 태그만 단독으로 사용하는 `` 형태는 불가능하다. + +### 컴포넌트명은 왜 대문자로 시작해야 할까? + +React에서 사용자 정의 컴포넌트는 **대문자로 시작해야 한다.** +소문자로 시작하면 React는 해당 태그를 HTML 태그로 인식한다. + +```tsx +function Hello() { + return

Hello

; +} + +export function App() { + return ; +} +``` + +잘못된 예시는 다음과 같다. + +```tsx +function hello() { + return

Hello

; +} + +export function App() { + // 소문자로 시작하므로 HTML 태그로 인식된다. + return ; +} +``` + +--- + +### JSXElementName + +`JSXElementName`은 JSX 요소 이름으로 사용할 수 있는 값을 의미한다. + +- `JSXIdentifier` + - JSX 내부에서 사용할 수 있는 식별자 + - `_`, `$` 외의 특수문자로 시작할 수 없다. + - 숫자로 시작할 수 없다. + +- `JSXNamespacedName` + - `JSXIdentifier:JSXIdentifier` 형태 + - 두 개 이상의 조합은 불가능하다. + +- `JSXMemberExpression` + - `JSXIdentifier.JSXIdentifier` 형태 + - 두 개 이상 연결할 수 있다. + +```tsx + + + +``` + +--- + +### JSXAttributes + +`JSXAttributes`는 JSX 요소에 전달하는 속성을 의미한다. +필수값은 아니며, 속성이 없어도 JSX는 정상적으로 동작한다. + +- `JSXAttribute` + - 일반적인 속성 선언 방식 + - 예: `` + +- `JSXSpreadAttributes` + - 자바스크립트의 전개 연산자를 사용해 속성을 전달하는 방식 + - 예: `` + +```tsx +const props = { + required: true, + disabled: false, +}; + +const ComponentA = ; +``` + +--- + +### JSXChildren + +`JSXChildren`은 JSX 요소 사이에 들어가는 자식 값을 의미한다. + +```tsx +function App() { + return
Hello React
; +} +``` + +JSX 안에서는 표현식도 사용할 수 있다. + +```tsx +function App() { + return <>{(() => 'foo')()}; +} +``` + +--- + +### JSXStrings + +`JSXStrings`는 JSX 요소 내부에서 사용되는 문자열을 의미한다. +HTML에서 사용할 수 있는 문자열은 대부분 JSX에서도 사용할 수 있다. + +다만 자바스크립트 문자열에서는 `\` 같은 특수문자를 사용할 때 이스케이프 처리가 필요하다. + +```html + + +``` + +```ts +// 잘못된 예시 +const escape1 = "\"; + +// 올바른 예시 +const escape2 = "\\"; +``` + +--- + +## JSX와 React.createElement + +JSX는 최종적으로 `React.createElement` 형태의 자바스크립트 코드로 변환된다. + +따라서 JSX에서 반복되는 구조가 있다면, `createElement`를 활용해 중복을 줄일 수 있다. + +### 중복이 있는 코드 + +```tsx +function TextOrHeading({ + isHeading, + children, +}: PropsWithChildren<{ isHeading: boolean }>) { + return isHeading ? ( +

{children}

+ ) : ( + {children} + ); +} +``` + +위 코드는 `className`과 `children` 구조가 반복된다. +태그만 다르고 나머지 구조가 같기 때문에, 속성이 늘어날수록 중복 관리가 어려워진다. + +### 중복을 줄인 코드 + +```tsx +import { createElement, PropsWithChildren } from 'react'; + +function TextOrHeading({ + isHeading, + children, +}: PropsWithChildren<{ isHeading: boolean }>) { + return createElement( + isHeading ? 'h1' : 'span', + { className: 'text' }, + children, + ); +} +``` + +리팩토링한 코드는 바뀌는 부분인 태그 이름만 조건으로 처리하고, 공통 속성은 한 번만 작성한다. +JSX가 결국 `createElement(type, props, children)` 형태로 변환된다는 점을 활용한 방식이다. + +--- + +# 가상 DOM과 리액트 파이버 + +## DOM과 브라우저 렌더링 과정 + +**DOM**은 웹 페이지의 구조를 표현하는 객체 모델이다. +브라우저는 DOM을 통해 웹 페이지의 콘텐츠와 구조를 해석하고 화면에 표시한다. + +## 브라우저 렌더링 과정 + +1. 브라우저가 HTML 파일을 다운로드한다. +2. HTML을 파싱해 DOM 트리를 생성한다. +3. CSS 파일을 만나면 CSS 파일을 다운로드한다. +4. CSS를 파싱해 CSSOM 트리를 생성한다. +5. DOM과 CSSOM을 바탕으로 화면에 보이는 노드만 렌더 트리로 구성한다. + - `display: none`처럼 화면에 보이지 않는 요소는 렌더 트리에 포함되지 않는다. +6. 각 노드의 위치와 크기를 계산한다. +7. 계산된 정보를 바탕으로 실제 화면에 픽셀을 그린다. + +--- + +### Reflow와 Repaint + +- `Reflow` + - 요소의 크기, 위치 등이 변경되어 브라우저가 레이아웃을 다시 계산하는 과정 + +- `Repaint` + - 레이아웃 변화 없이 색상, 배경 등 시각적인 부분만 다시 그리는 과정 + +일반적으로 `Reflow`는 `Repaint`보다 브라우저 비용이 크다. +`Reflow`가 발생하면 레이아웃 계산 이후 `Repaint`까지 이어질 수 있기 때문이다. + +따라서 잦은 DOM 조작이나 레이아웃 변경은 렌더링 성능 저하의 원인이 될 수 있다. +CSS 애니메이션에서는 `width`, `height`, `top`, `left`보다 `transform`, `opacity`처럼 `Reflow`를 유발하지 않는 속성을 우선 사용하는 것이 좋다. + +> 💡 CSS 애니메이션을 사용할 때는 단순히 움직임만 고려하는 것이 아니라, 브라우저 렌더링 비용도 함께 고려해야 한다. + +--- + +## 가상 DOM + +가상 DOM은 실제 브라우저 DOM이 아니라, React가 메모리에서 관리하는 UI의 가상 표현이다. + +React는 변경이 발생하면 먼저 가상 DOM에서 변경 결과를 계산한 뒤, 실제 DOM에는 필요한 변경 사항만 반영한다. + +### 가상 DOM의 배경 + +브라우저가 화면을 렌더링하는 과정은 비용이 크다. + +- MPA는 페이지 이동 시 HTML을 새로 받아 다시 렌더링한다. +- SPA는 페이지를 새로고침하지 않는 대신, 화면 내부의 DOM 변경이 자주 발생한다. + +SPA에서는 사용자 인터랙션에 따라 DOM을 세밀하게 관리해야 하므로 복잡도가 커진다. +가상 DOM은 이러한 DOM 변경 과정을 React가 효율적으로 관리하기 위해 사용된다. + +> 개발자는 DOM을 직접 어떻게 바꿀지보다, 최종적으로 어떤 UI가 되어야 하는지에 집중할 수 있다. + +--- + +## 리액트 파이버 + +리액트 파이버(React Fiber)는 React 16부터 도입된 새로운 재조정(Reconciliation) 엔진이자 React의 코어 아키텍처이다. + +파이버는 렌더링 작업을 작은 단위로 나누고 우선순위를 부여해, UI 업데이트의 응답성을 높인다. + +## 파이버의 역할 + +파이버는 다음과 같은 작업을 가능하게 한다. + +1. 작업을 작은 단위로 나누고 우선순위를 부여한다. +2. 작업을 일시 중지하고 나중에 다시 시작할 수 있다. +3. 이전 작업을 재사용하거나 필요 없는 작업을 폐기할 수 있다. + +> 💡 파이버의 핵심은 렌더링 작업을 더 유연하게 처리하는 데 있다. + +## 파이버 동작 과정 + +파이버의 작업은 크게 `Render 단계`와 `Commit 단계`로 나뉜다. + +### Render 단계 + +사용자에게 직접 보이지 않는 계산 작업을 수행하는 단계이다. + +- 업데이트가 필요한 컴포넌트 탐색 +- 작업 우선순위 지정 +- 작업 중단, 재시작, 폐기 가능 +- 변경 사항 계산 + +### Commit 단계 + +Render 단계에서 계산된 변경 사항을 실제 DOM에 반영하는 단계이다. + +- 실제 DOM 변경 +- `commitWork()` 실행 +- 동기적으로 실행되며 중단될 수 없음 + +React 요소는 렌더링이 발생할 때마다 새롭게 생성되지만, 파이버는 가능한 한 재사용된다. +파이버는 컴포넌트가 최초로 마운트될 때 생성되고, 이후 업데이트 과정에서 재사용된다. + +--- + +## 리액트 파이버 트리 + +React 내부에는 두 개의 파이버 트리가 존재한다. + +1. 현재 화면 상태를 나타내는 파이버 트리 +2. 작업 중인 상태를 나타내는 `workInProgress` 트리 + +작업이 완료되면 React는 `workInProgress` 트리를 현재 트리로 바꾼다. +이때 실제 트리를 복사하는 것이 아니라 포인터를 교체한다. + +이러한 방식을 **더블 버퍼링(Double Buffering)**이라고 한다. + +--- + +# 생명주기(Life Cycle) + +클래스 컴포넌트에서 자주 언급되는 개념이 생명주기이다. +함수 컴포넌트가 많이 사용되는 지금도 React의 동작 흐름을 이해하는 데 중요한 개념이다. + +## 생명주기란? + +React 컴포넌트가 생성되고, 업데이트되고, 사라지는 과정을 **생명주기(Life Cycle)**라고 한다. + +생명주기는 크게 세 단계로 나눌 수 있다. + +1. 마운트(Mount) + - 컴포넌트가 생성되고 화면에 나타나는 시점 + +2. 업데이트(Update) + - 이미 생성된 컴포넌트의 상태나 속성이 변경되는 시점 + +3. 언마운트(Unmount) + - 컴포넌트가 화면에서 사라지는 시점 + +--- + +## 클래스형 컴포넌트와 함수형 컴포넌트 비교 + +| 분류 | 클래스형 컴포넌트 | 함수형 컴포넌트 | +|---|---|---| +| Mounting | `constructor()` | 함수형 컴포넌트 내부 실행 | +| Mounting | `render()` | `return()` | +| Mounting | `componentDidMount()` | `useEffect()` | +| Updating | `componentDidUpdate()` | `useEffect()` | +| Unmounting | `componentWillUnmount()` | `useEffect()`의 cleanup 함수 | + +--- + +# 렌더링 + +## React 렌더링이란? + +브라우저의 렌더링은 HTML과 CSS를 기반으로 사용자에게 보여줄 화면을 그리는 과정이다. + +React의 렌더링은 컴포넌트가 가진 `props`와 `state`를 기반으로 어떤 UI를 구성할지 계산하는 과정이다. + +> 💡 브라우저 렌더링은 실제 화면을 그리는 과정이고, React 렌더링은 화면에 반영할 UI 결과를 계산하는 과정이다. + +--- + +## 리액트 렌더링이 발생하는 경우 + +렌더링은 크게 최초 렌더링과 리렌더링으로 나뉜다. + +- 최초 렌더링 + - 사용자가 애플리케이션에 처음 진입했을 때 발생하는 렌더링 + +- 리렌더링 + - 최초 렌더링 이후 상태 변화에 의해 다시 발생하는 렌더링 + +리렌더링은 주로 다음 상황에서 발생한다. + +- `setState` 실행 +- `forceUpdate` 실행 +- `useState`의 setter 함수 실행 +- `useReducer`의 dispatch 실행 +- `props` 변경 +- 부모 컴포넌트 리렌더링 +- `key` 값 변경 + +`key`는 형제 요소 사이에서 동일한 요소를 식별하기 위한 값이다. +React는 이전 트리와 새로운 트리를 비교할 때 `key`를 기준으로 어떤 요소가 유지, 추가, 삭제되었는지 판단한다. + +--- + +## 리액트 렌더링 프로세스 + +React는 컴포넌트 트리의 루트부터 아래로 탐색하며 업데이트가 필요한 컴포넌트를 찾는다. + +- 클래스 컴포넌트는 `render()`를 호출한다. +- 함수형 컴포넌트는 컴포넌트 함수 자체를 호출한다. + +이 과정을 통해 새로운 React 요소 트리를 만들고, 이전 결과와 비교한다. + +--- + +## Render와 Commit + +React 렌더링은 크게 `Render 단계`와 `Commit 단계`로 나눌 수 있다. + +### Render 단계 + +컴포넌트를 실행하고 변경 사항을 계산하는 단계이다. + +- `render()` 또는 함수형 컴포넌트 실행 +- 이전 결과와 새로운 결과 비교 +- 변경이 필요한 요소 탐색 + +React는 주로 다음 값을 기준으로 변경 여부를 판단한다. + +- `type` +- `props` +- `key` + +### Commit 단계 + +Render 단계에서 계산된 변경 사항을 실제 DOM에 반영하는 단계이다. + +이 단계가 끝난 이후 브라우저 렌더링이 발생하고, 사용자 화면에 변경 내용이 보인다. + +Commit 단계에서는 다음 작업도 함께 실행된다. + +- `componentDidMount` +- `componentDidUpdate` +- `useLayoutEffect` + +중요한 점은 React 렌더링이 발생했다고 해서 항상 실제 DOM 업데이트가 일어나는 것은 아니라는 점이다. +Render 단계에서 변경 사항이 없다고 판단되면 Commit 단계는 생략될 수 있다. + +--- + +# React.memo + +`React.memo`는 함수형 컴포넌트의 렌더링 결과를 메모이제이션(Memoization)하는 고차 컴포넌트(HOC)이다. + +동일한 `props`가 전달되면 이전 렌더링 결과를 재사용해 불필요한 리렌더링을 줄일 수 있다. + +```tsx +const MemoComponent = React.memo(Component); +``` + +React에서는 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 기본적으로 함께 리렌더링된다. +이때 자식 컴포넌트의 `props`가 변하지 않았다면 불필요한 렌더링이 발생할 수 있다. + +`React.memo`는 이전 `props`와 현재 `props`를 얕은 비교(Shallow Compare)하여, 값이 같다면 이전 렌더링 결과를 재사용한다. + +--- + +## 언제 React.memo()를 써야 할까? + +실시간으로 변경되는 데이터와, 변경되지 않는 UI가 함께 존재하는 경우 유용하다. + +예를 들어 조회수(`views`)는 계속 변경되지만 영화 정보(`title`, `releaseDate`)는 변경되지 않는 상황을 생각할 수 있다. +이 경우 부모 컴포넌트가 `views` 변경으로 리렌더링되면서 `Movie` 컴포넌트도 함께 리렌더링될 수 있다. + +```tsx +const Movie = React.memo(function Movie({ + title, + releaseDate, +}: { + title: string; + releaseDate: string; +}) { + console.log('Movie Render'); + + return ( +
+ {title} ({releaseDate}) +
+ ); +}); + +function MovieViewsRealtime({ + title, + releaseDate, + views, +}: { + title: string; + releaseDate: string; + views: number; +}) { + return ( +
+ +

Movie views: {views}

+
+ ); +} +``` + +위 코드에서 `views`가 변경되면 `MovieViewsRealtime`은 리렌더링된다. +하지만 `Movie`에 전달되는 `title`, `releaseDate`가 같다면 `React.memo`를 통해 `Movie`의 리렌더링을 방지할 수 있다. + +--- + +## React.memo를 사용하는 경우 + +- 동일한 `props`로 자주 렌더링되는 컴포넌트 +- 렌더링 비용이 큰 컴포넌트 +- 부모 컴포넌트의 리렌더링 영향만 받는 자식 컴포넌트 + +--- + +## React.memo 사용 시 주의할 점 + +`React.memo`는 `props`만 비교한다. + +따라서 다음 값이 변경되면 `React.memo`로 감싸더라도 리렌더링이 발생한다. + +- 컴포넌트 내부 `state` +- `context` +- `useReducer`로 관리하는 상태 + +또한 함수 props는 렌더링마다 새로운 참조값이 생성될 수 있다. +이 경우 필요에 따라 `useCallback`과 함께 사용할 수 있다. + +```tsx +const onClick = useCallback(() => { + // ... +}, []); +``` + +`React.memo`는 모든 컴포넌트에 무조건 적용하는 최적화가 아니다. +비교 연산에도 비용이 있으므로, 렌더링 비용이 크거나 불필요한 리렌더링이 실제로 문제가 되는 컴포넌트에 선택적으로 사용하는 것이 좋다.