Ra mắt hai series mới cực hot Trí tuệ nhân tạo A đến ZPython công cụ không thể thiếu khi nghiên cứu Data science, Machine learning.

Vuex là gì

Vue.js là làm việc với các component, tuy nhiên từ trước đến giờ chúng ta chưa có cách nào giúp chia sẻ trạng thái giữa các component này, hoặc có nhưng mã nguồn rất khó duy trì đặc biệt với các dự án lớn. Vuex là thư viện giúp quản lý trạng thái các component trong Vue.js, nó là nơi lưu trữ tập trung cho tất cả các component trong một ứng dụng, với nguyên tắc trạng thái chỉ có thể được thay đổi theo kiểu có thể dự đoán.

Vuex là gì?

  • Chúng ta cùng nhau phân tích xem, tại sao Vuex ra đời, đơn giản nhất là tìm hiểu mô hình "Luồng dữ liệu một chiều" thường được sử dụng trong các ứng dụng đơn thuần trước đây. Trong một ứng dụng khép kín có các thành phần như 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.

Hình dưới đây mô tả một cách đơn giản nhất cho khái niệm “Luồng dữ liệu một chiều”.

Mô hình tương tác thông thường

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ì.

mô hình vuex

Bắt đầu với Vuex

Trung tâm của mọi ứng dụng Vuex là kho chứa (store), một kho chứa đơn giản là nơi khai báo, lưu trữ các trạng thái ứng dụng. Có hai sự khác biệt giữa kho chứa Vuex và đối tượng toàn cục thông thường:

  • Kho chứa Vuex có thể tác động lại các Vue component lấy trạng thái từ đó, trạng thái sẽ được cập nhật khi có thay đổi một cách hiệu quả.
  • Các trạng thái trong kho chứa không thể thay đổi trực tiếp, chỉ có một cách duy nhất để thay đổi trạng thái là phải commit.

Ví dụ một kho chứa đơn giản:

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

Khi đó bạn có thể truy xuất trạng thái đối tượng thông qua store.state và khi trạng thái thay đổi thông qua commit bằng phương thức store.commit, một trigger sẽ được tạo ra.

store.commit('increment')

console.log(store.state.count) // -> 1

Các khái niệm cơ bản trong Vuex

State - trạng thái

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:

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.$store

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

Getter - bộ lọc trạng thái

Đô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.

Mutations - thay đổi trạng thái

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
  }
}
store.commit('increment', 10)

Action - hành động

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 = {
  // ...
  getters: {
    sumWithRootCount (state, getters, rootState) {
      return state.count + rootState.count
    }
  }
}

Thực hành với Vuex

Trên đây, chúng ta đã biết Vuex là gì? và các khái niệm cơ bản trong Vuex, với những người mới tìm hiểu về Vuex có thể sẽ khó nắm được ngay các vấn đề trong Vuex, đặc biệt nếu bạn chưa biết Vue.js là gì thì bạn nên tham khảo kiến thức về Vue.js trước khi tìm hiểu thư viện Vuex. Chúng ta cùng thực hành một ví dụ, xây dựng hệ thống chat online trên website đơn giản, ứng dụng có hai instance của component chat, khi người dùng đưa một tin nhắn vào một instance, nó sẽ hiển thị trên tất cả các cửa sổ chat bởi vì trạng thái tin nhắn đã được chia sẻ từ hai instance này.


CÁC BÀI VIẾT KHÁC

FirebirD

Đam mê Toán học, Lập trình. Sở thích chia sẻ kiến thức, Phim hài, Bóng đá, Cà phê sáng với bạn bè.

Sử dụng Vue-router cho các ứng dụng đơn trang

Yarn là gì? tại sao Yarn đang thay thế dần npm

7 Bình luận trong "Vuex là gì"

  1. binler

    3 years ago

    Phản hồi
    Ad có thể phân biệt rõ giữa Actions và Mutation cho mình biết được không? Vẫn chưa hiểu rõ lắm. Khi nào nên dùng Actions hoặc Mutations
    1. FirebirD

      3 years ago

      Phản hồi
      Chào bạn, khi sử dụng vuex có thể bạn thấy actions hình như hơi thừa, mình cũng vậy khi mới làm quen với Vuex vì các sự kiện có thể gọi trực tiếp luôn các mutations thì cần thông qua actions làm gì? Nhưng không phải vậy đâu, giữa actions và mutations có một số khác biệt như sau: 1. Actions là hoạt động bất đồng bộ (asynchronous) còn mutations thì là hoạt động đồng bộ (synchronous). Do Vuex học tập thiết kế luồng dữ liệu một chiều (one way data flow) từ các hệ thống như Flux, Redux ... do đó mutations phải là các hoạt động đồng bộ và một chiều trong thay đổi dữ liệu state. (Bạn xem thêm bài viết Tại sao dùng Vuex để hiểu hơn về kiến trúc này nhé). Nếu ứng dụng của bạn hoạt động đồng bộ thì bạn không cần đến các actions. 2. Mutations chỉ tập trung xử lý liên quan đến state, còn actions liên quan đến các xử lý nghiệp vụ. Một action có thể gọi đến nhiều mutations.
      1. Trường Nguyễn

        3 years ago

        Phản hồi
        Mình chưa hiểu lắm về đồng bộ và bất đồng bộ, theo như mình biết thì Javascript là xử lý đơn luồng và đồng bộ, tức là xử lý tuần tự mà? Bạn có thể cho mình một ví dụ thực tế về sử dụng bất đồng bộ trong ứng dụng không?
  2. binler

    3 years ago

    Phản hồi
    Góp ý với ad tý. hihihi. Nên làm 1 trang riêng về vuejs vs những thứ liên quan như nuxtjs, ..
    1. FirebirD

      3 years ago

      Phản hồi
      Thanks binler, sức người có hạn, team bên mình còn nhỏ chưa thể cover được hết nên tạm thời ở chung nhà :)
  3. Bình

    2 years ago

    Phản hồi
    Quá hay, bác viết thêm nhiều nữa đi. Không biết bác có trong group fb vuejs vietnam không nhỉ
  4. capu

    2 years ago

    Phản hồi
    actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) } } Thank bạn về bài viết. Bạn có thể giải thích thêm về việc truyền {commit} trong action incrementAsync ở trên không ?

Thêm bình luận