성능은 심각한 문제이며, 우리는 가능한 빠르게 앱을 만들어야한다.
이를 수행하는 방법은 최적화의 효과성뿐만 아니라, 코드의 복잡성(미래에 얼마나 빨리 개선 및 변경 할 수 있는지) 도
큰 영향을 미칩니다.
React 최적화에 대해 사람들이 많이 언급하는 것 중 하나는 리렌더링 최적화입니다.
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount((c) => c + 1)
return <button onClick={increment}>{count}</button>
}
우리가 위 버튼을 클릭할 때 마다 우리는 리런데링이 일어난다고 생각합니다. 리렌더링은 무엇일까요?
리렌더링이란 무엇인가요?
React가 처음 출시되었을 때, 많은 사람들이 Virtual DOM 덕분에 기존 UI 라이브러리에 비해 성능이 향상된 데 집중했습니다. 당시 인기 있는 기존 UI 라이브러리 대부분은 사용자가 직접 DOM을 업데이트하도록 하거나, 사용자를 대신하여 DOM을 업데이트했지만, 업데이트가 필요한 모든 구성 요소(또는 지시문)에 대해 순차적으로 업데이트했습니다.
기본적으로 다음과 같습니다.
- DOM을 업데이트하는 데 시간을 오래 걸린다는 점을 고려합니다.
element.appendChild(children)
- 성능 문제는 DOM 업데이트를 더 많이 할 수록 심해집니다.
- 필요한 모든 업데이트를 한 번에 수행하여 일부 성능 문제를 피할 수 있습니다.
- 모든 DOM 업데이트를 일괄 처리하면 DOM을 빠른 속도로 여러 번 업데이트 할 떄 발생하는 성능 문제를 줄일 수 있습니다.
그래서 React 팀은 DOM 업데이트를 일괄 처리하기로 했습니다. 즉, 30개의 DOM 업데이트가 발생하는 상태 변경이 있는 경우 하나씩 실행하는 대신 모두 한 번에 실행됩니다. 하지만 일괄 처리를 하려면 DOM 업데이트에 대한 소유권을 가져야 하므로 `React.createElement` DOM이 어떻게 보이기를 원하는지 설명해야 하며, 상태가 변경되면 React가 함수를 다시 호출하여 필요한 React 요소를 DOM에 렌더링합니다. 그런 다음 새 React 요소를 마지막으로 렌더링할 때 제공한 요소와 비교합니다. 이를 통해 어떤 DOM 업데이트를 해야 하는지 파악하고 가능한 가장 성능이 좋은 방식으로 업데이트 수행합니다. DOM을 업데이트하는 프로세스를 커밋단계라고 하는데, 렌더링한 React 요소를 가져와서 해당 업데이트를 DOM에 커밋하기 때문입니다.
이건 정말 중요한 구분이고, 여러분이 놓치길 바라지 않습니다.(그리고 이름이 약간 오해의 소지가 있으므로, 명확히 하고 싶습니다.) 렌더링은 React가 여러분의 함수를 호출하여 React 요소를 가져오는 것입니다. Render조정단계는 React가 해당 React 요소를 이전에 렌더링된 요소와 비교하는 것입니다. 커밋은 React가 이러한 차이점을 가져와 DOM을 업데이트하는 것 입니다.
명확하게 말해서
- 렌더링 단계 : React.createElement ( 요소 생성 )
- 조정 단계 : 이전 요소와 새 요소를 비교하는 과정
- 커밋 단계 : 실제 DOM을 업데이트합니다 ( 필요한 경우 )
일반적으로 이것의 가장 느린 부분은 DOM이 업데이트되는 커밋단계입니다. 하지만 모든 DOM 업데이트가 느린 것은 아닙니다. 사실 단순히 DOM이 느리다고 말하는 것은 약간의 오해의 소지가 있습니다. 왜냐하면 그보다 더 미묘하기 때문입니다. 이벤트 리스너 추가/제거와 같은 DOM 업데이트는 정말 빠릅니다. DOM의 느린 부분은 레이아웃입니다.
React의 batching 및 최적화된 코드 덕분에 우리는 이 문제에 대해 걱정하지 않고도 많은 함정을 피할 수 있지만, 가끔은 확실히 문제가 될 수 있습니다.
불필요한 리렌더링
구성 요소가 다시 렌더링된다고 해서 DOM 업데이트가 발생하는 것은 아닙니다.
다음은 그에 대한 간단한 인위적인 예입니다.
function Foo() {
return <div>FOO!</div>
}
function Counter() {
const [count, setCount] = React.useState(0)
const increment = () => setCount((c) => c + 1)
return (
<>
<Foo />
<button onClick={increment}>{count}</button>
</>
)
}
버튼을 클릭할 때마다 `Foo`함수가 호출되지만, 그것이 나타내는 DOM은 다시 렌더링되지 않습니다. 그 때문에 해당 구성 요소에 대한 DOM 업데이트가 전혀 없습니다. 이를 일반적으로 "불필요한 다시 렌더링" 이라고 합니다.
불행히도 렌더링과 커밋의 차이에 대한 혼란이 상당히 많았습니다. 많은 사람들이 "DOM은 느리다"는 사실을 알고 있지만(적어도 들어본 적이 있지만) 구성 요소가 다시 렌더링된다고 해서 DOM이 업데이트되는 것은 아니라는 사실을 깨닫지 못하는 사람들이 많습니다. 이러한 오해로 인해 구성 요소가 실제로 DOM을 업데이트할 필요가 없을 때 렌더링하는 것이 성능 병목 현상이라고 생각합니다.
이는 어떤 경우에는 확실히 문제가 될 수 있지만, 일반적으로 low-end 기기의 모바일 브라우저조차도 객체를 만드는 데(렌더링 단계) 매우 빠르게 이를 비교합니다(조정 단계). 그러면 다시 렌더링하는 데는 어떤 문제가 있을까요?
느린 렌더링
JavaScript가 렌더링 및 조정 단계를 처리하는 데 정말 빠르다는 점을 감안 할 때, 불필요한 리렌더링이 발생할 때 앱이 정지되는 이유는 무엇일까요?
그런 상황에서는 불필요한 리렌더링이 문제 일 수 있지만, 일반적으로 느린 렌더링과 관련된 문제일 가능성이 더 큽니다.
렌더링 단계에서 코드가 하는 일이 있어서 속도가 느려지는 것입니다. 먼저 진단하고 해결해야 합니다. 해당 문제를 해결한 후 앱을 다시 프로파일링하여 불필요한 리렌더링 문제가 여전히 있는지 확인할 수 있습니다.
사실, 느린 렌더링을 그대로 두고 대신 리렌더링을 줄이면 상황이 더 나빠질 수 있으며, 더 복잡한 코드를 작성하게 될 가능성이 큽니다.
아마 이게 제 요점을 잘 전달해 줄 겁니다.
눈을 깜빡일 때마다 얼굴을 주먹으로 때려야 한다고 가정해 봅시다. 😉🤛 🥴
"헉, 눈을 그렇게 많이 깜빡이지 않는게 낫겠다!" 라고 생각할지도 모릅니다. 제가 무슨 말을 하는지 아시나요?
눈을 깜빡일 때마다 얼굴을 주먹으로 때리는 것을 멈추라고 말씀드리는 겁니다! 그러니 나쁜 일이 일어나는 빈도(느린 렌더링)를 줄이는 대신, 나쁜 일을 없애고 눈이 원하는 만큼 눈을 깜빡이는(렌더링) 것을 자유롭게 생각해보세요. 😉
느린 렌더링을 수정하는 방법
그래서 우리는 먼저 느린 렌더링을 수정하고 싶다는 결론을 내렸습니다.
그런 다음 다시 렌더링하는 것이 여전히 문제인지 확인할 수 있습니다. 그러면 느린 렌더링을 어떻게 수정할까요?
종종 어떤 상호작용이 사용자에게 "janky"한 경험을 일으키는지 이미 알고 있습니다. 종종 탭을 열거나, 버튼을 클릭하거나, 텍스트 필드에 입력할 때 입니다.
다음은 당신이 하는 일입니다. 브라우저의 프로파일링 도구를 사용하여 앱을 프로파일링을 시작하고, 상호작용을 한 다음 다시 프로파일링을 중지합니다.
예를들어, ChromeDevTools 의 성능 탭을 통해 앱의 상호작용에서 Javascript 성능 병목 현상을 빠르게 파악하세요.
이 외에도 많은 내용이 있지만, 이러한 도구를 사용하는 방법을 배우는 것이 매우 유용합니다.
코드(코드의 종속성)의 어느 부분이 가장 오래 걸리는지 파악하고 문제를 해결한 후, 프로파일러로 다시 시도하고 개선(또는 퇴보)을 관찰하세요. React DevTools 프로파일러도 놓치지 마세요.
결론
렌더링의 100%가 필요한지 여부는 중요하지 않습니다. 렌더링이 느리다면 사용자에게 나쁜 경험을 제공할 것입니다. 눈을 깜빡일 때마다 스스로의 얼굴을 주먹으로 때리는 것을 멈추세요. 먼저 느린 렌더링을 수정하세요.
그런 다음 "불필요한 리렌더링"을 처리하세요(아직 필요한다면).
행운을 빕니다. Good Luck :)
Fix the slow render before you fix the re-render
Stay up to date Stay up to date All rights reserved © Kent C. Dodds 2025
kentcdodds.com