도움말 - 글감 수집하기 (인용)

도움말 - 부분 리뷰 작성하기

리액트, 리덕스와 리액트-리덕스(React, Redux and react-redux)

React와 Redux는 훌륭하다. 기본만 익힌다면 쿨(cool)한 웹 앱을 만드는 것이 정말 쉽다. 간단한 튜토리얼을 따라하면 몇 시간 내에 앱을 만들 수 있게 되는데, 이것이 내가 Angular 1.x를 좋아하는 이유이다. 반면에 리액트(React)는  조금 더 어려운 편이다.

이 포스트에서 나는 필터링 가능한(filterable) 목록(Angular ng-repeat / ngFor와 비슷한)을 아래의 순서로 총 3가지 버전으로 만들 것이다:

  1. 순수 React
  2. React + Redux
  3. React + Redux + react-redux

시작하기

create-react-app 덕분에 이제는 간단한 React 앱을 만드는 것이 매우 쉽다. 터미널에서 다음 명령을 실행하여 새로운 React 앱을 만든다.

create-react-app filterable-list  

실행이 완료 되었다면, npm start를 실행하고 좋아하는 에디터에서 src 폴더를 연다. 나는 개인적으로 vim을 사용하고 있다.


브라우저에서 앱의 메인 페이지가 열린다!

순수 리액트(React)

리액트만 사용한 버전을 만들어 보자. 코드를 작성하기 전에 우리가 만들려는 컴포넌트에 대해 생각해야 한다. 필터링 된 목록에는 2개의 컴포넌트가 필요하다. 입력(input) 필드(필터링을 위한) 와 실제 아이템 목록을 포함한 <FilteredList /> 컴포넌트와 <List items={frameworks} filterBy={what} /> 컴포넌트를 작업 할 것이다.

create-react-app의 기본 화면을 FilterList 컴포넌트로 변경한다.

<div className="App-intro">  
<FilterList />
</div>  

우리의 FilterList는 상태 값(state)을 포함하기 때문에 "스마트(smart) 컴포넌트"가 될 것이다. 상태의 키 값은 filteredBy가 될 것이다. 상태를 설정하는 입력(input box) 필드가 있고 필터링 대상 항목(item)이 있는 List 컴포넌트가 있을 것이다.

class FilterList extends Component {  
constructor() {
super();
// our default state, filter by nothing
this.state = {
filterBy: ''
};
}
// function that triggers on every change in the input box
updateFilter(ev) {
this.setState({ filterBy: ev.target.value });
}
render() {
const { filterBy } = this.state;
const frameworks = ['React', 'Angular', 'Vue', 'Ember'];
// simple input box and our List component
return (
<div>
<input type="text" onChange={(ev) => this.updateFilter(ev) }/>
<List items={frameworks} filterBy={filterBy} />
</div>
)
}
}

좋다, 특별히 어려워 보이진 않을 것이다. 이제 간단한 List 컴포넌트만 있으면 된다. 이것은 단순히 주어진 데이터만 표시(display)하기 때문에 "멍청한(dumb) 컴포넌트"이다. 그렇기 때문에, stateless functional component로 이를 만들 수 있다.

const List = ({ items, filterBy }) => {  
return (
<ul>
{
items
.filter(item => item.indexOf(filterBy) > -1)
.map((item, i) => <li key={i}>{item}</li>)
}
</ul>
)
}

이 List 컴포넌트는 목록을 렌더링한다. filtermap에 대해 잘 모른다면 awesome JavaScript array methods에 관한 글을 확인해 보는 것이 좋다.

문제 없이 잘 작동한다!

이러한 방식의 접근은 작은 예제에선 작동하지만 더 복잡한 상황에선 문제가 된다. 다른 장소에 여러개의 목록이 있었다면 어떻게 되었을까? 이들은 <FilteredList />의 state에 접근 할 수 없다. 컴포넌트 자체에 여러 다른 상태를 유지하는 것은 좋은 생각이 아니다. 직접적인 자식(direct children) 컴포넌트가 아닌 경우 다른 컴포넌트들이 동일한 state를 사용하는 것은 매우 혼란스럽고 어려울 것이다.

React + Redux

지금까지 리덕스(Redux)에 대해 한번도 들어 보지 못하진 않았을 것이다. 이것은 자바스크립트 앱을위한 상태 컨테이너(state container)이다. 다음을 사용하여 설치하자:

npm install redux --save  

yarn을 이용 할 수도 있다:

yarn add redux  

우리는 리덕스의 3가지 컨셉을 이해해야 한다.

  1. Actions
  2. Reducers
  3. Store

Actions

우리 앱의 기능은 매우 단순하기 때문에 한 개의 action만 있으면 된다. action은 일반 자바스크립트 객체이며, Action Creator로 생성 한다. 이것은 단순히 자바스크립트 객체를 반환하는 함수이다.

function setFilter(by) {  
return { type: 'SET_FILTER', by };
}

create-react-app를 이용하여 ES6를 사용할 수 있다는 점을 유의한다. 그래서 { by }는 실제로 { by: by }와 같다. 

이제 우리는 Action Creator를 만들었으니, reducer를 만들 차례이다.

Reducer

reducer는 현재 상태를 가지고 action에 따라 새로운 state를 반환한다. state는 절대로 undefined일 수 없기 때문에 항상 기본 state를 반환하거나 일종의 state를 리턴해야 한다 (undefined는 절대로 안된다).

우리의 단순한 reducer는 이렇다:

const initialState = {  
filterBy: ''
}
function reducer(state = initialState, action) {
switch (action.type) {
case 'SET_FILTER':
return Object.assign({}, state, {
filterBy: action.by
})
default:
return state
}
}

우리의 초기 state는 FilterList 컴포넌트에 있었던 this.state = { ... } 것과 동일하다. 지금부터는 store에서 컴포넌트 대신에 이를 추적 할 것이다.

return Object.assign ({} ...)을 구문을 보자. 이는 리덕스가 매번 새로운 상태를 반환해야 하기 때문이다. 이전과 동일한 state가 아니다. 이것이 리덕스의 핵심 개념이다. 

Store

store를 만드는 것은 이 중에서 가장 쉽다. 우선 리덕스 패키지에서 createStore를 가져(import)온다:

import { createStore } from 'redux'  

이제 store를 만드는 것은 놀랍도록 쉽다:

const store = createStore(reducer);  

좋다, Action Creator, Reducer와 Store가 같은 파일에 있는 것이 지금은 더 쉽기 때문에 이렇게 작성했지만, 일반적으로는 import와 export를 사용하여 더 깔끔하게 정리 할 수 있다.

먼저 FilterList 컴포넌트에서 정적 상태(static state)를 제거하고 store에서 가져 오려고 한다.

우리는 또한 상태를 다시 업데이트 하고, 컴포넌트를 리렌더링 하기 위해 store를 구독(subscribe)하는 것이 필요하다 (업데이트 된 filteredBy로).

FilterList의 constructor()는 다음과 같다:

constructor() {  
super();
// default state
this.state = store.getState();
// function that will execute every time the state changes
this.unsubscribe = store.subscribe(() => {
this.setState(store.getState());
});
}

여기에 this.unsubscribe가 무엇인지 궁금 할 것이다. 지금은 아무 기능을 하지 않지만, 컴포넌트가 언마운트(unmounted) 될 때, store에서 리스너(listening)를 해제(stop)하고, 메모리 누수(memory leak)을 막기 위해 이 함수 호출이 필요하다.

이 것 역시 추가 한다: 

componentWillUnmount() {  
this.unsubscribe();
}

이렇게 되면 컴포넌트가 언마운트(unmounted)될 때 (예를들어, 앱에서 다른 페이지로 이동하는 경우), store는 이 컴포넌트를 더이상 신경쓰지 않는다. 

마지막으로 수정해야 하는 것은 updateFilter() 함수다. 이것이 하던 것은 this.setState({ filterBy: ev.target.value }); 였다. 

하지만 아시다시피, 우리는 store에 상태를 유지하므로 store를 업데이트해야한다... action으로!

자, 수정해보자:

updateFilter(ev) {  
store.dispatch(setFilter(ev.target.value));
}

와우! 작동한다!

React, Redux와 react-redux

이 시점에서 왜 나는 react-redux를 사용하고 싶었을까? 이게 없다해도 잘 작동하는데 말이다. 사실 이유는 간단하다. react-redux를 사용하면 boilerplate의 일부를 제거하고 높은 수준의 최적화를 수행 할 수 있다. 

react-redux를 사용하면 store를 리스닝 하고 있는 컴포넌트에 대해 componentWillUnmount를 정의하지 않아도 된다. 게다가, 더이상 this.state = {...}를 constructor에 정의 할 필요가 없어진다.

npm / yarn을 사용하여 인스톨 해보자:

yarn add react-redux  

우리는 이 간단한 기능을 위해 react-redux에서 단지 두가지 기능만 필요하다. connect 함수와 Provider 컴포넌트.

import { Provider, connect } from 'react-redux';  

Provider 컴포넌트는 connect() 함수를 사용하여 "연결(connect)"할 수 있도록 앱의 store를 "제공(provide)"한다.

Provider를 추가하려면 다음과 같이 Provider 컴포넌트를 현재 앱에 래핑하면 된다:

<Provider store={store}>  
<div className="App">
...
</div>
</Provider>  

마지막으로 컴포넌트를 store에 연결해야 한다. 우리는 react-redux에서 가져온 connect() 함수를 사용하여 이것을 수행한다. 이것은 2개의 인자를 받아서 컴포넌트를 통과(pass)시키는 함수를 반환한다.

첫 번째 인자는 mapStateToProps이다. 여기서 우리는 컴포넌트의 props에 매핑 할 상태를 정의한다:

const mapStateToProps = (state) => {  
return {
filterBy: state.filterBy
}
}

이것이 실제로하는 일은 전과같이 this.state가 아닌 filterBy를 컴포넌트의 props에 추가하는 것이다.

두 번째 인자는 mapDispatchToProps인데, 이름에서 알 수 있듯이 dispatcher를 컴포넌트의 props에 매핑 한다. 

const mapDispatchToProps = (dispatch) => {  
return {
updateFilter: (ev) => dispatch(setFilter(ev.target.value))
}
}

이를 연결해보자:

class FilterList extends Component {  
render() {
// this comes from the props now rather than the state
const { filterBy, updateFilter } = this.props;
return (
<div>
<input type="text" onChange={updateFilter}/>
<List items={ITEMS} filterBy={filterBy} />
</div>
)
}
}
// very important!
FilterList = connect(mapStateToProps, mapDispatchToProps)(FilterList);

state 설정을 위해 있었던 constructor()를 완전히 제거한 것을 보자. 그것이 mapStateToProps가 한 것이다. 우리가 어떻게 updateFilter() {...}를 제거했는지 주목하자. 그것이 mapDispatchToProps가 한 것이다.

이제 끝났다.

이 포스트를 통해 Todo 앱 예제를 사용하지 않고 보다 명확하게 React + Redux와 react-redux를 적용 할 수 있기를 바란다. 완성된 App.js는 Github gist에서 확인 할 수 있다.



이 글은 Jiles React, Redux and react-redux를 번역한 글입니다. 전문 번역가가 아니라 오역이 있을 수 있으니 지적해주시면 고치도록 하겠습니다. 원문은 아래에서 확인 할 수 있습니다.

http://jilles.me/react-redux-and-react-redux/

혹시, smart, dumb 컴포넌트에 대해서 잘 모르신다면 아래 글도 한번 확인 해보시면 좋습니다.





리뷰