利用Vue.observable实现简易的Vuex

目录

  • 1、搭架子
  • 2、注入Vue构造函数
  • 3、初始化并声明commit和dispatch
  • 4、利用Vue.observable实现响应式
  • 5、利用Object.defineProperty实现getters
  • 6、测试
  • 7、完整代码

1、搭架子

首先把大致的架子搭好。

let Vue;
let _self;

class Store {
	constructor(state, getters, mutations, actions) {}

	commit(cb, ...args) {}

	dispatch(cb, ...args) {}

	static install(_Vue, storeName = "store") {}
}

export default Store;

2、注入Vue构造函数

这块很简单,主要利用当调用Vue.use()的时候,Vue会自动调用传进参数的install方法,如果传进来的是个函数则直接把这个函数当做install方法。然后install方法的第一个参数就是Vue构造函数。通过这种方式就可以获取到Vue

static install(_Vue, storeName = "store") {
	Vue = _Vue; // 保存备用
	// 为了能够将Store实例挂载到Vue原型上,先将生成的Store实例挂载在根实例上
	// 再通过全局混入得到根实例,则可拿到Store实例
	Vue.mixin({
		beforeCreate() {
			const store = this.$options[storeName];
			if (store) {
				// 只在根实例上做一次赋值操作即可
				Vue.prototype[`$${storeName}`] = store;
				// 保存store备用
				_self = store;
			}
		}
	});
}

3、初始化并声明commit和dispatch

这块其实很简单,无非是要调用函数修改state里面的值而已。

constructor({ state, getters, mutations, actions }) {
	this.state = state || {};
	this.mutations = mutations || {};
	this.actions = actions || {};
	this.getters = {}; // getters情况特殊一点,暂时这么写
}

commit(cb, ...args) {
	// 由于mutations的声明过程中,形参可能是利用了解构的方式
	// (this被解绑,在脚手架搭建的Vue项目中默认使用严格模式,
	// 因此此时this指向undefined),因此为了保证this
	// 始终指向store实例,利用install里面保存的_self
	_self.mutations[cb].apply(_self, [_self.state, ...args]);
}

dispatch(cb, ...args) {
	_self.actions[cb].apply(_self, [_self, ...args]);
}

4、利用Vue.observable实现响应式

Vue中,很重要的一个特征就是响应式。为了实现响应式,这里利用Vue.observable。官网对Vue.observable的解释如下:

让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景。
在 Vue 2.x 中,被传入的对象会直接被 Vue.observable 变更,所以它和被返回的对象是同一个对象。在 Vue 3.x 中,则会返回一个可响应的代理,而对源对象直接进行变更仍然是不可响应的。因此,为了向前兼容,我们推荐始终操作使用 Vue.observable 返回的对象,而不是传入源对象。

其实说到底就是这是Vue内部用来实现响应式的一个工具函数,在2.6.0版本的时候把它暴露出来给用户了。因此可以直接使用它达到响应式的效果。
代码如下,只需要给state加上Vue.observable即可

constructor({ state, getters, mutations, actions }) {
	this.state = Vue.observable(state || {});
	this.mutations = mutations || {};
	this.actions = actions || {};
	this.getters = {}; // getters情况特殊一点,暂时这么写

	/**
     * 注:也可以使用new Vue()实现响应式,
     * 因为它内部也调用了 Vue.observable,如下:
     * this.state = new Vue({ data: state || {} });
     */
}

这样一个简易的Vuex就实现的差不多了,最后我们来实现getters

5、利用Object.defineProperty实现getters

由于getters是类似计算属性,因此要做一层代理。

constructor({ state, getters, mutations, actions }) {
	this.state = Vue.observable(state || {});
	this.mutations = mutations || {};
	this.actions = actions || {};
	this.getters = {}; // getters情况特殊一点,暂时这么写

	const _getters = getters || {};
	for (let fn in _getters) {
		Object.defineProperty(this.getters, fn, {
			// 注意这里this指向最后生成的对象,所以要使用_self
			get() {
				return _getters[fn].apply(_self.state, _self.getters);
			}
		});
	}

	/**
     * 注:也可以使用new Vue()实现响应式,
     * 因为它内部也调用了 Vue.observable,如下:
     * this.state = new Vue({ data: state || {} });
     */
}

6、测试

现在我们来测试一下。创建一个文件夹,里面放上我们的代码和index.js文件。
在这里插入图片描述
index.js代码如下:

import Vue from "vue";
import Store from "./Store";

Vue.use(Store, "myStore");

export default new Store({
  state: {
    num: 1,
    num2: 1
  },
  getters: {
    age(state, getters) {
      return state.num + "岁, " + "性别:" + getters.gender;
    },
    gender(state) {
      return state.num === 1 ? "男" : "女";
    }
  },
  mutations: {
    add(state) {
      state.num++;
    },
    mul(state) {
		state.num2 += 2;
	}
  },
  actions: {
    asyncAdd({ commit }) {
      setTimeout(() => {
        commit("mul");
      }, 1000);
    }
  }
});

接着在main.js中引入:

import Vue from "vue";
import App from "./App.vue";
import myStore from "./my-store";
new Vue({
  myStore, // 别忘了注入
  render: (h) => h(App),
}).$mount("#app")

最后在App.vue中做测试:

<template>
  <div id="app">
    {{ $myStore.state.num }} <button @click="$myStore.commit('add')">Addbutton> <br />
    {{ $myStore.state.num2 }} <button @click="$myStore.dispatch('asyncAdd')">asyncAddbutton> <br />
    getters: {{ $myStore.getters.age }}
  div>
template>

<script>
export default {
  name: "App"
};
script>

在这里插入图片描述
分别点击一次两个按钮:
在这里插入图片描述
成功!

7、完整代码

let Vue;
let _self;

class Store {
  constructor({ state, getters, mutations, actions }) {
    this.state = Vue.observable(state || {});
    this.getters = {};
    this.mutations = mutations || {};
    this.actions = actions || {};

    const _getters = getters || {};

    for (const fn in _getters) {
      Object.defineProperty(this.getters, fn, {
        get() {
          return _getters[fn].apply(_self, [_self.state, _self.getters]);
        }
      });
    }
  }

  commit(cb, ...args) {
    _self.mutations[cb].apply(_self, [_self.state, ...args]);
  }

  dispatch(cb, ...args) {
    _self.actions[cb].apply(_self, [_self, ...args]);
  }

  static install(_Vue, storeName = "store") {
    Vue = _Vue;
    Vue.mixin({
      beforeCreate() {
        const store = this.$options[storeName];
        if (store) {
          // 只在根实例上做一次赋值操作即可
          Vue.prototype[`$${storeName}`] = store;
          // 保存store备用
          _self = store;
        }
      },
    });
  }
}

export default Store;

以上。

你可能感兴趣的:(Vue,vue.js,javascript)