useTransition의 내부 동작 원리

우혁·2024년 12월 10일
21

React

목록 보기
18/19

useTransition이란?

UI를 차단하지 않고 상태를 업데이트 할 수 있는 React Hook이다.

(리액트 19버전에서 변동사항이 있기 때문 리액트 19 공식문서를 보는 것을 권장드립니다)

const [isPending, startTransition] = useTransition(); // 매개변수를 받지 않는다.

useTransition은 정확히 두 개의 항목이 있는 배열을 반환한다.

  1. isPending 플래그: 대기 중인 Transition이 있는지 알려준다.
  2. startTransition 함수: 상태 업데이트를 Transition으로 표시할 수 있게 해주는 함수이다.

💡 React에서 Transition이란?
긴급하지 않는 상태 업데이트를 의미한다. React가 특정 상태 업데이트를 즉시 처리하지 않고, 우선순위가 낮은 작업으로 간주하여 처리할 수 있게 해준다.

  • UI 반응성 유지: 중요한 업데이트(ex. 사용자 입력)에 우선순위를 두어 UI가 항상 반응적으로 유지되도록 한다.
  • 부드러운 사용자 경험: 무거운 렌더링 작업을 뒤로 미루어 더 나은 사용자 경험(UX)를 제공한다.
  • 동시성 처리: 여러 상태 업데이트가 동시에 발생할 때, 중요한 업데이트를 먼저 처리할 수 있게 해준다.

Transition의 사용 예시

  • 사용자가 검색창에 입력할 때, 입력 필드의 업데이트는 즉시 이루어져야 한다.
  • 그러나 검색 결과를 표시하는 것은 Transition(낮은 우선순위)으로 처리할 수 있다. 이렇게 하면 사용자는 검색 결과가 로딩되는 동안에도 계속해서 입력할 수 있다.

startTransition 함수

useTransition이 반환하는 startTransition함수를 사용하면 state 업데이트를 Transition(낮은 우선순위)으로 표시할 수 있다.

function TabContainer(){
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState('A');
  
  const handleTabButtonClick = (tabName) => {
  	startTransition(() => {
      setTab(tabName);
    });
  }
}

💡 startTransition 내에서 호출되는 함수를 액션(Action)이라고 한다.
관례상 startTransition 내부에서 호출되는 모든 콜백(ex. 콜백 prop)은 action이라는 이름을 사용하거나 Action 접미사를 포함해야 한다.

function SubmitButton({ submitAction }) { // Action 접미사 사용
  const [isPending, startTransition] = useTransition();
  const handleSubmitButtonClick = () => {
    startTransition(() => {
      submitAction(); // Action 접미사 사용
    });
  }
}

매개변수

  • scope: 하나 이상의 set 함수를 호출하여 일부 state을 업데이트하는 함수이다.
    React는 매개변수 없이 scope를 즉시 호출하고 scope함수를 호출하는 동안 동기적으로 예약된 모든 state 업데이트를 Transition으로 표시한다.(non-blocking)
    현재로서는 await 이후의 set 함수를 추가적인 startTransition으로 감싸야 한다.(향후 수정 예정)
startTransition(async () => {
  await someAsyncFunction();
  // ✅ Using startTransition *after* await
  startTransition(() => {
    setPage('/about');
  });
});

주의 사항

  • useTransition은 Hook이기 때문에 컴포넌트나 커스텀 Hook 내부에서만 호출할 수 있다.
    만약 다른 곳에서 Transition을 시작해야 하는 경우, 독립형 startTransition 함수를 호출 해야한다.

  • startTransition에 전달되는 함수는 즉시 실행되며, 실행 중에 발생하는 모든 상태 업데이트를 Transition으로 표시한다.

  • Transition으로 표시된 state 업데이트는 다른 state 업데이트에 의해 중단된다.

  • Transition 업데이트는 텍스트 입력을 제어하는 데 사용할 수 없다.


useTransition의 내부 동작 원리

내부 동작 원리를 파악하는 코드는 React v19.0.0 버전의 코드입니다.

useTransition의 초기 마운트

function mountTransition(): [
  boolean, // isPending 플래그
  (callback: () => void, options?: StartTransitionOptions) => void // startTransition 함수
] {
  const stateHook = mountStateImpl((false: Thenable<boolean> | boolean)); // 상태 초기화

  // startTransition 함수 생성
  const start = startTransition.bind(
    null,
    currentlyRenderingFiber, // 현재 렌더링 중인 Fiber 노드(fiber)
    stateHook.queue, // 상태 업데이트 큐(queue)
    true, // 트랜지션 중의 상태(pendingState)
    false // 트랜지션 후의 상태(finishedState)
  );
  // Hook 저장
  const hook = mountWorkInProgressHook();
  hook.memoizedState = start;
  return [false, start];
}

mountTransition 함수는 useTransition Hook을 초기화한다.

트랜지션의 대기 상태를 나타내는 isPending 플래그와 트랜지션을 시작하는 startTransition 함수를 생성한다.(일부 매개변수는 바인딩되어 미리 설정된다)

생성된 startTransition 함수는 현재 Fiber 노드와 상태 큐에 바인딩되며 Hook의 memoizedState로 저장되어 재사용된다.

이 함수는 트랜지션을 시작할 때 사용되며 컴포넌트가 리렌더링되어도 동일한 함수 참조를 유지한다.


startTransition 함수의 동작 원리

export const enableAsyncActions = true; // 비동기 액선 플래그
export const enableTransitionTracing = false; // 트랜지션 추적 플래그
function startTransition<S>(
  fiber: Fiber, // 현재 작업중인 컴포넌트의 Fiber노드
  queue: UpdateQueue<S | Thenable<S>, BasicStateAction<S | Thenable<S>>>, // 상태 업데이트 큐
  pendingState: S, // 트랜지션 중의 상태
  finishedState: S, // 트랜지션 후의 상태
  callback: () => mixed, // 트랜지션 동안 실행될 콜백
  options?: StartTransitionOptions // 추가 옵션(ex. 트랜지션 이름 등)
): void {
  const previousPriority = getCurrentUpdatePriority(); // 현재 우선순위 저장
  setCurrentUpdatePriority(
    higherEventPriority(previousPriority, ContinuousEventPriority)
  ); // 현재 우선순위, ContinuousEventPriority 중 더 높은것 선택
  // ContinuousEventPriority는 연속적인 이벤트(드래그, 스크롤 등)에 사용되는 우선순위

  const prevTransition = ReactSharedInternals.T; // 현재 진행 중인 트랜지션 정보 저장
  const currentTransition: BatchConfigTransition = {}; // 새로운 트래지션 객체 생성

  if (enableAsyncActions) {
    // 현재 트랜지션 설정, 낙관적 업데이트 수행
    // 비동기 작업의 결과를 기다리지 않고 UI를 즉시 업데이트 하는 방식
    ReactSharedInternals.T = currentTransition;
    dispatchOptimisticSetState(fiber, false, queue, pendingState);
  } else {
    // 일반적인 상태 업데이트 수행
    // 적절한 업데이트 우선순위 요청
    ReactSharedInternals.T = null;
    dispatchSetStateInternal(
      fiber,
      queue,
      pendingState,
      requestUpdateLane(fiber)
    );
    ReactSharedInternals.T = currentTransition;
  }

  if (enableTransitionTracing) {
    // 트랜지션 추적(이름 할당, 시작 시간 기록)
    if (options !== undefined && options.name !== undefined) {
      currentTransition.name = options.name;
      currentTransition.startTime = now();
    }
  }

  try {
    if (enableAsyncActions) {
      const returnValue = callback(); // 콜백 실행, 결과 저장
      const onStartTransitionFinish = ReactSharedInternals.S;
      if (onStartTransitionFinish !== null) {
        // 트랜지션 시작을 추적할 때 사용
        onStartTransitionFinish(currentTransition, returnValue);
      }

      if (
        returnValue !== null &&
        typeof returnValue === "object" &&
        typeof returnValue.then === "function"
      ) {
        // 콜백의 결과가 Promise인 경우
        const thenable = ((returnValue: any): Thenable<mixed>);
        const thenableForFinishedState = chainThenableValue(
          thenable,
          finishedState
        ); // Promise 결과와 finishedState 연결
        dispatchSetStateInternal(
          fiber,
          queue,
          (thenableForFinishedState: any),
          requestUpdateLane(fiber)
        ); // 상태 업데이트
      } else {
        // 콜백의 결과가 Promise가 아닌경우
        // 바로 finishedState로 상태 업데이트
        dispatchSetStateInternal(
          fiber,
          queue,
          finishedState,
          requestUpdateLane(fiber)
        );
      }
    } else {
      // 비동기 액션이 비활성화인 경우
      // 먼저 finishedState로 상태 업데이트 후 콜백 실행
      dispatchSetStateInternal(
        fiber,
        queue,
        finishedState,
        requestUpdateLane(fiber)
      );
      callback();
    }
  } catch (error) {
    if (enableAsyncActions) {
      // 비동기 액션이 활성화인 경우 에러를 rejected 상태의 Thenable 객체로 변환
      const rejectedThenable: RejectedThenable<S> = {
        then() {},
        status: "rejected",
        reason: error,
      };
      dispatchSetStateInternal(
        fiber,
        queue,
        rejectedThenable,
        requestUpdateLane(fiber)
      ); // 상태 업데이트
    } else {
      throw error; // 비동기 액션이 비활성인 경우 에러를 그대로 던짐
    }
  } finally {
    setCurrentUpdatePriority(previousPriority); // 이전에 저장해둔 업데이트 우선순위 복원
    ReactSharedInternals.T = prevTransition; // 전역 트랜지션 상태를 이전 상태로 변경
  }
}

startTransition 함수는 React의 트랜지션의 시작과 종료를 관리한다.

현재 우선순위를 저장하고 새로운 우선순위를 설정한 후 콜백을 실행하며 콜백 실행 중 발생하는 상태 업데이트는 트랜지션으로 처리된다.

비동기 작업의 경우 Promise 처리를 통해 상태 업데이트를 관리한다. 에러 처리와 정리 작업도 수행하여 안정적인 트랜지션 처리를 보장한다.


낙관적 업데이트와 트랜지션 레인 관리

낙관적 상태 업데이트 처리

function dispatchOptimisticSetState<S, A>(
  fiber: Fiber, // 현재 작업중인 컴포넌트의 Fiber 노드
  throwIfDuringRender: boolean, // 렌더링 중 업데이트 시 에러를 던질지 여부
  queue: UpdateQueue<S, A>, // 상태 업데이트 큐
  action: A // 실행할 상태 업데이트 액션
): void {
  const transition = requestCurrentTransition(); // 현재 진행 중인 트랜지션 정보 가져오기

  const update: Update<S, A> = {
    lane: SyncLane, // 동기 레인으로 설정(높은 우선순위 부여)
    revertLane: requestTransitionLane(transition), // 트랜지션을 위한 레인 요청
    action, // 전달받은 상태 업데이트 액션
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };

  if (isRenderPhaseUpdate(fiber)) {
    // 렌더링 중인지 확인(UI를 그리고 있는지)
    if (throwIfDuringRender) {
      // 렌더링 중 업데이트가 허용되지 않는다면 에러 처리
      throw new Error("Cannot update optimistic state while rendering.");
    }
  } else {
    // 업데이트를 동시성 Hook 업데이트 큐에 추가
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, SyncLane);
    if (root !== null) {
      // 루트가 반환되면(업데이트가 필요한 경우)
      // 타이머를 시작하고 Fiber 업데이트를 스케줄링
      startUpdateTimerByLane(SyncLane);
      scheduleUpdateOnFiber(root, fiber, SyncLane);
    }
  }

  markUpdateInDevTools(fiber, SyncLane, action);
}

dispatchOptimisticSetState 함수는 낙관적 업데이트를 처리한다.

높은 우선순위(SyncLane)로 업데이트를 생성하여 UI를 즉시 반영하지만, 동시에 트랜지션 레인을 요청하여 후속 업데이트를 위한 준비를 한다.

이를 통해 사용자에게 즉각적인 피드백을 제공하면서도 실제 데이터 처리는 백그라운드에서 진행할 수 있도록 한다.


트랜지션 레인 요청 및 관리

export function claimNextTransitionLane(): Lane {
  const lane = nextTransitionLane; // 현재 사용가능한 다음 트랜지션 레인을 변수에 저장
  nextTransitionLane <<= 1; // 왼쪽으로 1비트 시프트(다음 트랜지션을 위해 레인을 준비하는 과정)
  if ((nextTransitionLane & TransitionLanes) === NoLanes) {
    // 만약 nextTransitionLane 모든 트랜지션 레인을 벗어났다면(NoLanes, 비트마스크가 모두 0)
    // 다시 첫번째 트랜지션 레인으로 리셋
    nextTransitionLane = TransitionLane1;
  }
  return lane; // 최종적으로 선택된 레인을 반환
}

export function requestTransitionLane(
  // 매개변수가 실제로 사용되지 않지만 이 함수는 트랜지션 컨텍스트 내에서 사용되어야 한다는 것을 알림
  transition: BatchConfigTransition | null
): Lane {
  if (currentEventTransitionLane === NoLane) {
    // 만약 현재 이벤트에 대한 트랜지션 레인이 설정되지 않았다면(NoLane)
    // 새 레인을 할당받는다.(새로운 이벤트의 시작을 의미)
    currentEventTransitionLane = claimNextTransitionLane();
  }
  // 현재 이벤트에 대한 트랜지션 레인 반환
  // 동일한 이벤트 내의 모든 트랜지션에 대해 재사용된다.
  return currentEventTransitionLane;
}

requestTransitionLane 함수는 현재 이벤트에 대한 트랜지션 레인을 관리한다.

만약 현재 이벤트에 할당된 레인이 없다면(새로운 이벤트인 경우, NoLane) 새로운 레인을 할당받는다.
그리고 이 레인을 반환하여 동일한 이벤트 내의 모든 트랜지션에 대해 일관된 우선순위를 제공한다.

이를 통해 React는 효율적으로 트랜지션을 관리하고 일관된 사용자 경험을 제공할 수 있다.


상태 업데이트 처리와 우선 순위 결정

내부 상태 업데이트 처리 및 최적화

function dispatchSetStateInternal<S, A>(
  fiber: Fiber, // 현재 작업 중인 컴포넌트의 Fiber 노드
  queue: UpdateQueue<S, A>, // 상태 업데이트 큐
  action: A, // 실행할 상태 업데이트 액션
  lane: Lane // 업데이트의 우선순위를 나타내는 레인
): boolean {
  // 업데이트 객체 생성
  const update: Update<S, A> = {
    lane,
    revertLane: NoLane,
    action,
    hasEagerState: false,
    eagerState: null,
    next: (null: any),
  };

  if (isRenderPhaseUpdate(fiber)) {
    // 렌더링 중인 경우(UI를 그리고 있는 경우) 업데이트를 렌더링 큐에 추가
    enqueueRenderPhaseUpdate(queue, update);
  } else {
    // 렌더링 단계가 아닌 경우
    const alternate = fiber.alternate;
    if (
      fiber.lanes === NoLanes &&
      (alternate === null || alternate.lanes === NoLanes)
    ) {
      // 현재 Fiber와 alternate Fiber에 진행 중인 업데이트가 없는 경우
      // 즉시 새 상태 계산(불필요한 리렌더링 방지)
      const lastRenderedReducer = queue.lastRenderedReducer;
      if (lastRenderedReducer !== null) {
        try {
          // 마지막에 사용된 리듀서 함수를 사용하여 새 상태 계산 시도
          const currentState: S = (queue.lastRenderedState: any);
          const eagerState = lastRenderedReducer(currentState, action);
          update.hasEagerState = true;
          update.eagerState = eagerState;
          if (is(eagerState, currentState)) {
            // 계산된 상태가 현재 상태와 같다면 bail out 처리하여 최적화
            enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
            return false;
          }
        } catch (error) {}
      }
    }

    // 업데이트를 동시성 Hook 업데이트 큐에 추가
    const root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
    if (root !== null) {
      // 루트가 반환되면(업데이트가 필요한 경우)
      // Fiber 업데이트를 스케줄링, 트랜지션 업데이트를 얽힘 처리
      scheduleUpdateOnFiber(root, fiber, lane);
      entangleTransitionUpdate(root, queue, lane);
      return true;
    }
  }
  return false;
}

dispatchSetStateInternal 함수는 React의 상태 업데이트 프로세스를 관리한다.

렌더링 단계에 따라 다르게 동작하며, 특히 렌더링 단계가 아닐 때는 즉시 새 상태를 계산하여 불필요한 리렌더링을 방지한다.

또한 업데이트를 큐에 추가하고 필요한 경우 Fiber 업데이트를 스케줄링한다.
이러한 최적화 과정을 통해 React는 효율적인 상태 관리와 렌더링을 수행한다.


업데이트 우선순위 레인 결정

export function requestUpdateLane(fiber: Fiber): Lane {
  const mode = fiber.mode;
  if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) {
    // Concurrent 모드가 아닌 레거시 모드라면 항상 동기 처리(SyncLane)
    return (SyncLane: Lane);
  } else if (
    (executionContext & RenderContext) !== NoContext &&
    workInProgressRootRenderLanes !== NoLanes
  ) {
    // 현재 렌더링 중이고, 진행 중인 렌더링 작업이 있다면
    // 현재 렌더링 중인 레인들 중 하나를 임의로 선택
    return pickArbitraryLane(workInProgressRootRenderLanes);
  }

  const transition = requestCurrentTransition();
  if (transition !== null) {
    // 현재 트랜지션이 있다면
    // 연관된 액션 스코프 레인(낙관적 UI 업데이트를 위해 사용되는 특별한 범위)이 있는지 확인
    const actionScopeLane = peekEntangledActionLane();
    // 액션 스코프 레인이 있다면 사용하고 없다면 새로운 트랜지션 레인 요청
    return actionScopeLane !== NoLane
      ? actionScopeLane
      : requestTransitionLane(transition);
  }

  // 위 모든 조건에 해당하지 않는 경우(일반적인 상태 업데이트 등) 현재 이벤트의 우선순위를 기반으로 레인 결정
  return eventPriorityToLane(resolveUpdatePriority());
}

requestUpdateLane 함수는 React 컴포넌트의 업데이트에 대한 우선순위(Lane)을 결정한다.

레거시 모드, 렌더링 중 업데이트, 트랜지션, 그리고 일반적인 상태 업데이트 등 다양한 상황에 따라 최적의 레인을 반환하여 React의 효율적인 업데이트 처리를 지원한다.


트랜지션 업데이트 얽힘 처리

function entangleTransitionUpdate<S, A>(
  root: FiberRoot, // 현재 작업 중인 Fiber 트리의 루트
  queue: UpdateQueue<S, A>, // 상태 업데이트 큐
  lane: Lane // 현재 업데이트의 우선순위를 나타내는 레인
): void {
  if (isTransitionLane(lane)) {
    // 현재 레인이 트랜지션 레인인 경우
    let queueLanes = queue.lanes;
    // 현재 큐의 레인과 대기 중인 레인을 교차하여(두 레인의 공통 부분 찾기) 실제로 처리해야 할 레인 결정
    queueLanes = intersectLanes(queueLanes, root.pendingLanes);
    // 새로운 큐 레인 생성(기존 큐 레인, 현재 업데이트 레인 병합)
    const newQueueLanes = mergeLanes(queueLanes, lane); 
    queue.lanes = newQueueLanes; // 새로운 큐 레인 설정
    // 루트에 이 레인들이 얽혀 있음(여러 업데이트가 서로 관련되어 있음)을 표시
    markRootEntangled(root, newQueueLanes); 
  }
}

entangleTransitionUpdate 함수는 트랜지션 업데이트의 레인을 관리한다.

현재 업데이트 레인과 기존 큐의 레인을 병합하고 이를 루트에 표시한다.
이러한 얽힘 처리를 통해 React는 여러 트랜지션 업데이트 간의 관계를 추적하고 효율적으로 처리할 수 있다.


useTransition의 업데이트 로직

function updateTransition(): [
  boolean, // isPending 플래그
  (callback: () => void, options?: StartTransitionOptions) => void // startTransition 함수
] {
  const [booleanOrThenable] = updateState(false); // 트랜지션 상태 초기화
  const hook = updateWorkInProgressHook(); // 훅 정보 가져오기
  const start = hook.memoizedState; // 시작 함수 가져오기
  // booleanOrThenable이 boolean이라면 그 값을 사용하고 아니라면 비동기 작업의 완료를 기다린다.
  // 이는 트랜지션이 진행 중인지(Pending) 여부를 결정한다.
  const isPending =
    typeof booleanOrThenable === "boolean"
      ? booleanOrThenable
        useThenable(booleanOrThenable);
  return [isPending, start];
}

updateTransition 함수는 useTransition Hook의 업데이트 로직을 처리한다.

현재 트랜지션을 확인하고, isPending 플래그를 업데이트한다. 비동기 작업의 경우 useThenable을 사용하여 처리한다.

이를 통해 React 컴포넌트는 트랜지션의 진행 상태를 추적하고 적절히 대응할 수 있다.


정리하기

useTransition은 React 애플리케이션에서 UI의 반응성을 유지하면서 복잡한 상태 업데이트를 관리하는 강력한 도구이다.

  1. 초기화: useTransition이 호출되면, isPending 플래그와 startTransition 함수를 생성한다.

  2. 우선순위 관리: startTransition 함수는 상태 업데이트를 낮은 우선순위로 표시합니다.

  3. 상태 업데이트 처리

    • 동기 처리: 일반적인 상태 업데이트는 즉시 처리되며, 필요한 경우 UI를 즉시 렌더링한다.

    • 비동기 처리: 트랜지션으로 표시된 업데이트는 다른 중요한 작업을 방해하지 않도록 지연시킨다. 비동기 작업이 완료되면 상태가 업데이트되고 UI를 리렌더링한다.

  4. 최적화: 불필요한 리렌더링을 방지하기 위해 현재 상태와 새로운 상태를 비교한다.(같다면 bail out 처리)

  5. 트랜지션 관리: 여러 트랜지션을 효율적으로 관리하기 위해 레인(Lane) 시스템을 사용한다.

동기 vs 비동기 처리

  • 동기 처리: dispatchSetStateInternal 함수를 사용하여 즉시 상태를 업데이트 하고 필요한 경우 리렌더링을 트리거한다.

  • 비동기 처리: dispatchOptimisticSetState 사용하여 낙관적 업데이트를 수행한다.
    이는 UI를 즉시 업데이트하지만, 실제 상태 변경은 비동기 작업이 완료된 후에 이루어진다.

이러한 매커니즘을 통해 useTransition은 복잡한 상태 업데이트를 효율적으로 관리하며, 사용자 경험을 크게 향상시킨다.


useTransition 사용 전

const updateQuantity = async (newQuantity) => {
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve(newQuantity);
    }, 2000)
  );
};

export default function App() {
  const [quantity, setQuantity] = useState(1);
  const [isPending, setIsPending] = useState(false);

  const updateQuantityAction = async (newQuantity) => {
    setIsPending(true);
    const savedQuantity = await updateQuantity(newQuantity);
    setIsPending(false);
    setQuantity(savedQuantity);
  };

  return (
    <div>
      <h1>Checkout</h1>
      <Item action={updateQuantityAction} />
      <hr />
      <Total quantity={quantity} isPending={isPending} />
    </div>
  );
}

수량을 빠르게 업데이트하면 요청이 진행 중일 때 계산 중이라는 UI가 표시되고, 수량을 클릭한 횟수만큼 총액이 여러번 업데이트 되는 것을 볼 수 있다.


useTransition 사용 후

const updateQuantity = async (newQuantity) => {
  return new Promise((resolve) =>
    setTimeout(() => {
      resolve(newQuantity);
    }, 2000)
  );
};

export default function App() {
  const [quantity, setQuantity] = useState(1);
  const [isPending, startTransition] = useTransition();

  const updateQuantityAction = async (newQuantity) => {
    startTransition(async () => {
      const savedQuantity = await updateQuantity(newQuantity);
      startTransition(() => {
        setQuantity(savedQuantity);
      });
    });
  };

  return (
    <div>
      <h1>Checkout</h1>
      <Item action={updateQuantityAction} />
      <hr />
      <Total quantity={quantity} isPending={isPending} />
    </div>
  );
}

수량을 빠르게 업데이트하면 요청이 진행 중일 때 계산 중이라는 UI가 표시되고, 최종 요청이 완료된 후에만 총액이 업데이트되는 것을 확인할 수 있다.

업데이트가 Action 내에서 이루어지기 때문에 요청이 진행 중일 때도 수량은 계속해서 업데이트할 수 있다.


🙃 도움이 되었던 자료들

startTransition - React 공식문서(v18.3.1)
useTransition - React 공식문서(v18.3.1)
useTransition - React 공식문서(v19)
How does useTransition() work internally in React?

profile
🏁

2개의 댓글

comment-user-thumbnail
6일 전

리액트 19에만 있는줄 알앗는데 18에도 있군요

1개의 답글