리액트 컴포넌트에서 useState 와 useReducer 를 사용하여 컴포넌트의 상태를 관리하는 방법을 공부해본다.
import React, { useState } from 'react';
function 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;
TypeScript 없이 리액트 컴포넌트를 작성하는 것과 별반 차이가 없다. useState 를 사용하실때 useState() 와 같이 Generics 를 사용하여 해당 상태가 어떤 타입을 가지고 있을지 설정만 해주시면 된다.
참고로 아래 코드와 같이 useState를 사용 할 때 Generics 를 사용하지 않아도 알아서 타입을 유추하기 때문에 생략해도 상관없다.
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;
import React from 'react';
import Counter from './Counter';
const App: React.FC = () => {
return <Counter />;
};
export default App;
상태가 null일 수도 있고 아닐수도 있을때 Generics 를 활용하시면 좋다.
type Information = { name: string; description: string };
const [info, setInformation] = useState<Information | null>(null);
이벤트를 이용하여 인풋의 상태를 관리하는 방법을 다루어 보자.
import React, { useState } from 'react';
type MyFormProps = {
onSubmit: (form: { name: string; description: string }) => void;
};
function MyForm({ onSubmit }: MyFormProps) {
const [form, setForm] = useState({
name: '',
description: ''
});
const { name, description } = form;
const onChange = (e: any) => {
// e 값을 무엇으로 설정해야하나?
// 일단 모를떄는 any 로 설정한다.
};
const handleSubmit = (e: any) => {
// 여기도 모르니까 any 로 하겠다.
};
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;
• onChange 이벤트 핸들러에서는 React.ChangeEvent 타입을 사용한다.
• handleSubmit 이벤트 핸들러에서는 React.FormEvent 타입을 사용한다.
내가 참고하는 강의서에서는 해당 부분에 마우스 커서를 누르면 나온다고는 하지만 나는 찾지 못하였다 그래서 다음 위 글과 같이 대중적인 타입을 일단 적용하였다.
import React, { useState } from 'react';
type MyFormProps = {
onSubmit: (form: { name: string; description: string }) => void;
};
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
});
};
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;
import React from 'react';
import MyForm from './MyForm';
const App: React.FC = () => {
const onSubmit = (form: { name: string; description: string }) => {
console.log(form);
};
return <MyForm onSubmit={onSubmit} />;
};
export default App;
Counter 를 useReducer를 사용하는 코드로 전환 해보자.
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' });
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;
지금은 액션들이 type 값만 있어서 굉장히 간단하다, 만약 액션 객체에 필요한 다른 값들이 있는 경우엔 다른 값들도 타입안에 명시를 해주면 추후 리듀서를 작성 할 때 자동완성도 되고 dispatch 를 할 때 타입검사도 해준다.
이를 확인해보기 위하여 다른 예시 컴포넌트 ReducerSample 라는 컴포넌트를 만들어보자.
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 {
switch (action.type) {
case "SET_COUNT":
return {
...state,
count: action.count, // count가 자동완성되며, number 타입인걸 알 수 있습니다.
};
case "SET_TEXT":
return {
...state,
text: action.text, // text가 자동완성되며, string 타입인걸 알 수 있습니다.
};
case "SET_COLOR":
return {
...state,
color: action.color, // color 가 자동완성되며 color 가 Color 타입인걸 알 수 있습니다.
};
case "TOGGLE_GOOD":
return {
...state,
isGood: !state.isGood,
};
default:
throw new Error("Unhandled action");
}
}
function ReducerSample() {
const [state, dispatch] = useReducer(reducer, {
count: 0,
text: "hello",
color: "red",
isGood: true,
});
const setCount = () => dispatch({ type: "SET_COUNT", count: 5 }); // count 를 넣지 않으면 에러발생
const setText = () => dispatch({ type: "SET_TEXT", text: "bye" }); // text 를 넣지 않으면 에러 발생
const setColor = () => dispatch({ type: "SET_COLOR", color: "orange" }); // 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;
import React from 'react';
import ReducerSample from './ReducerSample';
const App: React.FC = () => {
return <ReducerSample />;
};
export default App;
출처: https://react.vlpt.us/using-typescript/03-ts-manage-state.html