본문 바로가기

TECH/React

리액트의 VirtualDom 이 어떻게 실제 DOM 에 반영할까 1 - Diffing 알고리즘

리액트를 사용하시는 이유 중 하나가 가상돔 때문이라고 생각이 드는데,

리액트의 가상DOM 이 실제 DOM과 어떻게 비교를 해서 실제 DOM에 반영을 하는지 궁금해서 찾아보게 된 내용입니다. 이 글을 통해서 왜 리액트에서 Key 를 유니크한 값으로 설정해야하는지에 대해 알게되실 수 있을 것 같습니다 🙏

Heuristic 알고리즘

VirtualDom과 실제 Dom 을 비교할 때 따지고 보면 전체 Dom을 순회하는 것인데 오히려 성능 저하가 일어나지 않을까라는 생각이 듭니다 🤔

이런 고민에 대해 리액트는 휴리스틱 알고리즘으로 해결했습니다.

해결법이 정확히 해결되는가에 대한 문제를 배제하고, 경험과 직관을 통해, 일반적으로 좋은 해결법이나, 보다 간단한 해결법을 찾고자 하는 방법이다.

휴리스틱에 대한 정의가 와닿지 않을 수 있는데, 중요하지 않다고 판단되는 것들은 가지치기하여 판단하지 않는다는 의미입니다. 그럼, 중요하다고 판단되는 것들은 어떤 것인지 궁금증이 생기게 되는데, 그 판단을 Diffing 알고리즘과 자식에 대한 재귀처리를 통해 하게 됩니다.

Diffing 알고리즘

1. Element 타입이 다른 경우

<section>
	<Chilren/> // section 이 div로 변경되면서 Children로 다시 mount된다.
</section>

1-1 같은 Element 임에도 속성이 달라지는 경우는?

 

동일한 html 태그임에도 속성이 달라지는 경우는 어떨까요 ?

( 속성이라고 하면 className 이나 style 속성 등이 있습니다. )

리액트는 재랜더링하지 않고, 변경된 내역(className) 만 갱신하게 됩니다.

자식에 대한 재귀처리

 

예를 들어서 아래와 같은 코드에 third 가 맨 앞에 추가 되었다고 가정해보겠습니다.

<ul>
  // <li>third</li>
  <li>first</li>
  <li>second</li>
</ul>

first와 second가 동일함에도, third 때문에 ul 의 자식 노드인 first,second,third를 모두 새로 그려야합니다. 이러한 문제에 대해 해결하

기 위해 key 를 도입하게 됩니다.

 

key 덕분에 key=2016 이 추가되어도, key=2015 요소를 다시 그리지 않고 이동만 시켜줘야함을 알게되는 것입니다. 그래서, 실제 DOM과 가상 DOM을 순회할 때 key가 동일한 엘리먼트라고 판단이 되면 휴리스틱 알고리즘을 통해 가지치기 하여 순회 대상이 되지 않을 수 있습니다.

 


개인적으로, 부모의 요소가 재랜더링 될 때 내부 속성(Child) 가 key 값이 있다면 그것도 재렌더 대상에서 제외될 수 있을까 궁금했다.

 

App.jsx

function App() {
  const [isOpen, setIsOpen] = useState(false);

  const handleButtonClick = () => {
    setIsOpen(!isOpen);
  };

  return (
    <>
      <button onClick={handleButtonClick}>isOpen 변경</button>
      <div>
        <Parent isOpen={isOpen} />
      </div>
    </>
  );
}

export default App;

Parent.jsx

const Parent = ({ isOpen }) => {
  useEffect(() => {
    console.log("parent Component mount");
  });

  return (
    <div>
      <h1>{isOpen ? "true" : "false"}값은 이것임</h1>
      {["yarn", "dev", "hello", "world"].map((text, index) => (
        <>
          {isOpen && !index && <Child key={`${text}${index}1`} />}
          <Child key={`${text}${index}2`} />
        </>
      ))}
    </div>
  );
};

export default Parent;

child.jsx

const Child = () => {
  useEffect(() => {
    console.log("child 컴포넌트 mount");
  });

  return <div>hello</div>;
};

export default Child;

 

 

parent는 App.jsx 에서 props로 state 값을 받고, child는 props에 따라 변경되는 것이 없게끔 해주었다.

그리고, App.jsx 에서 버튼을 두어, 그것을 클릭하면 setState를 통해 state가 변경되고, parent의 props 값이 업데이트 될 수 있도록 해주었다. child는 parent에게 받는 props 없이, 그냥 map 을 돌면서 key 값만 설정해주었다.

parent, child 각각의 컴포넌트에 useEffect에 depth를 안두어 컴포넌트 mount, update 되는 순간을 모두 체크했다.

 

 

결론은 부모의 컴포넌트가 재랜더링이 이루어지면, key 값과 무관하게 child 컴포넌트가 재랜더링 된다는 것이 결과였다.

지금 생각해보니까 부모 컴포넌트가 재랜더링 된다는 것은 element를 새로 그린다는 것인데, 그 대상에서 child 가 포함될 수 밖에 없는 것이였다. 그 당시에는 key 값이 있다면, 그 부분만 다른 곳으로 이동시켰다가 붙여넣는 것이라고 생각했다..! 😅

결론은 key 설정 < 부모의 변화입니다.


Reference ,

https://ko.reactjs.org/docs/reconciliation.html

 

재조정 (Reconciliation) – React

A JavaScript library for building user interfaces

ko.reactjs.org

 

'TECH > React' 카테고리의 다른 글

말줄임 + hover시 툴팁 표시 컴포넌트 만들기  (0) 2023.05.19