Bài viết trước mình đã giới thiệu về package redux-waiters (quản lý trạng thái tải của action khi dùng redux-thunk). Vậy đối với các ứng dụng web các bạn dùng redux-saga thì sao nhỉ. Bài này mình không đề cập tới redux-saga là gì nhé, các bạn xem thêm ở official document
Đối với các ứng dụng cũ, các bạn đã dùng redux-thunk, thì cũng không có vấn đề gì, với những tính năng mới thì các bạn cũng có thể chuyển qua sử dụng redux-saga, chạy song song với redux-thunk.
Để quản lý được trạng thái tải của redux-saga, thì mình có cập nhật lại package redux-waiters, thì từ phiên bản 1.0.6, redux-waiters sẽ hổ trợ vừa redux-thunk và redux-saga. Và đây là cách mình giải quyết vấn đề, gồm có 2 bước sau
- Xử lý lại saga worker, để cập nhật được trạng thái bắt đầu của 1 action
- Cập nhật thêm waiter middleware để xử lý thêm trường hợp callback là Generator function
Xử lý lại saga worker
xét đoạn mã khi làm việc với redux-saga, trong file saga.js
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 |
import { all, takeEvery, delay, put } from 'redux-saga/effects' import { increAction } from './reducers/counter' function* incrCounter(action) { try { console.log('call incrCounter start', action) yield delay(4000) console.log('call increcounter continue') yield put(increAction.success(1)); console.log('call incr success') } catch (err) { console.log('err', err) } } function* watchIncrCounter() { yield takeEvery(increAction.start,incrCounter) } function* watchLog() { yield takeEvery('*', function* log(action) { console.log('action', action) }) } export default function* rootSaga() { yield all([ watchIncrCounter(), watchLog() ]) } |
Với đoạn mã trên thì mình có 2 saga watcher
- watchIncrCounter: theo dõi tăng giá trị counter
- watchLog: log lại các action được gọi (sử dụng pattern *)
Cách xử lý của mình ở đây là thay vì saga watcher sẽ call tới 1 saga worker là 1 generator function, thì mình sẽ wrap cái saga worker lại, để trước khi nó call tới 1 saga worker để sử lý các effect liên quan thì mình phải cập nhật được trạng thái tải của action trước tiên :). Các bạn theo dõi đoạn mã bên dưới mình đã xử lý lại
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function wrapWithGenerator(handler) { return function* wr(action) { if (!!action.continue) { yield* handler(action) } else { yield put({ type: increAction.start, callback: handler, action }) } } } function* watchIncrCounter() { yield takeEvery(increAction.start, wrapWithGenerator(incrCounter)) } |
Để thực hiện điều này, đầu tiên mình wrap cái saga worker lại với hàm wrapWithGenerator. Bước tiếp theo mình xử lý mình cũng sẽ trả về đúng đối tượng là takeEvery sẽ call là 1 generator function mới là function* wr(action){}. Tiếp theo với đoạn mã:
1 2 3 4 5 6 7 8 9 |
if (!!action.continue) { yield* handler(action) } else { yield put({ type: increAction.start, callback: handler, action }) } |
Mục đích của đoạn mã này là thay vì call tới saga worker liền mình sẽ put 1 action lên middleware để update lại trạng thái bắt đầu của sự kiện (xem thêm ở phần cập nhận waiter middleware), sau đó từ middleware mình sẽ recall lại action với 1 thuộc tính mới là continue= true, để cho biết là call trực tiếp với saga worker để xử lý
Cập nhật waiter middleware
Xem đoạn mã waiter middleware mình đã cập nhật để xử lý thêm vụ callback là generator function
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 |
import { startWaitAction, endWaitAction } from '../reducers'; const createWaiterMiddleWare = (extraArgument) => { return ({ dispatch, getState }) => (next) => (action) => { if (typeof action === 'object' && typeof action.callback === 'function') { dispatch(startWaiterAction(action.type.toString())) // check callback is Generator function if (action.callback.prototype && action.callback.prototype.toString() === '[object Generator]') { return dispatch({ ...action.action, continue: true }) } return action.callback(dispatch, getState, extraArgument); } if ( typeof action.type === 'string' && (action.type.endsWith('success/@@end') || action.type.endsWith('error/@@end')) ) { dispatch( endWaitAction( action.type .replace(/(?:[^\]].*?\]\s+?)(.*)/, '$1') .replace(/\/success\/@@end|\/error\/@@end/, ''), ), ); } return next(action); }; }; const waiter = createWaiterMiddleWare(); waiter.withExtraArgument = createWaiterMiddleWare; export default waiter; |
Với đoạn mã trên, mình có cập nhật thêm là callback của action là 1 generator function thì mình sẽ cập nhật trạng thái tải của action là bắt đầu, và recall lại action với tham số được thêm là continue = true, để nó thực hiện trực tiếp worker saga như ban đầu. Vậy là mình đã xử lý xong vụ tải trong redux-saga
Mình sẽ cập nhật lại package redux-waiters và thêm hướng dẫn tích hợp khi dùng redux-saga

1. Multiply counter: redux-thunk bình thường
2. Desc counter: redux-waiters
3. Incr counter: redux-waiter cho saga
Link github: https://github.com/truongluu/redux-waiters-with-saga-example
Comments 1