Redux-Toolkit

chaaerim·2022년 7월 4일
0

Redux

목록 보기
2/2

redux-toolkit

리덕스 팀에서 공식적으로 만든 라이브러리
리덕스 내부에 thunk, immer가 내장되어있음. 리덕스의 총집합이라고 생각해도 됨.

Redux Toolkit 공식 문서

Reducers and Actions에서 createSlice, createAsyncThunk만 사용해도 충분함.


그냥 공부하면서 정리해본 rtk 사용법 한 줄.

스토어 만들고 리액트 코드에서 디스패치해줌.
그러면 디스패치해준 데이터가 이미 생성해 놓은 createAsyncThunk에 들어옴. 그럼 여기서 리듀서이자 action인 slice를 부르는거지. immer를 사용하지 않아도 immer가 포함되어있으므로 편함.
동기인 작업은 actions(slices 내부에 적은)에서 불러오면 되고 비동기인 작업은 thunk로 생성하고 slices에서 불러오면 됨.


store.js

const { configureSotre } = require('@reduxjs/toolkit');
const reducer = require('./reducers');
const { addPost } = require('./actions/post');
const { logIn, logOut } = require('./actions/user');

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

const store = configureSotre({
  reducer,
  middleware: [firstMiddleware, ...getDefaultMiddleware()],
  devTools: true,
});

module.exports = store;
  • thunk도 내장되어있고, devtool도 내장. rtk는 기본적인 것들이 다 설정이 되어있기 때문에 reducer하나만 연결해주면 됨.
  • custom middleware와 rtk에 포함된 thunk도 함께 쓰고 싶다면 스프레드 연산자를 이용해서 getDefaultMiddleware()로 기존 미들웨어까지 가져와야 함.
  • devTools를 사용할 거면 true로 설정.

const store = configureSotre({
  reducer,
  preloadState:
});
  • SSR 할 때 서버로부터 initialstate가 오면 그 때 preloadState 넣어주면 됨.

slice

slice는 단순히 reducer뿐만 아니라 다양한 개념을 합친 것. reducer, initialState, action도 합친 것.
로그인 action은 user만 바꾸는 경우가 많음.
대부분의 action은 하나의 reducer에 종속되므로 하나로 합쳐버리자..! 하고 나온 것이 slice.
slice의 reducers는 동기적이라고 보면 되고, extraReducers는 비동기적이라 보면 됨. reducer에는 내부적인 action들을 넣고 extraReducers에는 외부에서 사용하는 action들을 넣음.


reducers/index.js

const { combineReducers } = require('redux');
const userSlice = require('./user');
const postSlice = require('./post');

module.exports = combineReducers({
  user: userSlice.reducer,
  posts: postSlice.reducer,
});
  • index.js에서는 slice를 바로 넣어주는 것이 아니라 slice 내부에 있는 reducer를 넣어주는 것임을 주의!!!
  • slice는 reducer와 action을 묶은 것.

reducers/user.js

const userSlice = createSlice({
  name: 'user',
  initialState,
  reducers: {
    logOut(state, action) {
      state.data = null;
    },
  },
});
  • 보통은 logOut이라는 액션을 만들어서 사용했었는데 이제 액션을 따로 만들 필요가 없음. toolkit이 알아서 해주므로.
  • 슬라이스에서 리듀서 안에 logOut이라는 함수를 하나 만들어줬을 뿐인데 알아서 slice가 logOut 액션을 만들어줌.

App.jsx

const onLogout = useCallback(() => {
    dispatch(userSlice.actions.logOut());
  }, []);
  • userSlice로부터 actions 꺼내서 logOut()을 해주면 툴킷이 알아서 logOut action을 만들어줌.

  • 동기액션들은 거의 slice의 reducer내부에 들어있으므로 rtk를 사용하면 actions에는 비동기 액션의 공간이 될 것.


actions/user.js

const logIn = createAsyncThunk('user/logIn', async(data, thunkAPI)=>{
    const state=thunkAPI.getState()
});
  • 첫번째 매개변수에는 액션의 이름(비동기 액션)을 적어주고 두번째 매개변수에는 액션을 호출할 때 받는 데이터와 thunkAPI를 적어줌.
  • thunkAPI를 이용하여 현재 상태를 가져올 수 있어서 state.user.data와 같이 접근 가능.
  • thunk에서는 request, success, failure대신 pending, fulfilled, rejected로 사용!!!!!!!
  • createAsyncThunk에는 비동기 액션을 적어 줌.
  • 액션 이름에 따라 리듀서에 접근.

비동기 처리

exports.logIn = createAsyncThunk('user/logIn', async (data, thunkAPI) => {
    // throw new Error('비밀번호가 틀렸습니다.');
    return await delay(500,{
      userId: 1,
      nickname: 'chaaerim'
    });
  });
  • createAsyncThunk에서는 try catch문을 사용 안하는 것이 좋음. 실패를 해야 rejected 상태로 가기 때문에 에러가 없으면 무조건 성공상태로 가기 때문에.

reducers/user.js

const userSlice = createSlice({
  name: 'user',
  initialState,
//동기 액션
  reducers: {
    logOut(state, action) {
      state.data = null;
    },
  },
  extraReducer: {
    [logIn.pending](state, action) { //user/logIn/pending
      state.isLoggingIn = true;
    },
    [logIn.fulfilled](state, action) {
      //action에 들어있는 데이터는 이제 payload라고 부름.
      state.date = action.payload;
      state.isLoggingIn = false;

    },
  [logIn.rejected](state, action) {
      state.data = null;
      state.isLoggingIn = false;
    },
});
  • action안에 이메일, 비밀번호도 넣음 그러나 toolkit은 액션에 대한 데이터는 action.payload로 다 합쳐놓음 action.payload.email의 형식으로 접근해야함.
  • 외부 액션은 extraReducer에 넣어줌.
  • [logIn.pending](state, action) createAsyncThunk에서 user/logIn이라고 액션 이름을 붙여줬지만 실제 이름은 /user/logIn/pending 이런 식으로 생성된다고 보면 됨. 따라서 logIn.pending, logIn.fulfilled로 접근 가능. → 변수명에 대한 고민이 줄어듦.

reducers/post.js

const { createSlice } = require('@reduxjs/toolkit');
const { addPost } = require('../actions/post');

const initialState = {
  list: [],
};

const postSlice = createSlice({
  name: 'post',
  initialState,
  reducers: {},
  extraReducers: (builder) =>
    builder
      .addCase(addPost.pending, (state, action) => {})
      .addCase(addPost.fulfilled, (state, action) => {
        state.list.push(action.payload);
      })
      .addCase(addPost.rejected, (state, action) => {}),
});

module.exports = postSlice;
  • slice를 쓰는 또 다른 방법이 builder가 있음.
  • addCase를 사용. 이렇게 써야 나중에 ts를 쓸 때 더 타입추론이 잘 됨.
  • builder를 사용하는 것을 더 추천.
  • .addCase(addPost.fulfilled, (state, action) => {
    state.list.push(action.payload);
    }) 성공했을 때 받은 데이터를 initialState에 차곡차곡 쌓아주는 액션

builder의 addMatcher

.addMatcher(
        (action) => {
          return action.type.include('/pending');
        },
        (state, action) => {
          state.isLoading = true;
        }
      ),
  • 공통으로 처리해주고 싶은 부분이 있을 때 사용.
  • 예를 들어 loading을 공통적으로 처리해주겠다 할 때에는 isLoading을 모두 true로 설정.
  • return action.type.include('/pending'); 이부분이 true인 액션들은 모두 state.isLoading = true;가 실행.

리덕스를 쓰지 말아야 할 때

하나의 컴포넌트 내부에서 데이터를 사용할 때 언제 리덕스를 쓰고 언제 쓰면 안될까..?

인풋에서는 리덕스를 안쓰는 것이 좋음.
인풋창에 리덕스를 사용하게 되는 경우 글자 하나를 칠 때마다 action이 발생하게 됨. 마지막에 받은 결과에만 리덕스를 적용하는 것이 좋음.


App.js

const email=useSelector((state)=>state.user.email);
const password=useSelector((state)>state.user.password)

const onSubmit=useCallback((e)=>{
    e.preventDefault();
    dispatchEvent(userSlice.actions.setLoginForm({
        email, password
    }))
}, [dispatch, email, password])
  • 위와 같이 onSubmit에만 리덕스를 적용하여 완료된 결과만 dispatch해주는 것이 좋음.
  • 즉 인풋창의 값들은 useState을 이용하여 먼저 저장하고 마지막 결과값만 보내주는 것.
  • 불필요한 리렌더링을 방지하기 위해서 initialState에서 값은 객체가 아닌 요소 하나하나 가져오는 것이 좋음. 즉 const {email, password}=useSelector((state)=>state.user)의 형태로 가져오지 말자..!
    → 왜냐하면 객체로 가져오면 내가 지금 사용하지 않는 initialState의 요소가 바뀌어도 리렌더링이 발생할 수 있기 때문에.

비동기서버요청이 컴포넌트 하나에서만 실행된다면 그리고 다른 컴포넌트에는 아예 영향을 미치지 않는다면 비동기 액션으로 만들지 않는 것이 좋음. 그냥 state과 useCallback을 이용해서 처리. 이럴 때에는 그냥 바로 axios.post를 하는 것이 좋음.

axios는 서버에 요청을 보내는 라이브러리.
→ 그러나 axios만 사용해도 리덕스와 비슷하게 구현하는 것이 좋음.


axios 예제 코드

const onClick = useCallback(async () => {
    setLoading(true);
    setDone(false);
    setError(false);
    try {
      const response = await axios.post('/login');
      setDone(done);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  }, []);

0개의 댓글