kok202
React 강의 정리 (5)

2019. 8. 18. 01:39[정리] 기능별 개념 정리/React

Redux

  • Facebook 에서 주도하여 개발
  • Flux 아키텍쳐를 구현한 라이브러리
  • Flux 와 완전히 같다고 할 수는 없다.

MVC 의 문제점과 Flux 아키텍처가 필요한 이유 : http://bestalign.github.io/2015/10/06/cartoon-guide-to-flux/

Redux 가 Flux 를 완전히 따라 할 수 없는 이유 : http://bestalign.github.io/2015/10/26/cartoon-intro-to-redux/ 

 

 

 

Redux 개념 

  • action : 발생하는 상황. 어떤 작업인지 정보를 가진 객체
  • action creator : 어떤 값을 업데이트 하기위해 어떤 action 을 발생시킬지 결정한다.
  • dispatcher : 액션을 읽고 어떤 일을 할지 처리한다. 동기 처리된다. 작업이 중첩되지 않도록 도와준다.
  • store : 애플리케이션의 현재 state 를 저장하는 공간이다. (reducer 들이 관리하는 state 들은 모두 여기에 모인다.)
  • store.getState() : 현재 상태를 받는다.
  • store.dispatch(action) : action 이 발생했음을 알린다.
  • store.subscribe(listener) : store 안의 데이터가 update 됬으면 listener (callback 메소드)를 실행한다.
  • reduce : Action 객체를 보고 어떤 일이 발생했는지에 따라 어떻게 처리할지 결정하는 함수다. 
  • reduce 함수는 순수 함수여야한다.

 

 

 

순수 함수

  1. 비동기 작업을 처리해선 안된다. (ex. API 콜, DB와의 IO, 등...)
  2. 넘어오는 Argument 값을 변경해선 안된다.
  3. 같은 Arument 에 대해 같은 결과가 실행되어 나와야한다.

 

 

 

Redux 의 3가지 주요 원칙

  1. 애플리케이션의 state 를 위해 단 하나의 Store 를 사용한다.
  2. State 는 반드시 dispatch(action) 으로만 변경되어야한다. 직접 변경해선 안된다.
  3. Reducer 는 순수 함수이여야 한다.

 

 

 

데이터 플로우

  1. 사용자가 버튼을 누른다.
  2. 버튼을 누르는 행위는 this.props.handlerA를 실행시킨다.
  3. this.props.handlerA는 dispatch({type:"A"})에 맵핑되어있다.
  4. dispatch에 의해 reducer가 실행된다.
  5. reducer는 action.type == "A" 일 경우 어떤 로직을 취하라고 되어있다.
  6. reducer는 state 를 변경시키고 newState 를 반환한다.
  7. 해당 state는 어떤 컴포넌트의 props 와 연결되있다. React는 props의 변경을 파악하고 렌더링을 다시한다.
  8. 해당 state는 store 에 의해 감시되고 있다.
  9. 해당 state에 변경이 이루어 졌으므로 수행해야하는 subscribe listener (callback) 를 실행한다.

 

 

 

강의에서는 배경화면을 변경하는 것도 다루고 있지만 편의상 배경화면 변경은 빼고 진행한다.

강의 예시 원본 프로젝트 : https://bitbucket.org/velopert/redux-example/src/master/

project
---- /node_module
---- /src
-------- /actions
-------- /components
-------- /reducers
-------- index.js
index.html
package.json
webpack.config.js

 

 

 

myProject/src/actions/ActionTypes.js

export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";

myProject/src/actions/index.js

import * as types from './ActionTypes';

export function increment() {
    return { type: types.INCREMENT };
}

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

import * as types : 데이터를 가져오는 데 타입으로 가져와라

Action 의 이름은 반드시 대문자와 언더스코어를 이용해서 이름을 정한다.

Action 은 객체다.

Action 은 Type 필드를 반드시 갖는다.

Action 안에 원하는 데이터 필드를 넣을 수 있다.

Action 안의 원하는 데이터 필드에 dispatch 할 때 데이터를 넣을 수 있다.

Action 안의 원하는 데이터 필드에 dispatch 할 때 넣은 데이터를 reducer 가 읽을 수 있다.

 

 

 

myProject/src/reducers/index.js

import { combineReducers } from 'redux';
import * as types from '../actions/ActionTypes';

const initialState = {
    number: 0,
};

export default function counterReducer(oldState = initialState, action) {
    let newState = { ...oldState };
    switch(action.type) {
        case types.INCREMENT:
            newState.number = oldState.number + 1;
        case types.DECREMENT: 
            newState.number = oldState.number - 1;
    }
    return newState;
}

const reducers = combineReducers({
    counterReducer
});

export default reducers;

reducer 는 (oldState, action) 을 인자로 가진다.

reducer 는 newState 를 반환한다.

reducer 를 여러 파일로 쪼개서 관리할 수 있다.

이 때 쪼개진 reducer 들을 합치기 위해 combineReducers 를 이용한다.

reducer 안의 데이터 변경은 반드시 새로운 것 만들고 이를 복사해서 반환하는 형식이여야한다.

 

 

 

myProject/src/index.js

import React from 'react';
import ReactDOM from 'react-dom';

import Application from './components/Application';
import { createStore } from 'redux';
import reducers from './reducers';
import { Provider } from 'react-redux';

const store = createStore(reducers);

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root')
);

createStore(reducers) 를 통해 reducer 들을 수집함과 동시에 reducer 들이 바라보는 state 를 관리한다.

Provider 를 통해 store 를 지정한다.

 

 

 

myProject/src/components/Application.js

import React, { Component } from 'react';
import Counter from './Counter';

class Application extends Component {
    render() {
        return(
            <Counter/>
        );
    }
}

export default Application;

myProject/src/components/Counter.js

import React, { Component } from 'react';
import CounterValue from './CounterValue';
import CounterButtons from './CounterButtons';
import { connect } from 'react-redux';
import * as actions from '../actions';

class Counter extends Component {
    render() {
        return(
            <div>
                <CounterValue number={this.props.number}/>
                <CounterButtons
                    onPlus={this.props.handleIncrement}
                    onSubtract={this.props.handleDecrement}/>
            </div>
        );
    }
}

const mapStateToProps = (state) => {
    return {
        number: state.counterReducer.number
    };
};

const mapDispatchToProps = (dispatch) => {
    return {
        handleIncrement: () => { dispatch(actions.increment())},
        handleDecrement: () => { dispatch(actions.decrement())},
    };
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

myProject/src/components/CounterValue.js

import React, { Component, PropTypes } from 'react';

const propTypes = {
    number: PropTypes.number
};

const defaultProps = {
    number: -1
};

class CounterValue extends Component {
    render() {
        return(
            <div>
                <h1>{this.props.number}</h1>
            </div>
        );
    }
}

CounterValue.propTypes = propTypes;
CounterValue.defaultProps = defaultProps;
export default CounterValue;

myProject/src/components/CounterButtons.js

import React, { Component, PropTypes } from 'react';

const propTypes = {
    onPlus: PropTypes.func,
    onSubtract: PropTypes.func,
};

const defaultProps = {
    onPlus: () => console.warn('onPlus is not defined');
    onSubtract: () => console.warn('onSubtract is not defined');
};

class CounterButtons extends Component {
    render() {
        return(
            <div>
                <button onClick={this.props.onPlus}>+</button>
                <button onClick={this.props.onSubtract}>-</button>
            </div>
        );
    }
}

CounterButtons.propTypes = propTypes;
CounterButtons.defaultProps = defaultProps;
export default CounterButtons;

mapStateToProps : Reducer 안에서 관리하는 값을 컴포넌트의 props 로 맵핑한다.

mapDispatchToProps : 컴포넌트의 props 에서 어떤 커맨드가 실행되면 어떤 것을 store.dispatch(action) 할지 맵핑한다.

connect(mapStateToProps, mapDispatchToProps)(Counter);

 

 

 

Smart 컴포넌트

  • Redux 와 연관 O
  • state 를 subscribce 하고 action 을 보낸다.
  • DOM, CSS 를 관리하지 않는다. 
  • 여기서는 Counter 가 똑똑한 컴포넌트다.

Dumb 컴포넌트

  • Redux 와 연관 X
  • props 에서 데이터를 읽는다
  • props 에서 callback 을 부른다.
  • DOM, CSS 를 관리한다.
  • 여기서는 CounterValue, CounterButtons 이 멍청한 컴포넌트다.

 

 

 

주요 개발 흐름

  1. Action 정의
  2. Reducer 생성, 로직 처리
  3. Reducer state 와 특정 컴포넌트의 props 아래 값과 연결 (mapStateToProps)
  4. 특정 컴포넌트 props 아래 식별자와 dispatcher 연결 (mapDispatchToProps)
  5. Reducer state 를 store 에 등록 (createStore(reducers))
  6. store 의 범위 지정 (<Provider store={store}> <Application> </Provider>)

사견 : Action 은 사실상 타입으로 정의만 해두는 것이고 dispatch 하면 consume 하는 reducer 가 주요 작업인 듯하다. 그래서 의미적으로 Reduce + flux 가 된 것이 아닌가 싶다. 

 

 

 

 

'[정리] 기능별 개념 정리 > React' 카테고리의 다른 글

React 기본 강좌 정리 - 1~2  (0) 2020.02.11
React 강의 정리 (6)  (0) 2019.08.23
React 강의 정리 (3,4)  (0) 2019.08.18
React 강의 정리 (2)  (0) 2019.08.18
React 강좌 정리 (1)  (0) 2019.08.17