기존의 리덕스 ducks패턴의 요소들이 전체적인 코드의 양을 늘린다는 불편함으로 인해 개량한 것
리덕스와 구조나 패러다임이 모두 똑같다. (dispatch,useSelector 사용)
모듈 부분만 차이가 있다.
configStore.js
import { createStore, combineReducers } from 'redux';
import counter from '../modules/counter';
// 모든 리듀서를 합쳐(combinReducers) rootReducer를 만들고
const rootReducer = combineReducers({
counter,
});
// store를 create한다.
const store = createStore(rootReducer);
// 이 스토어를 바깥으로 내보내서(app)
export default store;
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 api로 리듀서를 주입하면 전역 상태관리자로 사용가능
<Provider store={store}>
<App />
</Provider>
);
counter.js
// 리듀서
//Action Value - 상수로 만든이유? 휴먼 에러 방지
const ADD_NUMBER = 'counter/ADD_NUMBER';
const MINUS_NUMBER = 'counter/MINUS_NUMBER';
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER,
payload,
};
};
export const minusNumber = (payload) => {
return {
type: MINUS_NUMBER,
payload,
};
};
// 초기값
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 counter;
App.jsx
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { addNumber, minusNumber } from './redux/modules/counter';
function App() {
const number = useSelector((state) => state.counter.number);
const dispatch = useDispatch();
// dispatch할때 type,payload를 직접 입력하는 것이 아니라, 휴먼에러를 줄이기위해
// action creator를 입력해준다.
const onPlusButtonClickHandler = () => {
dispatch(addNumber(1));
};
const onMinusButtonClickHandler = () => {
dispatch(minusNumber(1));
};
return (
<div>
<h1>{number}</h1>
<button onClick={onPlusButtonClickHandler}>+</button>
<button onClick={onMinusButtonClickHandler}>-</button>
</div>
);
}
export default App;
yarn add @reduxjs/toolkit
configStore.js
// import { createStore, combineReducers } from 'redux';
import counter from '../modules/counter';
import { configureStore } from '@reduxjs/toolkit';
// AS-IS : 일반 reducer
// const rootReducer = combineReducers({
// counter,
// });
// const store = createStore(rootReducer);
// TO-BE : redux toolkit
// 모듈(slice)가 여러개인 경우 추가할때마다 reducer안에 각 모듈의 slice.reducer를 추가해줘야한다.
const store = configureStore({
reducer: {
counter: counter,
// todos: todos,
},
});
export default store;
counter.js
import { createSlice } from '@reduxjs/toolkit';
// AS-IS : 일반 redux
//Action Value - 상수로 만든이유? 휴먼 에러 방지
// const ADD_NUMBER = 'counter/ADD_NUMBER';
// const MINUS_NUMBER = 'counter/MINUS_NUMBER';
// Action Creator
// export const addNumber = (payload) => {
// return {
// type: ADD_NUMBER,
// payload,
// };
// };
// export const minusNumber = (payload) => {
// return {
// type: MINUS_NUMBER,
// payload,
// };
// };
// 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 counter;
// TO-BE: redux toolkit
// 초기값
const initialState = {
number: 0,
};
//createSlice() API : Action Value, Action Creator, Reducer가 하나로 합쳐졌다
const counterSlice = createSlice({
name: 'counter', // 모듈 이름
initialState, // 모듈의 초기 상태값
reducers: { // 모듈의 reducer 로직
// 리듀서 안에서 만든 함수 자체가 리듀서의 로직이자, 액션크리에이터가 된다.
// 그리고 action value까지 함수의 이름을 따서 자동으로 만들어진다.
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;
우리는 modules에서 액션아이템을 만들어서 액션크리에이터를 내보냈다. 그 액션크리에이터로 컴포넌트에서액션객체(,payload dispatch애소 사용했었다.
기존 moduel에서 관리했는데 이렇게 3개를
1. action creator
2. 리듀서 export
3. action value
너무 코드가 길고 많아서 createSlice API를 제공해서 위에서 사용하는 3가지를 한번에 처리할 수 있다.
configStore.js, todos.js만 수정해주면 된다.
immer를 사용하면 상태를 업데이트 할 때, 불변성을 신경쓰지 않으면서 업데이트를 해도 immer가 불변성 관리를 대신 해준다!
따라서 이전에는 원본 배열을 훼손하는 push 등의 사용이 권장되지 않았지만 RTK에서는 사용이 가능하다.
import { createSlice } from '@reduxjs/toolkit';
import React from 'react';
import { v4 as uuidv4 } from 'uuid';
// action items
// const ADD_TODO = "ADD_TODO";
// const REMOVE_TODO = "REMOVE_TODO";
// const SWITCH_TODO = "SWITCH_TODO";
/**
* 메서드 개요 : todo 객체를 입력받아, 기존 todolist에 더함
* 2022.12.16 : 최초작성
*
* @param {todo 객체} payload
* @returns
*/
// export const addTodo = (payload) => {
// return {
// type: ADD_TODO,
// payload,
// };
// };
/**
* 메서드 개요 : todo의 id를 입력받아, 일치하는 todolist를 삭제
* 2022.12.16 : 최초작성
*
* @param {todo의 id} payload
* @returns
*/
// export const removeTodo = (payload) => {
// return {
// type: REMOVE_TODO,
// payload,
// };
// };
/**
* 메서드 개요 : todo의 id를 입력받아, 일치하는 todo 아이템의 isDone을 반대로 변경
* 2022.12.16 : 최초작성
*
* @param {*} payload
* @returns
*/
// export const switchTodo = (payload) => {
// return {
// type: SWITCH_TODO,
// payload,
// };
// };
// initial states
const initialState = [
{
id: uuidv4(),
title: '리액트 공부하기',
contents: '빨리빨리 암기하기',
isDone: false,
},
{
id: uuidv4(),
title: '스프링 공부하기',
contents: '인강 열심히 들어보기!!',
isDone: true,
},
{
id: uuidv4(),
title: '데이트',
contents: '홍대입구역에서 3시까지',
isDone: false,
},
];
// reducers
// const todos = (state = initialState, action) => {
// switch (action.type) {
// case ADD_TODO: // 기존의 배열에 입력받은 객체를 더함
// return [...state, action.payload];
// case REMOVE_TODO: // 기존의 배열에서 입력받은 id의 객체를 제거(filter)
// return state.filter((item) => item.id !== action.payload);
// case SWITCH_TODO: // 기존의 배열에서 입력받은 id에 해당하는 것만 isDone을 반대로 변경(아니면 그대로 반환)
// return state.map((item) => {
// if (item.id === action.payload) {
// return { ...item, isDone: !item.isDone };
// } else {
// return item;
// }
// });
// default:
// return state;
// }
// };
// export
// export default todos;
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo: (state, action) => {
// 일반 redux에서는 push로 원본을 복사하지 않고 상태를 변경하는 것이 불가했는데
// RTK에서는 immer를 제공해주어서 상태를 업데이트할 때 불변성을 고려하지 않아도 대신 관리해주므로 사용가능해졌다.
// state.push(action.payload);
return [...state, action.payload];
},
removeTodo: (state, action) => {
return state.filter((item) => item.id !== action.payload);
},
switchTodo: (state, action) => {
return state.map((item) => {
if (item.id === action.payload) {
return { ...item, isDone: !item.isDone };
} else {
return item;
}
});
},
},
});
export const { addTodo, remoteTodo, switchTodo } = todosSlice.actions;
export default todosSlice.reducer;
// import { createStore } from "redux";
// import { combineReducers } from "redux";
import todos from "../modules/todos";
import { configureStore } from "@reduxjs/toolkit";
// AS-IS: 일반 redux
// 1. create rootReducer with reducers
// const rootReducer = combineReducers({
// todos,
// });
// // 2. create store
// const store = createStore(rootReducer);
// TO-BE : redux toolkit
const store = configureStore( {
reducer: {
todo: todos,
}
});
// 3. export
export default store;