- Redux(리덕스)란 JavaScript(자바스트립트) 상태관리 라이브러리다.
위와 같은 문제들을 해결하기 위해 상태 관리를 도와주는 도구들이 있다.
- React Context
- Redux
- MobX
이 중 가장 인기있는 상태 관리 라이브러리 리덕스를 알아보자.
1. Single source of truth
동일한 데이터는 항상 같은 곳에서 가지고 온다.
즉, 스토어라는 하나뿐인 데이터 공간이 있다는 의미이다.
2. State is read-only
리액트에서는 setState 메소드를 활용해야만 상태 변경이 가능하다.
리덕스에서도 Action(액션)이라는 객체를 통해서만 상태를 변경할 수 있다.
3. Changes are made with pure functions
변경은 순수함수로만 가능하다.
상태가 엉뚱한 값으로 변경되는 일이 없도록 순수함수로 작성되어야하는 Reducer와 연결되는 원칙이다.
Redux에서는 Action → Dispatch → Reducer → Store 순서로 데이터가 단방향으로 흐른다
import { createStore } from 'redux';
const store = createStore(rootReducer); // 리듀서가 연결 된 스토어 생성
// payload가 필요 없는 경우
{ type: 'INCREASE' }
// payload가 필요한 경우
{
type: 'ACTION_CHANGE_USER', // 필수
payload: { // 옵션
name: 'wonho',
age: 30
}
}
// payload가 필요 없는 경우
const increase = () => {
return {
type: 'INCREASE'
}
}
// payload가 필요한 경우
const setNumber = (num) => {
return {
type: 'SET_NUMBER',
payload: num
}
}
const count = 1
// Reducer를 생성할 때에는 초기 상태를 인자로 요구합니다.
const counterReducer = (state = count, action) => {
// Action 객체의 type 값에 따라 분기하는 switch 조건문입니다.
switch (action.type) {
//action === 'INCREASE'일 경우
case 'INCREASE':
return state + 1
// action === 'DECREASE'일 경우
case 'DECREASE':
return state - 1
// action === 'SET_NUMBER'일 경우
case 'SET_NUMBER':
return action.payload
// 해당 되는 경우가 없을 땐 기존 상태를 그대로 리턴
default:
return state;
}
}
import { combineReducers } from 'redux';
const rootReducer = combineReducers({
counterReducer,
anyReducer,
...
});
// Action 객체를 직접 작성하는 경우
dispatch( { type: 'INCREASE' } );
dispatch( { type: 'SET_NUMBER', payload: 5 } );
// 액션 생성자(Action Creator)를 사용하는 경우
dispatch( increase() );
dispatch( setNumber(5) );
위와 같이 Redux의 기본 개념을 알아보았다.
React-Redux에서 Redux를 사용할 때 활용할 수 있는 Hooks 메서드를 제공
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './store/store'; // 스토어 불러오기
import { Provider } from 'react-redux'; // store 사용설정을 위해 불러오기
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
import { useDispatch } from 'react-redux'
const dispatch = useDispatch()
dispatch( increase() )
console.log(counter) // 2
dispatch( setNumber(5) )
console.log(counter) // 5
// Redux Hooks 메서드는 'redux'가 아니라 'react-redux'에서 불러옴
import { useSelector } from 'react-redux'
const counter = useSelector(state => state)
console.log(counter) // 1
들어가기전 오해가 생길법한 내용을 알아보겠다.
Redux는 React의 관련 라이브러리, 혹은 하위 라이브러리라는 대표적인 오해가 있는데, 전혀 그렇지 않다.
Redux는 React 없이도 사용할 수 있는 상태 관리 라이브러리인 것이다.
create-react-app
으로 만든 리액트 프로젝트에서 리덕스를 사용할 경우에는
npm install redux react-redux
두 모듈을 다운 받는다.
package.json 을 보면 프로젝트가 두 모듈에 의존하는 것을 확인 할 수 있다.
Code Splitting(코드 분할)
코드 분할은 스크립트를 하나의 큰 파일로 로드하는 대신 더 작은 부분으로 나누고 해당 페이지에 필요한 것만 로드하는 기술이다.
많은 양의 JavaScript가 있는 프로젝트의 경우 성능이 크게 향상될 수 있다.
- src 폴더 안에 actions, reducers, store, components, pages 폴더로 분할한다.
- component 폴더는 프리젠테이션 컴포넌트로, page 폴더는 컨테이너 컴포넌트로 나눈다.
- Store는 하나의 리듀서만 관리할 수 있지만 리듀서를 여러개 나눠서 하나로 합칠 수 있다.
// reducers 폴더 안에 reducers.js
import { combineReducers } from 'redux'; // 모든 리듀서 합치기 위함
import itemReducer from './itemReducer';
import notificationReducer from './notificationReducer';
const rootReducer = combineReducers({ // 모든 리듀서 합치기
itemReducer,
notificationReducer
});
export default rootReducer;
이곳에서 각각의 리듀서의 코드를 구현하면 된다.
예를들어 하나만 보자면 아래와 같다.
// reducers 폴더 안에 itemReducer.js
import { REMOVE_FROM_CART, ADD_TO_CART, SET_QUANTITY } from '../actions/index';
import { initialState } from './initialState';
const itemReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TO_CART:
//TODO
return Object.assign({}, state, {
cartItems: [...state.cartItems, action.payload],
});
case REMOVE_FROM_CART:
//TODO
let currentItem = state.cartItems.filter((el) => el.itemId !== action.payload.itemId);
return Object.assign({}, state, {
cartItems: currentItem,
});
case SET_QUANTITY:
let idx = state.cartItems.findIndex((el) => el.itemId === action.payload.itemId);
//TODO
return {
...state,
cartItems: [...state.cartItems.slice(0, idx), action.payload, ...state.cartItems.slice(idx + 1)],
};
default:
return state;
}
};
export default itemReducer;
이처럼 Reducer에서 순수함수로 상태 업데이트를 해야한다.
방법으로는
- Object.assign()을 이용 (depth 1 까지 깊은 복사)
//단 상태 state가 객체일 경우 return Object.assign( {}, state, {새로 업데이트 할 것} );
참고 : Object.assign() – JavaScript | MDN
- Spread 펼쳐 문법을 이용
return { ...state, {새로 업데이트 할 것} };
// state 상태가 객체 형태라면 리듀서에서 특정 액션에서 리턴할 때
return Object.assign( {}, state, {새로 업데이트 할 것} );
👉 Spread 펼쳐 문법을 이용한다. [ ...state, {} ]
// state 상태가 객체 형태라면 리듀서에서 특정 액션에서 리턴할 때
return { ...state, {새로 업데이트 할 것} };
// store 폴더 안에 store.js => 스토어를 생성한 후 리듀서를 등록한다.
import { createStore } from "redux";
import rootReducer from '../reducers/index'; // 합친 리듀서 불러오기
const store = createStore(rootReducer); // 스토어 생성
export default store;
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import store from './store/store'; // 스토어 불러오기
import { Provider } from 'react-redux'; // store 사용설정을 위해 불러오기
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);