useState
훅을 통해 상태를 관리하려고 했다.
관리해야할 상태는 다음과 같은 구조였다. (객체들이 담긴 배열)
[{ id:'abc', text:'abc'}, ... , ... , ... ]
초기 상태의 값을 빈 배열로 두고 상태 변경 함수를 통해 데이터를 하나씩 추가해 나가고자 했다.
그래서 다음과 같이 코드를 작성했다.
const [todos, setTodos] = useState([]);
...
function todoAddHandler(text: string) { //사용자 입력으로 들어오는 text
setTodos([{ id: Math.random().toString(), text }, ...todos]);
}
...
이렇게 작성하게 되면 다음과 같은 에러가 발생한다.
이유는 위에서 상태의 초기값을 빈 배열로 설정했기 때문이다.
const [todos, setTodos] = useState([]);
이렇게 설정하게 되면, 타입스크립트는 앞으로도 상태의 구조가 빈 배열이라고 생각한다.
만약, 초기 상태가 문자열이었다면 타입스크립트는 앞으로도 상태가 문자열일 것이라고 생각한다.
아래의 두 예시처럼 완전히 타입이 일치해야 에러가 발생하지 않는다.
즉 아래의 코드에서 상태는 앞으로도 빈 배열
일 것이라고 해주었는데, 상태 변경 함수를 통해 빈 배열이 아닌 값을 채워 넣고 있어서 에러가 발생하는 것이다.
const [todos, setTodos] = useState([]);
...
function todoAddHandler(text: string) { //사용자 입력으로 들어오는 text
setTodos([{ id: Math.random().toString(), text }, ...todos]);
}
...
타입스크립트에서는 앞으로도 상태가 어떤 구조를 가지고 있을지 정확히 알려주어야 한다.
상태의 타입을 설정해 주고자 다음과 같이 코드를 작성했다.
interface Todo {
id: string;
text: string;
}
...
const [todos, setTodos] = useState<Todo[]>([]);
function todoAddHandler(text: string) {
setTodos([{ id: Math.random().toString(), text }, ...todos]);
}
상태의 초기 값을 빈 배열로 초기화 하고자 한다면, 상태가 나중에는 어떤 구조를 가질지 명시해 주어야 한다.
상태 변경 함수는 즉시 상태를 변경하지 않을 수 있다.
따라서 상태 변경 함수를 통해 이전 값에 의존하는 상태를 업데이트하다 보면 상태가 최신 값으로 업데이트되지 않을 수 있다.
다음과 같이 상태 변경 함수에 이전 상태를 매개변수로 받는 함수를 전달한다.
그리고 그 함수는 이전 상태와 새로운 상태를 반환한다.
function todoAddHandler(text: string) {
setTodos((prevTodos) => [
...prevTodos,
{ id: Math.random().toString(), text },
]);
}
이러한 패턴으로 이전 상태에 의존하는 상태를 최신으로 업데이트할 수 있다.