本文已参与「新人创作礼」活动,一起开启创作之路
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
什么时候用到:如果不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果应用够简单,最好不要使用 Vuex。一个简单的 store 模式就足够所需了。本篇以学习Vuex(以及后续的pinia)为目的进行基础使用方式的记录
引入设置Vuex
安装
可以在package.json
当中填写需要的包的名称和版本号,重启yarn服务
//package.json
{
...
"dependencies": {
"core-js": "^3.6.5",
"vue": "^3.0.0-0",
"vuex": "^4.0.0-0"
},
...
}
安装好vuex
以后,在src/app
目录下创建 app.store.ts
的文件进行初始化。
从vuex中引入createStore
。创建一个store
,它等于一个createStore
对象。再默认导出store
// src/app/app.store.ts
import { createStore } from 'vuex';
// 创建 Store
const store = createStore({ });
export default store;
接下来去入口文件 main.ts 载入创建好的store。接下来就可以在应用中访问到store了。
import { createApp } from 'vue';
import App from './app/app.vue';
import appStore from './app/app.store';
const app = createApp(App);
//应用store
app.use(appStore);
app.mount('#app');
核心概念
数据
组件的数据(state)可以储存在store里面,可以跨组件进行访问。
State
state可以定义在 createStore中,做为一个对象可以定义需要用到的数据。
// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
// 定义 state
state: {
name: 'Web.dev',
},
});
export default store;
在应用中访问时,可以直接使用$store.state.name
访问到刚刚定义的数据
// app.vue
<template>
<h3>
{{ $store.state.name }}
</h3>
</template>
mapState
在组件中访问数据时,需要加上$store.state
的前缀才能找到相应的数据。如果想要直接通过state
的名称访问,可以在computed
中使用mapState
的帮手方法展开需要的state
名称,之后在组件中就可以直接像组件内数据一样读取了。
// app.vue
<template>
<h3>
{{name }}
</h3>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState(['name']),
},
};
</script>
如果想要为store里面的state另外起其他名称,可以在mapState提供一个对象。对象的key值就是新的名称。
// app.vue
<script>
...
computed: {
...mapState({
appName:'name',
}),
},
...
</script>
获取器:Getters
可以认为是 store 的计算属性。
在组件中,会用到对state
的计算属性,如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它。所以可以使用Getters来统一处理。
在app.store.js
中添加getter
属性,函数名为在组件中使用的名称,给他一个state
的参数,再返回处理后的结果
// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
// 定义 state
state: {
name: 'Web.dev',
},
//定义 getter
getters: {
name(state) {
return ` ${state.name}`;
},
},
});
export default store;
在组件中使用时,可以在computed
中使用从vuex引入的mapGetters
的帮手方法展开需要的getters
名称,之后在组件中就可以直接像组件内数据一样读取了。
// app.vue
<script>
...
computed: {
...mapGetters(['name']),
},
...
</script>
修改器
store里面的数据无法在组件中直接修改,需要使用指定的修改器为数据更新值
Mutations
每一个mutations
都是一个方法,每一个方法接受一个state
参数和data
参数。state
就是需要修改的数据,data
是传递进来的数值。
// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
// 定义 state
state: {
name: 'Web.dev',
},
mutations: {
setName(state, data) {
state.name = data;
},
},
});
export default store;
在组件中使用时,需要使用 $store.commit()
方法。方法有两个参数,第一个参数是需要使用的mutations
的名称,第二个参数是需要传递的数据值。
// app.vue
<template>
<h3 @click="onClickName">
//name from store
{{ name }}
</h3>
</template>
<script>
export default {
...
methods: {
onClickName() {
this.$store.commit('setName', 'Web开发者');
},
},
...
};
</script>
mapMutations
如果需要把moutations
映射成组件的方法使用,可以用mapMutations
帮手方法解构出来。
引入mapMutations
帮手方法后,在methods中使用名称解构出来。结构出来后,直接传递数据就可以了
// app.vue
<template>
...
</template>
<script>
import { mapMutations } from 'vuex';
export default {
...
methods: {
...mapMutations(['setName'])
onClickName() {
//this.$store.commit('setName', 'Web开发者');
this.setName('Web开发者');
},
},
...
};
</script>
动作
可以在action
里面定义一些异步动作,比如从服务器获取数据或者发送数据到服务器。
如果需要从数据端获取一些数据,可以先在state
中定义一个值,再定义一个修改这个state
的mutations
,再定义一个获取数据的action
。在action
里面请求接口获取数据,再用mutation
修改state
中的数据。
Actions
定义一个action
,为state
提供数据
// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
//数据从action获取
state: {
name: '',
},
mutations: {
setName(state, data) {
state.name = data;
},
},
//定义action
actions: {
getName(context) {
const name = 'web.dev'
context.commit('setName', name);
},
},
});
export default store;
action
可以在组件的created
生命周期中使用。在$store
中找到dispatch
方法,参数设置为action
的名称。action
会通过刚刚设置的mutations
为state
中的name
更新数据。
// app.vue
<template>
...
</template>
<script>
export default {
...
created() {
this.$store.dispatch('getName');
},
...
};
</script>
mapActions
如需要直接在组件中使用actions
的名称。可以使用mapActions
在组件的methods
中结构出来。支持数组和对象。
// app.vue
<template>
...
</template>
<script>
import { mapMutations } from 'vuex';
export default {
...
created() {
this.getName();
},
methods: {
...mapActions(['getName'])
...mapMutations(['setName'])
onClickName() {
//this.$store.commit('setName', 'Web开发者');
this.setName('Web开发者');
},
},
...
};
</script>
Context参数
Action 函数接受一个与 store 实例具有相同方法和属性的 context
对象,因此你可以调用 context.commit
提交一个 mutation
,或者通过 context.state
和 context.getters
来获取 state
和 getters
。
可以通过参数结构来简化代码。
// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
...
//定义action
actions: {
getName({commit}) {
const name = 'web.dev'
commit('setName', name);
},
},
});
export default store;
综合运用:加载状态
模拟一下异步加载的状态。需要设定一个state
来表示是否加载成功(isLoading
)。再去action
中模拟从数据库获取数据的状态。
修改state的数据需要用到新的mutation
setLoading
。
// src/app/app.store.ts
import { createStore } from 'vuex';
const store = createStore({
...
state: {
name: '',
loading: false,
},
mutations: {
...
setLoading(state, data) {
state.loading = data;
},
},
//定义action
actions: {
getName({ commit }) {
commit('setLoading', true);
setTimeout(() => {
const name = '宁皓网';
commit('setName', name);
commit('setLoading', false);
}, 2000);
},
},
});
export default store;
再组件的created
周期当中使用getName
的action
,并且通过isLoading
的状态来确认页面上的显示内容。这样组件再2秒后会显示name
state
的内容
// app.vue
<template>
<h3>
{{ name }}
<span v-if="isLoading">加载中...</span>
</h3>
...
</template>
<script>
import { mapMutations } from 'vuex';
export default {
...
computed: {
...mapGetters(['name']),
...mapState(['loading']),
},
created() {
this.getName();
},
methods: {
...mapActions(['getName'])
},
...
};
</script>
进阶概念
modules
所有的数据都放在一个store,会让应用的架构变得臃肿。可以根据应用的需求,创建各自模块的store,单独储存各自的数据。例如把跟用户有关的内容放在 ./src/user
目录里面,创建user.store.ts
的store
。
每一个store都可以拥有自己的state、mutation、action等属性。
const store = {
state: {
currentUser: '',
},
};
export default store;
在应用的app.store.ts 中导入刚刚创建的user store
import user, { UserState } from '@/user/user.store';
const store = createStore({
...
// 使用 modules导入
modules: {
user,
},
});
export default store;
使用TS时,为modules中的store添加mutation、action等属性需要设置类型。store的类型为Module带两个参数 store: Module<UserState, RootState>
UserState 参数为本地定义的interface,RootState
是需要在app.store中定义
// user.store.ts
import { Module } from 'vuex';
import { RootState } from '@/app/app.store';
export interface UserState {
currentUser: string;
}
const store: Module<UserState, RootState> = {
state: {
currentUser: '',
},
mutations: {
setCurrentUser(state, data) {
state.currentUser = data;
},
},
...
};
export default store;
//app.store.ts
import { createStore } from 'vuex';
import user, { UserState } from '@/user/user.store';
export interface RootState {
name: string;
loading: boolean;
user?: UserState;
}
const store = createStore({
...
});
export default store;
namespaced
默认情况下modules的中store的 mutations、actions等属性是全局的名称。如果需要命名空间,可以在modules中打开。
// user.store.ts
import { Module } from 'vuex';
import { RootState } from '@/app/app.store';
...
const store: Module<UserState, RootState> = {
namespaced: true,
...
};
export default store;
在开启命名空间以后,在组件中展开时,需要加上模块的名称。
// app.vue
<script>
import { mapMutations } from 'vuex';
export default {
...
computed: {
...mapGetters({
...
// 展开时加上模块的名字
currentUser: 'user/currentUser',
}),
},
...
};
</script>