일반적으로 자식 컴포넌트를 렌더링하는 방법을 생각하면 아래 방식을 떠올립니다.
function App() {
return <Parent />;
}
function Parent() {
return <Child />
}
function Child() {
console.log('child!');
return <div>child</div>
}
위 방식의 경우에는 부모 컴포넌트인 Parent의 상태가 변경되어 Parent 컴포넌트가 리렌더링이 될 경우 Parent의 자식 컴포넌트인 Child 컴포넌트도 리렌더링이 되게 됩니다.
리렌더링을 일으키기 위해 상태를 사용하겠습니다.
function Parent() {
const [state, setState] = useState(0);
return <div onClick={() => setState(state + 1)}>
<Child />
{state}
</div>
}
onClick 이벤트 핸들러가 등록되어있는 div 태그를 클릭합니다.
그러면 {state} 부분이 웹 페이지에서 숫자로 1씩 증가하는 것을 볼 수 있을겁니다.
그리고 웹 브라우저의 콘솔창을 확인해보면 child!가 클릭시마다 찍히는 걸 볼 수 있습니다.
그런데 Child
컴포넌트는 아무런 변화가 없어서 리렌더링이 필요가 없는 상황입니다.
이게 가벼운 자식 컴포넌트면 성능상 문제가 되지는 않겠지만 상당히 헤비한 컴포넌트라면 불필요하게 리렌더링 되는 것은 문제가 될 수 있습니다. 그래서 이를 방지하기 위한 방법이 있는데 그건 자식 함수 컴포넌트를 prop으로 내리는 것입니다.
function App() {
return (
<Parent child={<Child />} />
)
}
function Parent({child}) {
const [state, setState] = useState(0);
return <div onClick={() => setState(state + 1)}>
{child}
{state}
</div>
}
prop으로 내렸을 경우 리렌더링이 어떻게 되는지 보겠습니다.
Child
컴포넌트가 리렌더링이 되지 않는 것을 볼 수 있습니다.
그 이유는 리액트에서는 리렌더링시 함수 컴포넌트의 리턴 값을 비교하여 다른 부분들을 리렌더링하는데 prop으로 던져진 경우에는 상위 스코프의 객체를 참조하는 상황이 되기 때문에 Child는 여전히 같습니다.
이제 위 방식이 불필요한 리렌더링을 방지하는 방법이란 걸 알았습니다.
하지만 컴포넌트를 prop으로 내리는건 가독성 면에서도 좋지 않다고 생각이 듭니다.
그래서 코드를 더 개선해보자면 아래와 같습니다.
function App() {
return (
<Parent>
<Child />
</Parent>
);
}
export const Parent = ({ children }) => {
const [state, setState] = useState(0);
return (
<div onClick={() => setState(state + 1)}>
{children}
<p>{state}</p>
</div>
);
};
children
도 prop이기 때문에 prop으로 컴포넌트를 내려준 것과 같은 상황이나 마찬가지입니다. 그래서 children
으로 내리는 경우에도 불필요한 리렌더링을 방지할 수 있습니다.