一、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.use
API 要求传入的参数是一个对象或函数,如果是对象的话对象里需要包含一个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