«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
Archives
Today
Total
Recent Posts
Recent Comments
관리 메뉴

뉴히의 개발 로그

[TIL] 20230707 - 비동기 통신 axios / axios instance, interceptor 사용방법 / Redux 미들 웨어 Thunk / React Query 본문

개발일지/TIL

[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'를 무효화시키고 다시 불러옴 ! 그럼 등록, 수정, 삭제가 가능하다