iOS · redux

Redux trong Swift – Phần 3: Action Creator và Middleware

Trong các phần trước mình đã giới thiệu về Redux cũng như thực hiện một ứng dụng đơn giản sử dụng Redux. Trong phần này mình sẽ giới thiệu những thành phần còn lại trong Redux là ActionCreator, Middleware. Trước khi bắt đầu, các bạn nên tìm hiểu thêm về lập trình hàm (functional programming): high-order, currying và composing.

Phần 1: Giới thiệu Redux
Phần 2: Sử dụng Redux trong Swift: Action, Store, Reducer
Phần 3: Sử dụng Middleware, ActionCreator

Chúng ta sẽ thực hiện ứng dụng GitHubBrowser, bao gồm những tính năng sau:

  • Đăng nhập GitHub
  • Hiển thị danh sách Repositories, tìm kiếm, kéo để cập nhật dữ liệu mới
  • Hỗ trợ Offline (Mục đích giới thiệu Middleware)

redux-github-browser-example.gif

Sau khi hoàn thành ứng dụng, hi vọng chúng ta có thể nắm bắt được cách sử dụng Action Creator và Middleware trong các ứng dụng thực tế. Trong ứng dụng này, mình chỉ tập trung vào ActionCreator và Middleware, không giới thiệu về cách thực hiện Reducer, State và Action. Chúng ta có thể tham khảo tại Source Code trên GitHub hoặc tham khảo phần trước.

Để chạy ứng dụng, chúng ta cần truy cập GitHub -> Edit Profile -> OAuth Application -> Register a new application. Nhập thông tin như hình phía dưới, sau đó mở file Defines.swift, thay đổi hai biến sau:

screen-shot-2016-12-09-at-4-25-04-pm

register-new-app-with GitHub.png

1. Action Creator

Trong phần trước, chúng ta thực hiện ứng dụng thực thi đồng bộ (synchronous). Mỗi khi một action được dispatch, state sẽ được update ngay lập tức. Trong thực tế, chúng ta thường có nhu cầu hiển thị dữ liệu từ API, hoặc thực hiện những logic phức tạp cần thực thi bất đồng bộ. Bây giờ chúng ta sẽ sử dụng ActionCreator để thực hiện chức năng đăng nhập. Chúng ta định nghĩa hai actions: authenticateUser và handleOpenURL như hình bên dưới.

authenticateUser: Định nghĩa các tác vụ cần thực thi khi người dùng nhấn nút đăng nhập: Mở trang đăng nhập của GitHub để người dùng nhập tên đăng nhập và mật khẩu.

handleOpenURL: Định nghĩa các tác vụ cần thực thi sau khi đăng nhập thành công, kết quả trả về “rxreduxgithub://success?token=ABC123”

Chúng ta có thể thấy authenticateUser và handleOpenURL là những high-order functions có kết quả trả về là một hàm (function) thay vì một đối tượng dữ liệu (plain object). Chúng được gọi là những Async Action,  có nhiệm vụ thực hiện side-effects như gọi API, xử lý yêu cầu nghiệp vụ, … sau đó phát ra các action khác, để thông báo kết quả cho Store.

Async Action thường nhận vào hai tham số cho phép xác định trạng thái của ứng dụng và chuyển tiếp các action, chẳng hạn như GetState và DispatchFunction như ở trên. Kiểu dữ liệu trả về có thể là Void, Observable, Promise, …

Tương tự, chúng ta thêm fectchGitHubRepositories để lấy danh sách Repositories từ API của GitHub.

redux-repositories-actions.jpg

 

Câu hỏi là các Async Action này sẽ được dispatch như thế nào?

Cho tới thời điểm hiện tại thì có hai cách tiếp cận thường được áp dụng:

  • Định nghĩa một hàm dispatch trong store, hàm này có tham số đầu vào là Action Creator. Cách này đang được sử dụng trong thư viện ReSwiftdispatch-in-store
  • Một cách tiếp cận khác là sử dụng Thunk Middleware. Nhưng trước đó, chúng ta cần tìm hiểu về Middleware.

2. Middleware

Nếu các bạn muốn hiểu sâu hơn về Middleware thì có thể tìm hiểu thêm về Composing Function và Currying Function trong Functional Programming (xem thêm: Understand Middleware). Trong phần này, mình chỉ tập trung giới thiệu về cách hiện thực và sử dụng Middleware.

2.1 Logging

Giả sử chúng ta muốn ghi log tất cả các action của ứng dụng để thuận tiện cho việc debug. Chúng ta thêm mới LoggingMiddleware như hình bên dưới.

redux-middleware-logging

Sau đó, đăng ký LoggingMiddleware với Store như sau:

redux-middleware-registration

Chạy ứng dụng, mọi actions sẽ được ghi log như sau:

"initializeGitHubConfigurations" is being dispatched.
"initialize" is being dispatched.
"setRouteAction(["loginRoute"], true)" is being dispatched.

2.2 – Persistence

Một ví dụ khác giúp chúng ta hiểu được vai trò và sức mạnh của Middleware đó là lưu trữ trạng thái ứng dụng vào cơ sở dữ liệu (CSDL). Giả sử, chúng ta muốn hỗ trợ ứng dụng hiển thị dữ liệu đã được load về khi không có kết nối mạng (offline). Khi đó, chúng ta cần lưu trữ dữ liệu vào CSDL. Khi ứng dụng được mở, dữ liệu từ CSDL sẽ được hiển thị.

Chúng ta có thể hiện thực yêu cầu này hết sức đơn giản bằng cách tạo ra một Middleware đảm nhiệm việc lưu trữ, hiển thị dữ liệu từ CSDL như sau:

redux-middleware-persistence.jpg

Tất nhiên, đừng quên đăng ký PersistenceMiddleware với Store

redux-middleware-registration.jpg

Xong. Ứng dụng đã được hỗ trợ offline như yêu cầu, ngắt kết nối mạng, tắt ứng dụng và mở lại, chúng ta vẫn có thể xem lại những dữ liệu được lưu trữ tại database.

2.3 Thunk Middleware

Trong phần trước mình có đề cập tới ActionCreator và Async Action. Khi sử dụng chúng ta muốn dispatch và đối xử các action này như nhưng Action thông thường. Nhưng Async Action là một hàm nên chúng ta không thể thừa kế từ protocol Action. Vì vậy, chúng ta cần đóng gói (wrap) Async Action bên trong một struct hoặc class được gọi là thunk. Những struct, class đó có thể thừa kế từ protocol Action. Sau đó, chúng ta định nghĩa một Thunk Middleware có nhiệm vụ unwrap những Thunk Action. Hình bên dưới là một Thunk Middleware trong RxRedux.

redux-thunk-middleware

Tuỳ theo yêu cầu, chúng ta có thể hiện thực thêm ObservableMiddleware, PromiseMiddleware, … Để giúp cho ứng dụng đơn giản, mình sẽ giới thiệu những Middleware này trong phần tiếp theo.

Bây giờ, Async Action có thể được dispatch như một Action thông thường:

dispatch-async-action

3. Kết

Trong phần này, mình vừa giới thiệu về cách hiện thực và sử dụng ActionCreator và Middleware trong các ứng dụng thực tế. Ngoài ra, các bạn có thể tham khảo thêm Redux-Saga là một cách tiếp cận khác để giải quyết những vấn đề như gọi API, side-effects, … Trong phần tiếp theo, mình sẽ đề cập tới một số vấn đề thường gặp trong Redux.

Source Code

18 thoughts on “Redux trong Swift – Phần 3: Action Creator và Middleware

    1. Trường hợp nếu có nhiều API lồng nhau thì xử lý sao vậy bạn ?

      Mình đang sử dụng ReSwift (chưa sử dụng RxRedux của bạn vì chưa nắm rõ RxSwift).

      *Action Creators* như sau :
      func getListAdmission(state: MainAppState, store: Store) -> Action? {
      Utils.performDelay(delay: 2) {
      // Mock up data from API
      let obj1 = AdmissionStep(name: “First item”)
      let obj2 = AdmissionStep(name: “Second item”)
      mainStore.dispatch(GetAdmissionStepsSuccessAction(steps: [obj1, obj2]))
      }
      return nil
      }

      *View Controller *:
      mainStore.dispatch(actionCreator.getListAdmission)
      mainStore.subscribe(self) { state in
      (state.admissionState?.steps)!
      }

      (theo ví dụ của Reswift)

      Cho mình hỏi là nếu mình cần gọi thêm 1 API nữa (sau khi lấy xong list admission) thì làm sao ?
      Vì hàm subcribe hiện tại ko biết nhận action nào hết

      Cám ơn nhiều !

      Số lượt thích

      1. about moT#0tum&e8230;&#822n;mhe most important thing you can do to achieve your goals is to make sure that as soon as you set them, you immediately begin to create momentum.” Anthony RobbinsYour statement that you had a goal and knew why you were doing it is key.

        Số lượt thích

    2. Well I have an interesting tip for you. Believe it or not, the color blue functions as an appetite sustserpanp. So serve up dinner on blue plates, dress in blue while you eat, and cover your table with a blue tablecloth. Conversely, avoid red, yellow, and orange in your dining areas. Studies find they encourage eating.

      Số lượt thích

  1. Trường hợp nếu có nhiều API lồng nhau thì xử lý sao vậy bạn ?

    Mình đang sử dụng ReSwift (chưa sử dụng RxRedux của bạn vì chưa nắm rõ RxSwift).

    *Action Creators* như sau :
    func getListAdmission(state: MainAppState, store: Store) -> Action? {
    Utils.performDelay(delay: 2) {
    // Mock up data from API
    let obj1 = AdmissionStep(name: “First item”)
    let obj2 = AdmissionStep(name: “Second item”)
    mainStore.dispatch(GetAdmissionStepsSuccessAction(steps: [obj1, obj2]))
    }
    return nil
    }

    *View Controller *:
    mainStore.dispatch(actionCreator.getListAdmission)
    mainStore.subscribe(self) { state in
    (state.admissionState?.steps)!
    }

    (theo ví dụ của Reswift)

    Cho mình hỏi là nếu mình cần gọi thêm 1 API nữa (sau khi lấy xong list admission) thì làm sao ?
    Vì hàm subcribe hiện tại ko biết nhận action nào hết

    Cám ơn nhiều !

    Số lượt thích

    1. Nếu bạn sử dụng observable middleware hoặc promise middleware thì simple. Bạn chỉ cần tạo 2 actions riêng biệt cho 2 lệnh call api, sau đó tạo ra action 3, flatMap 2 action này(nếu sử dụng rx), hoặc chain nó(nếu sủ dụng promise). Cuối cùng dispatch action thứ 3. Nếu không sử dụng 2 loại trên thì đơn giản nhất bạn tạo ra một action call 2 cái api đó lồng nhau trong cùng 1 action. Mình đang đi chơi, ko có máy. Sẽ làm ví dụ sau tết dương lịch

      Liked by 1 person

      1. Em co lam ma no bi bao loi khi dispath action.

        struct CountActionCreator {
        func authenticateUser() -> (@escaping GetState, @escaping DispatchFunction) -> Void {
        return { getState, dispatch in
        guard let state = getState() as? AppState else { return }
        _ = dispatch(CountAction.CounterActionIncrease)
        }
        }
        }

        Khi em dispath nhu the nay :
        store.dispatch(CountActionCreator().authenticateUser())
        Thi no bao loi nhu sau :
        Cannot invoke ‘dispatch’ with an argument list of type ‘((@escaping GetState, @escaping DispatchFunction) -> Void)’
        Anh co the giai thich cho em khong a.
        Thanks a.

        Số lượt thích

      2. ReSwift cung cấp 3 hàm dispatch để dispatch 3 loại actions:
        1. Action cơ bản
        2.public typealias ActionCreator = (_ state: State, _ store: Store) -> Action?
        3. public typealias AsyncActionCreator = (
        _ state: State,
        _ store: Store,
        _ actionCreatorCallback: @escaping ((ActionCreator) -> Void)
        ) -> Void

        Nếu bạn sử dụng ReSwift thì cái ActionCreator của bạn có tham số tương tự như trên

        Số lượt thích

      3. hiện tại cái này đang được dùng cho dự án của mình ở cty. mình có public trên Github, nên bạn có thể sử dụng. Về khác biệt thì ban đầu mình muốn giới thiệu Redux + Rx, tận dụng sức mạnh của Rx cho phần phản hồi từ store cho view, còn chiều ngược lại thì khá giống với ReSwift.

        Liked by 1 person

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google photo

Bạn đang bình luận bằng tài khoản Google Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s