Redux cho người mới bắt đầu - Part 2 First Project
Bài đăng này đã không được cập nhật trong 4 năm

Mở đầu
Tiếp nối bài viết về Redux cho người mới bắt đầu, trong bài viết này chúng ta sẽ cùng nhau thực hiện một Todo app để tìm hiểu cách sử dụng redux trong một project thực tế.
Trước khi bắt đầu cùng nhìn lại 1 lần các nhân vật ở kì trước :

Setup
Trong bài viết này mình sẽ sử dụng React + Redux. Chúng ta có thể tạo nhanh một react app thông qua create-react-app:
sudo npm install -g create-react-app
create-react-app test-react-redux
Tiếp theo là Redux:
cd test-react-redux
npm install --save redux react-redux
- react-redux : chính là
The view layer bindingtrong kì trước, làm nhiệm vụ kết nối cho redux và react.
Let's go
1.Cấu trúc thư mục
Ở phần trước chúng ta đã biết một app sử dụng Redux có 4 thành phần cơ bản action reducer store và view. Trong đó store chúng ta chỉ việc khởi tạo trong root component còn việc quản lý Redux sẽ lo. view thì bao gồm smart components (containers) những components giao tiếp với Redux và dumb components (components) những components không giao tiếp với Redux. Các action type của dụng thường là các hằng số được định nghĩa trước.
Do đó để tiện việc quản lý chúng ta có thể tạo ra các thư mục actions constants reducers containers components, app của chúng ta sẽ có cấu trúc như sau:

2.Actions
Đầu tiên chúng ta sẽ tạo ra các actions, các bạn còn nhớ The Action creators không, anh ta tạo ra những action là formated object chứa type và thông tin của action đó. Type thường sẽ là một hằng số được định nghĩa trước.
Ở đây chúng ta có TodoActions định nghĩa ra các thao tác thêm sửa xóa... công việc.
Các action mang type và các thông tin id, text.
// actions/TodoActions.js
import * as types from '../constants/ActionTypes';
export function addTodo(text) {
return {
type: types.ADD_TODO,
text
};
}
export function deleteTodo(id) {
return {
type: types.DELETE_TODO,
id
};
}
export function editTodo(id, text) {
return {
type: types.EDIT_TODO,
id,
text
};
}
export function markTodo(id) {
return {
type: types.MARK_TODO,
id
};
}
export function markAll() {
return {
type: types.MARK_ALL
};
}
export function clearMarked() {
return {
type: types.CLEAR_MARKED
};
}
3. Reducers
Tiếp theo là các reducers, chúng ta tạo ra các sub-reducers và một root-reducer quản lý chung. Reducers là các pure function hoạt động theo nguyên lý :
(
state,action) => (new state)
Vì là pure function nên các reducers sẽ không trực tiếp thay đổi state mà nó nhận được, mà tạo ra các bản copy và thay đổi trên đó. Để thực hiện điều này chúng ta có thể dùng các function filter() map() Object.assign()...
// reducers/todosReducers.js
import { ADD_TODO, DELETE_TODO, EDIT_TODO, MARK_TODO, MARK_ALL, CLEAR_MARKED } from '../constants/ActionTypes';
const initialState = [{
text: 'Use Redux',
marked: false,
id: 0
}];
export default function todos(state = initialState, action) {
switch (action.type) {
case ADD_TODO:
return [{
id: (state.length === 0) ? 0 : state[0].id + 1,
marked: false,
text: action.text
}, ...state];
case DELETE_TODO:
return state.filter((todo) => todo.id !== action.id);
case EDIT_TODO:
return state.map((todo) => todo.id === action.id ? { ...todo, text: action.text } : todo);
case MARK_TODO:
return state.map((todo) => todo.id === action.id ? { ...todo, marked: !todo.marked } : todo);
case MARK_ALL:
const areAllMarked = state.every((todo) => todo.marked);
return state.map((todo) => ({...todo, marked: !areAllMarked}));
case CLEAR_MARKED:
return state.filter((todo) => todo.marked === false);
default:
return state;
}
}
Root-reducer sẽ tập hợp các sub-reducers lại thông qua combineReducers() của Redux.
// reducers/rootReducers.js
import { combineReducers } from 'redux';
import todosReducers from './todosReducers';
const rootReducer = combineReducers({
todosReducers
});
export default rootReducer;
4.Views
-
Smart Component (containers)
Containerslà những component giao tiếp với Redux thông quaconnect()củareact-redux.connect()nhận vào 4 tham sốmapStateToPropsmapDispatchToPropsmergePropsoptions:mapStateToProps(state, [ownProps])là function. Nếu được định nghĩa, container sẽ được đăng ký (subscribe) vớistore. Mỗi khistoreupdatemapStateToPropssẽ được gọi, object mà nó trả về sẽ được merge vớipropscủa container. NếuownPropsđược định nghĩa, giá trị của nó sẽ làpropsđược gửi cho container, đồng thời mỗi khi container nhận đượcnew propsthìmapStateToPropscũng sẽ được gọi. NếumapStateToPropskhông được định nghĩa container sẽ không được đăng ký và nhận update từstore.mapDispatchToPropslà object hoặc function. Nếu là object mỗi function bên trong object sẽ được coi là mộtaction creator, đồng thời tất cả function này sẽ được tự động chay bởibindActionCreators()và merge chúng vớipropscủa container. Nếu là functionmapDispatchToPropssẽ nhận 2 tham số(dispatch, [ownProps]), chúng ta sẽ tự định nghĩa cách bind action vớidispatch, chúng ta cũng có thể sử dụngbindActionCreators({action}, dispatch)để tự động bind. NếuownPropsđược định nghĩa, giá trị của nó sẽ làpropsđược gửi cho container, đồng thời mỗi khi container nhận đượcnew propsthìmapDispatchToPropscũng sẽ được gọi. NếumapDispatchToPropskhông được định nghĩa sẽ chỉ códispatchđược merge vàopropscủa container.mergeProps(stateProps, dispatchProps, ownProps)là function. Nếu được định nghĩa, nó sẽ nhận vào tham số là kết qủa củamapStateToPropsmapDispatchToPropsvàparent props. Object mà nó trả về làpropsđược gửi cho container. Nếu không được định nghĩaObject.assign({}, ownProps, stateProps, dispatchProps)sẽ được sử dụng mặc định.optionslà object. Nếu được định nghĩa sẽ điều chỉnh hành vi củaconnector. Chứa 2 giá trịpurevàwithRef. Nếupure = truethì thực thishouldComponentUpdate()và so sánh kết qủa củamergePropsđể tránh những update không cần thiết, mặc định làtrue. NếuwithRef = truethì lưu trữ lạirefđến container instance và có thể truy cập thông quagetWrappedInstance(), mặc địnhfalse.
Trong app của chúng ta có 1 container là
TodoApp:
// containers/TodoApp.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import Header from '../components/Header';
import MainSection from '../components/MainSection';
import * as TodoActions from '../actions/TodoActions';
class TodoApp extends Component {
render() {
const { todos, actions } = this.props;
return (
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
);
}
}
function mapStateToProps(state) {
return {
todos: state.todosReducers
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(TodoActions, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(TodoApp);
-
Dump Components
Là những component thông thường, chúng không giao tiếp với Redux, chỉ nhận giá trị và thao tác thông qua
props.Các xử lý hiển thị dữ liệu sẽ thực thi ở đây và các
actionnhận được từ container sẽ sử dụng như callback.Trong app của chúng ta chúng là
Header,MainSection.....
5.Root component
Trong mọi React-app đều có root component, ở app sử dụng Redux root component đảm nhận thêm việc khởi tạo store và bao các component lại với Provider của react-redux giúp component có thể giao tiếp với redux.
// index.js
import 'todomvc-app-css/index.css';
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux'
import TodoApp from './containers/TodoApp';
import rootReducer from './reducers/rootReducer';
// initialState
const initialState = {}
// Create store
const store = createStore(rootReducer, initialState);
const appRoot = (
<Provider store={store}>
<div>
<TodoApp />
</div>
</Provider>
)
ReactDOM.render(appRoot, document.getElementById('root'))
Kết
Vậy là chúng ta đã tạo được một ứng dụng đơn giản sử dụng React-Redux. Kết hợp với Data Flow trong phần 1 hi vọng các bạn phần nào hình dung được cách mà Redux hoạt động và cách sử dụng Redux trong một ứng dụng.
Redux còn rất nhiều khái niệm khác như Async Middleware... các bạn có thể tìm hiểu thêm ở trang chủ của Redux hoặc hẹn gặp lại các bạn trong một bài viết khác (có thể (yaoming))
Tài liệu
All rights reserved