리덕스를 사용한 상태 관리


왜 리덕스를 사용하는가?


컴포넌트를 프리젠테이셔널 컴포넌트컨테이너 컴포넌트로 나눠서 사용하면 사용자가 이용할 유저 인터페이스와 상태를 다루는 데이터가 분리되어 프로젝트를 이해하기 쉽고 컴포넌트 재사용률도 높습니다.


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)



'언어 > ReactJS' 카테고리의 다른 글

[React] 리덕스 개념  (0) 2019.05.24
[React] 함수형 컴포넌트  (0) 2019.05.21
[React] 라이프 사이클 함수  (0) 2019.05.07
[React] 컴포넌트 반복  (0) 2019.05.05
[React] Ref  (0) 2019.05.02

리덕스 개념


리덕스?

리액트의 상태를 더 효율적으로 관리하는 데 사용하는 상태 관리 라이브러리로 상태 관리의 로직을 컴포넌트 밖에서 처리합니다.

리액트에서 사용하려고 만든 라이브러리지만, 리액트에 의존하지 않기때문에 리액트를 사용하지 않아도 리덕스 사용 가능 합니다.


리덕스 관련 용어
1) 스토어 : 애플리케이션의 상태 값들을 내장
2) 액션 : 상태 변화를 일으킬 때 참조하는 객체로 반드시 type을 가져야 함
3) 디스패치 : 액션을 스토어에 전달하는 것
4) 리듀서 : 상태를 변화시키는 로직이 있는 함수
5) 구독 : 스토어 값이 필요한 컴포넌트는 스토어를 구독

그림으로 표현하면 아래와 같습니다.

리덕스 규칙

리덕스 공식 메뉴얼에서도 안내하는 규칙 세가지가 있습니다.


1) 스토어는 단 한개만 존재

2) state는 읽기 전용

3) 변화는 순수 함수로 구성





리덕스 사용

리덕스 실습은 JSBin 사이트를 활용해서 리액트 환경이 아닌 환경에서 실습을 진행 했습니다.

1) HTML에 Redux 관련 <script>태그 추가

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.6.0/redux.js"></script>
</body>
</html>


2) console.log로 Redux 확인


아래와 같은 명령을 실행하면 정상적으로 추가했는지 확인 가능합니다.

console.log(Redux);


콘솔창 결과



3) action 추가


실습에서는 증가와 감소 액션을 만들어서 사용 했습니다.


const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

const increment = (diff) => ({
type : INCREMENT
,diff : diff
});
const decrement = (diff) => ({
type : DECREMENT
,diff : diff
});


4) 리듀서 추가


리듀서는 변화를 일으키는 함수로 본 실습에서는 실질적으로 증·감하는 역할을 합니다.


state값을 초기화하는 함수도 같이 추가 했습니다.


const initialState = {
number : 0
};

function counter(state = initialState, action){
switch(action.type){
case INCREMENT:
return {number:state.number + action.diff};
case DECREMENT:
return {number:state.number - action.diff};
default :
return state;
}
}


5) 스토어 생성


파라미터로 리듀서를 넘겨줍니다.


const {createStore} = Redux;

const store = createStore(counter);


6) 구독

리액트에서 구독하는 작업은 react-defux의 connect 함수가 대신 하지만, 본 실습에서는  subscribe함수를 직접 사용 했습니다.


스토어의 상태가 변화할 때마다 호출 되고, unsubscribe함수를 반환하기 때문에 unsubscribe 함수를 호출하면 구독 취소가 가능합니다.


const unsubscribe = store.subscribe(()=>{
console.log(store.getState());
});


7) 수행


dispatch함수를 통해 액션을 넘겨줍니다.


store.dispatch(increment(1));
store.dispatch(increment(10));
store.dispatch(decrement(5));


콘솔창 결과




참고 : 리액트를 다루는 기술(저:VELOPERT)


'언어 > ReactJS' 카테고리의 다른 글

[React ]리덕스를 사용한 상태 관리  (3) 2019.06.14
[React] 함수형 컴포넌트  (0) 2019.05.21
[React] 라이프 사이클 함수  (0) 2019.05.07
[React] 컴포넌트 반복  (0) 2019.05.05
[React] Ref  (0) 2019.05.02

+ Recent posts