가수면

리액트에서 DOM API를 사용하면 안 되는 이유 본문

React/React

리액트에서 DOM API를 사용하면 안 되는 이유

니비앙 2023. 5. 20. 12:40

리액트에서 DOM메서드를 사용하면 안 된다는 것은 쉽게 접할 수 있는 이야기다.

그렇다면 왜 리액트에서 직접 DOM을 조작하면 안 되는 것일까?

이것을 알기 위해선 먼저 리액트가 변경 사항을 어떻게 업데이트시키는지 동작 원리를 살펴볼 필요가 있다.

재조정 (Reconciliation)

리액트에서는 상태의 불변성을 강조한다. 상태를 직접 수정하면 안 된다는 것이다.

만약 상태를 직접 수정하게 되면 어떻게 될까?

당연히 업데이트되지 않는다.

그 이유는 리액트 업데이트의 모든 출발이 setState로부터 시작되기 때문이다.

 

리액트는 두 개의 가상DOM을 활용해 변경사항을 업데이트한다. 기존의 가상DOM과 새로 만들어진 가상DOM을 비교하는 방식 말이다.

그리고 새로운 가상DOM을 만드는 역할을 해주는 게 바로 setState다.

 

setState를 통해 값이 변경되면 다음과 같은 과정을 거치게 된다.

1. batch를 통해 동일한 업데이트 사이클에 해당되는 상태 변경들은 하나로 묶여 큐에 추가된다.

2. 변경 사항들이 처리된다.

3. render가 호출되며 리렌더링이 발생하고 로운 가상 DOM 트리가 생성된다.

4. 기존 DOM 트리와 새로 생성된 DOM트리의 비교를 통해 변경 사항을 파악(diffing)하는 과정을 거친다.

5. 변경된 부분만 실제DOM에 업데이트 시킨다.

 

그리고 이 과정을 재조정(Reconciliation)이라고 한다.

왜 DOM API를 사용하면 안 되는 건가?

리액트가 가상DOM을 이용해 업데이트하는 과정을 재조정이라고 했다.

그러나 DOM API는 가상DOM이 아닌 실제DOM을 직접 조작하는 API다.

 

그렇다면 재조정과 이어서 생각해봤을 때 왜 리액트에서 사용하면 좋지 않은지 바로 몇 가지 유추해볼 수 있을 것이다.

 

1. 비효율적인 업데이트

리액트의 장점 중 하나는 가상DOM을 이용해 변경사항만을 효과적으로 업데이트할 수 있다는 것이다.

하지만 DOM API를 이용하면 그 이점 뿐만 아니라 그 외 리액트에서 제공하는 최적화 전략 전부를 못 누리게 된다.

 

2. 가상DOM과 불일치 문제

가상DOM은 변경된 사항을 실제DOM에 업데이트하지만, 반대로 실제DOM이 변경되었을 때 가상DOM이 저절로 업데이트되진 않는다.

예를 들어보자.

import React, { useState } from "react";

function App() {
  const [text, setText] = useState("hi");

  const handleChangeState = () => {
    setText("Changed");
  };

  const handleChangeDom = () => {
    document.getElementById("text").textContent = "Changed";
  };

  return (
    <div>
      <div id="text">{text}</div>
      <button onClick={handleChangeState}>setState로 업데이트</button>
      <button onClick={handleChangeDom}>DOM API로 업데이트</button>
    </div>
  );
}

export default App;

"setState로 업데이트" 버튼을 누른다면 text가 "Changed"로 바뀌며 리렌더링이 일어날 것이고, 한번 더 누른다면 동일한 상태값으로 판단되어 render가 호출되지 않으니 리렌더링도 업데이트도 일어나지 않는다.

하지만 만약 "DOM API로 업데이트" 버튼을 눌러 text를 "Changed"로 변경하는 경우라면 어떨까?

실제DOM이 변경되어 눈에 보이는 text는 "Changed"로 표시되고 있지만, 가상DOM의 text는 여전히 "hi"다.

그 증거로 누르면 가상DOM이 호출되며 리렌더링이 일어나는 것을 확인할 수 있다. 물론 실제DOM의 text는 이미 "Changed"이기 때문에 업데이트는 일어나지 않지만 말이다.

 

이렇듯 불필요한 리렌더링도 유발할 수 있지만, 결정적으로 서로 불일치 하는 경우가 생기고 사이드 이펙트가 발생할 확률이 생긴다.

 

3. 기타 라이브러리와 충돌 가능성

2번과 연관된 얘기로 DOM을 직접 조작하는 것은 다른 리액트 라이브러리 이용 시 문제를 일으킬 수 있다.

 

4. 테스팅의 어려움

React Testing Library와 Jest와 같은 테스팅 도구들은 주로 가상 DOM 환경에서 테스트를 수행하기 때문에 테스트하는 데 어려움이 발생할 수 있다.

 

5. 리액트 철학 위배

리액트에는 제어 컴포넌트와 비제어 컴포넌트라는 개념이 있다.

사용자의 상호작용을 상태값으로 제어하는 컴포넌트를 제어 컴포넌트라고 하는데, 리액트 팀은 대부분의 입력 양식을 제어 컴포넌트로 구성하기를 추천하고, 더 나아가 어떠한 값을 변경해야하거나 화면의 변화를 일으켜야하는 경우에도 상태값을 사용하기를 적극 추천하고 있다.

상태의 변경과 가상 DOM이 리액트의 핵심 개념이기 때문이다.

그럼 ref는 사용해도 되나?

상황에 따라 적절히 사용한다면 오히려 불필요한 리렌더링도 방지하고 문제될 것은 없겠지만, 사실 ref로 값을 바꿔도 되는지에 대한 대답으로는 '아니오'에 가깝다.

 

리액트 공식 문서에 이런 표현이 있다.

ref로 가상 DOM이랑 엮이는 것들은 되도록 조작하지 말라는 얘기를 볼 수 있다.

ref, DOM API와는 뭐가 다른가?

하지만 그럼에도 사용해 값을 바꾸게 될 경우, DOM API보다는 위험이 덜하다.

ref는 리액트에서 제공되는 API로 DOM 조작 시 리액트의 내부 메커니즘과 충돌할 우려가 적다.

그러니까, useEffect와 같은 리액트 라이프사이클 내에서 안전하게 DOM에 접근하기 위해 설계가 되어있기 때문에 리액트가 기대하는 방식으로 DOM 엘리먼트에 접근하게되므로 예상치 못한 동작이나 오류에 대한 부담이 적은 것이다.

Comments