※ 렌더링이 일어나는 조건 - 컴포넌트가 처음으로 렌더링(마운트)을 하는 경우 - 자신 또는 부모 컴포넌트의 상태값(state, props, context 등)이 변한 경우
※ 코드로 이해하는 렌더링 (전체 코드 구조) - App.js: 진입점
- Parent.jsx: 부모 컴포넌트
- FirstChild.jsx: 자식 컴포넌트 - 1
- SecondChild.jsx : 자식 컴포넌트 - 2
※ 첫번째 테스트 : 상태값도 변하고 UI도 변하는 경우
- 최초 화면
- 최초 렌더링(마운트) 단계
- Set Flag 버튼 클릭을 통해 Parent 컴포넌트의 상태값 변경
- 변경 후 화면
- 결론 :useState로 선언한 상태값이 변경되었고, UI도 변경이 되어서 SecondChild 컴포넌트가 렌더링이 진행된것을 확인할 수 있다.
※ 두번째 테스트 : 상태값은 변했지만 UI에 변화가 없는 경우
- Parent.jsx 코드 수정 :flag 값과 관계없이 항상 FirstChild만 호출
- 최초 화면
- 최초 렌더링(마운트) 단계
- Set Flag 버튼 클릭을 통해 Parent 컴포넌트의 상태값 변경
- 변경 후 화면 (최초 화면과 동일)
- 결론 : useState로 선언한 상태값만 변경이 되고 UI는 동일하지만, Parent, FirstChild 컴포넌트가 리렌더링이 진행된것을 확인할 수 있다.
※ 세번째 테스트 : 버튼을 클릭하는 사용자 인터렉션은 있었지만 상태값, UI 모두 변화가 없는 경우
- Parent.jsx 코드 수정:버튼을 클릭 하더라도 상태값 유지
- 최초 화면
- 최초 렌더링(마운트) 단계
-Set Flag 버튼 클릭을 통해 Parent 컴포넌트의 상태값을 동일하게 변경 (false -> false)
- 변경 후 화면 (최초 화면과 동일, 상태값도 동일)
- 결론 :버튼을 클릭하였지만,useState로 선언한 상태값과 UI 변경이 없었기때문에 별도의 컴포넌트 리렌더링이 일어나지 않은 것을 확인할 수 있다.
React.memo
※ memo란? - 컴포넌트의 성능 최적화를 위해 사용한다. -컴포넌트의 props가 변경되지 않은 경우 리렌더링을 건너뛸 수 있다. - 이전 props와 현재 props를 얕은비교를 통해 비교한다.
※ 필요한 상황 - 동일한 props로 렌더링이 빈번하게 발생하는 컴포넌트일 경우 memo를 통해 성능상의 이점을 가져올 수 있다.
※ 불필요한 상황 - 위와 같은 상황을 제외하고는성능상의 이점을 가져올 수 없기 때문에 굳이필요하지 않다. - props가 자주 변하는 경우 props 비교는 대부분 false를 가져올 것이기때문에 비교가 무의미하다.
※ 기본 사용법
※ 예시 - Parent.jsx
import React, { useState } from "react";
import FirstChild from "./FirstChild";
import SecondChild from "./SecondChild";
export default function Parent() {
const [flag, setFlag] = useState([true, false, false, true]);
console.log("Parent Component Render");
const handleBtnClick = () => {
console.log("<<< Button Clicked!!! >>>");
// 버튼 클릭 시 Parent 컴포넌트 상태 변화
setFlag((prev) => prev.map((e) => !e));
};
return (
<div>
<FirstChild flag="Same props without memo" />
<SecondChild flag="Same props with memo" />
<button onClick={handleBtnClick}>Set Flag</button>
</div>
);
}
- FirstChild.jsx
import React from "react";
export default function FirstChild({ flag }) {
console.log("first child render");
return <div>FirstChild</div>;
}
- SecondChild.jsx
import React from "react";
function SecondChild({ flag }) {
console.log("second child render");
return <div>SecondChild</div>;
}
// memo 사용
export default React.memo(SecondChild);
- 화면
- 버튼 클릭 결과
- 결론 :memo로 감싼 SecondChild 컴포넌트는 동일한 props가 들어오는 경우 리렌더링이 되지 않는것을 확인할 수 있었다.
※ 주의사항 - 부모 컴포넌트가 리렌더링 되면서 함수를 새로 생성하기 때문에 이전에 넘긴 함수의 메모리 주소와 현재의 함수의 메모리 주소가 달라지게 된다. - 참조타입 같은경우 얕은 비교 시 참조하는 메모리 주소를 비교하게 되기 때문에 다른값으로 인식하게되어 memo를 사용하더라도 리렌더링이 일어나게 된다. (useState 안에서 선언된 참조타입은 해당 X)
useCallback
※ useCallback이란? - 컴포넌트가 리렌더링될 때마다 함수를 새로 생성하는 것을 방지하고, 이전에 생성한 함수를 재사용하여 불필요한 렌더링을 줄여준다. - 두번째로 넘겨주는 의존성 배열의 값이 변하지 않는 이상 항상 동일한 함수를 제공한다.
※ 필요한 상황 - 자식 컴포넌트에 함수를 props로 전달하는 상황에서 useCallback을 사용함으로써 자식 컴포넌트의 불필요한 리렌더링을 방지할 수 있다. - 큰 비용이 드는 함수일 경우 의존성 배열을 통해 특정 상황에서만 리렌더링이 될 수 있게 해줄수 있다.