官方解释:Vuex是一个专为Vue.js应用程序开发的状态管理模式。
个人理解:状态管理
如果这样的话,为什么官方还要专门出一个插件Vuex呢?难道我们不能自己封装一个对象来管理吗?
假如我们自己封装一个对象,如何封装的?
const sharedObj={
name:"haha",
age:18
}
Vue.prototype.sharedObj=sharedObj;//使用Vue原型,这就可以在其它组件共享使用
//在其它组件可以直接这样用
this.sharedObj.name来使用这对象的属性
注意:这样实现的对象只能在组件共享,还不能做到响应式。这里就不再做多讨论,重点是学习Vuex。
有什么状态是需要我们在多个组件间共享的呢?
这图片中的三种东西怎么理解呢?
下面一个简单的实例:
<template>
<div id="app">
<h3>{
{count}}h3>
<button @click="add">+button>
<button @click="sub">-button>
div>
template>
<script>
// import Home from "./components/Home.vue";
export default {
name: "App",
data: function () {
return {
count: 0,
};
},
methods: {
add: function () {
this.count++;
},
sub: function () {
this.count--;
},
},
};
script>
<style>
style>
当在多个页面想要实现共享时,我们会使用Vuex插件,既然它是一个插件,那么它需要下载安装:
npm install vuex --save
这个插件与vue-router插件都是Vue中非常重要的,vue-router在项目中有一个专门的router文件夹管理着路由相关的内容。因此,这里我们也在项目的src文件下创建一个名为store的文件夹并添加index.js文件,方便管理。
看看官方的Vuex状态管理图例:(该图是学习重点,务必记住)
图例解释:
实例:在多组间使用Vuex共享的数据:
下面开始使用Vuex插件:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter:999
},
mutations: {
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
这是一个比较固定的模板,Vuex实例中的属性基本我们都要用到。然后我们在Vue实例处引入Vuex实例并使用即可。
对象中属性解释:
在Vue实例中引用Vuex实例:
//main.js
import Vue from "vue";
import App from "./App";
import store from "./store/index";
Vue.config.productionTip = false;
new Vue({
el: "#app",
store,
render: h => h(App)
});
在App.vue和Home.vue组件使用Vuex共享的数据:
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>我在App.vue组件使用counter: {
{
$store.state.counter }}</h3>
<Home></Home>
</div>
</template>
<script>
import Home from "./components/Home.vue";
export default {
name: "App",
components: {
Home
}
};
</script>
<style></style>
Home.vue:
<template>
<div>
<hr />
<h2>我是App.vue的子组件</h2>
<h2>我在Home.vue组件中使用counter:{
{
$store.state.counter }}</h2>
</div>
</template>
<script>
export default {
};
</script>
<style></style>
运行:
上面两个组件是父子关系。他们都使用了Vuex中共享的counter。
注意:
(前提是你能使用外国网络,即)
首先我们在chorme浏览器中:
这个实例由于没有用到异步操作,所以直接从Component–>Mutations–>State,跳过Actions。这是一个点击按钮实现数字增减的实例。
Store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 999
},
mutations: {
add(state) {
state.counter++;
},
sub(state) {
state.counter--;
}
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
上面说过,Mutations相当于组件的methods属性。这里定义了两个方法,让两个组件调用共享。
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>我在App.vue组件使用counter: {
{
$store.state.counter }}</h3>
<Home></Home>
</div>
</template>
<script>
import Home from "./components/Home.vue";
export default {
name: "App",
components: {
Home
}
};
</script>
<style></style>
Home.vue:(重点)
<template>
<div>
<hr />
<h2>我是App.vue的子组件</h2>
<h3>我在Home.vue组件中使用counter:{
{
$store.state.counter }}</h3>
<button @click="addition">+</button>
<button @click="$store.commit('sub')">-</button>
</div>
</template>
<script>
export default {
methods: {
addition: function () {
this.$store.commit("add");
},
},
};
</script>
<style></style>
这里我使用了两种方式调用Vuex中mutations中的方法,就是为了说明这两种方法都可以使用。
下面是案例运行图:
看到Vuex的记录了吧,每点击一次按钮都会记录相应的状态。
在上面我已经说过:state是类似于组件中的data属性,用于定义在各个组件共享的数据。
下面举例使用:
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 999,
fruits: ["苹果", "火龙果", "百香果", "无敌果"]
},
mutations: {
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>我在App.vue组件使用counter: {
{
$store.state.counter }}</h3>
<ul>
<li v-for="fruit in $store.state.fruits">{
{
fruit}}</li>
</ul>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style></style>
上面我在store/index.js中的state属性定义了数据,然后到根组件App.vue使用数据,这里我就不再使用多组件了,在其它组件用法是一样的。
什么是单一状态树?
但是,它是什么呢?举一个生活中的例子:
这个和我们在应用开发比较类似:
getters类似于组件的computed计算属性。
什么时候使用计算属性呢?
当在state定义一些数据时,如果数据是直接可以使用的话,那就可以直接使用$store.state来使用;如果数据还需进行一些处理才可以使用的话,那么就用到getters属性。
注意:getters属性中定义的的函数的参数问题:
下面举个例子:将数据过滤得到想要的数据
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50,
people: [
{
name: "张三",
age: 18
},
{
name: "张si",
age: 25
},
{
name: "张wu",
age: 35
},
{
name: "张liu",
age: 45
}
]
},
mutations: {
},
actions: {
},
getters: {
Filter: function(state) {
return state.people.filter(man => man.age >= 20);
}
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<ul>
<li v-for="man in $store.getters.Filter">{
{
man}}</li>
</ul>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style></style>
即如果一个getters中的函数也想使用getters中的另一个函数,那该怎么使用呢?
第二个参数使用getters
实例:
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50,
people: [
{
name: "张三",
age: 18
},
{
name: "张si",
age: 25
},
{
name: "张wu",
age: 35
},
{
name: "张liu",
age: 45
}
]
},
mutations: {
},
actions: {
},
getters: {
Filter: function(state) {
return state.people.filter(man => man.age >= 20);
},
getLength: function(state,getters) {
return getters.Filter.length;
}
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
注意:getLength()的第二个参数传入getters作为函数的参数,然后使用getters.Filter调用了getters中的Filter函数,Fliter返回一个过滤后的数组,再取的长度。
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<ul>
<li v-for="man in $store.getters.Filter">{
{
man}}</li>
</ul>
<h2>数据长度:{
{
$store.getters.getLength}}</h2>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style></style>
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50,
},
mutations: {
},
actions: {
},
getters: {
example: function(state, getters) {
return function(obj) {
console.log("传入的参数:", obj);
return obj;
};
}
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h2>测试第三个参数:{
{
$store.getters.example({
name:"大刀王五",hobby:["篮球","耍刀"]})}}</h2>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style></style>
当我们想给函数传入自定义参数时,就像上面一样返回一个带参数的函数即可。
mutations是在Vuex-devtools保存状态记录的核心,如果想要让devtools记录着state数据的更新,那么唯一的方式就是提交到Mutations。
Mutations主要包括两部分:
为了证实只有通过Mutations的数据才保存状态记录,下面举两个例子:一个绕过Mutations更改数据,一个通过Mutations更改数据。
实例1:通过Mutations更改:按钮点击(通过Mutations提交)
store/index.js
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50
},
mutations: {
add: function(state) {
state.counter += 5;
},
sub: function(state) {
state.counter -= 5;
}
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>counter:{
{
$store.state.counter}}</h3>
<button @click="addition">+</button>
<button @click="subtraction">-</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
addition() {
this.$store.commit("add");
},
subtraction() {
this.$store.commit("sub");
},
},
};
</script>
<style></style>
从动图中可以看到devtools插件在记录着counter的状态改变,因为上面我们使用的是:
this.$store.commit("add");//参数是提交给mutations的处理的函数名
用commit提交到mutations中的add()来更改counter的状态。
Mutations主要包括两部分:
实例2:通过Mutations更改:按钮点击(绕过Mutations提交)
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50
},
mutations: {
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
由于它绕过mutations,所以这里mutations不定义任何东西。
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>counter:{
{
$store.state.counter}}</h3>
<button @click="$store.state.counter+=5">+</button>
<button @click="$store.state.counter-=5">-</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
},
};
</script>
<style></style>
$store.state.counter+=5
来修改Vuex的state,所以是绕过了Mutations,所以从上面动图那里看到Vuex插件并没有记录状态的变化。因此,想要记录状态的变化,必须经过mutations。
上面的例子是我直接在Mutations中:
mutations: {
add: function(state) {
state.counter += 5;
},
sub: function(state) {
state.counter -= 5;
}
}
将每次累加或累减写成了5,那我想通过传过来的参数决定每次加减多少:
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50
},
mutations: {
add: function(state, num) {
state.counter += num;
},
sub: function(state, num) {
state.counter -= num;
}
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>counter:{
{
$store.state.counter}}</h3>
<button @click="addition(10)">+</button>
<button @click="subtraction(20)">-</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
addition(num) {
this.$store.commit("add", num);
},
subtraction(num) {
this.$store.commit("sub", num);
},
},
};
</script>
<style></style>
this.$store.commit("add", num);
this.$store.commit("add", num);
上面的这种提交风格,我们称为普通风格。
下面看看特殊的风格如何书写:
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50
},
mutations: {
add: function(state, obj) {
state.counter += obj.num;
console.log(obj);
},
sub: function(state, num) {
state.counter -= num;
}
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:(注意区别)
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>counter:{
{
$store.state.counter}}</h3>
<button @click="addition(10)">+</button>
<button @click="subtraction(20)">-</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
addition(num) {
this.$store.commit({
type: "add",
num,
});
},
subtraction(num) {
this.$store.commit("sub", num);
},
},
};
</script>
<style></style>
注意:
通常情况下,Vuex要求在Mutations中的方法必须是同步方法。
在这里强调,不要在Mutations中进行异步操作。
下面举例:在mutations中写个异步函数,看看能否被devtools捕抓
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50
},
mutations: {
add: function(state, payload) {
setTimeout(() => {
state.counter += payload.num;
}, 1000);
},
sub: function(state, payload) {
setTimeout(() => {
state.counter -= payload.num;
}, 1000);
}
},
actions: {
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>counter:{
{
$store.state.counter}}</h3>
<button @click="addition(10)">+</button>
<button @click="subtraction(20)">-</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
addition(num) {
this.$store.commit({
type: "add",
num,
});
},
subtraction(num) {
this.$store.commit({
type: "sub",
num,
});
},
},
};
</script>
<style></style>
上面图片说明再Mutations使用异步函数是不合理的,虽然能正确显示数据,但是数据与抓捕的记录不一致,那使用Vuex就没有意义了。
举例:在Actions中使用异步函数
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
counter: 50
},
mutations: {
add: function(state, payload) {
state.counter += payload.num;
console.log("payload对象:", payload);
},
sub: function(state, payload) {
state.counter += payload.num;
}
},
actions: {
// context:上下文
Add: function(context, num) {
setTimeout(() => {
console.log("context对象:", context);
context.commit({
type: "add",
num
});
}, 1000);
},
Sub: function(context, num) {
setTimeout(() => {
context.commit({
type: "sub",
num
});
}, 1000);
}
},
getters: {
},
modules: {
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>counter:{
{
$store.state.counter}}</h3>
<button @click="addition(10)">+</button>
<button @click="subtraction(20)">-</button>
</div>
</template>
<script>
export default {
name: "App",
methods: {
addition(num) {
this.$store.dispatch("Add", num);
},
subtraction(num) {
this.$store.dispatch("Sub", num);
},
},
};
</script>
<style></style>
记录结果:
Module是模块的意思,为什么在Vuex中我们要使用模块呢》
在modules中写多个模块
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
getters: {
},
modules: {
moduleA: {
state: {
},
mutations: {
},
actions: {
},
getters: {
}
},
moduleB: {
state: {
},
mutations: {
},
actions: {
},
getters: {
}
}
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
上面在modules中定义了两个模块moduleA、moduleB。他们也有同样的特性,但一般不会再在模块(moduleA、moduleB)中定义modules属性。
如何使用modules中的数据:
store/index.js:
import Vue from "vue";
import Vuex from "vuex";
// 1.使用Vuex插件
Vue.use(Vuex);
// 2.创建Vuex对象
const store = new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
getters: {
},
modules: {
moduleA: {
state: {
name: "我是moduleA中state中的name" },
mutations: {
updateName: function(state) {
state.name = "我是修改后的name";
}
},
actions: {
},
getters: {
fullName: function(state) {
return state.name + "哈哈哈哈";
}
}
}
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
App.vue:
<template>
<div id="app">
<hr />
<h2>我是App.vue组件</h2>
<h3>{
{
$store.state.moduleA.name}}</h3>
<button @click="$store.commit('updateName')">修改</button>
<h3>{
{
$store.getters.fullName}}</h3>
</div>
</template>
<script>
export default {
name: "App",
};
</script>
<style></style>
注意:
当Vuex实例的数据过多时往往显得Vuex实例的内容很堵,开发中我们通常会将五个核心各个分成文件,最后通过es6的语法导出与引入各个文件:
在store/index.js:
import Vue from "vue";
import Vuex from "vuex";
import actions from "./actions";
import getters from "./getters";
import mutations from "./mutations";
import moduleA from "./modules/moduleA";
import moduleB from "./modules/moduleB";
// 1.使用Vuex插件
Vue.use(Vuex);
const state = {
name: "在这里写state的属性"
};
// 2.创建Vuex对象,这里使用了es6的对象解构语法 即state:state缩写为state
const store = new Vuex.Store({
state,
actions,
getters,
mutations,
modules: {
moduleA,
moduleB
}
});
// 3.导出Vuex对象,因为Vue实例要使用
export default store;
这样就好多了。