[React] Children Props 와 렌더링 최적화
요즘 최대 관심사는 [성능]이다. 그래서 최근 Reflow와 Repaint 관련 글을 보다가 브라우저 렌더링으로 관심사가 넘어가게 되었고 더 나아가 리엑트 렌더링과 최적화 관련해 글을 보던 중 한 가지 궁금한 점을 발견했다.
내 궁금증은 [10분 테코톡] 앨버의 리액트 렌더링 최적화]로부터 시작되었다.
- 렌더링 최적화 하는 방법으로는 `useMemo`, `React.memo`, `useCallback`을 사용할 수 있다. 다만, 이 작업은 값비싼 비용이 발생하기 때문에 많이 사용하게되면 성능에 오히려 좋지 않다.
- Childrend Props를 이용해서 리렌더링을 최적화할 수 있다.
- 불필요한 렌더링이 발생하지 않도록 처음부터 코드를 잘 작성하자. 근본적인 코드를 개선하자.
그 중, 가장 궁금했던 내용은 부모 컴포넌트가 변경되더라도 children props로 전달된 자식 컴포넌트는 리렌더링 되지 않는다는 것이다.
컴포넌트가 리렌더링 되는 조건은 다음과 같다.
- 상태(State)가 변경된 경우
- 부모로 부터 전달받은 속성(Props)이 변경된 경우
그런데, children도 props인데 왜 리렌더링 되지 않을까?
Children Props로 자식 컴포넌트 전달해보기
코드를 통해 확인해보자. ParentComponent의 Children Props로 ChildComponent를 전달하는 테스트 코드이다.
// 메인
function TestPage() {
return (
<ParentComponent>
<ChildComponent value='string' />
</ParentComponent>
);
}
// 자식 컴포넌트
function ChildComponent({ value }: Value) {
console.log("[Child Component] render");
return <p>{value}</p>;
}
// 부모 컴포넌트
function ParentComponent({ children }) {
console.log("[Parent Component] render");
const [state, setState] = useState(false);
const changeState = () => {
setState(!state);
};
return (
<>
<button onClick={changeState}>update state</button>
{children}
</>
);
}
위 코드를 실행한 후 update state 버튼을 눌러 상태를 변경해 보면, 콘솔에는 Parent Component만 메시지가 나타난다. 즉, Parent Component만 리렌더링 되고 Child Component는 리렌더링 되지 않았다.
나는 왜 자식 컴포넌트도 렌더링 된다고 생각했을까?
나는 다음과 같은 이유로 Parent Component가 리렌더링되면 Child Component도 리렌더링 될 것이라 생각했다.
- Children은 Props 속성 중 하나이다.
- 현재, 부모 컴포넌트의 상태나 props가 자식 컴포넌트에 전달되지 않고, 자식 컴포넌트가 그 정보에 의존하지 않다.
- 하지만, JSX 코드는 Babel 로 컴파일하면 React.createElement로 변환되고 React.createElement는 결국 React Element 객체(Object)를 반환한다.
- 객체는 변경 가능(mutable)한 값이기 때문에 Parent Component가 리렌더링 되면서 참조 값이 변경되어 새로 Child 컴포넌트를 생성할 것이다.
JavaScript로 변경해서 원인을 찾아보자
JSX는 결국 CreateElement로 변환되기 때문에 변경해서 확인해보기로 했다. 특정 JSX가 어떻게 변경되는지 확인해보고 싶다면 온라인 Babel 컴파일러를 통해 쉽게 확인할 수 있다.
컴파일된 코드에서 자질구래한 코드가 많아 확인하기 좀 어렵다면, 직접 CreateElement를 이용해 컴포넌트를 생성하는 방식으로 변경해 보면 좀더 이해하기 쉽다. 다음 코드는 TestPage 컴포넌트만 CreateElement로 변경한 코드이다.
찾았다! 이유!
변경해보니 child component는 변경이 안 되는 이유를 확실히 알 수 있었다. 자식 컴포넌트 생성은 ParentComponent가 아닌 TestPage에서 생성 후 전달해 주고 있었다. 왼쪽은 내가 생각했던 컴포넌트 생성 구조였고, 실제로는 오른쪽과 같았다.
이렇게 보면 더 알아보기 쉽다. 아래 두 함수는 결국 같다. 확실히 TestPage에서 자식 컴포넌트도 생성되는 것을 확인할 수 있다.
// 1.
function TestPage() {
const child = ChildComponent({ value: "string" });
return <ParentComponent children={child} />;
}
// 2.
function TestPage() {
return (
<ParentComponent>
<ChildComponent value='string' />
</ParentComponent>
);
}
결국 Child Component의 참조 값을 갖고 있는 ParentComponent는 변경되더라도 다음과 같은 이유로 변경되지 않았다.
- Child Component를 생성한 부모 컴포넌트인 Test Page 컴포넌트는 리렌더링되지 않았다.
- Child Component는 상수다.
- Child Component는 Parent Component의 상태와 속성에 의존적이지 않기 때문에 변경되지 않는다.
Children Props 관련된 자세한 내용은 아래 글들을 통해좀더 자세히 확인해볼 수 있다.
- The mystrey of React Element, children, parents and re-renders
- React Constant Element transformation support
결론
나는 ChildComponent는 ParentComponent에서 생성되지도 않는데 그 사실을 잊고 Children Props는 속성(props)인데 객체 값이니깐 무조건 변경되겠다는 착각을 했었다.
이유를 찾아보니, ChildComponent를 생성하는 위치가 달랐던... 생각보다 너무 간단했고, children 속성은 어려운 친구👯♀️였다. 덕분에 리액트 렌더링 과정에 대해서도 다시 공부하게되는 시간을 갖게 되어서 좋았다.