React) Redux-toolkit에서 서로 다른 slice의 state 동시에 수정하기

2ast·2022년 10월 23일
0
post-custom-banner

문제 상황

여러개로 흩어진 state들 사이에는 간혹 서로 긴밀하게 연결된 값들이 존재할 수 있다. 예를들어 우리 학급의 정보를 다루는 classSlice와 각 학생들의 정보를 다루는 studentSlice가 있다고 해보자. class의 state 중에는 전체 학생들의 봉사시간 총 합을 다루는 totalVolunteerTime이 있고, student state 중에는 각 학생들의 봉사시간을 다루는 volunteerTime이 있다. 이때 학생 totalVolunteerTime과 volunteerTime은 서로 긴밀하게 연결되어 있다. volunteerTime에 변화가 생기면 이 변화는 반드시 class state에도 반영이 되어야만 한다.

이 문제를 어떻게 해결할 수 있을까? 가장 간단하게 생각해볼 수 있는것은 그냥 dispatch를 두번 호출하면 된다. totalVolunteerTime을 수정하는 reducer를 classSlice에 구현하고, studentSlice에는 volunteerTime을 수정하는 reducer를 정의한 다음 특정 이벤트가 발생되었을 때 각각의 dispatch를 날리는 것이다.

onClick={()=>{
	dispatch(editVolunteerTime({id,time:15}))
  	dispatch(editTotalVolunteerTime(15))
}}

하지만 이 해결책은 뭔가 찝찝하다. 각 학생의 봉사시간이 변경될 때마다 학급 전체의 봉사시간은 반드시 수정되어야한다. 이 사실이 명확한데도, slice가 구분되어있다는 이유로 각각 dispatch를 날려주어야 하는 것이다. 만약 서로다른 5개의 state가 연관되어 있다면 매번 5개의 dispatch를 묶어서 날려야하는 상황이 연출될 수도 있다. 단 한번의 dispatch만 보내면 자동으로 연관된 상태들도 함께 업데이트 해줄 수 있는 방법을 찾고 싶어졌다.

extraReducers에 답이 있다.

가장 먼저 찾아본 방법은 studentSlice의 reducer에서 classSlice의 state를 참조하여 그 값을 변경할 수 있는가 하는 것이었다. 하지만 내가 찾아본 결과 해당 방법은 불가능했다. 다만 extraReducers를 사용해 동일한 기능을 구현할 수 있음을 알게 되었다.
redux-toolkit 공식문서의 extraReducers 항목을 보면 이 부분에 대해 자세하게 설명하고 있다.

One of the key concepts of Redux is that each slice reducer "owns" its slice of state, and that many slice reducers can independently respond to the same action type. extraReducers allows createSlice to respond to other action types besides the types it has generated.

redux에서 각 slice의 reducer는 자신만의 state에만 접근가능하기 때문에 독립적으로 상태를 처리할 수 있으며, 만약 외부 action type에 대응하려면 extraReducers를 사용하라는 내용이다.

즉, classSlice에 extraReducers를 설정하면 external actions(studentSlice의 editVolunteerTime action)가 발생했을 때 이에 따른 state 변경이 가능해진다는 뜻이다.

실제 코드에 적용해보자

조금 더 간단한 예시를 위해 봉사시간이 아니라 단순 counter와, counter * 2의 값을 갖는 twiceCounter를 만들어 사용해보려고 한다.

//counterSlice

import { createSlice } from "@reduxjs/toolkit";

export interface CounterState {
  value: number;
}

const initialState: CounterState = {
  value: 0,
};

export const counterSlice = createSlice({
  name: "counter",
  initialState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    }
  }
});


export const { increment, decrement } =
  counterSlice.actions;

export default counterSlice.reducer;
// twiceCounterSlice

import { createSlice } from "@reduxjs/toolkit";
import { decrement, increment } from "./counterSlice";

export interface CounterState {
  value: number;
}

const initialState: CounterState = {
  value: 0,
};

export const twiceCounterSlice = createSlice({
  name: "twiceCounter",
  initialState,
  reducers: {},
  extraReducers: {
    [increment]: (state) => {
      state.value += 2;
    },
    [decrement]: (state) => {
      state.value -= 2;
    },
  },
});

export default twiceCounterSlice.reducer;

counterSlice에 increment와 decrement reducers를 정의했고, twiceCounterSlice에서는 increment와 decrement가 호출되었을 때 실행할 reducer를 extraRecucers에 정의해주었다.

이제 프로젝트 내부에서 counterSlice의 increment가 호출되면 twiceCounterSlice의 extraReducers에서 정의한 대로 state.value +=2도 함께 호출될 것이다.

결과 확인

이를 확인하기 위해 간단한 화면을 구성해보았다.

import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { decrement, increment } from "./app/features/counter/counterSlice";
import { RootState } from "./app/store";

const App = () => {
  const counter = useSelector((state: RootState) => state.counter.value);
  const twiceCounter = useSelector(
    (state: RootState) => state.twiceCounter.value
  );
  const dispatch = useDispatch();
  
  return (
    <div>
      <div>
        <button onClick={() => dispatch(decrement())}>감소</button>
        {"counter: " + counter}
        <button onClick={() => dispatch(increment())}>증가</button>
      </div>

      <div>{"twice: " + twiceCounter}</div>
    </div>
  );
};

export default App;

잘 작동한다.

이제 서로다른 slice의 state를 변경하기 위해 각각 action을 dispatch해줄 필요 없이 한번의 dispatch로 연관된 state를 최신화할 수 있게 되었다.

profile
React-Native 개발블로그
post-custom-banner

0개의 댓글