interceptor: 중간에 가로채기
변수=http://localhost:4000
불러와서 사용하기
process.env.변수
import axios from "axios";
const instance = axios.create({
baseURL: `${process.env.REACT_APP_SERVER_URL}`,
}); // 새로운 인스턴스
export default instance;
한단계 가공할 수 있다.
axios → 생성한 파일 이름(api)
// axios
const { data } = await api.get(
`${process.env.REACT_APP_SERVER_URL}/todos`
);
// 가공
const { data } = await api.get("/todos");
import axios from "axios";
const instance = axios.create({
baseURL: `${process.env.REACT_APP_SERVER_URL}`,
timeout: 1, //해당 초 동안 들어오지 않으면 오류 발생
}); // 새로운 인스턴스
instance.interceptors.request.use(
// 요청을 보내기 전 수행되는 함수
function (config) {
console.log("인터셉터 요청 성공");
return config;
},
// 오류 요청을 보내기 전 수행되는 함수
function (error) {
console.log("인터셉터 요청 오류");
return Promise.reject(error);
}
);
instance.interceptors.response.use(
// 응답을 내보내기 전 수행되는 함수
function (response) {
console.log("인터셉터 응답 받았습니다");
return response;
},
// 오류 응답을 보내기 전 수행되는 함수
function (error) {
console.log("인터셉터 응답 오류");
return Promise.reject(error);
}
);
export default instance;
비동기 통신 미들웨어
미들웨어?
중간 작업을 넣기 위해 사용, 보통 통신을 위해 사용함
thunk, saga 등등
thunk?
redux에서 많이 사용하는 미들웨어
1) thunk 함수를 만들기 : createAsyncThunk
→ reduxtoolkit 내장 api
2) createSlice >extraReducer에 thunk 등록
3) dispatch 에 함수넣기
4) 테스트
createAsyncThunk()
변수명 : 보통 앞에 언더바(_)를 2개 넣어준다.
export const __addNumber = createAsyncThunk(
"ADD_NUMBER_WAIT",
(payload, thunkAPI) => {
// 수행하고 싶은 동작 : 3초를 기다리게 할 예정
setTimeout(() => {
thunkAPI.dispatch(addNumber(payload));
// 기존의 dispatch를 수행한다.
}, 3000);
}
);
추가 작업을 넣고 싶은 함수를 추가 작업(setTimeout 등) 내에 넣어주면 된다.
심화
thunk 함수 구현 →
리듀서 로직 구현: reducers → extraReducers
(서버 통신은 100% 성공 보장 x),
지금까지의 redux state(todos, counter)
앞으로는 state(isLoading, isError, data)을 통해 화면 제어 →
기능 확인(network)
import { createSlice } from "@reduxjs/toolkit";
import { createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
const initialState = {
todos: [],
isLoading: false,
error: null,
};
export const __getTodos = createAsyncThunk(
"getTodos",
async (payload, thunkAPI) => {
try {
const response = await axios.get("http://localhost:4000/todos");
console.log("res", response);
// Promise => resolve 요청 성공 시 dispatch
return thunkAPI.fulfillWithValue(response.data);
} catch (error) {
//
console.log("error", error);
return thunkAPI.rejectWithValue(error);
}
}
);
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {},
extraReducers: {
[__getTodos.pending]: (state, action) => {
// 진행중일 때
state.isLoading = true;
state.isError = false;
},
[__getTodos.fulfilled]: (state, action) => {
console.log("fulfilled", 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; // error 처리한 값이 들어갈 것이다.
},
},
});
export const {} = todosSlice.actions;
export default todosSlice.reducer;
import { useDispatch, useSelector } from "react-redux";
import "./App.css";
import { useEffect } from "react";
import { __getTodos } from "./redux/modules/todosSlice";
function App() {
const dispatch = useDispatch();
const { isLoading, error, todos } = useSelector((state) => {
return state.todos;
});
useEffect(() => {
dispatch(__getTodos());
}, [dispatch]);
if (isLoading) {
return <div>로딩중...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
return (
<>
<div>
{todos.map((todo) => {
return <div key={todo.id}>{todo.title}</div>;
})}
</div>
</>
);
}
export default App;
yarn add redux react-redux
yarn add @reduxjs/toolkit
yarn add json-server
yarn add axios
src -> redux -> config, modules
root -> db.js
yarn json-server --watch db.json --port 포트번호
yarn start
중복되는 코드를 줄이기 위해서 나만의 hook을 만드는 것
ex) input에 들어가는 value, onchange마다 e.target.value ~~
src -> hooks -> customhook이름.js
import { useState } from "react";
const useInput = () => {
// state
const [value, setValue] = useState("");
// handler
const handler = (e) => {
setValue(e.target.value);
};
return [value, handler];
};
export default useInput;
요즘에 많이 사용
Query: 데이터 요청
Mutation: 데이터 변경
Query Invalidation: 무효화, 최신 상태가 아닌 것을 무효화 하고 최신화한다.
yarn add react-query
**import { QueryClient, QueryClientProvider } from "react-query";**
**const queryClient = new QueryClient();**
const App = () => {
return (
**<QueryClientProvider client={queryClient}>**
<Router />;
**</QueryClientProvider>**
);
};
import axios from "axios";
const getTodos = async () => {
const response = await axios.get(`${process.env.REACT_APP_SERVER_URL}/todos`);
console.log(response);
return response.data;
};
export { getTodos };
TodoList.jsx
useQuery('쿼리의 이름', 가져온 api 메서드)
import React from "react";
import { StyledDiv, StyledTodoListHeader, StyledTodoListBox } from "./styles";
import { getTodos } from "../../../api/todos";
import { useQuery } from "react-query";
function TodoList({ isActive }) {
// const todos = useSelector((state) => state.todos);
const { isLoading, isError, data } = useQuery("todos", getTodos);
if (isLoading) {
return <h1>로딩중입니다~~~~</h1>;
}
if (isError) {
return <h1>오류가 발생하였습니다....!!!</h1>;
}
return (
<StyledDiv>
<StyledTodoListHeader>
{isActive ? "해야 할 일 ⛱" : "완료한 일 ✅"}
</StyledTodoListHeader>
<StyledTodoListBox></StyledTodoListBox>
</StyledDiv>
);
}
export default TodoList;
// 추가
const addTodo = async (newTodo) => {
await axios.post(`${process.env.REACT_APP_SERVER_URL}/todos`, newTodo);
};
export { getTodos, addTodo };
해당 함수를 추가해서 query로 불러와준다!
Input.jsx
const queryClient = useQueryClient();
const mutation = useMutation(addTodo, {
// 공식문서 처럼 비동기 함수를 첫 인자에 넣어줘도 되지만, 따로 만들어서 불러오는 것이 보기 좋다.
onSuccess: () => {
queryClient.invalidateQueries(**"todos"**); // 해당 key를 무효화 하고 다시 불러와줘
console.log("성공하였습니다!");
},
});
.
.
.
const newTodo = {
title,
contents,
isDone: false,
id: uuidv4(),
};
// todo를 추가하는 reducer 호출
// 인자 : payload
// dispatch(addTodo(newTodo));
mutation.mutate(newTodo); // 추가하는 기능이 react query를 통해 실행된다.
// state 두 개를 초기화
setTitle("");
setContents("");
입력이 성공하면 queryClient의 api인 invalidateQueries
를 사용하여 기존에 TodoList.jsx에서 선언한 const { isLoading, isError, data } = useQuery("todos", getTodos);
여기서 사용한 todos key를 넣어주어 무효화 하고 다시 불러와야 데이터가 변경되어 화면에 렌더링 된다.
성공하면 queryClient.invalidateQueries("key"); 사용하는 것 숙지
const { isLoading, isError, data } = useQuery("key", 비동기함수);
인자 1 key
key는 어떤 컴포넌트에 있던지 같은 쿼리, 데이터를 보장하게 해준다. key는 unique 해야한다.
배열 형태로 해석된다.
useQuery('todos',..)
querykey === ['todos']
인자 2 쿼리함수
promise 객체 return → 비동기 함수일 것이다.
resolve, error 오류처리 필요
결과물
isLoading + isSuccess + isError가 담긴 object
ex) 좋아요 버튼 같은 경우 연속 event가 발생하여 과부하가 발생할 수 있다.
많은 event , 실행은 1번 → 일정 구간마다 실행되도록 조절해주는 것
let timerId = null;
const throttle = (delay) => {
if (timerId) {
return;
}
console.log(`API 요청 실행 ${delay}ms 동안 추가요청은 안받습니다!`);
timerId = setTimeout(() => {
console.log(`${delay}ms 지남 추가요청 받습니다!`);
timerId = null;
}, delay);
};
쓰로틀링 → 페이지 이동 했을 경우 이대로 함수를 사용하면 쓰로틀링이 계속 실행되고 있기 때문에 메모리 누수가 발생한다.
useEffect(() => {
return () => {
// 언마운트 시
if (timerId) {
clearTimeout(timerId);
}
};
});
렌더링되면 지우는 것으로 메모리 누수를 방지할 수 있다.
연속해서 event 발생시 → 마지막으로부터 delay이 경과 후 한번만 호출
let timerId = null;
// 반복적인 이벤트 이후, delay가 지나면 function 실행
const debounce = (delay) => {
if (timerId) {
// null이 아닐 때
// 할당되어 있는 timerId에 해당하는 타이머 제거
clearTimeout(timerId);
}
timerId = setTimeout(() => {
console.log(`마지막 요청으로부터 ${delay}ms 지났으므로 API 요청 실행`);
timerId = null;
}, delay);
};
설치
yarn add lodash
밑줄처럼 씀
import _ from "lodash";
const 변수 = _.debounce(함수, 초)
제대로 사용하기 위해서는 useCallback을 사용해야 한번에 바뀐다.
const handleSearchText = useCallback(
_.debounce((text) => {
setSearchText(text);
}, 2000),
[]
);
const debounce = (callback, delay) => {
let timerId = null;
return (...args) => {
if (timerId) clearTimeout(timerId);
timerId = setTimeout(() => {
callback(...args);
}, [delay]);
};
};
내부에 있는 close함수를 반환받는다.
기존에 사용하던 라이브러리를 다른 라이브러리를 통해 간편하게 사용하는 법을 배울 수 있었다.
사용법만 익숙해지면 이해를 더 잘 할 수 있기 때문에 lv4 과제를 설계하고 작업하는 과정에서 어디에 적용할지 생각할 필요가 있어보인다.