렌더링
은 UI 요소를 화면에 그려내는 과정을 의미합니다.React
와 같은 UI 라이브러리 또는 프레임워크
를 사용하는 이유는 렌더링 과정을 효율적으로 처리하기 위해서입니다.렌더링
은 DOM 요소를 계산하고 화면에 그려내는 과정을 말합니다.DOM
과 CSSOM
이 결합되어 최종적으로 브라우저에 그려집니다.React
는 UI
를 선언적으로 작성하면 실제 렌더링은 React
에서 처리하며, 개발자는 UI 설계
에 집중할 수 있습니다.React
는 선언적인 개발 방식을 강조하는데, 이는 애플리케이션의 규모가 커져도 관리하기 쉽게 만들어줍니다.React
를 포함한 다른 라이브러리 및 프레임워크들은 애플리케이션에서 보여주고 싶은 핵심 UI
를 "선언"하기만 하면 실제 DOM 조작은 라이브러리가 대신 처리합니다.React
내부의 렌더링 과정을 최적화하기 위해 각 단계에서 렌더링이 언제 발생하고 어떤 과정을 거치는지 이해하는 것이 중요합니다.렌더링
을 최적화할 수 있습니다.state
가 변경되었을 때 발생합니다.UI
와 상태(state
)를 연동시키는 것입니다.UI
는 데이터를 가지고 있고, state
는 변할 수 있는 데이터를 나타냅니다.state
를 사용하여 UI
를 동적으로 업데이트할 수 있습니다.setState
함수를 사용하여 state
를 변경하고, 변경할 때마다 리렌더링이 수행됩니다.state
가 변경되면 해당 컴포넌트와 하위 컴포넌트들이 모두 리렌더링됩니다.state
의 변경을 통해 UI
를 업데이트하므로, UI
와 데이터를 연동시키는 핵심 원칙입니다.리액트에서의 렌더링 과정은 다음과 같은 단계로 이루어집니다:
기존 컴포넌트의 UI를 재사용
할 지 확인한다.
컴포넌트 함수를 호출하거나 클래스 컴포넌트의 render
메소드를 호출하여 새로운 Virtual DOM을 생성
한다.
render 메소드가 실행
되어 JSX 코드를 해석하여 Virtual DOM 객체를 생성
한다.이전의 Virtual DOM
과 새로운 Virtual DOM
을 비교하여 변경된 부분을 식별
한다.
변경된 요소
를 찾아낸다.변경된 부분만
실제 DOM에 적용하여 화면을 업데이트
한다.
DOM 조작
을 수행하여 화면에 변경 사항을 반영한다.이러한 과정을 통해 리액트는 Virtual DOM
을 활용하여 필요한 최소한의 DOM 조작
만 수행하여 브라우저의 성능을 최적화한다. 리액트는 Virtual DOM
을 통해 변경 사항을 비교하고 일부분만 업데이트함으로써 전체 화면을 다시 그리는 비용을 최소화하여 효율적인 렌더링을 가능하게 한다.
브라우저는 근본적으로 화면을 보여주기 위해서 HTML, CSS, JavaScript
를 다운로드 받고 그를 처리해서 화면에 픽셀 형태로 그려냅니다. 그리고 이 과정을 CRP(Critical Rendering Path)
라고 부릅니다.
Critical Rendering Path
는 기본적으로 아래의 과정을 수행합니다.
HTML을 파싱
해서 DOM을 만든다.CSS를 파싱
해서 CSSOM을 만든다.Render Tree
를 만든다.각 요소들의 위치와 크기를 계산
한다.(Layout)Render Tree상의 요소들을 실제 Pixel로 그려
낸다. (Paint)리액트에서는 UI
의 변화를 처리하기 위해 DOM 조작
이 필요합니다. 하지만 개별적인 DOM 조작은 브라우저에 많은 연산을 요구하고 성능 저하를 야기할 수 있습니다. 따라서 리액트는 이런 문제를 해결하기 위해 Virtual DOM
이라는 개념을 도입합니다.
리액트에서 UI
의 변화가 발생하면 변경 사항을 바로 실제 DOM
에 적용하는 대신, 리액트가 관리하는 Virtual DOM
객체 형태로 만들어냅니다. 이전의 Virtual DOM
과 새로운 Virtual DOM
을 비교하여 변경된 부분을 식별한 후, 실제로 변경이 필요한 DOM 요소
들만 찾아내고 한 번에 조작합니다.
이러한 처리를 통해 리액트는 브라우저에서 수행되는 Critical Rendering Path (CRP)
의 빈도를 줄이는 최적화를 수행합니다. 즉, 이전의 Virtual DOM
과 새로운 Virtual DOM
을 비교하여 변경된 부분만 실제 DOM
에 적용함으로써 최소한의 DOM 조작
만 수행하고 성능을 향상시킵니다. 이 최적화는 리액트 내부에서 처리되므로 개발자는 별도의 최적화를 수행할 필요가 없습니다.
따라서 개발자가 할 수 있는 최적화는 기존 컴포넌트의 UI를 재사용
하는지 확인하는 것과 새로운 Virtual DOM을 생성
할 때 최적한 형태로 만드는 것입니다. 이를 통해 리액트는 효율적인 렌더링을 위해 Virtual DOM
을 활용하고, 불필요한 DOM 조작을 최소화
하여 성능을 개선합니다.
리액트를 사용하는 개발자가 할 수 있는 최적화 방법은 다음과 같습니다:
기존 컴포넌트의 UI를 재사용
할 지 확인하기:
새로운 Virtual DOM을 생성하기:
새로운 Virtual DOM
을 생성하는 과정에서 최적화를 수행합니다.새로운 Virtual DOM
의 형태를 이전과 비교하여 차이가 적은 형태로
만들어질 수 있도록 설계합니다.UI를 변경
하기 위해 태그의 종류를 변경하는 것보다는 클래스 이름 등의 속성만 변경
하여 Virtual DOM의 차이를 최소화
합니다.<div> -> <span> (x)
<div className="block" /> -> <div className="inline"> (o)
위 두 가지 최적화 방법을 적절히 활용하여 리액트 컴포넌트의 불필요한 리렌더링과 Virtual DOM 생성을 줄일 수 있습니다. 이를 통해 성능 향상과 효율적인 렌더링을 달성할 수 있습니다.
리액트에서 컴포넌트의 UI를 재사용
하기 위한 최적화 방법:
state
가 변할 경우 해당 컴포넌트와 하위 컴포넌트를 모두 리렌더링한다.props
가 변하지 않았다면 UI
가 변화하지 않았을 수도 있다.모든 컴포넌트 트리를 순회
하여 확인하는 것은 비효율적
이다.React.memo
함수를 제공하여 개발자가 컴포넌트의 리렌더링 여부를 결정
할 수 있다.React.memo
는 컴포넌트를 래핑하여 이전에 렌더링된 결과를 기억하고, props
가 변경되었을 때에만 리렌더링을 수행한다.React.memo
를 사용하여 컴포넌트의 UI를 재사용
할 지 판단할 수 있다.props
를 비교하여 UI가 동일
하다고 판단되면 컴포넌트 함수 호출을 피하고 이전 결과를 재사용한다.효율적인 UI 업데이트
를 달성할 수 있다.따라서, React.memo를 활용하여 컴포넌트의 리렌더링 여부를 결정하고 이전 결과를 재사용함으로써 효율적인 UI 업데이트를 수행할 수 있다.
리액트에서 컴포넌트의 리렌더링을 제어하기 위해 React.memo를 사용하는 방법:
React.memo
는 HOC(Higher Order Component)
로, 컴포넌트를 인자로 받아 컴포넌트를 리턴하는 형태의 함수이다.React.memo
로 감싸진 컴포넌트는 상위 컴포넌트가 리렌더링될 때 모든 경우에 리렌더링되는 것이 아니라, 이전 props
와 다음 렌더링 때 사용될 props
를 비교하여 차이가 있을 경우에만 리렌더링을 수행한다.React.memo
의 두 번째 인자로는 변화를 판단하는 함수를 전달할 수 있다.true를 반환
할 경우 이전 결과를 재사용하고 false를 반환
할 경우 리렌더링을 수행한다.areEqual(prevProps, nextProps)
와 같이 변화를 판단하는 함수를 작성하여 React.memo
의 두 번째 인자로 전달할 수 있다.true를 반환
하면 이전 결과를 재사용하고, false를 반환
하면 리렌더링을 수행한다.아래는 React.memo
와 변화를 판단하는 함수를 사용한 코드 예시:
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
true를 반환할 경우 이전 결과를 재사용
false를 반환할 경우 리렌더링을 수행
*/
}
export default React.memo(MyComponent, areEqual);
이 예시에서는 MyComponent
를 React.memo
로 감싸고, 변화를 판단하는 함수 areEqual
을 두 번째 인자로 전달하였다. areEqual
함수는 이전 props
와 새로운 props
를 비교하여 true나 false를 반환
하며, 반환 값에 따라 리렌더링 여부가 결정된다.
자바스크립트
의 데이터 타입:
기본형 타입
과 참조형 타입
이 있다.기본형 타입(원시형 타입)
은 원시적이고 독립적인 형태로 존재하는 데이터로서, string
, number
, boolean
, null
, undefined
, bigint
, symbol
등이 있다.참조형 타입(객체형 타입)
은 여러 데이터를 모아서 만든 객체로서, 객체
, 배열
, 함수
등이 있다.React.memo
와 데이터 타입:
React.memo
는 기본적으로 props 객체 간의 비교를 통해 동작한다.React.memo
를 사용할 때, props의 데이터 타입에 대한 이해가 필요하다.기본형 타입
(props의 값이 변경되면 항상 다른 객체로 취급됨)과 참조형 타입
(props의 참조가 변경되지 않으면 같은 객체로 취급됨)을 구분하여 처리해야 한다.값이 변경
될 때마다 리렌더링이 발생한다.객체의 참조가 변경되지 않으면 이전 결과를 재사용하고 리렌더링을 방지
한다.객체의 가변성과 불변성에 관한 요약:
가변성과 비교:
불변성의 활용:
객체를 불변하게 유지하는 것은 객체를 변경하지 않고 새로운 객체를 생성하여 교체하는 방식을 의미합니다.
불변성을 유지하는 경우, 객체를 비교할 때 내부의 값이 변경되었는지를 일일이 확인하지 않고도 객체의 동등성을 판단할 수 있습니다.
불변성을 유지하면 객체의 변경을 추적하기 쉬워지고, 객체를 사용하는 다양한 컴포넌트나 함수에서 예상 가능한 동작을 보장할 수 있습니다.
예시와 요약:
예시 1:
let dog = "tori";
dog = "mozzi";
예시 2:
const yeonuk = { name: "yeonuk", gender: "male" };
yeonuk.name = "charlie";
요약:
React.memo
에서의 props 비교
와 shallow compare
에 관한 요약:
React.memo
는 기본적으로 props의 변화
를 판단하기 위해 shallow compare
를 수행합니다.props
는 객체 형태로 표현되며, 매 렌더링마다 새로운 props 객체가 생성
됩니다. 따라서 props 객체 자체를 비교
하는 것은 의미가 없습니다.React.memo
는 props 객체 안의 각 property를 개별적으로 비교
합니다.Object.is(===) 연산자를 사용
하여 수행됩니다. 이 연산자는 두 값이 정확히 같은지 비교하며, 하나라도 false
가 나오면 props가 변경
되었다고 판단하여 리렌더링을 수행합니다.예시와 요약:
<Component name="foo" hello="world" object={{first:1, second:2}} array={[1,2,3]} />
<Component name="bar" hello="world" object={{first:1, second:2}} array={[1,2,3]} />
const areEqual = (prevProps, nextProps) => {
if (prevProps.name !== nextProps.name) return false;
if (prevProps.hello !== nextProps.hello) return false;
if (prevProps.object !== nextProps.object) return false;
if (prevProps.array !== nextProps.array) return false;
return true;
}
<Component>
컴포넌트가 있습니다.React.memo
를 사용하여 컴포넌트를 래핑할 때 areEqual 함수
를 통해 props의 비교
를 수행합니다.areEqual 함수
는 prevProps
와 nextProps
를 받아 각 property들을 개별적으로 비교
합니다.props
객체의 각 property들이 변경되었는지를 체크
하여 변경이 있을 경우 false를 반환
하고, 모든 property들이 같을 경우 true를 반환
합니다.React.memo
는 props의 변경 여부를 판단
하여 리렌더링
을 수행할지 결정합니다.Memoization
은 특정 값을 저장해두고 재사용하는 테크닉을 의미합니다. 함수 컴포넌트는 매 렌더링마다 함수가 호출되기 때문에 이전 호출과 새로운 호출 간에 값을 공유할 수 없습니다. 하지만 리액트는 함수 컴포넌트에서 값을 memoization할 수 있는 API
를 제공합니다.
요약:
Memoization
은 값을 저장해두고 재사용하는 테크닉입니다.함수 컴포넌트
는 매 렌더링마다 호출되기 때문에 이전 호출과 새로운 호출 간에 값을 공유할 수 없습니다.memoization할 수 있는 API
를 제공합니다.리액트에서 제공하는 memoization 관련 API:
React.memo
: 컴포넌트의 출력 결과를 memoization하여 이전 결과와 동일한 경우 리렌더링을 방지합니다.useMemo
: 특정 계산 값을 memoization하여 의존성이 변경되지 않는 한 이전 값이 재사용됩니다.useCallback
: 특정 함수를 memoization하여 의존성이 변경되지 않는 한 이전 함수가 재사용됩니다.이러한 API를 활용하여 함수 컴포넌트 내에서 값을 memoization
할 수 있으며, 이는 성능 향상과 불필요한 계산을 방지하는 데 도움을 줍니다.
useMemo
는 리액트에서 값을 memoization
할 수 있도록 해주는 훅입니다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo
는 두 개의 인자를 받습니다. 첫 번째 인자는 콜백 함수
이며, 이 함수의 반환값이 메모
됩니다. 두 번째 인자는 의존성 배열
입니다. 의존성 배열에 포함된 값들 중 하나라도 이전 렌더링과 비교했을 때 변경되었다면, 메모된 값을 활용하는 대신에 새로운 값을 다시 계산합니다.
위의 예시에서 a와 b는 메모이제이션
을 위해 사용되는 두 개의 변수입니다. 만약 a나 b가 변경되면, 이전에 메모된 값을 사용하는 대신에 함수 내부의 코드가 다시 실행되어 새로운 값을 계산합니다. 이를 통해 의도한 결과를 유지할 수 있습니다.
const memorizedFunction = useMemo(() => () => console.log("Hello World"), []);
const memorizedFunction = useCallback(() => console.log("Hello World"), []); => 이게 더 좋음
// 1번 상황 - 복잡한 연산
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// 2번 상황 - 값의 동일성 보장
const memoizedObject = useMemo(() => ({ a, b }), [a, b]);
// useCallback과 React.memo와 함께 사용하는 경우
const memoizedCallback = useCallback(() => {
// 콜백 함수 로직
}, [dependency]);
const MemoizedComponent = React.memo(Component);
새로운 값을 만드는 연산이 복잡한 경우:
함수 컴포넌트의 이전 호출과 다음 호출 사이에 값의 동일성을 보장하고 싶은 경우:
useCallback과 React.memo와 함께 사용하는 경우:
최적화는 매력적인 주제이지만, 비용이 들고 추가적인 노력과 시간이 필요합니다. 따라서 항상 최적화를 할 때는 가치 창출과 관련하여 신중하게 판단해야 합니다.
주요 사항:
코드 예시 및 리스트 형태로 요약:
이러한 점들을 고려하여 최적화를 수행하면 효과적으로 성능 개선을 할 수 있습니다.