z@[toc]
overview
Vuex is a state management pattern + library developed specifically for Vue.js applications. It uses a 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 manner.
How to share data between components
- Passing value from parent to child: v-bind attribute binding
- Passing value from child to parent: v-on event binding
- Sharing data between sibling components: EventBus
- $on component that receives data
- $emit component that sends data
imagine a scene
If there are many pages (components/views) in your project, and there are multi-level nesting relationships between pages, at this time, if these pages all need to share a state, the following two problems will arise at this time:
- Multiple views depend on the same state
- Actions from different views need to mutate the same state
Move your little head and you will think of solutions to the above methods:
- For the first problem, if it is a multi-level nested relationship, you can use the parent-child component to pass parameters to solve it. Although it is a bit troublesome, it can be solved; it is difficult for sibling components or components with more complex relationships , although it can be solved by various methods, it is really not elegant, and when the project grows bigger, the code will become a mountain of shit, which is really annoying.
- For the second problem, you can directly reference through parent-child components, or change or synchronize multiple copies of the state through events. This mode is very fragile and often makes the code difficult to maintain, and it also turns the code into a mountain of shit.
At this point, now that I have thought about this, what if I change my mind:
- Extract the same state that each component needs to depend on, and use the singleton mode to manage it globally.
- In this mode, any component can directly access this state, or when the state changes, all components get updated.
At this time, Vuex was born!
This is the basic idea behind Vuex, borrowed from Flux and Redux. Different from other modes, Vuex is a state management library specially designed for Vue to use Vue.js' fine-grained data response mechanism for efficient state updates.
The vuex usage cycle diagram of the official website
Install
npm install vuex --save
use
import
import Vuex from 'vuex'
Vue.use(Vuex)
create store object
const store = new Vuex.Store({
// state中存放的就是全局共享数据
state:{
count: 0
}
})
Mount the store object
new Vue({
el: '#app',
render: h=>h(app)m
router,
//将创建的共享数据对象,挂载到Vue实例中
//所有的组件,就可以直接从store中获取全局的数据了
store
})
Create a vue project with vuex
- Open the terminal and enter the command: vue ui
- When the project dashboard is opened, we click the project management drop-down list in the upper left corner of the page, and then click Vue project manager
- Click to create project
- Set project name and package manager
- Set up manual configuration items
- Set function items
- create project
Project code formatting:
Create a .prettierrc file in the project root directory (same level as src), and write the code as follows:
{
"semi":false,
"singleQuote":true
}
Core idea
State
The concept
State is to provide the only public data source, and all shared data must be stored in the State of `Store.
If state information is stored in multiple Store objects, subsequent management and maintenance will become particularly difficult, so Vuex also uses a single state tree (Single Source of Truth) to manage all states at the application level .
A single state tree allows us to find a fragment of a certain state in the most direct way, and it can also be managed and maintained very conveniently in the subsequent maintenance and debugging process.
export default new Vuex.Store({
state: {
count: 0
}
}
State data access method 1
Access through this.$store.state.global data name, eg.
<h3>当前最新Count值为:{
{this.$store.state.count}}</h3>
State data access method 2
Import mapState function on demand from vuex
import {
mapState } from 'vuex'
Through the mapState function just imported, the global data required by the current component is mapped to the computed property of the current component:
<template>
<div>
<h3>当前最新Count值为:{
{
count }}</h3>
<button>-1</button>
</div>
</template>
<script>
import {
mapState } from "vuex";
export default {
computed: {
...mapState(["count"])
}
};
</script>
Getter
- Getters are used to process the data in the Store to form new data, similar to the computed properties in Vue
- If the data in the Store changes, the data in the Getters will also change accordingly.
Use one
Define getters in index.js
//定义 Getter
const store = new Vuex.Store({
state:{
count: 0
},
getters:{
showNum(state){
return '当前Count值为:['+state.count']'
}
}
})
In the component use:
this.$store.getters.名称
<h3>{
{
this.$store.getters.showNum }}</h3>
Use method two
In the component, import the mapGetters function
import {
mapGetters } from 'vuex'
Through the mapGetters function imported just now, map the required getters function to the computed method of the current component:
computed: {
...mapGetters(["showNum"])
}
When using it, just call it directly:
<h3>{
{
showNum }}</h3>
Mutation
introduce
If you want to modify the value of count, what should you do?
Maybe you are smart, you have already thought that you can directly operate this.$store.state.count in the component, the code is as follows
<template>
<div>
<h3>当前最新Count值为:{
{
this.$store.state.count}}</h3>
<button @click="add">+1</button>
</div>
</template>
<script>
export default {
methods: {
add() {
this.$store.state.count++;
}
}
};
</script>
The test found that this can meet the requirements and complete the +1 operation.
However, this method is strictly prohibited in vuex, so how to do it? At this time, you need to use Mutation.
concept
Mutation is used to change the data stored in the Store.
- The Store data can only be changed through mutation, and the data in the Store cannot be directly manipulated
- In this way, although the operation is a bit cumbersome, all data changes can be monitored centrally, and direct operation of Store data cannot be monitored.
Define the Mutation function
Define functions in mutations, as follows:
mutations: {
// 自增
add(state) {
state.count++
}
}
The defined function will have a default parameter state, which is the state object stored in the Store.
Call the Mutation function
Asynchronous operations cannot be performed in Mutation. If asynchronous operations are required, please handle them in Action
Method 1:
In the component, complete the trigger through this.$store.commit (method name)
mutations: {
// 自增
add(state) {
state.count++
}
}
Method 2:
Import the mapMutations function in the component
import {
mapMutations } from 'vuex'
Through the mapMutations function imported just now, map the required mutations function to the methods method of the current component:
methods:{
...mapMutations('add','addN'),
// 当前组件设置的click方法
addCount(){
this.add()
}
}
Mutation passing parameters
When updating data through mutation, it is sometimes necessary to carry some additional parameters. Here, the parameters are called the payload of the mutation.
If there is only one parameter, the payload corresponds to this parameter value. For example,
if there are multiple parameters, it will be passed in the form of an object. At this time, the payload is an object, and relevant data can be retrieved from the object.
When defining functions in mutations, parameters can also be received, examples are as follows:
mutations: {
// 自增
add(state) {
state.count++
},
// 带参数
addNum(state, payload) {
state.count += payload.number
}
}
In the component, call it like this:
methods: {
add() {
// this.$store.state.count++;
this.$store.commit("add");
},
addNum() {
this.$store.commit("addNum", {
number: 10
});
}
}
Mutation response rules
The State in the Vuex store is responsive. When the data in the State changes, the Vue components will also be automatically updated.
This requires us to abide by some rules corresponding to Vuex:
- Initialize the required properties in the store in advance
- When adding new properties to objects in State, use the following:
- Use Vue.set(obj, 'newProp', 'propValue')
- Reassign old object with new object
Sample code:
updateUserInfo(state) {
// 方式一
Vue.set('user', 'address', '北京市')
// 方式二
state.user = {
...state.user,
'address': '上海市'
}
}
Mutation constant type
introduce
Think about a question:
In mutation, we define many event types (that is, the method names). As the project grows larger, Vuex manages more and more states, and there are more and more situations that need to update the state, which means There are more and more methods in Mutation.
When there are too many methods, users need to spend a lot of time and energy to remember these methods, and even switch back and forth between multiple files to check the method names, and there are also cases of copying or spelling errors.
So how to avoid it?
- In various Flux implementations, a very common solution is to use constants instead of Mutation event types
- These constants can be placed in a separate file for easy management, and all event types of the entire App can be seen at a glance
solution
- Create a mutation-types.js file where you define constants
- When defining constants, you can use the style in ES2015, using a constant as the name of the function
- Just import the file at the use
New mutation-types.js:
introduced and used in store/index.js:
import Vue from 'vue'
import Vuex from 'vuex'
import * as types from './mutation-type'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
user: {
name: '旺财',
age: 12
}
},
mutations: {
// 自增
[types.ADD_NUM](state) {
state.count++
},
}
In the component, import and call:
<script>
import {
ADD_NUM } from "../store/mutation-type";
export default {
methods: {
add() {
this.$store.commit(ADD_NUM);
// this.addAsync();
// this.$store.state.count++;
// this.$store.commit("add");
}
}
};
</script>
Action
Action is similar to Mutation, but it is used to handle asynchronous tasks, such as network requests, etc.
If you change data through asynchronous operations, you must use Action instead of Mutation, but you still need to indirectly change data by triggering Mutation in Action.
parameter context
The methods defined in actions will have a default value context.
- context is an object with the same methods and properties as the store object
- Commit-related operations can be performed through context, and context.state data can be obtained
But they are not the same object, the difference will be introduced in Modules.
Use one
In index.js, add actions and corresponding methods:
export default new Vuex.Store({
state: {
count: 0
},
//只有 mutations 中定义的函数,才有权力修改 state 中的数据
mutations: {
// 自增
add(state) {
state.count++
}
},
actions: {
addAsync(context) {
setTimeout(() => {
//在 action 中,不能直接修改 state 中的数据
//必须通过 context.commit() 触发某个 mutation 才行
context.commit('add')
}, 1000);
}
}
})
Called in the component:
<script>
export default {
methods: {
addNumSync(){
// dispatch函数 专门用于触发 Action
this.$store.dispatch('addAsync')
}
}
};
</script>
Use method two
In the component, import the mapActions function
import {
mapActions } from 'vuex'
Through the mapActions function imported just now, map the required actions function to the methods method of the current component:
<script>
import {
mapActions } from "vuex";
export default {
methods: {
...mapActions(["addAsync"]),
add() {
Î
this.addAsync()
},
}
Use method three
After importing mapActions, you can directly bind the specified method to the @click event.
...mapActions(["addAsync"]),
---------------------------
<button @click="addAsync">+1(异步)</button>
This method also applies to imported mapMutations
Actions carry parameters
In the actions of index.js, add the method of carrying parameters, as follows:
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
// 带参数
addNum(state, payload) {
state.count += payload.number
}
},
actions: {
addAsyncParams(context, payload) {
setTimeout(() => {
context.commit('addNum', payload)
}, 1000);
}
}
})
In the component, call it like this:
methods: {
addNumSyncParams() {
this.$store.dispatch("addAsyncParams", {
number: 100
});
}
}
Combining Actions with Promises
Promise is often used for asynchronous operations. In Action, you can put asynchronous operations in Promise, and call the corresponding resolve or reject after success or failure.
Example:
In store/index.js, add an asynchronous method for actions:
actions: {
loadUserInfo(context){
return new Promise((resolve)=>{
setTimeout(() => {
context.commit('add')
resolve()
}, 2000);
})
}
}
Called in the component, as follows:
methods: {
addPromise() {
this.$store.dispatch("loadUserInfo").then(res => {
console.log("done");
});
}
}
Module
concept
Module means module, why use module in Vuex?
- Vues uses a single state tree, which means that many states will be managed by Vuex
- When the application becomes very complex, the Store object may become quite bloated
- To solve this problem, Vuex allows us to divide the store into modules (Module), and each module has its own State, Mutation, Actions, Getters, etc.
use
In the store directory, create a new folder modules to store the modules files of each module, here moduleA is taken as an example.
In the modules folder, create a new moduleA.js, and the internal properties, state, mutations, etc. are the same as before. Please refer to the code for comments. The example is as follows:
export default {
state: {
name: '凤凰于飞'
},
actions: {
aUpdateName(context) {
setTimeout(() => {
context.commit('updateName', '旺财')
}, 1000);
}
},
mutations: {
updateName(state, payload) {
state.name = payload
}
},
getters: {
fullName(state) {
return state.name + '王昭君'
},
fullName2(state, getters) {
// 通过getters调用本组方法
return getters.fullName + ' 礼拜'
},
fullName3(state, getters, rootState) {
// state代表当前module数据状态,rootState代表根节点数据状态
return getters.fullName2 + rootState.counter
}
}
}
- The local state is exposed through context.state, and the root node state is context.rootState
Reference moduleA in store/index.js as follows:
import Vue from "vue"
import Vuex from "vuex"
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
a: moduleA
}
})
export default store
In this way, the modular splitting of state management is completed through sub-modules.
optimization
If the project is very complex, in addition to sub-module division, the actions, mutations, getters, etc. of the main module can also be separated out, split into separate js files, exported through export, and then imported into index.js for use.
Example: separate the actions, mutations, and getters of the main module into js files and export them, taking actions.js as an example,
export default{
aUpdateInfo(context, payload) {
return new Promise((resolve, reject) => {
setTimeout(() => {
context.commit('updateInfo')
resolve()
}, 1000);
})
}
}
In store/index.js, import and use, as follows:
import Vue from "vue"
import Vuex from "vuex"
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
import moduleA from './modules/moduleA'
Vue.use(Vuex)
const state = {
counter: 1000,
students: [
{
id: 1, name: '旺财', age: 12 },
{
id: 2, name: '小强', age: 31 },
{
id: 3, name: '大明', age: 45 },
{
id: 4, name: '狗蛋', age: 78 }
],
info: {
name: 'keko'
}
}
const store = new Vuex.Store({
state,
mutations,
getters,
actions,
modules: {
a: moduleA
}
})
export default store
Final project directory diagram:
source
Teach you to use Vuex step by step, a tutorial Vue series that monkeys can understand - Vuex Detailed Explanation