3 / 3
상태 전이에 스프레드를 쓰지 않는다
타입이 막아주는 건 "없어야 할 걸 쓰는 것"이지, "남아 있는 걸 끌고 오는 것"이 아니다.
유니언으로 바꾼 뒤
팀은 안심했다. 불가능한 상태는 이제 없다.
상태를 바꾸는 코드는 여전히 손에 익은 대로 썼다. 기존 값은 유지하고, 바꿀 것만 덮어쓴다. 짧고, 읽기 쉽고, 타입 체크도 통과했다.
setState({ ...state, status: "error", error: e });몇 주 뒤
에러 리포팅 도구를 붙이기로 했다. "에러 상태가 되면 state를 통째로 보내자. 디버깅하기 편하게."
같은 주에 다른 사람이 헤더를 고쳤다. 데이터가 있으면 사용자 이름을 보여준다.
if (state.status === "error") {
report(state);
}if ("data" in state) {
showUserName(state.data.name);
}그리고 어느 날
로그를 보던 사람이 이상한 걸 발견했다. 에러 로그마다 직전 사용자의 개인정보가 통째로 붙어 있었다.
같은 날, 계정 전환 중에 에러가 난 사용자의 헤더에는 이전 계정의 이름이 떠 있었다.
{
status: "error",
error: e,
data: 이전 User,
}왜 이렇게 됐나
스프레드는 이전 상태의 필드를 전부 복사해온다. 그리고 TypeScript는 스프레드로 들어온 여분 필드를 검사하지 않는다.
유니언이 막은 건 "error 상태에 data를 쓰는 것"이었다. 이미 있던 data가 따라오는 것까지는 막지 못했다.
터지기 전에
상태마다 전이 함수를 두고, 그 함수로만 바꾼다. 함수는 필요한 필드만으로 새 객체를 만들기 때문에 끌려올 게 없다.
"기존 거 유지하고 이것만"은 AI가 가장 자주 쓰는 손버릇이기도 하다. 리뷰로 매번 잡는 것보다, 쓸 길을 없애는 편이 싸다.
const toLoading = (): State => ({ status: "loading" });
const toSuccess = (data: User): State => ({ status: "success", data });
const toError = (error: Error): State => ({ status: "error", error });
setState(toError(e));