手写简易版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
相关推荐
HeiSenBerg3 小时前
Android 网络协议全解析:从 TCP 三次握手到 HTTPS 加密,一篇就够了
面试
jarvisuni4 小时前
DeepSeekFlash前端依旧拉垮,而且变慢了很多!
前端·javascript·算法
破z晓5 小时前
javascript 导出excel表
开发语言·javascript·excel
小罗水7 小时前
第17章 后台管理端与演示支持
javascript·vue.js·ecmascript
乐思智能科技有限公司7 小时前
PLECS软件学习使用(二)直流电机基本系统模型
人工智能·算法·机器学习·面试·职场和发展
GitLqr7 小时前
深度拆解 Dart 事件循环:从面试题看清 Microtask 与 Event Queue 的执行顺序
flutter·面试·dart
haerapi8 小时前
用 System V 共享内存实现本机高速 IPC:从原理到可运行环形队列
面试
万少8 小时前
DeepSeek-V4-Flash 正式版上线了,但这 3 个坑我帮你提前踩了
前端·javascript·后端
宁风NF8 小时前
JavaScript:网络请求与前端通信
开发语言·前端·javascript·网络·学习·ecmascript
吾儿良辰9 小时前
Flutter 迎来重大变革:Material 设计系统拆分,迈向更开放的 UI 框架
javascript·flutter·ui