[Problem]
React + TypeScript로 프로젝트를 진행하면서 setState를 props로 넘길 때 타입을 지정해야 하는데 오류가 발생했다.
import { useState } from "react";
const Parent = () => {
const [number, setNumber] = useState<number>(0);
return <Children setNumber={setNumber}/>;
};
interface ChildrenProps {
setNumber: (value: number) => void;
}
const Children = ({setNumber}: ChildrenProps) => {
const handleClick = () => {
setNumber((prev: number) => prev + 1); // Error 발생
}
return <button onClick={handleClick}>더하기</button>;
};
// 오류 메세지
// Argument of type '(prev: number) => number' is not assignable to parameter of type 'number'Argument of type '(prev: number) => number' is not assignable to parameter of type 'number'
위의 예제에서 오류가 발생한 이유는 값을 단순하게 업데이트하는거나 전달하는 게 아닌 이전 상태 값을 활용해 업데이트를 하기 때문에 발생한 오류이다.
[Solve]
구체적인 원인을 찾기 위해서 SetStateAction을 뜯어봤다.
type SetStateAction<S> = S | ((prevState: S) => S);
즉, setState는
1. 새로운 값을 직접 전달
2. 이전 상태값을 받아 새로운 값을 반환하는 함수((prevState: S) => S)를 전달
이렇게 두 가지로 볼 수 있다.
하지만 위 예제 코드에서 (value: number) => void로 타입을 지정하면 새로운 값을 직접 전달하는 형태로만 사용하고 콜백 함수 방식(prev => prev + 1)을 허용하지 않기 때문에 오류가 나는 것이다.
해결책으로는 Dispatch<SetStateAction<T>>를 타입으로 사용하는 것이다.
import { useState, Dispatch, SetStateAction } from "react";
const Parent = () => {
const [number, setNumber] = useState<number>(0);
return <Children setNumber={setNumber} />;
};
interface ChildrenProps {
setNumber: Dispatch<SetStateAction<number>>; // 이렇게 수정해야 함
}
const Children = ({ setNumber }: ChildrenProps) => {
const handleClick = () => {
setNumber((prev) => prev + 1);
};
return <button onClick={handleClick}>더하기</button>;
};
[Why]
그렇다면 어떻게 왜 Dispatch<SetStateAction<T>>를 사용하면 되는지 궁금해서 해석을 해봤다.
React 내부에서는 setState를 아래와 같이 정의하고 있다.
type Dispatch<A> = (value: A) => void;
이걸 setStateAction<S>와 합치면 아래와 같이 된다.
type Dispatch<SetStateAction<S>> = (S | ((prevState: S) => S)) => void;
예제를 대입해 보면 아래와 같다.
type Dispatch<SetStateAction<number>> = (value: number | ((prev: number) => number)) => void;
const setNumber: (value: number | ((prev: number) => number)) => void;
즉, 위에서 말한 새로운 값 직접 전달, 이전 상태값을 받아 새로운 값을 반환하는 함수 전달 두 가지 다 정의가 되는 것을 볼 수 있다.