Archive.sakamoto
React QueryReduxZustandState ManagementPattern

실무 데이터 관리 패턴 (단일 캐시 + 파생 상태)

Sakamoto·

1. 핵심 개념

실무에서는 원본 데이터는 한 군데에 통으로 저장하고,

필터링·정렬·검색 같은 파생 상태는 컴포넌트 혹은 selector에서 처리하는 방식이 표준이에요.

원칙 ✅

  • 서버/클라이언트 구분 없이 원본 데이터는 단일 소스(Single Source of Truth)
  • 필터·정렬 등은 항상 UI에서 파생
  • JSX는 항상 파생 상태를 참조

2. 서버 상태 관리 패턴 (React Query / SWR / Apollo)

① 원본 데이터 캐싱

const { data: todos = [], isLoading } = useQuery({
  queryKey: ['todos'],
  queryFn: fetchTodos,  // 서버에서 전체 todos 한 번에 가져오기
});

② 파생 상태는 컴포넌트에서 처리

const filteredTodos = useMemo(() => {
  if (filterType === 'active') return todos.filter(t => !t.done);
  if (filterType === 'completed') return todos.filter(t => t.done);
  return todos;
}, [todos, filterType]);

③ JSX에 바인딩

return (
  <ul>
    {filteredTodos.map(todo => (
      <TodoItem key={todo.id} todo={todo} />
    ))}
  </ul>
);

3. 클라이언트 상태 관리 패턴 (Redux / Zustand / MobX)

① Redux 예시

// store.ts
const todosSlice = createSlice({
  name: 'todos',
  initialState: [] as Todo[],
  reducers: {
    setTodos: (_, action: PayloadAction<Todo[]>) => action.payload,
    toggleTodo: (state, action: PayloadAction<string>) => {
      const todo = state.find(t => t.id === action.payload);
      if (todo) todo.done = !todo.done;
    },
  },
});

// component.tsx
const todos = useSelector((state: RootState) => state.todos);
const filteredTodos = useMemo(() => {
  switch (filterType) {
    case 'active': return todos.filter(t => !t.done);
    case 'completed': return todos.filter(t => t.done);
    default: return todos;
  }
}, [todos, filterType]);

② MobX 예시

class TodoStore {
  @observable todos: Todo[] = [];

  @action setTodos(newTodos: Todo[]) {
    this.todos = newTodos;
  }

  @computed get activeTodos() {
    return this.todos.filter(t => !t.done);
  }

  @computed get completedTodos() {
    return this.todos.filter(t => t.done);
  }
}

③ Zustand 예시

const useTodoStore = create<TodoStore>((set) => ({
  todos: [],
  setTodos: (todos) => set({ todos }),
  toggleTodo: (id) =>
    set((state) => ({
      todos: state.todos.map(t =>
        t.id === id ? { ...t, done: !t.done } : t
      ),
    })),
}));

const todos = useTodoStore((s) => s.todos);
const filteredTodos = useMemo(() => {
  if (filterType === 'active') return todos.filter(t => !t.done);
  if (filterType === 'completed') return todos.filter(t => t.done);
  return todos;
}, [todos, filterType]);

4. 실무 패턴의 장점

항목단일 캐시 + 파생 상태 (실무 표준)쿼리별 별도 캐싱 (강의식)
데이터 일관성✅ 원본 한 곳 → 어디서든 동일 데이터❌ 각 캐시별 업데이트 필요
코드 복잡도setQueryData 한 번invalidateQueries 여러 번
성능✅ 한 번만 캐싱❌ 동일 데이터 중복 캐싱
유지보수성✅ 간단하고 직관적❌ 탭별 관리 필요
실무 활용도표준 패턴학습 단계에서만 주로 사용

5. 요약

실무 상태 관리 원칙 ✅

  1. 원본 데이터는 단일 소스에서 통으로 관리
  2. 필터링·정렬·검색 등은 컴포넌트에서 파생
  3. JSX는 항상 파생 상태만 참조
  4. 서버 상태(React Query)든 클라이언트 상태(Redux, Zustand, MobX)든 패턴은 동일