개발 블로깅/React 개념

[2019.05.08] react-redux 사용하기 (매우 어려움 주의...)

Hello이뇽 2019. 5. 8. 22:04

리액트 컴포넌트의 Depth가 커질 때마다 컴포넌트들끼리 state를 주고받기가 힘들어진다.

예를 들어, Depth가 5 정도 있는 컴포넌트가 Depth가 2 정도에 있는 컴포넌트의 state를 변경한다고 하면, 중간에 있는 모든 컴포넌트들이 해당 기능을 전달해 주어야 한다.

 

그래서 모든 컴포넌트들이 state를 쉽게 공유할 수 있게 하는 방식redux이다... 

개념이 정말 엄청나게 어렵다... 지금까지 배운 스프린트 중 제일 어려운 것 같다.

 이틀동안 리덕스만 공부했는데, 이제야 감이 조금 잡힐 정도이다.

 

내가 공부해서 느낀대로, 습득한 대로 정리한 리덕스 개념.

 

우선 리덕스는 리액트 내부에 있는 기술이 아니다. 리덕스는 순수 html, javascript 내에서도 사용 가능하다.

그냥 대체적으로 리액트와 잘 어울려서 같이 쓸 뿐이다.

 

 

리덕스는 flux라는 개념에서 나온 기술인데, 무슨 디자인 패턴인가 보다. 인터넷에 찾아보니 'flux와 redux의 차이' 같은 글을 찾아볼 수가 있는데, 읽어보니 flux를 변형시킨 다른 디자인 패턴이 redux라고 나와있다.

 

우선 redux 모듈을 설치하는 법은 아래와 같다. (로컬 설치 법)

npm i -D redux
npm i -D react-redux
  • redux 모듈 : 말 그대로 redux 기능을 제공해주는 모듈
  • react-redux 모듈 : 리액트와 리덕스의 연동 기능을 제공해주는 모듈 (connect, mapStateToProps, mapDispatchToProps 등등)

 

리덕스는 4가지의 행동으로 분리된다. (개념 매우 어려움 주의..)

# Action

말 그대로 행위, 어떤 작업을 할 건지 구분을 해주는 녀석.

예를 들어, Up, Down 버튼 두 개가 있으면, 둘 중 어떤 버튼을 눌렀는지 구분하게 해 주는데, 이게 if문 이런 식으로 자체에서 구분하는 게 아니라, Reducer(실제 상태를 바꾸는 작업을 하는 녀석)한테 어떤 작업을 할 건지 Object 형태로 flag를 던짐. 필요하면 flag 뿐 아니라 필요로 한 데이터도 같이 던진다.

export const ADDTODO = 'ADDTODO';
export const COMPLETED = 'COMPLETED';

export function addTodo(){  // flag만 전달
  return {
    type: ADDTODO
  }
}
export function completed(id){ // flag와 필요로한 데이터를 같이 전달
  return {
    type: COMPLETED, 
    id:id
  }
}

위 코드를 예시로 보자. TodoList에서 새 리스트 등록을 하는 addTodo()와, 리스트 중 하나의 Todo 완료 기능을 하는 completed() 두개가 있다.

addTodo()는 object를 던지는데, 안에 type으로 ADDTODO만 넣어서 보내준다. 반면에, 완료 기능을 하는 completed()는 매개변수로 id값을 받고, object에 flag뿐 아니라, id값도 추가해서 보낸다.

그러면 이 action에 정의된 함수들은 누가 실행시키나요? 그건 컴포넌트 쪽에서 action함수를 호출하여, 반환된 값을 Dispatch라는 함수의 인자로 넣어주는데, 이건 뒤에 따로 설명할 것이다.


# Reducer

action에게 받은 object로, 모든 컴포넌트들이 공유할 수 있는 state의 값을 변경한다. 요놈이 setState를 하는 녀석이라고 생각하면 된다.

 

코드 복잡함 주의...

const init = {
  TodoList:[{id:0, memo:'테스트입니다.', complete:true}],
}

const Reducer = (state = init, action) => {
  switch(action.type){
    case ADDTODO: 
      var newTodo = {id: state.TodoList[state.TodoList.length-1].id + 1, memo: '새로운 메모', complete:false};  
      return Object.assign({}, state, {
        TodoList: [...state.TodoList, newTodo]
      })
    case COMPLETED: 
      return Object.assign({}, state, {
        TodoList: state.TodoList.map(todo=>{
          if(todo.id === action.id) todo = !todo.complete
          return todo;
        })
      })
      
    default: return state;
  }
}

코드가 보기 힘들 수도 있다. 하지만 어쩔 수 없다.

우선 첫째 줄 init가, 모든 컴포넌트들이 공유하는 state에 초기로 적용시킬 모습이다. 그 다음 줄에는 Reducer라는 함수가 있다. Reducer는 두 개의 매개변수를 가진다.

  • state: 모든 컴포넌트들이 접근 가능한 state!
  • action: action에서 return했던 object를 받는 곳. 

Reducer 함수 안에 switch문으로 바로 무슨 액션을 했었는지 구분한다. 그리고 해당 액션에 맞게 state값을 변경한다.


# Store

Store는 상태를 계속 확인하고 View한테 변경된 사항을 알려주는 녀석... 이라고는 하는데, 사실 코드 상에서는 그냥 Reducer를 Store에 적용시켜주는 것만 하면 된다.

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

const store = createStore(reducer);

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

// 최상위 경로의 index.js

위 코드와 같이 적용하면, state가 변경될 때마다, view를 새로 그려준다. 마치 setState()와 같은 기능을 한다.   


# View

모두가 알고있는 그냥 화면에 보여주는 view이다.


# mapStateToProps

하나의 예시 컴포넌트를 보자.

import React, { Component } from 'react'
import { connect } from 'react-redux'
import Todo from './Todo'

export class ListFiled extends Component {
  render() {
    return (
      <div>
        {this.props.todo.map(todo => {if(todo.viewState === true) return <Todo todo={todo} /> })}
      </div>
    )
  }
}

const mapStateToProps = (state) => ({
  todo: state.TodoList 
})

export default connect(mapStateToProps)(ListFiled)

 

하위에 mapStateToProps함수가 있다. 그리고 state를 매개변수로 받는다. 매개변수의 state가 모든 Reducer함수에서 setState 한 값이다. 쉽게 말해서 이 state가 모든 컴포넌트들이 공통으로 쓰는 state이다. mapStateToProps() 함수에서 return 된 값이, 해당 컴포넌트의 props로 들어간다. 이 mapStateToProps함수를 마지막 줄 connect의 첫 번째 인자로 넣어줘야 한다.

 

# mapDispatchToProps

또 다른 예시이다

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

class Todo extends Component {
  render() {
    return (
      <div>
        <div>{this.props.todo.memo}</div>
        <input type='checkbox' defaultChecked={Element.viewState} onChange={()=>{this.props.onCheck(this.props.todo.id)}}/>
      </div>
    )
  }
}
const mapDispatchToProps = (Dispatch) => {
  return {
    onCheck: (id)=> Dispatch(completed(id))
  }
}

export default connect(null, mapDispatchToProps)(Todo);

하위에 mapDispatchToProps() 함수가 있고, 매개변수로 Dispatch라는 녀석이 있다.

우선 이 mapDispatchToProps()함수도 mapStateToProps() 함수처럼 리턴된 값이 props로 넘어간다.

onCheck(내가 지은 키 이름)가 있고, value로는 Dispatch를 호출하는 함수가 있다.

(아까 action부분에서, action함수를 호출하는 부분이 여기이다.)

completed함수는 action함수이다. completed가 반환한 값을 Dispatch의 인자로 넣어준다. 그러면 이 인자가 Reducer 함수의 action 매개변수로 간다.

마지막 줄에, connect의 두 번째 인자로 mapDispatchToProps() 함수를 넣어준다. (connect의 인자 순서 주의!)


여기까지다..... 아.... 

내용이 많지도 않은데 블로깅 시간은 제일 오래 걸린 것 같다...-0-

이 방식이 적응되려면 고생을 좀 해야 될 것 같다... 덤으로 Context API라는 것도 있다. 이 방식을 쓰면 Redux보다 더 편하다고 하는데, 시간 나면 한번 봐바야겠다.

반응형