사용교재 : 벨로퍼트와 함께하는 모던 리액트
범위 : 4.1 ~ 4.5
노션 용량문제로 Migration
3장은 todoList 실습파트로 스킵
axios.요청 메서드("API 주소", {요청 바디})
1) 데이터 요청
axiosconst [users, setUsers] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
const fetchUsers = async () => {
try {
setError(null);
setUsers(null);
setLoading(true);
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
setUsers(response.data);
} catch (e) {
setError(e);
}
setLoading(false);
};
fetchUsers();
}, []);
if (loading) return <div>로딩중..</div>;
if (error) return <div>에러가 발생했습니다</div>;
if (!users) return null;
(!) useEffect 의 첫번째 파라미터 함수에는 async 사용불가
→ 내부 함수에서 async 를 쓰는 함수를 새로 선언
useEffect , fetchUsers 는 분리가능!const fetchUsers = async () => {
try {
setError(null);
setUsers(null);
setLoading(true);
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
setUsers(response.data);
} catch (e) {
setError(e);
}
setLoading(false);
};
useEffect(() => {
fetchUsers();
}, []);
요청 상태 관리 : 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
});
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 });
}
};
useEffect(() => {
fetchUsers();
}, []);
const { loading, data: users, error } = state;
//...
}
useState간결함 : ↔setState 로 매번 상태변화를 줘야함
재사용성: 별로로 로직을 분리하기 때문에 다른 컴포넌트에서도 재사용 가능
useAsyncAPI 요청 상태 관리 로직
// useAsync.js
import { useReducer, useEffect } from 'react';
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 = []) {
const [state, dispatch] = useReducer(reducer, {
loading: false,
data: null,
error: false
});
const fetchData = async () => {
dispatch({ type: 'LOADING' });
try {
const data = await callback();
dispatch({ type: 'SUCCESS', data });
} catch (e) {
dispatch({ type: 'ERROR', error: e });
}
};
useEffect(() => {
fetchData();
}, deps);
return [state, fetchData];
}
export default useAsync;
useAsync 의 두번째 파라미터 deps 는 해당 함수를 호출할 때 할당한다. → 동적으로 함수 내부의 useEffect 사용가능
// User.js
async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
return response.data;
}
function Users() {
const [state, refetch] = useAsync(getUsers, []);
//...
}
skip)skip = true, false
→ useEffect(() => { if ( skip ) return; ... }, deps)
前) 렌더링 시 자동으로 API 요청
改) skip 파라미터가 true 일 때만, API 요청
id 등 API 요청에 파라미터가 필요한 경우, API 주소가 들어간 함수에서 파라미터를 받게 만든다 → 여기에선 User.js의 getUsers( id )
요청 상태를 관리하는 react 라이브러리
앞선 커스텀 훅 useAsync 와 유사함
반환값: 객체
useAsync(파라미터){ promiseFn, customerId ...}promiseFn : 호출할 함수customerId :watch : 값이 바뀔 때마다 promiseFn 재호출{ data, error, isLoading, run ... }API에 요청한 외부 데이터들을 다른 컴포넌에서도 전역적으로 사용하기 위함
// UserContext.js
const initialState = {
users: {
loading: false,
data: null,
error: null
},
user: {
loading: false,
data: null,
error: null
}
};
const loadingState = {
loading: true,
data: null,
error: null
};
const success = data => ({
loading: false,
data,
error: null
});
const error = error => ({
loading: false,
data: null,
error: error
});
function usersReducer(state, action) {
switch (action.type) {
case 'GET_USERS':
return {
...state,
users: loadingState
};
case 'GET_USERS_SUCCESS':
return {
...state,
users: success(action.data)
};
case 'GET_USERS_ERROR':
return {
...state,
users: error(action.error)
};
case 'GET_USER':
return {
...state,
user: loadingState
};
case 'GET_USER_SUCCESS':
return {
...state,
user: success(action.data)
};
case 'GET_USER_ERROR':
return {
...state,
user: error(action.error)
};
default:
throw new Error(`Unhanded action type: ${action.type}`);
}
}
const UsersStateContext = createContext(null);
const UsersDispatchContext = createContext(null);
export function UsersProvider({ children }) {
const [state, dispatch] = useReducer(usersReducer, initialState);
return (
<UsersStateContext.Provider value={state}>
<UsersDispatchContext.Provider value={dispatch}>
{children}
</UsersDispatchContext.Provider>
</UsersStateContext.Provider>
);
}
export function useUsersState() {
const state = useContext(UsersStateContext);
if (!state) {
throw new Error('Cannot find UsersProvider');
}
return state;
}
export function useUsersDispatch() {
const dispatch = useContext(UsersDispatchContext);
if (!dispatch) {
throw new Error('Cannot find UsersProvider');
}
return dispatch;
}
// UserContext.js
export async function getUsers(dispatch) {
dispatch({ type: 'GET_USERS' });
try {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
dispatch({ type: 'GET_USERS_SUCCESS', data: response.data });
} catch (e) {
dispatch({ type: 'GET_USERS_ERROR', error: e });
}
}
export async function getUser(dispatch, id) {
dispatch({ type: 'GET_USER' });
try {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${id}`
);
dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
} catch (e) {
dispatch({ type: 'GET_USER_ERROR', error: e });
}
}
api.js// api.js
import axios from 'axios';
export async function getUsers() {
const response = await axios.get(
'https://jsonplaceholder.typicode.com/users'
);
return response.data;
}
export async function getUser(id) {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/users/${id}`
);
return response.data;
}
// asyncActionUtils.js
export default function createAsyncDispatcher(type, promiseFn) {
const SUCCESS = `${type}_SUCCESS`;
const ERROR = `${type}_ERROR`;
async function actionHandler(dispatch, ...rest) {
dispatch({ type }); // 요청 시작됨
try {
const data = await promiseFn(...rest);
dispatch({
type: SUCCESS,
data
}); // 성공함
} catch (e) {
dispatch({
type: ERROR,
error: e
}); // 실패함
}
}
return actionHandler; // 만든 함수를 반환합니다.
}
UsersContext.js// UsersContext.js
export const getUsers = createAsyncDispatcher('GET_USERS', api.getUsers);
export const getUser = createAsyncDispatcher('GET_USER', api.getUser);