이 글은 벨로퍼트님의 Redux (3) 리덕스를 리액트와 함께 사용하기 를 보고 작성한 글입니다.

이 글을 보시기 전에 리액트 기초을 보고오세요.

0. 배경

+ 버튼을 클릭하면 Counter가 증가
- 버튼을 클릭하면 Counter가 감소하는 웹을 만들어 보겠습니다.

1. 기본 설치

먼저 프로젝트를 생성합니다.

> create-react-app react-redux-counter

react-redux-counter 디렉토리에 들어가 필요한 모듈을 설치합니다.

npm install --save  redux react-redux

2. 입력받을 화면 만들기

/src/components/Counter.js 를 생성합니다.

import React from 'react' ;
const Counter = () => {
  return (
    <div>
      <h1>myCounter</h1> // 카운터를 표시해줄 부분
      <button>+</button> // 증감 버튼
      <button>-</button> // 감소 버튼
    </div>
  ) ;
} ;

export default Counter ;

src/App.js

import React, { Component } from 'react';

import './App.css';
import Counter from './components/Counter';

class App extends Component {
  render() {
    return (
      <div className="App">
        <Counter  />
      </div>
    );
  }
}

export default App;

image.png

3. 액션

액션: 상태 변화를 일으킬 때 참조하는 객체입니다.
우리는 Counter 값을 변화시키는데 + 버튼, - 버튼을 이용하여 증.감소를 합니다.

그럼 만들어야할 액션은 두가지 + 버튼, -버튼입니다.

 * Store에 대해 뭔가 하고싶은 경우에 Action을 발행한다.
 * Store의 문지기(Reducer)가 Action을 감지하면, 새로운 State가 생긴다.

/src/store/modules/counter.js

// 액션 타입 정의
const INCREMENT = 'counter/INCREMENT' ;
const DECREMENT = 'counter/DECREMENT' ;

// 액션 생성 함수 정의
export const increment = () => ({ type: INCREMENT }) ;
export const decreuemt = () => ({ type: DECREMENT }) ;

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

image.png

4. 리듀서 작성

리듀서는 상태에 변화를 일으키는 함수이다. 리듀서는 파라미터를 두 개를 받습니다.
첫번째 파라미터는 현재상태이고, 두번째 파라미터는 액션 객체입니다.

/src/store/modules/counter.js

// 리듀서 작성
export default function Counter(state=initialState, action) {
    switch(action.type) {
        case INCREMENT:
            return {
                ...state,
                number: state.number + 1 ,
            } ;
        case DECREMENT:
            return {
                ...state,
                number: state.number - 1,
            } ;
        default:
            return state ;
    }
}

image.png

5. 리듀서 합치기

redux의 내장함수인 combineReducers를 사용하여 리듀서를 하나로 합치는 작업을한다.
아직은 하나의 리듀서뿐이지만..!

src/store/modules/index.js

import { combineReducers } from 'redux' ;
import counter from './counter' ;

export default combineReducers({
    counter,
    //다른 리듀서를 만들게 되면 여기에 import
}) ;

6. 스토어 만들기

스토어는 하나의 애플리케이션 에는 하나의 스토어가 있다.
우리도 딱 하나의 스토어만 만들어주면 됩니다.

src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
// createStore 와 루트 리듀서 불러오기
import { createStore } from 'redux';
import rootReducer from './store/modules';
import { Provider } from 'react-redux' ;

import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

// **** 리덕스 개발자 도구 적용
const devTools =
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__();
const store = createStore(rootReducer, devTools);

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

여기까지 정리하자면 우리는 화면(Counter.js) 에서 +,- 버튼을 클릭하게 되면 액션을 통해 리듀서를(counter.js) 거치고 스토어 상태를 변화한 후 다시 view에 변화를 주어야한다.

우리가 한 일은
1. 화면생성 (Counter.js)
2. 액션생성 (counter.js)
3. 리듀서 생성(counter.js)
4. 스토어 생성(index.js)

이렇게하면 다 된 것 같지만 여기서 몇까지 빼먹은 것이 있다.!

1. 액션을 스토어에 전달하는 역할인 dispatch 생성 (mapDispatchToProps)
2. store에 state를 view에 전달해주기 (mapStateToProps)
3. store와 reducer를 연결시킬 수 있도록 만들어진 Component 생성 (CounterContainter.js)
4. 위의 두가지가 적용된 props를 받을 수 있는 component를 정의 (App.js)

7. 위의(1,2,3) 생성

src/container/CounterContainer.js

위에서 말했다싶이, 컴포넌트에 리덕스 스토어 안에 있는 값이나 액션 함수들을 연결해주어야합니다.
리덕스와 연동된 컴포넌트를 우리는 컨테이너 컴포넌트라고 부릅니다.

import React, { Component } from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increment, decrement } from '../store/modules/counter';

class CounterContainer extends Component { //3
    handleIncrement = () => {
        this.props.increment() ;
    } ;

    handleDecrement = () => {
        this.props.decrement () ;
    } ;

    render() {
        const { number } = this.props ;
        return (
            <Counter 
                value={number}
                onIncrement={this.handleIncrement}
                onDecrement={this.handleDecrement}
            />
        ) ;
    } 
}


const mapStateToProps = ({ counter }) => ({  //2
    number: counter.number,
}) ;

const mapDispatchToProps = {increment, decrement} ; //1

export default connect ( // 스토어와 연결
    mapStateToProps,
    mapDispatchToProps
)(CounterContainer) ;

8. Coutner.js 수정

이제 우리는 스토어에서 변경된 counter(value 값),
증가, 감소 버튼을 Props로 디스패치한 것을 받아와야합니다.

src/containers/Counter.js

import React from 'react' ;

const Counter = ({ value, onIncrement, onDecrement }) => {
  return (
    <div>
      <h1>{value}</h1>
      <button onClick={onIncrement}>+</button>
      <button onClick={onDecrement}>-</button>
    </div>
  ) ;
} ;

export default Counter ;

9. App.js 수정

import React, { Component } from 'react';

import './App.css';
import CounterContainer from './container/CounterContainer.js' ;

class App extends Component {
  render() {
    return (
      <div className="App">
        <CounterContainer  />
      </div>
    );
  }
}

export default App;

image.png

버튼이 잘 동작하죠 ?

저도 리액트 초보라 잘못된 부분이 있으면 답글에 꼭 알려주세요.
고쳐나가겠습니다.