12/08/2018, 17:57

Tìm hiểu vuex trong vue

Vue hoạt động theo mô hình "Luồng dữ liệu một chiều" với các thành phần sau: State: Trạng thái, là nơi khởi nguồn để thực hiện ứng dụng. View: Khung nhìn, là các khai báo ánh xạ với trạng thái. Action: Hành động, là những cách thức làm trạng thái thay đổi phản ứng lại các nhập liệu của người ...

Vue hoạt động theo mô hình "Luồng dữ liệu một chiều" với các thành phần sau:

State: Trạng thái, là nơi khởi nguồn để thực hiện ứng dụng.

View: Khung nhìn, là các khai báo ánh xạ với trạng thái.

Action: Hành động, là những cách thức làm trạng thái thay đổi phản ứng lại các nhập liệu của người dùng từ View

Tuy nhiên, mô hình này bị phá vỡ khi chúng ta có rất nhiều các component cùng chia sẻ một trạng thái: Nhiều view cùng phụ thuộc vào một trạng thái nào đó. Các hành động từ các view khác nhau cần thay đổi cùng dữ liệu trạng thái. Vuex nhìn thấy tại sao không đưa các trạng thái được chia sẻ của các component ra và quản lý chúng trong một bộ máy toàn cục, và đó chính là lý do cho sự ra đời của Vuex. Trong đó, các component trở thành các view và các component có thể truy xuất trạng thái hoặc trigger các hành động. Với cách thức này, mã nguồn có cấu trúc và dễ dàng duy trì.

State

Vuex sử dụng một cây trạng thái duy nhất, đối tượng này sẽ chứa tất các trạng thái của ứng dụng, như vậy bạn chỉ có duy nhất một kho lưu trữ cho mỗi ứng dụng, điều này làm cho việc xác định các trạng thái là dễ dàng và cũng đơn giản trong việc tạo ra các ảnh chụp trạng thái (snapshot) của ứng dụng hiện tại. Khái niệm cây trạng thái duy nhất không làm mất đi tính module hóa, trong các phần tiếp theo chúng ta sẽ tìm hiểu cách chia nhỏ các trạng thái và thay đổi chúng trong các module con. Để sử dụng các trạng thái trong Vue component, chúng ta sẽ lấy các trạng thái và trả về trong thuộc tính computed của component:

// let's create a Counter component
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return store.state.count
    }
  }
}

Mỗi khi store.state.count thay đổi, thuộc tính computed sẽ được tính toán lại và một trigger được tạo ra cho các DOM liên quan. Trong ứng dụng thiết kế dạng module, cần import store ở những nơi component sử dụng trạng thái. Vuex cung cấp cơ chế giúp sử dụng store ở tất cả các component con từ component gốc với tùy chọn store:

const app = new Vue({
  el: '#app',
  // provide the store using the "store" option.
  // this will inject the store instance to all child components.
  store,
  components: { Counter },
  template: `
    <div class="app">
      <counter></counter>
    </div>
  `
})

Các component con có thể truy xuất store thông qua this.$$tore

const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

Getter

Đôi khi chúng ta cần lấy các trạng thái dựa vào việc tính toán, lọc bỏ các trạng thái được cung cấp bởi kho lưu trữ, ví dụ:

computed: {
  doneTodosCount () {
    return this.$store.state.todos.filter(todo => todo.done).length
  }
}

Nếu nhiều component cần làm điều này, chúng ta có thể định nghĩa getter trong store để thực hiện.

const store = new Vuex.Store({
  state: {
    todos: [
      { id: 1, text: '...', done: true },
      { id: 2, text: '...', done: false }
    ]
  },
  getters: {
    doneTodos: state => {
      return state.todos.filter(todo => todo.done)
    }
  }
})

Khi đó chúng ta có thể truy xuất các trạng thái đã được lọc này bằng cách sử dụng cú pháp state.getter.doneTodos.

Mutation

Trạng thái không thể thay đổi trực tiếp mà chỉ được thay đổi thông qua commit, Vuex mutation tương tự như các sự kiện, mỗi mutation có kiểu chuỗi và một handler. Handler function là nơi chúng ta thực hiện các thay đổi trạng thái và nó cần được truyền vào tham số đầu tiên là state.

const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // mutate state
      state.count++
    }
  }
})

Bạn không thể gọi trực tiếp một handler của mutation, cách thức gọi các handler này sẽ giống như việc đăng ký các sự kiện: “Khi mutation với dạng increment được trigger, gọi đến handler này”, cách thức này được thực bằng cách sử dụng store.commit

store.commit('increment')

Bạn có thể truyền thêm tham số cho các handler trong mutation:

// ...
mutations: {
  increment (state, n) {
    state.count += n
  }
}

Action

Action cũng tương tự như mutation, tuy nhiên có một vài điểm khác biệt

Thay vì thay đổi trạng thái, action commit các thay đổi. Action có thể chứa các hoạt động không đồng bộ. Chúng ta cùng xem ví dụ một action đơn giản như sau:

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})

Các handler của action nhận đầu vào là đối tượng context có các phương thức và thuộc tính giống với instance của store, do vậy chúng ta có thể gọi context.commit để commit một thay đổi hoặc truy xuất trạng thái và getter thông qua context.state và context.getter. Các action sẽ được trigger khi sử dụng phương thức store.dispatch

store.dispatch('increment')

Có vẻ như hơi rườm rà, nếu chúng ta muốn tăng trạng thái count , tại sao không gọi store.commit(‘increment’) trực tiếp? Chú ý rằng, mutation cần phải đồng bộ, nhưng với action thì không, chúng ta thực hiện các hoạt động không đồng bộ trong một action.

actions: {
  incrementAsync ({ commit }) {
    setTimeout(() => {
      commit('increment')
    }, 1000)
  }
}

Module

Vuex sử dụng cây trạng thái duy nhất, tất cả các trạng thái của ứng dụng được đưa vào một đối tượng, như vậy khi ứng dụng phát triển lên, store có thể phình lên rất nhiều. Vuex cho phép chia nhỏ store thành các module nhỏ hơn, mỗi module cũng có state, mutation, action, getter và thậm chí còn cho phép các module lồng nhau.

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> `moduleA`'s state
store.state.b // -> `moduleB`'s state

Chú ý, các mutation và getter, tham số đầu tiên sẽ là state cục bộ của module:

const moduleA = {
  state: { count: 0 },
  mutations: {
    increment (state) {
      // `state` is the local module state
      state.count++
    }
  },

  getters: {
    doubleCount (state) {
      return state.count * 2
    }
  }
}

state toàn cục sẽ được truyền vào tham số với tên là rootState

const moduleA = {
  // ...
  actions: {
    incrementIfOddOnRootSum ({ state, commit, rootState }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit('increment')
      }
    }
  }
}

Hi vọng các bạn hiểu thêm về vuex qua bài viết

0