Tại sao chúng ta nên dùng một middleware như Redux Thunk
Có bao giờ bạn tự hỏi tại sao mình lại phải sử dụng một middleware như Redux Thunk, Redux Saga hay chưa? Hay bạn chỉ dùng vì thấy các tutorial trên mạng bảo nên dùng và thế là bạn dùng.
Dù gì thì cũng đến lúc bạn cần nhìn nhận lại rằng liệu chúng ta có thực sự cần một middleware hay không, ở bài viết này mình sẽ phân tích giữa cách viết thuần không middleware và cách dùng Redux Thunk nhé.
🥇 Dispatch bất đồng bộ
Mình ví dụ người dùng đăng nhập vào trang web chúng ta, sau khi đăng nhập thành công thì hiển thị một cái toast thông báo, sau 5s thì cái thông báo đó tự động ẩn đi.
Đây là cách đơn giản nhất để làm trong Redux
Hoặc như thế này bên trong connected
component
Chỉ có một sự khác biệt là bên trong connected
component, thường thì bạn sẽ không truy cập trực tiếp đến store mà bạn sẽ nhận dispatch
thông qua prop (hoặc hook đối với React Hook). Tuy nhiên, không có sự khác biệt nào đáng kể.
Nếu bạn không muốn gõ lại khi dispatch
cùng các action từ các component khác nhau, bạn có thể tách action ra như thế này thay vì phải dispatch
một object
Hoặc nếu bạn đưa các action vào trong connect()
thì bạn sẽ dùng như thế này
Nãy giờ thì chúng ta chưa sử dụng bất cứ middleware nào hoặc concept nâng cao nào cả.
🥇 Tách ra thành action bất đồng bộ
Cách tiếp cận bên trên làm việc tốt ở trong những trường hợp đơn giản, nhưng bạn có thể tìm thấy một vài vấn đề:
-
Nó làm cho bạn phải viết lại logic này ở bất kỳ đâu mà bạn muốn show thông báo.
-
Các thông báo không có ID để phân biệt với nhau, dễ dẫn đến hiện tượng dispatch
HIDE_NOTIFICATION
1 cái là tất cả các thông báo hiện có trên màn hình đều bị ẩn sớm hơn dự tính.
Để giải quyết những vấn đề này, chúng ta cần tách ra thành một function mà chỉ tập trung logic timeout và dispatch 2 action. Nó có thể trông như thế này:
Bây giờ thì các component có thể sử dụng showNotificationWithTimeout
mà không bị duplicate đoạn logic trên hoặc gặp phải vấn đề ẩn hiện notification:
Tại sao showNotificationWithTimeout()
có dispatch
như là đối số thứ nhất? Bởi vì nó cần dispatch
các action vào store. Bình thường một component thực hiện việc dispatch
nhưng vì chúng ta muốn một function ngoài làm việc này, chúng ta cần truyền dispatch
vào.
Nếu bạn có một singleton store được export từ một module nào đó, bạn có thể import nó và sử dụng dispatch
trực tiếp như thế này
Điều này trông có vẻ đơn giản hơn nhưng chúng ta không nên làm vậy. Nguyên nhân chính là bởi vì nó ép store phải là một singleton. Điều này làm cho nó khó tích hợp vào server rendering. Trên server, bạn sẽ muốn mỗi request có store riêng, để mỗi user khác nhau nhận một preload data khác nhau.
Một singleton cũng khó để test hơn.
Vì thế chúng ta không nên làm như thế, hoặc bạn chắc chắn trong tương lai đi nữa thì app cũng chỉ client-side thôi.
Quay trở lại với phiên bản trước đó:
Cách này đã giải quyết vấn đề với việc lặp lại logic và ẩn hiện notification.
🥇 Thunk Middleware
Với những app đơn giản, cách tiếp cận trên có vẻ ổn. Bạn không cần quan tâm đến middleware nếu bạn hài lòng với nó.
Trong những app lớn, có thể bạn sẽ gặp một vài bất tiện xung quanh nó.
Ví dụ, nó có vẻ không hay lắm khi chúng ta phải truyền dispatch
đi khắp nơi. Điều này làm cho việc phân tách container và conponent trở nên phức tạp hơn bởi vì bất cứ component nào mà dispatch
một Redux action bất đồng bộ thì phải nhận dispatch
như một prop. Bạn không thể bind action với connect()
được nữa bởi vì showNotificationWithTimeout()
không thực sự là một action creator nữa rồi. Nó không return về một object (Redux action).
Thêm nữa, khá là không hay khi ta phải nhớ function nào là action đồng bộ như showNotification()
và cái nào là bất đồng bộ như showNotificationWithTimeout()
. Vì cách sử dụng chúng khác nhau nên bạn cũng phải cẩn thận nếu không sẽ dẫn đến những lỗi không đáng.
Chúng ta cần cách gì đó để cho Redux thấy được những action creator bất đồng bộ như là một trường hợp đặc biệt của action creator thay vì là một function khác biệt hoàn toàn.
Nếu bạn còn ở đây với mình thì bạn cũng nhận ra được vấn đề bên trong app của bạn, chào mừng bạn sử dụng Redux Thunk middleware.
Trong gist, Redux Thunk "dạy" cho Redux nhận biết được các action đặc biệt này.
Khi middleware này được enable, nếu bạn dispatch một function, Redux Thunk middleware sẽ đưa function đó một đối số dispatch. Redux Thunk cũng giúp cho reducer của bạn chỉ nhận plain object actions.
Redux Thunk cũng cho phép chúng ta khai báo showNotificationWithTimeout()
như một Redux action creator thông thường.
Để ý cách viết gần giống với phiên bản trước đó. Tuy nhiên nó không nhận vào dispatch
như đối số đầu tiên. Thay vào đó nó return một function mà nhận vào đối số là dispatch
.
Chúng ta sử dúng nó trong component như thế nào? Rõ ràng, chúng ta có thể viết như thế này:
Chúng ta đang dùng như một currying function và truyền dispatch
vào.
Có vẻ nó còn trông "ngố" hơn phiên bản trước đó.
Nhưng như mình đã nói trước đó. Nếu Redux Thunk middleware được enable, bất cứ khi nào bạn dispatch
một function thay vì một object, middleware sẽ gọi function đó với dispatch
được truyền vào như đối số đầu tiên.
Vì thế chúng ta có thể làm như thế này
Cuối cùng, dispatch
một async action trông không khác với một sync action. Đây là điều tốt bởi vì component không cần quan tâm điều gì xảy ra bên trong action, mặc kệ nó là đồng bộ hay bất đồng bộ.
Nếu kết hợp với connect()
thì cách chúng ta dispatch
sẽ ngắn gọn hơn nữa.
🥇 Đọc state trong Thunk
Trong trường hợp bạn muốn get state hiện tại của Redux store, bạn có thể truyền getState
như đối số thứ 2 vào function mà bạn return từ thunk action creator. Điều này cho phép thunk đọc state hiện tại của store.
🥇 Return trong Thunk
Redux không quan tâm bạn return gì từ thunk, nhưng nó sẽ đưa cho bạn giá trị mà bạn return từ thunk sau khi dispatch
xong. Đó là lý do tại sao bạn có thể return một Promise từ thunk và đợi nó cho đến khi nó thành công bằng cách gọi
🥇 Tóm lại
Đừng sử dụng bất cứ middleware nào từ Redux Thunk, Redux Saga nếu bạn thực sự không cần chúng và hiểu bạn đang làm gì.
Nếu app của bạn tương lai có thể mở rộng và bạn muốn nhận được những lợi ích mà thunk mang lại như giải quyết được vấn đề truyền dispatch
đi khắp mọi nơi trong component thì mình recommend là dùng ngay và luôn cho chắc. Dù gì nó cũng rất nhẹ.
Cảm ơn bạn đã đọc đến đây, hẹn gặp lại ở những bài viết tiếp theo.
🥇 Tham khảo
Từ một câu trả lời của Dan Abramov – founder Redux Thunk trên stackoverflow
Nguồn bài viết: https://duthanhduoc.com