먼저 DOM 구조를 한번 살펴보면
출처 : https://dev.to/swarnaliroy94/introduction-to-react-real-dom-virtual-dom-363
이 같은 구조에서 변경이나 삭제를 하는 과정을 거치게 된다면 브라우저가 리플로우와 리페인트에 해당하는 재연산을 해야된다. 이 과정이 많아지게 된다면 결국에는 브라우저 속도가 느려지게 된다.
하나의 요소만 바꾸려고 해도 전체 요소가 다 리렌더링되기 때문에 그에 대한 비용도 더 많이 든다.
출처 : https://dev.to/iamusj/what-is-the-virtual-dom-2e0a
리액트에서는 virtual DOM을 이용해서 이전과 현재 간의 차이를 비교해서 변경된 부분이 있으면 그 부분만 실제 DOM에 반영하게 된다.
React는 트리 구조를 이용하며, 레벨 순회 방식으로 탐색을 한다.
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li> // third 추가
</ul>
이런식으로 third가 추가됐다고 하면 React에서는 first,second는 그대로 된다는 것을 인지하지 못하고 싹 다 리렌더링하게 된다.
알고리즘에서 배열보다 linkedList가 조금 더 좋다는게 이러한 이유인거 같기도 하다.
보통 ul
,li
태그를 사용하면서 이러한 에러를 마주치는 것도 이 때문이다.
<ul>
<li key="1">first</li>
<li key="2">second</li>
</ul>
<ul>
<li key="1">first</li>
<li key="2">second</li>
<li key="3">third</li> // third 추가
</ul>
이렇게 형제 간에서 유니크한 key 값을 부여하게 되면 전체가 리렌더링 되는 것을 막아줄 수 있다.
다만 index
를 key로 사용하게 되면 index는 그대로지만 해당 요소가 바뀌게 되면 전체 요소가 바뀌었다고 인지하고 새로운 DOM 트리를 만들어버리기 때문에 비효율적으로 동작한다.
보통 map
을 쓸 때 key로 index를 권장하지 않는 것도 그 이유다.
특정한 value
를 재사용하고자 할 때 쓰는 Hook
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
출처 : 공식문서
Hook 이름 그대로 Memoization 기법을 이용해서 기존에 했던 연산 결과를 메모리에 저장해두고 나중에 재활용한다.
useMemo와 달리 함수를 재사용하는 Hook
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useMemo
의 경우에는 deps에 있는 값이 변경되면 콜백을 통해 해당 함수가 실행되고
useCallback
의 경우 deps에 있는 값이 변경되면 해당 함수를 반환한다.