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
를 사용할 때 꽤 편리하게 코딩할 수 있을 것 같다!
참고
타입스크립트 교과서(조현영 저)