(Redux) 리액트와 리덕스의 결합

호두파파·2021년 4월 28일
0

Redux

목록 보기
3/6

컨테이너 컴포넌트는 리액트 컴포넌트에서 발생한 이벤트를 해석해 리듀서로 전달하고, 스토어가 전파하는 상태 변경 이벤트를 받아서 변경한 상태 값을 컴포넌트에 전달하는 역할을 수행한다.
한 마디로, 리덕스 시스템과 리액트 컴포넌트를 결합하는 얇은 레이어다.

import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  // > 닫은 괄호 또는, "무엇에 비해 크다"라는 뜻 

  return {
    onClick: () = {
      dispatch(setVisibilityFilter(ownProps.filter))
    }
  }
}

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

export default FilterLink

리덕스는 connect라는 함수를 제공한다. connect는 이름을 보고 알 수 있듯이, 리액트 컴포넌트와 리덕스 시스템을 결합하는 역할을 수행한다. connect는 첫 번째 호출에서 두개의 함수를 인자로 받는다.

const mapStateToProps = (state, ownProps) = {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

mapStateToProps와 mapDispatchToProps다. 함수 이름이 매우 직관적이라, 보기만해도 어떤 일을 하는 녀석인지 알 수 있을 것이다.

  • mapStateToProps를 이용하면 스토어에서 전달받은 상태 트리를 컴포넌트에 전달하기 전에 원하는 형태로 가공할 수 있다. 통역사 역할을 수행하는 샘인데, 이 함수 덕에 컴포넌트와 리듀서, 어느 쪽의 상태구조가 바귀어도 변경이 미치는 파급을 최소화할 수 있다.
const mapStateToProps = (state, ownProps) = {
  return {
  	active: ownProps.filter === state.visibilityFilter
  }
}

리덕스처럼 이벤트 통지를 직접 사용자가 제어하지 않는 단일 스토어 방식은 컴포넌트가 구독할 스토어의 이벤트를 직접 지정할 수 없다는 단점을 가지고 있다. 이로 인해 컴포넌트는 자신의 활동과 상관없는 이벤트를 구독해야 한다. 결국 하위 컴포넌트에 불필요한 조정 프로세스를 발생시켜 결과로 애플리케이션의 성능을 저하시킬 가능서이 높아진다.

이러한 것을 해결하는 것이 reselector같은 모듈이다. 이전 상태를 캐시하고 있다가 새로운 상태로 들어오면 변경 여부를 확인해 변경이 있을 때만 하위 컴포넌트로 상태를 전파하는 문지기 역할을 수행한다. 리듀서의 상태를 불변(Immutable)하게 관리하면 이 지점에서 성능상 이점을 얻을 수 있다.**

import { createSelector } from 'reselect'

const getVisibilityFilter = (state, props) =>
  state.todoLists[props.listId].visibilityFilter

const getTodos = (state, props) =>
  state.todoLists[props.listId].todos

const makeGetVisibleTodos = () => {
  return createSelector(
    [ getVisibilityFilter, getTodos ],
    (visibilityFilter, todos) => {
      switch (visibilityFilter) {
        case 'SHOW_COMPLETED':
          return todos.filter(todo => todo.completed)
        case 'SHOW_ACTIVE':
          return todos.filter(todo => !todo.completed)
        default:
          return todos
      }
    }
  )
}

const makeMapStateToProps = () => {
  const getVisibleTodos = makeGetVisibleTodos()
  const mapStateToProps = (state, props) => {
    return {
      todos: getVisibleTodos(state, props)
    }
  }
  return mapStateToProps
}

mapStateToProps가 스토어에서 리액트 컴포넌트로 들어가는 통로라면,
mapDispatchToProps는 반대로 리액트 컴포넌트에서 스토어로 들어가는 통로다. mapDispatchToProps는 리액트 컴포넌트에서 발생한 이벤트를 액션과 결합해 스토어로 전달한다. 스토어로 전달한 액션은 리듀서로 넘어가 전역 상태를 변경한다.

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter));
    }
  }
}

리덕스는 mapDispatchToProps가 반환하는 객체를 하위 컴포넌트에 props로 전달한다. 결국 이는 어플리케이션의 요구 사항을 하위 컴포넌트에 콜백으로 전달하는 샘이다. 리엑트와 리덕스를 결합하는 방식을 크게 두가지로 압축할 수 있다.

  • 이벤트(onClick, onChange, onUpdate...) 기반으로 결합
  • 요구 사항을 수행하는 메서드(toggleTodo, updateVideo)를 전달해 결합

첫번째 방식은 react 컴포넌트로 이벤트 콜백 함수를 내려주면, 컴포넌트는 내부에서 발생하는 사건을 이벤트로 컨테이너에게 알려준다. 컨테이너가 발생한 이벤트를 해석해 수행할 동작을 결정하는 방식이다.

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClickDisplay: () => {
      dispatch(setVisibilityFilter(ownProps.filter));
    },
    onClickTodo: (id) => {
      dispatch(toggleTodo(id));
    }
  }
}

두번째 방식은 컨테이너에서 dispatch와 액션을 결합한 행위를 조합해 props로 내려주면 리액트 컴포넌트가 어떤 행위를 어떤 시점에 실행할지 알아서 결정하는 방식이다.

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    setVisibilityFilter: () => {
        dispatch(setVisibilityFilter(ownProps.filter));
    },
    toggleTodo: () => {
        dispatch(toggleTodo(id));
    }
}

어떤 방법을 사용하든 UI에서 발생하는 인터렉션ㅇ르 해석해 어떤 액션과 결합할 것인지를 결정하는 책임은 컨테이너에 맡겨야 한다는 점이다. 그래야 리액트 컴포넌트와 리덕스 시스템의 결합도를 낮출 수 있다.


출처

원문보기

profile
안녕하세요 주니어 프론트엔드 개발자 양윤성입니다.

0개의 댓글