From 55ae735a59f75192e7bbb0763ea7cd92847dc232 Mon Sep 17 00:00:00 2001 From: RookieAND Date: Sun, 10 Mar 2024 02:19:50 +0900 Subject: [PATCH] =?UTF-8?q?=E2=9C=85=20=EB=B0=B1=EA=B4=91=EC=9D=B8=202024-?= =?UTF-8?q?03-08?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/RookieAND/dil/2024-03-08.md | 379 +++++++++++++++++++++++++++++-- 1 file changed, 358 insertions(+), 21 deletions(-) diff --git a/docs/RookieAND/dil/2024-03-08.md b/docs/RookieAND/dil/2024-03-08.md index 147bf38..b03da46 100644 --- a/docs/RookieAND/dil/2024-03-08.md +++ b/docs/RookieAND/dil/2024-03-08.md @@ -92,29 +92,29 @@ JSX 는 `JSXElement`, `JSXAttributes`, `JSXChildren`, `JSXString` 의 4가지 ```jsx const Component = ( - - Hello World - + + Hello World + ); // React 17 이전 변환 결과 // createElement(Component, props, children) 함수로 변환된 결과다. var Component = React.createElement( - A, - { option: 'a', key: 'b' }, - 'Hello World' + A, + { option: "a", key: "b" }, + "Hello World" ); // React 17 이후 변환 결과 -import { jsx as _jsx } from 'react/jsx-runtime'; +import { jsx as _jsx } from "react/jsx-runtime"; var Component = _jsx( - A, - { - option: 'a', - children: 'Hello World', - }, - { key: 'b' } + A, + { + option: "a", + children: "Hello World", + }, + { key: "b" } ); ``` @@ -151,7 +151,6 @@ const TestHeading = ({ isHeading, children }: PropsWithChildren) => { DOM 은 문서 (document) 와 문서 내부의 요소 (Element) 에 JS 로 접근할 수 있도록 설계된 Object 다. - > 브라우저에서 특정 화면을 보여주기 위한 렌더링 과정은 아래와 같이 작성할 수 있다. 1. 서버로부터 받은 HTML 파일을 다운로드 하여 브라우저 렌더링 엔진이 이를 파싱하도록 한다. @@ -166,12 +165,13 @@ DOM 은 문서 (document) 와 문서 내부의 요소 (Element) 에 JS 로 접 ### ✏️ VDOM 의 탄생 배경 - 1. 비싼 렌더링 비용 + - 특정 요소의 스타일 혹은 내부 요소의 변경이 일어날 경우 브라우저는 렌더링 과정을 재수행하게 된다. - 이러한 과정은 **Reflow** 와 **Repaint** 로 나뉘는데, Reflow 의 경우 필연적으로 Repaint 작업까지 수행하기에 **렌더링 비용이 비싸다.** 2. SPA 의 특수한 경우 + - 싱글 페이지 애플리케이션 (SPA) 의 경우 하나의 화면에서 각 요소를 다시 설계하고 그려야 하기 때문에 DOM 을 관리하는데 드는 비용이 높다. > 이러한 문제점을 해결하기 위해 등장한 개념이 바로 가상 돔 (Virtual DOM) 이다. @@ -181,8 +181,7 @@ DOM 은 문서 (document) 와 문서 내부의 요소 (Element) 에 JS 로 접 - VDOM 은 절대로 일반 DOM 을 수정하는 것보다 **결코 빠르지 않다**. 오히려 렌더링을 진행하기 위한 과정이 추가된 셈이라 상황에 따라서는 더 성능이 느릴 수 있다. - 최근 등장한 웹 프레임워크 (SolidJS, Svelte) 의 경우에는 실제 DOM 을 수정하는 방식을 채택하여 Virtual DOM 이 가진 한계를 타파하는 모습을 보였다. - -### ✏️ Reconciliation +### ✏️ Reconciliation 컴포넌트 내부에 변경 사항이 발생하여 리렌더링이 진행될 경우, 기존의 DOM Tree 와 새롭게 구축된 DOM Tree 간의 비교 사항을 알아야 할 필요가 있다. @@ -193,7 +192,6 @@ DOM 은 문서 (document) 와 문서 내부의 요소 (Element) 에 JS 로 접 1. Element 의 타입이 달라질 경우 해당 Element 는 서로 다른 트리를 구성한다. 2. **key props** 가 변경될 경우 해당 Element 는 서로 다른 트리를 구성한다. - ### ✏️ React Fiber 의 등장 계기 기존 재조정 알고리즘의 구조는 Stack 기반이었기 때문에 해당 스택에 필요한 작업을 적재시키고 순차적으로 이를 해결하는 방식을 채택했다. @@ -208,7 +206,346 @@ DOM 은 문서 (document) 와 문서 내부의 요소 (Element) 에 JS 로 접 따라서 이러한 문제를 해결하기 위해 React 16 버전 이상부터는 기존의 Stack 방식이 아닌 **React Fiber** 라는 새로운 아키텍쳐가 도입되었다. -### ✏️ React Fiber +### ✏️ React Fiber Reconciliation + +Fiber Reconciliation 은 각각의 작업에 대한 우선 순위를 매기고 이에 대한 일시 정지, 재가동, 우선 가동을 가능하도록 설계되었다. 이러한 방식을 **incremental rendering** 이라 한다. + +또한 Fiber Reconciliation 과정은 전부 비동기로 동작하기 때문에 Stack Reconciliation 과는 달리 선택적으로 작업을 취합하여 진행할 수 있다. + +### ✏️ React Fiber 구조 살펴보기 + +```js +function FiberNode( + tag: WorkTag, + pendingProps: mixed, + key: null | string, + mode: TypeOfMode +) { + // Instance + this.tag = tag; // Fiber 의 종류를 의미 (FunctionalComponent, SuspenseComponent 등) + this.key = key; // React 내 key 속성 + this.elementType = null; + this.type = null; + this.stateNode = null; // Fiber Node 와 연관된 실제 DOM 노드 및 컴포넌트 인스턴스 (클래스 컴포넌트일 경우) 를 의미 + + // Fiber + this.return = null; // 부모 Fiber Node + this.child = null; // 부모 Fiber Node 에서 첫 번째로 가진 자식 노드 + this.sibling = null; // 자신의 바로 다음 형제 노드 + this.index = 0; // 자신의 형제들 중에서 몇 번째 순서인지를 나타냄 + + this.ref = null; // DOM Node 혹은 컴포넌트 인스턴스의 상태 및 업데이트를 관리 + + this.pendingProps = pendingProps; // Fiber Node 생성 당시에는 렌더링 작업이 종료되지 않았으므로 인계 받은 props 를 pendingProps 으로 관리. + this.memoizedProps = null; // Render Phase 종료 이후 사용되었던 pendingProps 를 보관 + this.updateQueue = null; // 상태 업데이트 + this.memoizedState = null; // 함수형 컴포넌트 내에서 생성된 Hook list + this.dependencies = null; // 컴포넌트 내부의 여러 의존성을 관리하는 field + + this.mode = mode; // 컴포넌트의 렌더링 모드를 설정 + + // Effects + this.flags = NoFlags; // Fiber Node 의 현재 Flag (Update, Mount, etc) + this.subtreeFlags = NoFlags; // 하위 Fiber Node Tree 의 상태 Flag + this.deletions = null; // 삭제 예정인 자식 노드를 담은 field + + this.lanes = NoLanes; + this.childLanes = NoLanes; + + this.alternate = null; +} +``` + +- Fiber Node 를 이루는 여러 속성에 대해서는 추후 시간을 내어 조사한 후 별도의 정리가 필요해보인다 (양이 너무 방대하다...) +- Source Code : https://github.com/facebook/react/blob/v18.2.0/packages/react-reconciler/src/ReactFiber.new.js + +### ✏️ React Fiber Tree 살펴보기 + +Fiber Tree 는 React 내부에서 두 개로 나뉘어 관리된다. 하나는 현재 모습을 담은 트리이며 다른 하나는 작업 중인 상태를 나타내는 트리다. 이러한 방식을 **더블 버퍼링**이라 한다. + +변경 사항을 적용할 VDOM Tree 와 현재 화면에 보여지는 화면을 구성하는 VDOM Tree 를 둘 다 메모리에 적재하여 (Buffer) 변경 사항이 모두 완료되기 전까지는 이전에 구축한 트리를 보여줌으로서 두 트리를 교차하는 방식이다. + +- `current` : 현재 렌더링 중인 화면을 구성하는 VDOM 트리 +- `workInProgress` : Render Phase 에서 작업 중인 변경 사항을 적용하는 VDOM 트리 + +workInProgress 트리는 Render Phase 를 거쳐 Commit Phase 로 넘어갈 경우 포인터를 변경하여 current 트리로 변경한다. + +### ✏️ Fiber 의 작업 순서 살펴보기 + +1. `beginWork` 함수를 실행하여 상태가 변경된 컴포넌트를 찾고, 변경점을 찾았다면 작업을 수행한다. +2. 작업이 완료되었을 때를 기준으로 형제 노드를 찾고, 만약 현제 노드가 없다면 부모 노드를 대상으로 작업을 수행한다. +3. 작업이 완료된 Fiber Node 를 대상으로 `completeWork` 메서드를 실행하여 Node 의 tag 에 맞는 Element 를 생성하여 Commit Phase 에 넘긴다. +4. 모든 작업이 완료되어 루트 노드가 완성되었다면 Commit Phase 에 진입하여 실제 DOM 에 변경 사항을 반영한다. + +> Fiber 가 Reconciliation 과정을 통해 실제 DOM 에 반영되는 프로세스는 이보다 훨씬 복잡하다 + +- 추후 해당 내용은 별도의 시간을 들여 공부할 예정이다. + +# ✒️ Class Component 살펴보기 + +### ✏️ Class Component 의 구조 + +```jsx +class Welcome extends React.Component { + constructor(props) { + super(props); + this.state = { + count: 0, + }; + this.handleClick = this.handleClick.bind(this); + } + + render() { + const { + props: { name }, + } = this; + + return

Hello, {name}

; + } +} +``` + +- 클래스 컴포넌트는 기본적으로 `React.Component` 혹은 `React.PureComponent` 를 확장해야 한다. +- 컴포넌트 클래스의 생성자 내부에는 `super` 키워드를 사용하여 상위 클래스인 `React.Component` 의 생성자를 호출한다. +- state 기본 값 또한 constructor 내부에 정의했으나, ES2022 를 기준으로 추가된 클래스 필드 문법을 사용하면 클래스 내부에 state 를 바로 정의할 수 있다. + +### ✏️ Class Component 의 LifeCycle + +- 클래스 컴포넌트 내부에서는 컴포넌트가 Mount, Update, Unmount 되면서 실행되는 여러 LifeCycle 메서드를 사용할 수 있다. +- 함수형 컴포넌트에서는 이러한 생명 주기 메서드를 사용하지 못하지만 일부 라이브러리의 경우 아직 클래스 컴포넌트의 생명주기 메서드에 의존하고 있다. + +생명주기 메서드가 실행되는 시점은 크게 세 가지로 정의할 수 있다. + +1. Mount : 컴포넌트가 생성되는 시점 +2. Update : 생성된 컴포넌트의 내용이 변경되어 업데이트 되는 시점 +3. Unmount : 생성된 컴포넌트가 사라져 존재하지 않는 시점 + +### ✏️ LifeCycle Method 목록 + +1. render() + +- 클래스 컴포넌트의 필수 값으로 쓰이며, 컴포넌트가 UI 를 렌더링하기 위해 쓰인다. +- 해당 메서드는 항상 순수 함수로 구성되어야 한다 (같은 입력이 들어가면 같은 결과물을 반환) + +2. componentDidMount() + +- 클래스 컴포넌트가 Mount 된 직후 실행되는 생명주기 메서드이다. + +3. componentDidUpdate() + +- 컴포넌트가 Update 되고 난 이후 실행된다. + +4. componentWillUnmount() + +- 컴포넌트가 Unmount 되기 직전에 실행된다. 주로 cleanup 함수를 실행할 때 쓰인다. + +5. shouldComponentUpdate + +- state 및 props 의 변경으로 리렌더링이 트리거 될 때, 해당 메서드를 활용하여 컴포넌트의 업데이트를 제한할 수 있다. + +6. static getDerivedStateFromProps + +- render 메서드가 실행되기 직전에 호출되며 componentWillReceiveProps 를 대체하는 메서드이다. +- 이후 렌더링 시점에 인계 받은 props 를 바탕으로 현재 state 를 변경할 때 쓰인다. + +7. getSnapShotBeforeUpdate + +- DOM 이 실제로 업데이트 되기 전에 실행되는 메서드이며, 여기서 반환된 값은 componentDidUpdate 로 이전된다. + +8. getDerivedStateFromError() + +- 자식 컴포넌트에서 에러가 발생할 경우 호출되는 에러 메서드이며, 반드시 state 값을 반환해야 한다. +- `react-error-boundary` 라이브러리에서는 자식 컴포넌트에서 발생한 에러를 받아 state 로 보관하기 위해 해당 생명주기 메서드를 쓴다. + +```js +static getDerivedStateFromError(error: Error) { + return { didCatch: true, error }; // catch 된 에러를 state 로 보관 +} +``` + +9. componentDidCatch + +- 자식 컴포넌트에서 에러가 발생할 경우 getDerivedStateFromError 에서 에러를 잡고 state 를 반환한 후 실행된다. +- 해당 메서드는 두 개의 인자를 받으며, 첫 번째 인자는 발생한 에러이며 두 번째 인자는 에러를 발생시킨 컴포넌트 정보다. +- `react-error-boundary` 라이브러리에서는 자식 컴포넌트에서 발생한 에러를 받아 이를 기반으로 `onError` props 로 인계 받은 함수를 실행시킨다. + +```js +componentDidCatch(error: Error, info: ErrorInfo) { + this.props.onError?.(error, info); +} +``` + +### ✏️ 클래스 컴포넌트의 한계 + +클래스 컴포넌트는 아래와 같은 한계를 가진다. + +1. 데이터의 흐름을 추적하기 어렵다 +2. 애플리케이션의 내부 로직의 재사용이 어렵다. +3. 기능이 많아질수록 컴포넌트의 규모가 커지고 추후 분리가 어렵다. +4. 함수형 컴포넌트에 비해 러닝 커브가 존재한다. +5. 기본적으로 작성해야 하는 코드의 양이 많기에 코드 최적화가 어렵다. +6. Hot Reload 에 불리함이 있다. + +### ✏️ Class Component VS Functional Component + +1. 생명주기 메서드의 부재 + +- 생명주기 메서드는 `React.Component` 클래스 내부의 메서드이기 때문에 이를 사용하지 않는 함수형 컴포넌트의 경우 더 이상 해당 메서드에 접근할 수 없다. +- 다만 `useEffect` Hook 을 통한 Passive Effect 를 기반으로 기존에 사용하던 `componentDidUpdate` 와 같은 생명주기 메서드를 부분적으로 대체할 수 있다. +- 하지만 `useEffect` 는 해당 생명주기 메서드를 대체하기 위한 목적이 아닌 Passive Effect 를 실행하기 위한 훅임을 명심하자. + +2. 함수형 컴포넌트의 state 와 props 는 생성될 당시의 값에 대한 SnapShot 이다. + +- 클래스형 컴포넌트의 경우 내부에서 변경된 state 와 props 에 바로 접근할 수 있다. 이는 두 값을 Class 내부의 instance 에서 보관하기 때문이다. +- 하지만 함수형 컴포넌트의 경우 props 는 인자로 받으며, state 의 경우 컴포넌트 외부의 공간에서 Closure 로 관리된다. +- 따라서 매 리렌더링이 일어날 때마다 반환된 props 와 state 를 기반으로만 동작하며, 해당 값이 변경되었다면 이후 렌더링이 일어난 후 반영된다. + +> 과거 state 에 대하여 정리한 블로그 포스팅 링크 + +- https://velog.io/@rookieand/React%EC%9D%98-state-%EA%B7%B8%EB%A6%AC%EA%B3%A0-useState%EC%97%90-%EB%8C%80%ED%95%B4-%EB%8D%94-%EC%95%8C%EA%B3%A0-%EC%8B%B6%EC%96%B4%EC%A1%8C%EB%8B%A4` + +# ✒️ React Rendering + +### ✏️ 리액트에서의 렌더링이란? + +- 현재 컴포넌트의 state 와 prop를 기반으로 컴포넌트에게 어떻게 UI 를 구성하고 이를 DOM 에 적용할지를 계산하는 과정이다. + +### ✏️ Rendering Process in React + +React 의 Rendering 과정은 크게 아래와 같이 세 단계로 나뉠 수 있다. + +1. Trigger Render + +- 컴포넌트가 처음 렌더링 되거나, 컴포넌트 내부의 리렌더링을 유발시키는 요소 (props, state change) 가 존재할 경우 리렌더링이 발생한다. +- JSX 코드는 babel 같은 트랜스파일러에 의해 변환되어 `React.createElement` 코드로 변환되고, 해당 함수는 `ReactElement` 객체를 반환한다. + +2. Render Phase + +- React 에서 관리하는 **Virtual DOM 을 조작하는 일련의 과정**이다. +- 컴포넌트의 호출은 Render Phase 에서 실행되며 반환된 ReactElement 는 Fiber Node로 확장되어 이를 기반으로 Virtual DOM Tree 를 생성한다. +- 기존의 Stack 기반의 재조정 과정에서는 각 작업 간의 우선 순위를 지정할 수 없었으나 Fiber Architecture 를 도입하며 각 과정을 취소, 중지, 재시작할 수 있게 되었다. + +> Render Phase 에서는 변경 사항이 **화면에 반영되는 것이 아니다.** + +- Render Phase 는 각 컴포넌트가 호출되어 반환된 ReactElement 가 Fiber 로 확장되어 Virtual DOM 에 반영되는 과정이다. +- 변경 사항이 적용된 Virtual DOM 을 기반으로 실제 DOM Tree 를 재구성한다는 의미가 절대 아니다. + +3. Commit Phase + +- Render Phase 에서 재조정된 Virtual DOM 을 실제 DOM 에 적용하는 단계다. +- Commit Phase 또한 Virtual DOM 의 변경 사항을 DOM 에 Mount 한다는 의미지, 실제 Paint 작업까지 실행되는 것은 아니다. +- 해당 단계는 Production, Development 모드와 관계 없이 일관된 화면 업데이트를 위해 동기적으로 실행된다. + +> Commit Phase 또한 변경 사항이 **화면에 반영되는 것이 아니다.** + +- Commit Phase 의 경우는 동기적으로 실행되기에 DOM 을 조작하기 위한 작업을 Call Stack 에 적재시킨다. +- 해당 단계가 모두 끝나고 Call Stack 이 비어야 브라우저에서 비로소 화면을 그리는 작업을 시작한다. +- 정리하자면 Virtual DOM 에 존재하는 변경 사항을 실제 DOM 에 반영하는 과정이 Commit Phase 라고 할 수 있다. + +### ✏️ 언제 리렌더링이 발생하는가? + +1. 상위 컴포넌트로부터 내려 받은 props 의 변화 +2. 컴포넌트 내부의 state 값의 update 발생 +3. 상위 컴포넌트에서 리렌더링이 발생한 경우 + +리액트에서 리렌더링은 상위 컴포넌트로부터 순차적으로 일어나므로, 자식 컴포넌트의 state 및 props 가 변경되지 않았음에도 같이 리렌더링 됨을 알아야 한다. + +# ✒️ Memoization + +### ✏️ 메모이제이션이란? + +- React 에서 메모이제이션 기법은 주로 특정 값이나 함수를 별도의 메모리에 보관하여 리렌더링을 방지하기 위해 쓰이는 기법이다. +- React 에서는 아래와 같은 방법으로 메모이제이션을 구현할 수 있다. + +1. `React.memo` + +- 컴포넌트를 감싸는 고차 함수이며, props 의 변화가 있는 경우에만 리렌더링을 진행한다. + +2. `useCallback` + +- 인자로 받은 callback 을 메모이제이션 하는 Hook 이다. +- 추후 리렌더링이 발생한 경우 새롭게 생성된 함수를 반환할지 아니면 메모이제이션 된 함수를 반환할지 결정하는 Hook 이다. + +> useCallback 은 함수의 생성을 방지하는 게 아니다. + +- useCallback 이 함수의 생성까지는 막지 않는다는 점이다. 매 리렌더링 마다 인자로 받은 callback 은 새롭게 생성된다. +- 기존의 메모이제이션 된 함수를 반환할지는 deps 내 요소의 변화가 관측되었는지에 따라 결정된다. +- useCallback 을 쓰는 이유는 함수를 props 로 내릴 경우 **매 렌더링마다 새로운 함수를 생성**하기에 메모리 주소가 달라지고, 이로 인해 해당 함수를 props 로 받는 컴포넌트의 리렌더링을 유발하기 때문이다. +- 따라서 이를 메모이제이션 하여 같은 메모리에 담긴 함수를 반환하면 동등성 검사를 통과하기에 리렌더링이 발생하지 않는다. + +3. `useMemo` + +- 인자로 받은 callback 함수가 반환하는 값을 메모이제이션 하는 Hook 이다. +- useMemo 또한 useCallback 과 같이 callback 의 생성 및 실행을 막지 못한다. 다만 이전에 생성된 값을 메모이제이션 할 뿐이다. + +### ✏️ 메모이제이션이 항상 만능일까? + +메모이제이션은 **절대 공짜가 아니다**. 특정 값을 메모리에 적재하는 것은 비용을 수반하며 어떤 경우에는 오히려 값을 보관하는 것이 새로운 값을 매번 생성하는 것보다 비효율적일 수 있다. + +또한 메모이제이션의 경우 이전의 값과 새롭게 생성된 값 중 어떤 값을 반환하는지를 결정하는 과정을 필수적으로 거치기 때문에 추가적인 비용이 발생한다. + +```jsx +// AS - IS : useDisclosure 훅이 넘겨주는 객체는 매번 다른 메모리 주소를 가진다. +const useDisclosure = (initState) => { + const [isOpen, setIsOpen] = useState(initState); + + const toggleState = () => { + setIsOpen((prev) => !prev); + }; + + return { isOpen, toggleState }; +}; + +// TO - BE : useDisclosure 훅이 넘겨주는 객체는 state 의 변경이 없다면 같은 메모리 주소에 위치한 객체를 반환한다. +const useDisclosure = (initState) => { + const [isOpen, setIsOpen] = useState(initState); + + const toggleState = () => { + setIsOpen((prev) => !prev); + }; + + return useMemo(() => ({ + isOpen, + toggleState, + })); +}; + +const Component = () => { + const { isOpen, toggleState } = useDisclosure(false); + + useEffect(() => { + console.log(isOpen); + }, [isOpen]); + + return

{isOpen ? "열림" : "닫힘"}

; +}; +``` + +- 상단의 예시의 경우 `useDisclosure` 훅은 `isOpen` state 와 `toggleState` 함수를 하나의 객체로 감싸 반환한다. +- 이때 `useDisclosure` 가 반환하는 객체는 매번 해당 Hook 이 호출될때마다 새롭게 생성되기에 해당 값을 받는 Component 내 `useEffect` 는 매 리렌더링마다 호출된다. +- 따라서 해당 객체를 `useMemo` 를 사용하여 감싸주면, state 의 변화가 없이는 매번 메모리 주소가 같은 객체를 넘겨준다. + + +### ✏️ 메모이제이션에 대한 책의 생각, 그리고 나의 생각 + +책에서는 메모이제이션이 늘 최적화의 도움을 준다고 이야기 했으나 필자의 의견은 살짝 다르다. + +1. React 내부에서 Memoization 이 필요한 작업이 그렇게 많은가? + +메모이제이션은 공짜가 아니며 특정 값을 별도의 공간에 보관하는 비용과 deps 의 동등성 비교를 통해 두 값중 어떤 값을 반환할지를 결정하는 비용을 수반한다. + +또한 메모이제이션은 **함수와 값의 생성까지는 막지 못하기에** prop로 참조형 값을 넘겨주거나 특정 값을 추출하기까지 오랜 시간이 걸리는 케이스가 아닌 이상은 오히려 메모이제이션이 불필요해질 수 있다. + +2. 섣부른 Memoization 은 때로 개발자에게 혼선을 줄 수 있다. + +리렌더링이 반드시 발생해야 하는 지점에서도 잘못된 메모이제이션으로 인한 결과로 의도치 못한 결과를 맞이할 수 있다. + +특정 함수나 특정 값을 **props 로 넘기는 게 아닌 이상** 이를 사용하는 컴포넌트 입장에서는 결국 매 렌더링마다 새로운 값 혹은 함수를 생성해야 한다. + +3. 리렌더링이 반드시 나쁜 것은 아니라고 생각한다. + +사용자의 의도와는 다르게 리렌더링이 계속 생기는 것은 나쁘지만, 반드시 실행되어야 하는 리렌더링이 잘못된 메모이제이션으로 인해 **트리거 되지 않는 케이스**도 문제라 생각한다. + +리렌더링 또한 특정 컴포넌트의 내부 값이 업데이트 되어 발생하는 경우와 컴포넌트가 아예 Unmount 된 이후 Mount 되는 케이스가 있는데, 전자가 후자보다 비용이 압도적으로 적다. -- React Fiber 는 각각의 작업에 대한 우선 순위를 매기고 이에 대한 일시 정지, 재가동, 우선 가동을 가능하도록 설계되었다. 이러한 방식을 **incremental rendering** 이라 한다. -- \ No newline at end of file +따라서 Update 로 인한 리렌더링을 방지하는 비용이 메모이제이션으로 인한 비용보다 **과연 더 클지를 항상 고민해보는 게 좋다고 생각**한다. 현재 브라우저와 사용자의 디바이스 성능은 계속해서 좋아지고 있기 때문에, 규모와 로직이 작은 컴포넌트까지 메모이제이션을 굳이 해야 하는가에 대한 고민을 하면 어떨까 싶다.