likes
comments
collection
share

手写基础 vuex

作者站长头像
站长
· 阅读数 60

闲来无事 手搓一版基础的vuex 主旨在于理解vuex最核心的功能逻辑 跟实际源码出入比较大 了解一下即可 让新手同学对vuex有个初步了解

先从引入vuex开始:

个人习惯会 在入口文件 通过引入 './store/base.js' 文件 获取 store 实例 并注入vue根实例中

手写基础 vuex

在 './store/base.js' 中代码如图所示:其中 Vuex 就是这次手写的部分

手写基础 vuex

根据上图分析如下:

  • 需要定义一个返回 install 方法的对象 (需要了解 Vue.use 的用法)

  • 我们需要定义一个Store类 并通过 new 来创建 store 实例同时在创建时传入 store 的配置 包括 state, getters, mutations, actions 等

开始行动:

第一步 定义 index.js 入口文件实现 install 与 Store 导出

// index.js
import install from './install'
import Store from './store'

export default {
  install,
  Store
}

第二步 实现 install 方法


// install.js

export let Vue  // 在vuex代码中使用的Vue 与外层项目一致 此处导出可以在其他文件使用

function install(vue){
  Vue = vue  // install方法接收vue 并赋值给Vue 
  
  // 通过mixin混入在每一vue实例中添加 beforCreate方法 
  // 有兴趣的同学可以了解一下vue 的 mergeOptions 操作,深入了解一下 混入以及合并策略
  Vue.mixin({  
    beforeCreate(){
      const options = this.$options
      if(options.store){ //只有根实例上我们注入了store,options.store存在证明这是根实例
        this.$store = options.store //在vue根实例添加$store
      }else{
        //已知根实例上已经有了$store 其他的vue一般都在根实例内部 所以我们通过$parent向上查找
        //联想一下 出列在根实例外创建的vue 是不是都能链式的拿到 根上定义的那个$store
        //由于是对象引用 所以所有的vue实例中定义的$store都指向根的$store store实例只需一个即可
        if(this.$parent && this.$parent.$store){
          this.$store = this.$parent.$store
        }
      }
    }
  })
}

export default install

通过 vue.mixin 我们在每一个vue实例中都能获取到$store对象 我们再来看看Store类做了什么

第三步 实现 Store 类


// store.js
import { Vue } from '../install'  //从install中获取导出的Vue 


class store{
  constructor(options) {
    // new Vue.Store({....}) 时传入 options 解构 options 
    let { state, getters = {}, mutations, actions } = options
    
    //当我们修改store.state 属性值时需要触发页面更新操作 所以这里需要对state做响应式处理
    //state响应式原理很巧妙的 使用new Vue实例来实现
    const vueOptions = {
      data: {
       //命名为$$state 第一:防止重复 第二 以$开头的属性vue在做proxy时会忽略
       //所以 不能通过 this.$$state直接访问到 需要通过this._data.$$state访问
        $$state:state 
      }
    }
    // 初始化 getters getter实际就是vue computed的getter
    this.initGetters(vueOptions, getters)
    this.initMutations(mutations)
    this.initActions(actions)
    this._vm = new Vue(vueOptions)
    /* 在 dispatch 调用 actions 时 内部使用commit或者dispatch时会出现this绑定问题 在这里手动绑定一下 或者可以使用箭头函数*/
    this.commit = this.commit.bind(this)
    this.dispatch = this.dispatch.bind(this)
  }

  /* (代理模式) 可以通过 this.state 访问this._vm._data.$$state */
  get state(){
    return this._vm._data.$$state
  }

  //store commit 方法  初始化未手动绑定this时此处可以使用箭头函数确保 this指向;
  // 如:commit = (type,payload)=> ... 
  commit(type, payload){
    return this.mutations[type](payload)
  }

  /* store dispatch 方法  this 绑定问题同上 */
  dispatch(type, payload){
    return this.actions[type](payload)
  }

  /* getters 使用computed来实现 并注入 state 参数  */
  initGetters(vueOptions, getters){
    const computed = {}
    this.getters = {}
    // 遍历getters生成compute 并且 传入this.state 使用getter时可以拿到state
    Object.keys(getters).forEach(key =>{
      computed[key] = ()=>{ 
        return getters[key].call(this,this.state)
      }
      // 对getters做一层代理 在 vue中使用时 通过访问getter的key 实际获取的是 store实例中_vm的computed
      Object.defineProperty(this.getters, key, { 
        get:()=>{
          return this._vm[key]
        }
      })
    })
    vueOptions.computed = computed
  }
  /* 初始化 mutations 并注入 state */
  initMutations(mutations){
    this.mutations = {}
    Object.keys(mutations).forEach(key =>{
      this.mutations[key] = (payload)=>{
        return mutations[key].call(this,this.state, payload)
      }
    })
  }
  /* 初始化 actions 并注入 store (包括: state, commit dispatch...) */
  initActions(actions){
    this.actions = {}
    Object.keys(actions).forEach(key =>{
      this.actions[key] = (payload)=>{
        return actions[key].call(this,this,payload)
      }
    })
  }
}

export default store

描述一下上面的流程:

  • 解构 options 获取 用户定义的 state,getters,mutations,actions
  • 初始化 state 将 state 做成响应式数据 在state发生变化时触发组件更新
  • 初始化 getters 每一个getter就是一个computed 只是多了一个可以接收state值 同时需要对getters做代理 当通过this.$store.getters.xxx时能代理到 store._vm的computed属性上去
  • 初始化 mutations 遍历mutations 重新定义方法 (payload)=>{xxx.call(this,state,payload)} 方式实现 commit 中 state参数以及payload参数
  • 初始化 actions 同mutations一样传入 stroe 实例 在dispatch中都能获取到 state属性 以及 commit dispatch 方法
  • 定义 commit 方法 通过 $store.commit(type,payload) 找到对应的mutation并执行 在mutation中 修改state 会触发响应式更新
  • 定义 dispatch 方法 通过 $store.dispatch(type,payload) 找到对应的actions并执行

到此为止 简单版本的 vuex 就实现了 可以在项目中尝试一下 这里没有考虑 modules 严格模式 插件等功能 后续会出一版 包含这些功能的版本 这里写的比较粗糙 主旨还是为了扫盲 了解一下 vuex 即可

转载自:https://juejin.cn/post/7360879591236419618
评论
请登录