Back Ground

ReactJS - Redux 본문

Javascript/React.js

ReactJS - Redux

Back 2018. 11. 24. 22:10



리덕스

리덕스는 리액트에서 상태를 더 효율적으로 관리하는 데 사용하는 상태관리 라이브러리



App에서 모든 상태의 로직을 관리하고 있기 때문에 App 컴포넌트의 state를 업데이트하면 App컨포넌트가 리랜더링되고,

리액트 특성상 하위 컴포넌트도 모두 리렌더링된다.


기존에 사용 하던 방식으론 컴포넌트 개수가 많지 않기 때문에 App에서 상태 관리를 하고,

최적화하는 것이 그렇게 어렵지않지만 프로젝트가 좀더 복잡 해지면 어떨까..


02

[좀 더 복잡한 혀태의 컴포넌트 구조]


참고 : https://velopert.com/1225






리덕스는 쉽게 설명하면 상태 관리의 로직을 컴포넌트 밖에서 처리하는 것.

리덕스를 사용하면 스토어(store)라는 객체 내부에 상태를 담게 됩니다. 이를 사용하면 다음 구조로 프로젝트를 관리 할 수 있다.




[리덕스를 적용한 구조]





 스토어

 애플리케이션의 상태 값들을 내장하고 있다

 액션

 상태변화를 일으킬 때 참조하는 객체

 디스패치

 액션을 스토어에 전달하는 것을 의미

 리듀서

 상태를 변화시키는 로직이 있는 함수

 구독

 스토어 값이 필요한 컴포넌트는 스토어를 구독







리덕스의 세가지 규칙



스토어는 단 한 개 


스토어는 언제나 단 한개

스토어를 여러 개 생성해서 상태를 관리하면 안된다. 그 대신 리듀서를 여러 개 만들어서 관리할 수 있다. 




state는 읽기 전용


리덕스의 상태, state값은 읽기 전용이다.

값은 절대로 직접 수정하면 안된다. 그렇게 하면 리덕스의 구독 함수를 제대로 실행하지 않거나 

컴포넌트의 리렌더링이 되지 않을 수 있다. 


상태를 업데이트할 때는 언제나 새 상태 객체를 만들어서 넣어 주어야 한다.

업데이트를 할 때 마다 새 객체를 만들어야 하다니 메모리 누수가 일어나지 않을까 걱정되지 않나

걱정하지 마세요.








예제 


src에 디렉터리 만들기


>actions

>components

>containers

>reducers


src/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './containers/App';
 
//리덕스 관련 불러오기
import { createStore } from 'redux';
import reducers from './reducers';//reducers (리듀서) : 상태를 변화시키는 로직이 있는 함수 
import { Provider } from 'react-redux';//Provider : 리액트 애플리케이션에 손쉽게 스토어를 연동할수 있도록 도와주는 컴포넌트
 
/*
- 엔트로 포인트 index.js - 
엔트리 : 시작점을 웹팩에서는 엔트리(entry)라고 한다. http://blog.jeonghwan.net/js/2017/05/15/webpack.html*/
 
//스토어 생성 (스토어 : 애플리케이션의 상태 값들을 내장하고 있다.)
const store = createStore(reducers); //createStore를 불러와 해당 함수에 직접 만든 리듀서를 넣어서 생성
//store에 state를 넣어 공유 하는 개념
 
ReactDOM.render(
    <Provider store={store}>{/*Provider : 스토어에 state를 갖고 있는 리듀서 넣게 해준다*/}
       <App />
    </Provider>
    , document.getElementById('root')
    );
 
cs






containers/CounterContainer.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import Counter from '../components/Counter';
import * as actions from '../actions';
import {connect} from 'react-redux';
 
/* 컨테이너 컴포넌트 
    - 프로젠테이셔널 컴포넌트로 전달 
    컨테이너 컴포넌트에는 스토어가 연동 되어 있다.
    react-redux라이브러리의 connect함수를 사용하여 컴포넌트 스토어에 연결시킨다.
    mapStateToProps    : store.getState()결과 값인 state를 파라미터로 받아 컴포넌트의 props로 사용할 객체를 반환한다.
    mapDispatchToProps : dispatch를 파라미터로 받아 액션을 디스패치하는 '함수들'을 객체안에 넣어서 반환한다.
    mergeProps : state와 dispatch를 동시에 필요한 함수를 props로 전달해야 할 때 사용하는데, 일반적으로는 잘 사용하지 않는다.
*/  
 
export function getRandomColor(){
    const colors = [
         '#495057'
        ,'#F03e3e'
        ,'#d5225c'
        ,'#ae2ec9'
        ,'#7048e8'
        ,'#1098ad'
        ,'#37daae'
        ,'#eee'
        ,'#fed'
        ,'#000'
        ,'#3355de'
        ,'#8adefe'
        ,'#0f0'
    ];
 
    //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));
    }
});
 
const CounterContainer = connect(
    mapStateToProps,
    mapDispatchToProps
)(Counter);// mapStateToProps,mapDispatchToProps의 모든 값을 Counter에 넣게된다.
 
/*
connect 함수를 호출하고 나면 또 다른 함수를 반환한다.
이때 반환하는 함수의 파라미터로 리덕스에 연결 시킬 컴포넌트를 넣으면
    mapStateToProps,
    mapDispatchToProps 에서 정의한 값들을 props로 받아 오는 새 컴포넌트를 만든다.
*/
 
export default CounterContainer;
cs



containers/App.js

1
2
3
4
5
6
7
8
9
10
11
12
import React, { Component } from 'react'
import CounterContainer from './CounterContainer';
 
export default class App extends Component {
    render() {
        return (
            <div>
                <CounterContainer/>
            </div>
        );
    }
}
cs







components/Counter.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.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; /* 에니메이트 효과 */
}
cs



components/Counter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import React from 'react';
import PropTypes from 'prop-types'
import './Counter.css';
 
/* 프리젠테이셔널(presentational) 
   - 숫자와 색상값 더하기/빼기 , 색상변경 함수를 props로 전달 받는다.
*/
 
/*
컴포넌트의 뷰가 어떻게 생길지만 담당하는 
프리젠테이셔널(presentational)[ 프레젠테이션 기술 개발 강좌 ] 컴포넌트를 저장한다. 
*/
const Counter = ({number,color,onIncrement,onDecrement,onSetColor}) => {
    return(                      
        <div
            className = "Counter"
            onClick={onIncrement}
            onContextMenu={(e)=>//onContextMenu 마우스 오른쪽버튼 이벤트
                e.preventDefault(); //e.preventDefault() 메뉴가 열리는 것을 방지
                onDecrement();
            }}
            onDoubleClick={onSetColor}
            style={
                {backgroundColor : color}
            }
        >
            {number}
        </div>
    );
};
 
Counter.prototypes = {
    number : PropTypes.number,
    color : PropTypes.string,
    onIncremet : PropTypes.func,
    onDecrement : PropTypes.func,
    onSetColor : PropTypes.func,
}
 
Counter.defaultProps = {
    number : 0,
    color : 'black',
    onIncremet  : () => console.warn('onIncrement not defined'),
    onDecrement : () => console.warn('onDecrement not defined'),
    onSetColor  : () => console.warn('onSetColor not defined'),
}
 
export default Counter;
cs






actions/ActionTypes.js

1
2
3
export const INCREMENT = 'INCREMENT';
export const DECREMENT = 'DECREMENT';
export const SET_COLOR = 'SET_COLOR';
cs



actions/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import * as types from './ActionTypes';
 
//액션 타입과 액션 생성자 파일을 저장
// 모든 객체엔 type값이 필수 
export const increment = () =>({
    type : types.INCREMENT
});
 
export const decrement = () =>({
    type : types.DECREMENT
});
 
//액션과 함께 전달 헤야 할 값이 있을때 키를 추가 하는 방식으로
export const setColor = (color) =>({
    type : types.SET_COLOR,
    color //color : black ( 기본값으로 설정 Counter.defaultProps ) 
});
cs



reducers/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import * as types from '../actions/ActionTypes';
 
//스토어의 기본 상태값과 상태의 업데이트를 담당하는 리듀서 파일들을 저장한다.
 
const initialState = {
    color : 'black',
    number : 0
}
 
/* 
리듀서 함수를 정의합니다.(상태를 변화 시키는 로직이 있는 함수)
 
리듀서는 state와 action을 파라미터로 받습니다. 
state가 undefined일 때 ( 스토어가 생성 될 때 ) 
state기본 값을 initialState로 사용합니다. 
이때 주의할 점은 state를 직접 수정하면 안 되고,
기존 상태 값에 원하는 값을 덮어쓴 새로운 객체를 만들어서 반환해야 합니다.
*/
 
function counter(state = initialState, action){//state가 없다면 initialState가 대신 함
    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;
cs








서브 리듀서 생성 



reducers/color.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import * as types from '../actions/ActionTypes';
 
const initialState = {
    color:'black'
};
 
const color = (state = initialState , actions )=> {
    switch (actions.type) {
        case types.SET_COLOR:
            return {
                color: actions.color
            };
        default:
            return state;
    }
}
 
export default color;
cs




reducers/number.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import * as types from '../actions/ActionTypes';
 
const initialState = {
    number : 0 
};
 
const number = (state = initialState , action) => {
    switch (action.type) {
        case types.INCREMENT:
            return {
                number : state.number + 1
            }
        case types.DECREMENT:
            return {
                number : state.number - 1
            }
        default:
            return state;
    }
}
 
export default number;
cs



reducers/color.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//import * as types from '../actions/ActionTypes';
import number from './number';
import color from './color';
import { combineReducers } from 'redux';
 
//스토어의 기본 상태값과 상태의 업데이트를 담당하는 리듀서 파일들을 저장한다.
 
/*
const initialState = {
    color : 'black',
    number : 0
}
*/
 
/* 
리듀서 함수를 정의합니다.(상태를 변화 시키는 로직이 있는 함수)
 
리듀서는 state와 action을 파라미터로 받습니다. 
state가 undefined일 때 ( 스토어가 생성 될 때 ) 
state기본 값을 initialState로 사용합니다. 
이때 주의할 점은 state를 직접 수정하면 안 되고,
기존 상태 값에 원하는 값을 덮어쓴 새로운 객체를 만들어서 반환해야 합니다.
*/
 
 
/*
function counter(state = initialState, action){//state가 없다면 initialState가 대신 함
    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;    
    }
};
*/
 
const reducers = combineReducers({
    numberData : number,
    colorData : color
});
 
export default reducers;
cs


'Javascript > React.js' 카테고리의 다른 글

ReactJS - PureComponent vs Component  (0) 2018.12.01
ReactJS - 컴포넌트 이용한 게시판  (0) 2018.12.01
React - 스타일링(SCSS)  (0) 2018.11.02
React - 함수형 컴포넌트  (0) 2018.11.01
React - 라이프 사이클  (1) 2018.10.31
Comments