React: setState()에서 'Excess Property Checks'를 사용하여 미묘한 버그를 방지
Source: Dev.to
문제
type State = {
name: string,
count: number,
}
// Initial state
const [state, setState] = useState({ name: 'Unknown', count: 0 });
// Intended update
function resetCount() {
setState(state => ({
...state,
count: 0,
}));
}
// Oops – typo goes unnoticed
setState(state => ({
...state,
cont: 0, // typo of `count`
}));
스프레드 연산자는 완전한 State 객체를 생성하기 때문에, 잘못 적힌 cont는 초과 속성으로 간주되고 타입 검사기가 오류를 발생시키지 않습니다. 이는 count를 personCount와 같이 이름을 바꿀 때 조용히 발생할 수 있으며, 미묘한 버그를 초래할 수 있습니다.
해결 방법: 업데이트 함수의 반환 타입에 주석 달기
setState에 전달되는 함수의 반환 타입을 명시적으로 선언하면 초과 속성 검사가 적용됩니다.
type State = {
name: string,
personCount: number, // renamed from `count`
};
const [state, setState] = useState({ name: 'Unknown', personCount: 0 });
function resetCount() {
setState((state): State => ({
...state,
count: 0, // ❌ TypeScript error: 'count' does not exist in type 'State'
}));
}
이제 오타(또는 오래된 속성 이름)가 빌드 시점에 잡히게 됩니다.
다른 접근법
복잡한 상태에는 useReducer 사용
리듀서의 반환값에 명시적으로 타입을 지정하면 초과 속성 검사가 트리거됩니다.
// ❌ Inferred return type – no excess‑property checking
function reducer(state: State, action: Action) {
/* ... */
}
// ✅ Explicit return type – catches incorrect properties
function reducer(state: State, action: Action): State {
/* ... */
}
Immer를 활용한 mutable‑style 업데이트
Immer는 프록시를 제공해 “변경” 코드를 작성하면서도 불변성을 유지합니다. TypeScript는 여전히 속성 이름을 검증합니다.
Note: This isn’t the idiomatic React pattern and may reduce readability for some teams.
헬퍼 업데이트에 Partial 사용
// Helper that merges a partial state update
function update(partial: Partial) {
setState(s => ({
...s,
...partial,
}));
}
function resetCount() {
update({ cont: 0 }); // ❌ Error: 'cont' does not exist in type 'State'
}
추가 자료
- Type Compatibility documentation – 초과 속성 검사에 대한 설계 결정을 설명합니다.