Khi sử dụng dvaJS để làm việc với các ứng dụng reactJS thì mình thấy có một điểm khá hay đó là ở từng model mình không cần phải tạo các thuộc tính để lưu trữ trạng thái tải (loading, adding, fetching…), mà dvaJS cung cấp cho mình 1 plugin là dva-loading, nếu chưa biết gì về dvaJS các bạn coi thêm hoặc official document. Xem đoạn mã bên dưới mình có một counter model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
const initialState = { counter: 0 }; export default { namespace: 'counter', state: initialState, effects: [ *addNumber(actions, { put }){ yeild put({ type: 'saveCounter', payload: 1 }); } ], reducers: [ saveCounter(state, action) { return { ...state, counter: state.counter + action.payload || 1 }; } ] } |
Và ở trong component mình có thể control được việc tải của từng async action là vô cùng đơn giản, với mã
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import React from 'react'; import { Button } from 'antd'; import { connect } from 'dva'; function App({ addingNumber, counter, dispatch }) { return (<> <h1>Counter</h1> <p>Current number: {counter}</p> <Button loading={addingNumber} onClick={() => dispatch({ type: 'counter/addNumber' })}>Add number</Button> </>); } export default connect(({ counter, loading }) => ({ counter: counter.counter, addingNumber: loading.effects['counter/addNumber'] }))(App); |
Rất tiện lợi 🙂
Nhưng khi làm việc với create-react-app thì hiện tại mình thấy đang làm việc khai báo lặp đi lặp lại việc này quá nhiều. Lấy ý tưởng từ việc áp dụng dva-loading, react-wait, redux-thunk, mình có xây dựng một thư viện redux-waiters. Redux waiter là một thư viện cung cấp cho mình một middleware và một số hàm chức năng để khi làm việc các bạn hoàn toàn có thể bỏ qua việc khai báo không cần thiết về trạng thái tải trong model.
Trong thư viện mình có xử dụng thêm 2 thư viện kèm theo là redux-act để quản lý state và action hiệu quả hơn, bộ chọn reselect để tăng performace cho hàm tiện ích mình cung cấp (isWaiting, anyWaiting).
Gới thiệu và cách sử dụng redux-waiters
Redux Waiters
Waiter middleware for Redux.
1 |
npm install redux-waiters |
Or
1 |
yarn add redux-waiters |
Motivation
Redux Waiter middleware allows you to control all loading state of action creator when you call it.
What’s a waiters?!
Inspired from react-wait, redux-thunk. Thanks for Fatih Kadir Akın
Installation
1 |
npm install redux-waiters |
1 |
yarn add redux-waiters |
Then, to enable Redux Waiters, use applyMiddleware()
:
Using
In store
1 2 3 4 5 6 |
import { createStore, applyMiddleware } from 'redux'; import waiter from 'redux-waiters'; import rootReducer from './reducers/index'; // Note: this API requires redux@>=3.1.0 const store = createStore(rootReducer, applyMiddleware(waiter)); |
If you use it with redux-thunk
1 2 3 4 5 6 7 |
import { createStore, applyMiddleware } from 'redux'; import waiter from 'redux-waiters'; import thunk from 'redux-thunk'; import rootReducer from './reducers/index'; // Note: this API requires redux@>=3.1.0 const store = createStore(rootReducer, applyMiddleware([waiter, thunk])); |
In rootReducer file
1 2 3 4 5 6 7 |
import { combineReducers } from 'redux'; import { waiterReducer } from 'redux-waiters'; export defaut combineReducers({ ... waiter: waiterReducer }); |
In example counterReducer
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
import { createActionResources, createReducer } from 'redux-waiters'; import { delay } from '../helpers/utils'; const initialState = { counter: 0, error: false, errorMsg: '', }; export const addNumberAction = createActionResources('add number'); export const minusNumberAction = createActionResources('minus number'); export default createReducer( { [addNumberAction.success]: (state) => { return { ...state, counter: state.counter + 1, error: false, }; }, [addNumberAction.error]: (state) => { return { ...state, error: true, }; }, [minusNumberAction.start]: (state) => { return { ...state, errorMsg: '', error: false, }; }, [minusNumberAction.success]: (state) => { return { ...state, counter: state.counter - 1, }; }, [minusNumberAction.error]: (state, errorMsg) => { return { ...state, error: true, errorMsg, }; }, }, initialState, ); export const addNumberCreator = () => addNumberAction.waiterAction(async (dispatch) => { try { dispatch(addNumberAction.start()); await delay(3000); dispatch(addNumberAction.success()); } catch (err) { dispatch(addNumberAction.error()); } }); export const minusNumberCreator = () => minusNumberAction.waiterAction(async (dispatch) => { try { dispatch(minusNumberAction.start()); await delay(3000); throw new Error('error occur when minus number'); } catch (err) { dispatch(minusNumberAction.error(err.message)); } }); |
Example code in your component file
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import { isWaiting, anyWaiting } from 'redux-waiters'; impport { Button } from 'antd'; import { addNumberCreator, minusNumerCreator, addNumberAction, minusNumberAction } from 'reducer/counterReducer'; function App({ adding, minusing, anyLoading }) { return ( <> App Component Add number Minus number {anyLoading ? 'Loading...' : ''} ); } const isAddingNumerSelector = isWaiting(addNumberAction.id); const isMinusingNumerSelector = isWaiting(minusNumberAction.id); const mapStateToProps = (state) => { const { waiter } = state; return { adding: isAddingNumerSelector(waiter), minusing: isMinusingNumerSelector(waiter), anyLoading: anyWaiting(waiter) }; }; const mapDispatchToProps = (dispatch) => { return { addNumber: () => dispatch(addNumberCreator()), minusNumber: () => dispatch(minusNumberCreator()) }; }; export default connect(mapStateToProps, mapDispatchToProps)(App); |
Injecting a Custom Argument
It’s the same as redux-thunk, redux-waiters supports injecting a custom argument using the withExtraArgument
function:
1 2 3 4 5 6 7 8 9 10 |
const store = createStore( reducer, applyMiddleware(waiter.withExtraArgument(api)), ); // later const fetchUser = (id) => fetchUserAction.waiterAction(async (dispatch, getState, api) => { // you can use api here }); |
To pass multiple things, just wrap them in a single object. Using ES2015 shorthand property names can make this more concise.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
const api = 'http://www.example.com/sandwiches/'; const whatever = 42; const store = createStore( reducer, applyMiddleware(waiter.withExtraArgument({ api, whatever })), ); // later const fetchUserCreator = (id) => fetchUserAction.waiterAction( async (dispatch, getState, { api, whatever }) => { try { dispatch(fetchUserAction.start()); dispatch(fetchUserAction.success()); } catch (err) { dispatch(fetchUserAction.error()); } }, ); |
Template mẫu: https://github.com/truongluu/redux-waiters-example
Happy coding 🙂
Comments 1