Redux_memo_combineReducers, Redux Toolkit: createAction, createReducer, configureStore, createSlice

Lina Hongbi Ko·2023년 10월 25일
0

Redux_memo

목록 보기
1/1

리액트 리덕스를 공부하면서 업데이트된 내용들을 습득한 것을 기록하고자 한다.

1. combineReducers()

리덕스를 사용할때 리듀서와 액션 관련코드들을 하나의 파일에 몰아서 작성하는 것을 Ducks 패턴이라고 한다. 여러 리듀서를 만들어서 index.js 라는 파일에 하나로 합쳐서 쓸 때 이 API를 사용한다.

<script>

// modules / index.js

import { combineReducers } from 'redux';
import counter from './counter';
import todos from './todos';

const rootReducer = combineReducers({
	counter,
    todos
});

export default rootReducer;

</script>

쉽게 말해 여러 리듀서를 하나로 합친다는 말이다.

<script>

// index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { crateStore } from 'redux';
import { Provider } from 'react-redux';
import rootReducer from './modules'

const store = createStore(rootReducer); // 스토어 만들기

ReactDOM.render(
	<Provider store={store}>
    	<App />
    </Provider>, document.getElementById('root')
);

serviceWorker.unregister();

</script>

2. Redux Toolkit

처음 Redux를 공부하고 설치할 때 공식 홈페이지에 들어가서 tutorial을 봤는데, 요걸 설치하라고 권장해서 나중에 찾아보니, Redux를 더 편리하게 사용할 수 있게 한 패키지 라는 것을 알았다.

이제 찾아본 것들을 정리해보자.

Redux Toolkit은 Redux를 보다 편리하고 효율적으로 사용하기 위해 Redux에서 개발한 Redux의 패키지이다. Redux의 문제들을 해결하기 위해 만들어졌는데,

문제들로는...

  • "Configuring a Redux store is too complicated."
    Redux store의 설정이 너무 복잡하다.

  • "I have to add a lot of packages to get Redux to do anything useful."
    Redux를 유용하게 사용하기 위해서는 많은 패키지들을 추가해야 한다.

  • "Redux requires too much boilerplate code."
    Redux로 작업하려면 많은 Boilerplate 코드가 필요하다.

이런 점들이 대표적으로 있고, 그래서 ReduxToolkit이 탄생되었다.

자, 그렇다면 이제 Redux Toolkit에 대해 본격적으로 알아보자.

✏️ Redux Toolkit 설치

1. create-react-app 으로 React 프로젝트 생성시 함께 추가하기

Redux를 처음 설치했을 때에도 이 방법을 언급했듯, React 프로젝트와 Redux를 함께 설치할때 사용한다.

npx create-react-app my-app --template redux

2. 기존 프로젝트에 추가하기

기존 프로젝트에 Redux Toolkit을 추가하는 경우이다.

npm install @reduxjs/toolkit

or

yarn add @reduxjs/toolkit

✏️ Redux Toolkit 기초 구문

1. createAction

<script>

// Action type명
const ADD_TODO = "ADD_TODO";
const DELETE_TODO = "DELETE_TODO";

// Action Creator
const addToDo = (text) => {
	return {
    	type: ADD_TODO,
        text
    }
}

const deleteToDo = (id ) => {
	return {
    	type: DELETE_TODO,
        id
    }
}

// Action Creator 묶음
export const actionCreators = {
	addToDo,
    deleteToDo
}

</script>

Redux를 사용할 때 우리는 위의 코드처럼 액션명을 정의해주고, 액션을 발생시켜주는 함수를 작성해줘서 그 함수를 호출해 액션을 사용했다.

그에 따라,

  • Action Type 명을 정의해줘야하는 번거로움
  • addToDo와 deleteToDo 라는 액션 생성 함수를 하나하나 만들어줘야 하는 번거로움

이 생겼고,

이를 해결해 주는 "createAction" 이 등장했다.

<script>

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

const addToDo = createAction("ADD_TODO");
const deleteToDo = createAction("DELETE_TODO);

// createAction의 첫번째 인자는 type
// createAction의 두번째 인자는 payload
// type은 action의 이름
// payload는 action이 받아온 데이터

</script>

createAction 함수를 쓰면, 액션 생성 함수를 직접 만들 필요 없이 바로 액션을 사용할 수 있게 한다. 인자에 Action type을 넣어주면 된다. 이 함수를 호출하면 Object가 반환되는데 (이 Object가 액션이겠지) type과 payload 라는 속성을 default로 갖게 된다.

이렇게 액션들을 잘 생성했으니 이 액션들을 reducer 함수의 각 case에 적용해보자.

<script>

const reducer = (toDos = [], action) => {
	switch(action.type) {
    	case addToDo.type:
        	return [{text: action.payload, id: uuidv4()}, ...toDos];
        case deleteToDo.type:
        	return toDos.filter((toDo) => toDo.id !== action.payload);
        default:
        	return toDos;
    }
}

</script>

case를 createAction으로 만든 action의 type으로 설정해준다. 그리고 Action으로 받아온 데이터를 payload에 담는다.

위의 코드를 보면, return문에서 action이 받아오는 값을 action.payload로 넣어준 것을 확인할 수 있다.

2. createReducer

<script>

const reducer = (toDos=[], action) => {
	switch(action.type) {
    	case addToDo.type:
        	return [ {text: action.payload, id: uuidv4()}, ...toDos ];
        case deleteToDo.type:
        	return toDos.filter((toDo) => toDo.id !== action.payload);
        default:
        	return toDos;
    }
}

</script>

우리는 Redux를 이용할때 reducer 함수를 위의 코드처럼 만들어주었다.

하지만,

  • switch문과 같은 조건문을 이용해 각 type에 대한 조건을 작성해야 함
  • Redux의 기본 3원칙 중 하나인 "State is read-only"라는 원칙에 따라, state를 변경하지 못함. state를 변경할 수 없어서 spread 연산자나 Object.assign() api를 이용해 state의 불변성을 유지했는데 귀찮음

요런 불편함들이 있었다. 그래서 createReducer로 reducer 함수의 작성을 간결하게 하고, state를 변경 가능하게 만들었다.

<script>

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

// createReducer: Reducer 함수 생성
// createRecuer의 첫번째 인자: initial State
const reducer = createReducer([], {

	// [addToDo] : action이 addToDo일 때
	[addToDo] : (state, action) => {
    	state.push( {text: action.payload, id: uuidv4()});
        // state에 새로운 데이터를 push해서 state를 변경시킴 (기존데이터변경)
        // state를 변경하는 경우에는 state를 return 하지 않아도 된다
    },
    
    // [deleteToDo] : action이 deleteToDo일 때
    [deleteToDo] : (state, action) => state.filter((toDo) => toDo.id !== action.payload)
    // filter을 사용해 새로운 state를 만들어서 return함 (새로운데이터만듦)
    // 새로운 state를 만드는 경우에는 state를 return 해줘야 한다
});

const toDosStore = crateStore(reducer);

</script>

createReducer 함수는 Reducer 함수를 생성하고, Redux에서의 Reducer 함수처럼 첫번째 인자에 initial State가 들어간다. 여기서는 빈 배열이 들어간 것을 확인할 수 있다. 그리고 두번째 인자에 액션을 넣는데 조건에 따라 분기시켜 주었다.

addToDo에서 state.push()를 사용한 것을 볼 수 있는데, JavaScript의 Array.push는 기존 배열의 끝에 요소를 추가하는 메소드이어서 (기존배열에 요소 추가) 배열 자체를 있는 그대로 변경시켜버린다. 그래서 Array.push()를 사용하면 state의 불변성이 훼손된다. 하지만 Redux Toolkit의 createReducer 함수는 내부적으로 Immer를 사용해 state를 변경시키는 것을 가능하게 해준다.

3. configureStore

기존 Redux에서는 store을 생성할때 createStore()을 사용했지만 Redux Toolkit에서는 configureStore()를 사용한다. configureStore은 Redux 미들웨어와 함께 store를 생성할 수 있게 해준다.

<script>

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

const toDosStore = configureStore({ reducer });

</script>

기존의 createStore과 비교했을때 configureStore을 통해 생성된 store은 Redux DevTools를 사용할 수 있다. Redux DevTools로 dispatch된 action과 history, state 변경사항들을 쉽게 볼 수 있다는 것이 장점이다.

4. createSlice

createAction과 createReducer로 코드를 간단하게 줄였지만 createSlice를 사용하면 코드를 더욱 줄일 수 있다.

createSlice는 Action과 Reducer 함수의 생성을 동시에 할 수 있게 해준다. Action과 Reducer 함수를 한번에 만들 수 있게 해주어서 코드를 훨씬 적게 작성할 수 있다.

<script>
// store. js

import { v4 as uuidv4 } from "uuid";
import { configureStore, createSlice } from "@reduxjs/toolkit";

const toDos = createSlice({
	name: "toDosReducer",
    initialState: [],
    reducers: {
    	
        // Action creator
        add : (state, action) => {
        	state.push({ text:action.payload, id:uuidv4() });
        },
        
        // Action creator
        remove : (state, action) => state.filter((toDo) => toDo.id !== action.paylooad)
    }
});

export const { add, remove } = toDos.actions;

export default configureStore( {reducer: toDos.reducer});

</script>

createSlice는 아래의 옵션들을 가진 Object를 받는다.

  • name: 모듈의 이름, useSelector할때 값을 가져오는 장소 이름
  • initialState: state의 초기값 설정 (처음 상태 정의)
  • reducers : reducer를 작성한다. 이때 reduer의 key값으로 Action 함수가 자동으로 생성된다. 해당 state를 변화시키는 action을 지정
  • actions : dispatch를 통해 상태를 변화시킬 수 있음

createSlice 내부에서 모듈이름, state, reducer, action이 한번에 정의 된 것을 볼 수 있다.

그리고 이렇게 만들어진 Slice Object로 구조분해할당을 이용해 reduer과 액션생성자를 만들 수 있다.

const { actions, reducers } = toDos;
export const { add, remove } = toDos.actions;
export default reducer;

다른 컴포넌트에서 예를 들어 사용하려면,

<script>

import { add } from "../store";

// ... 생략...

function mapDispatchToProps(dispatch) {
	return {
    	addToDo: (text) => dispatch(add(text))
    }
}

</script>
<script>

import { remove } from "../store";

// ... 생략...

function mapDispatchToProps(dispatch, ownProps) {
	return {
    	deleteToDo: () => dispatch(remove(ownProps.id))
    }
}

</script>

요렇게 사용하면 된다.

5. useSelector, useDispatch (요건 redux에서 import)

이 함수들을 이용하면, 값을 꺼내오고나 변화시킬 수 있다.

  • useSelector() : 스토어에서 현재 상태 값을 가져온다.
  • useDispatch() : 변경되는 값을 스토어로 전달한다.

6. 실습해보기

배운 것들을 이용해 예제 하나를 실습해보자.

<script>

// src / index.js

import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App";
import store from "../store/store";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
	<Provider store={store}>
    	<App />
    </Provider>
)

</script>
<script>

// store / store.js

impor { combineReducers, configureStore } from "@reduxjs/toolkit";
import counter from "../store/counter/countSlice";

const combinedReducer = combineReducers({
	counter
});

const store = configureStore({
	reducer: combinedReducer
});

export default store;

</script>
<script>

// store / counter / countSlice.js

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

const counterSlice = createSlice({
	name: "counter",
    initialState: {
    	value: 0
    },
    reducers: {
    	plus: (state) => {
        	state.value += 1;
        },
        minus: (state) => {
        	state.value -= 1;
        }
    }
});

const { actions, reducer } = counterSlice;
export const { plus, minus } = actions;
export default reducer;

</script>
<script>

// src / App.js

import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { minus, plus } from "../store/counter/countSlice";

export default function App() {
	const dispatch = useDispatch();
    const { value } = useSelector((state) => state.counter);
    
    return (
    	<div>
        	<button onClick={()=>dispatch(minus())}>-</button>
            <div>value: {value}</div>
            <button onClick={()=>dispatch(plus())}+</button>
        </div>
    )
}

Redux Toolkit을 사용하면 코드를 훨씬 줄일 수 있어서 효율적인면에서 아주 좋은 것 같다. 다른 프로젝트들을 하면서 Redux와 Redux Toolkit을 잘 버무려 사용해서 만들어야겠다.


출처
https://redux-toolkit.js.org/introduction/getting-started

https://velog.io/@iamhayoung/Redux-Toolkit-createAction-createReducer-configureStore-createSlice

https://velog.io/@sham/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A1%9C-%ED%95%98%EB%8A%94-%EB%A6%AC%EB%8D%95%EC%8A%A4

https://choi-hyunho.tistory.com/198

profile
프론트엔드개발자가 되고 싶어서 열심히 땅굴 파는 자

0개의 댓글