리덕스를 사용한 상태 관리
왜 리덕스를 사용하는가?
컴포넌트를 프리젠테이셔널 컴포넌트와 컨테이너 컴포넌트로 나눠서 사용하면 사용자가 이용할 유저 인터페이스와 상태를 다루는 데이터가 분리되어 프로젝트를 이해하기 쉽고 컴포넌트 재사용률도 높습니다.
1) 프리젠테이셔널 컴포넌트
오직 뷰만 담당하는 컴포넌트로 UI 관련된 state외에는 state는 존재하면 안되며, props로만 데이터를 처리합니다.
2) 컨테이너 컴포넌트
프리젠테이셔널 컴포넌트들과 컨테이너 컴포넌트들의 관리를 담당하는 컴포넌트로 프리젠테이셔널 컴포넌트와 반대로 스타일은 존재하면 안됩니다.
리덕스
리덕스의 개념적인 부분은 아래 포스팅을 참고해주세요
예제
0) react-redux 라이브러리 설치
yarn을 사용하시는 분들은 yarn add react-redux
npm을 사용하시는 분들은 npm install react-redux로 설치 가능합니다.
1) 작업 디렉토리 생성
/actions : 액션타입, 액션 생성자 파일
/components : 프리젠테이셔널 컴포넌트
/containers : 컨테이너 컴포넌트
/reducers : 스토어의 기본 상태값, 리듀서 파일
/lib : 일부 컴포넌트에서 함꼐 사용되는 파일
2) 프리젠테이셔널 컴포넌트 생성
좌클릭 시 증가, 우클릭 시 감소, 더블클릭 시 색 변경함수를 props로 전달받습니다.
/components/Counter.js
import React from 'react';
import PropTypes from 'prop-types';
import './Counter.css';
const Counter=({number, color, onIncrement, onDecrement, onSetColor})=>{
return (
<div
className = "Counter"
onClick={onIncrement}
onContextMenu={(e)=>{
e.preventDefault();
onDecrement();
}}
onDoubleClick={onSetColor}
style={{
backgroundColor : color
}}
>
{number}
</div>
);
};
Counter.propTypes ={
number : PropTypes.number,
color : PropTypes.string,
onIncrement : PropTypes.func,
onDecrement : PropTypes.func,
onSetColor : PropTypes.func
}
Counter.defaultProps ={
number : 0,
color : 'black',
onIncrement : ()=>console.warn('onIncrement not defined'),
onDecrement : ()=>console.warn('onDecrement not defined'),
onSetColor : ()=>console.warn('onSetColor not defined')
}
export default Counter;
/components/Counter.css
.Counter{
width: 10rem;
height: 10rem;
display: flex;
align-items: center;
justify-content: center;
margin:1rem;
color:white;
font-size:3rem;
border-radius:100%;
cursor:pointer;
user-select: none;
transition: background-color 0.75s;
}
3) 액션 생성
증가, 감소, 색상 변경 액션을 정의합니다.
/actions/ActionTypes.js
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const SET_COLOR = 'SET_COLOR';
정의한 액션을 만들어주는 함수를 만들어 줍니다.
/actions/index.js
import * as types from './ActionTypes';
export const increment = () => ({
type : types.INCREMENT
});
export const decrement = () => ({
type : types.DECREMENT
});
export const setColor = (color) => ({
type : types.SET_COLOR,
color
});
4) 리듀서 생성
액션의 type에 따라 변화를 일으키는 함수입니다. 최초 변화를 일으키기전에 가지고 있어야 하는 초기 상태(initai를 정의 해야합니다.
/reducers/index.js
import * as types from '../actions/ActionTypes';
const initialState = {
color : 'black'
, number : 0
};
function counter (state=initialState, action){
switch(action.type){
case types.INCREMENT:
return{
...state,
number:state.number+1
};
case types.DECREMENT:
return{
...state,
number:state.number-1
};
case types.SET_COLOR:
return{
...state,
color : action.color
};
default:
return state;
}
}
export default counter;
5) 스토어 생성
리덕스에서 가장 핵심적인 인스턴스로 현재 상태가 내장되어 있고, 상태를 업데이트 할 떄마다 구독중인 함수를 호출합니다.
리덕스에서 createStore를 불러와서 리듀서를 파라미터로 넣어 스토어를 생성합니다.
/src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './containers/App';
import './index.css';
import {createStore} from 'redux';
import reducers from './reducers';
const store = createStore(reducers);
ReactDOM.render(
<App />
,document.getElementById('root')
);
6) Provider 컴포넌트로 리액트 앱에 store연동
Provider는 react-redux라이브러리에 내장된 리액트 애플리케이션에 손쉽게 스토어를 연동할 수 있도록 도와주는 컴포넌트 입니다.
Provider를 불러와서 최상위 컴포넌트를 감싸줍니다.
import {Provider} from 'react-redux';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
,document.getElementById('root')
);
7) 컨테이너 컴포넌트 생성
react-redux의 connect 함수를 사용하여 스토어에 연결합니다.
connect([mapStateToProps], [mapDispatchToProps], [mergeProps])
mapStateToProps : store.getState() 결과 값인 state를 파라미터로 받아서 props로 사용할 객체를 반환
mapDispatchToProps : dispatch를 파라미터로 받아 액션을 디스패치하는 함수들을 객체 안에 넣어서 반환
mergeProps : state와 dispatch가 동시에 필요한 함수를 props로 전달해야할 때 사용(일반적으로 잘 사용하지는 않음)
/containers/CounterContainer.js
import Counter from '../components/Counter';
import * as actions from '../actions';
import { connect } from 'react-redux';
// 13가지 색상 중 랜덤으로 선택하는 함수
export function getRandomColor() {
const colors = [
'#495057',
'#f03e3e',
'#d6336c',
'#ae3ec9',
'#7048e8',
'#4263eb',
'#1c7cd6',
'#1098ad',
'#0ca678',
'#37b24d',
'#74b816',
'#f59f00',
'#f76707'
];
// 0부터 12까지 랜덤 숫자
const random = Math.floor(Math.random() * 13);
// 랜덤 색상 반환
return colors[random];
}
// store 안의 state 값을 props로 연결
const mapStateToProps = (state) => ({
color: state.color,
number: state.number
});
/*
액션 생성 함수를 사용하여 액션을 생성하고,
해당 액션을 dispatch하는 함수를 만든 후, 이를 props로 연결
*/
const mapDispatchToProps = (dispatch) => ({
onIncrement: () => dispatch(actions.increment()),
onDecrement: () => dispatch(actions.decrement()),
onSetColor: () => {
const color = getRandomColor();
dispatch(actions.setColor(color));
}
});
// Counter 컴포넌트의 Container 컴포넌트
// Counter 컴포넌트를 애플리케이션의 데이터 레이어와 묶는 역할
const CounterContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
export default CounterContainer;
실행 결과
참고 : 리액트를 다루는 기술(저:VELOPERT)