[Team Project] 뉴스피드 프로젝트 (1)

liinyeye·2024년 6월 3일
0

Project

목록 보기
13/44
post-thumbnail

맛집 공유 뉴스 피드 사이트

이 프로젝트는 React와 Supabase를 사용하여 맛집을 공유하고 리뷰를 작성할 수 있는 뉴스 피드 사이트입니다.

목차

프로젝트 소개

이 프로젝트는 사용자들이 맛집을 공유하고, 다른 사용자의 리뷰를 확인하며, 자신만의 맛집 리뷰를 작성할 수 있는 플랫폼을 제공합니다. React로 프론트엔드를 구성하고, Supabase로 백엔드와 데이터베이스를 관리합니다.

기능

  • 사용자 인증: 회원가입, 로그인, 로그아웃 기능
  • 맛집 게시물: 맛집에 대한 게시물 작성, 수정, 삭제 기능
  • 리뷰 작성: 각 게시물에 대한 리뷰 작성 및 조회 기능
  • 팔로우 기능: 게시물 작성자에 대한 팔로우 기능
  • 마이 페이지: 사용자가 작성한 글 및 팔로우한 사람의 글 보기 기능
  • 실시간 데이터 업데이트: Supabase의 실시간 데이터베이스 기능을 활용한 실시간 업데이트

이번 프로젝트에서 내가 맡은 부분은 사용자 인증 부분이다. 아직 Thunk에 대해서 배우지는 않았지만 이번 기회에 사용법을 한 번 익히고 로직을 살펴보면 좋을 것 같아 사용해보기로 했는데 배우지 않은 부분을 시도하려니 생각보다 어려워 개념을 한 번 정리해보려고 한다.

thunk 만들기

createAsyncThunk

  • 액션 타입 문자열, 프로미스를 반환하는 비동기 함수, 추가 옵션 순서대로 인자를 받는 함수다.
  • 입력받은 액션 타입 문자열을 기반으로 프로미스 라이프사이클 액션 타입을 생성하고, thunk action creator를 반환한다.
  • thunk action creator: 프로미스 콜백을 실행하고 프로미스를 기반으로 라이프사이클 액션을 디스패치한다.
  • 리듀서를 생성해주는 기능은 없기 때문에 액션들을 처리할 로직을 직접 작성해야 한다.
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가 반환된다.

  • fetchUserById.pending: 'users/fetchByIdStatus/pending' 액션을 디스패치하는 thunk action creator
  • fetchUserById.fulfilled: 'users/fetchByIdStatus/fulfilled' 액션을 디스패치하는 thunk action creator
  • fetchUserById.rejected: 'users/fetchByIdStatus/rejected' 액션을 디스패치하는 thunk action creator

이 액션들이 디스패치되면, thunk는 아래 과정을 실행한다.

  • pending 액션을 디스패치한다.
  • payloadCreator 콜백을 호출하고 프로미스가 반환되기를 기다린다.
  • 프로미스가 반환되면, 프로미스의 상태에 따라 다음 행동을 실행한다.
    - 프로미스가 이행된 상태라면, action.payload를 fulfilled 액션에 담아 디스패치한다.
    - 프로미스가 거부된 상태라면, rejected 액션을 디스패치하되 rejectedValue(value) 함수의 반환값에 따라 액션에 어떤 값이 넘어올지 결정된다.
    - rejectedValue가 값을 반환하면, action.payload를 reject 액션에 담는다.
    - rejectedValue가 없거나 값을 반환하지 않았다면, action.error 값처럼 오류의 직렬화된 버전을 reject 액션에 담는다.
  • 디스패치된 액션이 어떤 액션인지에 상관없이, 항상 최종적으로 디스패치된 액션을 담고 있는 이행된 프로미스를 반환한다.

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);
    }
  }
);

참고 자료

profile
웹 프론트엔드 UXUI

0개의 댓글