Allra Fintech

(리액트) Flux 패턴

React 상태 관리의 뿌리인 Flux 패턴과 단방향 데이터 흐름을 이해합니다.

상태 변경이 꼬이면 생기는 일

앱이 커지면 상태를 바꾸는 곳이 많아집니다.

  • 버튼을 누르면 목록이 바뀌고
  • 목록이 바뀌면 헤더의 숫자가 바뀌고
  • 숫자가 바뀌면 알림 아이콘이 바뀌고

이렇게 한 곳의 변경이 다른 곳의 변경을 연쇄적으로 일으키면, 어디서 뭐가 바뀌었는지 추적하기 어려워집니다.

프론트엔드에서 양방향 흐름이 문제가 된 이유

Spring MVC를 떠올려 봅시다.
요청이 Controller → Service → Model → View 순서로 흘러가고, View가 Model을 직접 수정하는 일은 없습니다.
서버 쪽 MVC는 이미 단방향에 가깝습니다.

문제는 프론트엔드에서 생겼습니다.
2010년대 초반, 프론트엔드에서는 View와 Model이 서로를 직접 바꾸는 구조가 흔했습니다.
입력 필드가 바뀌면 Model이 바뀌고, Model이 바뀌면 다른 View가 바뀌고, 그 View가 또 다른 Model을 바꾸는 식입니다.

페이스북도 자체 프론트엔드에서 이런 구조를 쓰고 있었고, 앱이 커지면서 연쇄 반응을 감당할 수 없게 되었습니다.
알림 카운트를 읽어도 사라지지 않는 버그가 반복되었고, 고칠 때마다 다른 곳이 깨졌습니다.

Flux의 답 — 단방향 흐름

페이스북이 제안한 해결책이 Flux 패턴입니다.
핵심 규칙은 하나입니다. 데이터는 한 방향으로만 흐른다.

구성 요소역할
Action"무슨 일이 일어났는지"를 설명하는 객체
DispatcherAction을 받아서 Store에 전달하는 중앙 허브
Store상태를 저장하고, Action에 따라 상태를 변경
ViewStore의 상태를 보고 화면을 그림 (React 컴포넌트)

View는 Store를 직접 수정하지 않습니다.
"이런 일이 일어났어"라는 Action을 보내고, 그 Action을 받은 Store가 스스로 상태를 바꿉니다.
상태가 바뀌면 View가 다시 그려집니다.

useState도 이미 단방향입니다

리액트 기초 트랙의 투두리스트를 다시 봅시다.

function TodoApp() {
  const [todos, setTodos] = useState<string[]>([]);
  const [text, setText] = useState('');

  function handleAdd() {
    setTodos([...todos, text]);
    setText('');
  }

  function handleRemove(index: number) {
    setTodos(todos.filter((_, i) => i !== index));
  }

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <button onClick={handleAdd}>추가</button>
      {todos.map((todo, i) => (
        <div key={i}>
          <span>{todo}</span>
          <button onClick={() => handleRemove(i)}>삭제</button>
        </div>
      ))}
    </div>
  );
}

이 코드의 데이터 흐름을 그려보면 이렇습니다.

사용자가 "추가" 클릭 → handleAdd 호출 → setTodos로 상태 변경 → 화면 다시 그리기

View가 상태를 직접 건드리지 않고, 함수를 통해 상태 변경을 요청합니다.
이미 단방향 흐름입니다. Flux의 구조와 대응시켜 보면 이렇습니다.

FluxuseState 코드
Action (무슨 일이 일어났는지)handleAdd() 호출
Dispatcher (전달)setTodos 호출
Store (상태)todos
View (화면)todos.map(...)

useState를 쓰는 것 자체가 이미 Flux의 단방향 흐름을 따르고 있는 겁니다.

도구가 달라도 원리는 같습니다

useState 외에도 useReducer, Zustand, Redux 같은 상태 관리 도구들이 있습니다.
사용법은 제각각이지만, 내부적으로는 전부 Flux 패턴으로 동작합니다.

사용자 조작 → 함수 호출(Action) → 상태 변경(Store) → 화면 다시 그리기(View)

어떤 도구를 쓰든 이 흐름은 바뀌지 않습니다.
나중에 새로운 상태 관리 라이브러리를 만나더라도, "결국 이 흐름이겠구나" 하고 읽으면 금방 익숙해집니다.

직접 해보기

  • 투두리스트에서 "추가" 버튼을 눌렀을 때 데이터가 어떤 순서로 흐르는지 써보기
  • setTodos를 거치지 않고 todos 배열을 직접 push하면 어떻게 되는지 확인해보기

다음 문서