Khi làm việc chung với kiến trúc dvaJS, mình có gặp nhiều tình huống khác nhau và bài viết này mình chia sẻ một số tips khi làm việc với dvaJS có thể nhiều bạn sẽ cần để tăng hiệu suất làm việc của mình và làm việc nó chuyên nghiệp hơn. Các bạn có thể để lại comment cho những tips bạn thấy hay nhé hoặc có tips nào hay bạn đã gặp, để mình cập nhật thêm (nếu chưa biết gì về dvaJS các bạn xem thêm bài viết về dvaJS)
- Cách sử dụng loading model trong dvaJS.
- Tải dữ dữ liệu khi vào 1 trang cụ thể và tải theo filter từ queries của url.
- Put effect trong dvaJS là non blocking, vậy muốn dùng put mà đợi kết quả trả về để chạy được tác vụ khác thì sao.
- Theo dõi effect (bắt đầu hoặc kết thúc) để làm 1 tác vụ gì đó liên quan.
- Sử dụng call effect với các thư viện hoặc built-in chỉ hổ trợ callback.
Cách sử dụng loading model trong dvaJS
Đối với loading model mình sẽ thao tác được những gì, cụ thể như sau nhé
Loading model cung cấp cho chúng ta 3 cách để truy xuất
- Theo dõi trạng thái tải của toàn ứng dụng (có cái gì loading thì nó cũng thông báo)
- Theo dõi trạng thái tải theo model chỉ định
- Theo dõi trạng thái tải theo 1 effect chỉ định
Giả sử mình có 2 model là user và login như hình

model user và login có nội dung sau
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
export default { namespace: 'user', state: { profile: {} }, effects: { *effect1(action, { call, put }) { }, *effect2(action, { call, put }) { }, } }; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
export default { namespace: 'login', state: { login: false }, effects: { *effect1(action, { call, put }) { }, *effect2(action, { call, put }) { }, } }; |
Và mình có 1 page là user.js, với mã đơn giản
1 2 3 4 5 6 7 8 9 10 |
import React from 'react'; import { connect } from 'dva'; export default (props) => { return ( <div> <h1>User page</h1> </div> ); } |
Ở page user này mình sẽ viết lại mã để theo dõi trạng thái tải toàn bộ, model cụ thể và effect cụ thể như sau
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 |
import React from 'react'; import { connect } from 'dva'; export default connect(({ loading }) => ({ globalLoading: loading.global, userLoading: loading.models['user'], loginLoading: loading.models['login'], effect1UserLoading: loading.effects['user/effect1'] }))((props) => { return ( <div> <h1>User page</h1> <ul> <li> globalLoading: {props.globalLoading} </li> <li> userLoading: {props.userLoading} </li> <li> loginLoading: {props.loginLoading} </li> <li> effect1UserLoading: {props.effect1UserLoading} </li> </ul> </div> ); }) |
Vậy là mình đã theo dõi được những gì mình cần
Tải dữ liệu khi vào 1 trang cụ thể và tải theo filter từ queries của url
Trường hợp này các bạn hay gặp khi nào, thay vì vào 1 page cụ thể như user, các bạn sẽ vào trong hàm componentDidMount rồi gọi dispatch({type: ‘user/effect1’}) gì đó là cách mà các bạn hay làm, thì ở đây mình kiểm tra và fetch dữ liệu trong model luôn nhé.
Model cho mình theo dõi được thay đổi của pathname, và việc mình cần làm là đăng ký nó trong subscriptions, đối với model nào thì mình đăng ký ở model đó nhé (khi vào page user thì đăng ký trong subscriptions của user model chẳng hạn, hoặc các bạn có thể tổ chức tập trung hay gì đó tùy). Các bạn theo dõi đoạn mã sau trong model user nhé
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
export default { namespace: 'user', ... subscriptions: { setup(history, dispatch) { return history.listen(({pathname}) => { if (pathname === '/user') { dispatch({ type: 'effect1' }); } }); } } } |
Đoạn mã trên mình lắng nghe thay đổi pathname và kiểm tra nếu pathname hiện tại là /user (ứng với trang user) thì gọi effect1 để xử lý tiếp
Vậy trong trường hợp bạn đang xử lý tải dữ liệu theo url khi vào trang /user?page=1…, thì trong subscriptions, bạn cũng truy xuất được biến query và tải dữ liệu theo phân trang, xét đoạn mã bên dưới:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
export default { namespace: 'user', ... subscriptions: { setup(history, dispatch) { return history.listen(({pathname, query}) => { if (pathname === '/user') { dispatch({ type: 'effect1', payload: query }); } }); } } } |
Với đoạn mã trên thì ta truy xuất thêm được thêm tham số là query (tham số này là 1 object chưa tất cả query đang có trên url hiện tại)
Và khi xử lý mã tải dữ liệu theo trang trong subscriptions, ta cũng giải quyết luôn trường hợp là refesh lại trình duyệt và vẫn tải dữ liệu đúng theo trang và tham số query trên url
Put effect trong dvaJS là non blocking, vậy muốn dùng put và đợi kết quả trả về để chạy tác vụ khác thì sao
Thông thường các bạn thường gọi put effect để gọi tới reducer hoặc effect nào đó, nhưng có bao giờ các bạn suy nghĩ put effect đó nó sẽ chạy như thế nào, nếu nhiều nhiều put effect 1 lần thì sao có ưu tiên gì hay thứ tự nó chạy sao. Xét ví dụ bên dưới
1 2 3 4 5 6 7 |
yield put({ type: 'user/updateData' }) yield put({ type: 'login/updateLogin' }) |
Đoạn mã trên cho phép ta gọi tới 1 effect hoặc 1 reducer bên trong effect hiện tại và put này là non blocking như các bạn (non blocking là gì các bạn tham khảo thêm saga effect nhé).
Vậy nếu ở đoạn mã trên chúng ta muốn chắc chắn rằng user/updateData đã chạy hoàn thành thì mới chạy 1 put effect khác bên dưới. Đơn giản thôi nha các bạn put effect cung cấp cho chúng ta 1 chức năng đó là resolve. Viết lại đoạn mã trên
1 2 3 4 5 6 7 |
yield put.resolve({ type: 'user/updateData' }) yield put({ type: 'login/updateLogin' }) |
Với đoạn mã trên thì effect là user/updateData sẽ đảm bảo là chạy xong thì mới chạy đến đoạn mã bên dưới
Theo dõi effect (bắt đầu hoặc kết thúc) để làm 1 tác vụ gì đó liên quan
Tips này mình đã viết riêng ở 1 bài, các bạn theo dõi bài viết này nhé https://luuxuantruong.info/watcher-effect-trong-dvajs/
Sử dụng call effect với các thư viện hoặc built-in chỉ hổ trợ callback
Đơn giản là chuyển hàm mình cần thao tác về 1 Promise nha các bạn, vậy là chúng ta sẽ đảm bảo được call effect sẽ lấy được kết quả trả về nếu callback được gọi à. Xét trường hợp ví dụ:
Mình muốn lấy location hiện tại của trình duyệt bằng built-in function của object navigator trình duyệt bằng phương thức navigator.geolocation.getCurrentPosition (mà hàm này chỉ cung cấp callback để sử dụng), nên mình sẽ chuyển nó về 1 Promise như mã sau:
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 |
export function geoFindMeEffect(maps) { return new Promise((resolve, reject) => { if (!navigator.geolocation) { reject(new Error('Vị trí địa lý không được hổ trợ trên trình duyệt của bạn')); return; } function error() { reject(new Error('Không thể truy xuất vị trí của bạn')); } function success(position) { const geocoder = new maps.Geocoder(); geocoder.geocode( { location: { lat: position.coords.latitude, lng: position.coords.longitude } }, (results, status) => { if (status === 'OK') { if (results[0]) { const point = { lat: position.coords.latitude, lng: position.coords.longitude, address: results[0].formatted_address, }; return resolve(point); } } return reject(new Error('Không thể lấy địa chỉ hiện tại')); } ); } navigator.geolocation.getCurrentPosition(success, error); }); } |
hàm này sẽ trả về là point object nếu lấy được từ trình duyệt hoặc ném lỗi với thông báo ‘Không thể lấy được địa chỉ hiện tại’ nha các bạn
Và ở 1 effect tương ứng cần gọi thì mình gọi bình thường
1 2 3 4 5 6 7 8 9 10 11 12 13 |
export default { namespace: 'user', ... effects: { * getLocation(action, { call }) { try { const currentLocation = yield call(geoFindMeEffect, mapInstanceObject); } catch (err) { console.log(err); } }, } |
Vậy là mình đã gọi tới geoFindMeEffect và đợi point trả về (trường hợp không nhận được point trả về thì bắt ngoại lệ ở catch và thông báo lỗi)
Happy coding 🙂