컨테이너 컴포넌트는 리액트 컴포넌트에서 발생한 이벤트를 해석해 리듀서로 전달하고, 스토어가 전파하는 상태 변경 이벤트를 받아서 변경한 상태 값을 컴포넌트에 전달하는 역할을 수행한다.
한 마디로, 리덕스 시스템과 리액트 컴포넌트를 결합하는 얇은 레이어다.
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다. 함수 이름이 매우 직관적이라, 보기만해도 어떤 일을 하는 녀석인지 알 수 있을 것이다.
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로 전달한다. 결국 이는 어플리케이션의 요구 사항을 하위 컴포넌트에 콜백으로 전달하는 샘이다. 리엑트와 리덕스를 결합하는 방식을 크게 두가지로 압축할 수 있다.
첫번째 방식은 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에서 발생하는 인터렉션ㅇ르 해석해 어떤 액션과 결합할 것인지를 결정하는 책임은 컨테이너에 맡겨야 한다는 점이다. 그래야 리액트 컴포넌트와 리덕스 시스템의 결합도를 낮출 수 있다.