Cơ bản về Vuex

Khái niệm

Vuex is a state management pattern + library for Vue.js applications. It serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion. It also integrates with Vue's official devtools extension to provide advanced features such as zero-config time-travel debugging and state snapshot export / import.

Đây là khái niệm của vuex trên trang chủ của nó, các bạn có thể tìm đọc tại đây. Hiểu đơn giản thì Vuex là một thư viện có chức năng là chia sẻ trạng thái giữa các components , Vuex là nơi lưu trữ tập trung cho tất cả các components trong ứng dụng.

Cấu trúc của vuex

Vuex hoạt động theo mô hình "one-way data flow"(luồng dữ liệu một chiều) với ba thành phần chính sau:

  • State: Trạng thái - là nơi khởi nguồn để thực hiện ứng dụng
  • View: Những gì được nhìn thấy, là các khai báo ánh xạ với trạng thái
  • Actions: Là cách mà trạng thái thay đổi phản ứng lại với các nhập liệu của người dùng từ view.

Tuy nhiên, với các hoạt động đơn giản trên thì chúng sẽ dễ dàng bị phá vỡ khi mà một components chia sẻ chung nhiều state

  • Nhiều view có thể phụ thuộc vào một trạng thái
  • Các actions đến từ nhiều views có thể biến đổi cùng một trạng thái, gây ra xung đột.

Để dễ hiểu hơn mình sẽ đưa ra các ví dụ để các bạn hiểu hơn về các trường hợp quản lý state. Nếu một app không sử dụng vuex mà với số lượng components lớn ở trong hệ thống của bạn, mà mỗi components lại có những trạng thái riêng biệt, thì mô hình ứng dụng của bạn sẽ như thế này.

Một vấn đề xảy ra khi mà một component thay đổi trạng thái của nó, mà một components nào đó xa xôi mà có "họ hàng" với component này cùng sử dụng chung 1 trạng thái, thì chúng ta sẽ cần phải "giao tiếp" 2 component đó lại với nhau. Với cách thông thường thì chúng ta sẽ dùng các event để truyền dữ liệu từ component con sang cha, và dùng các props để truyền từ cha xuống con, nghe rất là phức tạp đúng k. Đây là hình ảnh minh họa

Thì đây chính là lý do mà vuex được sinh ra để quản lý trạng thái trong ứng dụng. Thay vì mỗi component có trạng thái riêng thì chúng ta sẽ kết hợp chúng lại vào một nơi chứa tất cả các trạng thái của hệ thống. Nhưng mà nếu chỉ mỗi hợp nhất dữ liệu vào một nguồn cũng sẽ không hoàn toàn giải quyết được các vấn đề vì điều gì sẽ xảy ra khi mà nhiều components thay đổi trạng thái bằng nhiều cách và từ nhiều nơi, thì lúc đó chúng ta cũng cần có một cái cấu trúc "tiêu chuẩn" để kiểm soát các trạng thái. Ví dụ với trong vuejs

new Vue({
    data: {
    
    },
    methods: {
    
    },
    computed: {
    
    }
});

thi tương tự trong Vuex nó cũng có 1 standard(tiêu chuẩn) code riêng

new.Vuex.Store({
    state: {
    
    },
    mutations: {
    
    },
    actions: {
    
    },
    getters: {
    
    }
});

Như các bạn có thể thấy ở trên thì trong Vuex, state của ứng dụng được quản lý tập trung trong một chỗ gọi là store bao gồm những phần chính.

  • State: Trong vuejs chúng ta có thuộc tính data thì trong vuexstate
  • Actions: Cũng tương tự vuejs có method, thì ở đây chúng ta có actions để thao tác với state
  • Getters: Giống như vuejs có thuộc tính computedthì vuex cũng có getters - bộ lọc states
  • Mutations: Gần giống với watch theo dõi sự thay đổi thì ở đây, vuex cũng cung cấp mutations để chúng ta có thể track sự thay đổi của state.

Cài đặt

CDN

Sử dụng link trực tiếp thông qua CDN links, cái này sẽ luôn trỏ tới versions mới nhất

<script src="/path/to/vue.js"></script>
<script src="/path/to/vuex.js"></script>

NPM

npm install vuex --save

Yarn

yarn add vuex

Bây giờ mình sẽ thử cài vuex vào project vuejs thông qua nhé. Ở bài này mình cũng sử dụng luôn project mà mình đã tạo trong bài này, các bạn có thể xem qua phần cài đặt vuejs.
Tiếp theo mình chạy lệnh

npm install vuex --save

Để biết đã cài thành công vuex vào hay chưa các bạn vào project mở file package.json nếu thấy dòng như này là đã cày đặt thành công.

"dependencies": {
    "vue": "^2.5.2",
    "vuex": "^3.3.0"
  },

Bây giờ chúng ta cần tạo một kho lưu trữ tập trung của vuex. Trước tiên vào folder src tạo 1 folder có tên là store. Trong folder này các bạn tạo thêm một file store.js và thêm đoạn code sau.

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex) // khai báo vue sử dụng plugin vuex

export const store = new Vuex.Store({
  state: {

  },
  mutations: {

  },
  actions: {

  },
  getters: {

  }
});

Cơ bản là chúng ta đã tạo ra một kho lưu trữ tập trung. GIờ chúng ta cần khai báo cho cho project để sử dụng cái kho dữ liệu này các bạn vào main.js

import Vue from 'vue'
import App from './App'
import { store } from './store/store'

new Vue({
  el: '#app',
  store,
  components: { App },
  template: '<App/>'
})

Vậy là xong đơn giản phần cài đặt.

Các thành phần trong vuex

1. State

Để có thể hiểu state một cách dễ hiểu hơn mình sẽ làm theo ví dụ để các bạn có cái nhìn trực quan hơn. Trước tiên các bạn tạo thêm cho mình một component trong src/components đặt tên là Result.vue, và các bạn thêm đoạn code này vô.

`<template>
  <div class="result">
      <p></p>
  </div>
</template>

<script>
export default {
}
</script>

<style scoped>
</style>

và tạo thêm một component có tên là Counter.vue

<template>
  <div class="count">
      <button @click="increament">Increment</button>
      <button @click="decrement">Decrement</button>
  </div>
</template>

<script>
export default {
  methods: {
    increament () {
    },
    decrement () {
    }
  }
}
</script>

<style scoped>
</style>

và sửa code trong App.vue nữa nhé

<template>
  <div id="app">
    <app-counter />
    <app-result />
  </div>
</template>

<script>
  import AppCounter from './components/Counter'
  import AppResult from './components/Result'
export default {
  components: {
    AppCounter,
    AppResult
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>
  • Như mình nói ở trên, state giống với data trong component ở vuejs, chỉ khác là state lưu toàn bộ data cho các components
  • Đây là 1 object bên trong chứa các thông tin có dữ liệu như : string, arrays hay một object.
  • Các thông tin này được lưu trữ trong toàn ứng dụng.

Để khai báo một state rất đơn giản, trong store.js

export const store = new Vuex.Store({
  state: {
    result: 10
  },
  mutations: {
  },
  actions: {
  },
  getters: {
  }
});

Để sử dụng cái trạng thái result này trong Result.vue chúng ta sẽ gọi trong thuộc tính computed. Một lưu ý nhỏ cho các bạn là computed luôn chạy trước khi dữ liệu của chúng ta được load. Trong Result.vue

<template>
  <div class="result">
      <p>Kết quả là {{ result }}</p>
  </div>
</template>

<script>
export default {
  computed: {
    result () {
      return this.$store.state.result
    }
  }
}
</script>

<style scoped>
</style>

Cú pháp có phần hơi hơi lạ nhỉ, giải thích cho các bạn chút đó là this.$store chính là tương ứng với cái đoạn mà chính ta khai báo const store = new Vuex.Store, rồi để truy cập đến state thì đơn giản như các bạn truy cập vào object thôi. GIờ các bạn vào trong Counter.vue thêm đoạn code vào script

<script>
export default {
  methods: {
    increament () {
      this.$store.state.result++
    },
    decrement () {
      this.$store.state.result--
    }
  }
}
</script>

OK các bạn chạy npm run dev lên và vào http://localhost:8080/#/ xem kết quả nhé

Các bạn có thể dễ dàng thấy rằng, khi state ở componet Counter.vue thay đổi lập tức các component khác sẽ cũng nhận được sẽ thay đổi đến từ state đó. Rất nhanh đúng không nào.

2. Getters

  • Đây là một function
  • Nó không thể thay đổi được trạng thái nhưng có thể định dạng lại các thông tin trong state mà chúng ta cần
  • Giống với computed trong vue

Đôi khi chúng ta cần tính toán lại state trong nhiều components, chúng ta k thể với mỗi components đó lại tính toán lại, như thế khá là dài dòng, thì việc sử dụng getters trong trường hợp này là khá hợp lý.
Giá sử chúng ta vào store.js vừa tạo ở trên và thử tạo ra một getters

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex) // khai báo vue sử dụng plugin vuex

export const store = new Vuex.Store({
  state: {
    result: 10
  },
  mutations: {

  },
  actions: {

  },
  getters: {
    RESULT: state => {
      return state.result * 5
    }
  }
});

Ở đây chúng ta đặt tên function là RESULT, bạn không nhất thiết phải viết uppercase toàn bộ tên hàm, đây chỉ là một cách theo convention mà mình gợi ý cho các bạn. các bạn có thể đặt 1 tên nào đó tùy thích. Cạnh đó là parameter state (cái này đặt tên là gì cũng được nhưng mình đặt trùng với state cho dễ hiểu). Đặc biệt là cái parameter này chính là vào cái state ở trên. Nên việc truy cập vào các giá trị bên trong cũng như bình thường.
Để sử dụng getters thì cũng đơn giản như khi chúng ta gọi đến state

<script>
export default {
  computed: {
    result () {
      return this.$store.getters.RESULT
    }
  }
}
</script>

Hoặc là chúng ta cũng có thể sử dụng các hàm trong cùng getters

    getters: {
      doneTodos: state => {
          return state.todos.filter(todo => todo.done)
        },

      doneTodosCount: (state, getters) => {
        return getters.doneTodos.length
      }
    }

3. Mutations

  • Đây là một function
  • Chúng chỉ có chức năng là thay đổi trạng thái
  • Được gọi bằng actions
  • Cơ chế hoạt động là đồng bộ

Theo như document thì mutations là cách duy nhất để có thể thay đổi được state, mỗi mutations có một typehandler. Handler function đây là nơi để thay đổi state và nhận tham số đầu tiên là state. Ví dụ

state: {
    count: 1
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }

Chúng ta có thể thêm một tham số nữa và mutations, tham số này được gọi là payload. Ví dụ :

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

Và khi gọi đến mutations.

this.$store.commit('increment', 10)

Payload có thể là một đối tượng chứa nhiều trường.

mutations: {
  increment (state, payload) {
    state.count += payload.amount
  }
}

Khi gọi cũng đơn giản.

this.$store.commit('increment', {
  amount: 10
})

hay có 1 cách gọi khác là sử dụng thuộc tính type trong mutations

this.$store.commit({
  type: 'increment',
  amount: 10
})

4. Actions

  • Đây là function, chứa các business logic
  • Để thay đổi state phải commit mutations
  • Có thể gọi các actions thông qua dispatch
  • Có thể chứa cơ chế bất đồng bộ

Ví dụ luôn cho dễ hình dung

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

Để sử dụng actions ở một components khác thì

store.dispatch('increment')

Có một tham số mà mình truyền vào trong một action là context, đây là một đối tượng tập hợp các phương thức/ thuộc tính trong store, do đó chúng ta có thể gọi để context.commit để thực hiện một action, hay gọi đến context.state, context.getters. Để đơn giản hóa hơn thì trong ES5 chúng ta có thể gọi đến mutationsactions như sau

actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

Kết luận

Trên đây là những kiến thức mà mình nghĩ là hướng tới những người mới bắt đầu, nó có thể chưa được sâu nên các bạn có thể xem thêm ở trang chủ của nó https://vuex.vuejs.org/. Cảm ơn các bạn đã đọc.

All Rights Reserved