본문 바로가기
Programming/Javascript

React강좌3 - Redux0. redux 구조

by Wilkyway 2020. 8. 9.
반응형

과제를 따라 수행하면서도 리덕스(Redux) 개념이 잘 안서는데, 나름 이해한 부분을 정리해봅니다.

 

리덕스는 store로부터 데이터를 직접 공급받기 위한 관리체계 개념입니다. 1개의 store에서 모든 데이터를 관리하죠. 동작 순서는 아래와 같으며, Action, Reducer를 별도 파일로 구성하고, Store도 App.js에서 생성해주어야 합니다. 

1. Action

'액션 생성자'에서 Action에 대한 정의만합니다. 단순히 어떤 동작(타입)인지만 정의합니다. 실제 'Action' 이라고 할 수 있는 '동작'의 개념은 Reducer가 수행합니다.  예로 아래와 같이 사용합니다. 이전 수행한 과제 블로그를 보시면 type 마져도 ActionType.js에 모아서 정의해 놓고 import해서 쓰기도 합니다.

import * as types from './ActionTypes'; // Action 타입만 별도로 정의할 경우..

export function setColor(color) {
    return {
        type: types.SET_COLOR,
        color: color
    };
}

export function decrement(){
    return{
        type: types.DECREMENT
    };
}

export function setColor(color){
    return {
        type: types.SET_COLOR,
        color
    }
}

기본적으로 'type' 은 필수항목입니다. 그리고 추가 키가 있을 수 있는데(optional), payload라고도 하고, 본 과제에서는 인자로 color값을 받아들이기 위해 color라는 키를 사용했습니다.

 

2. Reducer

위에서 말한대로 실제 동작을 정의합니다. 즉, 실질적인 store의 state를 변화시키는 함수입니다. 단 몇가지 제약이 있습니다.

(1) 순수 함수이어야 합니다. class type으로 정의 불가능합니다.

(2) 받아들인 데이터를 변경할 수 없습니다. 이전 state와 action을 받아서 다음 state를 반환할 뿐, 이전 state를 변경할 수 없습니다. (previous State, Action) => newState

 

사용 방법의 예는 아래와 같습니다.

(1) initialState를 선언해서 컴포넌트의 인자료 전달해줍니다. 

(2) switch문을 사용하여 action.type에 따라 각각의 변경을 수행합니다. (types가 아닌 type인 점 주의 필요)

import * as types from '../Actions/ActionTypes';

//초기상태 정하기

const initialState = {
    number: 0,   //최초 초기상태 정의
};

export default function counter(state = initialState, action) {  // 초기값 입력이 undefined일 때 initialState 할당

    switch (action.type) {
        case types.INCREMENT:
            return {
                ...state, // ...state에서 기존 정의한 state를 모두 복사해옴. 
                number: state.number + 1,     //그 중 number에 새 값 설정                
            };
        case types.DECREMENT:
            return {
                ...state,
                number: state.number - 1
            };
        default:
            return state;   // 기존 상태 그냥 전달. 이 세 케이스에 SET_COLOR가 없으므로 다른 reducer를 찾게 됨

    }

}

3. Store

Store는 공용 데이터가 저장되는 공간입니다. Reducer가 처리하여 생성한 new state가 store에 반영됩니다.

(1) store 생성은 createStore(reducers) 함수로 하며, 인자로 reducer를 전달합니다.

(2) Provider로 <App /> 컴포넌트를 감싸서 store가 적용될 컴포넌트를 설정하고, <Provider store={store}> 로 Provider에 store를 인식시켜줍니다.

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';

import { createStore } from 'redux';
import reducers from './Reducers';  // index.js 파일 불러옴

import { Provider } from 'react-redux'; // react-redux의 Provider 임포트

const store = createStore(reducers);  //스토어 생성시에는 reducer를 전달함

ReactDOM.render(
  <React.StrictMode>
    <Provider store={store}>  {/*Provider의 props로 store를 선언 */}
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

4. View(컴포넌트) - Action 연결

(1) 컴포넌트와 redux와의 연결은 connect 함수로 합니다. 개념은, Redux와 컴포넌트를 연결하는 새로운 함수를 반환하는 것입니다. connect의 인자로는 mapStateToProps와 mapDispatchToProps가 있습니다.

(2) mapStateToProps는 현재 컴포넌트의 props와 store의 state를 연결합니다.

(3) mapDispatchToProps는 현재 컴포넌트의 props action과 리덕스의 액션( type 정의 부분)과 연결합니다.

import React, { Component } from 'react';
import Value from './Value';
import Control from './Control';
import { connect } from 'react-redux';
import * as actions from '../Actions';


class Counter extends Component {
    constructor(props) {
        super(props);
        this.setRandomColor = this.setRandomColor.bind(this);
    }

    setRandomColor() {
        const color = [
            Math.floor((Math.random() * 55) + 200),
            Math.floor((Math.random() * 55) + 200),
            Math.floor((Math.random() * 55) + 200)
        ];
        this.props.handleSetColor(color);
    }

    render() {

        const color = this.props.color;
        const style = {
            background: `rgb(${color[0]}, ${color[1]}, ${color[2]})`
        };

        return (
            <div style={style}>
                <Value number={this.props.number} />
                <Control
                    onPlus={this.props.handleIncrement}
                    onSubtract={this.props.handleDecrement}
                    onRandomizeColor={this.setRandomColor}
                />
            </div>
        );
    }
}


const mapStateToProps = (state) => { // 컴포넌트의 state가 아닌 그냥 파라미터 이름(리덕스의 state 와 연결)
    return {
        number: state.counter.number,   // number에 store state의 counter.number 연결
        color: state.ui.color           // number에 store state의 ui.color 연결
    };
};

const mapDispatchToProps = (dispatch) => {  //액션을 연결.
    return {
        handleIncrement: () => { dispatch(actions.increment()) },
        handleDecrement: () => { dispatch(actions.decrement()) },
        handleSetColor: (color) => { dispatch(actions.setColor(color)) }
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);  
// 컴포넌트를 redux에 연결하는 또다른 함수 반환하고, 반환된 함수의 인자로 Counter를 넣어준다.

 

정리하자면, 

 

1. View

<Control> 컴포넌트의 onPlus 이벤트 발생 시 props의 handleIncrement가 실행되고,

2. Action

mapDispatchToProps에서 정의된 연결에 따라 dispatch(actions.increment()) 가 실행되면 Action에서

{ type: type.INCREMENT} 로 설정한다.

3. Reducer

type키의 값에 따라 INCREMENT일 경우 Reducer의 동작 정의에 따라 숫자 1이 더해지고, store의 state값을 갱신한다.

4. Store

Reducer에 의해 Store의 state값이 갱신되면 컴포넌트를 다시 렌더링 한다.

 

입니다. 좀 어렵습니다. 익숙해지는 건 더 어려울 것 같습니다. 그래도 힘내서 열심히 공부해보겠습니다^^

여러분도 화이팅~

 

- 끝 - 

반응형

댓글