만약, TypeScript에 대해 궁금하시다면 아래 링크를 참조해주세요
기존 React Project에 TypeScript 도입
TypeScript를 사용하는 React Project 만들기
npx create-react-app {project-name} --typescript
ex. npx create-react-app react-ts --typescript
JSX를 사용하는 typescript의 파일 확장자는 .tsx
함수형 컴포넌트의 타입 설정
변수에 arrow function을 할당할 때 변수 타입을 React.FC로 지정해줄 수 있습니다.
여기에는 몇 가지 특징이 있습니다.
1. HelloProps에 children을 정하지 않아도 자동으로 등록되어 있습니다.
2. defaultProps가 잘 작동하지 않습니다.
import React from "react";
type HelloProps = {
word: string,
};
const Hello: React.FC<HelloProps> = ({ word }) => {
return <div>{word}</div>;
};
export default Hello;
다른 방법은 function 키워드를 사용하여 Component를 만들 수 있습니다.
이런 경우 defaultProps가 정상 작동합니다.
다만, children props를 설정하려면 아래 처럼 children?: React.ReactNode를 설정해줘야 합니다.
오히려 아래와 같은 방법이 children을 사용하는지 안하는지 확실히 알 수 있어서 좋을 수 있습니다.
import React from "react";
type HelloProps = {
word: string;
children?: React.ReactNode;
};
function Hello({ word }: HelloProps) {
return <div>{word}</div>;
}
export default Hello;
Hooks
useState 및 event 객체의 type을 설정하도록 하겠습니다.
useState는 Generic 타입을 설정해 줄 수 있습니다. 번거롭다면 생략해주셔도 상관없습니다.
여기서 문제는 onChange 함수를 선언할 때 event객체를 파라미터로 받습니다.
이 객체의 타입을 알기 위해서는 VS Code에서 input Tag의 onChange에 마우스 커서를 가져다 놓으면
다음과 같이 설명칸을 보고 타입을 알아낼 수 있습니다.
event 객체의 타입은 React.ChangeEvent<HTMLInputElement>입니다.
import React, { useState } from "react";
type Inputs = {
name: string;
age: string;
};
type InputsProps = {
onSubmit(inputs: Inputs): void;
}
export default function Inputs({onSubmit}: InputsProps) {
const [inputs, setInputs] = useState<Inputs>({
name: "",
age: "",
});
const { name, age } = inputs;
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setInputs({ ...inputs, [name]: value });
};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(inputs);
setInputs({
name: '',
age: '',
})
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={name} onChange={onChange} />
<input name="age" value={age} onChange={onChange} />
<button type="submit">등록</button>
</form>
);
}
useReducer
useReducer Hook은 순수 함수인 reducer를 받고 다음으로 initialState를 받습니다.
reudcer에서 필요한 파라미터 타입과 리턴 타입을 설정해주면 됩니다.
ctrl + space bar 누르시면 해당 타입에서 필요한 부분을 IDE가 알려줍니다.
import React, { useReducer } from 'react'
type State = {
count: number;
text: string;
}
type Action =
{ type: 'SET_NUMBER'; count: number}
| { type: 'SET_TEXT'; text: string};
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'SET_NUMBER':
return {
...state,
count: action.count,
}
case 'SET_TEXT':
return {
...state,
text: action.text,
}
default:
throw new Error('Unhandled Action Type')
}
}
export default function TestReducer() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
text: 'test',
});
const setCount = () => dispatch({type: 'SET_NUMBER',count: 3})
const setText = () => dispatch({type: 'SET_TEXT',text: 'hi'})
return (
<div>
<p>{state.count}</p>
<p>{state.text}</p>
<button onClick={setCount}>숫자 설정</button>
<button onClick={setText}>텍스트 설정</button>
</div>
)
}
ContextAPI
App.tsx
import React from "react";
import TestReducer from "./TestReducer";
import TestContext from "./TestContext";
const App = () => {
return (
<TestContext>
<TestReducer />
</TestContext>
)
};
export default App;
TestContext.tsx // context 설정 컴포넌트
import React, {createContext, Dispatch, useContext, useReducer} from 'react'
type State = {
count: number;
text: string;
}
type Action =
{ type: 'SET_NUMBER'; count: number}
| { type: 'SET_TEXT'; text: string};
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'SET_NUMBER':
return {
...state,
count: action.count,
}
case 'SET_TEXT':
return {
...state,
text: action.text,
}
default:
throw new Error('Unhandled Action Type')
}
}
// 초기값이 null이기 때문에 제네릭 타입을 State | null로 설정
const TestContextState = createContext<State | null>(null);
// Dispatch 함수 타입은 React에 정의 되어 있음
// Dispatch는 파라미터로 action 객체를 갖기 때문에 action 객체의 타입인 Action을 제네릭 타입에 설정
const TestContextDispatch = createContext<Dispatch<Action> | null>(null);
type TestContextProps = {
children: React.ReactNode;
}
export default function TestContext({ children }: TestContextProps) {
const [state, dispatch] = useReducer(reducer, {
count: 0,
text: 'hi',
});
return (
<TestContextState.Provider value={state}>
<TestContextDispatch.Provider value={dispatch}>
{children}
</TestContextDispatch.Provider>
</TestContextState.Provider>
)
}
/*
값이 없을 때 error를 내주지 않으면 해당 함수의 리턴 값은 State | null이 되는데
다른 컴포넌트에서 사용할 때 에러가 발생할 수 있기 때문에 return 값의 타입이 항상 State이도록 예외 처리
*/
export function useTestState() {
const state = useContext(TestContextState);
if(!state) throw new Error("state is null")
return state;
}
export function useTestDispatch() {
const dispatch = useContext(TestContextDispatch);
if(!dispatch) throw new Error('dispatch is null')
return dispatch;
}
TestReudcer.tsx // context 사용 컴포넌트
import React from 'react'
import { useTestDispatch, useTestState } from './TestContext'
export default function TestReducer() {
const state = useTestState();
const dispatch = useTestDispatch();
const setCount = () => dispatch({type: 'SET_NUMBER',count: 3})
const setText = () => dispatch({type: 'SET_TEXT',text: 'hi'})
return (
<div>
<p>{state.count}</p>
<p>{state.text}</p>
<button onClick={setCount}>숫자 설정</button>
<button onClick={setText}>텍스트 설정</button>
</div>
)
}