宝刀未老,vuex基本原理&手写简单的vuex

218 阅读2分钟

这是我参与更文挑战的第 8 天,活动详情查看: 更文挑战

2021-06-08 原创 TANGJIE Vue2 Vuex

宝刀未老,vuex基本原理&手写简单的vuex

想要写一个简单的vuex,就必须先知道vuex是干什么,设计目标是什么,怎么用的;只有这样,站在巨人的肩膀上我们才能变的更强(脑袋变得更秃~~~

1. Vuex基本概念

Vuex 是集中式管理存储应用的所有组件状态的一个库,它可以以相应的规则保证状态以可以预测的方式发生改变

1.1 基本概念

  • state,状态与数据
  • mutations 这个单词的意思就是变化的意思,是state的更改函数
  • actions 异步行为
  • store 包含上面概念的容器

1.2 基本使用

先上一张图:

4-1.png

其实使用很简单的:

  1. 在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
  }
})

  1. 组件下使用(基本使用)
...
// 直接获取到store里面state的属性值
this.$store.state.name
...
methods:{
	// 直接使用 mutation
  	changeStateName(){
  		this.$store.commit('setName','小明喜欢上了小红')
  	},
  	// 直接使用action
  	afterSecondsChangeName(){
  		this.$store.dispatch('launchSetName','两秒之后小红被吓跑了')
  	},
}
...

看到这里对照上面的图,是不是一下子就清晰了起来,原来使用就这么简单~

  1. 组件下使用(使用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来触发Storemutations来看,commit接受2个参数,一个是storemutation的属性名,一个是载荷参数,然后就能触发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
};