이 프로젝트는 React와 Supabase를 사용하여 맛집을 공유하고 리뷰를 작성할 수 있는 뉴스 피드 사이트입니다.
이 프로젝트는 사용자들이 맛집을 공유하고, 다른 사용자의 리뷰를 확인하며, 자신만의 맛집 리뷰를 작성할 수 있는 플랫폼을 제공합니다. React로 프론트엔드를 구성하고, Supabase로 백엔드와 데이터베이스를 관리합니다.
이번 프로젝트에서 내가 맡은 부분은 사용자 인증 부분이다. 아직 Thunk에 대해서 배우지는 않았지만 이번 기회에 사용법을 한 번 익히고 로직을 살펴보면 좋을 것 같아 사용해보기로 했는데 배우지 않은 부분을 시도하려니 생각보다 어려워 개념을 한 번 정리해보려고 한다.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { userAPI } from './userAPI'
const fetchUserById = createAsyncThunk(
// string action type value: 이 값에 따라 pending, fulfilled, rejected가 붙은 액션 타입이 생성된다.
'users/fetchByIdStatus',
// payloadCreator callback: 비동기 로직의 결과를 포함하고 있는 프로미스를 반환하는 비동기 함수
async (userId, thunkAPI) => {
const response = await userAPI.fetchById(userId);
return response.data;
},
// 세 번째 파라미터로 추가 옵션을 설정할 수 있다.
// condition(arg, { getState, extra } ): boolean (비동기 로직 실행 전에 취소하거나, 실행 도중에 취소할 수 있다.)
// dispatchConditionRejection: boolean (true면, condition()이 false를 반환할 때 액션 자체를 디스패치하지 않도록 한다.)
// idGenerator(): string (requestId를 만들어준다. 같은 requestId일 경우 요청하지 않는 등의 기능을 사용할 수 있게 된다.)
);
createAsyncThunk는 thunk action creator를 반환한다.
위의 경우를 예로 들면, 다음 세 가지 thunk action creator가 반환된다.
이 액션들이 디스패치되면, thunk는 아래 과정을 실행한다.
slice 만들기
const usersSlice = createSlice({
name: 'users',
initialState: { entities: [], loading: 'idle' },
reducers: {
// standard reducer logic, with auto-generated action types per reducer
},
extraReducers: (builder) => {
// Add reducers for additional action types here, and handle loading state as needed
builder.addCase(fetchUserById.fulfilled, (state, action) => {
// Add user to the state array
state.entities.push(action.payload);
})
},
});
이렇게 만든 thunk와 slice는 다음과 같이 컴포넌트에서 사용할 수 있다.
dispatch(fetchUserById(123));
앞서 말했듯, createAsyncThunk는 결과에 상관없이 무조건 항상 이행된 프로미스를 반환한다. 따라서, 오류 처리는 별도의 방법을 사용해서 진행해야 한다.
디스패치된 thunk가 반환한 이행된 프로미스는 unwrap 프로퍼티를 가지고 있는데, 이를 사용해서 오류 처리를 할 수 있다.
const onClick = async () => {
try {
const originalPromiseResult = await dispatch(fetchUserById(userId)).unwrap();
// handle result here
} catch (rejectedValueOrSerializedError) {
// handle error here
}
}
rejectedValue(value) 함수를 사용해서 createAsyncThunk 내부에서 오류 처리를 할 수도 있다.
const updateUser = createAsyncThunk(
'users/update',
async (userData, { rejectWithValue }) => {
const { id, ...fields } = userData;
try {
const response = await userAPI.updateById(id, fields);
return response.data.user;
} catch (err) {
// Use `err.response.data` as `action.payload` for a `rejected` action,
// by explicitly returning it using the `rejectWithValue()` utility
return rejectWithValue(err.response.data);
}
}
);
참고 자료