React hook에 대해 알아본다.
"use client";
import { useState } from "react";
const CountUseStatePage = () => {
console.log("CountUsageStatePage render");
const [count, SetCount] = useState(0);
const clickHandler = () => {
// React의 일괄업데이트(batch update)로 setCount(count+1)을 하면 0+1, 0+1, 0+1을 반복하게 됨.
// 콜백함수를 사용함으로써 이전 상태값을 매개변수로 새로운 상태를 안전하게 계산함.
SetCount((count) => count + 1);
SetCount((count) => count + 1);
SetCount((count) => count + 1);
};
// 초기값에 null을 넣더라도 나중에 저장할 타입까지 고려해 제너릭을 명시함.
const [name, setName] = useState<string | null>(null);
const [age, setAge] = useState<number | null>(null);
const [gender, setGender] = useState<string | null>(null);
// 개발 상태값을 객체로 묶어서 관리할 수 있음.
const [formState, setFormState] = useState({
name: "",
age: 0,
gender: "",
});
const clickHandler2 = () => {
setName("Cloud");
setAge(30);
setGender("female");
};
const clickHandler3 = () => {
setFormState({ ...formState, age: 20, name: "정대운", gender: "male" });
};
return (
<>
<div style={{ display: "flex" }}>
<h3 style={{ margin: "10px" }}>Count: {count}</h3>
<button onClick={clickHandler}>증가</button>
</div>
<div>
<p>이름: {name}</p>
<p>나이: {age}</p>
<p>성별: {gender}</p>
<button onClick={clickHandler2}>변경</button>
</div>
<div>
<p>이름: {formState.name}</p>
<p>나이: {formState.age}</p>
<p>성별: {formState.gender}</p>
<button onClick={clickHandler3}>변경</button>
</div>
</>
);
};
export default CountUseStatePage;
export function counterReducer(state: number, action: { type: string }) {
switch (action.type) {
case "INCREMENT":
return state + 1;
case "DECREMENT":
return state - 1;
case "RESET":
return 0;
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
"use client";
import { useReducer } from "react";
import { counterReducer } from "../reducer/counterReducer";
const CountReducerPage = () => {
console.log("CountReducerPage render");
const [count, dispatch] = useReducer(counterReducer, 0);
return (
<>
<h1>Count: {count}</h1>
<button onClick={() => dispatch({ type: "DECREMENT" })}>감소</button>
<button onClick={() => dispatch({ type: "RESET" })}>초기화</button>
<button onClick={() => dispatch({ type: "INCREMENT" })}>증가</button>
</>
);
};
export default CountReducerPage;
"use client";
import { memo, useCallback, useState } from "react";
const MemoA = memo(function A() {
console.log("MemoA is rendered");
return (
<>
<div>Child Component</div>
</>
);
});
const CountPage = () => {
console.log("CountPage render");
const [count, setCount] = useState(0);
// const increment = useCallback(() => setCount(count + 1), []);
const increment = useCallback(() => setCount((count) => count + 1), []);
return (
<>
<h1>App Count: {count}</h1>
<button onClick={increment}>증가</button>
<MemoA />
</>
);
};
export default CountPage;
import { useState, useEffect } from "react";
const useFetch = <T>(url: string, initialData: T) => {
const [data, setData] = useState<T>(initialData);
const [error, setError] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(true);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
const fetchData = async () => {
try {
const response = await fetch(url, { signal });
if (!response.ok) {
throw new Error("데이터를 불러오지 못했습니다.");
}
const data = await response.json();
setData(data);
setIsLoading(false);
} catch (error) {
setError(
error instanceof Error
? error.message
: "알 수 없는 오류가 발생했습니다."
);
setIsLoading(false);
} finally {
setIsLoading(false);
}
};
fetchData();
return () => {
controller.abort();
};
}, [url]);
return { data, error, isLoading };
};
export default useFetch;
import useFetch from "./useFetch";
const App = () => {
const { data, error, isLoading } = useFetch<
{ id: number; title: string; completed: boolean }[]
>("https://jsonplaceholder.typicode.com/todos", []);
if (isLoading) return <div>로딩 중 ...</div>;
if (error) return <div>{error}</div>;
return (
<div>
{data.map((todo) => (
<li key={todo.id}>{todo.title}</li>
))}
</div>
);
};