Immutable : https://immutable-js.github.io/immutable-js/
- 자바 스크립트에서 불변성 데이터를 다룰 수 있도록 도와주는 라이브러리
Ducks : 액션타입, 액션생성함수, 리듀서를 모두 한 파일에서 모듈화하여 관리하기 위한 파일 구조
https://github.com/erikras/ducks-modular-redux
- Ducks 구조 규칙
1) export default 를 이용하여 리듀서를 내보내야 한다.
2) export를 이용하여 액션 생성 함수를 내보내야 한다.
3) 액션 타입 이름은 npm-module-or-app/reducer/ACTION_TYPE 형식으로 만들어야 한다.
4) 외부 리듀서에서 모듈의 액션 타입이 필요할 때는 액션 타입을 내보내도 된다.
이번에는 저번에 만든 일정관리에 리덕스를 적용해보겠습니다.
create-react-app reduxexample2
...
yarn add redux react-redux redux-actions immutable
yarn add node-sass
yarn eject
// y선택
config/webpack.config.js
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
},
'sass-loader'
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
부분을 아래와같이 수정
{
test: sassRegex,
exclude: sassModuleRegex,
use: getStyleLoaders(
{
importLoaders: 2,
sourceMap: isEnvProduction && shouldUseSourceMap,
}).concat({
loader: require.resolve('sass-loader'),
options: {
includePaths: [paths.appSrc + '/styles'],
sourceMap: isEnvProduction && shouldUseSourceMap
}
}
),
// Don't consider CSS imports dead code even if the
// containing package claims to have no side effects.
// Remove this when webpack adds a warning or an error for this.
// See https://github.com/webpack/webpack/issues/6571
sideEffects: true,
},
yarn add open-color
src/modules/input.js
import { Map } from 'immutable';
import { handleActions, createAction } from 'redux-actions';
// 액션이름 설정
const SET_INPUT = 'input/SET_INPUT';
// 액션 생성 함수 생성
export const setInput = createAction(SET_INPUT);
// 리듀서 초기값
const initialState = Map({
value: ''
});
// 리듀서 생성
export default handleActions({
[SET_INPUT]: (state, action) => {
return state.set('value', action.payload)
}
}, initialState);
src/modules/todos.js
/* 구현할 액션
INSERT : 추가
TOGGLE : 토글
REMOVE : 삭제
*/
import { Map, List} from 'immutable';
import { handleActions, createAction} from 'redux-actions';
const INSERT = 'todos/INSERT';
const TOGGLE = 'todos/TOGGLE';
const REMOVE = 'todos/REMOVE';
export const insert = createAction(INSERT);
export const toggle = createAction(TOGGLE);
export const remove = createAction(REMOVE);
// 리듀서의 초기값
const initialState = List([
Map({
id: 0,
text: '리액트 공부하기',
done: true
}),
Map({
id: 1,
text: '컴포넌트 스타일링 해보기',
done: false
})
]);
// 리듀서 생성 (두번째 매개값은 초기값)
export default handleActions({
[INSERT]: (state, action) => {
/* payload 안에 있는 id, text, done에 대한 레퍼런스를 만들어줍니다.
레퍼런스를 만들지 않고, 바로 push(Map(action.payload))를 해도 되지만,
나중에 이 코드를 보게 됐을 때,
이 액션이 어떤 데이터를 처리하는지 쉽게 보기 위해서 하는 작업입니다. */
const { id, text, done } = action.payload;
return state.push(Map({
id,
text,
done
}));
},
[TOGGLE]: (state, action) => {
const { payload: id } = action;
// = const id = action.payload;
/* 비구조화 할당을 통하여 id라는 레퍼런스에 action.payload란 값을 넣습니다.
이 작업이 필수는 아니지만, 나중에 이 코드를 보게 되었을 때 여기서의 payload가
어떤 값을 의미하는지 이해하기 쉬워집니다. */
// 전달받은 id 를 가지고 index 를 조회합니다.
const index = state.findIndex(todo => todo.get('id') === id);
// updateIn을 통해 현재 값을 참조하여 반대값으로 설정합니다.
return state.updateIn([index, 'done'], done => !done);
/* updateIn을 사용하지 않는다면 다음과 같이 작성할 수도 있습니다.
return state.setIn([index, 'done'], !state.getIn([0, index]));
어떤 코드가 더 편해 보이나요? 둘 중에 여러분이 맘에 드는 코드로 작성하면 됩니다.
*/
},
[REMOVE]: (state, action) => {
const { payload: id } = action;
const index = state.findIndex(todo => todo.get('id') === id);
return state.delete(index);
}
}, initialState);
두 모듈을 합치는 src/modules/index.js
// 리듀서 합치기
import input from './input';
import todos from './todos';
import { combineReducers } from 'redux';
export default combineReducers({
input,
todos
});
src/containers/TodoInputContainer.js
import React, { Component } from 'react';
import TodoInput from '../components/TodoInput';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
// 액션 생성 함수들을 한꺼번에 불러옵니다.
import * as inputActions from '../modules/input';
import * as todosActions from '../modules/todos';
class TodoInputContainer extends Component {
id = 1
getId = () => {
return ++this.id;
}
handleChange = (e) => {
const { value } = e.target;
const { InputActions } = this.props;
InputActions.setInput(value);
}
handleInsert = () => {
const { InputActions, TodosActions, value } = this.props;
const todo = {
id: this.getId(),
text: value,
done: false
};
TodosActions.insert(todo);
InputActions.setInput('');
}
render() {
const { value } = this.props;
const { handleChange, handleInsert } = this;
return (
<TodoInput
onChange={handleChange}
onInsert={handleInsert}
value={value}
/>
);
}
}
/* 이번에는 mapStateToProps와 mapDispatchToProps 함수에 대한 레퍼런스를
따로 만들지 않고, 그 내부에 바로 정의해주었습니다.*/
export default connect(
(state) => ({
value: state.input.get('value')
}),
(dispatch) => ({
/* bindActionCreators를 사용하면 다음 작업들을 자동으로 해줍니다:
{
actionCreator: (...params) => dispatch(actionCreator(...params))
}
그래서 이전에 우리가 했었던 것처럼 하나하나 dispatch할 필요가 없습니다.
예를 들면 InputActions의 경우 다음과 같은 작업이 되어 있는 것이죠.
InputActions: {
setInput: (value) => dispatch(inputActions.setInput(value))
}
나중에 이를 호출할 때는 this.props.InputActions.setInput을 호출하면 됩니다.
*/
InputActions: bindActionCreators(inputActions, dispatch),
TodosActions: bindActionCreators(todosActions, dispatch)
})
)(TodoInputContainer);
src/containers/TodoListContainers.js
import React, { Component } from 'react';
import TodoList from '../components/TodoList';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as todosActions from '../modules/todos';
class TodoListContainer extends Component {
handleToggle = (id) => {
const { TodosActions } = this.props;
TodosActions.toggle(id);
}
handleRemove = (id) => {
const { TodosActions } = this.props;
TodosActions.remove(id);
}
render() {
const { todos } = this.props;
const { handleToggle, handleRemove } = this;
return (
<TodoList
todos={todos}
onToggle={handleToggle}
onRemove={handleRemove}
/>
);
}
}
export default connect(
(state) => ({
todos: state.todos
}),
(dispatch) => ({
TodosActions: bindActionCreators(todosActions, dispatch)
})
)(TodoListContainer)
src/components/TodoItem/TodoItem.js
import React, { Component } from 'react';
import styles from './TodoItem.scss';
import classNames from 'classnames/bind';
const cx = classNames.bind(styles);
class TodoItem extends Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.done !== nextProps.done;
}
render() {
const {done, children, onToggle, onRemove} = this.props;
/* 위 코드에선 비구조화 할당을 통하여 this.props 안에 있는
done, children, onToggle, onRemove 에 대한 레퍼런스를 만들어주었습니다. */
return (
<div className={cx('todo-item')} onClick={onToggle}>
<input className={cx('tick')} type="checkbox" checked={done} readOnly/>
<div className={cx('text', { done })}>{children}</div>
<div className={cx('delete')} onClick={(e) => {
onRemove();
e.stopPropagation();
}
}>[지우기]</div>
</div>
);
}
}
export default TodoItem;
src/components/TodoList/TodoList.js
import React, { Component } from 'react';
import TodoItem from '../TodoItem';
class TodoList extends Component {
shouldComponentUpdate(nextProps, nextState) {
return this.props.todos !== nextProps.todos;
}
// todo가 List이므로 get방식으로 변경
render() {
const { todos, onToggle, onRemove } = this.props;
const todoList = todos.map(
todo => (
<TodoItem
key={todo.get('id')}
done={todo.get('done')}
onToggle={() => onToggle(todo.get('id'))}
onRemove={() => onRemove(todo.get('id'))}>
{todo.get('text')}
</TodoItem>
)
);
return (
<div>
{todoList}
</div>
);
}
}
export default TodoList;
src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './styles/main.scss';
import App from './components/App';
import modules from './modules';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const store = createStore(modules, window.devToolsExtension && window.devToolsExtension());
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root')
);
결과)
shouldComponentUpdate를 이용하여 Virtual DOM 불필요한 리렌더링을 방지하고
리덕스로 인해 복잡한 상황에서도 액션, 디스패치, 구독으로 인해 상태 관리가 잘되는것을 볼수있습니다.
'WEB > REACT' 카테고리의 다른 글
11. 웹 요청 (0) | 2019.07.12 |
---|---|
10. 미들웨어 (0) | 2019.07.11 |
8. 리덕스 활용 예제 (0) | 2019.07.09 |
7. 리덕스 (상태 관리 라이브러리) (0) | 2019.07.08 |
6. 예제 - 일정관리 (0) | 2019.06.25 |