[Next.js] Next.js 13버젼 + Redux Thunk

S_Soo100·2023년 8월 10일
0

web

목록 보기
6/6
post-thumbnail

이전 글: Next.js 13버젼 + 리덕스 툴킷
참고자료


Redux Thunk?

  • Redux를 사용할 때 모든 상태관리는 reducer로 상태를 바꾸고 selector로 읽는다.
    하지만 비동기를 사용 할 때 그대로 reducer로 처리하면 리듀서에 억지로 비동기 작업을 붙여야 하고 이것은 안티패턴이 된다.
    이런 때에 Redux Thunk를 사용하여 비동기 요청을 처리한다.
    사실 진짜 간단하니까 이 글을 통해 기본만 봐도 응용할 수 있을 것이다.

  • 설치부터 하자고?
    리덕스 툴킷에는 비동기 로직을 위한 Thunk 애드온이 기본적으로 포함되어 있다.


Redux Thunk의 적용 순서

(구현 1) Redux Thunk로 reducer만들기

  • slice의 reducers에 정의하는게 아니라 createAsyncThunk()메서드를 통해 따로 먼저 선언해줘야 한다.
    createAsyncThunk는 액션 타입 문자열과 프로미스를 반환하는 함수를 받아서
    pending/fulfilled/rejected의 3가지 액션 타입을 디스패치해주는 thunk를 생성해준다.

    이전 글에서 작성한 logIn 코드를 Thunk 리듀서로 바꾸며 비교해보자

    src/redux/slices/auth-slice.ts

    logIn: (State, action: PayloadAction<string>) => {
    	return {
          value: {
           isAuth: true, // 로그인 했으니까 true
           username: action.payload,
           uid: "uid",
           isModerator: false,
    	},
    };

    위 코드가 저번 리덕스 툴킷에서 만들어둔 logIn 리듀서이다.
    createSlice()reducers: {}안에 선언했었지만 이제 밖으로 빼서 작성해야 한다.

    src/redux/slices/auth-slice.ts

    const logIn = createAsyncThunk(
     'auth/logIn',
       async (username: number, thunkAPI) => {
           const response = await userAPI.fetchByUsername(username); 
         	// 별도로 외부에 api 호출을 하는 함수를 만들자 
           return response.data;
    	}
    );
  • 파라미터는 2개이다.
    1. 이름(string)
    2. 비동기 payloadCreator 함수

  • 이름은 통상적으로 "slice이름/함수이름"으로 선언한다.
    그리고 비동기 함수는 thunkAPI는 기본으로 받되,
    필요한 다른 파라미터를 같이 넣어주자.

    userAPI는 src/api/userApi.ts 등에 별도로 비동기 호출을 하는 함수를 만들자.
    이번 글은 thunk를 정리하기 위한 글이니 생략하지만,
    Fake Store API를 사용해서 테스트 하면 쿠키 등이 없어도 바로 샘플 유저 데이터를 가져올 수 있는 등 편하다


(구현 2) slice 연결

  • 이제 slice 안에 thunk reducer를 넣자.
    기존처럼 reducers가 아니라 extraReducers에 추가해야 한다.
    우선은 코드를 보면서 따라가자.

    src/redux/slices/auth-slice.ts

const initialState = {
  value: {
    id: 0,
    username: "",
    email: "",
    isAdmin: false,
    isStaff: false,
  } as IAuth,
  status: "initial",
} as InitialState;

export const auth = createSlice({
  name: "auth", // slice name
  initialState, // initial state
  reducers: { // 기존 비동기가 아닌 리듀서
    logOut: () => {
      return initialState; // 이전에 만든 로그아웃 기능
    },
  },
  extraReducers: (builder) => { //비동기 리듀서들
    builder.addCase(logIn.pending, (state) => {
      state.status = "loading";
    });
    builder.addCase(logIn.fulfilled, (state, action) => {
      state.value = action.payload;
      state.status = "complete";
    });
    builder.addCase(logIn.rejected, (state) => {
      state.status = "error";
    });
  },
});
  • addCase로 리듀서의 상태에 따라 행동을 정의해주는데,
    ${리듀서 이름}뒤에 비동기 리듀서의 3가지 상태에 따라 다른 행동을 정의해줄 수 있다.
    1. pending : 아직 처리되지 않고 로딩 혹은 지연되는 상태
    2. fulfilled : 비동기 처리가 완료된 상태
    3. rejected : 비동기 처리가 완료되지 않고 실패한 상태

  • 위 코드로 보면 logIn.pending과 logIn.rejected에서는 state말고 action값이 오지 않는다. 비동기를 완료해야 요청한 action값이 주어지기 때문.

(구현 3) 비동기 호출

  • 이제 열심히 배관공사를 했으니 물을 틀어보자.
    dispatch에서 아까 만든 리듀서를 호출하면 된다.
    상위 컴포넌트에서 selector로 user가 있는지를 확인하고,
    없으면 아래 예시 코드를 보여주자.
export default function LogIn() {
  const [username, setUsername] = useState<string>("");
  const [password, setPassword] = useState<string>("");
  
  const dispatch = useDispatch<AppDispatch>();
  
  const onClickLogIn = () => {
    dispatch(logIn({ username: username, password: password }));
  };

  return (
    <div className="items-center gap-2 ">
      <input
        className={style.inputStyle}
        type="text"
        onChange={(e) => setUsername(e.target.value)}
      />
      <input
        className={style.inputStyle}
        type="password"
        onChange={(e) => setPassword(e.target.value)}
      />
      <button className={style.buttonStyle} onClick={onClickLogIn}>
        Log In
      </button>
    </div>
  );
}


profile
플러터, 리액트

4개의 댓글

comment-user-thumbnail
2023년 8월 10일

13 버전 글이 잘 없는데 좋은 글 잘 보고 갑니다 :)

1개의 답글
comment-user-thumbnail
2023년 8월 10일

많은 도움이 되었습니다, 감사합니다.

1개의 답글