가상 돔에서의 성능 최적화
React | | 10분 읽기

가상돔을 만들어 실제 돔에 반영할 바뀐 것을 찾을 때 ⇒ 렌더(재조정)
바뀐 부분을 실제 돔에 반영할 때 ⇒ 커밋
가상 돔을 만들어 실제 돔에 반영할 것을 찾을 때, 어떻게 반영을 하는지
요소의 타입 또는 속성을 변경
타입을 변경하면 해당 요소의 모든 자식 요소도 같이 변경
// flag가 1초마다 상태가 바뀐다고 가정
const [flag, setFlag] = useState(true);
if (flag) {
return (
<div>
<p>apple</p>
<p>banana</p>
</div>
)
} else {
return (
<span>
<p>apple</p>
<p>banana</p>
</span>
)
}flag가 1초마다 상태가 바뀐다고 가정할 때, 1초마다 렌더 단계에서 상태 업데이트 전과 후의 가상 DOM 트리를 비교하고 변경 사항들을 커밋 단계에서 실제 DOM에 적용한다.
// ReactDOM.render
const elementTree = {
type: 'div', // => span으로 변경되므로 모든 자식 요소 삭제하고 다시 추가
props: {
children: [
{
type: 'p' // 컴포넌트가 올 수도 있음, 이때 렌더링은 type이 다 문자열인 경우에만 이뤄짐
children: { children: 'apple' }
},
{
type: 'p'
children: { children: 'banana' }
},
]
}
}여기서 부모 타입이 div에서 span으로 바뀌므로 부모가 업데이트 되면서 모든 자식 요소가 제거되고 다시 추가된다.
요소의 속성값만 변경하면 해당하는 속성만 실제 돔에 반영 ex) class, style
// flag가 1초마다 상태가 바뀐다고 가정
return (
<div
className={flag ? 'yes' : 'no'}
style={{ color: 'black', background: flag ? 'red' : 'green' }}
>
<p>apple</p>
<p>banana</p>
</div>
)새로운 요소를 추가하거나 삭제하는 경우
- 맨 끝에 오렌지만 추가, 삭제
// flag가 1초마다 상태가 바뀐다고 가정
if (flag) {
return (
<div>
<p>apple</p>
<p>banana</p>
</div>
)
} else {
return (
<div>
<p>apple</p>
<p>banana</p>
<p>orange</p>
</div>
)
}-
중간에 요소를 추가하는 경우에는?
- 중간에 요소를 추가하면 뒤에 요소들이 변경되지 않았다는 것을 모름 .. 바보 .. 바나나가 안 바뀌었다는 것을 알기 위한 경우를 다 따지면 .. 기하급수적인 계산이 따를수도
⇒ 바나나에서 오렌지로 변경된 부분만 실제 돔에 반영
⇒ 마지막 요소에 바나나가 새로 생겼으니 추가!
// flag가 1초마다 상태가 바뀐다고 가정
if (flag) {
return (
<div>
<p>apple</p>
<p>banana</p>
</div>
)
} else {
return (
<div>
<p>apple</p>
<p>orange</p>
<p>banana</p>
</div>
)
}-
key 속성값을 이용하면 같은 키를 가지는 요소끼리만 비교해서 변경점을 찾음
⇒ 오렌지 키만 새로 입력됐으므로 오렌지만 실제 돔에 추가
// flag가 1초마다 상태가 바뀐다고 가정
if (flag) {
return (
<div>
<p key="apple">apple</p>
<p key="banana">banana</p>
</div>
)
} else {
return (
<div>
<p key="apple">apple</p>
<p key="orange">orange</p>
<p key="banana">banana</p>
</div>
)
}-
순서 정보인 index를 활용해도 되지만 배열 중간에 요소를 추가하거나 삭제하는 경우, 순서를 바꾸는 경우에는 비효율적으로 렌더링됨
-
순서 정보를 입력할 때는 배열의 끝에서만 원수를 추가/삭제하며 순서를 바꾸지 않는 경우에만 적용
⇒ 사과를 제외하고 나머지 요소가 1초마다 계속 실제 돔에 변경, 추가, 삭제되며 반영
⇒ 바나나 요소의 내용이 변경되지 않아도 순서가 바뀌었기 때문
⇒ 순서정보인 index로 key를 입력하는 것은 바람직하지 않다, 즉시 생성한 랜덤한 값도!
// flag가 1초마다 상태가 바뀐다고 가정
const fruit1 = [apple, banana]
const fruit2 = [apple, orange, banana]
const fruits = flag ? fruit1 : fruit2
return (
<div>
{fruits.map((fruit, index) => <p key={index}>{fruit}</p>)}
</div>
)리액트에서 상태 보존하고 재설정하기(재조정)
상태(state)는 렌더 트리의 위치에 연결
React 는 UI의 컴포넌트 구조에 대한 렌더 트리를 만든다.
React는 컴포넌트가 UI 트리에 있는 위치를 이용해 React가 가지고 있는 각 상태를 알맞은 컴포넌트와 연결합니다.
export default function App() {
return (
<div>
<Counter />
<Counter />
</div>
);
}
function Counter() {
const [score, setScore] = useState(0);
return (
<div>
<h1>{score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}이 둘은 각각 트리에서 자기 고유의 위치에 렌더링되어 있으므로 분리되어있는 카운터이다.
각 컴포넌트는 완전히 분리된 상태를 가지기 때문에 서로 영향을 끼치지 않는다.
같은 자리의 같은 컴포넌트는 state를 보존
export default function App() {
const [flag, setFlag] = useState(false);
return (
<div>
{flag ? (
<Counter flag={true} />
) : (
<Counter flag={false} />
)}
// flag 상태를 바꾸는 .. 어쩌구들 ..
</div>
);
}체크 박스를 선택하거나 선택 해제할 때 카운터 상태는 초기화되지 않고 상태가 보존된다.
root App 컴포넌트가 반환한 div의 첫 번째 자식으로 flag가 true이든 false이든 <Counter />는 같은 자리에 있기 때문이다.
타입이 같고, 최소한의 변경사항만 실제 DOM에 반영하기 위해 컴포넌트의 props만 새로 업데이트 한다.
같은 위치의 다른 컴포넌트는 state를 초기화
export default function App() {
const [isPaused, setIsPaused] = useState(false);
return (
<div>
{isPaused ? (
<p>See you later!</p>
) : (
<Counter /> // 상태를 제거
)}
</div>
);
}같은 자리의 다른 컴포넌트 타입으로 바꾸면 <div>가 Counter를 갖고 있다가 p로 바뀌면 React는 UI 트리에서 Counter와 그 상태를 제거한다.
즉, 리렌더링할 때 상태를 유지하고 싶다면, 트리 구조가 같아야한다.
같은 위치에서 state를 초기화하기 위해선?
export default function Scoreboard() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA ? (
<Counter person="Taylor" />
) : (
<Counter person="Sarah" />
)}
<button onClick={() => {
setIsPlayerA(!isPlayerA);
}}>
Next player!
</button>
</div>
);
}각자 다른 플레이어에 따라 점수를 보존하는 법?
- 다른 위치에 컴포넌트를 렌더링하기 (&& 연산자)
- 각 컴포넌트에
key로 명시적인 식별자를 제공하기