Redux Saga là gì? Hiểu về Redux Saga trong 5 phút

4526

Khi bạn tìm keyword này trên Google này thì chắc hẳn bạn đã có chút kiến thức về React native và redux. Trong các dự án sử dụng React native, một phần rất quan trọng và gần như chắc chắn phải có đó chính là middleware.

Middleware là gì?

Một ứng dụng thực tế thường đòi hỏi có những thao tác xử lý cần thời gian để phản hồi ( các thao tác bất đồng bộ lấy dữ liệu từ api hay các thao tác đọc ghi file hay đọc cookie từ trình duyệt, …). Các thao tác như vậy trong lập trình hàm gọi là side effects. Nhưng redux lại có một số ràng buộc:

  • Thứ nhất: Các xử lý trong Reducers phải là các hàm đồng bộ và pure, trả về state mới
  • Thứ hai: Reducers sẽ ko được sử dụng các hàm async vì không được thay đổi global state.

Do đó để có thể giải quyết được các side effect này ta cần thực hiện nó ở middeware.

Middleware trong Redux

 

Trong Redux, middleware là một lớp nằm giữa Reducers và Dispatch Action, nó sẽ modify và được gọi trước khi action được dispatch. Hiện nay có khá nhiều thư viện middleware cho redux nhưng nổi tiếng và được sử dụng nhiều nhất đó là redux-saga, redux-thunk và redux-observable.

Cả 3 thư viện trên đều có thể giải quyết được hầu hết các trường hợp. Mặc dù redux thunk khá dễ hiểu và dễ code nhưng nó chưa thực sự mạnh trong một số tình huống, điều này được khắc phục với các thư viện mạnh mẽ hơn như redux saga và redux observale.

Ok giờ bạn đã hiểu sự cần thiết của redux middleware để giải quyết vấn đề rồi. Trong phạm vi bài viết này tôi sẽ giới thiệu về thư viện redux saga.

Redux-Saga là gì?

Redux-Saga là một thư viện redux middleware, giúp quản lý những side effect trong ứng dụng redux trở nên đơn giản hơn. Bằng việc sử dụng tối đa tính năng Generators (function*) của ES6, nó cho phép ta viết async code nhìn giống như là synchronos.

Saga không chỉ tồn tại trong thế giới javascript, nó còn được coi là 1 pattern. Bạn có thể xem qua về saga pattern bằng clip này: https://youtu.be/xDuwrtwYHu8

Side effect là gì??

Ta đã biết tất cả những xử lý ở REDUCER đều phải là synchronous và pure tức chỉ là xử lý đồng bộ. Nhưng trong ứng dụng thực tế thì cần nhiều hơn vậy ví dụ như asynchronous (thực hiện một số việc như gọi một hàm AJAX để fetch dữ liệu về nhưng cần đợi kết quả chứ kết quả không trả về ngay được).

Hoặc là impure (thực hiện lưu, đọc dữ liệu ra bên ngoài như lưu dữ liệu ra ổ cứng hay đọc cookie từ trình duyệt… đều cần đợi kết quả). Những việc như thế trong lập trình hàm gọi nó là side effects.

Generator function là gì?

Để hiểu được sự hoạt động của redux saga bạn cần hiểu một số khái niệm cơ bản như generator function. Generator function là function có khả năng hoãn lại quá trình thực thi mà vẫn giữ nguyên được context.

Khác với function bình thường là thực thi và trả về kết quả, thì Generator function có thể thực thi, tạm dừng trả về kết quả và thực thi bằng tiếp. Từ khóa để làm được việc đấy là “YIELD”.

Nói một cách đơn giản thì generator function là 1 function có khả năng tạm ngưng trước khi hàm kết thúc (khác với pure function khi được gọi sẽ thực thi hết các câu lệnh trong hàm), và có thể tiếp tục chạy tại một thời điểm khác. Chính chức năng mới này giúp ta giải quyết được việc bất đồng bộ, hàm sẽ dừng và đợi async chạy xong rồi tiếp tục thực thi.

Generator được đưa ra cách đây mấy chục năm nhưng đến ES2015 mới được bổ sung, các ngôn ngữ khác đã được bổ sung tính năng này như C#, PHP, Ruby, C++, R…

Redux-Saga hoạt động như thế nào?

 

Đối với logic của saga, ta cung cấp một hàm cho saga, chính hàm này là hàm đứng ra xem xét các action trước khi vào store, nếu là action quan tâm thì nó sẽ thực thi hàm sẽ được thực thi, nếu bạn biết khái niệm hook thì hàm cung cấp cho saga chính là hàm hook.

Điều đặc biệt của hàm hook này nó là một generator function, trong generator function này có yield và mỗi khi yield ta sẽ trả về một plain object (plain object mô tả hành động xảy ra theo cách clear nhất, nhìn vào object đó nó giúp bạn hiểu ngay được những gì đang diễn ra trong app và tại sao nó thay đổi). Object trả về đó được gọi Effect object.

Effect object này đơn giản chỉ là một object bình thường nhưng chứa thông tin đặc biệt dùng để chỉ dẫn middleware của Redux thực thi các hoạt động khác ví dụ như gọi một hàm async khác hay put một action tới store. Để tạo ra effect object đề cập ở trên thì ta gọi hàm từ thư viện của saga là redux-saga/effects.

Nói tóm gọm hoạt động lại như sau:

Redux saga cung cấp các hàm helper effect, các hàm này sẽ trả về một effect object chứa đựng thông tin đặc biệt chỉ dẫn middeware của Redux có thể thực hiện tiếp các hành động khác. Các hàm helper effect sẽ được thực thi trong các generator function.

Một số helper trong redux saga:

  1. takeEvery() : thực thi và trả lại kết quả của mọi actions được gọi.
  2. takeLastest() : có nghĩa là nếu chúng ta thực hiện một loạt các actions, nó sẽ chỉ thực thi và trả lại kết quả của của actions cuối cùng.
  3. take() : tạm dừng cho đến khi nhận được action
  4. put() : dispatch một action.
  5. call(): gọi function. Nếu nó return về một promise, tạm dừng saga cho đến khi promise được giải quyết.
  6. race() : chạy nhiều effect đồng thời, sau đó hủy tất cả nếu một trong số đó kết thúc.

 

Tại sao tôi phải sử dụng Saga??

Khi bắt đầu tìm tòi về redux, bạn hay tìm thấy các bài hướng dẫn sử dụng redux-thunk hoặc redux-saga để quản lý các async action. Vậy tại sao bạn lại được khuyên sử dụng redux-saga ?

Trích dẫn trong document của redux-saga:

“Contrary to redux thunk, you don’t end up in callback hell, you can test your asynchronous flows easily and your actions stay pure. Tạm dịch: trái với redux thunk, bạn không cần phải phát dồ lên với các callback trong mỗi action, đến với saga đi, bạn có thể test các async action với một quy trình dễ dàng mà không làm hư các action đó”

So sánh saga và thunk:

  1. redux-thunk

     

    Ở đây ta có một action creator getDataFromAPI() bắt đầu async progress như sau:

    • Đầu tiên bắn ra action cho phép store biết rằng chuẩn bị 1 API request ( dispatch(getDataStarted())
    • Tiếp theo thực hiện API request trả về một Promise
    • Cuối cùng bắn ra success action nếu request thành công hoặc error action nếu có lỗi
  2. redux-saga

     

    Cùng một process, nhưng cách implement khác nhau hoàn toàn.

    • put thay cho dispatch
    • function cuối (apiSaga()) giúp theo dõi tổng thể toàn bộ các action (ở đây có API_BUTTON_CLICK)
    • Với cách gọi của redux-saga, chúng ta có thể get data từ bất kì async function nào (promise, …)
  3. Nhận xét

    Cả 2 cách implement đều dễ đọc, tuy nhiên đối với redux-thunk , bạn phải đối đầu với một tá các promise, các callback nếu có, rất mất thời gian cho người maintain đọc và tìm code. Với redux-saga , đơn giản bạn chỉ cần track theo try/catch block để theo dõi dòng code, bên cạnh đó còn có hàm giúp bạn track các action một cách dễ dàng.

    Ưu điểm:

    • Do tách riêng side-effect ra khỏi action nên việc testing là dễ dàng hơn Redux-Thunk.
    • Giữ cho action pure synchronos theo chuẩn redux và loại bỏ hoàn toàn callback theo javascript truyền thống.

    Nhược điểm:

    • Function ko viết được dạng arrow-function.
    • Phải hiểu về Generator function và các khái niệm trong saga pattern
    • Cách viết theo lối Imperative

Ví dụ

Chúng ta sẽ thực hiện một ví dụ để hiểu cách thực hiện redux saga trong Project. Ở đây ta sẽ thực hiện một hành động đó là lấy danh sách các bộ phim từ server. Để thực thi một Saga, trước hết chúng ta cần tạo một action. Trong file actions.js, ta thêm đoạn code sau:

Chúng ta tạo một function để lấy dữ liệu film từ api và đặt nó trong file api.js

Chúng ta tạo file reducer có tên là app và đặt vào file reducer.js

Các bạn tạo một file mới saga.js và thêm vào đoạn code sau để có thể thêm một action bất đồng bộ khi action fetchData thực thi:

Chúng ta import phương thức puttakeEvery từ redux-saga/effects. Khi chúng ta gọi phương thức put, redux saga sẽ hướng middleware tới thực thi một action. takeEvery là phương thức lắng nghe kết quả trả về của hành động FETCHING_FILM_LIST và gọi về một phương thức callback là fetchFilms

Khi phương thức fetchFilms đc gọi, sau khi thực hiện getFilm() trả về thành công thì nó sẽ thực thi hành động FETCHING_FILMS_SUCCCESS.

Chúng ta tạo file configureStore.js để sử dụng saga middleware.

Những điểm chính cần lưu ý trong file này là nếu chúng ta import saga và cũng createSagaMiddleware từ thư viện redux-saga. Khi chúng ta tạo ra store, chúng ta sẽ pass mọi thứ cho sagaMiddleware mà chúng ta đã tạo ra và gọi sagaMiddleWare.run trước khi mọi thứ được lưu trữ ở store.

Chạy ứng dụng và bạn sẽ nhận được danh sách các bộ phim để có thể đẩy dữ liệu ra view.

Redux cho người mới bắt đầu – Part 1 Introduction

Sơ lược về connect React-redux

Cách test ứng dụng react redux?

SHARE