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)에서 stateinitialValue의 타입과 동일한 타입이 된다는 것을 알 수 있다.
  • setState 함수의 타입을 알기 위해서는 아래 함수를 살펴봐야 한다.
  • DispatchSetStateAction은 다음과 같은 타입이다.
    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에 의해서 stateundefined 타입이 되므로, 후술할 방법으로 타입을 지정해 주어야 한다.
  • 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번의 경우에는 매개변수로 1을 넘겨주고 있기 때문에 countnumber로 추론될 것이다. 하지만 number 타입임을 명시해 주기 위해서 아래와 같이 써도 된다.
const [count, setCount] = useState<number>(1);
  1. 2번의 경우에는 매개변수로 문자열 "ready"를 넘겨주고 있다. 따라서 modestring 타입으로 추론될 것이다. 하지만 여기서 리터럴 타입을 적용한다면 어떨까? 우선 아래와 같이 Mode라는 타입을 정의한다.
    type Mode = "ready" | "running" | "finished";
    다음으로는 1번처럼 해당 타입을 적용해주면 된다.
    const [mode, setMode] = useState<Mode>("ready");
  2. 이번에는 coords에 x와 y를 키로 가지는 object를 저장하고자 한다. 이 경우 역시 Coords같이 타입을 새로 정의하고 사용할 수 있다.
    type Coords = { x: number; y: number; }; const [coords, setCoords] = useState<Coords>({ x: 0, y: 0 });
  3. 매개변수가 없는 경우이다. 이렇게만 쓴다면 valueundefined로 추론되기 때문에, 사용할 타입에 맞게 타이핑해주어야 한다. 예를 들어서 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를 사용할 때 꽤 편리하게 코딩할 수 있을 것 같다!

참고


React docs - useState

타입스크립트 교과서(조현영 저)