react.js

[React] flushSync

JJIMJJIM 2024. 6. 8. 21:01
728x90
반응형
SMALL

 

[React] flushSync 

React의 flushSync 함수는 비동기적으로 작동하는 React 업데이트를 동기적으로 처리하도록 강제하는 유틸리티입니다. 주로 사용자 인터페이스(UI)에서 즉각적인 반응이 필요할 때 사용됩니다.

 

 

주요 개념

  1. 비동기 업데이트:
    • 기본적으로 React의 상태 업데이트는 비동기적으로 일어납니다.
    • 업데이트하는 함수(이벤트)들을 큐에 모아두고 한번에 순서대로 실행시킵니다 (batch)
  2. 동기 업데이트:
    • flushSync를 사용하면 특정 상태 업데이트를 동기적으로 처리하여 즉시 화면에 반영됩니다.
    • 이는 특히 사용자 입력에 대한 즉각적인 피드백이 필요할 때 유용합니다.

 

 

필요 예시

아래는 todo리스트를 추가하면 추가한 할것 (리스트이 마지막)으로 스크롤링해주는 코드입니다.

import { useState, useRef } from 'react';

export default function TodoList() {
  const listRef = useRef(null);
  const [text, setText] = useState('');
  const [todos, setTodos] = useState(
    initialTodos
  );

  function handleAdd() {
    const newTodo = { id: nextId++, text: text };
    setText('');
    setTodos([ ...todos, newTodo]);
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

  return (
    <>
      <button onClick={handleAdd}>
        Add
      </button>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <ul ref={listRef}>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </>
  );
}

let nextId = 0;
let initialTodos = [];
for (let i = 0; i < 20; i++) {
  initialTodos.push({
    id: nextId++,
    text: 'Todo #' + (i + 1)
  });
}

 

 

그러나 기대했던 것 처럼 마지막 todo로 이동하지 않습니다 (최신생성한 todo)

이유는 추가버튼을 눌렀을 때 실행하는 setter 함수는 비동기적으로 실행되기에 바로 큐로 넣습니다.

그러나 그 아래에 useRef를 이용하여 dom을 스크롤한 이벤트는 동기적으로 실행되기 때문에 바로 실행됩니다.

즉, 스크롤이 먼저 이동하고, 큐에서 비동기 함수들이 실행. --> 스크롤 이동 후, 추가한 아이템이 리스트 마지막에 추가

그래서 항상 아이템하나만큼 스크롤이 부족하게 됩니다.

 

이를 해결하기 위해선

useEffect(() => {
	listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
}, [todos])

 

버츄어 돔을 실제 브라우저에 업데이트하고나서 실행되는 useEffect를 이용하여 해결할 수도 있습니다.

useRef도 초기생성에서 null로 초기화, 그리고 커밋전 null로 초기화 그리고 브라우저 업데이트 후 실제 dom에 연결되는데 useEffect는 그 이후에 실행되기 때문에, 변경되는 todos를 종속하게 하고 필요함수를 넣으면 실행 됩니다.

 

그러나 기존 코드에서 동기적으로 바로 실행시키고 싶다면

flushSync를 사용하는 방법이 있습니다.

 

flushSync(() => {
  setTodos([ ...todos, newTodo]);
});
listRef.current.lastChild.scrollIntoView();

 

아래는 전체코드

import { useState, useRef } from 'react';
import { flushSync } from 'react-dom';

export default function TodoList() {
  const listRef = useRef(null);
  const [text, setText] = useState('');
  const [todos, setTodos] = useState(
    initialTodos
  );

  function handleAdd() {
    const newTodo = { id: nextId++, text: text };
    flushSync(() => {
      setText('');
      setTodos([ ...todos, newTodo]);      
    });
    listRef.current.lastChild.scrollIntoView({
      behavior: 'smooth',
      block: 'nearest'
    });
  }

  return (
    <>
      <button onClick={handleAdd}>
        Add
      </button>
      <input
        value={text}
        onChange={e => setText(e.target.value)}
      />
      <ul ref={listRef}>
        {todos.map(todo => (
          <li key={todo.id}>{todo.text}</li>
        ))}
      </ul>
    </>
  );
}

let nextId = 0;
let initialTodos = [];
for (let i = 0; i < 20; i++) {
  initialTodos.push({
    id: nextId++,
    text: 'Todo #' + (i + 1)
  });
}

 

 

언제 사용해야 할까?

  • 즉각적인 사용자 피드백: 사용자 클릭, 입력 등의 이벤트에 즉각적으로 UI를 업데이트하여 빠른 피드백을 제공해야 할 때.
  • 애니메이션: 애니메이션이 정확하게 타이밍에 맞게 작동해야 할 때.
  • 순차적 업데이트: 특정 상태 업데이트가 다른 상태 업데이트보다 우선되어야 할 때.

 

 

주의 사항

  • 성능 고려: flushSync는 비동기 업데이트의 성능 최적화를 무시하고 동기적으로 처리하므로, 빈번한 사용은 성능에 영향을 미칠 수 있습니다.
  • 적절한 사용: 필요한 경우에만 사용하고, 가능한 한 최소화하여 사용합니다.

 

 

결론

flushSync는 React의 비동기 업데이트를 동기적으로 처리하여 즉각적인 UI 반응을 구현할 수 있는 유용한 도구입니다.

그러나 성능에 영향을 미칠 수 있으므로 필요한 경우에만 신중하게 사용해야 합니다.

이를 통해 사용자 경험을 개선할 수 있는 상황에서 효과적으로 활용할 수 있습니다.

 

 

 

 

useEffect vs flushSync

 

비교

동작 방식

  • flushSync 사용:
    • flushSync를 사용하면 상태 업데이트(setText와 setTodos)가 동기적으로 처리되어, UI가 즉시 업데이트됩니다.
    • 상태 업데이트가 즉시 반영되므로, listRef.current.lastChild.scrollIntoView가 호출될 때 최신 상태의 DOM을 참조할 수 있습니다.
  • useEffect 사용:
    • useEffect는 상태가 업데이트된 후에 실행됩니다.
    • handleAdd 함수에서 상태 업데이트를 수행하고, 상태가 변경되면 useEffect가 실행되어 스크롤을 조정합니다.

 

성능 및 사용 사례

  • 성능:
    • flushSync는 상태 업데이트를 동기적으로 처리하므로, 빈번하게 사용하면 성능에 영향을 미칠 수 있습니다.
    • useEffect는 비동기적 업데이트를 따르며, React의 최적화된 렌더링 사이클을 유지합니다.
  • 사용 사례:
    • flushSync는 즉각적인 UI 반응이 필요한 경우, 예를 들어 사용자 입력에 대한 빠른 피드백이 필요한 상황에서 유용합니다.
    • useEffect는 일반적인 상태 업데이트 후 후속 작업을 처리하는 데 적합하며, 더 일반적이고 효율적인 패턴입니다.

 

 

결론

두 접근 방식 모두 동일한 결과를 제공할 수 있지만, 그 동작 방식과 성능 특성에 따라 적절히 선택해야 합니다. 일반적으로는 useEffect를 사용하는 것이 더 효율적이며, 특별한 경우에만 flushSync를 사용하는 것이 좋습니다.

 

728x90
반응형
LIST