+4

Middleware trong Redux

Trong bài viết ngày hôm nay, tôi sẽ trinh bày với các bạn về một vấn đề trong việc lập trình web với Redux, đó là về Middleware và tác dụng cũng như cách sử dụng chúng trong việc phát triển web phần backend.

Middleware

Middleware là thành phần phần mềm hoặc các ứng dụng với nhau. Nó bao gồm tập các dịch vụ cho phép tương tác giữa các tiến trình thực hiện trên một hoặc nhiều máy tính với nhau. Công nghệ middleware đã được phát triển để cung cấp khả năng hoạt động tương hỗ, phục vụ cho các kiến trúc phân tán thường được để hỗ trợ và đơn giản hóa các ứng dụng phân tán phức tạp.

Middleware nằm ở giữa các ứng dụng phần mềm chạy trên các hệ điều hành khác nhau. Nó tương tự với tầng giữa của một kiến trúc hệ thống đơn 3 tầng, chỉ khác ở chỗ nó trải rộng qua các hệ thống và ứng dụng khác nhau. Ví dụ là các phần mềm EAI, phần mềm truyền thông, Transaction Processing System, và các phần mềm thông điệp-và-hàng đợi.

Middleware trong Web

Với tư tưởng chung là cầu nối giữa tương tác của người dùng và phần nhân của hệ thống, trong lập trình Web, Middleware sẽ đóng vai trò trung gian giữa request/response (tương tác với người dùng) và các xử lý logic bên trong web server.

Do đó, Middleware trong các Framework lập tình Web (Django, Rails, React), sẽ là các hàm được dùng để tiền xử lý, lọc các request trước khi đưa vào xử lý logic hoặc điều chỉnh các response trước khi gửi về cho người dùng.

Trong bài viết ngày hôm nay, chúng ta sẽ cùng tìm hiểu cách Redux sử dụng middleware để xử lý các truy cập từ phía người sử dụng.

Middleware trong Redux

về cơ bản, middleware sẽ kiểm soát các action thực hiện, chỉnh sửa Redux middleware giải quyết được những vấn đề khác với Express or Koa middleware, nhưng theo cách thức khái niệm tương tự. Nó cung cấp điểm mở rộng của bên thứ ba giữa việc gửi một hành động, và thời điểm nó đạt đến reducer. Developer sử dụng middleware Redux để ghi logging, crash reporting, talking to an asynchronous API, routing, and more.

Logging

Một trong những lợi ích của Redux là nó làm thay đổi trạng thái dự đoán và minh bạch. Mỗi lần một hành động được gửi đi, state mới được tính toán và lưu lại. State không thể tự thay đổi, nó chỉ có thể thay đổi như là kết quả của một hành động cụ thể.

Tiếp cận với Redux:

Phương pháp 1: Logging Manually

  • Giải pháp đơn giản nhất là ghi lại hành động và trạng thái tiếp theo mỗi khi gọi store.dispatch(action)
let action = addTodo('Use Redux')

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

Điều này tạo ra hiệu quả mong muốn, nhưng bạn sẽ không muốn làm điều đó mỗi lần. Nó không thực sự là một giải pháp, nhưng là bước đầu tiên để hiểu vấn đề.

Phương pháp 2: Wrapping Dispatch

  • Đóng gói thành thủ tục:
function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
}
  • sau đó gọi lại bất kỳ nơi nào cần thiết
dispatchAndLog(store, addTodo('Use Redux'))

Đây là một giải pháp tốt, tuy nhiên không phải lúc nào cũng thuận tiện để gọi một hàm đặc biệt

Phương pháp 3: Monkeypatching Dispatch

Đôi khi chúng ta cần thay thế hàm dispatch trong store instance. Redux storelà một đối tượng đơn giản, nên chúng ta có thể sử dụng monkey patch [1]

let next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

Đây là điều mà sẽ làm chúng ta gần hơn với những gì chúng ta muốn. Dù chúng ta thực hiện hành động nào , thì vẫn sẽ đảm bảo việc logging

Crash Reporting

Ở một số trình duyệt cũ, sự kiện window.onerror không đáng tin cậy, vì nó không cung cấp đầy đủ thông tin, vậy làm thế nào để hiểu chuyện gì đã xảy ra. Chúng ta có thể tham khảo một phương pháp sau:

function patchStoreToAddLogging(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

function patchStoreToAddCrashReporting(store) {
  let next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action)
    } catch (err) {
      console.error('Caught an exception!', err)
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      })
      throw err
    }
  }
}

Các modul này được xây dựng từng chức năng riêng biệt, sau đó chúng ta có thể sử dụng chúng:

patchStoreToAddLogging(store)
patchStoreToAddCrashReporting(store)

Tuy nhiên điều này chưa hẳn là tốt, chúng ta cùng tham khảo thêm phương pháp Hiding Monkeypatching cải thiện vấn đề này như nào

Phương pháp 4: Hiding Monkeypatching

  • Replace any method you like Trước đây chúng ta thay thế store.dispatch và điều gì sẽ xảy ra khi trả lại dispatch ?
function logger(store) {
  let next = store.dispatch

  // Previously:
  // store.dispatch = function dispatchAndLog(action) {

  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

Chúng ta có thể cung cấp một helper bên trong Redux mà có thể áp dụng việc thực hiện monkeypatching như một chi tiết thực hiện:

function applyMiddlewareByMonkeypatching(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  // Transform dispatch function with each middleware.
  middlewares.forEach(middleware =>
    store.dispatch = middleware(store)
  )
}

Chúng ta có thể sử dụng nó để áp dụng nhiều middleware như sau:

applyMiddlewareByMonkeypatching(store, [logger, crashReporter])

Tuy nhiên, nó vẫn còn monkeypatching. Thực tế, giấu nó trong thư viện không làm thay đổi thực tế này.

Phương pháp 5: Removing Monkeypatching

Khi ghi đè nên dispatch sẽ làm các middleware khác có thể truy cập và gọi

function logger(store) {
  // Must point to the function returned by the previous middleware:
  let next = store.dispatch

  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

Nó là cần thiết để các trói lại các middleware Nếu applyMiddlewareByMonkeypatching không chỉ định store.dispatch ngay sau khi xử lý phần mềm trung gian đầu tiên,store.dispatch sẽ tiếp tục trỏ đến dispatch chức năng ban đầu . Sau đó, trung gian thứ hai cũng sẽ được ràng buộc với dispatchchức năng ban đầu .

Nhưng cũng có một cách khác để cho phép kết nối. Các middleware có thể chấp nhận next() chức năng gửi như là một tham số thay vì đọc nó từ store.

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}

Phương pháp 6

Thay vì applyMiddlewareByMonkeypatching(), chúng ta có thể viết applyMiddleware()

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()
  let dispatch = store.dispatch
  middlewares.forEach(middleware =>
    dispatch = middleware(store)(dispatch)
  )
  return Object.assign({}, store, { dispatch })
}

Việc thực hiện các applyMiddleware là tương tự với Redux, tuy nhiên nó khác nhau vài ba khía cạnh quan trọng:

  • Nó chỉ đưa ra một tập con của Store API với phần mềm trung gian: dispatch(action), getState().
  • Nó không phải là một thủ thuật để đảm bảo rằng nếu bạn gọi store.dispatch(action) từ trung gian của bạn thay vìnext(action), hành động sẽ thực sự đi lại toàn bộ chuỗi trung gian, bao gồm cả phần mềm trung gian hiện tại. Điều này hữu ích cho các phần mềm trung gian không đồng bộ, như chúng ta đã thấy trước đây .
  • Để đảm bảo rằng bạn chỉ có thể áp dụng phần mềm trung gian một lần, nó hoạt động trên createStore() chứ không phải là về store của chính nó. Thay vào đó (store, middlewares) => store

Áp dụng với store trong Redux

import { createStore, combineReducers, applyMiddleware } from 'redux'

let todoApp = combineReducers(reducers)
let store = createStore(
  todoApp,
  // applyMiddleware() tells createStore() how to handle middleware
  applyMiddleware(logger, crashReporter)
)

Bây giờ bất kỳ hành động gửi đến store sẽ chạy qua logger và crashReporter:

// Will flow through both logger and crashReporter middleware!
store.dispatch(addTodo('Use Redux'))

Tổng kết

Bài viết trên giới thiệu một số khái niệm về Middleware, một vài ví dụ cho thấy sức mạnh của nó. Hi vọng nó sẽ hữu ích với các bạn. Chi tiết bạn có thể xem tại (Middleware)


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí