实现一个vuex

实现一个vuex

设计理念:

  • 集中式管理
  • 状态可预测
    在这里插入图片描述

之所以的可预测的:是因为这个就是一个单向数据流,不管是直接进程commit还是通过异步的action来进行修改,最后都要通过commit给mutation然后再对state里面的数据进行变化的,所以我们就可以在这个commit的过程中,实现一些 拦截、监听器、中间件,这样就可以预测状态的改变监听状态的改变了

这个就是状态可预测的原因了

之所以把state修改了,组件中使用了这个vuex里面的数据也会改变是因为,把state里面的数据声明为了响应式的,所以通过mutation修改state之后,数据的依赖就会重新执行render方法,这样就可以进行修改state然后依赖也会改变了,这样就可以得到一个虚拟DOM,然后就和真实DOM进行diff比较,再进行DOM树修改即可重新进行渲染了

我们先把vuex整合进来

vue add vuex

之后在项目目录中就会出现一个 store/index.js 文件了

在这里插入图片描述

可以看到

Vue.use(Vuex)

所以就猜测和vue-router一样

在Vue.use里面是进行了一个this.$store挂载操作

就可以通过 this.$store.state.xxx 来访问到下面通过new Vuex.store({}) 新建的vuex实例了

需要做的:

  • state声明为响应式数据
  • commit和mutations中的方法有关
  • dispatch和actions中的方法有关

其实还有一个getter

另外一个疑问点:

  mutations: {
    add(state) {
      
    }
  },

mutation中方法的第一个参数,state是怎么来的?

下面是一个基本的使用:

<template>
  <div id="app">
    <h1>{{$store.state.counter}}</h1>
    <button @click="$store.commit('add')">加1</button>
    <button @click="$store.dispatch('add')">async 加1</button>
  </div>
</template>
import Vue from 'vue'
import Vuex from 'vuex'

// 挂载this.$state
Vue.use(Vuex)

export default new Vuex.Store({
  // 通过this.$store.state.xxx 访问
  state: {
    counter: 0
  },
  mutations: {
    // 这里的state从哪里来的
    add(state) {
      return state.counter++;
    }
  },
  actions: {
    // 参数是什么?哪里来的
    add({commit}) {
      setTimeout(() => {
        commit("add");
      }, 1000);
    }
  },
  modules: {
  }
})

其中的代码思路就是:

  • 实现一个store类
  • 并且把给类实例挂载到this.$store上

如果是模仿vue-router,那么下面就是Store的一个基本写法:

在这里插入图片描述

但是我们可以看到在store/index.js引入的时候

import Vue from 'vue'
import Vuex from 'vuex'

// 挂载this.$state
Vue.use(Vuex)

export default new Vuex.Store({})

是通过了一个叫做Vuex的

所以要改成
在这里插入图片描述

这样的话,当通过Vue.use(Vuex)的时候,是可以通过Vuex.install来执行install方法的,也可以通过new Vuex.Store来new一个对应的对象

第一件事就是:挂载$store
在这里插入图片描述

然后在class Store里面把,拿到的options里面的state数据转化成是响应式数据

我们在vue-router中使用的而是 Vue.util.defineReactive()这个方法的

但是在state中是可能有多个数据,并且还可能有对象的嵌套的

今天我们就用new Vue的方法,就是把数据放在里面,曲线救国,去变成响应式

new Vue的话,vue会把里面的data递归的都变成是响应式的数据的

在这里插入图片描述

然后外面是通过 this.$store.state.xxx来进行访问的

并且还要把这个生成的响应式数据暴露到外面
在这里插入图片描述

代码如下:

import Vue from 'vue'
import Vuex from './vuex'

// 挂载this.$state
Vue.use(Vuex)

export default new Vuex.Store({
  // 通过this.$store.state.xxx 访问
  state: {
    counter: 0
  },
  mutations: {
    // 这里的state从哪里来的
    add(state) {
      return state.counter++;
    }
  },
  actions: {
    // 参数是什么?哪里来的
    add({commit}) {
      setTimeout(() => {
        commit("add");
      }, 1000);
    }
  },
  modules: {
  }
})

下面就开始实现commit方法

class Store {
    constructor(options) {
        // state响应式处理
        // 后面就可以通过this.$store.state.xxx来进行访问了
        this.state = new Vue({
            data: options.state
        });
        this._mutations = options.mutations;
    }
    commit(type, payload) {
        const entry = this._mutations[type];
        if (!entry) {
            console.error("unknown mutation type");
        }
        entry(this.state, payload);
    }
}

这里就回答了刚刚的一个问题,就是mutation函数里面的state是怎么拿到的

其中的payload,是用户传递的参数

优化:?????????????

在对state进行响应式的时候,源码中,其实为了防止用户直接对vue实例进行操作,就是不想把new Vue直接暴露到外面,它是这样做的

所以就实现了一个get方法,而不能set,这样就是对外界进行了一些隐藏,如果用户set的话会报错

所以只能通过mutation,而不能直接this.state进行改变了

// 1、插件:挂载$store
// 2、实现Store
let Vue;

class Store {
    constructor(options) {
        // state响应式处理
        // 后面就可以通过this.$store.state.xxx来进行访问了
        this._vm = new Vue({
            data: {
                $$state: options.state
            }
        });
        this._mutations = options.mutations;
    }

    get state() {
        return this._vm._data.$$state;
    }

    set state(v) {
        console.error("Please use replaceState to reset state");
    }

    commit(type, payload) {
        const entry = this._mutations[type];
        if (!entry) {
            console.error("unknown mutation type");
        }
        entry(this.state, payload);
    }
}

const install = (_Vue) => {
    Vue = _Vue;

    Vue.mixin({
        beforeCreate() {
            if (this.$options.store) {
                Vue.prototype.$store = this.$options.store
            }
        }
    })
};
export default { Store, install };

通过this.state会触发get方法,设置的话就会触发set方法了

实现dispatch方法

拓展:

在这里插入图片描述

在action的参数中,我们可以通过结构来获取到getter、commit、state这些

所以就认为,传给action方法的就是Store实例this本身了

dispatch(type, payload) {
        const entry = this._actions[type];

        if (!entry) {
            console.error("unkown mutation type");
        }
        entry(this, payload);
    }

然后就测试

报错了
在这里插入图片描述

因为我们传入了this,这个this就会有执行的问题

我们在action中是

actions: {
    // 参数是什么?哪里来的
    add({commit}) {
      setTimeout(() => {
        commit("add");
      }, 1000);
    }
  },

在seTimeout里面的执行了方法,这时候this其实就已经变了,早就不是我们的Store实例本身了,所以一般这个时候都会使用 bind来进程绑定this的了

在这里插入图片描述

然后就实现了

目前代码:

vuex.js

// 1、插件:挂载$store
// 2、实现Store
let Vue;

class Store {
    constructor(options) {
        // state响应式处理
        // 后面就可以通过this.$store.state.xxx来进行访问了
        this._vm = new Vue({
            data: {
                $$state: options.state
            }
        });
        this._mutations = options.mutations;
        this._actions = options.actions;

        // bind绑定this
        this.commit = this.commit.bind(this);
        this.dispatch = this.dispatch.bind(this);
    }

    get state() {
        return this._vm._data.$$state;
    }

    set state(v) {
        console.error("Please use replaceState to reset state");
    }

    commit(type, payload) {
        const entry = this._mutations[type];
        if (!entry) {
            console.error("unknown mutation type");
        }
        entry(this.state, payload);
    }

    dispatch(type, payload) {
        const entry = this._actions[type];

        if (!entry) {
            console.error("unkown mutation type");
        }
        entry(this, payload);
    }
}

const install = (_Vue) => {
    Vue = _Vue;

    Vue.mixin({
        beforeCreate() {
            if (this.$options.store) {
                Vue.prototype.$store = this.$options.store
            }
        }
    })
};
export default { Store, install };

实现getters

我们在store/index.js中

import Vue from 'vue'
import Vuex from './vuex'

// 挂载this.$state
Vue.use(Vuex)

export default new Vuex.Store({
  // 通过this.$store.state.xxx 访问
  state: {
    counter: 0
  },
  mutations: {
    // 这里的state从哪里来的
    add(state) {
      return state.counter++;
    }
  },
  actions: {
    // 参数是什么?哪里来的
    add({commit}) {
      setTimeout(() => {
        commit("add");
      }, 1000);
    }
  },
  modules: {
  },
  getters: {
    doubleCounter(state) {
      return state.counter * 2;
    }
  }
})

定义了一个getters

vuex.js中具体实现

实现流程:

因为我们使用是这样的:

    <h1>两倍:{{$store.getters.doubleCounter}}</h1>

也就是访问了store对象里面getters熟悉的一个key = doubleCounter的属性值

所以

  1. 在Store类中定义一个getters对象
  2. 然后对用户定义的getters进行遍历,然后动态的生成对应函数名的key值和value值
  3. 然后用户访问这个key值的时候,其实就是去执行了key这个名字的 getters中对应的函数了

完整代码如下:

// 1、插件:挂载$store
// 2、实现Store
let Vue;

class Store {
    constructor(options) {
        // state响应式处理
        // 后面就可以通过this.$store.state.xxx来进行访问了
        this._vm = new Vue({
            data: {
                $$state: options.state
            }
        });
        this._mutations = options.mutations;
        this._actions = options.actions;

        // bind绑定this
        this.commit = this.commit.bind(this);
        this.dispatch = this.dispatch.bind(this);
    
        this.getters = {};

        options.getters && this.handleGetters(options.getters);
    }
    handleGetters(getters) {
        Object.keys(getters).map(key => {
            Object.defineProperty(this.getters, key, {
                get: () => getters[key](this.state)
            })
        })
    }
    get state() {
        return this._vm._data.$$state;
    }

    set state(v) {
        console.error("Please use replaceState to reset state");
    }

    commit(type, payload) {
        const entry = this._mutations[type];
        if (!entry) {
            console.error("unknown mutation type");
        }
        entry(this.state, payload);
    }

    dispatch(type, payload) {
        const entry = this._actions[type];

        if (!entry) {
            console.error("unkown mutation type");
        }
        entry(this, payload);
    }
}

const install = (_Vue) => {
    Vue = _Vue;

    Vue.mixin({
        beforeCreate() {
            if (this.$options.store) {
                Vue.prototype.$store = this.$options.store
            }
        }
    })
};
export default { Store, install };
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值