该笔记参考拉勾教育前端课程整理:kaiwu.lagou.com/
vuex
-
Vue组件间通信方式回顾
-
Vuex核心概念和基本使用回顾
-
购物车案例
-
模拟实现Vuex
组件内的状态管理流程
Vue核心功能:数据驱动和组件化
state:驱动应用的数据源view:以声明方式将state映射到视图actions:响应在view上的用户输入导致的状态变化
Vue组件间通信方式回顾
父组件给子组件传值
- 子组件中通过
props接收数据 - 父组件中给子组件通过相应属性传值
<!--父:注册子组件-->
<child title="My journey with Vue"></child>
//子
props: {
title: String
}
子组件给父组件传值
- 父:注册事件
<child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
<child :fontSize="hFontSize" v-on:enlargeText="hFontSize += $event"></child>
enlargeText (size) {
this.hFontSize += size
}
- 子:通过
$emit传递参数
<button @click="handler">文字增大</button>
handler () {
//this:当前子组件对象
this.$emit('enlargeText', 0.1)
}
不相关组件之间传值
使用Event Bus作为事件中心
- eventbus.js
//调用它的$emit和$on
import Vue from 'vue'
export default new Vue()
- 组件一:调用
$emit发布
bus.$emit('numchange', this.value)
- 组件二:调用
$on订阅
bus.$on('numchange', (value) => {
this.msg = `您选择了${value}件商品`
})
通过ref获取子组件
- $root
- $parent
- $children
$refs- 在普通HTML标签上使用ref,获取到的是DOM
- 在组件标签上使用ref,获取到的是组件实例
<template>
<input ref="input">
</template>
<script>
export default {
methods: {
// 用来从父级组件聚焦输入框
focus: function () {
this.$refs.input.focus()
}
}
}
</script>
在使用子组件的时候,添加 ref 属性:
<base-input ref="usernameInput"></base-input>
然后在父组件等渲染完毕后使用$refs访问:
mounted(){
this.$refs.usernameInput.focus()
}
简易的状态管理方案
- 如果多个组件之间要共享状态(数据),多个组件之 间互相传值很难跟踪数据的变化,如果出现问题很难定位问题
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
- 把组件的共享状态抽取出来,以一个全局单例模式管理
- 创建一个共享的仓库 store 对象
export default {
debug: true,
state: {
user: {
name: 'xiaomao',
age: 18,
sex: '男'
}
},
setUserNameAction (name) {
if (this.debug) {
console.log('setUserNameAction triggered:',name)
}
this.state.user.name = name
}
}
- 把共享的仓库 store 对象,存储到需要共享状态的组件的 data 中
import store from './store'
export default {
methods: {
// 点击按钮的时候通过 action 修改状态
change () {
store.setUserNameAction('componentB')
}
},
data () {
return {
privateState: {},
sharedState: store.state
}
}
}
接着我们继续延伸约定,组件不允许直接变更属于 store 对象的 state,而应执行
action来分发(dispatch) 事件通知 store 去改变,这样最终的样子跟Vuex的结构就类似了。这样约定的好处是,我们能够记录所有 store 中发生的 state 变更,同时实现能做到记录变更、保存状态快照、历史回滚/时光旅行的先进的调试工具
Vuex概念
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
Vuex 是专门为 Vue.js 设计的状态管理库,以利用 Vue.js 的细粒度数据响应机制来进行高效的状态更新
把组件的共享状态抽取出来,以一个全局单例模式管理(不管在树的哪个位置,任何组件都能获取状态或者触发行为)
- Vuex 是专门为 Vue.js 设计的状态管理库
- 它采用集中式的方式存储需要共享的数据
- 从使用角度,它就是一个 JavaScript 库
- 它的作用是进行状态管理,解决复杂组件通信,数据共享
使用场景
大型单页应用程序:
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
eg:购物车
核心概念
- Store:容器,包含应用中大部分状态,要通过提交Mutation方式改变状态
- State:状态,响应式,保存在Store中,状态是唯一的,称为单一状态树。后续+模块化。
- Getter:计算属性,方便从一个属性派生出其它的值,内部可以对计算的结果进行缓存
- Mutation:状态变化需要过提交Mutation
- Action:类似Mutation,还可以进行异步操作。内部改变状态时都要提交Mutation
- Module:模块。使用单一状态树,所有对象会集中在一个对象树上,应用十分复杂时,对象会变得臃肿;可以将Store划分为模块,每个模块有自己的State、Mutation、Action
结构分析
- 导入 Vuex
import Vuex from 'vuex'
- 注册 Vuex
//store\index.js
Vue.use(Vuex)
export default new Vuex.State({
state:{
},
mutations:{
},
modules:{
}
})
- 注入 $store 到 Vue 实例
//app.js
import store from './store'
new Vue({
store
}).$mount('#app')
State
- 在组件中绑定状态
store\index.js
//store\index.js
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
msg: 'Hello Vuex'
},
})
app.vue
<!--app.vue-->
<div>
<!--count:{{ $store.count }}-->
msg:{{ message }}
</div>
<script>
//简化模板内容
import { mapState } from 'vuex'
export default{
computed: {
// count: state => state.count
//映射状态属性
...mapState({ num: 'count', message: 'msg' }),
},
}
</script>
- Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态
- 使用
mapState简化 State 在视图中的使用,mapState 返回计算属性
Getter
- Getter 就是 store 中的计算属性,使用 mapGetter 简化视图中的使用
- 意思就是在状态内部使用它去处理属性
store\index.js
////store\index.js
getters: {
reverseMsg (state) {
return state.msg.split('').reverse().join('')
}
},
join()方法會將陣列(或一個類陣列(array-like)物件)中所有的元素連接、合併成一個字串,並回傳此字串
app.vue
<!--app.vue-->
<div>
reverseMsg: {{ reverseMsg }}
</div>
<script>
//简化模板内容:可以传数组和对象
import { mapGetters } from 'vuex'
export default{
computed: {
...mapGetters(['reverseMsg']),
},
}
</script>
Mutaion
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数
使用 Mutation 改变状态的好处是:集中的一个位置对状态修改,不管在什么地方修改,都可以追踪到状态的修改。可以实现高级的 time-travel 调试功能
store\index.js
mutations: {
increate (state, payload) {
state.count += payload
}
},
app.vue
<!--app.vue-->
<div>
<!--count的值会加2
<button @click="$store.commit('increate', 2)">Mutation</button>
</div>
-->
<button @click="increate(3)">Mutation</button>
</div>
<script>
//简化模板内容
import { mapMutations } from 'vuex'
export default{
methods: {
...mapMutations(['increate']),
}
}
</script>
Action
Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态
- Action 可以包含任意异步操作
store\index.js
actions: {
increateAsync (context, payload) {
setTimeout(() => {
context.commit('increate', payload)
}, 2000)
}
},
-
app.vueAction 通过
store.dispatch方法触发:
<!--app.vue-->
<div>
<!--count的值会加2
<button @click="$store.dispatch('increateAsync', 6)">Mutation</button>
</div>
-->
<button @click="increateAsync(6)">Action</button>
</div>
<script>
//简化模板内容
import { mapActions } from 'vuex'
export default{
methods: {
...mapActions(['increateAsync']),
}
}
</script>
Module
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
products.js
const state = {
products: [
{ id: 1, title: 'iPhone 11', price: 8000 },
{ id: 2, title: 'iPhone 12', price: 10000 }
]
}
const getters = {}
const mutations = {
setProducts (state, payload) {
state.products = payload
}
}
const actions = {}
export default {
//开启命名空间
namespaced: true,
state,
getters,
mutations,
actions
}
store\index.js:导入两个模块
import products from './modules/products'
import cart from './modules/cart'
modules: {
products,
cart
}
通过store.state.xxx访问模块成员
app.vue
<!--app.vue-->
<div>
<!-- products: {{ $store.state.products.products }} <br>
<button @click="$store.commit('setProducts', [])">Mutation</button> -->
products: {{ products }} <br>
<button @click="setProducts([])">Mutation</button>
</div>
<script>
//简化模板内容
import { mapMutations } from 'vuex'
export default{
methods: {
//1.命名空间 2.映射属性
...mapMutations('products', ['setProducts'])
}
}
</script>
严格模式
const store = new Vuex.Store({
// ...
strict: true
})
在严格模式下,无论何时发生了状态变更且不是由 mutation 函数引起的,将会抛出错误。这能保证所有的状态变更都能被调试工具跟踪到
e.g:浏览器会报错,但数据还是会被修改
<button @click="$store.state.msg = 'Lagou'">strict</button>
- 不要在生产环境下开启严格模式,它会:
严格模式会深度监测状态树来检测不合规的状态变更——请确保在发布环境下关闭严格模式,以避免性能损失
状态树:用一个对象就包含了全部的应用层级状态
- 构建工具来处理,webpack打包时通过环境变量去处理
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
购物车案例
功能列表
- 商品列表组件
- 商品列表中弹出框组件
- 购物车列表组件
商品列表
- Vuex 中创建两个模块,分别用来记录商品列表和购物车的状态,store 的结构:
商品列表功能-Products.js
-
products 模块,
store/modules/products.js -
store/index.js中注册 products.js 模块export default new Vuex.Store({ modules: { products, cart }, }) -
views/products.vue中实现商品列表的功能
添加购物车-cart.js
- cart 模块实现添加购物车功能,
store/modules/cart.js store/index.js中注册 cart 模块view/products.vue中实现添加购物车功能
弹出购物车窗口
src\components\pop-cart.vue
-
展示购物车列表
...mapState('cart', ['cartProducts']), -
删除
src\store\modules\cart.js...mapMutations('cart', ['deleteFromCart']) -
小计
src\store\modules\cart.js...mapGetters('cart', ['totalCount', 'totalPrice'])
购物车
购物车列表
……
全选功能
- cart 模块实现更新商品的选中状态,
store/modules/cart.js
Vuex插件介绍
- Vuex的插件就是一个函数
- 这个函数接收一个store的参数
const myPlugin = store => {
//当store初始化后调用
store.subscribe((mutain, state) => {
//每次mutation之后调用
//mutation的格式为{type, payload}
})
}
const store = new Vuex.Store({
plugins: [myPlugin]
})
以该项目为例:src\store\index.js
const myPlugin = store => {
store.subscribe((mutation, state) => {
if(mutation.type.startWith('cart/')){
window.localStorage.setItem('cart-products', JSON.stringify(state.cart.cartProducts))
}
})
}
简单模拟Vuex
初始化Vuex架构
src\myvuex\index.js
let _Vue = null
class Store { }
//vue-router类似
//vue构造函数
function install(Vue) {
_Vue = Vue
}
export default {
Store,
install
}
- install
//vue-router类似
function install(Vue) {
_Vue = Vue
//通过混入beforeCreate获取到vue实例
_Vue.mixin({
beforeCreate(){
if(this.$options.store) {
_Vue.prototype.$store = this.$options.store
}
}
})
}
- store
class Store {
constructor(options) {
const {
state = {},
getter = {},
mutations = {},
actions = {}
} = options
this.state = _Vue.observable(state)
this.getters = Object.create(null)
Object.keys(getters).forEach(key => {
Object.defineProperty(this.getters, key, {
get: () => getters[key](state)
})
})
this._mutations = mutations
this._actions = actions
}
commit(type, payload) {
this._mutations[type](this.state, payload)
}
}