本文将简单实现 vuex
中的 state
、mutations
、actions
和getters
核心步骤
先简单回顾一下 vuex
vuex
集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以可预测的方式发生变化
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
// 每个组件通过 this.$store 访问仓库
Vue.use(Vuex)
export default new Vuex.store({
// 状态应该是响应式的
// 在组件中访问值格式是 this.$store.state.xxx
state: {
counter: 0
},
mutations: {
add (state) {
// state 从哪来?
// 为什么直接对其中的数据进行操作就能响应式地改变?
state.counter++
}
},
actions: {
add ({ commit }) {
// 第一个参数是什么?从哪来的?
setTimeout(() => {
commit('add')
}, 1000)
}
},
getters: {
doubleCounter (state) {
return state.counter * 2
}
}
})
views/HelloWorld.vue
<template>
<div class="hello">
<p @click="$store.commit('add')">counter: {{$store.state.counter}}</p>
<p @click="$store.dispatch('add')">async counter: {{$store.state.counter}}</p>
<p>{{$store.getters.doubleCounter}}</p>
</div>
</template>
手撸 vuex
store/index.js
import Vue from 'vue'
// import Vuex from 'vuex'
import Vuex from '../myVuex'
// 每个组件通过 this.$store 访问仓库
Vue.use(Vuex)
export default new Vuex.store({
// 状态应该是响应式的
// 在组件中访问值格式是 this.$store.state.xxx
state: {
counter: 0
},
mutations: {
add (state) {
// state 从哪来?
// 为什么直接对其中的数据进行操作就能响应式地改变?
state.counter++
}
},
actions: {
add ({ commit }) {
// 第一个参数是什么?从哪来的?
setTimeout(() => {
commit('add')
}, 1000)
}
},
getters: {
doubleCounter (state) {
return state.counter * 2
}
}
})
main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
myVuex/index.js
// 1.实现插件,挂载 $store
// 2.实现 Store
let Vue
// 在 store/index.js 中进行的 new 操作
class Store {
constructor (options) {
// 对 data 进行响应式处理
// 这里用一个巧妙的方法,利用 new Vue({data: xxx}) 实现对传入数据的响应式绑定
// 因为 Vue 会对它的所有数据进行响应式处理
// 在源码中也运用了这个方法
// 在每个组件中通过 this.$store.state.xx 访问
this._vm = new Vue({
data: {
$$state: options.state
}
})
// 保存用户定义的 mutations 和 actions
this._mutations = options.mutations
this._actions = options.actions
// 定义 getters
this.getters = {}
Object.keys(options.getters).forEach((g) => {
Object.defineProperty(this.getters, g, {
get: () => options.getters[g](this.state)
})
})
// 最好强绑定一下 this,特别是异步时
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
get state () {
// 在 Vue 中,_data 可以映射到上述的 data
// 在这里先想成是 Vue 的黑魔法完成的
// 两个 $$ 不会被代理
return this._vm._data.$$state
}
set state () {
console.error('Please use derived state to reset state')
}
commit (type, payload) {
const entry = this._mutations[type]
if (!entry) {
console.error('Unknown mutation type')
return
}
// 此处解答了为什么在 mutations 中可以直接访问 state 并直接修改
entry(this.state, payload)
}
dispatch (type, payload) {
const entry = this._actions[type]
if (!entry) {
console.error('Unknown mutation error')
return
}
entry(this, payload)
}
}
function install (_Vue) {
Vue = _Vue
Vue.mixin({
beforeCreate () {
// 在 main.js 中初始化根组件时会传入 store
if (this.$options.store) {
/*
new Vue({
router,
store.
render: h => h(App)
})
*/
Vue.prototype.$store = this.$options.store
}
}
})
}
// 之所以用这种形式是因为在 store 的使用形式是
// new Vuex.store({xxx})
export default { Store, install }
在 getters
的实现上,其实在源码中还利用了 Vue
的计算属性的缓存特性,在本文中只是简单实现了一下,还是推荐大家看一下源码
非常感谢你能看到这里!