1. What is VueX
Vuex is a state management pattern + library developed specifically for Vue.js applications . It uses centralized storage to manage the state of all components of the application, and uses corresponding rules to ensure that the state changes in a predictable way.
2. Why use VueX
A simple illustration of the "one-way data flow" concept:
The simplicity of one-way data flow can easily be broken when our application encounters multiple components sharing state :
- Multiple views depend on the same state.
- Actions from different views require changing the same state.
Regarding question 1, the method of passing parameters will be very cumbersome for multi-layer nested components, and it is powerless for state transfer between sibling components. For question two, we often use parent-child components to directly reference or use events to change and synchronize multiple copies of the state. These patterns are very fragile and often lead to unmaintainable code.
Therefore, why don't we extract the shared state of the component and manage it in a global singleton mode? In this mode, our component tree forms a huge "view", and any component can obtain status or trigger behavior no matter where it is in the tree!
By defining and isolating the various concepts in state management and maintaining the independence between views and state by enforcing rules, our code will become more structured and maintainable.
VueX was born from this:
3. Five major modules of VueX
(1) State: single state tree
Vuex uses a single state tree - yes, a single object that contains all application-level state. At this point it exists as a "single source of data ( SSOT )". This also means that each application will only contain one store instance. A single state tree allows us to directly locate any specific piece of state and easily obtain a snapshot of the entire current application state during debugging.
To put it simply, all states in the project are placed in State.
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
// 全局个人数据
userInfo:{},
isLogin:true,
loading:false,
tabActive:0
},
mutations: {
},
actions: {
},
modules: {
}
})
How to get it when using it on other pages
1. Get the value directly from the store instance
In main.js, register the store under the root instance
import store from './store'
(1) In other page js, you can use this.$stroe.state. attribute to directly obtain the value.
export default {
computed: {
testNum() {
return this.$store.state.testNum;
}
}
};
(2) Write the $stroe.state. attribute in the html of other pages.
<div class="box" v-id="$store.state.isLogin">
<MySelfHeader></MySelfHeader>
<MySelfBody></MySelfBody>
</div>
2.mapState auxiliary function
When a component needs to obtain multiple states, declaring these states as computed properties will be somewhat repetitive and redundant. To solve this problem, we can use mapState
helper functions to help us generate computed properties.
First introduce import {mapState} from "vuex";
// 在单独构建的版本中辅助函数为 Vuex.mapState
import { mapState } from 'vuex'
export default {
// ...
computed: mapState({
// 箭头函数可使代码更简练
count: state => state.count,
// 传字符串参数 'count' 等同于 `state => state.count`
countAlias: 'count',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
countPlusLocalState (state) {
return state.count + this.localCount
}
})
}
When the name of the mapped calculated attribute is the same as the name of the child node of the state, we can also pass mapState
a string array.
computed: mapState([
// 映射 this.count 为 store.state.count
'count'
])
(2) Getter
Sometimes we need to derive some state from the state in the store, such as filtering and counting the list:
computed: {
doneTodosCount () {
return this.$store.state.todos.filter(todo => todo.done).length
}
}
Vuex allows us to define "getters" in the store (which can be thought of as computed properties of the store).
Getter accepts state as its first parameter:
const store = createStore({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos (state) {
return state.todos.filter(todo => todo.done)
}
}
})
How to get it on other pages:
In Js: this.$store.getters.method/property
In html: $store.getters.method/property
(3) Mutation
The only way to change the state in the Vuex store is to submit a mutation . Mutations in Vuex are very similar to events: each mutation has a string event type (type) and a callback function (handler) . This callback function is where we actually make the state changes, and it accepts state as the first parameter:
const store = createStore({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
You cannot call a mutation handler directly. This option is more like an event registration: "When a increment
mutation of type is triggered, call this function." To wake up a mutation handler, you need to call the store.commit method with the corresponding type:
store.commit('increment')
Submit payload
You can store.commit
pass in additional parameters, the payload of the mutation :
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
Note: Mutation must be a synchronous function, and asynchronous operations cannot be performed in Mutation.
***Submit Mutation in component***
You can use commit mutations in components this.$store.commit('xxx')
, or use mapMutations
helper functions to map methods in components to store.commit
calls (need to be injected at the root node store
).
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
Important: Methods in Mutation must be called through commit
(4) Action
In some cases, we want to perform some asynchronous operations in vuex, such as network requests, which must be asynchronous, so we need to use actions at this time.
Action is similar to mutation, except that:
- Action submits a mutation rather than directly changing the state.
- Action can contain any asynchronous operation.
const store = createStore({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
The Action function accepts a context object with the same methods and properties as the store instance , so you can call context.commit
Submit a mutation, or obtain state and getters through context.state
and .context.getters
DistributeAction
Action store.dispatch
is triggered via methods:
store.dispatch('increment')
At first glance, it seems unnecessary. Wouldn't it be more convenient for us to distribute mutations directly? In fact, this is not the case. Remember the restriction that mutations must be executed synchronously ? Action is not restricted! We can perform asynchronous operations inside the action :
actions: {
incrementAsync ({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
}
}
Actions supports the same payload method and object method for distribution:
// 以载荷形式分发
store.dispatch('incrementAsync', {
amount: 10
})
// 以对象形式分发
store.dispatch({
type: 'incrementAsync',
amount: 10
})
****Distributing Actions in Components***
this.$store.dispatch('xxx')
You use dispatch actions in the component , or use mapActions
auxiliary functions to map the component's methods to store.dispatch
calls (you need to inject them in the root node first store
):
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
(5) Module
Due to the use of a single state tree, all the state of the application will be concentrated into a relatively large object. When an application becomes very complex, store objects have the potential to become quite bloated.
In order to solve the above problems, Vuex allows us to split the store into modules . Each module has its own state, mutation, action, getter, and even nested submodules - divided in the same way from top to bottom:
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = createStore({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
4. VueX source code
let Vue;
//定义一个Store类
class Store {
constructor () {
this._vm = new Vue({
data: {
$$state: this.state
}
})
}
commit (type, payload, _options) {
const entry = this._mutations[type];
entry.forEach(function commitIterator (handler) {
handler(payload);
});
}
dispatch (type, payload) {
const entry = this._actions[type];
return entry.length > 1
? Promise.all(entry.map(handler => handler(payload)))
: entry[0](payload);
}
}
//保证 vm 中都可以访问 store 对象
function vuexInit () {
const options = this.$options;
if (options.store) {
this.$store = options.store;
} else {
this.$store = options.parent.$store;
}
}
//Vue.js 提供了一个 Vue.use 的方法来安装插件,内部会调用插件提供的 install 方法。所以我们的插件需要提供一个 install 方法来安装。
export default install (_Vue) {
Vue.mixin({ beforeCreate: vuexInit });
Vue = _Vue;
}
For more details, please jump to the official website——