본문 바로가기
프로그래밍/react

타입스크립트로 Hooks(useState,useRef,reducer) 사용하기

by 한코코 2022. 12. 6.

기본구조

컴포넌트를 사용해야하기때문에 확장자를 tsx로 두었다.

src
  ㄴ App.tsx
  ㄴ index.tsx
// index.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
// App.tsx
import React from "react";

const App = () => {
  return (
    <div>
    </div>
  );
};

export default App;

 

 

 

 

 


useState 사용하기

import React, { useState } from "react";

const Counter = () => {
  const [count, setCount] = useState<number>(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;

위와 같이 명확하게 useState 초기값이 정해져있다면 타입스크립트가 유추해서 잘 실행해서 상관없지만,

값이 어느것이 들어갈지 모를때는 제네릭을 명시해주는 것이 좋다.

// 값이 Information이 들어갈지 null일지 모를때.
type Information = { name: string; description: string };
const [info, setInformation] = useState<Information | null>(null);

상태가 까다로운 값이거나 구조가 어려운 형태일때도 제네릭을 명시해준다.

type Todo = { id: number; text: string; done: boolean };
const [todos, setTodos] = useState<Todo[]>([]);

 

 

 

useState로 관리하는 입력값

타입스크립트라서 입력값을 받는 형식도 다 정해줘야 하는 것을 생각하면서 작성하면 좀 복잡해진다.

기본적인 리액트 구조를 알고나서 타입스크립트로 바꾼다고 생각하자.

import { useState } from "react";

type MyFormProps = {
  onSubmit: (form: { name: string; apple: string }) => void;
};

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

  const { name, apple } = form;

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setForm({
      ...form,
      [name]: value,
    });
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onSubmit(form);
    setForm({
      name: "",
      apple: "",
    }); // 초기화
  };

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

export default MyForm;

 

 

입력받는 값의 type을 지정해주고,

type MyFormProps = {
  onSubmit: (form: { name: string; apple: string }) => void;
};

그 값을 사용하는 함수에 넣어주자.

// 함수형태기때문에 어떤 형식이든지 다 사용가능하다
function MyForm({ onSubmit }: MyFormProps){...}
const MyForm = ({ onSubmit }: MyFormProps) => {...}

onChange 함수에 넣을 값을 제네릭으로 선언해준다.

const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name, value } = e.target;
  setForm({
    ...form,
    [name]: value,
  });
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  onSubmit(form);
  
  setForm({
    name: "",
    apple: "",
  }); // 초기화
};

근데 React.ChangeEvent<HTMLInputElement> 이게 뭘까.

onChange의 이벤트의 인자값으로 들어갈거기때문에 이벤트 타입에 맞는 ChangEvent를 넣고,

해당 이벤트를 발생시키는 요소를 제네릭 값으로 넣으면 된다. (해당 설명 링크)

-라고 다들 설명하는데 이해는 잘 안가고, 우선 익숙해질때까지 사용해보는게 답일듯.

ChangeEventHandler는 target 속성이 없어서 사용을 못한다는데 뭐가 다른지 모르겠음.

 

MyForm 컴포넌트를 App.tsx로 가져오면 다음과 같이 사용할 수 있다.

import React from "react";
import Counter from "./Counter";
import MyForm from "./MyForm";

const App: React.FC = () => {
const onSubmit = (form:{name:string; apple:string})=>{
  console.log(form);
}

  return (
    <div>
      <Counter />
      <MyForm onSubmit={onSubmit} />
    </div>
  );
};

export default App;

입력받은 값(form)을 출력하고 있다.

 

 

 

 

 

 


useReducer 사용하기

useState로 만들었던 Counter를 useReducer로 만들어보자

// ReducerCount.tsx
import React, { useReducer } from "react";

type Action = { type: "INCREASE" } | { type: "DECREASE" };

const 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");
  }
};

const ReducerCount = () => {
  const [count, dispatch] = useReducer(reducer, 0);
  const onIncrease = () => dispatch({ type: "INCREASE" });
  const onDecrease = () => dispatch({ type: "DECREASE" });

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

export default ReducerCount;

초기값이 먼저 화면에 표시되고 버튼을 누를때마다 설정해둔 값을 하나씩 바뀐다.

 

 

useRef 사용하기

 리액트 컴포넌트에서 외부 라이브러리의 인스턴스 또는 DOM 을 특정 값 안에 담을 때 사용한다.

추가적으로, 이를 통해 컴포넌트 내부에서 관리하고 있는 값을 관리할 때 유용하다.

단, 이 값은 렌더링과 관계가 없어야 한다.

 

다음은 handleSubmit 이벤트가 등록 됐을 때 첫번째 인풋에 포커스가 잡히도록 만든 코드다.

import React, { useState, useRef } from "react";

type MyFormProps = {
  onSubmit: (form: { name: string; apple: string }) => void;
};

const MyForm = ({ onSubmit }: MyFormProps) => {
  const inputRef = useRef<HTMLInputElement>(null);

  const [form, setForm] = useState({
    name: "",
    apple: "",
  });

  const { name, apple } = form;

  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setForm({
      ...form,
      [name]: value,
    });
  };

  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onSubmit(form);
    setForm({
      name: "",
      apple: "",
    }); // 초기화
    // 리액트 컴포넌트에서 외부 라이브러리의 인스턴스 또는 DOM 을 특정 값 안에 담을 때 사용
    // 이를 통해 컴포넌트 내부에서 관리하고 있는 값을 관리할 때 유용하죠. 단, 이 값은 렌더링과 관계가 없어야 합니다
    if (!inputRef.current) {
      return;
    }
    inputRef.current.focus();
  };

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

export default MyForm;

아래는 inputRef값을 사용하기 위해 입력값이 null체킹을 하는 코드다.

타입스크립트에서 만약 어떤 타입이 undefined 이거나 null 일 수 있는 상황에는, 해당 값이 유효한지 체킹하는 작업을 꼭 해주어야 자동완성도 잘 이루어지고, 오류도 사라진다.

if (!inputRef.current) {
  return;
}

onSubmit 버튼을 활성화시키면 inputRef가 있는 첫번째 입력칸이 활성화된다.

 

 

 

 

참고블로그

이 블로그 정리 잘 되어있어서 이해하기 쉬웠다.

댓글