前言
所有模块的vuex文件堆在一起太难看了
拆成多个,所有项目都这么做
vue2版本的vuex
脚手架搭建
npm install -g @vue/cli
vue create vuexDemo
cd vuexDemo
步骤
1)安装
npm i vuex@3
2)快速入门
学过一次后老是记不住?害怕使用vuex?
那是因为你对他的认识还不够深,当你知道他一点点执行流程,我包你写到飞起,以下是讲解,
不过手写vuex的其他代码就没深入了,和vue的响应式defineproperty差不多,代理来代理去,遍历循环等等,
如果你想冲击大厂,可以关注b站一个叫不会cv的,教程挺不错的,
我这里就先二八定律了
1.在main.js里引入vuex并注册,然后创建实例
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'
//插件注册
Vue.use(Vuex)
Vue.config.productionTip = false
//创建vuex实例
const store = new Vuex.Store({
state: {
count: 0,
},
mutations: {
increment(state) {
state.count++
},
},
})
new Vue({
render: h => h(App),
}).$mount('#app')
Vue.use(Vuex)为插件注册
2.1)vue注册插件原理如下
Vue.use()会经历如下
参数1:可以是对象(内部包含名为install函数写具体插件逻辑)也可以是一个函数,函数名任意,
参数2:options选项
这个typescript怎么有点类似java的泛型接口啥的
插件定义举例如下
也可以这样定义
定义一个对象插件,install函数接收a,b
外部Vue use该插件调用install,默认传入第一个参数为vue构造函数,第二个参数为可选值,结合源码查看
首先,
Vue.**use** = function (**plugin**: Function | any)
该函数接收一个参数名为函数或者任意类型
const installedPlugins =
this._installedPlugins || (this._installedPlugins = [])
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
记录当前this也就是vue构造函数本身了,如果不为-1,则不给加插件了,这样就能保证插件不能二次vue.use(xxx)安装在vue构造函数上
// additional parameters
const args = toArray(arguments, 1)
args.unshift(this)
arguments标识所有参数列表,1标识从1开始获取直至结束
目的是获取use插件里调用install方法传入的第二个参数options可选值,通过如下方法生成新的array数组
export function toArray(list: any, start?: number): Array<any> {
start = start || 0
let i = list.length - start
const ret: Array<any> = new Array(i)
while (i--) {
ret[i] = list[i + start]
}
return ret
}
接收一个list,任意类型,也就是刚才哪个arguments参数数组, start为刚才传进来的1下标,从1开始
let i 用参数总长度减去 截取掉的长度 得到新数组长度,获取原先的数组[i+start]也就是最后一个,赋值给新数组的最后一个i,也就是长度减掉开始后的最后一个的下标值
while循环,依次从后往前复制到新数组里,最后返回
args.unshift(this)
然后新数组的第一个位置给他unshift vue构造函数也就是this
if (isFunction(plugin.install)) {
plugin.install.apply(plugin, args)
} else if (isFunction(plugin)) {
plugin.apply(null, args)
}
接着判断传进来的plugin是一个对象下有一个install函数,是的话就apply调用该函数,第一个参数为上下文,也就是调用人this,在install方法里的this也就变成了plugin,第二个值为刚才拼接的新数组 含vue构造函数和额外参数
installedPlugins.push(plugin)
return this
最后push防止后续二次use插件,并且返回
总结:调用了一个install方法,传递了vue和可选参数,和vue.prototype.$xxx给原型对象上直接赋值对象,调用里的一系列方法差不多,
只不过你可以将它抽离出去,后续引入vue.use(xxx)即可,该插件的install方法会自动给vue.protoType挂载方法
例如下
下面那一块你得写在另一个js文件,然后默认暴露后引入再use,这里图方便直接写在一起了
好的,知道了这个原理,那我们应该也能猜出来vuex的安装原理是啥了,无非就是vuex这个插件,别人写好了,我们来use他帮我们挂载到vue.prototype.$store然后由于原型对象为构造函数的静态方法池
原型对象A上有一个构造函数方法B
构造方法B上也有一个原型对象A (你中有我我中有你了属于是)
构造方法创建出来实例对象C上有一个__proto原型,可以访问构造方法的静态方法池A,
因此new Vue出来的实例,的上下文的this.$store我们就可以用了,该对象上的dispatch啊,commit啊,getter啊,就可以调用这些函数api了,不过此时store实例对象也就是各个模块的js还没创建,因此还得new Vuex store
个人用java进行理解,可能不是很正确,就是给Vue构造函数上注册了一个构造函数,该构造函数就是new Vuex.Store(参数).而这个构造函数在下面的定义 生成实例
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
}
});
此时再将他挂载到 vue构造函数 上,作为参数生成实例,
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
内部一系列实现就不扯了,貌似有一个mixin将store设置到vue上 名为$store,然后在store内部根据 自定义的action mutation做挂载,state挂载到data上,实现响应,getter挂载到computed上 遍历其传入的getters里所有的getterKey给computed[getterKey]设置key
就是将其做一个代理到了this.$store身上,不深究了,二八原则了,如果想冲大厂可以自行查阅源码视频教程或者生啃源码
2.2)具体步骤
下载vuex
npm i vuex@3
vue2用vuex3.用vuex4报错,好像这个给vue3用的
给vue加入插件,生成store实例
分为以下几部分
1.state,就是sql里的表字段
2.mutation,更新state的玩意,通过commit更新,直接修改state不推荐(该对象下的方法使用时通过commit(‘函数名字’,参数))
反面例子如下
- vuex不记录,调试困难,好比你要修改商品价格,不通过web程序还是什么其他方法,而是直接跑到数据库进行修改
- Vue的响应式系统失效,即使修改了state
3.actions, 发异步任务的,也就是触发后里面的方法不会立即修改,
当你有许多地方都使用到了 api异步请求,在请求回调后调用commit,逻辑分散,commit分散,且组件里充斥着许多commit,臃肿,使用后可以统一管理异步,方便查错,抽离更改状态信息的业务代码,组件变得十分干净
4.getter,计算属性,用处不多说了,参考todolist案例的已完成复选框即可
main.js文件
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'
//插件注册
Vue.use(Vuex)
Vue.config.productionTip = false
//创建vuex实例
const store = new Vuex.Store({
// 通过 this.$store.state.count调用
// 或者直接通过引入 import { mapState } from 'vuex'
// mapperState然后在data里...mapperState(直接摊开
state: {
count: 0,
color: '',
salary: 100,
},
// 通过this.$store.commit('peeing',参数)调用,
// 如果是当前页下的action触发直接commit即可,
// 因为已经从当前context上下文解构出了commit,或者另一种方法,context对象作为第一个参数,然后context.commit
mutations: {
increment(state) {
state.count++
},
peeing(state, payload) {
state.color = payload
},
},
//通过this.$store.dispatch('peeing',参数)调用,
// 常见搭配一个api请求,返回一个promise给派发任务的组件一个then回调成功resolve或者reject结果
actions: {
peeing({ commit }, payload) {
setTimeout(() => {
commit('peeing', payload)
}, 2000)
},
},
// 在组件里的computed通过this.$store.getters.doubleSalary获取,
getters: {
//第一种写法
doubleSalary(state) {
return state.salary * 2
},
//第二种写法
doubleSalaryII: (state) => state.salary * 4,
},
})
new Vue({
render: (h) => h(App),
store,
}).$mount('#app')
App文件
<template>
<div id="app">
<button type="" @click="increment">点我count加一</button>{{ $store.state.count }}<br>
<button type="" @click="peeing">上厕所</button><span>上厕所的颜色{{ $store.state.color }}</span><br>
<span>双倍工资 {{ $store.getters.doubleSalary }}</span><br>
<span>四倍工资 {{ $store.getters.doubleSalaryII }}</span><br>
<span>count --- {{ count }}</span><br>
<span>countAlias ---{{ countAlias }}</span><br>
<span>countPlusWithLocalState --- {{ countPlusWithLocalState }}</span><br>
<span>doubleSalaryAlias --- {{ doubleSalaryAlias }}</span><br>
</div>
</template>
<script>
// import { mapState } from 'vuex'
// import { mapGetters } from 'vuex'
// import { mapMutations } from "vuex";
export default {
name: 'App',
data() {
return {
count2: 1
}
},
methods: {
// // 映射vuex的mutation方法,
// ...mapMutations(['increment']),
// // 映射vuex的mutation方法,别名
// ...mapMutations({
// incrementAlias: 'increment'
// }),
increment() {
this.$store.commit('increment')
},
peeing() {
this.$store.dispatch('peeing', 'red')
},
},
// 映射字段方式1,使用传入对象形式,只能获得vuex里的属性,无法在当前组件自定义计算属性
// computed: mapState({
// // 1.使用对象式的箭头函数
// count: state => state.count,
// // 2.给vuex属性起别名
// countAlias: 'count',
// // 3.与当前组件的data进行计算,使用普通函数形式
// countPlusWithLocalState(state) {
// return state.count + this.count2
// }
// })
// 映射字段方式2,y使用传入数组形式,直接传一个字符串,与vuex里的state匹配
// computed: mapState(['count'])
// 映射字段方式3,使用展开运算符将vuex数据拷贝引用到当前computed
// 这样一来,组内的计算属性能与之共存
// computed: {
// doubleSalary() { return this.$store.getters.doubleSalary },
// ...mapState({
// count: 'count',
// // 1.使用对象式的箭头函数
// // count: state => state.count,
// // 2.给vuex属性起别名
// countAlias: 'count',
// // 3.与当前组件的data进行计算,使用普通函数形式
// countPlusWithLocalState(state) {
// return state.count + this.count2
// },
// // countPlusWithLocalState2: function (state) {
// // return state.count + this.count2 + 1
// // }
// })
// }
// 映射字段方式4,使用展开运算符传入数组 开发最常用的
// computed: {
// doubleSalary() { return this.$store.getters.doubleSalary },
// ...mapState(['count'])
// }
// 映射字段方式5,映射getters
computed: {
...mapGetters(['doubleSalary']),
//起别名
...mapGetters({
doubleSalaryAlias: 'doubleSalary'
})
}
}
</script>
3)模块化
如果以后有user,good商品,一大堆全挤在mainjs不是要挤爆炸了吗
我们可以利用ES6的模块语法
export function xxx为具名导出,导入需指定名字,
import {xxx} from 文件路径
如果要起别名就 import {xxx as 别名} from 文件路径
export default function aaa 或者一个对象,导入时可以不指定名字,也可以自定义名字
import 自定义名字 from 路径
还可以先定义变量,然后最后一行export default 变量名
1.文件目录结构为
2.子模块文件创建
先定义四大天王四个对象变量,state mutation actions getters,然后底下export default 暴露对象,内部包含这四个对象
然后index文件里再进行创建store实例,传入一个modules对象,
键为到时候根据什么键寻找该子模块的标识,值为引入的其他子模块的对象。就那四大天王
如果键值同名,简写为一个即可
最后将index里的store实例暴露出去,main js里引入并且作为options参数传入vue构造方法即可
使用模块的属性通过
this.$store.A模块.name
this.$store.getters[‘A模块/getter名’]
摊平写法…mapGetters(‘A模块’, [‘getter名’]),
this.$store.dispatch(‘A模块/actions名’,参数/参数对象);
this.$store.commit(‘A模块/mutations名’,参数/参数对象);
不过该前提应该开启命名空间。在暴露子模块增加一个属性namespace:true
思考
父模块有一个a对象,里有age,那么this.$store.a.age是调用的a模块的age还是父模块的a对象的age呢
子模块会覆盖所有父模块同名的值
代码测试
A模块
const state = {
name: '张三',
money: 100,
}
const mutations = {
addMoney(state, money) {
state.money += money
},
}
const actions = {
waitMoney({ commit }, money) {
setTimeout(() => {
commit('addMoney', money)
}, 1000)
},
}
const getters = {
doubleMoney(state) {
return (state = state.money * 2)
},
getMoney(state) {
return state.money
},
}
export default {
namespaced: true,
state: state /* ==>简写为state */,
mutations,
actions,
getters,
}
B模块
const state = {
name: '李四',
money: 200,
}
const mutations = {
addMoney(state, money) {
state.money += money
},
}
const actions = {
waitMoney({ commit }, money) {
setTimeout(() => {
commit('addMoney', money)
}, 1000)
},
}
const getters = {
doubleMoney(state) {
return (state = state.money * 2)
},
getMoney(state) {
return state.money
},
}
export default {
namespaced: true,
state /* ==>简写为state */,
mutations,
actions,
getters,
}
index模块
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
import A from "../store/module/A";
import B from "../store/module/B";
const store = new Vuex.Store({
modules: {
A,
B
}
})
export default store
App
<template>
<div id="app">
<!-- <button type="" @click="increment">点我count加一</button>{{ $store.state.count }}<br>
<button type="" @click="peeing">上厕所</button><span>上厕所的颜色{{ $store.state.color }}</span><br>
<span>双倍工资 {{ $store.getters.doubleSalary }}</span><br>
<span>四倍工资 {{ $store.getters.doubleSalaryII }}</span><br>
<span>count --- {{ count }}</span><br>
<span>countAlias ---{{ countAlias }}</span><br>
<span>countPlusWithLocalState --- {{ countPlusWithLocalState }}</span><br>
<span>doubleSalaryAlias --- {{ doubleSalaryAlias }}</span><br> -->
<span>A模块的name{{ $store.state.A.name }}</span><br>
<span>B模块的name{{ $store.state.B.name }}</span><br>
<button type="" @click="Amutation(100)">触发A模块的mutation</button>
<span>A的钱{{ $store.getters['A/getMoney'] }}</span><br>
<button type="" @click="Bmutation(22)">触发B模块的mutation</button>
<span>B的钱{{ $store.getters['B/getMoney'] }}</span><br>
</div>
</template>
<script>
// import { mapState } from 'vuex'
// import { mapGetters } from 'vuex'
import { mapMutations } from "vuex";
export default {
name: 'App',
data() {
return {
count2: 1
}
},
methods: {
// // 映射vuex的mutation方法,
// ...mapMutations(['increment']),
// // 映射vuex的mutation方法,别名
// ...mapMutations({
// incrementAlias: 'increment'
// }),
...mapMutations({
Amutation: 'A/addMoney',
Bmutation: 'B/addMoney'
}),
increment() {
this.$store.commit('increment')
},
peeing() {
this.$store.dispatch('peeing', 'red')
},
},
// 映射字段方式1,使用传入对象形式,只能获得vuex里的属性,无法在当前组件自定义计算属性
// computed: mapState({
// // 1.使用对象式的箭头函数
// count: state => state.count,
// // 2.给vuex属性起别名
// countAlias: 'count',
// // 3.与当前组件的data进行计算,使用普通函数形式
// countPlusWithLocalState(state) {
// return state.count + this.count2
// }
// })
// 映射字段方式2,y使用传入数组形式,直接传一个字符串,与vuex里的state匹配
// computed: mapState(['count'])
// 映射字段方式3,使用展开运算符将vuex数据拷贝引用到当前computed
// 这样一来,组内的计算属性能与之共存
// computed: {
// doubleSalary() { return this.$store.getters.doubleSalary },
// ...mapState({
// count: 'count',
// // 1.使用对象式的箭头函数
// // count: state => state.count,
// // 2.给vuex属性起别名
// countAlias: 'count',
// // 3.与当前组件的data进行计算,使用普通函数形式
// countPlusWithLocalState(state) {
// return state.count + this.count2
// },
// // countPlusWithLocalState2: function (state) {
// // return state.count + this.count2 + 1
// // }
// })
// }
// 映射字段方式4,使用展开运算符传入数组 开发最常用的
// computed: {
// doubleSalary() { return this.$store.getters.doubleSalary },
// ...mapState(['count'])
// }
// 映射字段方式5,映射getters
// computed: {
// ...mapGetters(['doubleSalary']),
// //起别名
// ...mapGetters({
// doubleSalaryAlias: 'doubleSalary'
// })
// }
}
</script>
main.js
import Vue from 'vue'
import App from './App.vue'
import Vuex from 'vuex'
//插件注册
Vue.use(Vuex)
Vue.config.productionTip = false
//创建vuex实例,单模块模式
// const store = new Vuex.Store({
// // 通过 this.$store.state.count调用
// // 或者直接通过引入 import { mapState } from 'vuex'
// // mapperState然后在data里...mapperState(直接摊开
// state: {
// count: 0,
// color: '',
// salary: 100,
// },
// // 通过this.$store.commit('peeing',参数)调用,
// // 如果是当前页下的action触发直接commit即可,
// // 因为已经从当前context上下文解构出了commit,或者另一种方法,context对象作为第一个参数,然后context.commit
// mutations: {
// increment(state) {
// state.count++
// },
// peeing(state, payload) {
// state.color = payload
// },
// },
// //通过this.$store.dispatch('peeing',参数)调用,
// // 常见搭配一个api请求,返回一个promise给派发任务的组件一个then回调成功resolve或者reject结果
// actions: {
// peeing({ commit }, payload) {
// setTimeout(() => {
// commit('peeing', payload)
// }, 2000)
// },
// },
// // 在组件里的computed通过this.$store.getters.doubleSalary获取,
// getters: {
// //第一种写法
// doubleSalary(state) {
// return state.salary * 2
// },
// //第二种写法
// doubleSalaryII: (state) => state.salary * 4,
// },
// })
//创建vuex实例 多模块模式
import store from "./store/index";
new Vue({
render: (h) => h(App),
store,
}).$mount('#app')