Sitemap

(번역) {상태 전이} = f(상태)

Ricki
5 min readApr 9, 2025

원문: {transitions} = f(state)

요약: 리액트 컴포넌트 트리를 상태 기계(state machine)로 모델링하는 것은 비동기 업데이트와 리액트의 동시성 기능의 의미를 명확히 하는 데 도움이 됩니다.

상태 기계는 시간에 따라 변화하는 상태 기반 시스템을 설명하는 정형화된 방법입니다. 일반적으로 시스템이 가질 수 있는 상태를 명시적으로 정의하고, 각 상태에서 유효한 전이(시스템을 새로운 상태로 만드는 업데이트)를 나열한 상태 전이 테이블을 포함합니다.

리액트 애플리케이션은 상태 기계를 모델링하는 것으로 생각할 수 있습니다. 각 렌더링은 상태를 입력으로 받아 해당 상태에 대한 UI를 생성합니다. 이는 리액트의 유명한 UI = f(state) 멘탈 모델입니다. 그러나 복잡한 애플리케이션에서는 전이 테이블을 명시적으로 나열하는 것이 현실적이지 않을 수 있습니다. 가능한 상태의 수가 무한하거나 매우 많을 때는 전이 테이블로 충분하지 않으며, 이때는 상태를 입력으로 받아 해당 상태에서 유효한 전이의 집합을 반환하는 개념적 함수를 정의해야 합니다.

사실, 각 리액트 애플리케이션은 이미 이러한 함수를 정의하고 있습니다. 개발자는 컴포넌트가 특정 상태에서 렌더링될 때 DOM에 바인딩하는 이벤트 핸들러를 통해 각 상태에서 사용자가 수행할 수 있는 유효한 전이를 암묵적으로 정의합니다.

따라서 리액트 컴포넌트 트리는 상태에 따른 UI를 정의할 뿐만 아니라, 동일한 함수로 상태의 유효한 전이도 정의합니다. 간단히 표현해보면 다음과 같습니다.

{상태 전이} = f(상태)

예시

할 일 관리 애플리케이션에서 complete(todoId)라는 상태 업데이트를 통해 특정 일을 완료로 표시할 수 있다고 가정해 보겠습니다. 그러나 이미 삭제된 일을 "complete"로 표시하면 에러가 발생할 수 있습니다. 최선의 경우 사용자에게 에러 팝업이 표시되겠지만, 최악의 경우 "TypeError: Cannot set properties of undefined"와 같은 자바스크립트 에러가 발생할 수 있습니다. 상태 기계의 관점에서 보면, complete(x)는 해당 ID x의 할 일이 존재하지 않는 상태에서는 유효한 전이가 아닙니다.

리액트의 프로그래밍 모델은 이러한 에러를 방지하는 직관적인 방법을 제공합니다. 너무 직관적이어서 이런 종류의 오류를 방지할 필요성에 대해 생각조차 하지 않는 경우가 많습니다. 예를 들어, <Todo /> 컴포넌트의 "Complete" 버튼을 통해서만 complete(todoId) 업데이트를 트리거할 수 있다면, 할 일이 삭제될 때 상태에서 제거함으로써 해당 할 일의 "Complete" 버튼이 렌더링되지 않도록 자동으로 보장됩니다. 이는 사용자가 유효하지 않은 업데이트를 수행하지 못하도록 합니다.

이러한 동작은 자바스크립트가 단일 스레드로 동작하며 UI를 차단하기 때문입니다. 애플리케이션의 상태를 동기적으로 업데이트할 때, 브라우저가 추가적인 사용자 입력을 처리하기 전에 이러한 상태 변경을 UI에 반영할 것이라고 신뢰할 수 있습니다.

비동기 업데이트

비동기 업데이트의 경우, 이러한 방어 장치를 더 이상 자동으로 얻을 수 없습니다. 할 일을 삭제하기 위해 API에 네트워크 요청을 보내야 하는 상황을 가정해보겠습니다. API 요청이 완료된 이후에야 상태를 업데이트하도록 구현했다면, 요청이 진행되는 동안 이미 삭제된 할 일의 “Complete” 버튼이 여전히 화면에 남아 클릭 가능한 상태로 남아있게 됩니다. 이는 사용자가 유효하지 않은 상태 전이를 시도할 수 있는 문제를 발생시킵니다.

사용자가 유효하지 않은 업데이트를 수행하지 못하게 하는 보호 장치가 더 이상 자동으로 제공되지 않으므로, 이를 방지하기 위한 추가적인 작업이 필요합니다. 다음과 같은 두 가지 좋은 방법이 있습니다.

  1. 네트워크 요청이 완료되기 전에 로컬 상태에서 할 일을 즉시(낙관적으로) 삭제하여, 네트워크 요청이 진행되는 동안 해당 할 일이 렌더링되지 않도록 합니다.
  2. 네트워크 요청이 진행되는 동안 해당 할 일을 “pending” 상태로 표시하고, 이 기간 동안 “Complete” 버튼을 비활성화합니다.

이러한 낙관적 업데이트나 대기 상태 표시를 동기적으로 처리하면, {상태 전이} = f(상태) 모델에 따라 사용자가 유효하지 않은 업데이트를 시도하지 못하도록 보장할 수 있습니다.

동시 업데이트

리액트의 일부 동시성 모드 기능, 예를 들어 startTransition은 상태 업데이트를 의도적으로 DOM에 즉시 반영하지 않고 사용자 입력을 먼저 처리할 수 있도록 합니다. 비동기 업데이트와 마찬가지로, 이 경우에도 UI가 유효하지 않은 업데이트를 사용자가 일시적으로 수행할 수 있도록 허용하는 시간의 틈이 발생합니다.

따라서, 상태 업데이트로 인해 유효한 전이가 변경될 수 있는 경우에는 동기적으로 처리되는 낙관적 업데이트나 대기 상태 표시를 함께 사용해야 합니다.

이러한 이유로 리액트의 많은 동시성 기능은 isPending 플래그(useTransition, useActionState, useFormStatus)나 우선순위가 낮은 업데이트와 함께 사용하는 낙관적 업데이트(useOptimistic)에 대한 내장 지원을 제공합니다.

결론

{상태 전이} = f(상태)는 애플리케이션이 사용자가 유효하지 않은 업데이트를 수행하지 못하도록 방지하는 방법을 이해하는 데 유용한 멘탈 모델입니다. 이를 통해 어떤 업데이트가 비동기적 또는 동시적으로 안전하게 적용될 수 있는지, 그리고 어떤 업데이트가 동기적인 업데이트와 함께 사용되어야 하는지 명확하게 파악할 수 있습니다. 또한 리액트 컴포넌트가 이러한 보호 장치를 구현하는 데 어떤 역할을 하는지, 예를 들어 상태가 대기 중일 때 어떤 컴포넌트가 다르게 렌더링되어야 하는지 이해하는 데 도움이 됩니다.

이러한 통찰을 제공해 준 Evan Yeung에게 감사드리며, 이 글의 초기 초안을 검토해 준 Joe Savona, Jordan Brown, Jack Pope, Rick Hanlon에게도 감사의 말씀을 전합니다.

--

--

Ricki
Ricki

Written by Ricki

Life is tons of discipline.

No responses yet