Redux개념잡기1 - 스탑워치 만들기

Redux개념잡기1 - 스탑워치 만들기

Redux ?

노마드 코더의 tomato todo app 만들기를 참고하여 정리한 정리노트

혼자 공부하며 이해한 내용을 정리한거라서 부족한 점이 많을 수 있습니다. 가감없는 피드백은 항상 감사합니다!

source code: github
게시중

Redux가 필요한 이유

컴포넌트는 local state와 global state를 갖고 있다.
글의 '좋아요’를 켜고 끄는 것은 local state,
로그인상태와 오프라인 상태가 global state의 대표적인 예.

이처럼 local state는 해당 컴포넌트에서만 알고있으면 되지만, global state는 모든 컴포넌트가 알고 있어야 한다.
즉, global state는 모든 컴포넌트에 공유될 수 있어야 하는데,
이러한 global shared state를 저장하는 것이 바로 Redux의 역할이다.
Redux = State Container

React의 경우 컴포넌트간에 state를 공유하기 위해서는 props로 전달해주어야 한다. 전달할 props가 많을수록, 컴포넌트의 깊이가 깊어질수록 이 과정에서 문제가 발생할 확률도 높고 매우 귀찮은 일…
이러한 불편을 해소해주는 단비가 바로 Redux이다.

하지만 Redux를 사용하기 위해서는 다소(?) 복잡한 작업을 해야하고 개념을 익혀야 하기 때문에,
복잡하지 않은 간단한 구조라면 그냥 props를 사용해 전달하는 것이 더 효율적이다.
그래도 앱이 커질수록 Redux와 같은 state 관리도구는 선택이 아닌 필수!

Redux 예시

굳이 Redux가 필요하지 않은 경우

단순히 블로그에 글을 포스트 하고 보는 경우에는 Redux가 필요하지 않다.

enter image description here
API를 불러와서 렌더링하면 끝!

하지만 Comment에 답글을 달고 싶을 때 문제가 발생한다.
답글을 달기 위해선 글을 쓰는 사람과 받는사람을 구분해야 한다.

enter image description here

enter image description here

이처럼 User를 호출한 최상위 컴포넌트에서 계속해서 하단의 컴포넌트에게 Props로 전달해야 한다. 자체로 매우 귀찮은 일이다… 또 Post자체에서는 User가 필요가 없지만 Comment를 위해서 꼭 Post를 거쳐서 전달받아야 한다. 이처럼 쓰지 않고 오로지 전달을 위해 받는 Props를 flying Props라고 한다.

Redux는 위에서 밑으로 내려주는 전달방식이 아닌 Store라는 저장소에서 정보를 가져오면 된다!

enter image description here

주의사항

Redux의 정보는 object 형태로 저장되는데, state가 굉장히 크고 복잡해질 수 있기 때문에 state를 수정하는데 매우 엄격하다.
object안의 정보를 수정하고 싶다면 dispatch를 이용해 action을 붙여서 사용한다. react에서 state를 바로 수정하지 않고 setState()를 이용해서 수정하는 것과 비슷한 개념이라고 생각하면 될 것 같다.

스탑워치 만들기

Directory

디렉토리 구조
enter image description here

  • actions: 액션타입, 액션생성자
  • components: 뷰만 담당하는 presentational 컴포넌트
  • containers: store에 접근이 닿는 container 컴포넌트
  • reducers: 스토어의 기본 상태와, 상태의 업데이트 담당하는 리듀서
  • store: 주로 미들웨어를 설정한다. (비동기통신, state 로그관리 …)
  • utils: 일부 컴포넌트들에서 공용되는 파일들

actions

무언가 일어난다는 사실을 기술
앱에서 사용하는 명령어(action type)와 API 통신과 같은 작업을 하는 액션 메서드(action creator)를 모아둔 폴더이다. 서비스에 따라 한 곳에 모아두거나 도메인별로 구분해 나눠놓기도 한다.

enter image description here

actionTypes.js
앱에서 사용하는 명렁어 정의

// Clock Page
export const START_TIMER = 'START_TIMER';
export const RESTART_TIMER = 'RESTART_TIMER';
export const ADD_SECOND = 'ADD_SECOND';

timer.js
액션 생성자

// Action Creators
function startTimer(){
  return {
    type: START_TIMER
  }
}
function restartTimer(){
  return {
    type: RESTART_TIMER
  }
}
function addSecond(){
  return {
    type: ADD_SECOND
  }
}

reducers

상태관리! 액션의 결과로 앱의 상태가 어떻게 바뀌는지 정의
모든 상태는 하나의 객체에 저장된다. state를 직접 수정하는 것이 아니라 기존 상태에 원하는 값을 덮어쓴 새로운 객체를 만드는게 핵심!

enter image description here

index.js
reducer들을 묶어서 export,
현재는 리듀서가 하나뿐이라 굳이 필요 없지만, 추후 리듀서가 여러개라면 combineReducers()와 같은 함수를 이용해 reducer를 묶어서 반환

timerReducer.js
초기 상태 정의, 함수정의

const TIME_DURATION = 1800;
const initialState = {
  isPlaying: false,
  elapsedTime: 0,
  timerDuration: TIME_DURATION
}
function reducer(state = initialState, action){
  switch(action.type){
    case START_TIMER:
      return applyStartTimer(state);
    case RESTART_TIMER:
      return applyRestartTimer(state);
    case ADD_SECOND:
      return applyAddSecond(state);
    default:
      return state;
  }
}

// Reducer Functions
function applyStartTimer(state){
  return {
    ...state,
    isPlaying: true,
    elapsedTime: 0
  }
}

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
    }
  }
}

// Export Reducer
export  default  timerReducer

containers (Smart Component)

Redux와 직접 닿아 소통하며 state를 관리, 제어한다. 단순히 Props만 전달받아서 사용하는 Components(Dumb Component)와 구분해서 사용하는 것이 좋은데, containers에서는 앱의 상태가 자주 바뀔수록 빈번하게 업데이트가 일어나기 때문에 불필요한 업데이트를 막기 위해 구분하는 것이 좋다.

개인적으로 많이 실수했던 부분인데, container에서 component를 connect시켜주기 때문에 연결시킨 component는 따로 렌더링하지 않고 container를 렌더링해야 정상적으로 store에 접근할 수 있다. 습관적으로 component를 렌더링하지 말 것!

mapStateToProps (state)
state를 연결할 때,
Provider의 store에서 state를 불러와서 연결시킨다.
액션 생성자를 사용해 액션을 생성하고, 해당 액션을 dispatch하는 함수를 만들어 Props로 전달한다.

mapDispatchtoProps (dispatch)
액션을 연결시킬땐 dispatch,

connect 함수
컨테이너 컴포넌트를 store에 연결
파라미터로 컴포넌트에 연결시킬 상태와 액션함수 전달

TimerContainer.js

import { connect } from  'react-redux';
import { bindActionCreators } from  'redux';
import {actioncreators  as  timerActions} from  '../actions/timer';
import  Timer  from  '../components/Timer';

/*
 ** 상태를 연결시킬 땐 state
 Provider의 store에서 state를 불러옴
*/

const  mapStateToProps  = (state) => ({
 isPlaying:  state.isPlaying,
 elapsedTime:  state.elapsedTime,
 timerDuration:  state.timerDuration,
})

/*
 ** 액션을 연결시킬땐 dispatch
 액션 생성자를 사용해 액션을 생성하고,
 해당 액션을 dispatch 하는 함수를 만들어 이를 props로 전달
*/

const  mapDispatchToProps  = (dispatch) => ({
 startTimer:  bindActionCreators(timerActions.startTimer, dispatch),
 restartTimer:  bindActionCreators(timerActions.restartTimer, dispatch),
 addSecond:  bindActionCreators(timerActions.addSecond, dispatch),
})

// mapStateToProps를 Clock에게
const  TimerContainer  =  connect(
mapStateToProps,
mapDispatchToProps,
)(Timer);

export  default  TimerContainer;

components (Dumb Component)

단순히 Props를 전달받아서 사용하는 컴포넌트들
**

Timer.js


class  Timer  extends  Component {
// 컴포넌트가 새로운 Props를 받을 때마다 실행
componentWillReceiveProps(nextProps) {
const  currentProps  =  this.props;
console.log('current: ', currentProps.elapsedTime);
console.log('next: ', nextProps.elapsedTime);
if (!currentProps.isPlaying  &&  nextProps.isPlaying) {
// 1초마다 addSecond
const  timerInterver  =  setInterval(() => {
currentProps.addSecond();
}, 1000);

this.setState({
interval:  timerInterver
})
} else  if (currentProps.isPlaying  &&  !nextProps.isPlaying) {
clearInterval(this.state.interval)
}
}

render() {
console.log('Timer', this.props);
const {
isPlaying,
elapsedTime,
timerDuration,
startTimer,
restartTimer,
} =  this.props;

return (
<div  className="TimerForm">
<div  className="timer">{formatTime(timerDuration - elapsedTime)}</div>

{!isPlaying && <Button  size="large"  color="secondary"  onClick={startTimer}>Burning!!</Button>}
{isPlaying && <Button  size="large"  color="secondary"  onClick={restartTimer}>STOP</Button>}
</div>
)
}
}

export  default  withStyles(styles)(Timer);


참고자료: 노마드코더 강의
참고자료: Naver Redux 적용기

Written with StackEdit.

댓글

  1. 우와 잘배우고 갑니다!! 이해가 쏙쏙 잘되네요!! ㅎㅎ

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

CGV 상영시간표 알리

[Swift Error] Outlets cannot be connected to repeating content.

tensorflow softmax_cross_entropy_with_logits