手写简易版Vuex,初探Vuex原理

一、Vuex是如何使用的

Vue项目中,使用vuex的步骤大概如下:

  1. src下创建一个store/index.js,然后添加如下代码
js 复制代码
// src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {}
})
  1. main.js引入
js 复制代码
import Vue from 'vue'
import App from './App.vue'
import store from './store/index'

new Vue({
  store,
  render: h => h(App)
}).$mount('#app')

然后就可以通过this.$store访问了。

二、开始手写

1、创建文件

src/store下新建一个自己手写的文件vuex.js。通过前面官方vuex的使用可以知道,vuex主要用到了两个东西:Vue.use()new Vuex.Store()

  • Vue.useAPI 要求传入的参数是一个对象或函数,如果是对象的话对象里需要包含一个install方法。

  • new Vuex.Store({})则需要我们创建一个Store类。

所以我们首先需要创建如下最基本的满足条件:一个Store类和一个install方法。

js 复制代码
// src/store/vuex.js
class Store {
    constructor() {}
}

const install = (Vue) => {}

const vuex = {
  Store,
  install
}

export default vuex

然后替换掉之前引入的官方vuex,改成我们自己实现的vuex

js 复制代码
// src/store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from './vuex.js'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    name: '张三'
  },
  getters: {},
  mutations: {},
  actions: {}
})

替换后发现一切正常,没什么报错信息出现,接下来我们一步一步来实现功能。

2、实现install

前面在main.js里我们仅仅是在根组件注入了store,我们希望所有组件都能拥有store,所以我们可以使用Vue.mixin进行全局混入。

js 复制代码
const install = (Vue) => {
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) { // 根组件
        this.$store = this.$options.store
      } else if (this.$parent && this.$parent.$store) { // 子组件
        this.$store = this.$parent.$store
      }
    }
  })
}

$options里可以获取我们一开始在根组件里注入的store,然后赋值给$store。 由于父组件的beforeCreate执行先于子组件的beforeCreate,所以我们在beforeCreate里可以拿到父组件已经存在的$store并将其赋值给子组件,使得每个组件都拥有$store,都可以通过this.$store调用。

3、实现state

现在我们通过this.$store.state是无法取到值的,前面我们通过new传入了一个对象:

css 复制代码
{
  state: {
    name: '张三'
  },
  getters: {},
  mutations: {},
  actions: {}
}

所以我们可以在类的构造器里获得这个对象参数。

js 复制代码
class Store {
  constructor(options) {
    this.state = options.state || {}
  }
}

打印this.$store.state

到这里我们的工作还没完成,因为state是支持响应式的,如何让state实现响应式?我们都知道Vue里的data是响应式的,所有我们可以借助它来实现我们想要的效果。

js 复制代码
class Store {
  constructor(options) {
    // this.state = options.state || {}
    this._vm = new Vue({
      data: {
        state: options.state || {}
      }
    })
  }

  get state() {
    return this._vm.state
  }
}

state传入new Vue()里的data,这样我们便很容易的将state变成了一个响应式数据,此时我们便可以通过this.$store._vm.state获取数据了,但为了方便,后面我们增加get接口并返回_vm.state,方便使用时可以通过this.$store.state取值。

接下来简单测试一下是否生效:

html 复制代码
<div>{{ $store.state.name }}</div>
js 复制代码
created() {
    setTimeout(() => {
      // 实际使用中不提倡这种直接修改state的状态
      this.$store.state.name = '李四'
    }, 2000)
},

2秒后可以看到页面视图数据从张三变成了李四,说明我们的state响应式是OK的。

4、实现getters

先看看我们是如何使用getters的:

js 复制代码
{
  state: {
    name: '张三'
  },
  getters: {
    getHello: (state) => {
      return `hello,${state.name}`
    }
  },
  mutations: {},
  actions: {}
}

可以看到getters对象里实际是一些函数,函数参数里可以拿到state,所以我们在实现时需要把state作为参数抛出来。

js 复制代码
class Store {
  constructor(options) {
    // this.state = options.state || {}
    this._vm = new Vue({
      data: {
        state: options.state || {}
      }
    })
    // 新增代码
    const getters = options.getters || {}
    this.getters = {}
    Object.keys(getters).forEach(key => {
      Object.defineProperty(this.getters, key, {
        get: () => {
          return getters[key](this.state)
        }
      })
    })
  }

  get state() {
    return this._vm.state
  }
}

因为我们在使用gettters时是不需要写括号的:this.$store.getters.getHello,所以采用Object.definePropertyget接口实现。

5、实现mutations

mutations是通过commit触发的,因此我们在将传入的mutations对象存储起来后还需要实现commit方法,然后commit根据第一个参数的值触发mutations里对应的方法。

js 复制代码
this.$store.commit('changeName', payload)
js 复制代码
mutations: {
  changeName(state, payload) {
    state.name = payload
  }
}

新增mutations实现代码:

js 复制代码
class Store {
  constructor(options) {
    // 省略前面的代码....

    const mutations = options.mutations || {}
    this.mutations = {}
    Object.keys(mutations).forEach(key => {
      // 将函数存储进this.mutations里
      this.mutations[key] = (param) => {
        mutations[key](this.state, param)
      }
    })
  }

  commit = (type, param) => {
    // 执行this.mutations里对应函数
    this.mutations[type](param)
  }

  get state() {
    return this._vm.state
  }
}

6、实现actions

actionsmutations类似,不同的是actions是通过dispatch调用,在actions里的方法的第一个参数是Store

js 复制代码
mutations: {
  changeName(state, payload) {
    state.name = payload
  }
},
actions: {
  changeNameAction({ commit }, payload) {
    commit('changeName', payload)
  }
}
js 复制代码
setTimeout(() => {
  this.$store.dispatch('changeNameAction', '李四')
}, 3000)

新增actions实现代码:

js 复制代码
class Store {
  constructor(options) {
    // 省略前面的代码....

    const actions = options.actions || {}
    this.actions = {}
    Object.keys(actions).forEach(key => {
      this.actions[key] = (param) => {
        actions[key](this, param)
      }
    })
  }

  dispatch = (type, param) => {
    this.actions[type](param)
  }

  get state() {
    return this._vm.state
  }
}

三、完整代码

js 复制代码
import Vue from 'vue'

class Store {
  constructor(options) {
    this._vm = new Vue({
      data: {
        state: options.state || {}
      }
    })
    
    const getters = options.getters || {}
    this.getters = {}
    Object.keys(getters).forEach(key => {
      Object.defineProperty(this.getters, key, {
        get: () => {
          return getters[key](this.state)
        }
      })
    })

    const mutations = options.mutations || {}
    this.mutations = {}
    Object.keys(mutations).forEach(key => {
      this.mutations[key] = (param) => {
        // 将函数存储进this.mutations里
        mutations[key](this.state, param)
      }
    })
    
    const actions = options.actions || {}
    this.actions = {}
    Object.keys(actions).forEach(key => {
      this.actions[key] = (param) => {
        actions[key](this, param)
      }
    })
  }

  dispatch = (type, param) => {
    this.actions[type](param)
  }

  commit = (type, param) => {
    // 执行this.mutations里对应函数
    this.mutations[type](param)
  }

  get state() {
    return this._vm.state
  }
}

const install = (Vue) => {
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) { // 根组件
        this.$store = this.$options.store
      } else if (this.$parent && this.$parent.$store) { // 子组件
        this.$store = this.$parent.$store
      }
    }
  })
}

const vuex = {
  Store,
  install
}

export default vuex
相关推荐
web守墓人1 小时前
【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库
前端·javascript·html
秋田君6 小时前
深入理解JavaScript设计模式之命令模式
javascript·设计模式·命令模式
风吹落叶花飘荡8 小时前
2025 Next.js项目提前编译并在服务器
服务器·开发语言·javascript
加减法原则8 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js
yanlele8 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
天若有情6738 小时前
React、Vue、Angular的性能优化与源码解析概述
vue.js·react.js·angular.js
烛阴9 小时前
WebSocket实时通信入门到实践
前端·javascript
草巾冒小子9 小时前
vue3实战:.ts文件中的interface定义与抛出、其他文件的调用方式
前端·javascript·vue.js
追逐时光者9 小时前
面试第一步,先准备一份简洁、优雅的简历模板!
后端·面试
DoraBigHead9 小时前
你写前端按钮,他们扛服务器压力:搞懂后端那些“黑话”!
前端·javascript·架构