(1) 리덕스툴킷
리덕스를 사용하기 위해 작성했던 ducks 패턴의 요소들이 전체적인 코드의 양을 늘린다는 개발자들의 불만이 발생하기 시작했고, 리덕스 팀에서는 이것을 수용하여 코드는 더 적게, 그리고 리덕스를 더 편하게 쓰기 위한 기능들을 흡수해서 만든 것이 리덕스툴킷 입니다. 줄여서 RTK 라고도 합니다.
(2) 새로운 것인가? 아니다.
즉 새로운 것이 아닙니다. 리덕스의 전체 코드의 양을 줄이기 위해 새로운 API가 추가되었고 우리가 일일히 손으로 만들어 줘야 했던 ducks 패턴의 요소들이 어느정도 자동화 되었습니다.
컴포넌트에서 useSelector를 통해서 사용하는 것은 모두 똑같습니다.
바뀐 부분은 그저 모듈 파일 뿐이죠. 이후 일반 리덕스와 툴킷의 요소를 비교해서 어떤 식으로 바뀌었는지 살펴보겠습니다.
yarn
을 통해 아래 패키지를 설치합니다.yarn add react-redux @reduxjs/toolkit
// 일반 리덕스 예시 코드
// Action Value
const ADD_NUMBER = "ADD_NUMBER";
const MINUS_NUMBER = "MINUS_NUMBER";
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload,
};
};
export const minusNumber = (payload) => {
return {
type: MINUS_NUMBER,
payload,
};
};
// Initial State
const initialState = {
number: 0,
};
// Reducer
const counter = (state = initialState, action) => {
switch (action.type) {
case ADD_NUMBER:
return {
number: state.number + action.payload,
};
// [퀴즈 답]
case MINUS_NUMBER:
return {
number: state.number - action.payload,
};
default:
return state;
}
};
// export default reducer
export default counter;
아래 코드가 리덕스 툴킷을 사용해서 만든 counter 프로그램 모듈입니다. 일반 리덕스를 사용했을보다 확실히 코드의 양이 줄었습니다.
한번 자세히 살펴보면 큰 차이점은 Action Value와 Action Creator를 이제 직접 생성해주지 않고, Action Value, Action Creator, Reducer가 하나로 합쳐졌다는 점 입니다.
이제 우리는 Slice
라는 API를 사용합니다. 이 슬라이스를 사용하면, 저 3개를 각각 만들어줄 필요 없이 한번에 3개가 모두 만들어집니다. 아래 코드를 작성해봅시다.
// src/redux/modules/counterSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
number: 0,
};
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
슬라이스만 자세히 보겠습니다. 슬라이스는 createSlice 라는 API를 통해 만들 수 있습니다. 그리고 그 인자로 설정정보를 객체로 받는데, 그 안에 우리가 필수로 작성해줘야 하는 값은 name, initialState, reducers가 있습니다.
//createSlice API 뼈대
const counterSlice = createSlice({
name: '', // 이 모듈의 이름
initialState : {}, // 이 모듈의 초기상태 값
reducers : {}, // 이 모듈의 Reducer 로직
})
신기한 것은 위의 counterSlice 리듀서 객체 안에서 만들어주는 함수가 리듀서의 로직이 되면서도 동시에 Action Creator가 된다는 점입니다. 그리고 Action Value 까지 함수의 이름을 따서 자동으로 만들어집니다. 그래서 우리는 Reducer만 만들어주면 됩니다.
// counterSlice.js의 Slice 구조
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
// 리듀서 안에서 만든 함수 자체가 리듀서의 로직이자, 액션크리에이터가 된다.
addNumber: (state, action) => {
state.number = state.number + action.payload;
},
minusNumber: (state, action) => {
state.number = state.number - action.payload;
},
},
});
그리고 우리가 일반 리덕스에서 export를 통해서 각각의 Action Creator를 내보내주었던 것을 아래 코드를 작성하면 똑같이 내보낼 수 있습니다. 그래서 리듀서에 로직을 추가할 때마다 함수를 추가해서 내보내주면 됩니다.
// 액션크리에이터는 컴포넌트에서 사용하기 위해 export 하고
export const { addNumber, minusNumber } = counterSlice.actions;
// reducer 는 configStore에 등록하기 위해 export default 합니다.
export default counterSlice.reducer;
// 일반 리덕스 combineReducers 예시 코드
import { createStore } from "redux";
import { combineReducers } from "redux";
import counter from "../modules/counter";
const rootReducer = combineReducers({
counter,
});
const store = createStore(rootReducer);
export default store;
configStore
에서도 아래와 같이 작성하면 되며, 크게 달라지는 점은 없습니다. todos 모듈 코드를 아래에 제공해드릴테니, 모듈을 추가해서 아래 코드처럼 여러개의 모듈을 store에 등록해보세요. 아래 코드를 작성해봅시다.
// src/redux/modules/config/configStore.js
import { configureStore } from "@reduxjs/toolkit";
/**
* import 해온 것은 slice.reducer 입니다.
*/
import counter from "../modules/counterSlice";
import todos from "../modules/todosSlice";
/**
* 모듈(Slice)이 여러개인 경우
* 추가할때마다 reducer 안에 각 모듈의 slice.reducer를 추가해줘야 합니다.
*
* 아래 예시는 하나의 프로젝트 안에서 counter 기능과 todos 기능이 모두 있고,
* 이것을 각각 모듈로 구현한 다음에 아래 코드로 2개의 모듈을 스토어에 연결해준 것 입니다.
*/
const store = configureStore({
reducer: { counter: counter, todos: todos },
});
export default store;
configureStore
에 todosSlice를 추가합니다. (아래 코드는 단순히 비어있는 todosSlice 입니다.)// src/redux/modules/todosSlice.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
todos: [],
};
const todosSlice = createSlice({
name: "todos",
initialState,
reducers: {},
});
export const {} = todosSlice.actions;
export default todosSlice.reducer;
그리고 이렇게 생성한 store를 export default 해서 최상위의 index.js Provider에 주입해주는 것은 전혀 바뀐게 없습니다.
// index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/config/configStore";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
App.jsx
에서는 툴킷을 사용해서 만든 모듈을 조회할 수 있습니다. 방식은 일반리덕스를 사용했을 때와 동일합니다. 아래 코드를 작성하여, 리덕스 모듈들이 잘 연결되었는지 확인해봅시다.
// src/App.js
import React from "react";
import { useSelector } from "react-redux";
const App = () => {
// Store에 있는 todos 모듈 state 조회하기
const todos = useSelector((state) => state.todos);
// Store에 있는 counter 모듈 state 조회하기
const counter = useSelector((state) => state.counter);
return <div>App</div>;
};
export default App;
우리가 위에서 작성한 파일들의 구조 이미지 입니다. modules안에 있는 파일의 이름은 임의로 하시면 됩니다. counter.js, todos.js로 하는 개발자도 있고, counterSlice.js, todosSlice.js로 하는 개발자도 있습니다. 선택입니다. 아래 파일 구조와 동일한지 확인해봅시다.
리덕스를 사용하면, 리덕스 devtools를 사용할 수 있습니다.
다른 패키지에서는 찾아볼 수 없는 굉장히 강력한 개발툴입니다. 현재 프로젝트의 state 상태라던가, 어떤 액션이 일어났을 때 그 액션이 무엇이고, 그것으로 인해 state가 어떻게 변경되었는지 등 리덕스를 사용하여 개발할 때 아주 편리하게 사용할 수 있습니다.
이 devtools 때문에 리덕스를 사용한다고 말하는 것도 과언이 아닌데요. 우리도 한번 사용해보겠습니다.
구글 웹스토어에서 플러그인을 설치해야 합니다.
웹 스토어에서 devtools를 설치하고 만약 리액트 프로젝트에서 리덕스를 사용하고 있으면 이렇게 플러그인에 녹색으로 불이 켜집니다.
그리고 개발자도구 탭에서 Redux 라는 메뉴를 볼 수 있을 것 입니다. 설치는 아주 간단하죠?
이제 우리가 만든 Counter 프로그램을 리덕스 devools 를 띄워놓고 한번 작동시켜 보겠습니다. devtools 에서 어떤 정보들을 우리에게 보여줄까요?
더하기
버튼을 누르면 addNumber
라는 액션이 dispatch 됩니다. 그것을 devtools에서 보여주는 것이고, 그것으로 인해 state 값이 어떻게 변했는지를 전부 보여줍니다.
프로젝트가 점점 복잡해질수록 로그를 확인하는 것이 쉽지 않은데, 이 툴을 이용해서 정말 쉽게 디버깅을 할 수 있습니다. 여러분들도 이 devtools를 잘 활용하시길 바랍니다.
툴킷이 아닌 일반 리덕스에서 devtools를 사용하고자 한다면, 별도 설정이 필요합니다. 툴킷은 devtools이 내장되어 있기 때문에 별도의 코드 설정 없이 바로 사용 가능합니다.
만화로 보는 Flux
https://bestalign.github.io/translation/cartoon-guide-to-flux/
Flux와 redux의 관계
https://taegon.kim/archives/5288
출처 : 스파르타코딩 2023 강의자료