반응형
250x250
Notice
Recent Posts
Recent Comments
Link
관리 메뉴

짧은코딩

redux-toolkit 본문

인프런, 유데미/Redux

redux-toolkit

5_hyun 2022. 11. 19. 01:31

redux-toolkit

redux-toolkit에는 immer, thunk, redux 등 개발자들이 많이 사용하는 라이브러리가 기본적으로 내장되어있다.

 

-공식 사이트

https://redux-toolkit.js.org/introduction/getting-started

 

Getting Started | Redux Toolkit

 

redux-toolkit.js.org

제로초님은 공식 사이트에서 createSlice, createAsyncThunk를 읽어보는 것을 강력 추천한다고 한다. 왜냐하면 저 2개가 실무에서 가장 많이 사용되기 때문이다.

사용하기

-store.js

const { configureStore } = require("@reduxjs/toolkit");

const reducer = require("./reducers");

const firstMiddleware = (store) => (next) => (action) => {
  console.log("로깅", action);
  next(action);
};

const store = configureStore({
  reducer,
  // 커스텀 미들웨어를 넣고 기존 미들웨어도 넣으려면 concat() 안에 적으면 된다.
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(firstMiddleware),
  devTools: process.env.NODE_ENV !== "production",
  // 이건 initialState인데 ssr에서만 사용한다.
  // preloadedState
});

module.exports = store;

기존의 store.js에 비해 양이 확 줄어든다. redux를 쓰면 코드가 늘어난다는 단점을 redux-toolkit이 어느 정도 보완해줬다.

store에서는 middleware, devTools 등의 설정을 해줄 수 있다.

actions

const { createAsyncThunk } = require("@reduxjs/toolkit");

const delay = (time, value) =>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(value);
    }, time);
  });

//data는 로그인 할 유저의 데이터
const logIn = createAsyncThunk("user/logIn", async (data, thunkAPI) => {
  console.log(data);

  // 서버가 보내준 응답이라고 보면된다
  // try, catch가 없으면 thunk가 무조건 fulfilled(성공) 상태로 간다.
  // createAsyncThunk에서는 try, catch를 안 붙이는 것이 좋다.
  const result = await delay(
    500,
    // 이 값들이 reducers에서 action.payload
    {
      userId: 1,
      nickname: "zerocho",
    }
  );
  // 서버에서 온 응답이 fulfilled 전달이 된다.
  return result;
});

module.exports = {
  logIn,
};

createAsyncThunk로 외부 요청을 만들 수 있다. delay는 서버가 없어서 만든 임시의 동기 코드이다.

createAsyncThunk에서는 웬만하면 try, catch를 써주지 않는 것이 좋다.

reducers

-index.js

const { combineReducers } = require("redux");
// slice는 단순히 reducer뿐만 아니라 reducer, action, initialState 등이 들어있다.
// 보통 action은 reducer에 종속되어 있다. 이것을 나누지말고 합쳐버리자라는 의미에서 slice가 나왔다. 
// 그룹으로 만들자는 느낌
const userSlice = require("./user");
const postSlice = require("./post");

module.exports = combineReducers({
  user: userSlice.reducer,
  posts: postSlice.reducer,
});

slice를 사용해서 사용할 수 있다. slice는 단순 reducer가 아니라 reducer, action, initialState 등이 포합되어 있다.

보통 action은 reducer에 종속되어 있으니 다 합치자는 개념으로 만들어진 것이 slice이다.

 

-user.js

const { createSlice } = require("@reduxjs/toolkit");
const { logIn } = require("../actions/user");

const initialState = {
  isLoggingIn: false,
  data: null,
};

const userSlice = createSlice({
  name: "post",
  initialState,
  // 동기적, 내부적인 것(postReducer 안에 있는 actions)
  reducers: {
    // 요렇게 만들면 toolkit이 알아서 action을 만들어줘서 action을 굳이 만들 필요가 없다.
    logOut(state, action) {
      state.data = null;
    },
  },
  // 비동기적, 외부적인 것(postReducer 밖에 있는 actions, 보통 네트워크 요청/setTimeout)
  // 이제 actions 폴더는 동기 요청을 위한 폴더가 될 것이다.
  // 여기에 있는 것들은 외부에 있는 것이라서 다른 reducer에 가져가서 사용할 수 있다.
  extraReducers: {
    // 객체의 동적 다이나믹 속성
    [logIn.pending](state, action) { // user/logIn/pending
      // 로그인중에 나타나는 빙글빙글 모양이라 생각하면된다.
      state.isLoggingIn = true;
    },
    [logIn.fulfilled](state, action) { // user/logIn/fulfilled
      // action의 data를 action.payload라고 표현한다.
      state.data = action.payload;
      state.isLoggingIn = false;

    },
    [logIn.rejected](state, action) { // user/logIn/rejected
      state.data = null;
      state.isLoggingIn = false;
    },
  },
});

module.exports = userSlice;

이제 createSlice를 사용한다. 

 

1. reducers: 동기적이며 userReducer 안에 있는 것을 사용한다. 또한 reducer를 만들면 알아서 action을 만들어준다.

2. extraReducers: 비동기적이며 userReducer 밖에 있는 것을 사용한다. 예를 들면 네트워크 요청, setTimeout 같은 것을 사용한다. 이제 actions 폴더는 비동적 reducer를 위한 파일이 된다. 혹은 thunk로 만든 것들도 이곳에 넣는다.

extraReducers 안에서는 pending, fulfilled, rejected로 상태를 파악할 수 있다. 그리고 action의 data는 action.payload로 표현할 수 있다. 만약 id를 가져오고 싶으면 action.payload.id로 가져오면된다.


devtools에서 상태 보기

devtools의 Action Tree를 보면 payload, meta가 있다. 실패한 경우에는 Action Tree에 erroer도 있다.

성공했을 땐 payload,실패했을 땐 error가 뜬다.

meta.arg: 처음에 넣어줬던 데이터

meta를 보면 requestId를 자동으로 설정해준다. 따라서 어떤 요청이 실패하고 성공했는지 알 수 있다.

불변성이 깨지는 경우

immer를 쓰다보면 불변성이 깨질때도 있다.

    [addPost.pending](state, action) {
      state = 123;
      return state;
    }

새로운 값을 넣어야 해서 불변성이 깨지면 return을 해주면 된다.

extraReducers의 builder

  extraReducers: (builder) =>
    builder
      .addCase(addPost.pending, (state, action) => {})
      .addCase(addPost.fulfilled, (state, action) => {
        state.data.push(action.payload);
      })
      .addCase(addPost.rejected, (state, action) => {})
      .addMatcher(
        (action) => {
          return action.type.includes("/pending");
        },
        (state, action) => {}
      )
      .addDefaultCase((state, action) => {
        //default
      }),

extraReducer에서 builder을 사용하는 것이 좋다. 왜냐하면 TypeScript로 코딩을 할 때, builder가 타입 추론을 잘해주기 때문이다.

 

-addMatcher

코딩을 하다보면 (pending, fulfilled, rejected)이 3개가 계속 세트를 이루는데, 예를 들어 모든 pending에 똑같은 작업을 처리해 주고 싶을 때 사용할 수 있다. 혹은 switch문에서 case를 2개를 연달아 사용하는 경우처럼도 사용할 수 있다.

 

-addDefaultCase

switch문에서 default를 작성해주는 것처럼 사용이 가능하다.

728x90
반응형
Comments