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 ở bài 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:
Code Block |
---|
sudo npm install -g create-react-app
create-react-app test-react-redux
|
Tiếp theo là Redux:
Code Block |
---|
cd test-react-redux
npm install --save redux react-redux
|
react-redux : chính là The view layer binding
trong bài trước, làm nhiệm vụ kết nối cho redux và react.
Let's go¶
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:
...
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
...
language | js |
---|
Tài liệu¶
Cài đặt source code.
Giải thích:
Action:
Code Block |
---|
import * as types from '../constants/ActionTypes'; export function addTodoadd(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 }; } |
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
Code Block | ||
---|---|---|
| ||
import { ADD_TODO, DELETE_TODO, EDIT_TODO, MARK_TODO, MARK_ALL, CLEAR_MARKED |
Mô tả hành động .
Reducer:
Ta sẽ có countReducer, có nhiệm vụ biến đổi state sau khi nhận được action add:
Code Block |
---|
import {ADD} from '../constants/ActionTypes'; // initialState const initialState = [{ text: 'Use Redux', marked: false, idcount': 0 }]; export default function todoscountReducer(state = initialState, action) { switch (action.type) { case ADD_TODO: return [{ id: (state.length === 0) ? 0 : state[0].id + 1,...state, 'count' : state.count + 1 marked: false, } textdefault: action.text }, ...state]return state; } } |
Combine reducer vào root reducer:
Code Block |
---|
import { combineReducers 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);} from 'redux'; import countReducer from './countReducer'; const rootReducer = combineReducers({ countReducer }); export default rootReducer; |
Khởi tạo store:
Code Block |
---|
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import * as serviceWorker from './serviceWorker'; import {Provider} from "react-redux"; import { createStore, applyMiddleware } from 'redux'; import { createLogger } from 'redux-logger'; import CountExample from './components/CountExample'; import rootReducer from './reducers/rootReducer'; // initialState const initialState = {} // Create store const store = createStore( rootReducer, initialState, applyMiddleware( createLogger() ) ); ReactDOM.render( <Provider store={store}> <div> return state.map((todo) => ({...todo, marked: !areAllMarked})); <CountExample /> case CLEAR_MARKED: </div> </Provider>, return statedocument.filter((todo) => todo.marked === falsegetElementById('root') ); // If you want your app to work default:offline and load faster, you can change // unregister() to register() below. Note returnthis state;comes with some } } |
Root-reducer
sẽ tập hợp các sub-reducers
lại thông qua combineReducers()
của Redux.
reducers/rootReducers.js
Code Block | ||
---|---|---|
| ||
import { combineReducers } from 'redux';
import todosReducers from './todosReducers';
const rootReducer = combineReducers({
todosReducers
});
export default rootReducer;
|
Views¶
Smart Component (containers)¶
Containers
là những component giao tiếp với Redux thông qua connect()
của react-redux
.
connect()
nhận vào 4 tham số mapStateToProps mapDispatchToProps mergeProps options
:
mapStateToProps(state, [ownProps])
là function. Nếu được định nghĩa, container sẽ được đăng ký(subscribe)
vớistore
. Mỗi khistore
updatemapStateToProps
sẽ được gọi, object mà nó trả về sẽ được merge vớiprops
củ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 props
thìmapStateToProps
cũng sẽ được gọi. NếumapStateToProps
không được định nghĩa container sẽ không được đăng ký và nhận update từstore
.mapDispatchToProps
làobject
hoặcfunction
. 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ớiprops
của container. Nếu là functionmapDispatchToProps
sẽ 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 props
thìmapDispatchToProps
cũng sẽ được gọi. NếumapDispatchToProps
không được định nghĩa sẽ chỉ códispatch
được merge vào props củ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ủamapStateToProps
mapDispatchToProps
và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.options
là object. Nếu được định nghĩa sẽ điều chỉnh hành vi củaconnector
. Chứa 2 giá trịpure
vàwithRef
. Nếupure = true
thì 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 = true
thì 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
Code Block | ||
---|---|---|
| ||
import React, {pitfalls. // Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister(); |
Trong componet, map state của redux với props, map dispatch của redux với action props:
Code Block |
---|
import React, { Fragment, Component } from 'react'; import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import Header from '../components/Header'; import MainSection from '../components/MainSection'; import * as TodoActionsCountActions from '../actions/TodoActionsCountActions'; class TodoAppCountExample extends Component { render() { const { todosactions, actionscount } = this.props; return ( <div><Fragment> <Header addTodo={actions.addTodo} /><div> <MainSection todos={todos} actions<button onClick={actions.add} />>Increase</button> </div> ); } }<div> function mapStateToProps(state) { return { todos: state.todosReducers { }; } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(TodoActions, dispatch) count }; } 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 action
nhận được từ container sẽ sử dụng như callback
.
Trong app của chúng ta chúng là Header
, MainSection
.....
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
Code Block | ||
---|---|---|
| ||
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> </Fragment> ); } } function mapStateToProps(state) { return { count: state.countReducer.count <div> }; } function mapDispatchToProps(dispatch) { <TodoApp /> return { </div>actions: bindActionCreators(CountActions, dispatch) }; } </Provider> )export default ReactDOM.renderconnect(appRootmapStateToProps, 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.
Tài liệu¶
...
mapDispatchToProps)(CountExample);
|