리액트, 리덕스와 리액트-리덕스
November 20, 2016
원문: React, Redux and react-redux
React와 Redux는 훌륭하다. 기본만 익힌다면 쿨(cool)한 웹 앱을 만드는 것이 정말 쉽다. 간단한 튜토리얼을 따라 하면 몇 시간 내에 앱을 만들 수 있게 되는데, 이것이 내가 Angular 1.x를 좋아하는 이유이다. 반면에 리액트(React)는 조금 더 어려운 편이다.
이 포스트에서 나는 필터링 가능한(filterable) 목록(Angular ng-repeat
/ ngFor
와 비슷한)을 아래의 순서로 총 3가지 버전으로 만들 것이다.
- 순수 React
- React + Redux
- React + Redux + react-redux
시작하기
create-react-app 덕분에 이제는 React 앱을 만드는 것이 매우 쉽다. 터미널에서 다음 명령을 실행하여 새로운 React 앱을 만든다.
create-react-app filterable-list
실행이 완료되었다면, npm start를 실행하고 좋아하는 에디터에서 src
폴더를 연다. 나는 개인적으로 vim을 사용하고 있다.
앱의 메인 페이지가 열린다! k
순수 리액트(React)
리액트만 사용한 버전을 만들어 보자. 코드를 작성하기 전에 우리가 만들려는 컴포넌트에 대해 생각해야 한다. 필터링 된 목록에는 2개의 컴포넌트가 필요하다. 입력(input) 필드(필터링을 위한)와 실제 아이템 목록을 포함한 ` 컴포넌트를 작업할 것이다.
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 컴포넌트는 목록을 렌더링한다. filter와 map에 대해 잘 모른다면 awesome JavaScript array methods에 관한 글을 확인해 보는 것이 좋다.
문제 없이 잘 작동한다!
이러한 방식의 접근은 작은 예제에선 작동하지만, 더 복잡한 상황에선 문제가 된다. 다른 곳에 여러개의 목록이 있었다면 어떻게 될까? 이들은
React + Redux
지금까지 리덕스(Redux)에 대해 한 번도 들어 보지 못하진 않았을 것이다. 이것은 자바스크립트 앱을 위한 상태 컨테이너(state container)이다. 아래처럼 패키지를 추가하면 된다.
npm install redux --save
yarn을 이용 할 수도 있다.
yarn add redux
우리는 리덕스의 3가지 컨셉을 이해해야 한다.
- Actions
- Reducers
- 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를 번역한 글입니다. 전문 번역가가 아니라 오역이 있을 수 있으니 지적해주시면 고치도록 하겠습니다. 원문은 아래에서 확인 할 수 있습니다.
혹시, smart, dumb 컴포넌트에 대해서 잘 모르신다면 아랫글도 한번 확인해 보시면 좋습니다.
https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0