LabHub
배우기 러닝패스 코스

상태 관리 — 라이브러리를 직접 만들어 본다 · 서버의 값을 복사해 두었다 · 이론

서버 상태는 상태가 아니라 캐시다

LabHub 에서 이어서 보기

한 줄 요약

서버가 진실을 쥔 값은 상태가 아니라 캐시다. 그 둘을 같은 통에 넣는 순간
"언제 낡았는가" 라는 질문을 코드 어디에도 적을 자리가 없어진다.

왜 이게 필요했나

스토어에 이런 모양을 넣어 본 적이 있을 것이다.

{ user: null, orders: [], loading: false, error: null }

여기에는 서로 다른 두 종류가 섞여 있다. userorders서버가 주인
값이다. 내가 안 건드려도 다른 사람이 바꾼다. 반면 모달이 열렸는지, 어떤 탭을
보고 있는지는 이 브라우저만 아는 값이다. 아무도 뒤에서 바꾸지 않는다.

둘을 같은 통에 넣으면 서버 쪽 값에만 필요한 질문들 — 언제 다시 받아야 하나,
지금 보는 것이 얼마나 낡았나, 두 화면이 같은 것을 보고 있나 — 을 적을 자리가
없다. 그래서 그 질문들이 화면 코드로 흩어진다. useEffect 안의 fetch,
"새로고침" 단추, 라우터 전환 때의 수동 초기화. 어느 것도 같은 규칙을 따르지
않아서, 한 화면만 낡은 값을 보여 주는 일이 생긴다.

React 공식 문서도 같은 이야기를 다른 각도에서 한다. 렌더 결과로 계산할 수 있는
값은 상태에 두지 말라는 것
([You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)),
그리고 상태는 중복 없이 구조화하라는 것
([상태 구조화](https://react.dev/learn/choosing-the-state-structure)).
서버 응답을 그대로 복사해 두는 것은 그 중복의 가장 큰 형태다.

어떻게 동작하나

가르는 기준은 "누가 바꾸는가"

질문은 하나면 된다. 이 값을 나 말고 다른 누가 바꿀 수 있는가?

| 질문 | 답이 '예' | 답이 '아니오' |
|---|---|---|
| 이름 | 서버 상태 | 클라이언트 상태 |
| 예시 | 주문 목록, 잔액, 남의 댓글 | 모달 열림, 선택한 탭, 입력 중인 글자 |
| 필요한 것 | 열쇠, 낡음 판정, 무효화 | 그냥 값 |
| 저장 위치 | 질의 캐시 | 스토어나 지역 상태 |

서버 상태에는 값 말고 열쇠(key)받은 시각이 함께 있어야 한다. 그
둘이 없으면 "다시 받아야 하나" 를 물을 수 없다.

낡음(stale)과 버림(gc)은 다른 눈금이다

많은 사람이 이 둘을 하나로 생각한다. 실제로는 축이 둘이다.

TanStack Query 의 기본값이 이 구분을 잘 보여 준다. 받아 온 데이터는 기본적으로
바로 낡은 것으로 친다(staleTime 0), 그래서 화면이 다시 마운트되거나 창에
초점이 돌아오면 배경에서 다시 받는다. 반면 아무도 안 보는 질의 결과는 5분 뒤
치운다(gcTime 기본 1000 * 60 * 5)
([Important Defaults](https://tanstack.com/query/latest/docs/framework/react/guides/important-defaults)).

이 기본값이 왜 그렇게 잡혔는지가 중요하다. "낡았다" 를 기본으로 두면 최악의
경우가 불필요한 요청 한 번이지만, "신선하다" 를 기본으로 두면 최악의 경우가
틀린 값을 계속 보여 주는 것이다. 둘 중 어느 쪽 실패가 싼지는 고민할 거리가
아니다.

무효화는 "지워라" 가 아니라 "다시 물어라"

주문을 하나 취소했다. 목록 캐시를 지우면 화면이 빈칸으로 깜박였다가 다시
채워진다. 대신 낡음 표시만 하고 값은 남겨 두면, 보던 것을 그대로 보여 주면서
뒤에서 새 값을 받아 조용히 갈아 끼운다. 같은 문서가 적듯 결과는 구조적으로
공유되어, 실제로 바뀐 것이 없으면 참조도 그대로 유지된다 — st-core 에서 만든
참조 비교와 메모이제이션이 여기서 그대로 값을 한다.

현장에서 만나는 모습

한 팀은 로그인 사용자 정보를 Redux 에 넣어 두고 앱 시작 때 한 번만 받았다.
사용자가 다른 탭에서 이름을 바꿔도 이 탭은 몇 시간 동안 옛 이름을 보여 줬고,
"로그아웃했다 다시 들어오세요" 가 공식 안내가 됐다. 서버 상태를 클라이언트
상태처럼 다룬 대가다.

반대 방향의 사고도 흔하다. 입력 중인 글자를 질의 캐시에 넣어 두었다가, 배경
재요청이 돌면서 사용자가 치고 있던 글자를 덮어썼다. 아무도 뒤에서 바꾸지 않는
값을 서버 상태 취급하면 이렇게 된다.

가르는 선이 흐릿할 때 쓰는 실전 기준이 하나 있다. **탭을 두 개 열어 놓고 한쪽에서
바꿨을 때 다른 쪽이 따라와야 하면 서버 상태다.** 따라올 필요가 없으면 클라이언트
상태다. 이 한 문장이면 대부분 갈린다.

경계에 걸리는 값도 있다. 장바구니는 로그인 전에는 클라이언트 상태였다가 로그인
뒤에는 서버 상태가 된다. 이럴 때 답은 "둘 다" 가 아니라 **한쪽을 주인으로 정하고
나머지를 그쪽으로 옮기는 것**이다. 두 사본을 동시에 진실로 두면 어느 쪽이 이기는지
아무도 설명하지 못하는 병합 코드가 생기고, 그 코드는 반드시 항목을 잃는다.

다음 퀴즈에서 확인할 것

어떤 값이 서버 상태인지 가르는 기준, 낡음과 버림이 왜 다른 눈금인지, 무효화를
"지우기" 로 짰을 때 화면에 무엇이 보이는지를 여섯 문항으로 확인한다.