React(리액트)_Counter_with Redux(리덕스)


* redux 의 기본 원칙

 1) redux 의 상태(state)는 store에 보관된다.

 2) store는 1개만 만든다.(store는 reducer 함수에 의해 만들어진다. reducer는 복수개 사용 가능)

 3) store의 state를 변경할 수 있는 유일한 방법 : action dispatch [ action을 store에 dispatch ]

  - store내에 존재하는 reducer가 action을 받아서 각 action type에 따라 해당하는 reducer 함수(순수함수)를 작동시키고, 각각의 reducer 함수의 작동에 의해 state를 변화시킨다. 

 - 즉, react component 에서 store로 dispatch 하는 action에 따라 store 내의 reducer 함수가 작동하여 store 내의 state 값을 변화시킨다.


* Actions

 - action은 state 변화를 정의하기 위한 객체.

 - action은 반드시 type(string 형태)을 가져야 함. type 외에도 reducer의 작동에 영향을 미칠 다른 요소(ex: text)가 있다면 추가할 수 있음.

{
type: ADD_TODO,
text
}

 - action creators (액션 객체를 만드는 함수)

function addTodo(text) {
return {
type: ADD_TODO,
text
}
}


* Reducers

 - Action은 state 변화를 정의하기 위한 요소들을 가지고 있지만, 그 요소들을 이용해서 어떻게 state를 변화시킬 것인지를 특정하는 함수가 reducer임. 


------------------------------------------------------------------------------


*********** 리덕스를 이용한 카운터 만들기 Tutorial: React(리액트)_Counter_with Redux(리덕스) **************


* Counter sample 화면



1. Counter app 의 src 폴더 구조

 - src-main.js

 - src-actions, reducers, components, containers

 - src-actions-ActionType.js, index.js

 - src-reducers-counterReducer.js, index.js

 - src-components-App.js

 - src-containers-AddCounter.js, Counter.js, RemoveCounter.js


2. src-main.js

 - main.js는 app.js를 자식 component로 가지는 가장 상위의 파일임.

 - 역할 : store를 생성해서 <Provider>로 <App> 을 감싸서 App에 store를 전달.(Provider의 props로 store를 전달

 --> App에 store가 전달되면, App 및 App의 모든 하위 component는 store의 모든 data에 접근 가능함.

 - store 생성 : const store = createStore(reducer);


// src-main.js

import React from 'react';
import { render } from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import App from './components/App';
import reducer from './reducers';

const store = createStore(reducer);

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


3. src-components-App.js

 - App.js(Dumb Component, Presentational Component)는 container component 들을 rendering 하여 화면을 구성하는 주요 파일임.

 - 렌더링되는 3개의 container component(Smart Component) : Counter, AddCounter, RemoveCounter

 - Dumb Component는 container component를 child component로 가지고, 그들을 렌더링하는 역할만을 수행함. Dumb Component는 store와 interaction을 하지 않으며, store의 state를 읽어오지 못함.

 - Smart Component는 store와 interaction 하며, store의 current state를 get 하거나, store에 action을 dispatch 하여 store의 state 를 변화시키기도 함.


// src-components-App.js

import React from 'react';
import Counter from '../containers/Counter';
import AddCounter from '../containers/AddCounter';
import RemoveCounter from '../containers/RemoveCounter';

const App = () => {
return (
<div className="container">
<Counter></Counter><br />
<div className="columns">
<div className="column is-11">
<AddCounter></AddCounter>
</div>
<div className="column auto">
<RemoveCounter></RemoveCounter>
</div>
</div>
</div>
)
}
export default App;


4. src-containers-AddCounter.js, Counter.js, RemoveCounter.js

 - Smart Component 3종

 - Smart Component는 store와 interaction 하며, store의 current state를 get 하거나, store에 action을 dispatch 하여 store의 state 를 변화시키기도 함.


src-containers-AddCounter.js

 - AddCounter component : Add 버튼 클릭시 Counter 숫자가 1씩 증가하도록.

 - Add 버튼 클릭시 +1 기능을 하는 액션을 dispatch의 parameter로 전달하면 됨.( ADD_COUNTER actionType을 dispatch)

 - addCounter 액션 생성함수를 실행하면 ADD_COUNTER action이 생성됨.(즉, addCounter() = ADD_COUNTER action)

 - 버튼의 onClick 속성에 (e) => {this.props.dispatch(addCounter())} 를 정의함.

 - mapDispatchToProps(dispatch)를 이용해서 dispatch(addCounter())를 addCounter component의 Props로 받아옴.

 - mapDispatchToProps(dispatch)의 return 내용으로는 { actions: bindActionCreators(addCounter, dispatch) } 를 사용.

 - bindActionCreators의 parameter로는 액션 생성함수와 dispatch 함수를 전달.

 - connect 함수에 mapDispatchToProps 를 인자로 전달하면 새로운 함수가 리턴되며, 그 함수는 인자로 Props를 받을 대상 component가 들어간다. (즉, 자기 자신 component에게 Props를 전달하려면 AddCounter를 넣고, 다른 파일의 component(Dumb Component 등)를 넣을 수도 있음.)

// src-containers-AddCounter.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { addCounter } from '../actions';
import { bindActionCreators } from 'redux';

class AddCounter extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="container">
<form>
<div className="field is-grouped">
<div className="control">
<button className="button is-primary" onClick={(e) => {e.preventDefault();this.props.dispatch(addCounter())}}>Add</button>
</div>
</div>
</form>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(addCounter, dispatch) }
}
export default connect(mapDispatchToProps)(AddCounter);


src-containers-Counter.js

 - mapStateToProps(state) 를 이용해서 count 값을 store로부터 this.props.count 로 전달받아서 사용.

 - connect(mapStateToProps)(Counter) : state 값을 Props로 Counter component(자기 자신)에 전달.


// src-containers-Counter.js


import React, { Component } from 'react';
import { connect } from 'react-redux';

class Counter extends Component {
constructor(props){
super(props);
}
render(){
return (
<div className="cotainer">
<div className="notification">
<h1>
{this.props.count}
</h1>
</div>
</div>
)
}
}
function mapStateToProps(state){
return {
count: state.counterReducer,
};
}
export default connect(mapStateToProps)(Counter);


src-containers-RemoveCounter.js

 - store에 connect하여 this.props.dispatch(removeCounter()) 를 props로 전달받아 사용.

// src-containers-RemoveCounter.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { removeCounter } from '../actions';
import { bindActionCreators } from 'redux';

class RemoveCounter extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="container">
<form>
<div className="field is-grouped">
<div className="control">
<button className="button is-primary" onClick={(e) => {e.preventDefault();this.props.dispatch(removeCounter())}}>Remove</button>
</div>
</div>
</form>
</div>
)
}
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(removeCounter, dispatch) }
}

export default connect(mapDispatchToProps)(RemoveCounter);


5. src-actions-ActionType.js, index.js


src-actions-ActionType.js

 - 액션 타입 정의

// src-actions-ActionType.js

export const ADD_COUNTER = 'ADD_COUNTER';
export const REMOVE_COUNTER = 'REMOVE_COUNTER';


src-actions-index.js

 - 액션 생성함수 정의

 - type 외의 요소로 증가/감소 숫자인 1을 payload 값으로 정의.

// src-actions-index.js

import * as actionType from './ActionType';

export const addCounter = () => ({
type: actionType.ADD_COUNTER,
payload: 1
});

export const removeCounter = () => ({
type: actionType.REMOVE_COUNTER,
payload: 1
});


6. src-reducers-counterReducer.js, index.js


src-reducers-counterReducer.js

 - counterReducer 정의 : innitialstate(state=0)와 action 을 parameter로 전달.

 - action.type에 따라 switch-case 문으로 분기

// src-reducers-counterReducer.js

import * as actionType from '../actions/ActionType';

const counterReducer = (state = 0, action) => {
let newState;
switch (action.type) {
case actionType.ADD_COUNTER:
return newState = state + action.payload;
case actionType.REMOVE_COUNTER:
return newState = state - action.payload;
default:
return state
}
}

export default counterReducer;


src-reducers-index.js

 - 리듀서가 복수인 경우를 대비해 combineReducers 함수를 이용해 최종 리듀서인 counterApp을 생성.

 - main.js 에서 import reducer from './reducers'; 로 사용하게 됨. 


// src-reducers-index.js

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

const counterApp = combineReducers({
counterReducer
})

export default counterApp






















+ Recent posts