nested component를 피해야 하는 이유
Introduction
프로젝트를 진행하면서 불필요하게 re-render가 발생하는 문제를 만났다.
단순히 render를 하는 것 뿐 아니라 매 render시마다 DOM을 새롭게 다시 그리고 있던 것이 문제였는데, 원인은 nested component로 사용한 코드였다.
따라서 이 글에서는 nested component를 사용하는 것을 피해야 하는 이유를 설명하고자 한다.
Cheat Sheet
// Bad
const List = ({ hasWrapper, borderStyle, children }) => {
const Border = () => {
return <p style={borderStyle}>{children}</p>;
}
if (hasWrapper) {
return (
<Wrapper>
<Border />
</Wrapper>
);
}
return <Border/>;
};
// Good
const List = ({ hasWrapper, borderStyle, children }) => {
if (hasWrapper) {
return (
<Wrapper>
<Border borderStyle={borderStyle}>
{children}
</Border>
</Wrapper>
);
}
return (
<Border borderStyle={borderStyle}>
{children}
</Border>
);
};
const Border = ({ borderStyle, children }) => {
return <p style={borderStyle}>{children}</p>;
}
위와 같이 Border
의 코드를 재사용하고, List
의 props를 재사용하기 위해 nested component를 사용하게 될 수 있다.
하지만 이런 경우 children은 불필요하게 매 render시마다 새롭게 DOM이 그려지게 된다.
문제 케이스
import { useEffect, useState } from 'react'; export default function App() { const [count, setCount] = useState(0); useEffect(() => { setTimeout(() => { setCount(count + 1); console.log('update count: ', count + 1); }, 5000); }, [count]); return ( <List count={count}> <img src="https://picsum.photos/200/200" /> </List> ); } export const List = ({ count, children }) => { const Border = () => { return <div>{children}</div>; }; return ( <ul> <p>count: {count}</p> <Border>{children}</Border> </ul> ); };
문제 케이스는 같은 image url에서 다른 image를 보여주는 https://picsum.photos/200/200의 특성을 이용해서 DOM이 매번 새로 그려지는지 확인하고 있는 에시 코드이다.
코드를 보았을 때 App
컴포넌트에서 매 5초마다 count를 증가하고 있고, List 컴포넌트에는 영향을 끼치지 않는다. 하지만 React의 특성상 children이 re-render 되는건 당연하다.
하지만 여기서 주목할 점은 DOM까지 새롭게 그려지고 있는 것이다. React는 Virtual DOM을 사용해서 변경점이 없다면 browser paint를 스킵해야한다. 하지만 예시 코드는 그렇지 않은 것을 볼 수 있다.
원인은 Border
컴포넌트 때문이다. Border
컴포넌트는 nested component로 사용되고 있어서 매 render시마다 새로운 결과물을 만들고, children
이 새롭게 만들어지므로 React에서는 DOM이 동일한 것을 인지하지 못하고 업데이트 하는 것이다.
export const List = ({ children }) => {
// Every render, children is newly created.
const Border = () => {
return <div>{children}</div>;
};
return (
<ul>
<Border>{children}</Border>
</ul>
);
};