- Published on
타입스크립트 리액트 Hooks
- Authors

- Name
- dwook
Table of Contents
useState
interface Payload {
text: string;
}
function App() {
const [payload, setPayload] = useState<Payload | null>(null); // payload의 타입을 제네릭으로 확인
}
import { useState } from 'react';
interface Product {
name: string;
price: number;
}
function App() {
const [count, setCount] = useState<number>(0); // 값의 사용
const [products, setProducts] = useState<Product[]>([]); // 배열 사용
const [product, setProduct] = useState<Product>({} as Product); // 타입 캐스팅 방식
const [product, setProduct] = useState<Product>({ name: '', price: 0 }); // 초기값 설정 방식
return (
<div className="App">
{count}
{products.map((product: Product, productIdx: number) => (
<div>
<h1>{product.name}</h1>
<span>{product.price}</span>
</div>
))}
<h1>{ product.name}</h1>
<span>{ product.price }</span>
</div>
);
}
export default App;
useEffect
function App() {
const [payload, setPayload] = useState<Payload | null>(null);
useEffect(() => {
fetch("/data.json")
.then(res => res.json())
.then(data => {
setPayload(data);
});
}, [])
return (
<div>{JSON.stringify(payload)}</div>
)
}
- useEffect를 사용할 때 function 또는 undefined 이외의 것을 반환하지 않아야한다.
function DelayedEffect({ timerMs }: { timerMs: number }) {
useEffect(
() =>
setTimeout(() => {
/* do stuff */
}, timerMs),
[timerMs]
);
// 나쁜 사례!
// 화살표함수 본문이 중괄호로 감싸지지 않았기 때문에 setTimeout은 암묵적으로 숫자를 리턴.
return null;
}
function DelayedEffect({ timerMs }: { timerMs: number }) {
useEffect(() => {
setTimeout(() => {
/* do stuff */
}, timerMs);
}, [timerMs]);
// better; void 키워드를 사용하여 undefined를 반환하는지 확인.
return null;
}
useReducer
interface Todo {
id: number;
done: boolean;
text: string;
}
type ActionType =
| { type: "ADD"; text: strinf }
| { type: "REMOVE"; id: number };
function App() {
const [todos, dispatch] = useReducer((state: Todo[], action: ActionType) => {
switch (action.type) {
case "ADD":
return [
...state,
{
id: state.length,
text: action.text,
done: false,
},
];
case "REMOVE":
return state.filter(({ id }) => id !== action.id);
default:
throw new Error();
}
}, []);
return <>
{todos.map((todo) => (
<div key={todo.id}>
{todo.text}
<button
onClick={() =>
dispatch({
type: "REMOVE",
id: todo.id,
})
}
>
Remove
</button>
</div>
))}
</>
}
import { useReducer } from 'react';
const initialState = { count: 0 };
type ACTION =
| { type: "increment"; payload: number }
| { type: "decrement"; payload: string };
function reducer(state: typeof initialState, action: ACTION) {
switch (action.type) {
case "increment":
return { count: state.count + action.payload };
case "decrement":
return { count: state.count - action.payload };
default:
throw new Error();
}
}
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<div>
<button onClick={() => dispatch({ type: "decrement", payload: 1 })}>-</button>
<button onClick={() => dispatch({ type: "increment", payload: 1 })}>+</button>
</div>
</>
);
}
export default App;
useRef
- useRef는 DOM 또는 값을 가짐
function App() {
const newTodoRef = useRef<HTMLInputElement>(null);
const onAddTodo = useCallback(() => {
if (newTodoRef.current) {
dispatch({
type: "ADD",
text: newTodoRef.current.value,
});
newTodoRef.current.value = "";
}
}, []);
return <>
<div>
<input type="text" ref={newTodoRef} />
<button onClick={onAddTodo}>Add Todo</button>
</div>
<>
}
import { useRef } from 'react';
function App() {
const domRef = useRef<HTMLDivElement | null>(null);
const valRef = useRef<Number | null>(1);
return (
<>
<div ref={domRef}>etc</div>
</>
);
}
export default App;
Advanced Properties
커스텀 hook 사용하지 않을 때(useState)
const Incrementer: React.FunctionComponent<{
value: number;
setValue: React.Dispatch<React.SetStateAction<number>>;
}> = ({ value, setValue }) => (
<Button onClick={() => setValue(value + 1)} title={`Add - ${value}`} />
);
function App() {
const [value, setValue] = useState(0);
return <>
<Incrementer value={value} setValue={setValue} />
</>
}
커스텀 hook 사용할 때(useNumber)
const useNumber = (initialValue: number) => useState<number>(initialValue);
type UseNumberValue = ReturnType<typeof useNumber>[0]; // number
type UseNumberSetValue = ReturnType<typeof useNumber>[1]; // React.Dispatch<React.SetStateAction<number>>
const Incrementer: React.FunctionComponent<{
value: UseNumberValue;
setValue: UseNumberSetValue;
}> = ({ value, setValue }) => (
<Button onClick={() => setValue(value + 1)} title={`Add - ${value}`} />
);
function App() {
const [value, setValue] = useNumber(0);
return <>
<Incrementer value={value} setValue={setValue} />
</>
}
Custom Hooks
- 함수를 만들고 내보낼 때, useCallback 사용 권장
useTodos.ts
import { useCallback, useReducer } from "react";
type ActionType =
| { type: "ADD"; text: string }
| { type: "REMOVE"; id: number };
interface Todo {
id: number;
done: boolean;
text: string;
}
export function useTodos(initialTodos: Todo[]): {
todos: Todo[];
addTodo: (text: string) => void;
removeTodo: (id: number) => void;
} {
const [todos, dispatch] = useReducer((state: Todo[], action: ActionType) => {
switch (action.type) {
case "ADD":
return [
...state,
{
id: state.length,
text: action.text,
done: false,
},
];
case "REMOVE":
return state.filter(({ id }) => id !== action.id);
default:
throw new Error();
}
}, initialTodos);
const addTodo = useCallback((text: string) => {
dispatch({
type: "ADD",
text,
});
}, []);
const removeTodo = useCallback((id: number) => {
dispatch({
type: "REMOVE",
id,
});
}, []);
return { todos, addTodo, removeTodo };
}
App.tsx
import { useTodos } from "./useTodos";
function App() {
const { todos, addTodo, removeTodo } = useTodos([
{ id: 0, text: "Hey there", done: false },
]);
const newTodoRef = useRef<HTMLInputElement>(null);
const onAddTodo = useCallback(() => {
if (newTodoRef.current) {
addTodo(newTodoRef.current.value);
newTodoRef.current.value = "";
}
}, [addTodo]);
return <>
{todos.map((todo) => (
<div key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>Remove</button>
</div>
))}
<div>
<input type="text" ref={newTodoRef} />
<Button onClick={onAddTodo}>Add Todo</Button>
</div>
</>
}