[패스트캠퍼스 수강 후기] 프론트엔드 인강 100% 환급 챌린지 40회차 미션 - 33강 useReducer, useAsync, 커스텀 Hook
useReducer로 요청 상태 관리하기
이전에 썼던 코드를 useReducer로 다시 만들어보자. 처음에는 코드가 좀 길어지는 것 같기도 할것이다. 그치만 이렇게하면 요청관리에 대한 파일을 따로 관리해서 재사용 할 수 있다는 장점이 있다. 일단 useState로 썼던 코드를 지우자. 그리고 아래와 같이 수정해준다.
Users.js
import React, { useEffect, useReducer } from "react";
import axios from "axios";
// LOADING, SUCCESS, ERROR
function reducer(state, action) {
switch (action.type) {
case "LOADING":
return {
loading: true,
data: null,
error: null,
};
case "SUCCESS":
return {
loading: false,
data: action.data,
error: null,
};
case "ERROR":
return {
loading: false,
data: null,
error: action.error,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
function Users() {
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: null,
});
useEffect(() => {
const fetchUsers = async () => {
dispatch({ type: "LOADING" });
try {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/users/"
);
dispatch({ type: "SUCCESS", data: response.data });
} catch (e) {
dispatch({ type: "Error", error: e });
}
};
fetchUsers();
}, []);
const { loading, data: users, error } = state;
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다. </div>;
if (!users) return null;
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
);
}
export default Users;
이렇게 reducer를 상단에 함수로 따로 쓸 수 있다. 그리고 VSC의 기능을 이용하면 바로 파일 분리도 가능하다. 먼저 reducer의 이름을 acyncReducer로 바꾸고 이름부분을 오른클릭 해보자. ‘Refactor…‘을 누르고 ‘Move to new file’을 하면 파일이 분리되어 새로 생성된다(유레카!) 이렇게 할 수 있다는 걸 알아두고 일단 원상 복귀한다.
useAsync 커스텀 Hook 만들어서 사용하기
useAsync.js
import { useReducer, useEffect, useCallback } from "react";
// LOADING, SUCCESS, ERROR
function reducer(state, action) {
switch (action.type) {
case "LOADING":
return {
loading: true,
data: null,
error: null,
};
case "SUCCESS":
return {
loading: false,
data: action.data,
error: null,
};
case "ERROR":
return {
loading: false,
data: null,
error: action.error,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
function useAsync(callback, deps = []) {
//export를 이렇게 할 수도 있음
//deps dependency
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: null,
});
const fetchData = async () => {
dispatch({ type: "LOADING" });
try {
const data = await callback();
dispatch({ type: "SUCCESS", data });
} catch (e) {
dispatch({ type: "ERROR", error: e });
}
};
useEffect(() => {
fetchData();
// eslint-disable-next-line
}, deps);
return [state, fetchData];
}
export default useAsync;
그리고 Users.js는 아래와 같이 바꿔보자.
Users.js
import React from "react";
import axios from "axios";
import useAsync from "./useAsync";
async function getUsers() {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
return response.data;
}
function Users() {
const [state, refetch] = useAsync(getUsers, []);
const { loading, data: users, error } = state;
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다. </div>;
if (!users) return null;
return (
<>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
</>
);
}
export default Users;
지금의 경우에는 처음 렌더링 되는 시점에만 데이터 요청이 이루어지고 있는데, 만약에 컴포넌트가 처음 렌더링 될때는 요청하지 않고 특정 버튼을 눌러야지만 요청을 시작하고 싶다면 어떻게 하는지 알아보자.
글을 작성한다던지 수정한다던지 등 사용자의 특정 인터렉션이 있는 경우에만 발생하는게 일반적이기 때문이다.
useAsync.js
import { useReducer, useEffect, useCallback } from "react";
// LOADING, SUCCESS, ERROR
function reducer(state, action) {
switch (action.type) {
case "LOADING":
return {
loading: true,
data: null,
error: null,
};
case "SUCCESS":
return {
loading: false,
data: action.data,
error: null,
};
case "ERROR":
return {
loading: false,
data: null,
error: action.error,
};
default:
throw new Error(`Unhandled action type: ${action.type}`);
}
}
function useAsync(callback, deps = [], skip = false) {
//skip 추가
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: null,
});
const fetchData = async () => {
dispatch({ type: "LOADING" });
try {
const data = await callback();
dispatch({ type: "SUCCESS", data });
} catch (e) {
dispatch({ type: "ERROR", error: e });
}
};
useEffect(() => {
if (skip) {
//skip 추가
return;
}
fetchData();
}, deps);
return [state, fetchData];
}
export default useAsync;
Users.js
import React from "react";
import axios from "axios";
import useAsync from "./useAsync";
async function getUsers() {
const response = await axios.get(
"https://jsonplaceholder.typicode.com/users"
);
return response.data;
}
function Users() {
const [state, refetch] = useAsync(getUsers, [], true); //세번째 파라미터인 skip = true
const { loading, data: users, error } = state;
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다. </div>;
if (!users) return <button onClick={refetch}>불러오기</button>; //불러오기 추가
return (
<>
<ul>
{users.map((user) => (
<li key={user.id}>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
</>
);
}
export default Users;
그럼 아래와 같이 ‘불러오기’를 한 다음에 리스트를 불러오는 것을 볼 수 있다.
이번에는 API에서 특정 아이디를 조회해서 가져오는 것을 해보자.
우선 새로운 컴포넌트 User.js를 만들어준다.
User.js
import React from "react";
import axios from "axios";
import useAsync from "./useAsync";
async function getUser(id) {
const response = await axios.get(
`http://jsonplaceholder.typicode.com/users/${id}`
);
return response.data;
}
function User({ id }) {
const [state] = useAsync(() => getUser(id), [id]);
const { loading, data: user, error } = state;
if (loading) return <div>로딩중...</div>;
if (error) return <div>에러가 발생했습니다.</div>;
if (!user) return null;
return (
<div>
<h2>{user.username}</h2>
<p>
<b>Email:</b> {user.email}
</p>
</div>
);
}
export default User;
그리고 Users를 수정해보자.
Users.js
import React, { useState } from "react";
import axios from "axios";
import useAsync from "./useAsync";
import User from "./User"; //User 추가
async function getUsers() {
const response = await axios.get("http://jsonplaceholder.typicode.com/users");
return response.data;
}
function Users() {
const [state, refetch] = useAsync(getUsers, [], true);
const [userId, setUserId] = useState(null);
const { loading, data: users, error } = state;
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다. </div>;
if (!users) return <button onClick={refetch}>불러오기</button>;
return (
<>
<ul>
{users.map((user) => (
<li
key={user.id}
//li를 클릭하면 User 보이도록 추가
onClick={() => setUserId(user.id)}
>
{user.username} ({user.name})
</li>
))}
</ul>
<button onClick={refetch}>다시 불러오기</button>
{userId && (
<User
//User컴포넌트 불러와서 User의 아이디를 가져와줌
id={userId}
/>
)}
</>
);
}
export default Users;
우리가 만약에 요청상태를 관리해야할때마다 useState 세개씩 만들어야 했다면 굉장히 귀찮았을 것이다.
렌더링 될때 데이터를 요청해야하는 상황에만 userAsync Hook을 만들어서 useEffect도 대신 해주게끔 사용하면 코드를 많이 아낄 수 있을 것이다.
그리고 fetchData도 Hook으로 만들어서 써주어서 매번 안만들어 줘도 되게끔 해보았다.
이제 이 Hook이 어떻게 만들었는지 배웠으니 개인의 용도에 맞춰서 계속 활용해보자!
오늘은 여기까지..
시청 영상 33강 03~04까지
프론트엔드 개발 올인원 패키지 with React Online. 👉 https://bit.ly/31Cf1hp
Comments