랜더링 시점
- 컴포넌트에 예정된 상태 업데이트가 있을 경우.
- 컴포넌트에서 사용된 커스텀 훅의 예정된 업데이트가 있을 경우도 포함합니다.
- 부모 컴포넌트가 렌더링 되고 리렌더링에서 제외되는 기준에 충족하지 않을 경우. 제외되는 기준은 다음의 네 가지 조건을 모두 동시에 충족해야 합니다.
- 컴포넌트가 이전에 렌더링 되었어야 함. 즉, 이미 마운트 되었어야 함.
- 변경된 props(참조)가 없어야 함.
- 컴포넌트에서 사용하고 있는 context 값이 변경되지 않아야 함.
- 컴포넌트에 예정된 상태 업데이트가 없어야 함.
"렌더"의 뜻
"리액트가 컴포넌트를 렌더링 한다"
컴포넌트(UI에서 업데이트를 예약할 수 있도록 리액트에 의해 강화된 함수)는 리액트에 의해 호출되는 것입니다. 컴포넌트가 스스로의 상태를 능동적으로 변화시켰든 다른 변화 때문인지든 상관없이 리액트에 의해 호출되는 것입니다.
리액트의 핵심 디자인 원칙 중 하나에 따라 리액트는 UI 스케줄링 및 업데이트를 완전히 제어할 수 있습니다. 이는 몇 가지 의미를 가집니다.
- 컴포넌트가 만든 하나의 상태 업데이트가 반드시 하나의 렌더링(리액트에 의해 한 번의 컴포넌트 호출)으로 변환되는 것은 아닙니다. 그 이유는 다음과 같습니다.
- 리액트는 컴포넌트의 상태에 의미 있는 변화가 없다고 생각할 수 있습니다.(object.is에 의해 결정됩니다)
- 리액트는 상태 업데이트를 하나의 렌더 패스로 일괄 처리하려 합니다.
- But, 리액트는 promise가 resolve 되는 타이밍을 제어할 수 없기 때문에 promise에서의 상태 업데이트를 일괄 처리할 수 없습니다. setTimeout, setInterval 및 requestAnimatonFrame 같이 별도의 이벤트 루프 콜 스택에서 실행되는 네이티브 이벤트 핸들러도 마찬가지입니다.
- 리액트는 여러 렌더 패스로 작업을 분할할 수도 있습니다.(concurrent 리액트 기능)
- 리액트는 다양한 이유로 컴포넌트를 렌더링(즉, 함수 호출) 할 수 있기 때문에 컴포넌트를 한 번 렌더링 하는 것이 UI의 시각적 업데이트로 반드시 변환되지는 않습니다.
리액트가 props 변경을 탐지하는 데 사용하는 규칙 변경 방법
위에서 언급했던 것처럼, 기본적으로 리액트는 ===를 사용하여 이전 props와 현재 props를 비교합니다.
다행히도 리액트는 컴포넌트를 PureComponent로 만들어가 React.memo로 감쌀 경우, props 변경을 확인할 다른 방법을 제공합니다. 이런 경우에 리액트는 ===를 사용하여 참조가 변경되었는지 확인하는 대신, props의 모든 property에 얕은 비교를 수행합니다. 개념적으로 Object.keys(prevProps).some(key => prevProps[key] !== nextProps[key])와 유사합니다.
그러나 이러한 최적화는 악용되어서는 안되며, 리액트가 이를 기본 렌더링 동작으로 만들지 않은 데에는 이유가 있습니다. Dan Abramov는 props 비교에 드는 비용을 무시해서는 안 되며 더 나은 대안이 많이 있다고 거듭 언급했습니다.
두 가지 유형의 렌더링
- 능동적인 렌더링:
- 컴포넌트(혹은 컴포넌트에서 사용한 커스텀 훅)가 능동적으로 상태를 변경하기 위한 업데이트를 예약합니다.
- ReactDOM.render를 직접 호출합니다.
- 수동적인 렌더링: 부모 컴포넌트(들)이 상태 업데이트를 예약하고 컴포넌트가 렌더링 제외 기준을 충족하지 않습니다.
능동적인 렌더링
"능동적인 렌더링"은 제가 만든 단어입니다. "능동적인"이란 컴포넌트 자체(또는 컴포넌트가 사용하는 커스텀 훅)가 업데이트를 예약하기 위해 다음을 통해 능동적으로 상태를 변경하는 것을 의미합니다:
- 클래스 컴포넌트를 사용하는 경우, Component.prototype.setState(즉, this.setState)
- 함수형 컴포넌트를 사용하는 경우, 훅에 의해 발생한 dispatchAction:
- useReducer 훅의 dispatch 함수와 useState 훅의 상태 업데이트 함수는 모두 dispatchAction을 사용합니다.
업데이트를 능동적으로 예약하는 또 다른 방법은, ReactDOM.render를 직접 호출하는 것입니다. 다음은 리액트 공식 문서의 예입니다.
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(element, document.getElementById("root"));
}
setInterval(tick, 1000);
수동적인 렌더링
수동적인 렌더링은 리액트가 일부 부모 컴포넌트를 렌더링 하고 컴포넌트가 렌더링 제외 기준을 충족하지 않았을 때 발생합니다.
function Parent() {
return (
<div>
<Child />
</div>
);
}
위의 예시에서 부모 컴포넌트가 리액트에 의해 렌더링 되면, 자식 컴포넌트는 props에 참조/아이덴디티가 변경된 것(이 내용은 나중에 더 자세히 알아봅시다) 외에 의미 있는 변경사항이 없더라도 렌더링 됩니다.
위의 예시에서 부모 컴포넌트가 리액트에 의해 렌더링 되면, 자식 컴포넌트는 props에 참조/아이덴디티가 변경된 것(이 내용은 나중에 더 자세히 알아봅시다) 외에 의미 있는 변경사항이 없더라도 렌더링 됩니다.
렌더 단계에서 리액트는 재귀적으로 컴포넌트 트리를 탐색하여 컴포넌트를 렌더링 합니다. 따라서 만약 자식 컴포넌트에게 또 다른 자식 컴포넌트가 있다면 해당 자식 컴포넌트도 렌더링 됩니다. (계속해서 얘기하지만, 렌더링 제외 기준을 충족하지 않을 경우에 한합니다.)
function Child() {
return <GrandChild />; // 만약 `Child` 가 렌더링 되면 `GrandChild` 또한 렌더링 됩니다.
}
그러나 컴포넌트 중 하나가 렌더링 제외 기준을 충족한다면 리액트는 해당 컴포넌트를 렌더링 하지 않습니다.
모든 자식 컴포넌트가 동일하게 만들어지지 않았을 때
먼저 예를 살펴보겠습니다.
default function App() {
return (
<Parent lastChild={<ChildC />}>
<ChildB />
</Parent>
);
}
function Parent({ children, lastChild }) {
return (
<div className="parent">
<ChildA />
{children}
{lastChild}
</div>
);
}
function ChildA() {
return <div className="childA"></div>;
}
function ChildB() {
return <div className="childB"></div>;
}
function ChildC() {
return <div className="childC"></div>;
}
만약 Parent의 업데이트가 예정되어 있다면, 어떤 컴포넌트가 리렌더링 될까요?
ChildA는 리렌더링 되었습니다. 이는 부모 컴포넌트가 업데이트를 예약하고 리렌더링 되었다는 것을 알고 있기 때문에 놀라운 일은 아닙니다.
그러나 ChildA와 달리 ChildB와 ChildC는 리렌더링 되지 않았습니다. 그 이유는 ChildB와 ChildC가 렌더링 제외 기준을 충족했기 때문에 리액트가 렌더링을 건너 뛴 것입니다.
참고:
https://velog.io/@eunbinn/when-does-react-render-your-component#%ED%9D%90%EB%A6%84%EB%8F%84
'Framework > React' 카테고리의 다른 글
| e.preventDefault()와 e.stopPropagation() (0) | 2024.11.22 |
|---|---|
| setTimeout(function, 0) (0) | 2024.11.21 |
| React Hooks - useBeforeLeave (0) | 2024.11.19 |
| [React] 구글 애널리틱스 (1) | 2024.11.15 |
| [React] react-jsBarcode 자바스크립트로 바코드 이미지 생성하기 (2) | 2024.11.11 |