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가 타입을 지정해야할 때는 언제일까?
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를 명시하는 것이 좋다.
이벤트 타입을 지정하는 방법에 대해서 알아보자.
// 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)=> {
// ...
},[]);
React.ChangeEvent<HTMLInputElement>로 지정,React.FormEvent<HTMLFormElement>로 지정하면 된다.props에 대한 타입을 선언할 때 type과 interface 중 한가지를 사용하면 된다. (프로젝트에서 일관성 유지할 것.)
// 자식 컴포넌트
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>
);
}
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>
);
};
interface들 따로 빼줘서 필요한 곳에 import해준다.
export interface QA {
id: number;
ask: string;
selections: [
{
id: number;
option: string;
}
];
}