리액트 18의 주요 변화 : 자동 배치

HeeChan Kim·2024년 11월 24일
post-thumbnail

이 글은 모던 리액트 Deep Dive를 기반으로 작성되었습니다.



💎React 버전 변화

조만간 19버전이 나온다는 소식을 들었는데 19버전이 나오기전에 17버전과 18버전의 주요 변화를 보면서, 미리 변화를 감지해보자 :)!



💎자동 배치란?

자동 배치란 React 18에서 도입된 기능으로 React 18 이전에는 이벤트 핸들러 내부의 상태 업데이트만 배치 처리되었고, 다른 비동기 작업(예: setTimeout, fetch)에서는 배치가 자동으로 이루어지지 않았다. React 18에서는 모든 상태 업데이트를 자동 배치하여, 모든 상황에서 성능 최적화를 지원한다.

여러 상태 업데이트를 한 번의 렌더링으로 처리하는 것을 의미한다.



💎React 18 이전에는?

그렇다면, React 18 이전에는 어땠고 무슨 문제가 있었길래 React 18에서 새롭게 추가가 되었을까?

⚡️React 17 이하에서 배치는?

React 17까지는 동기적 상태 업데이트를 기본으로 했다. 이벤트 핸들러 내부에서는 상태 업데이트가 자동으로 배치 처리되었지만, 비동기 작업에서는 각 상태 업데이트가 개별적으로 처리되었다.

이벤트 핸들러 내부에는 자동 배치 처리, 그러나 비동기 작업에서는 개별적 처리

⚡️무슨 문제가 있었지?

비동기 작업에서는 자동 배치 처리가 불가능하기에 개별적 처리가 일어났고, 그에 따라 상태 업데이트마다 렌더링이 발생

상태 업데이트마다 렌더링이 발생된다는 건 성능상에 문제점

⚡️코드 예제

버튼 클릭 시 로딩 UI가 뜨며 데이터 로드 코드

import React, { useState } from "react";

function FetchDataExample() {
  const [data, setData] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  const fetchData = async () => {
    setIsLoading(true); // 로딩 시작
    const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
    const result = await response.json();
    setData(result); // 데이터 업데이트
    setIsLoading(false); // 로딩 종료
  };

  return (
    <div>
      <button onClick={fetchData}>Fetch Data</button>
      {isLoading && <p>Loading...</p>}
      {data && <p>{data.title}</p>}
    </div>
  );
}

export default FetchDataExample;

이 코드의 렌더링을 분석해보면

  • 🌸첫 번째 상태 업데이트 (setIsLoading(true)):
    isLoading 상태가 true로 업데이트되면서 첫 번째 렌더링 발생.
    화면에 "Loading..." 메시지가 표시

  • 🌸두 번째 상태 업데이트 (setData(result)):
    data 상태가 업데이트되면서 두 번째 렌더링 발생.
    로드된 데이터가 화면에 표시

  • 🌸세 번째 상태 업데이트 (setIsLoading(false)):
    isLoading 상태가 false로 업데이트되면서 세 번째 렌더링 발생.
    "Loading..." 메시지가 사라짐

    총 3번의 렌더링



💎React 18 이후에는?

위의 코드 예제와 같이 비동기함수에도 자동배치가 가능하여 단 1번의 렌더링만 일어난다.

⚡️동작원리

React 18에서는 상태 업데이트를 React 내부 큐에 저장하고, 이것들을 한 번의 렌더링으로 묶어서 처리한다. 그러나 17에서는 이 동작이 제한적으로 특정 조건에서만 이루어졌음. 즉, React에서 제어하는 이벤트 핸들러에서만 배치

setTimeout이나 Fetch, Axios 등 브라우저에서 동작하거나 React 내부에서 발생하는 비동기 작업에는 제한적이었음.



💎과연 항상 좋을까?

과연 비동기 함수도 자동 배치되어 하나의 렌더링으로 처리하는 것이 항상 좋지 않을 수도 있다. 결국 즉각적인 렌더링이 필요할 수도 있다. 이럴 땐 flushSync를 사용하여 배치를 강제 중단하고 즉시 렌더링을 수행할 수 있다.

⚡️flushSync 사용 O

import React, { useState } from "react";
import { flushSync } from "react-dom";

function ScrollSyncExample() {
  const [items, setItems] = useState([1, 2, 3]);

  const addItem = () => {
    flushSync(() => {
      setItems((prevItems) => [...prevItems, prevItems.length + 1]);
    });
    
    const container = document.getElementById("container");
    container.scrollTop = container.scrollHeight;
  };

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <div
        id="container"
        style={{
          maxHeight: "100px",
          overflowY: "auto",
          border: "1px solid black",
        }}
      >
        <ul>
          {items.map((item) => (
            <li key={item}>{Item ${item}}</li>
          ))}
        </ul>
      </div>
    </div>
  );
}

export default ScrollSyncExample; 

즉각적인 아이템 추가 렌더링으로 스크롤 위치의 조정이 정확해진다.

⚡️flushSync 사용 X

const addItem = () => {
  setItems((prevItems) => [...prevItems, prevItems.length + 1]);
  
  const container = document.getElementById("container");
  container.scrollTop = container.scrollHeight;
};

스크롤 위치가 새로 추가된 항목을 고려하지 않은 상태로 계산될 가능성이 있다.



💎결론

만약 그냥 자동배치에 대해 공부했다면 React 18에 대해서만 공부했을텐데, 17에서 18의 변화과정을 보니 양쪽 버전을 공부할 수 있어 좋은 것 같다.

벌써 React19라니 너무 빨라잉~

profile
디발자겸 개자이너

2개의 댓글

comment-user-thumbnail
2024년 11월 25일

무럭무럭자라라 리액트야 ~

답글 달기
comment-user-thumbnail
2024년 11월 25일

flushSync를 써보고 싶어요..!

답글 달기