实现vuex源码,手写
Vuex 是专门为 Vue.js 应用程序开发的状态管理模式 + 库,它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
第一步:定义初始化Store类
创建文件夹store/vuex.js
1.定义 Store 类:
- 创建一个名为
Store
的类,它接受一个options
对象作为参数。 - 在
options
对象中,包含state
(应用的状态)、mutations
(同步更改状态的方法)、actions
(异步操作或包含任意异步操作的方法)、以及getters
(从 state 中派生出一些状态的方法)。
javascript
let Vue;
class Store{
constructor(options) {
}
}
2.初始化 Vue 实例:
- 在
Store
类的构造函数中,使用new Vue({ data: { $$state: options.state } })
创建一个 Vue 实例,用于响应式地存储状态。这里使用$$state
作为属性的名称是为了避免与 Vue 实例自身的state
属性冲突,但这不是必须的,只是一个命名约定。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
}
}
3.存储 mutations 和 actions:
- 将
options.mutations
和options.actions
分别存储在this._mutations
和this._actions
中。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
}
}
4.绑定 commit 和 dispatch 方法:
- 使用
Function.prototype.bind
方法将commit
和dispatch
方法绑定到Store
实例上,以确保在回调函数中this
指向正确。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
}
5.初始化 getters:
- 创建一个空对象
this.getters
用于存储 getter 方法。 - 如果
options.getters
存在,则调用this.handleGetters(options.getters)
方法来初始化 getters。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
this.getters = {}
options.getters && this.hanleGetters(options.getters)
}
}
第二步:实现 handleGetters 方法和其他 Store 方法
1.实现 handleGetters 方法:
- 在
handleGetters
方法中,遍历getters
对象的键。 - 使用
Object.defineProperty
在this.getters
对象上定义每个 getter 属性,其get
方法返回getters[key](this.state)
的结果。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
this.getters = {}
options.getters && this.hanleGetters(options.getters)
}
handleGetters(getters){
Object.key(getters).map((key)=>{
Object.defineProperty(this.getters,key,
get: () => getters[key](this.state)
})
})
}
}
2.实现 state 的 getter 和 setter:
- 使用
get state()
方法来访问 Vue 实例中存储的状态。 - 使用
set state(v)
方法来防止直接修改状态(虽然在这里,setter 只是打印了一个错误消息)。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
this.getters = {}
options.getters && this.hanleGetters(options.getters)
}
handleGetters(getters){
Object.key(getters).map((key)=>{
Object.defineProperty(this.getters,key,
get: () => getters[key](this.state)
})
})
}
//get set
get state(){
return this._vm.data.$$state
}
set state(v) {
console.error("please provide");
}
}
3.实现 commit 方法:
commit
方法用于触发 mutations,它接受一个type
(mutation 的类型)和一个可选的payload
(传递给 mutation 的数据)。- 根据
type
从this._mutations
中找到对应的 mutation 方法,并调用它,传入this.state
和payload
。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
this.getters = {}
options.getters && this.hanleGetters(options.getters)
}
handleGetters(getters){
Object.key(getters).map((key)=>{
Object.defineProperty(this.getters,key,
get: () => getters[key](this.state)
})
})
}
//get set
get state(){
return this._vm.data.$$state
}
set state(v) {
console.error("please provide");
}
//commit
commit(type,value){
const entry = this._mutations[type]
if(!entry){
console.error("please provide");
}
entry(this.state,value)
}
}
4.实现 dispatch 方法:
dispatch
方法用于触发 actions,它的工作原理与commit
类似,但通常用于处理异步操作。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
this.getters = {}
options.getters && this.hanleGetters(options.getters)
}
handleGetters(getters){
Object.key(getters).map((key)=>{
Object.defineProperty(this.getters,key,
get: () => getters[key](this.state)
})
})
}
//get set
get state(){
return this._vm.data.$$state
}
set state(v) {
console.error("unknown mutation type");
}
//commit
commit(type,value){
const entry = this._mutations[type]
if(!entry){
console.error("please provide");
}
entry(this.state,value)
}
//dispatch
dispatch(type,value){
const entry = this._actions[type]
if(!entry){
console.error("unknown action type")
}
entry(this.state,value)
}
}
第三步:安装现在自定义vuex插件,需要一个install方法
- 创建一个名为
install
的函数,它接受一个 Vue 构造函数作为参数。 - 在
install
函数中,将 Vue 构造函数存储在全局变量Vue
中。 - 使用
Vue.mixin
方法来全局注册一个 beforeCreate 钩子,该钩子会在每个 Vue 组件实例创建之前被调用。 - 在 beforeCreate 钩子中,检查
this.$options.store
是否存在,如果存在,则将其赋值给Vue.prototype.$store
,这样在任何 Vue 组件中都可以通过this.$store
访问到 store 实例。
javascript
let Vue;
class Store{
constructor(options) {
this._vm = new Vue({
data:{
$$state:options.state
}
})
this._mutations = options.mutations
this._actions = options.actions
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
this.getters = {}
options.getters && this.hanleGetters(options.getters)
}
handleGetters(getters){
Object.key(getters).map((key)=>{
Object.defineProperty(this.getters,key,
get: () => getters[key](this.state)
})
})
}
//get set
get state(){
return this._vm.data.$$state
}
set state(v) {
console.error("unknown mutation type");
}
//commit
commit(type,value){
const entry = this._mutations[type]
if(!entry){
console.error("please provide");
}
entry(this.state,value)
}
//dispatch
dispatch(type,value){
const entry = this._actions[type]
if(!entry){
console.error("unknown action type")
}
entry(this.state,value)
}
}
Store.install = (_vue)=>{
Vue = _vue
Vue.mixin({
beforeCreate(){
if(this.$options.store){
Vue.prototype.$store = this.$options.$store
}
}
})
}
第四步:导出install,Store
javascript
let Vue;
class Store {
constructor(options) {
this._vm = new Vue({
data: {
$$state: options.state,
},
});
this._mutations = options.mutations;
this._actions = options.actions;
this.commit = this.commit.bind(this);
this.dispatch = this.dispatch.bind(this);
this.getters = {};
options.getters && this.handleGetters(options.getters);
}
handleGetters(getters) {
console.log(Object.keys(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 provide");
}
commit(type, payload) {
console.log(type, payload)
const entry = this._mutations[type];
if (!entry) {
console.error("unknown mutation type: " + type);
}
entry(this.state, payload);
}
dispatch(type, payload) {
console.log(this._actions[type]);
const entry = this._actions[type];
if (!entry) {
console.error("unknown mutation type: " + 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,
};
第五步:创建store/index.js
javascript
import Vue from 'vue'
// 引入自己的写的vuex,里面有一个对象{install},当你use时,会自动调用这个方法
import Vuex from './vuex.js'
Vue.use(Vuex)
//需要创建一个仓库并导出
//当new的时候,给Vuex.js中传入了一堆的东西
export default new Vuex.Store({
state: {
name: 1
},
//getters中虽然是一个方法,但是用时,可以把他当作属性
getters: { // 说白了,就是vue中data中的computed
powerCount(state) {
return state.name * 2
},
},
// 改变状态:异步请求数据 事件
mutations: {
add(state) {
state.name++
}
},
actions: {
add(state) {
setTimeout(() => {
console.log(state)
state.name = 30
}, 1000);
}
}
})
第六步:在main中挂载store
javascript
/* eslint-disable vue/multi-word-component-names */
import Vue from 'vue'
import App from './App.vue'
import store from "./store.js"
Vue.config.productionTip = false
new Vue({
name:"main",
store,
render: h => h(App),
}).$mount('#app')
第七步:如何使用store
和vuex一样的用法,语法一样
javascript
<template>
<div>
{{ $store.state.name }}
{{ $store.getters.powerCount }}
<button @click="add">123</button>
</div>
</template>
<script>
export default {
name: "app",
data() {
return {
}
},
methods:{
add(){
this.$store.commit('add')
// this.$store.dispatch('add')
console.log(this.$store.getters.powerCount)
this.$store.handleGetters.powerCount
}
},
mounted() {
console.log(this.$store.state.name)
}
}
</script>
{{ $store.state.name }}
{{ $store.getters.powerCount }}
<button @click="add">123</button>
</div>
</template>
<script>
export default {
name: "app",
data() {
return {
}
},
methods:{
add(){
this.$store.commit('add')
// this.$store.dispatch('add')
console.log(this.$store.getters.powerCount)
this.$store.handleGetters.powerCount
}
},
mounted() {
console.log(this.$store.state.name)
}
}
</script>