[배우는 중] useState를 너무 많이 뒀다 — 파생 상태 이야기
React를 쓰긴 쓰는데 상태 설계에서 자꾸 꼬였다. 무엇을 state로 두고 무엇을 계산할지, 막혔던 지점과 이해한 과정을 그대로 남긴다.
배우는 중(Learning log) — 완성된 정리가 아니라, 혼자 공부하다 막힌 지점과 그걸 넘은 과정을 그대로 남기는 시리즈입니다. 지금 제 이해라서 틀릴 수 있고, 나중에 알게 되면 정정 노트를 답니다.
React를 실무에서 “쓰긴” 씁니다.
그런데 요즘 개념을 다시 파는 중인데, 상태 설계에서 매번 걸려 넘어져요.
검색+필터 목록.
검색어로 거르고, 정렬 바꾸고, “결과 N개”를 보여주는 흔한 화면이었어요.
버릇처럼 이렇게 시작했습니다.
const [query, setQuery] = useState('');
const [sort, setSort] = useState('recent');
const [filtered, setFiltered] = useState(items); // ← 여기가 문제였다
const [count, setCount] = useState(items.length); // ← 이것도
query가 바뀌면 filtered를 다시 계산해서 setFiltered, 이어서 setCount. 그런 식으로요.
그런데 자꾸 어긋났어요.
검색어를 지웠는데 목록은 이전 결과 그대로.
”결과 3개”라는데 화면엔 5개.
useEffect로 query를 감시해서 filtered를 맞추려다 보니, 상태 하나 바꿀 때마다 다른 상태를 손으로 맞추는 코드가 계속 늘었어요.
머릿속엔 같은 질문만 맴돌았어요.
값은 다 있는데, 왜 안 맞지.
한참 헤매다 이런 질문을 만났어요.
”filtered는 items·query·sort만 있으면 매번 계산할 수 있는 값 아닌가?”
그때 걸렸어요.
filtered랑 count는 내가 따로 저장할 값이 아니라, 다른 상태에서 계산되는 값이었어요.
이런 걸 파생 상태(derived state)라고 부르더라고요.
원칙은 하나예요 — 렌더 중에 계산할 수 있는 건 state로 두지 않는다.
그래서 이렇게 바꿨어요.
const [query, setQuery] = useState('');
const [sort, setSort] = useState('recent');
// state가 아니라 렌더할 때마다 계산
const filtered = items
.filter((it) => it.name.includes(query))
.sort(sortBy(sort));
const count = filtered.length;
filtered를 state에서 빼자 동기화 코드가 통째로 사라졌어요.
useEffect도 필요 없고요.
query나 sort가 바뀌면 리렌더가 일어나고, 그때 filtered는 최신 값으로 다시 계산되니까 어긋날 수가 없어요.
돌아보니 제 오해는 “화면에 보이는 값은 다 state에 담아야 한다”였어요.
아니었어요.
state는 이것만 있으면 나머지가 전부 계산되는 최소한의 진실이면 되고, 나머지는 거기서 뽑아내는 거였어요.
퍼블리셔로 치면 소스 하나만 두고 나머지는 거기서 파생시키는 감각이랑 같았고요.
남은 질문도 있어요.
매 렌더마다 filter+sort를 다시 도는데, 그럼 언제부터 useMemo로 감싸야 하나.
”느려지면”이라는데 그 기준을 아직 감으로 못 잡겠어요.
(그건 다음 글에서 팠습니다.)
서버에서 받은 값을 사용자가 편집하는 폼처럼, 파생으로 빼면 안 되는 예외는 또 어떻게 잡는지도 아직이고요.
혹시 “값은 다 있는데 왜 안 맞지”로 헤매는 중이면 — 그 값, state에 저장하지 말고 계산할 수 있는지부터 의심해 보세요.
댓글