TIL71. Hooks에 TypeScript 적용하기

조연정·2021년 1월 24일
post-thumbnail

useState + 타이핑

useState<T>()와 같이 Generics를 사용하여 해당 상태가 어떤 타입을 가지고 있을지 설정하면 된다.

import * as React from "react";
import { 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;

//class 컴포넌트
import {component} from 'react';
interface State {
  count: number;
}

class Couter extends Component<{}, state> {
  state = { count: 0 };
  // ...
}

함수형 컴포넌트는 타입스크립트없이 컴포넌트를 작성하는 것과 별 차이가 없다.
hooks는 class 컴포넌트와 달리, useState를 사용할 때 Generics를 사용하지 않아도 타입스크립트가 타입을 유추하기 때문에 생략해도 상관없다.

useState + Generics를 사용하는 경우

그렇다면 useState가 타입을 지정해야할 때는 언제일까?

const [input, setInput] = useState<HTMLInputElement | null>(null);
  • 상태가 null일 수도 있고, 아닐수도 있을때 Generics을 사용할 수 있다.
  const [tries, setTries] = useState([]);
  
  //타이핑 적용하기
interface TryInfo {
  try: string;
  result: string;
}
const [tries, setTries] = useState<TryInfo[]>([]);
  • useState에서 빈배열을 사용하는 경우 타이핑 문제를 일으킨다. ts내에서 해당 배열이 어떤 타입으로 이루어진 배열인지 추론하지 못하고 'never'를 보여준다. 이 경우에도 Generics를 사용하여 해결한다.

  • 이외에도 상태의 타입이 까다로운 구조를 가진 객체이거나 배열일 때는 Generics를 명시하는 것이 좋다.

event + 타이핑

이벤트 타입을 지정하는 방법에 대해서 알아보자.

// 1. 파라미터에 타입 지정
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setForm({
      ...form,
      [name]: value
    });
  };

const hadleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
  // 2. 제네릭 사용하여 타입 지정
const onSubmitForm = useCallback<(e: React.FormEvent) => void>((e)=> {
// ...
  },[]);
  1. 즉시 실행과 달리 함수를 따로 빼준 경우 파라미터 추론이 안된다.
    hadleChange의 e객체의 타입을 React.ChangeEvent<HTMLInputElement>로 지정,
    handleSubmit의 e객체의 타입을 React.FormEvent<HTMLFormElement>로 지정하면 된다.
  2. useCallback으로 한번 감싸줬기 때문에 타입 추론이 안된다. 제네릭을 사용하여 타입을 지정하면 해결된다.

props + 타이핑

props에 대한 타입을 선언할 때 type과 interface 중 한가지를 사용하면 된다. (프로젝트에서 일관성 유지할 것.)

  • React.FC 생략

    자식 컴포넌트
// 자식 컴포넌트
import { QA } from './types';

function Buttons({ ask, selections }: QA) {
	return (
		<AnswerWrapper>
			<h2>{ask}</h2>
			{selections.map((answer, idx) => (
				<button key={idx}>{answer.option}</button>
			))}
		</AnswerWrapper>
	);
}

부모 컴포넌트

// 부모 컴포넌트
import { QA } from './types';
import Buttons from './buttons';

function Quiz() {
	const [question, setQuestion] = useState<QA[]>([]);
	useEffect(() => {
		axios.get(`${QUESTIONAPI}`).then((res) => {
			setQuestion(res.data.questions);
		});
	}, []);

	return (
		<QuizWapper>
			{question.map((QA, idx) => (
				<Buttons key={idx} id={QA.id} ask={QA.ask} selections={QA.selections} />
			))}
		</QuizWapper>
	);
}
  • React.FC

    React.FC 를 사용 할 때는 props 의 타입을 Generics로 넣어서 사용한다. 하지만 React.FC를 사용하는 경우 DefaultProps가 제대로 작동하지 않는 문제점이 발생하기 때문에 권장하지 않는다.
import { QA } from './types';
// 자식 컴포넌트
const Buttons: React.FC<{ qa: QA }> = ({ qa }) => {
	return (
		<ButtonsWrapper>
			<h2>{qa.ask}</h2>
			<img width="40px" src="/Images/home.png" />
			{qa.selections.map((answer, idx) => (
				<button key={idx}>{answer.option}</button>
			))}
		</ButtonsWrapper>
	);
};

*type.ts

interface들 따로 빼줘서 필요한 곳에 import해준다.

export interface QA {
	id: number;
	ask: string;
	selections: [
		{
			id: number;
			option: string;
		}
	];
}
profile
Lv.1🌷

0개의 댓글