手写vuex源码

一、项目准备
创建vue项目

vue create projectName

进入项目

cd projectName

安装vuex

vue add vuex

在store/index.js中写上测试代码:

import Vue from "vue";
import Vuex from "./jvuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    add(state) {
      state.count++;
    }
  },
  actions: {
    add({ commit }) {
      setTimeout(() => {
        commit("add");
      }, 1000);
    }
  },
  modules: {}
});

 App.vue中写测试代码:

<p @click="$store.commit('add')">{{$store.state.count}}</p>
<p @click="$store.dispatch('add')">async: {{$store.state.count}}</p>

手写vuex:在store文件夹中创建jvuex.js文件。

将index.js文件中引入Vuex地址改为jvuex.js的地址

import Vuex from "vuex";
import Vuex from "./jvuex";


 
二、手写Vuex

1.由于组件中使用Vuex时,是new Vuex.Store(...),所以我们应该暴露出一个包含Store和install的对象。

为了在组件中能直接用this.$store,所以要将store挂载到vue原型链上。犹豫install方法是在创建vue实例之前执行,所以使用混合的方式,将挂载延迟到beforeCreate时

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

2.分析state:state中数据改变时,相应的位置也会重新渲染,所以state中的数据应设置为响应式数据,可以创建vue实例,将state传进去,但不能让用户随意操作state,所以要尽量把state隐藏起来。在get的时候返回对应的值,在set时进行报错处理。

let Vue;
class Store {
    constructor(options) {
        // 将state中的数据做响应式处理
        // 为了不暴露vue实例,用_vm隐藏
        // 添加$$,Vue就不会代理,_vm就访问不到options.state中的属性,将options.state尽可能隐藏起来,用户只能使用state的方式访问options.state
        this._vm = new Vue({
          data: {
            $$state: options.state
          }
        });
    }
    get state() {
        return this._vm._data.$$state;
      }
    set state(v) {
        console.error("请使用replaceState重置状态");
    }
}
function install(_vue) {
    Vue = _vue
}
export default {Store, install}

3.commit:使用时传入mutation类型和数据两个参数,执行该类型的方法


constructor(options) {
    this._mutations = options.mutations;
}
commit(type, payload) {
    const mutation = this._mutations[type];
    if (mutation) {
      mutation(this.state, payload);
    }
}

4.dispatch:使用时传入action类型和数据两个参数,执行该类型的方法


constructor(options) {
    this._actions= options.actions;
}
dispatch(type, payload) {
    const action = this._actions[type];
    if (action) {
      // 执行的类型方法第一个参数是上下文
      action(this, payload);
    }
}

5.getters:getters需要借助computed来实现,遍历getters中的属性,在computed中设置一样的属性,由于computed中的属性会挂载到vue原型链上,且是响应式的,所以当获取getters中属性时,可以使用在vue实例上获取到

    // 初始化computed,方便getters中使用
    const computed = {};
    this._wrappedGetters = options.getters;
    this.getters = {};
    // 由于forEach中的this已经不再是Store实例,所以在这里将this存为store
    const store = this;

    // 遍历getters中的属性
    Object.keys(this._wrappedGetters).forEach(key => {
      // 获取getters
      const fn = store._wrappedGetters[key];
      // 由于使用computed时不能带参数,所以我们在computed中设置同名参数,返回高阶函数fn,并传入参数执行
      computed[key] = function() {
        return fn(store.state)
      }
      // 由于用户只能读取getters,所以只定义只读属性
      Object.defineProperty(store.getters, key, {
        // computed中把key都放到_vm中了,所以这里可以用_vm获取key
        get: () => store._vm[key]
      })
    })

    //   将state中的数据做响应式处理
    // 为了不暴露vue实例,用_vm隐藏
    // 添加$$,Vue就不会代理,_vm就访问不到options.state中的属性,将options.state尽可能隐藏起来,用户只能使用state的方式访问options.state
    this._vm = new Vue({
      data: {
        $$state: options.state
      },
      // 设置computed
      computed:computed
    });

 

三、整体代码

let Vue;
class Store {
  constructor(options) {
    this._mutations = options.mutations;
    this._actions = options.actions;

    // 在使用时不知道上下文是谁了,所以这里锁死Store实例
    this.commit = this.commit.bind(this);
    this.dispatch = this.dispatch.bind(this);

    // 初始化computed,方便getters中使用
    const computed = {};
    this._wrappedGetters = options.getters;
    this.getters = {};
    // 由于forEach中的this已经不再是Store实例,所以在这里将this存为store
    const store = this;
    Object.keys(this._wrappedGetters).forEach(key => {
      // 获取getters
      const fn = store._wrappedGetters[key];
      // 由于使用computed时不能带参数,所以我们在computed中设置同名参数,返回高阶函数fn,并传入参数执行
      computed[key] = function() {
        return fn(store.state)
      }
      // 由于用户只能读取getters,所以只定义只读属性
      Object.defineProperty(store.getters, key, {
        // computed中把key都放到_vm中了,所以这里可以用_vm获取key
        get: () => store._vm[key]
      })
    })

    //   将state中的数据做响应式处理
    // 为了不暴露vue实例,用_vm隐藏
    // 添加$$,Vue就不会代理,_vm就访问不到options.state中的属性,将options.state尽可能隐藏起来,用户只能使用state的方式访问options.state
    this._vm = new Vue({
      data: {
        $$state: options.state
      },
      // 设置computed
      computed:computed
    });
  }
  get state() {
    return this._vm._data.$$state;
  }
  set state(v) {
    console.error("请使用replaceState重置状态");
  }
  commit(type, payload) {
    const mutation = this._mutations[type];
    if (mutation) {
      mutation(this.state, payload);
    }
  }
  dispatch(type, payload) {
    const action = this._actions[type];
    if (action) {
      action(this, payload);
    }
  }
}
function install(_vue) {
  Vue = _vue;
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store;
      }
    }
  });
}
export default { Store, install };

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值