这是我参与更文挑战的第 8 天,活动详情查看: 更文挑战
2021-06-08 原创 TANGJIE Vue2 Vuex
宝刀未老,vuex基本原理&手写简单的vuex
想要写一个简单的vuex,就必须先知道vuex是干什么,设计目标是什么,怎么用的;只有这样,站在巨人的肩膀上我们才能变的更强(脑袋变得更秃~~~
)
1. Vuex基本概念
Vuex 是集中式管理存储应用的所有组件状态的一个库,它可以以相应的规则保证状态以可以预测的方式发生改变
1.1 基本概念
- state,状态与数据
- mutations 这个单词的意思就是变化的意思,是state的更改函数
- actions 异步行为
- store 包含上面概念的容器
1.2 基本使用
先上一张图:
其实使用很简单的:
- 在src下创建一个store目录用来存放vuex代码文件(习惯上vuex的modules部分我喜欢放在各种的路由组件下面),然后写上state,mutations,actions等代码
import Vue from 'vue'
import Vuex from 'vuex'
import modelone from './modelone'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
name:'测试',
},
mutations: { // 同步方法
setName(state,proload){
state.name = proload
}
},
actions: {
launchSetName(ctx,proload){ //异步方法
// 2秒之后改
setTimeout(()=>{
ctx.commit('setName',proload)
},2000)
}
},
modules: {
modelone
}
})
- 组件下使用(基本使用)
...
// 直接获取到store里面state的属性值
this.$store.state.name
...
methods:{
// 直接使用 mutation
changeStateName(){
this.$store.commit('setName','小明喜欢上了小红')
},
// 直接使用action
afterSecondsChangeName(){
this.$store.dispatch('launchSetName','两秒之后小红被吓跑了')
},
}
...
看到这里对照上面的图,是不是一下子就清晰了起来,原来使用就这么简单~
- 组件下使用(使用
vuex
给的mapXXX
去使用)
...
computed:{
// 映射state的属性值
...mapState({
name:state => state.name
})
},
methods:{
// map方式使用mutation
...mapMutations({
changeMapStateName: 'setName'
}),
// map方式使用action
...mapActions({
launchSetName:'launchSetName'
})
}
...
1.3 vuex使用场景的思考
vuex虽然对于跨层级状态共享很舒服,但是维护,代码迁移等其实还是挺麻烦的。个人觉得不宜把过多的状态属性都保存在Vuex里面的,对于路由组件的全局属性,其实使用provide/inject
要比使用vuex更丝滑。
然后很多开发实践里面喜欢吧后端异步请求的数据都纳入vuex
状态管理,在 Action
中封装数据的增删改查等逻辑,这样可以一定程度上对前端的逻辑代码进行分层,使组件中的代码更多地关注页面交互与数据渲染等视图层的逻辑,而异步请求与状态数据的持久化等则交由 vuex
管理vue
,对这种做法我持保留意见的态度,后端api管理完全可以由一个独立的api文件去管理。如果是后台数据的持久化,keep-alive
是更好的选择
发展到现在我倒是觉得vuex最大的作用就是在ssr方面的作用了。(个人意见)
2. 开发一个简单的Vuex
通过上面了解了vuex的基本概念和vuex的使用,就开始从使用的角度入手,进行一个简易的vuex开发。以方便我们更加深入透彻的理解vuex
2.1 第一步建立一个install方法
在这个install
方法里面,要实现获取到Store的实例,并挂载到Vue的prototype上,这里的一个比较重要的问题是不知道Vue.use(Vuex.Store)
出现的时机是在new Store()
之前还是之后,为了确保能获取到Store的实例,因此一般如下处理
const install = ( Vue )=>{
vueComponent = Vue;
Vue.mixin({
beforeCreate(){
if(this.$options.store){
Vue.prototype.$store = this.$options.store
}
}
})
}
这里可能有人就有疑问?为什么从this.$options.store里面就能拿到Store的实例,好看Vue使用Vuex的代码
...
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
...
从上面的代码我们可以清楚的看到,被实例化的Store是直接new Vue(...)
这样初始化进去的,而vm.$options
是用于当前 Vue
实例的初始化选项,而Vue.mixin
是混入到Vue的所有实例,但是只有跟实例被初始化了Store实例。
2.2 构建Store类
从Vuex的使用角度来看,构建一个Store类就很容易了
class Store{
constructor( options ){
// 建立Store的state属性,因为state是响应式的因此还要响应式化
this.state = options.state
vueComponent.observable(this.state);
// 建立Store的actions属性
this.actions = options.actions
// 建立Store的mutations属性
this.mutations = options.mutations
// 绑定this
this.commit.bind(this);
this.dispatch.bind(this);
}
commit(){}
dispatch(){}
}
2.3 确立commit
方法功能
从我们使用commit
来触发Store
的mutations
来看,commit
接受2个参数,一个是store
下mutation
的属性名,一个是载荷参数,然后就能触发mutations
上的函数了。为此有如下代码
...
commit(type,payload){
if( this.mutations[type] ){
this.mutations[type](payload)
}
}
...
actions
的原理也和mutation
的差不多!
2.4 完整代码
let vueComponent;
class Store {
constructor( options ){
console.log( this === Store.constructor,this ,'this')
this.mutations = options.mutations;
this.actions = options.actions;
this.state = options.state;
// 绑定this,上下文一直指向store实例
this.commit.bind( this );
this.dispatch.bind( this );
// 将state响应式化
vueComponent.observable(this.state);
}
commit(type,payload){
const entry = this.mutations[type];
if(entry){
entry(this.state,payload)
}
}
dispatch(type,payload){
const entry = this.actions[type];
if(entry){
entry(this,payload)
}
}
}
const install = ( Vue )=>{
vueComponent = Vue;
Vue.mixin({
beforeCreate(){
if(this.$options.store){
// 把store挂载到vue的原型上
// 为什么要这样去呢,这是因为 Vue用插件是使用的Vue.use
// Vue.use出现的时机往往比Store实例化的时机要快
// 因此需要用生命周期去保证获取到的是Store的实例
Vue.prototype.$store = this.$options.store
}
}
})
}
export default{
Store,
install
};