[React] flushSync

    728x90
    반응형

     

    [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
    반응형

    댓글