这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
更多文章
[vue2]熬夜编写为了让你们通俗易懂的去深入理解nextTick原理
[vue2]熬夜编写为了让你们通俗易懂的去深入理解vue-router并手写一个
[vue2]熬夜编写为了让你们通俗易懂的去深入理解v-model原理
[vue2]熬夜编写为了让你们通俗易懂的去深入理解双向绑定以及解决监听Array数组变化问题
熬夜不易,点个赞再走吧
思路
在main.js中
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store/index'
Vue.config.productionTip = false
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
声明了store,是为了在实例中能够访问
在/store/index中
Vue.use(Vuex);
install 注册vuex实例,让外部组件可以轻松通过this.$store获取。并且获取状态并且修改状态
因为在main.js中引用了它,所以需要导出一个Store
export default new Vuex.Store({
state: {},
mutations:{},
actions:{},
modules:{},
getters:{}
});
state
单一状态树,保存数据,和Vue实例中data遵循相同规则
mutations
- mutation必须是同步的
- 在 mutation 中混合异步调用会导致程序很难调试
- 为了区分有了action的概念
actions
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作
modules
如果所有数据保存在一个对象,以后会很臃肿,所以有了模块化,模块的集合,就是modules
getters
store的计算属性
测试
现在我们给state中声明一个count,并且在mutation和action中写入能改变count的方法
state: {
count:0
},
mutations:{
add(state){
state.count++
}
},
actions:{
asyncAdd({commit}){
setTimeout(() => {
commit('add')
}, 1000);
}
}
开始
这里测试store运行正常,接下来需要自己手写vuex这几个功能并且让它运行正常,现在新建一个vuex.js
,并自行改一下路径Vuex的导入路径
这里因为功能是在Vuex.Store中实现,包含state, getters, muations, action 等可选属性
而Vuex要用install进行注册
所以这里导出一个Store和一个install
// 实现Vuex.use(Vuex)中的Vuex插件
let Vue
class Store {
constructor(options) {
console.log(Vue,options)
}
}
function install(_Vue) {
Vue = _Vue
}
export default { Store, install }
注册store
这里跟注册vue-router一样
具体参考我上一篇文章手写vue-router
利用mixin混入在尽早的生命周期内进行注册挂载
// 注册$store
Vue.mixin({
beforeCreate() {
if(this.$options.store){
console.log(this.$options.store)
Vue.prototype.$store = this.$options.store
}
},
})
这个时候已经注册了store,但是却获取不了count
count在state里,所以我们需要一个state
state
加上$$,目的是藏起来不让Vue做代理,不让直接通过state去访问它,具体源码在vuex.js:689行
this._vm = new Vue({
data(){
return{
$$state: options.state
}
}
})
那怎么去访问呢,提供了一个get/set的方法,具体源码在vuex.js:443-451行
get state(){
console.log(this._vm,'this._vm')
return this._vm._data.$$state
}
set state(v){
console.error("不能直接覆盖state,可以使用过replaceState");
}
现在刷新一下,发现可以打印出来并且不报错了
mutation
我们看到打印的options里有这些东西
那我们为了方便,把它提出来,后面会用上
constructor(options) {
this._mutations = options.mutations
this._actions = options.actions
}
通过mutation的使用方法看,会通过commit进入到mutation的方法里
而mutation的方法里会接收两个参数
'add'是对应的方法名
可以接收到的是状态树state和载荷playload
// commit('add', 1)
// ----------------
// mutations:{
// add(state, n){
// state.count += n
// }
// },
所以我们需要在里面写入commit的方法
// commit
commit(type, payload){
console.log(type, payload,'type, payload')
const mutation = this._mutations[type]
if(!mutation){
console.error("找不到对应的mutation")
return
}
}
然后在commit的操作里,我们最后会进入到mutation里 加入以下代码
// commit通过type找到mutations中对应的方法后,会把参数传给它
mutation(this.state, payload)
最后的commit是这样
// commit
commit(type, payload){
console.log(type, payload,'type, payload')
const mutation = this._mutations[type]
if(!mutation){
console.error("找不到对应的mutation")
return
}
// commit通过type找到mutations中对应的方法后,会把参数传给它
mutation(this.state, payload)
}
action
接下来是action,action跟mutation的区别是可跟踪和不可跟踪,异步和同步,所以有异曲同工之妙,如此如此,cv一下
// dispatch
dispatch(type, payload){
const action = this._actions[type]
if(!action){
console.error("找不到对应的action")
return
}
}
这里与commit中的传参有些变化,action中接收的上下文ctx中包含commit, dispatch, state等,因此第一个参数要改为this
mutation(this, payload)
最后是
// dispatch
dispatch(type, payload){
const action = this._actions[type]
if(!action){
console.error("找不到对应的action")
return
}
// 这里与commit中的传参有些变化,action中接收的上下文ctx中包含commit, dispatch, state等,因此第一个参数要改为this
action(this, payload)
}
这里执行下会报错
[Vue warn]: Error in v-on handler: "ReferenceError: mutation is not defined"
为什么呢,因为之前store里我们写到
actions:{
asyncAdd({commit}){
setTimeout(() => {
commit('add')
}, 1000);
}
},
这里面包含setTimeOut这种方法时,this容易混乱,那么需要绑定一下this,让它指向store实例
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
这样再次执行就成功了
getters
这里还是像保存_mutations和_actions一样
constructor(options) {
this._getters = options.getters
}
然后它本身相当于是个计算属性,那么会想到computed
在_vm里声明一下
this._vm = new Vue({
data() {
return {
$$state: options.state
}
},
computed
})
保存一个getters对象,之后在组件里调用的就是这个对象
this.getters = {}
const computed = {}
getters是个对象,传入的时候在getters里是个方法, 所以需要把对象的方法,变成对象的属性返回, 并接受一个state 参数
//遍历对象的方法
Object.keys(this._getters).forEach(key=>{
})
在循环里进行以下操作
获取用户定义的getter,保存在一个变量里
const fn = this._getters[key]
这个fn就是getter方法,遍历到computed对象里
console.log(fn)
computed[key] = () => {
return fn(this.state)
}
响应式为getters定义只读属性
Object.defineProperty(this.getters, key, {
get: () => {
console.log(this._vm[key]) // count的值
console.log(this.getters) // 这个时候对象里已经有值了
return this._vm[key]
}
})
这个时候打印this.getters,对象里有了一个doubleCount
组件模板里加入一行
<p>getters: {{$store.getters.doubleCount}}</p>
现在来看看效果