리액트를 배우다 보면 '가상돔'이라는 개념을 마주하게된다. 가상돔이란 말 그대로 가상으로 만들어진 돔을 뜻한다. 실제로 돔 API를 계속 호출하면 리페인팅, 리플로우로 인해 렌더링이 계속 발생하고, 성능이 떨어지게 된다.
이런 문제를 해결하기 위해 리액트에서는 가상돔을 사용하여 바뀐 부분만 변화시킨다. 최근에 알고리즘 공부에 푹 빠져있기도 하고(존잼...) 페이스북뿐만 아니라 인스타그램등 성능이 중요한 빅테크 기업에서도 리액트를 사용해 성능을 개선하고 있어, diffing 알고리즘의 실체가 너무 궁금해져 글을 쓰게되었다.
In React, displaying 1000 elements would require in the order of one billion comparisons. This is far too expensive. Instead, React implements a heuristic O(n) algorithm based on two assumptions:
1. Two elements of different types will produce different trees.
2. The developer can hint at which child elements may be stable across different renders with a key prop.
위의 설명은 리액트 공식홈페이지에 나와있는 설명이다.
즉, 요소 하나하나를 바꾸다보면 수십만번의 비교가 이루어져야하고, 이 작업은 상당히 비쌀 수 밖에 없다. 그래서 리액트에서는 두 개의 트리가 나온다는 점과 모든 개발자들이 key props를 통해 바뀌었다는 것을 명시할 수 있다는 점으로 해당 문제를 해결하고자 했다.
우선, 두 가지 돔 트리를 비교한다는 점은 위에서 명시했다. 그러면 다양한 케이스가 나올 수 있다.
1. 루트 노드가 아예 다른 경우
이 경우는 아예 처음부터 다르기 때문에 아예 새로운 트리를 구축하게된다.그리고 이전에 있던 state들은 모두 사라진다. 사실 상식적으로 생각해보면 당연한 것 같다. 부모가 바뀌는데 자식들이 남아있으면 그것도 이상하니까..ㅎㅎ
2. 타입은 같지만 속성이 다른 경우
속성(스타일)만 달라지는 경우에는 두 속성을 비교하여 바뀐 부분만 변화 시킨다.
3. 자식 요소가 바뀌는 경우
DOM동시에 두 리스트를 순회하고 바뀐 점이 있다면 그 부분만 재귀가 돌면서 처리해준다. 단, 첫 번째 자식부터 비교하며 변경하기 때문에 첫 자식 요소에 요소를 삽입하는 경우 처음부터 비교를 해야하기 때문에 성능상 좋지 않다. 이 문제를 리액트에서는 key를 통해 해결했다.
위의 문제점처럼 처음부터 하나하나 비교하는 것이 아니라, key속성을 통해 해당 Key가 존재하는지만 확인하면 된다. 다만 배열의 경우, index를 key로 사용했을때 재배열한다면 미묘하게 결과가 맞지 않을것이다. 그 이유는 index는 유일한 값이 아니기 때문에 오류가 날 수 밖에 없다. 그래서 key로는 id처럼 유일한 값을 사용해야 한다.
diffing 알고리즘에서는 heuristics알고리즘을 이용한 순회를 하게되고 변경된 결과를 업데이트 한다. 이 때 휴리스틱 알고리즘은 중요한 정보만 고려해서 최선의 값을 찾아내는 알고리즘이라 100% 정확하지 않다고 공홈에 적혀있다 (물론 결과는 똑같이 나온다)
diffing 알고리즘은 성능을 완벽하게 개선하지 못한다고 판단한 페이스북은 2017년 4월에 Reconciliation 에서 Fiber 을 도입했다.
공홈에서 올려준 코드와 미디엄에서 분석해놓은 걸 보면 볼수록 신기하다. 역시 페이스북이란 생각도 들고, 이러한 동작 원리 덕분에 성능이 개선된다는것이 정말 신기하다. 나도 정말 알고리즘을 잘해서 성능을 개선할 수 있는 core 개발자가 되고 싶다.