2021.11.20
타입스크립트로 리액트 상태 관리

리액트의 상태관리 Hooks 함수 useState, useReducer 를 타입스크립트로 작성하고자 한다.

 

useState

useState 에서 관리할 상태를 Generics 를 통해서 설정해줄 수 있다.

타입스크립트를 사용하고 있으므로 Generics 를 생략해도 값의 타입을 읽어서 보여준다.

function Counter() {
    const [count, setCount] = useState<number>(0);
}

 


Counter.tsx

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const onIncrease = () => setCount(count + 1);
  const onDecrease = () => setCount(count - 1);
  return (
    <div>
      <h1>{count}</h1>
      <div>
        <button onClick={onIncrease}>+1</button>
        <button onClick={onDecrease}>-1</button>
      </div>
    </div>
  );
}

export default Counter;

 


MyForm.tsx

이벤트 e 에 관한 타입을 모를 때는 any 로 작성해두었다가 해당 객체를 사용한 뒤 마우스를 대면 자동으로 해당 props 에 대한 타입이 뜬다.

import React, { useState } from "react";

type MyFormProps = {
    onSubmit: (form: { name: string; description: string }) => void; // 결과 반환 X
};

function MyForm({ onSubmit }: MyFormProps) {
    const [form, setForm] = useState({
        name: '',
        description: '',
    });

    const { name, description } = form;

    const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value }= e.target;
        setForm({
            ...form,
            [name]: value // [name] 에는 name 또는 description 이 들어온다
        })                // value 에는 e.target 에서 받아온 value (사용자가 바꾸려는 값)
    }

    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault(); // 서버에 전송 시 새로고침 되는 것 방지
        onSubmit(form);
        setForm({  // 값을 초기화해줌
            name: '',
            description: ''
        })
    }

    return (
        <form onSubmit={handleSubmit}>
            <input name="name" value={name} onChange={onChange} />
            <input name="description" value={description} onChange={onChange} />
            <button type="submit">등록</button>
        </form>
    );
}

export default MyForm;

 

App.tsx

렌더링 해줄때에는 선언된 타입이 작성되지 않으면 오류가 뜨므로 다음과 같이 props 를 작성해주면 된다. 

const App: React.FC = () => {
  const onSubmit = (form: { name: string; description: string }) => {
    console.log(form);
  }
  return <MyForm onSubmit={onSubmit} />
}

 

 

useReducer

타입스크립트로 useReducer 를 사용할 때에는 action 에 관한 타입을 type Action = | 으로 연달아서 나열하여서 사용한다.

reducer 함수를 구현할 때 action 의 타입으로 action 에 관한 타입을 정리해주었던 Action 을 작성해주면 된다.

그러면 useReducer 함수에서 type 을 작성할 때에도 자동완성으로 작성되어 실수를 방지할 수 있다.

action 이 아니더라도 들어올 값의 타입을 type xx = { } 식으로 정리를 해두면 받아오는 값의 타입을 작성하기에 좋다.

 


Counter.tsx

import React, { useReducer } from 'react';

type Action = { type: 'INCREASE' } | { type: 'DECREASE' }; // 이렇게 액션을 | 으로 연달아서 쭉 나열하세요.

function reducer(state: number, action: Action): number {
  switch (action.type) {
    case 'INCREASE':
      return state + 1;
    case 'DECREASE':
      return state - 1;
    default:
      throw new Error('Unhandled action');
  }
}

function Counter() {
  const [count, dispatch] = useReducer(reducer, 0);
  const onIncrease = () => dispatch({ type: 'INCREASE' }); // type: '' 자동완성 미리보기가 뜬다.
  const onDecrease = () => dispatch({ type: 'DECREASE' });

  return (
    <div>
      <h1>{count}</h1>
      <div>
        <button onClick={onIncrease}>+1</button>
        <button onClick={onDecrease}>-1</button>
      </div>
    </div>
  );
}

export default Counter;

 


ReducerSample.tsx

reducer 함수에서는 반환할 타입을 State 타입을 반환해야만 오류가 뜨지 않는다.

import React, { useReducer } from "react";

type Color = 'red' | 'orange' | 'yellow';

type State = {
    count: number;
    text: string;
    color: Color;
    isGood: boolean;
};

type Action = 
    | { type: 'SET_COUNT'; count: number }
    | { type: 'SET_TEXT'; text: string }
    | { type: 'SET_COLOR'; color: Color }
    | { type: 'TOGGLE_GOOD'; };

function reducer(state: State, action: Action): State {    // State 반환
        switch (action.type) {
            case 'SET_COUNT':
                return {
                    ...state,
                    count: action.count
                }
            case 'SET_TEXT':
                return {
                    ...state,
                    text: action.text
                }
            case 'SET_COLOR':
                return {
                    ...state,
                    color: action.color
                }
            case 'TOGGLE_GOOD':
                return {
                    ...state,
                    isGood: !state.isGood
                }
            default: 
                throw new Error('Unhandled action type');
        }
}

function ReducerSample() {
    const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: 'hello',
        color: 'red',
        isGood: true
    });

    const setCount = () => dispatch({ type: 'SET_COUNT', count: 5 });
    const setText = () => dispatch({ type: 'SET_TEXT', text: 'bye' });
    const setColor = () => dispatch({ type: 'SET_COLOR', color: 'orange' });
    const toggleGood = () => dispatch({ type: 'TOGGLE_GOOD' });

    return (
        <div>
            <p>
                <code>count: </code> {state.count}
            </p>
            <p>
                <code>text: </code> {state.text}
            </p>
            <p>
                <code>color: </code> {state.color}
            </p>
            <p>
                <code>isGood: </code> {state.isGood ? 'true' : 'false'}
            </p>
            <div>
                <button onClick={setCount}>SET_COUNT</button>
                <button onClick={setText}>SET_TEXT</button>
                <button onClick={setColor}>SET_COLOR</button>
                <button onClick={toggleGood}>TOGGLE_GOOD</button>
            </div>
        </div>
    );
}

export default ReducerSample;