一、Vuex是如何使用的
在Vue项目中,使用vuex的步骤大概如下:
- 在
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: {}
})
- 在
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.defineProperty的get接口实现。
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
actions与mutations类似,不同的是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