第一章Vuex入门
1.1Vuex介绍
什么是vuex?
它是vue团队提供的状态管理模式,简单来说就是针对vue.js提供的数据驱动解决方案。
vue是双向绑定,vuex是单向绑定的,一般来说单向绑定更安全
vuex为什么非常重要?
它可以使vue开发大型复杂的产品变得容易。
安装vuex
npm install vuex --save
yarn add vuex
cnpm install -save vuex
配置vuex
1 创建一个store文件夹,然后在里面创建一个index.js 内容如下
import Vue from 'vue'
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state:{}
})
2 找到vue工程的main.js文件 将store导入
import store from './store';
// Vue实例化这里把 store 加入进去
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
创建全新工程:
安装好vue cli
npm install -g @vue/cli-service-global
创建包含vuex的新工程
打开终端软件(如果是window电脑,请使用 git-bash 软件)
在终端里进入你的常用编程目录,比如 cd /d/work
继续执行命令 vue create hello-world,创建一个hello-world 工程
按回车键后,进入选择页面(可以用键盘的上下键进行移动,按空格键选中)
选择Manually select features,并按回车键
选择:Babel、Router、Vuex、Linter/Formatter,并按回车键
一直按回车键
等待下载完成后,就成功了
vue.config.js配置
在工程目录下创建vue.config.js文件,然后输入配置
module.exports = {
devServer: {
disableHostCheck: true,
host: "0.0.0.0"
}
};
第二章vuex的核心技术
2.1 state/muation/action
vuex比较复杂,要先在使用框架的过程中加深对框架的理解
定义vue store:我们接下来说的store 都是指Vuex的store文件
state:用来存储数据,表示数据在vuex中的存储状态,可以看成一个全局(到处都可以访问到)的大json对象
编写state
例子:编写一个count变量来纪录数据
export default new Vuex.Store({
state: {
count: 0
}
});
muation:作用就是修改state状态
muation是一个函数,可以接收两个参数 :state 当前上下文的state实例,可以通过这个参数修改state的值
payload 接受外部传入的对象,需要和调用方约定好数据类型,也可以不传参数
例子
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++; //用于count的自增
},
decrement(state) {
state.count--; //用于count的自减
}
}
});
Action :如果想着vue中修改state的值,只能通过action触发muation函数来进行修改
export default new Vuex.Store({
state: {
count: 0
},
actions: {
increment: ({ commit }) => commit('increment'),
decrement: ({ commit }) => commit('decrement') //也可以定义多个参数
increment: ({ state,commit,dispatch }) => commit("increment")
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
}
});
action是一个函数 它有两个参数
第一个参数context对象 包含了以下属性和方法
{
state, // 等同于 `store.state`,若在模块中则为局部状态
rootState, // 等同于 `store.state`,只存在于模块中,
commit, // 等同于 `store.commit`
dispatch, // 等同于 `store.dispatch`
getters, // 等同于 `store.getters`
rootGetters; // 等同于 `store.getters`,只存在于模块中
}
第二个参数是用来接收外部传递的参数
commit:它是个函数,作用就是把数据提交到muation中,也有2个参数
第一个参数是muation函数的名称
第二个是自定义的对象
actions: {
increment: ({ commit }) => commit('increment'); //可以把对象传递给muation
}
运用vuex store
只要我们定义好state和action我们就可以在任意vue文件中得到数据和执行数据变化
获取state
我们可以通过$store.state.count来获取state中的count值 state只支持单向绑定 muation更新了state的状态之后,vue的值也会更新
例子
<template>
<div>
Clicked: {{ $store.state.count }} times
</div>
</template>
mapState这个函数可以动态的将state和compute绑定
counter.vue
<template>
<div>
Clicked: {{ count }} times
</div>
</template>
<script>
// 导入 mapState 函数
import { mapState } from 'vuex';
export default {
computed: {
...mapState({
// 箭头函数可使代码更简练
count: state => state.count
})
}
}; //根据count的值变化而刷新页面而不是只根据state的状态
执行store的action
<template>
<div>
Clicked: {{ count }} times
<div>
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</div>
</template>
<script>
// 导入 mapState mapActions 函数
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState({
count: state => state.count
})
},
methods: { ...mapActions(['increment', 'decrement']) } //把store的action绑定到了vue的methods对象中
};
</script>
###2.2getter
vuex的gtter函数类似于computed函数的作用,它会将返回值根据依赖缓存起来,只有当依赖发生改变时,值才会被重新计算
定义getter
getters: {
evenOrOdd: state => (state.count % 2 === 0 ? "偶数" : "奇数") //state作为第一个参数 位置
放在store中和state mutation action并列
}
使用getter,在组件中使用
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'evenOrOdd'
])
}
}
###2.3 vuex request
我们知道action是用来接受数据并处理逻辑的地方。当vuex需要从远处获取数据时即action接受外部参数然后执行网络请求需要添加async关键字
api
https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/users
这个api返回的数据
{
"data": {
"users": [
{
"name": "Richard Harris",
"email": "k.yyziq@sdewmv.net"
}
]
}
}
书写store
state
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
users: []
}
});
mutation
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
users: []
},
mutations: {
load(state, payload) { //定义一个函数load来更新user
state.users = payload;
}
}
});
action :因为网络请求是一个异步行为,所以我们需要使用await/async。action函数声明上添加async关键字,执行request请求也需要添加await关键字
做网络请求时一般使用axios框架
import axios from "axios";
const response = await axios.get( //await关键字
"https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/users"
);
console.log(response.data); //可以获取服务器的内容
api的返回数据
{
"data": {
"users": [
{
"name": "Richard Harris",
"email": "k.yyziq@sdewmv.net"
}
]
}
}
console.log(response.data.data.users); //获取user api的返回值
在action中运用axios 例子
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
users: []
},
actions: {
load: async ({ commit }) => {
const response = await axios.get(
"https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/users"
);
commit("load", response.data.data.users);
}
},
mutations: {
load(state, payload) {
state.users = payload;
}
}
});
运用:在user.vue中添加store的依赖和配置
<template>
<div>
<ul v-for="user in users" :key="user.name">
<li>
<p>
<label>用户名</label>:
<span>{{user.name}}</span>
</p>
<p>
<label>邮箱</label>:
<span>{{user.email}}</span>
</p>
</li>
</ul>
</div>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
async mounted() {
await this.load();
},
computed: {
...mapState({
users: state => state.users
})
},
methods: { ...mapActions(["load"]) }
};
</script>
注意点 :vuex进行远程数据操作时的await/async关键字设置
养成按顺序编写store的习惯
2.4 vuex表单
通过功能来熟悉通过传递参数去请求数据
api url
https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/login
api methods:post
api 请求参数
{
"username": "string",
"password": "string"
}
返回模型
{
"success": boolean,
"data": {
"username": "string",
"email": "string"
}
}
登录成功
测试参数
{
"username": "admin",
"password": "123456"
}
提交参数后返回的结果
{
"success": true,
"data": {
"username": "admin",
"email": "r.owukoelkl@scrdunktvw.kn"
}
}
登录失败
如果传递的参数是有问题的,返回的结果是
{
"success": true,
"data": {
"username": "admin",
"email": "r.owukoelkl@scrdunktvw.kn"
}
}
注意点:一定要正确识别api的约定和定义
store的定义:按照s(state)m(mutation)a(action)的顺序来
state:将user作为state的属性来定义
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {}
}
});
mutation:定义一个函数来更新state
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {}
},
mutations: {
setUser(state, payload) {
state.user = payload;
}
}
});
action:由于api的post参数,所以我们使用axios.post
const response = await axios.post('api...', {
username: 'admin',
password: '123456'
});
完整的store代码
import Vue from "vue";
import Vuex from "vuex";
import axios from "axios";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
user: {}
},
mutations: {
setUser(state, payload) {
state.user = payload;
}
},
actions: {
login: async ({ commit }, param) => {
// 执行远程请求
const response = await axios.post(
"https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/login",
{
username: param.username,
password: param.password
}
);
if (response.data.success) {
commit("setUser", response.data.data);
}
}
}
});
在组件中使用
<template>
<div>
<div>
<label>用户名</label>
<input type="text" v-model="username" />
</div>
<div>
<label>密码</label>
<input type="password" v-model="password" />
</div>
<button @click="onLogin">登录</button>
<div v-if="loginError">登录失败</div>
</div>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
data() {
return {
username: "",
password: "",
loginError: false
};
},
computed: {
...mapState({
user: state => state.user
})
},
methods: {
...mapActions(["login"]),
async onLogin() {
// 执行 store 的登录 action
await this.login({
username: this.username,
password: this.password
});
if (this.user && this.user.username) {
// 登录成功,随便跳转一下
window.location.href = "https://m.baidu.com";
} else {
// 登录失败
this.loginError = true;
}
}
}
};
</script>
第三章
3.1 配置模块
对于小应用我们可以把代码写在一个index.js文件中,但是企业级项目是非常复杂的,所以我们会使用模块来维护store
配置vuex模块:就是把一个store分割为多个js文件,每个文件就是一个模块,每个模块都有自己的smag,甚至模块还可以嵌套子模块
多个模块在一个文件里
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
如果我们想获取一个模块,我们可以
store.state.a; // -> moduleA 的状态
store.state.b; // -> moduleB 的状态
分拆模块文件
count.js
export default {
namespaced: true, //开启命名空间,使同名action也没有影响
state: {
count: 0
},
actions: {
increment: ({ commit }) => commit("increment"),
decrement: ({ commit }) => commit("decrement")
},
mutations: {
increment(state) {
state.count++;
},
decrement(state) {
state.count--;
}
}
};
集成module
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
import count from "./count";
export default new Vuex.Store({
modules: {
count:count //module对象里面添加的属性名称就是module的名称
}
});
vue中使用module store
vuex提供一个createNamespacedHelpers函数来创建指定namespace下的module引用。
createNamespacedHelpers这个函数接受的参数名称就是module的名称。
import { createNamespacedHelpers } from "vuex";
// 创建 count 模块的引用
const { mapState, mapActions } = createNamespacedHelpers("count");
export default {
computed: {
...mapState({
count: state => state.count
})
},
methods: { ...mapActions(["increment", "decrement"]) }
};
也可以使用多个模块
const { mapState, mapActions } = createNamespacedHelpers("count"); //引用count
const { mapState:demoMapState, mapActions:demoMapActions} = createNamespacedHelpers("demo"); //引用demo
export default {
computed: {
...mapState({
count: state => state.count
}),
...demoMapState({
demoCount: state => state.count
})
},
methods: { ...mapActions(["increment", "decrement"]) }
};
3.2 多模块(一)
我们用一个将上面添加到购物车的例子来方便理解
api
1 products
request
GET https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/products
Accept: application/json, */*
Content-Type: application/json
response
[{
id:"int",// 主键
title:"string",// 商品名称
price:"number" // 商品价格
}]
2 cart
request
POST https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/buyproducts
Accept: application/json, */*
Content-Type: application/json
Body:
{
"products":[
{
id:""
}
]
}
response
{
"success": false
}
API开发
当项目复杂时,我们可以把api封装在api文件中,可以在src目录下面创建一个api文件夹,然后在文件夹下面创建不同的文件
如 例子中 我们就创建product.js、cart.js 这两个文件来存放商品和购物车数据
product.js
import axios from "axios";
export default {
async getProducts() {
const response = await axios.get(
"https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/products"
);
return response.data;
}
};
cart.js
import axios from "axios";
export default {
async buyProducts(products) {
const response = await axios.post(
"https://www.fastmock.site/mock/a9b15cd4db90d4e03ed76cd3c76d9197/f6/buyproducts",
{
products
}
);
return response.data;
}
};
3.3 多模块(二)
一般我们都把module store都存放在src/store/modules这个目录下
product state 定义一个all数组存放数据
export default {
namespaced: true,
state: {
all: []
}
};
product mutation 定义一个setproducts方法使加载所有商品是触发修改状态
export default {
namespaced: true,
state: {
all: []
},
mutations: {
setProducts(state, products) {
//更新所有的商品
state.all = products;
}
}
};
product actions 需要一个getAllProducts方法来加载所有的商品数据
// 加载 api
import productApi from "../../api/product";
export default {
namespaced: true,
state: {
all: []
},
mutations: {
setProducts(state, products) {
state.all = products;
}
},
actions: {
async getAllProducts({ commit }) {
// 加载所有商品记录
const products = await productApi.getProducts();
// 触发数据状态变化
commit("setProducts", products);
}
}
};
store集成
import Vue from "vue";
import Vuex from "vuex";
import product from "./modules/product";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
product
}
});
3.4 多模块(三)
cart state
可以定义一个items数组 每个item对象可以是
还需要一个checkoutStatus属性来存储购买状态
export default {
namespaced: true,
state: {
items: [],
checkoutStatus: null
}
};
cart mutation 可以分解出多个数据状态更新的mutation
添加到购物车
export default {
mutations: {
// 接收商品id,完成添加商品到购物车
pushProductToCart(state, { id }) {
// 添加一条商品购买记录
state.items.push({
id,
quantity: 1
});
}
}
};
添加购物车商品数量
export default {
mutations: {
// 接收商品id,完成购物车商品数量+1
incrementItemQuantity(state, { id }) {
// 根据 id,找出对应的 item 记录
const cartItem = state.items.find(item => item.id === id);
// 购买数量+1
cartItem.quantity++;
}
}
};
更新全部购物车商品:可以用于清空购物车或者从其他地方获取购物车
export default {
mutations: {
// 全量更新购物车商品
setCartItems(state, { items }) {
state.items = items;
}
}
};
更新购物车状态
export default {
mutations: {
setCheckoutStatus(state, status) {
state.checkoutStatus = status;
}
}
};
cart actions
如何在当前模块调用其他模块?可以通过指定命名空间来调用
例如 调用product store的mutation方法
commit(
"products/decrementProductInventory", //命名空间/mutation函数的名称
{ id: product.id }, //decrementProductInventory函数的第二个参数
{ root: true } //设置在全局变量空间内分发
);
调用product模块的getAllProducts这个action
export default {
actions: {
async demo({ state, commit,dispatch }) {
// 触发 product module的 getAllProducts 这个 action
dispatch('product/getAllProducts', null, { root: true })
}
}
}
添加到购物车
export default {
actions: {
addProductToCart({ state, commit }, product) {
// 重制购物车状态
commit("setCheckoutStatus", null);
if (product.inventory <= 0) {
// 如果商品库存没有了则不用继续执行了,直接返回
return;
}
// 查找购物车商品
const cartItem = state.items.find(item => item.id === product.id);
// 判断购物车里没有商品,则执行添加商品,否则执行添加数量
if (!cartItem) {
commit("pushProductToCart", { id: product.id });
} else {
commit("incrementItemQuantity", cartItem);
}
// 执行商品的库存减法,确保商品不会超卖
commit(
"products/decrementProductInventory",
{ id: product.id },
{ root: true }
);
}
}
};
store 集成
import Vue from "vue";
import Vuex from "vuex";
import cart from "./modules/cart";
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
cart
}
});
3.5 多模块(四)
商品组件
<template>
<ul>
<li v-for="product in products" :key="product.id">
{{ product.title }} - {{ product.price }}
<br />
<button :disabled="!product.inventory" @click="addProductToCart(product)">
Add to cart
</button>
</li>
</ul>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
const {
mapState: productMapState,
mapActions: productMapActions
} = createNamespacedHelpers("product");
const { mapActions: cartMapActions } = createNamespacedHelpers("cart");
export default {
computed: {
...productMapState({
products: state => state.all
})
},
methods: {
...cartMapActions(["addProductToCart"]),
...productMapActions(["getAllProducts"])
},
created() {
this.getAllProducts();
}
};
</script>
购物车组件
<template>
<div class="cart">
<h2>Your Cart</h2>
<p v-show="!cartProducts.length">
<i>Please add some products to cart.</i>
</p>
<ul>
<li v-for="product in cartProducts" :key="product.id">
{{ product.title }} - {{ product.price }} x {{ product.quantity }}
</li>
</ul>
<p>
<button :disabled="!cartProducts.length" @click="checkout(cartProducts)">
Checkout
</button>
</p>
<p v-show="checkoutStatus">Checkout {{ checkoutStatus }}.</p>
</div>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
const { mapState, mapActions } = createNamespacedHelpers("cart");
const { mapState: productMapState } = createNamespacedHelpers("product");
export default {
computed: {
...mapState({
items: state => state.items,
checkoutStatus: state => state.checkoutStatus
}),
...productMapState({
allProducts: state => state.all
}),
cartProducts() {
// 遍历 items,并修改数据
return this.items.map(({ id, quantity }) => {
const product = this.allProducts.find(product => product.id === id);
// 通过 return 修改原始数据,把product 的名称和价格赋值给对象
return {
id,
title: product.title,
price: product.price,
quantity
};
});
}
},
methods: {
...mapActions(["checkout"])
}
};
</script>
store集成
<template>
<div id="app">
<h1>Shopping Cart Example</h1>
<hr>
<h2>Products</h2>
<ProductList/>
<hr>
<ShoppingCart/>
</div>
</template>
<script>
import ProductList from './components/ProductList.vue'
import ShoppingCart from './components/ShoppingCart.vue'
export default {
components: { ProductList,ShoppingCart }
}
</script>