useState를 타이핑하는 방법
TypeScript에서 useState를 사용하기
Table Of Contents
들어가기
setState hook에서 set function을 반환받았다. set function을 하위 컴포넌트로 넘겨줄 때, 또또또 setState: any라고 쓰려다가 멈췄다. (아니면 setState: Function도 있다🤣)
state를 타이핑하는 방법은 쉬운데, set function을 prop으로 넘겨줄 때 타입은 어떻게 해야 할까?
useState란?
useState란 React의 Hook 중 하나인데, state variable을 컴포넌트에 추가시킨다.
사용
useState는 아래와 같이 사용하면 된다.
const [state, setState] = useState(initialState);
initialState라는 값을 인자로 넘겨주면 JS의 Array Destructing 문법을 이용해서 state와 setState 함수에 값을 할당하는 구조이다.
initialState: state의 초기값으로 지정하고자 하는 값이다.state: 현재 상태를 저장하는 변수이다. 초기 render시에 initialState값으로 초기화된다.setState: set 함수는state값을 업데이트할 때 사용된다. 이 때, render가 다시 일어난다.setState(nextState)처럼 호출해서 사용하면,state값이nextState로 업데이트된다.setState(prevState => prevState + 1)처럼 이전state로부터 새로운state를 계산하는 함수를 넣어도 된다.
예시
function Component() { const [inputString, setInputString] = useState(""); const [count, setCount] = useState(1); setInputString("test"); setCount(prevCount => prevCount + 1); }
inputString이라는 변수에 빈 문자열을 초기값으로 지정하고, 아래에서는setInputString함수를 호출해 "test"라는 값으로 업데이트했다.count변수는 1로 초기화한 다음,setCount함수로 이전 값 + 1로 업데이트했다. 이전 값이 1이므로 다음 값은 2가 될 것이다.
TypeScript 파일 살펴보기
@types/react v18을 기준으로 작성되었습니다.
TypeScript에서는 아래와 같이 정의되어 있다.
function useState<S>( initialState: S | (() => S) ): [S, Dispatch<SetStateAction<S>>]; function useState<S = undefined>(): [ S | undefined, Dispatch<SetStateAction<S | undefined>> ];
매개변수가 있으면 첫 번째 오버로딩, 없다면 두 번째 오버로딩에 해당한다.
1. 매개변수(initialState)가 있는 경우
function useState<S>( initialState: S | (() => S) ): [S, Dispatch<SetStateAction<S>>];
- 우선
[state, setState] = useState(initialValue)에서state는initialValue의 타입과 동일한 타입이 된다는 것을 알 수 있다. setState함수의 타입을 알기 위해서는 아래 함수를 살펴봐야 한다.Dispatch와SetStateAction은 다음과 같은 타입이다.type SetStateAction<S> = S | ((prevState: S) => S); type Dispatch<A> = (value: A) => void;Dispatch<SetStateAction<S>>처럼 둘을 합치면 아래처럼 풀어 쓸 수 있다.(value: S | ((prevState: S) => S)) => void;setState함수는 위에서 설명했듯 prevState를 사용할 수도 있고, 사용하지 않을 수도 있다.- 타입 선언에서 이 부분을 확인할 수 있다. prevState를 사용하지 않는 경우면
S => void, 사용하는 경우면((prevState: S) => S) => void타입이 될 것이므로, 결론적으로(value: S | ((prevState: S) => S)) => void;라고 표현된다.
2. 매개변수(initialState)가 없는 경우
function useState<S = undefined>(): [ S | undefined, Dispatch<SetStateAction<S | undefined>> ];
- initialState를 넘겨주지 않고 useState를 사용하는 경우이다.
- 크게 본다면,
S였던 타입이S | undefined로 변한 것을 제외하면 달라진 점은 없다. - 이 경우에 state는
S | undefined타입이 된다.state에 타입을 지정해 주지 않는다면S = undefined에 의해서state가undefined타입이 되므로, 후술할 방법으로 타입을 지정해 주어야 한다.
setState함수도 크게 다르지 않다.- 이 경우에는
state가 undefined일 경우를 잘 핸들링해야 한다.
state 타이핑하기
이제 실제로 state 변수를 타이핑하는 방법을 알아보자. 이에 앞서 매개변수를 사용하는 경우와 사용하지 않는 경우의 예시를 먼저 보자.
// 1 const [count, setCount] = useState(1); // 2 const [mode, setMode] = useState("ready"); // 3 const [coords, setCoords] = useState({ x: 0, y: 0 }); // 4 const [value, setValue] = useState();
매개변수를 사용하는 경우 3가지와 사용하지 않는 경우 1가지를 가져왔다.
- 1번의 경우에는 매개변수로 1을 넘겨주고 있기 때문에
count는number로 추론될 것이다. 하지만number타입임을 명시해 주기 위해서 아래와 같이 써도 된다.
const [count, setCount] = useState<number>(1);
- 2번의 경우에는 매개변수로 문자열 "ready"를 넘겨주고 있다. 따라서
mode는string타입으로 추론될 것이다. 하지만 여기서 리터럴 타입을 적용한다면 어떨까? 우선 아래와 같이Mode라는 타입을 정의한다.
다음으로는 1번처럼 해당 타입을 적용해주면 된다.type Mode = "ready" | "running" | "finished";const [mode, setMode] = useState<Mode>("ready"); - 이번에는
coords에 x와 y를 키로 가지는 object를 저장하고자 한다. 이 경우 역시Coords같이 타입을 새로 정의하고 사용할 수 있다.type Coords = { x: number; y: number; }; const [coords, setCoords] = useState<Coords>({ x: 0, y: 0 }); - 매개변수가 없는 경우이다. 이렇게만 쓴다면
value는undefined로 추론되기 때문에, 사용할 타입에 맞게 타이핑해주어야 한다. 예를 들어서boolean타입으로 사용하고 싶다면 아래처럼 쓸 수 있다.const [value, setValue] = useState<boolean>();
setState 함수 타이핑하기
setState 함수는 하위 컴포넌트로 내리는 일 이외에는 거의 타이핑할 일이 없다고 생각하기 때문에 컴포넌트로 예를 들어보겠다.
하지만 다른 경우에서도 똑같이 하면 된다.
1. 매개변수(initialState)가 있는 경우
import { Dispatch, SetStateAction } from 'react'; ... function SomeComponent({ setState, }: { setState: Dispatch<SetStateAction<number>> }) { ... }
number 타입 state를 변경하는 setState 함수는 이렇게 타이핑하면 된다. number 이외의 타입도 해당 자리에 적절히 써 주면 잘 작동한다.
위에서 봤듯이,
Dispatch<SetStateAction<number>>
는 결국
(prevState: number | ((prevState: number) => number)) => void;
와 같기 때문에 이렇게 바꿔 써 주어도 잘 작동한다!
2. 매개변수(initialState)가 없는 경우
import { Dispatch, SetStateAction } from 'react'; ... function SomeComponent({ setState, }: { setState: ( prevState: Dispatch<SetStateAction<number | undefined>> ) => void; }) { ... }
undefined가 추가되는 것 말고 다른 점은 없다.
이 역시
prevState: | (number | undefined) | ((prevState: number | undefined) => number | undefined)
처럼 써 주어도 작동한다.
결론
useState<type>()
와
Dispatch<SetStateAction<type>>
만 기억하면 앞으로 TypeScript와 useState를 사용할 때 꽤 편리하게 코딩할 수 있을 것 같다!
참고
타입스크립트 교과서(조현영 저)