Quản lý trạng thái tải của side effect trong NuxtJS

Khái niệm side effect nghĩa là nó nói tới một xử lý hành động về bất đồng bộ (async) nào đó, ứng với một số framework thì nó có các khái niệm:

  1. ReactJS: Khi sử dụng reduxredux-thunk trong ứng dụng thì side effect mình sử dụng bên trong action
  2.  redux-saga thì side effect mình sử dụng trong các generator function
  3. ReactJS: Với kiến trúc DvaJS thì nó khai báo bên trong thuộc tính effects của model
  4. VueJS: Còn khi mình dùng NuxtJS thì nó được thực thi bên trong các actions tương ứng của store
  5. Angular: nếu bạn dùng package @ngrx/store thì nó lấy cảm hứng từ Redux bên ReactJS nên side effect nó cũng nằm ở actions. Trong trường hợp bạn không dùng @ngrx/store thì đa số là request api được viết trực tiếp bên trong service của angular

Điểm khác biệt giữa reducereffects bên ReactJS (actionsmutations bên Vuex) đó là có sử dụng side effect hay không (có xảy ra hành động bất đồng bộ nào hay không)

khi làm việc với kiến trúc DvaJS chúng ta có khái niệm effects trong model, cái mà mình nói theo ý của mình đó là những thao tác liên quan tới side effect, thì bên này nó có một cái hay là cung cấp cho chúng ta thêm một plugin là loading để theo dõi được các effect được gọi tới, và đã thực hiện effect xong chưa. Ví dụ: khi gọi tới effect là login của model user, chúng ta hoàn toàn có thể quan sát được trạng thái đang gọi effect và hiện loading đang xử lý, và sau khi effect hoàn thành thì tắt loading đi.

Để quản lý state trong NuxtJS, chúng ta dùng vuex, vuex là gì cách dùng sao các bạn tham khảo vuexbên NuxtJS thì việc xử lý các side effect nó nằm ở actions

Chúng ta xét thử một cấu trúc state đơn giản sau: auth.js

import Cookies from 'js-cookie'

export const state = () => {
  return {
    auth: null,
    token: null
  }
}

export const mutations = {
  SET_AUTH(state, auth) {
    state.auth = auth
    Cookies.set('auth', auth)
  },
  SET_TOKEN(state, token) {
    state.token = token
    Cookies.set('token', token)
  },
  RESET_AUTH(state) {
    state.token = null
    state.auth = null
    Cookies.remove('token')
    Cookies.remove('auth')
  }
}

export const actions = {
  reset({ commit }) {
    commit('RESET_AUTH')
  },
  async login({ commit }, params) {
    const response = await this.$authApi.post('/auth/login', params)
    if (response && response.data) {
      const {
        data: { profile, token }
      } = response
      if (token) {
        commit('SET_TOKEN', token)
        commit('SET_AUTH', profile)
        this.app.router.push('/')
      }
    }
    return response
  },

  logout({ commit }) {
    commit('RESET_AUTH')
    this.app.router.push('/')
  }
}

Cấu trúc trên mình có khai báo state, mutations và actions. Việc tương tác từ component vue thì chúng ta thực hiện store.dispatch từ component. Khi bấm vào nút login chúng ta gọi:

store.dispatch('auth/login', {email, password})

dispatch tới action login của state auth và gởi theo tham số email, password

Vậy tình huống đưa ra là làm sao chúng ta quản lý được, ứng với side effect nào thì tiến trình của nó đang ở đâu, bắt đầu hay xong chưa, để hiện loading tương ứng, thông thường thì các bạn sẽ tạo ứng tới mổi state sẽ có thuộc tính là loading:

export const state = () => {
  return {
    auth: null,
    token: null,
    loading: false
  }
}

và khi gọi tới action nào thì chúng ta gọi thêm một mutation tương ứng là cập nhật loading = true | false, cấu trúc của state auth sẽ được thay đổi:

import Cookies from 'js-cookie'

export const state = () => {
  return {
    auth: null,
    token: null,
    loading: false
  }
}

export const mutations = {
  SET_AUTH(state, auth) {
    state.auth = auth
    Cookies.set('auth', auth)
  },
  SET_TOKEN(state, token) {
    state.token = token
    Cookies.set('token', token)
  },
  RESET_AUTH(state) {
    state.token = null
    state.auth = null
    Cookies.remove('token')
    Cookies.remove('auth')
  },
  SET_LOADING(state, edit) {
    state.loading = edit
  }
}

export const getters = {
  isLoggedIn(state) {
    return state.auth !== null
  },
  token(state) {
    return state.token
  },
  auth(state) {
    return state.auth
  }
}

export const actions = {
  reset({ commit }) {
    commit('RESET_AUTH')
  },
  async login({ commit }, params) {
    commit('SET_LOADING', true)
    const response = await this.$authApi.post('/auth/login', params)
    commit('SET_LOADING', false)
    if (response && response.data) {
      const {
        data: { profile, token }
      } = response
      if (token) {
        commit('SET_TOKEN', token)
        commit('SET_AUTH', profile)
        this.app.router.push('/')
      }
    }
    return response
  },

  logout({ commit }) {
    commit('RESET_AUTH')
    this.app.router.push('/')
  }
}

Trước và sau khi thực hiện một side effect ta cập nhật lại trạng thái của  loading = true | false. và bên ngoài component ta theo dõi loading của từng state tương ứng và xử lý thao tác tương ứng, cách này chạy được, nhưng không hay, vì nó khá thủ công, bạn phải đi tạo loading ở tất cả các state và sẽ có những đoạn code lặp lại

Nếu bên ReactJS với kiến trúc DvaJS, cung cấp cho ta một global model là dva-loading để quản lý được trạng thái của tất cả effects của các model khác trong ứng dụng ReactJS, thì bên NuxtJS chúng ta làm bằng cách nào. Đó là chúng ta sử dụng một package vue-wait (vue plugin for global loading management), hiện tại mình đang thấy có 2 phiên bản cho VueJSReactJS (nếu bạn dùng ReactJS mà ko dùng kiến trúc DvaJS thì có thể áp dụng package này để quản lý trạng thái tải của ứng dụng). Thư viện này hổ trợ khá nhiều cho chúng ta là việc với ReactJS, VueJS hoặc code javascript thuần cũng được :), quá tiện

Đầu tiên là chúng ta cài đặt package vue-wait:

yarn add vue-wait

vue-wait cho chúng ta cấu hình khi dùng chung với nuxtJS, bạn mã trên vào nuxt.config.js

{
  modules: [
    // Simple usage
    'vue-wait/nuxt'

    // Optionally passing options in module configuration
    ['vue-wait/nuxt', { useVuex: true }]
  ],

  // Optionally passing options in module top level configuration
  wait: { useVuex: true }
}

vue-wait cung cấp cho chúng ta nhiều cách thức để quản lý được tải của state. Ở đây, mình chỉ quan tâm tới

  1. global template helpers là
    .is(loaders Array<String | Matcher>) or .waiting(loaders Array<String | Matcher>)

    helper này trả về kiểu là boolean nếu loaders đã cho tồn tại trong trang, giúp chúng ta kiểm tra được là action nào đang được tải trong trang (nghĩa là is(‘auth/login’) trả về true khi ta gọi dispatch(‘wait/start’, ‘auth/login’, { root: true}), ngược lại khi không tồn tại action auth/login hoặc gọi dispatch(‘wait/end’, ‘auth/login’, { root: true}) thì sẽ nhận được giá trị là false)

  2. action kèm theo để quản lý được trạng thái bắt đầu và kết thúc của 1 action
    dispatch('wait/start', actionName, { root: true }); dispatch('wait/end', actionName, { root: true });

    Với 2 action wait/start và wait/end giúp chúng ta  thiết lập được trạng thái bắt đầu và kết thúc của 1 action. Việc này cũng giống như khi ta thiết lập lập trạng thái loading = true | false của từng model tương ứng

    Để xem thêm nhiều chức năng mà vue-wait cung cấp, các bạn tham khảo thêm ở trang chủ vue-wait

Vậy khi áp dụng vue-wait, thì đoạn mã auth.js được viết lại như sau:

export const actions = {
  reset({ commit }) {
    commit('RESET_AUTH')
  },
  async login({ commit, dispatch }, params) {
    dispatch('wait/start', 'auth/login', true)
    const response = await this.$authApi.post('/auth/login', params)
    dispatch('wait/end', 'auth/login', false)
    if (response &amp;&amp; response.data) {
      const {
        data: { profile, token }
      } = response
      if (token) {
        commit('SET_TOKEN', token)
        commit('SET_AUTH', profile)
        this.app.router.push('/')
      }
    }
    return response
  },

  logout({ commit }) {
    commit('RESET_AUTH')
    this.app.router.push('/')
  }
}

và bên page login ta thiết lập tải bằng theo dõi

<v-btn :loading="$wait.is('auth/login')" depressed @click="submit">
  Đăng NHẬP
</v-btn>

Khi ta gọi tới action với name là auth/login thì sẽ hiện tại đang tải ở nút “Đăng nhập”

Khi sử dụng thì ở view đã ổn thì ta có thể theo dõi được trạng thái tải của bất kỳ model nào rồi, còn bên trong action của mổi model thì thiết lập mã vẫn còn thủ công. Dựa vào những action mà vue-wait cung cấp, mình có cập nhật lại một tí là build một hàm chức năng toàn cục để có thể gọi trong component hoặc action

  1. Tạo một hàm chức năng callWithWaiter với đầu vào là actionName và payload
    export const callWithWaiter = dispatch => async (actionName, payload) => {   dispatch('wait/start', actionName, { root: true })   const response = await dispatch(actionName, payload)   dispatch('wait/end', actionName, { root: true })   return (response && response.data) || null }

    khi gọi hàm thì nó cập nhật lại trạng thái tải của action tương ứng và trả về giá trị cần thiết

  2. Thiết lập hàm toàn cục để có thể gọi ở trong component hoặc actions, bằng cách sử dụng plugin trong NuxtJS
    import rules from '@/configs/rules' import { callWithWaiter } from '@/helpers/ultils' export default ({ app: { store } }, inject) => {   inject('actionWatcher', callWithWaiter(store.dispatch))   inject('rules', rules) }

    Đăng ký global plugin vào trong nuxt.config.js

    plugins: [     {       src: '~plugins/vue-carousel',       ssr: false     },     {       src: '~plugins/global'     },     {       src: '~plugins/axios',       ssr: false     },     {       src: '~plugins/helpers'     },     {       src: '~plugins/filters'     }   ],

    và cập nhật lại mã trong trang đăng nhập của ứng dụng, bằng cách gọi tới hàm toàn cục $actionWatcher

    <script> export default {   middleware: 'notAuthenticated',   layout: 'default',   components: {},   data: () => ({     email: '',     password: ''   }),   methods: {     submit(event) {       if (this.$refs.form.validate()) {         this.$actionWatcher('auth/login', {           email: this.email,           password: this.password         })       }     }   } } </script>

    Vậy với việc sử dụng package vue-wait và thêm một hàm chức năng nhỏ, chúng ta hoàn toàn quản lý được việc thay đổi trạng thái loading của ứng dụng NuxtJS

5/5 - (1 vote)

You May Also Like

About the Author: truongluu

1 Comment

Leave a Reply

Your email address will not be published. Required fields are marked *