0%

리액트 정리 - 2

Goal

리액트로 구현한 전화번호부에 리덕스 적용한 내용 정리.

Redux

리덕스는 리액트에서 상태를 더 효율적으로 관리하는데 사용하는 상태관리 라이브러리다.
상태를 컴포넌트에서 관리하게 될 경우 앱 규모가 커지면 props가 많아질수 있고 하위 컴포넌트로 props를 전달만 하는 컴포넌트가 생길수가 있다.
리덕스는 상태 관리 로직을 컴포넌트 밖(스토어)에서 처리하여 위 문제를 해결 할 수 있다.
Redux

Immutable

리액트 컴포넌트는 state 또는 상위 컴포넌트에서 전달받은 props값이 변할 때 리렌더링되는데, 배열이나 객체를 직접 수정하며 내부 값을 수정해도 레퍼런스가 가르키는곳은 같기 때문에 똑같은 값으로 인식한다. 그래서 변경사항이 있을때 보통 새로운 배열이나 객체를 생성하고 변경내용을 저장하는 방식을 사용한다.
Immutable 라이브러리를 사용하면 보다 편리하게 구현 할 수 있다.

1
2
3
4
5
6
7
// 배열의 요소를 삭제할때..

// Immutable 미사용시, 삭제할 요소를 뺀 새로운 배열을 생성해야한다.
[...allList.slice(0, index), ...allList.slice(index + 1, allList.length)]

// Immutable 사용시, delete메소드 지원한다.
list.delete(index);

Ducks

리덕스에서 사용하는 파일은 액션타입, 액션생성함수, 리듀서 이렇게 3종류로 분리관리한다. 이렇게 관리하게되면 액션을 하나 만들때마다 파일 3개를 수정해야한다. Ducks 구조는 한 파일 모듈화하여 액션타입, 액션생성함수, 리듀서 3가지를 모두를 관리하는 구조이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Map } from 'immutable';
import { handleActions, createAction } from 'redux-actions';
import { dummyList } from '../static/DummyList';

// 액션 타입
const SEARCH = 'phonebook/SEARCH';

// 액션 생성 함수
export const search = createAction(SEARCH);

const initialState = Map({
list: dummyList,
filteredList: dummyList,
selected: ''
});

// 리듀서
export default handleActions({
[SEARCH]: (state, action) => {
const list = state.get('list');
return state.set('filteredList', list.filter(phonebook => phonebook.get('name').search(action.payload) !== -1));
}
}, initialState);

redux-actions

redux-actions 패키지에는 리덕스 액션들을 관리할 때 유용한 createAction과 handleActions 함수가 있다.

createAction

리덕스에서 액션을 만들다 보면 모든 액션에서 일일이 액션생성자를 만드는 것이 번거로울 수 있다. 액션생성자는 단순히 액션타입과 필요시 payload를 넣는일만 하기 때문에 그런 것을 조금 편하게 자동화해 만든것이 createAction이다.

1
2
3
4
export const search = createAction(SEARCH); // 검색 액션 생성자
export const select = createAction(SELECT); // 선택 액션 생성자
export const insert = createAction(INSERT); // 추가 액션 생성자
export const remove = createAction(REMOVE); // 삭제 액션 생성자

handleActions

switch문으로 리듀서를 구현할 경우 함수 스코프로 인해 서로다른 case에서 동일한 변수 선언시 오류가 발생할 수 있다. handleActions는 그런 문제를 해결하기 위한 대안으로 첫번째 파라미터는 액션에따라 실행할 함수들을 가진 객체이고, 두번째 파라미터는 기본값을 넣어준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export default handleActions({
// 전화번호부 검색시 실행할 함수
[SEARCH]: (state, action) => {
const list = state.get('list');
return state.set('filteredList', list.filter(phonebook => phonebook.get('name').search(action.payload) !== -1));
},
// 특정 전화번호부 이름 선택하는 함수
[SELECT]: (state, action) => {
return state.set('selected', action.payload);
},
// 신규 전화번호부 추가할 때 실행하는 함수
[INSERT]: (state, action) => {
const list = state.get('list');
return state.set('list', list.push(action.payload));
},
// 전화번호부 삭제시 실행하는 함수
[REMOVE]: (state, action) => {
const list = state.get('list');
const index = list.findIndex(phonebook => phonebook.get('id') === action.payload);
return state.set('list', list.delete(index));
}
}, initialState);

Store 생성 및 연동

스토어는 보통 프로젝트 엔트리포인트인 src/index.js 파일에서 생성한다.
스토어를 리액트 앱에 연동하기 위해서는 연동할 프로젝트의 최상위 컴포넌트를 Provider로 감싸고 props로 store를 넣어주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import modules from './modules';

// 파라미터는 리듀서이다
const store = createStore(modules);

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

Container Component

컨테이너 컴포넌트는 스토어를 연동하는 역할을 한다. react-redux라이브러리의 connect 함수를 사용해 컴포넌트를 스토어에 연동시킨다. 각 파라미터는 선택으로 불필요하면 생략해도 된다.

1
connect([mapStateToProps], [mapDispatchToProps], [mergeProps])

  • mapStateToProps: 스토어의 state를 파라미터로 받아 컴포넌트 props로 사용할 객체를 반환한다.
  • mapDispatchToProps: dispatch를 파라미터로 받아 액션을 디스패치하는 함수들을 객체 안에 넣어 반환한다.
  • mergeProps: state와 dispatch가 동시에 필요한 함수를 props로 전달해야 할 때 사용하는데, 일반적으로 사용하지 않는다.
1
2
3
4
5
6
7
8
9
10
11
export default connect(
// mapStateToProps: 스토어의 state를 가져와 컴포넌트내에서 this.props.value로 접근할 수 있다.
(state) => ({
value: state.input.get('value')
}),
// mapDispatchToProps: bindActionCreators를 통해 액션생성함수들을 dispatch해주어 실제 컴포넌트에서는 this.props.InputActions로 접근할 수 있다.
(dispatch) => ({
InputActions: bindActionCreators(inputActions, dispatch),
PhonebookActions: bindActionCreators(phonebookActions, dispatch)
})
)(SearchBarContainer);

Conclusion

리액트로 구현한 전화번호부에 리덕스를 적용하면서 주요부분들에 대한 내용을 정리해 보았다.
구현에 대한 전체소스는 여기 에서 확인 할 수 있다.

Reference : 리액트를 다루는 기술
이미지출처: https://www.smashingmagazine.com/2016/06/an-introduction-to-redux