[Redux] Redux-toolkit (RTK)

Wonhyun Kwon·2023년 6월 7일
0

Redux

목록 보기
4/5
post-thumbnail

1. Redux-toolkit ?

Redux 팀에서 공식적으로 만든 가장 최신 상태 관리 라이브러리이다.

Redux-toolkit (이하 RTK) 은 아래와 같은 특징을 가진다.

  • 반복적이고 번거로운 작업을 최소화한다.
  • 코드의 가독성을 높였다.
  • 유지 보수성을 향상시킬 수 있는 유틸리티 함수와 패턴이 제공된다.



2. 상태 관리 도구들 간의 관계

지금까지 배웠던 Redux, React-redux 그리고 지금 RTK 까지 Redux 관련하여 많은 상태 관리 도구들이 존재한다.
상태 관리를 더욱 효율적이고 사용하기 쉽게 한다는 목적은 동일한데, 과연 무엇이 다른걸까?


1. Redux

ReduxReact 와는 직접적인 관계는 없다.
JavaScript 로 된 것이라면 어디서든지 사용할 수 있다.
반대로 이야기하자면, React 에서 사용하기엔 다소 불편한 점이 많다.

자주 사용하는 메소드는 다음과 같다.

  • createStore
  • subscribe
  • getState
  • dispatch

2. React-redux

Redux 의 본질적인 개념을 토대로 React 에 맞춰 상태 관리를 도와주는 도구이다.
사용 방법이나 기본 원리는 모두 Redux 에 기반하기에 동일하다고 봐도 무방하다.
다만, 컴포넌트 단위에서 작업이 이뤄지는 React 의 특성에 맞게 그에 맞는 메소드를 제공해주는 차이점이 있다.

자주 사용하는 메소드는 다음과 같다.

  • Provider
  • useDispatch
  • useSelector

3. Redux-toolkit (RTK)

React-redux 라는 React 에 맞게 딱 맞게 나온 라이브러리임에도 불구하고 다음과 같이 여전히 많은 불편한 점이 많았다.

  • 설정할 것이 많다.
  • 미들웨어를 설치 및 설정해야 하고, 그 것에 대한 학습 비용이 존재한다.
  • 반복되는 코드가 많다. (ex. Action creator)
  • 불변성 유지의 어려움이 존재한다. (순수 JavaScript 로 짜기엔 난이도가 높다. 라이브러리를 사용하더라도 학습 비용이 존재한다.)

바로 이러한 문제점들을 타파하고자 나온 것이 바로 Redux-toolkit 이다.




3. slice

기존의 Redux 는 거대한 하나의 store 에 모든 데이터를 담았다.
하지만 프로젝트가 커지면 기능 별로 작은 store가 필요할 것이다.
이 작은 store를 slice 라고 칭한다.
물론, Redux 가 근본적으로 추구하는 하나의 거대한 store 의 정체성은 지킨다. configureStore 라는 메소드로 작은 store인 slice 를 한데 모아 하나의 큰 store 에 취합한다.




4. 예제

기존 라이브러리와 차이점을 알아보기 위해 + 버튼을 눌렀을 때 값이 증가하는 예제를 만들어 본다.

기존의 Redux, React-redux로 구현한 코드는 다음과 같다.
ReduxcreateStore 를 이용하여 store 를 한 군데 모으고, React-reduxuseSelector , useDispatch 를 이용하여 상태를 업데이트 하는 코드이다.

import {Button, SafeAreaView, Text, View} from 'react-native';
import {createStore} from 'redux';
import {Provider, useSelector, useDispatch} from 'react-redux';

const reducer = (state, action) => {
  if (action.type === 'up') {
    return {...state, value: state.value + action.step};
  }
  return state;
};

const initialState = {
  value: 0,
};
const store = createStore(reducer, initialState);

const Counter = () => {
  const dispatch = useDispatch();
  const count = useSelector(state => state.value);

  return (
    <SafeAreaView>
      <View>
        <Text>{count}</Text>
        <Button
          title="+"
          onPress={() => {
            dispatch({
              type: 'up',
              step: 2,
            });
          }}></Button>
      </View>
    </SafeAreaView>
  );
};

function App(): JSX.Element {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

이제 위 코드를 RTK 를 이용하여 만들어보자.

import {Button, SafeAreaView, Text, View} from 'react-native';
import {Provider, useSelector, useDispatch} from 'react-redux';
import {createSlice, configureStore} from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: {
    value: 0,
  },
  reducers: {
    up: (state, action) => {
      state.value = state.value + action.payload;
    },
  },
});

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
  },
});

const Counter = () => {
  const dispatch = useDispatch();
  const count = useSelector(state => {
    return state.counter.value;
  });

  return (
    <SafeAreaView>
      <View>
        <Text>{count}</Text>
        <Button
          title="+"
          onPress={() => {
            dispatch(counterSlice.actions.up(2));
          }}></Button>
      </View>
    </SafeAreaView>
  );
};

function App(): JSX.Element {
  return (
    <Provider store={store}>
      <Counter />
    </Provider>
  );
}

export default App;

코드를 하나씩 떼어서 보자.

1) createSlice

createSlice 는 이름에서 알 수 있듯이 앞서 설명한 작은 store인 slice 를 만든다.
slice 안에서 initialStatereducer 를 함께 정의한다.

먼저, slice의 이름을 정한다. 필자는 'counterSlice' 고 정했다.

다음은 initialState 이다. 초기값을 0으로 세팅했다.

마지막으로는 reducer 이다. 여기서 reducer복수형 reducers 임을 알 수 있다. 이 뜻은, 하나의 작은 기능을 담당하는 slice 하나에 여러 개의 reducer 를 설정할 수 있다는 의미를 지닌다.

현재는 버튼을 눌렀을 때 숫자가 올라가는 up 이라는 리듀서 하나이지만, 숫자가 감소되는 down, 두 배씩 증가하는 multiply 등 여러 개의 reducer 를 정의할 수 있으며 원하는 리듀서를 골라 사용할 수 있다.

const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: {
    value: 0,
  },
  reducers: {
    up: (state, action) => {
      state.value = state.value + action.payload;
    },
    down: () => {
      ...
    },
    multiply: () => {
      ...
    },
  },
});

2) configureStore

createSlice 를 통해 만든 slice 들을 한 데 모아 하나의 큰 store 로 합쳐주는 메소드이다.
즉, Redux 라이브러리의 createStore 와 동일한 역할이다.
(참고로 Redux 팀에서 createStore 를 사용하는 대신 RTKconfigureStore 를 사용하라고 권고하는 입장이다.)

const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    other: otherSlice.reducer,
    ...
  },
});

이 객체의 키값을 보면 reducer 가 있다.
createSlice 객체의 reducers 는 복수형이라고 말했다. 복습하는 차원에서 다시 설명하자면, reducers 는 하나의 slice 가 여러 개의 기능을 담당할 수 있으므로 복수 개의 reducer 를 지원한다는 의미에서 복수형이라고 말했다.

그렇다면 configureStorereducer 는 왜 단수형인가?
counterSlice와 같이 여러 작은 기능들을 담당하는 모든 slice 를 하나의 reducer 로 통합하겠다는 의미이다.

또한, 이는 Redux 라이브러리의 createStore 에 들어가는 파라미터 중 reducer 와 동일하다.
밑에 코드를 보면 첫 번째 인자로 reducer 를 받고 있음을 알 수 있다.

const reducer = (state, action) => {
  if (action.type === 'up') {
    return {...state, value: state.value + action.step};
  }
  return state;
};

const store = createStore(reducer, initialState);

reducer 객체 안의 counter: counterSlice.reducer 의미를 알아보자.
앞서 이야기 한 것처럼, counterSlice 라는 이름의 sliceup 이라는 기능을 하는 reducer 외에도 여러 개가 존재할 수 있다. 이를 counter 라는 이름으로 한데 모아 하나에 담겠다는 뜻이다.
이를 사용할 땐 counter 객체에 존재하는 특정 데이터를 호출하면 된다.
(밑에서 설명한다.)


3. useSelector

useSelector 는 기존과 동일하게 React-redux 라이브러리의 메소드를 사용한다.

const count = useSelector(state => {
  console.log(state);
  return state.counter.value;
});

먼저 console 결과를 보자.

{"counter": {"value": 0}}

앞에 configureStore 에서 정의한 slicereducer 를 한데모은 키값이 상위 객체로 잡혀있다.

이 말은 즉, 어떤 slice의 데이터를 원하는지 직접 선택해야 된다는 뜻이다.
그래서 return을 state.counter.value; 라고 한 것처럼 counter라는 slice 의 value 라는 데이터를 가져오겠다고 구체적인 명시를 한 것이다.


4. useDispatch

useDispatch 역시 기존과 동일하게 React-redux 라이브러리의 메소드를 사용한다.

다음은 기존처럼 action creator 를 만들어서 dispatch 를 하는 코드이다.

const Counter = () => {
  const dispatch = useDispatch();

  return (
    <Button
      title="+"
      onPress={() => {
        dispatch({
          type: 'counterSlice/up',
          step: 2,
      });
  	}}/>
  );
};

이렇게 해도 당연히 동작은 한다!
하지만, 매번 저렇게 type과 value를 써서 action을 create 하는 것은 개발하는 입장에서 굉장히 번거로운 단순작업 임에 틀림없다.

따라서 RTK 는 이를 아주 간편하게 구현해주도록 도와준다.

const Counter = () => {
  const dispatch = useDispatch();

  return (
    ...
    <Button
      title="+"
      onPress={() => {
	    dispatch(counterSlice.actions.up(2));
	  }}
	/>
  );
};

action creator 대신 actions 라는 키워드와 어떤 reducer 이며 state 를 어떻게 바꿀 것인지를 위와 같이 한줄로 작성하면 끝난다.

그렇다면 dispatch 를 감지하고 reducer 에선 어떻게 수신할까?
아래와 같이 payload 를 통해 모든 것을 수신한다!!
즉, 아래 주석 친 것처럼 action creator 를 통해 본인이 정한 어떠한 데이터 키값을 넣어주는 것이 아니라, 모든 것을 action.payload 를 통해 수신한다.

const counterSlice = createSlice({
  name: 'counterSlice',
  initialState: {
    value: 0,
  },
  reducers: {
    up: (state, action) => {
      // state.value = state.value + action.step;
      state.value = state.value + action.payload;
    },
  },
});
profile
모든 사용자가 만족하는 UI를 만드는 FE 개발자 권원현입니다.

0개의 댓글