본문 바로가기
Coding/Etc

React Redux 정리

by Hide­ 2018. 4. 24.
반응형

리액트(React.js)는 컴포넌트 기반으로 구성되어있다.

각 컴포넌트별로 지역변수/전역변수가 존재하지만 가끔씩 앱 전역으로 쓰이는 전역변수가 필요하다.

예를 들면 로그인 유무를 검사하기 위해 isLogin이라는 변수를 사용한다고 가정해보자.

isLogin이 true일 경우 정상적으로 로그인이 완료된 상태이기 때문에 회원만 접근이 가능한

컴포넌트를 보여줄 것이다. 하지만 false일 경우는 비회원을 위한 컴포넌트만 접근이 가능해야한다.

Redux를 알기전까지는 하위 컴포넌트들에게 props로 넘겨줬지만 이는 상당히 불편한 접근법이다.

A -> B -> C 형태의 컴포넌트가 있다고 가정해보자.

A가 가지고 있는 특정 state가 C에서도 필요하다면 A에서 B로, 다시 B에서 C로 전달해줘야 한다.

하지만 중간에 있는 B는 해당 state가 필요하지 않다. (Useless Props) 다만 하위 컴포넌트로 props를 내려줘야 하기때문에 거치는 것이다.

이러한 문제점을 리덕스를 통해 해결해줄 수 있다.

리덕스를 사용하면 state를 전역으로 다룰 수 있다. 쉽게 말해서 State Container라고 생각하면 된다.


(출처 : 노마드아카데미)


Redux는 위와 같은 형태로 생겼다.

쉽게 생각해서 모든 state는 Redux가 가지고 있고 각 컴포넌트에서 필요한 state만

Redux에서 받아와서 사용하는 형태이다.

위와 같은 형태를 쓴다면 이전에 말한 Useless Props 문제를 피할 수 있다.

리덕스를 사용하면서 알아둬야할 점은 다음과 같다.


1. 모든 state는 object로 저장된다.

2. state를 업데이트하려면 action을 보내야한다.

3. action을 reducer로 보내면 reducer가 object를 수정한다.


쉽게 말해서 redux store에 직접적으로 접근하여 state를 수정하는것은 금지된다는 말이다.

state에 관한 모든 작업은 Redux에서 정의해놓은 방법대로 진행해야한다.


이제 실제 Redux를 사용하여 Action, Reducer등을 구현해본다.

대상은 노마드 아카데미의 토마토 타이머이며 해당 앱을 구현하기 위한 state를 기준으로 설명한다.

다음의 3가지를 Redux를 통해 관리하는 형태로 만들것이다.

기본적으로 사용하는 패턴과는 조금 다른 Redux Ducks Pattern으로 구현한다.


isCounting : true/false이며 현재 숫자를 세고있는지 판단한다.

counterDuration: 숫자이며 몇초에서 시작할지 결정한다.

elapsedTime: 숫자이며 몇초가 경과했는지 판단한다.


[reducer.js]

// 1. 필요한 것을 import
// 이 앱은 따로 import 할것이 없다.

// 2. Action을 정의
// 타이머를 시작/정지/재시작하는 액션을 만들어야 한다.
// 이것은 나중에 Switch문에서 사용할 변수들이다.
const START_TIMER = 'START_TIMER';
const RESTART_TIMER = 'RESTART_TIMER';
const ADD_SECOND = 'ADD_SECOND';

// 3. 정의한 Action에 대한 Action Creator를 정의
function startTimer() {
return {
type: START_TIMER
};
}

function restartTimer() {
return {
type: RESTART_TIMER
};
}

function addSecond() {
return {
type: ADD_SECOND
};
}

// 4. Reducer 정의
// Action을 보낼 때 마다 Redux는 자동으로 reducer를 실행한다. (Action을 Reducer로)
// 먼저 initial state를 만든다.
const TIME_DURATION = 1500;
const initialState = {
isPlaying: false,
elapsedTime: 0,
timeDuration: TIME_DURATION
}

// 위에서 생성한 초기 state로 시작하기 위해 아래와 같아 입력
function reducer(state = initialState, action) {
switch(action.type) { // 받은 Action의 type을 기준으로 전부 다른 작업을 한다.
case START_TIMER:
return applyStartTimer(state); // 여기서 state는 현재 state이다.
case RESTART_TIMER:
return applyRestartTimer(state);
case ADD_SECOND:
return applyAddSecond(state);
default:
return state;
}
}

// 5. Reducer Function 정의
function applyStartTimer(state) {
return {
...state,
isPlaying: true
};
}

function applyRestartTimer(state) {
return {
...state,
isPlaying: false,
elapsedTime: 0
}
}

function applyAddSecond(state) {
if(state.elapsedTime < TIME_DURATION) {
return {
...state,
elapsedTime: state.elapsedTime + 1
}
} else {
return {
...state,
isPlaying: false
}
}
}

// 6. export action creator
const actionCreators = {
startTimer,
restartTimer,
addSecond
}
export { actionCreators };

// 7. export reducer
export default reducer;


이제 reducer를 실제 앱에 연결해야한다.


[App.js]

import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Timer from './components/Timer';
import reducer from './reducer';
// reducer.js에서 reducer를 export default로 해놨기 때문에 function으로 선언해놓은 reducer가 자동으로 불러온다.
import { createStore } from 'redux';
import { Provider } from 'react-redux';
// Provider는 컴포넌트안의 store를 복사해서 다시 하위 컴포넌트로 복사하는 것.

let store = createStore(reducer);

export default class App extends React.Component {
render() {
return (
<Provider store={store}>
<Timer />
</Provider>
)
}
}

위처럼 createStore를 통해 store를 생성하고 Provider로 감싸주면 된다.

다음으로 presenter를 만들어줘야 한다.

Timer 컴포넌트에서 presenter 컴포넌트를 불러오는 형식이며 presenter에서는 Redux작업은 하지않고 오로지 데이터만 보여준다.

Redux를 통한 state 관리작업은 Timer에서 진행한다.


[index.js]

import { connect } from 'react-redux';
import Timer from './presenter';

function mapStateToProps(state) { // store에서 state를 복사하여 컨테이너의 props에 붙여넣는 함수
const { isPlaying, timeDuration, elapsedTime } = state;
return {
isPlaying,
timeDuration,
elapsedTime
}
}

export default connect(mapStateToProps)(Timer);


이렇게 해주게 되면 state를 복사해서 내려주는것까지 완료했다.

하지만 여기서 끝나는게 아니라 action도 같이 복사해줘야 한다.


import { bindActionCreators } from 'redux';

Action도 복사해주기 위해서는 bindActionCreators를 사용한다.

(참고로 알아둘것 -> dispatch는 action을 reducer로 보내는 함수이다)


function mapDispatchToProps(dispatch) {
return {
startTimer: bindActionCreators(tomatoActions.startTimer, dispatch),
restartTimer: bindActionCreators(tomatoActions.restartTimer, dispatch)
}
}


마지막으로 connect를 아래와 같이 수정

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