뉴히의 개발 로그
[TIL] 20230707 - 비동기 통신 axios / axios instance, interceptor 사용방법 / Redux 미들 웨어 Thunk / React Query 본문
[TIL] 20230707 - 비동기 통신 axios / axios instance, interceptor 사용방법 / Redux 미들 웨어 Thunk / React Query
뉴히 2023. 7. 7. 20:44[ 비동기 통신 axsios ]
대표적인 비동기 통신 방법 axios / fetch
axios란?
Promise기반 http 클라이언트 : http를 이용해서 서버와 통신하기 위해 사용하는 패키지
// 사용방법, url 필수
axios.get(url[, config]);
axios.post(url[, data[, config]]);
axios.delete(url[, config]);
axios.patch(url[, data[, config]]);
config 옵션 -> https://axios-http.com/kr/docs/req_config
가공한 axios instance 사용하기
// App.jsx
const fetchTodos = async () => {
const { data } = await axios.get('http://localhost:4000/todos'); // 가공하지 않은 axios
=>
const { data } = await api.get('/todos'); // 가공한 axios (instance)
setTodos(data);
};
// api.js
const instance = axios.create({
baseURL: process.env.REACT_APP_SERVER_URL,
timeout: 3000 // 1000 (ms) = 1초
});
// .env
REACT_APP_SERVER_URL=http://localhost:4000/todos
api 파일을 만들어 axios를 가공한 후 app.jsx에서 axios를 api로 변경해준다.
interceptor를 이용한 요청과 응답 사이 관여하기
(요청과 응답 사이에 로직 넣기)
instance.interceptors.request.use(
// 요청을 보내기 전 수행되는 함수
function (config) {
console.log('인터셉터 요청 성공');
return config;
},
// 오류 요청을 보내기 전 수행되는 함수
function (error) {
console.log('인터셉터 요청 오류!');
return Promise.reject(error); // 프로미스로 에러객체를 인자로 넣어줘야함
}
);
instance.interceptors.response.use(
// 응답을 내보내기 전 수행되는 함수
function (respons) {
console.log('인터셉터 응답 받았습니다.');
return respons;
},
// 오류응답을 내보내기 전 수행되는 함수
function (error) {
console.log('인터셉터 오류 발생');
return Promise.reject(error);
}
);
더 적용할 수 있는 부분
- 요청 시, content-type 적용
- token 등 인증 관련 로직 적용
- 서버 응답 코드에 대한 오류 처리(controller)
- 통신시작 및 종료에 대한 전역 상태를 관리하여 spinner, progress bar 등 구현 가능
axios interceptor를 통해 통신의 중간과정에서 개발자의 머릿속에 있는 모든 것을 다 구현할 수 있다
[ Redux 미들 웨어 ]
Thunk 함수 만들기
// modules > counter.js
// Thunk 함수 이름 앞에는 __가 들어감! 그냥 convention ㅎㅎ
export const __addNumber = createAsyncThunk(
// Thunk 함수에는 2개의 input이 들어간다
// 1. 이름 : 의미는 크게 없음
// 2. 함수
)
=====================================================>
export const __addNumber = createAsyncThunk('ADD_NUMBER_WAIT', (payload, thunkAPI) => {
setTimeout(() => {
thunkAPI.dispatch(addNumber(payload));
}, 3000);
});
export const __minusNumber = createAsyncThunk('MINUS_NUMBER_WAIT', (payload, thunkAPI) => {
setTimeout(() => {
thunkAPI.dispatch(minusNumber(payload));
}, 3000);
});
// App.jsx
const plusBotton = () => {
// dispatch(addNumber(+number)); // 기본 dispatch
dispatch(__addNumber(+number)); // Thunk로 만든 함수
setNumber('');
};
const minusBotton = () => {
// dispatch(minusNumber(+number));
dispatch(__minusNumber(+number));
setNumber('');
};
Thunk 함수에는 두개의 인자가들어 간다. ("이름", 함수(payload,thunkAPI) => { ... })
두번째로 들어가는 함수에서 2개의 인자를 꺼내 사용할 수 있는데, 첫번째 인자는 컴포넌트에서 보내준 payload이고, 두번째 인자는 thunk에서 제공하는 여러가지 기능이다.
- dispatch: thunk 함수안에서 dispatch를 할 때 사용
- getState: thunk 함수안에서 현재 리덕스 모듈의 state 값을 사용하고 싶을 때 사용
// Thunk 함수
export const __getTodos = createAsyncThunk('getTodos', async (payload, thunkAPI) => {
try {
const response = await axios.get('http://localhost:4000/todos');
console.log('response', response);
// toolkit 에서 제공하는 API
// promise -> resolve (=네트워크 요청이 성공한 경우)->dispatch해주는 기능을 가진 API
return thunkAPI.fulfillWithValue(response.data);
} catch (error) {
console.log('error code', error);
return thunkAPI.rejectWithValue(error);
}
});
// extraReducer
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {},
extraReducers: {
[__getTodos.pending]: (state, action) => {
//아직 진행중일때
state.isLoading = true;
state.isError = false;
},
[__getTodos.fulfilled]: (state, action) => {
state.isLoading = false;
state.isError = false;
state.todos = action.payload;
},
[__getTodos.rejected]: (state, action) => {
state.isLoading = false;
state.isError = true;
state.error = action.payload;
}
}
});
// App.jsx
const { isLoading, error, todos } = useSelector((state) => {
return state.todos;
});
useEffect(() => {
dispatch(__getTodos()); //axios get 할때 payload를 사용하지않기때문에 payload 불필요
}, []);
if (isLoading) {
// useSelector로 상태를 읽어왔더니 isLoading이 true이면 아래로 내려갈 필요가 없다.
return <div>로딩중 ... </div>;
}
if (error) {
return <div>{error.message}</div>;
}
return (
<div>
{todos.map((item) => {
return (
<div key={item.id}>
{item.id} : {item.title}
</div>
);
})}
</div>
);
thunkAPI.fulfillWithValue()는 함수이름 __getTodos.fulfilled를 찾아가고 rejectWithValue()는 __getTodos.rejected 리듀서를 찾아간다.
[ React Query ]
리액트 쿼리의 주요 키워드 3!
- Query : 문의/의문 ==> axios.get 요청과 비슷
- Mutation : 변화 ==> axios.post/put/patch/delete 와 비슷
- Query Invalidation : 무효화 하다 ==> 기존의 쿼리를 무효화 시킨 후 최신화 시키는 것
yarn add react-query
리덕스 Provider 주입하듯이 queryclient를 생성하고 주입해준다.
// App.jsx
const queryClient = new QueryClient();
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<Router />
</QueryClientProvider>
);
};
그럼 프로젝트 전체에서 queryclient가 사용된다.
src/api 폴더에 axios 요청이 들어가는 모든 모듈을 모아둘 파일을 만든다.
Query
// todos.js
import axios from 'axios';
//조회
const getTodos = async () => {
const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
return response.data;
};
export { getTodos };
//App.js
const { isLoading, isError, data } = useQuery('todos', getTodos);
if (isLoading) {
return <h1>로딩중입니다.</h1>;
}
if (isError) {
return <h1>오류가 발생하였습니다.</h1>;
}
react query에는 isLoading, isError 이런것들을 알아서 제공한다. 그래서 구조분해 할당으로 변수로 받아오기만하면 알아서 딱딱 뽑아쥼!
Mutation & Query Invalidation
// todos.js
import axios from 'axios';
// 추가
const addTodo = async (newTodo) => {
await axios.post(`${process.env.REACT_APP_SERVER_URL}/todos`, newTodo);
};
export { addTodo };
// Input.jsx
const queryClient = useQueryClient();
const mutation = useMutation(addTodo, {
onSuccess: () => {
queryClient.invalidateQueries('todos'); // 상위에서 만들어둔 useQuery의 퀴리key로 불러와서 그것을 무효화 시켜라!
}
});
mutation.mutate(newTodo); // dispatch(addTodo(newTodo))와 같음, 인자로 받은 payload값을 보내줘랑
//App.js
const { isLoading, isError, data } = useQuery('todos', getTodos);
if (isLoading) {
return <h1>로딩중입니다.</h1>;
}
if (isError) {
return <h1>오류가 발생하였습니다.</h1>;
}
post/put/patch/delete 라고 보면된다. 요청 성공시 invalidateQueries를 통해 useQuery의 'todos'를 무효화시키고 다시 불러옴 ! 그럼 등록, 수정, 삭제가 가능하다