快速上手Vuex 到 手写简易 Vuex

快速上手Vuex-->手写简易 Vuex

前言

今天本篇文章是关于 Vuex,大家使用 Vue 不会陌生吧

今天我们先对 Vuex 进行了解,然后讲下基本的用法,然后我们自己实现一个简易的 Vuex

img

往期精彩:

从了解到深入虚拟DOM和实现diff算法

手写一个简易vue响应式带你了解响应式原理

从使用到自己实现简单Vue Router看这个就行了

前端面试必不可少的基础知识,虽然少但是你不能不知道

1.简介

Vuex 状态管理插件

Vue 最重要就是 数据驱动组件化,每个 组件都有自己 data ,templatemethods, data是数据,我们也叫做状态,通过methods中方法改变 状态来更新视图,在单个组件中修改状态更新视图是很方便的,但是实际开发中是多个组件(还有多层组件嵌套)共享同一个状态时,这个时候传参就会很繁琐,我们这里就引进 Vuex 来进行状态管理,负责组件中的通信,方便维护代码

Vuex 主要解决的问题

  • 多个视图依赖同一个状态
  • 来自不同视图的行为需要变更同一个状态

使用 Vuex 的好处

  • 能够在 vuex 中集中管理共享的数据,易于开发和后期维护
  • 能够高效地实现组件之间的数据共享,提高开发效率
  • vuex 中的数据都是响应式的

2.Vuex 基础使用

首先在Vue中添加 Vuex 插件

通过 vue-cli 添加了 Vuex 后,在项目的 src 目录下会多出一个 store 目录,目录下会有个 index.js

当然也通过 npm 进行安装 Vuex

npm install vuex --save
<span class="copy-code-btn">复制代码</span>

在开发环境开启严格模式 这样修改数据 就必须通过 mutation 来处理

在 package.json 文件 scripts 中可以设置环境,当我们处于开发环境时,可以开启严格模式

开启严格模式的方式也是很简单的一行代码就可以

strict:products.env.NODE_ENV !== 'production'

/* src/store/index.js */

// 导入 Vue
import Vue from 'vue'
// 导入 Vuex 插件
import Vuex from 'vuex'

// 把 Vuex 注册到Vue 上
Vue.use(Vuex)

export default new Vuex.Store({
  // 在开发环境开启严格模式 这样修改数据 就必须通过 mutation 来处理
  strict:products.env.NODE_ENV !== 'production',
  // 状态
  state: {
  },
  // 用来处理状态
  mutations: {
  },
  // 用于异步处理
  actions: {
  },
  // 用来挂载模块
  modules: {
  }
})

复制代码

要使用 store 就在把 store 挂载到 Vue 中

把 store 挂载到 Vue 之后 ,所有的组件就可以直接从 store 中获取全局数据了

import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
  // 挂载到vue 中
  store,
  render: (h) => h(App),
}).$mount('#app')
复制代码

1.state

在 state 中添加数据

我们需要共享的状态都放在写在 state 对象里面

/* src/store/index.js */

// 导入 Vue
import Vue from 'vue'
// 导入 Vuex 插件
import Vuex from 'vuex'

// 把 Vuex 注册到Vue 上
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    name: '张三',
    age: 21,
  },
  mutations: {},
  actions: {},
  modules: {},
})

复制代码

组件中获取 state 中的数据

获取到 state 有两种方式

1.直接使用 this.$store.state[属性] ,(this 可以省略)

<template>
  <div id="app">
    {{ this.$store.state.name }}
    {{ this.$store.state.age }}
  </div>
</template>
复制代码

2.使用 mapState

通过 mapStatestore 映射到 组件的计算属性,就相当于组件内部有了 state 里的属性

知道这里为啥要用 ...展开吗,到时候实现 mapState 时就知道了

<template>
  <div id="app">
    {{ name }}
    {{ age }}
  </div>
</template>

<script>

// 从 Vuex 中导入 mapState
import { mapState } from 'vuex'
export default {
  name: 'App',
  computed: {
    // 将 store 映射到当前组件的计算属性
    ...mapState(['name', 'age'])
  }
}
</script>

<style  scoped>
</style>

复制代码

注意

当store 中的值 和 当前组件有相同的状态,我们可以在 mapState 方法里传递一个对象 而不是一个数组,在对象中给状态起别名

computed: {
    // name2 和 age2 都是别名
    ...mapState({ name2: 'name', age2: 'age'}])
}
复制代码

2.Mutation

Store 中的状态不能直接对其进行操作,我们得使用 Mutation 来对 Store 中的状态进行修改,虽然看起来有些繁琐,但是方便集中监控数据的变化

state 的更新必须是 Mutation 来处理

我们现在 mutaions 里定义个方法

如果想要定义的方法能够修改 Store 中的状态,需要参数就是 state

/* src/store/index.js */

// 导入 Vue
import Vue from 'vue'
// 导入 Vuex 插件
import Vuex from 'vuex'

// 把 Vuex 注册到Vue 上
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    name: '张三',
    age: 21,
  },
  mutations: {
    // 在这里定义 方法
    /**
     *
     * @param {*} state 第一个参数是 Store 中的状态(必须传递)
     * @param {*} newName 传入的参数 后面是多个
     */
    changeName(state, newName) {
      // 这里简单举个例子 修改个名字
      state.name = newName
    },
  },
  actions: {},
  modules: {},
})

复制代码

在组件中使用 mutations 中的方法

同样有两种方法在组件触发 mutations 中的方法

1.this.$store.commit() 触发

methods 中定义一个方法,在这个方法里面进行触发 mutations 中的方法

<template>
  <div id="app">
    <button @click="handleClick">方式1 按钮使用 mutation 中方法</button>
    {{ name }}
  </div>
</template>

<script>

// 从 Vuex 中导入 mapState
import { mapState } from 'vuex'
export default {
  name: 'App',
  computed: {
    // 将 store 映射到当前组件的计算属性
    ...mapState(['name', 'age'])
  },
  methods: {
    handleClick() {
      // 触发 mutations 中的 changeName
      this.$store.commit('changeName', '小浪')
    }
  },
}
</script>

<style  scoped>
</style>

复制代码

2.使用 mapMutations

<template>
  <div id="app">
    <button @click="changeName('小浪')">方式2 按钮使用 mutation 中方法</button>
    {{ name }}
  </div>
</template>

<script>

// 从 Vuex 中导入 mapState
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'App',
  computed: {
    // 将 store 映射到当前组件的计算属性
    ...mapState(['name', 'age'])
  },
  methods: {
    // 将 mutations 中的 changeName 方法映射到 methods 中,就能直接使用了 changeName 了
    ...mapMutations(['changeName'])
  },
}
</script>

<style  scoped>
</style>

复制代码

3.Action

ActionMutation 区别

Action 同样也是用来处理任务,不过它处理的是异步任务,异步任务必须要使用 Action,通过 Action 触发 Mutation 间接改变状态,不能直接使用 Mutation 直接对异步任务进行修改

先在 Action 中定义一个异步方法来调用 Mutation 中的方法

/* src/store/index.js */

// 导入 Vue
import Vue from 'vue'
// 导入 Vuex 插件
import Vuex from 'vuex'

// 把 Vuex 注册到Vue 上
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    name: '张三',
    age: 21,
  },
  mutations: {
    // 在这里定义 方法
    /**
     *
     * @param {*} state 第一个参数是 Store 中的状态(必须传递)
     * @param {*} newName 传入的参数 后面是多个
     */
    changeName(state, newName) {
      // 这里简单举个例子 修改个名字
      state.name = newName
    },
  },
  actions: {
    /**
     *
     * @param {*} context 上下文默认传递的参数
     * @param {*} newName 自己传递的参数
     */
    // 定义一个异步的方法 context是 store
    changeNameAsync(context, newName) {
      // 这里用 setTimeout 模拟异步
      setTimeout(() => {
        // 在这里调用 mutations 中的处理方法
        context.commit('changeName', newName)
      }, 2000)
    },
  },
  modules: {},
})

复制代码

在组件中是 Action 中的异步方法也是有两种方式

1.this.$store.dispatch()

<template>
  <div id="app">
    <button @click="changeName2('小浪')">方式1 按钮使用 action 中方法</button>
    {{ name }}
  </div>
</template>

<script>

// 从 Vuex 中导入 mapState mapMutations
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'App',
  computed: {
    // 将 store 映射到当前组件的计算属性
    ...mapState(['name', 'age'])
  },
  methods: {
    changeName2(newName) {
      // 使用 dispatch 来调用 actions 中的方法
      this.$store.dispatch('changeNameAsync', newName)
    }
  },
}
</script>

<style  scoped>
</style>

复制代码

2.使用 mapActions

<template>
  <div id="app">
    <button @click="changeNameAsync('小浪')">
      方式2 按钮使用 action 中方法
    </button>
    {{ name }}
  </div>
</template>

<script>

// 从 Vuex 中导入 mapState mapMutations mapActions
import { mapState, mapMutations, mapActions } from 'vuex'
export default {
  name: 'App',
  computed: {
    // 将 store 映射到当前组件的计算属性
    ...mapState(['name', 'age'])
  },
  methods: {
    // 映射 actions 中的指定方法 到 methods中,就可以在该组件直接使用
    ...mapActions(['changeNameAsync'])
  },
}
</script>

<style  scoped>
</style>

复制代码

4.Getter

简介

Getter 类似于计算属性,但是我们的数据来源是 Vuex 中的 state ,所以就使用 Vuex 中的 Getter 来完成

应用场景

需要对 state 做一些包装简单性处理 展示到视图当中

先来写个 Getter

/* src/store/index.js */

// 导入 Vue
import Vue from 'vue'
// 导入 Vuex 插件
import Vuex from 'vuex'

// 把 Vuex 注册到Vue 上
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    name: '张三',
    age: 21,
  },
  getters: {
    // 在这里对 状态 进行包装
    /**
     *
     * @param {*} state 状态 如果要使用 state 里面的数据,第一个参数默认就是 state ,名字随便取
     * @returns
     */
    decorationName(state) {
      return `大家好我的名字叫${state.name}今年${state.age}岁`
    },
  },
})

复制代码

当然 Getter 也有两种方式导入

1.this.$store.getters[名称]

<template>
  <div id="app">
    {{ this.$store.getters.decorationName }}
  </div>
</template>
复制代码

2.使用 mapGetters

<template>
  <div id="app">
    {{ decorationName }}
  </div>
</template>

<script>

// 从 Vuex 中导入 mapGetters
import { mapGetters } from 'vuex'
export default {
  name: 'App',
  computed: {
    // 将 getter 映射到当前组件的计算属性
    ...mapGetters(['decorationName'])
  },
}
</script>
复制代码

5.Module

为了避免在一个复杂的项目 state 中的数据变得臃肿,Vuex 允许将 Store 分成不同的模块,每个模块都有属于自己的 stategetteractionmutation

我们这里新建一个 animal.js 文件

/* animal.js */

const state = {
  animalName: '狮子',
}
const mutations = {
  setName(state, newName) {
    state.animalName = newName
  },
}

//导出
export default {
  state,
  mutations,
}

复制代码

store/index.js中的 modules 进行挂载这个模块

/* src/store/index.js */

// 导入 Vue
import Vue from 'vue'
// 导入 Vuex 插件
import Vuex from 'vuex'
// 引入模块
import animal from './animal'

// 把 Vuex 注册到Vue 上
Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    animal,
  },
})

复制代码

然后我们就可以在组件中使用了

<template>
  <div id="app">
    {{ this.$store.state.animal.animalName }}
    <button @click="$store.commit('setName', '老虎')">改名</button>
  </div>
</template>
复制代码

$store.state[在module中挂载的模块名][挂载的模块里的属性]

是不是觉得这种模式很复杂

img

添加命名空间

其实也可以使用 mapXXX 方法进行映射,不过写法有些许不同,先在导出的添加一个命名空间 namespaced: true

/* animal.js */

const state = {
  animalName: '狮子',
}
const mutations = {
  setName(state, newName) {
    state.animalName = newName
  },
}

export default {
  // 开启命名空间 方便之后使用 mapXXX
  namespaced: true,
  state,
  mutations,
}

复制代码

方式2

<template>
  <div id="app">
    {{ animalName }}
    <button @click="setName('老鹰')">改名</button>
  </div>
</template>

<script>

// 从 Vuex 中导入 mapState mapMutations
import { mapState, mapMutations } from 'vuex'
export default {
  name: 'App',
  computed: {
    // mapState 使用方式和之前有些许不同,第一个是module挂载的模块名
    // 第二个参数是 animal 模块中的 state 属性
    ...mapState('animal', ['animalName'])
  },
  methods: {
    // mapMutations 使用方式也和之前有些许不同,第一个是module挂载的模块名
    // 第二个参数是 animal 模块中的 mutation 方法
    ...mapMutations('animal', ['setName'])
  },
}
</script>


复制代码

3.模拟一个简单的Vuex

上面我们已经介绍了 Vuex 的基本使用,现在我们来自己动手写个简单 Vuex

代码我都会写满注释方便大家观看,代码很少,有兴趣,大家耐心观看 ヽ( ̄▽ ̄)ノ

1.index.js

先搭个基本的架子

我们在 src 目录下 建立一个属于我们自己的 Vuex 的文件夹,并且在这个目录下添加一个 index.js 文件,我们要模拟的这个 Vuex 就会放在这里面

/* my-vuex/index.js */

// 保存一个全局的 Vue 之后会用到
let _Vue = null

// Store 类
class Store {
  // 先完成构造方法,构造方法接收一个对象
  constructor(options) {
      //...待实现
  }
}

// 因为Vuex 需要 Vue.use() 安装,所以我们必须要有个 install 方法 传入 Vue
// 第二个参数是一个可选对象
function install(Vue) {
    //... 待实现
}

// 导出 install 和 Store
export default {
  install,
  Store,
}
复制代码

2.install方法

因为Vuex 插件 需要 Vue.use() 安装,所以我们必须要有个 install 方法,第一个参数 传入 Vue

// 第二个参数是一个可选对象
function install(Vue) {
  // 保存到全局 _Vue
  _Vue = Vue
  // 全局注册混入 这样在所有的组件都能使用 $store
  _Vue.mixin({
    // beforeCreate vue初始化阶段
    // 在 beforeCreate 这个时候把 $store 挂载到 Vue 上
    beforeCreate() {
      // 判断 Vue 传递的对象是否有 store 需要挂载
      // this.$options  是new Vue() 传递的对象
      if (this.$options.store) {
        // 把 store 挂载到 Vue 原型上
        _Vue.prototype.$store = this.$options.store
      }
    },
  })
}
复制代码

3.我们继续来实现 Store

先完成基础的 构造方法

/* my-vuex/index.js */

// 保存一个全局的 Vue 之后会用到
let _Vue = null

// Store 类
class Store {
  // 先完成构造方法,构造方法接收一个对象
  constructor(options) {
    // 赋初值
    const state = options.state || {}
    const mutations = options.mutations || {}
    const actions = options.actions || {}
    const getters = options.getters || {}
}

//...install
复制代码

接着,我们来实现 state,getters,mutations,actions,commit,dispatch

( ゚▽゚)/

3.state

是不是超简单,直接调用 Vueobservablestate 变成响应式

/* my-vuex/index.js */

// Store 类
class Store {
  constructor(options) {
    //...其他细节
    // 1.实现state 把 state 中的数据转为 响应式,直接用 Vue 中的 observable
    this.state = _Vue.observable(state)
  }
}
复制代码

4.getters

为每一个 getters 里面的 方法添加了一个 get

/* my-vuex/index.js */

// Store 类
class Store {
  constructor(options) {
    //...其他细节
    // 2.实现 getters 这里为什么不知直接 把this.getters 赋值 {} 而是 Object.create(null)
    // 好处是不用考虑会和原型链上的属性重名问题
    this.getters = Object.create(null)
    // 我们要为 getters 添加一个 get 方法,这里就要使用 数据劫持
    // 先拿到 getters 中每一个 方法
    Object.keys(getters).forEach((key) => {
      // 第一个参数是给谁添加 ,第二个是添加的属性名,第三个对象里面可以设置很多参数
      // 比如 可枚举,可配置,get,set
      Object.defineProperty(this.getters, key, {
        // 为 this.getters 每一项都添加 一个 get 方法
        get: () => {
          // 还记得吗,getters 中的方法 默认把 state传入进去,改变this指向
          return getters[key].call(this, this.state)
        },
      })
    })
  }
}
复制代码

5.mutations

这里改变 this 指向

/* my-vuex/index.js */

// Store 类
class Store {
  constructor(options) {
    //...其他细节
    // 3.实现 mutations
    // 先遍历 mutaions 中的对象进行改变 this指向
    this.mutations = {}
    Object.keys(mutations).forEach((key) => {
      this.mutations[key] = (params) => {
        // 改变this指向 ,默认是要传入 state
        mutations[key].call(this, this.state, params)
      }
    })
  }
}
复制代码

6.actions

其实呢,和上面的 mutations 处理方式差不多,不过参数 传递的不一样,需要传递 上下文 context 也就是 Store 的一个实例,这里就是 this

/* my-vuex/index.js */

// Store 类
class Store {
  constructor(options) {
    //...其他细节
    // 4.实现 actions
    // 和 mutations 一样我们需要重新改变 this 指向
    this.actions = {}
    Object.keys(actions).forEach((key) => {
      this.actions[key] = (params) => {
        // 改变this指向 ,默认是要传入 store也就是 this
        actions[key].call(this, this, params)
      }
    })
  }
}
复制代码

7.commit

/* my-vuex/index.js */

// Store 类
class Store {
  constructor(options) {
    //...其他细节
  }
  // 5.实现commit 方法
  // 用于 触发mutations中的方法
  // 第一个参数是事件名 ,第二个是参数
  commit = (eventName, params) => {
    this.mutations[eventName](params)
  }
}
复制代码

8.dispatch

dispatchcommit 实现差不多

/* my-vuex/index.js */

// Store 类
class Store {
  constructor(options) {
    //...其他细节
  }
  // 6.实现 dispatch 方法
  // 用于 触发actions中的异步方法
  // 第一个参数是事件名 ,第二个是参数
  dispatch = (eventName, params) => {
    this.actions[eventName](params)
  }
}
复制代码

好了,到了这里差不多,一个丐版的 Vuex 就这样诞生了,我们写个例子去测试下吧

9.测试例子

先导入我们自己写的 Vuex

/* src/store/index.js */

// 导入 Vue
import Vue from 'vue'
// 导入 我们自己写的 Vuex 插件
import Vuex from '../my-vuex/index'
// 把 Vuex 安装到到Vue 上
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    name: '张三',
    age: 21,
  },
  mutations: {
    changeName(state, newName) {
      // 这里简单举个例子 修改个名字
      state.name = newName
    },
  },
  actions: {
    changeNameAsync(context, newName) {
      // 这里用 setTimeout 模拟异步
      setTimeout(() => {
        // 在这里调用 mutations 中的处理方法
        context.commit('changeName', newName)
      }, 2000)
    },
  },
  getters: {
    decorationName(state) {
      return `大家好我的名字叫${state.name}今年${state.age}岁`
    },
  },
})

复制代码

一个简单的 vue 组件

<template>
  <div id="app">
    <h1>我是 state 测试:{{ this.$store.state.name }}</h1>
    <h1>我是 getters 测试:{{ this.$store.getters.decorationName }}</h1>
    <button @click="$store.commit('changeName', 'mutations 按钮')">
      mutations 按钮
    </button>
    <button @click="$store.dispatch('changeNameAsync', 'actions 按钮')">
      actions 按钮
    </button>
  </div>
</template>

<script>
          
export default {
  name: 'App',
}
</script>

<style  scoped>

</style>

复制代码

在 mian.js 还是之前一样的挂载没改

import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
  // 挂载到vue 中
  store,
  render: (h) => h(App),
}).$mount('#app')

复制代码

例子预览

27

到了这里并不是结束了,不如简单实现一下 几个 mapXXX ,实现起来都差不多

img

10.mapSate

...mapSate(['age',['name']]) ,最后 computed 得到的就是 age: 21, name : '张三' 这样,就可以在 组件中直接使用了

const mapState = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function() {
      return this.$store.state[item]
    }
  })
  return obj
}
复制代码

11.mapMutations

const mapMutations = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function(params) {
      return this.$store.commit(item, params)
    }
  })
  return obj
}
复制代码

12.mapActions

const mapActions = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function(params) {
      return this.$store.dispatch(item, params)
    }
  })
  return obj
}
复制代码

13.mapGetters

const mapGetters = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function() {
      return this.$store.getters[item]
    }
  })
  return obj
}
复制代码

14.导出和使用

最后就是导出

// 导出
export { mapState, mapMutations, mapActions, mapGetters }
复制代码

使用方法和之前一样

<template>
  <div id="app">
    <button @click="changeName('狗子')">mapMutations</button>
    <button @click="changeNameAsync('狗2子')">mapMutations</button>
    {{ decorationName }}
    {{ age }}
  </div>
</template>

<script>
// 导入
import { mapState, mapMutations, mapActions, mapGetters } from './my-vuex/index'

export default {
  name: 'App',
  computed: {
    ...mapState(['age']),
    ...mapGetters(['decorationName'])
  },
  methods: {
    ...mapMutations(['changeName']),
    ...mapActions(['changeNameAsync'])
  },
}
</script>
...
</style>

复制代码

3.结语

好了到了这里,关于 Vuex 本文就结束了,我们从 Vuex 是啥,怎么使用,动手实现一个简单 Vuex 我们都完成了,希望大家有所收获

下面把完成的我们模拟的 Vuex 代码贴出,欢迎大家,多多交流,有什么写错的地方,请大家指出

img

模拟 Vuex 完整代码

/* my-vuex/index.js */

// 保存一个全局的 Vue 之后会用到
let _Vue = null

// Store 类
class Store {
  // 先完成构造方法,构造方法接收一个对象
  constructor(options) {
    // 赋初值
    const state = options.state || {}
    const mutations = options.mutations || {}
    const actions = options.actions || {}
    const getters = options.getters || {}
    // 1.实现state 把 state 中的数据转为 响应式,直接用 Vue 中的 observable
    this.state = _Vue.observable(state)

    // 2.实现 getters 这里为什么不知直接 把this.getters 赋值 {} 而是 Object.create(null)
    // 好处是不用考虑会和原型链上的属性重名问题
    this.getters = Object.create(null)
    // 我们要为 getters 添加一个 get 方法,这里就要使用 数据劫持
    // 先拿到 getters 中每一个 方法
    Object.keys(getters).forEach((key) => {
      // 第一个参数是给谁添加 ,第二个是添加的属性名,第三个对象里面可以设置很多参数
      // 比如 可枚举,可配置,get,set
      Object.defineProperty(this.getters, key, {
        // 为 this.getters 每一项都添加 一个 get 方法
        get: () => {
          // 还记得吧,getters 中的方法 默认把 state传入进去,改变this指向
          return getters[key].call(this, this.state)
        },
      })
    })

    // 3.实现 mutations
    // 先遍历 mutaions 中的对象进行改变 this指向
    this.mutations = {}
    Object.keys(mutations).forEach((key) => {
      this.mutations[key] = (params) => {
        // 改变this指向 ,默认是要传入 state
        mutations[key].call(this, this.state, params)
      }
    })

    // 4.实现 actions
    // 和 mutations 一样我们需要重新改变 this 指向
    this.actions = {}
    Object.keys(actions).forEach((key) => {
      this.actions[key] = (params) => {
        // 改变this指向 ,默认是要传入 store也就是 this
        actions[key].call(this, this, params)
      }
    })
  }

  // 5.实现commit 方法
  // 用于 触发mutations中的方法
  // 第一个参数是事件名 ,第二个是参数
  commit = (eventName, params) => {
    this.mutations[eventName](params)
  }

  // 6.实现 dispatch 方法
  // 用于 触发actions中的异步方法
  // 第一个参数是事件名 ,第二个是参数
  dispatch = (eventName, params) => {
    this.actions[eventName](params)
  }
}

// 因为Vuex 需要 Vue.use() 安装,所以我们必须要有个 install 方法 传入 Vue
// 第二个参数是一个可选对象
function install(Vue) {
  // 保存到全局 _Vue
  _Vue = Vue
  // 全局注册混入 这样在所有的组件都能使用 $store
  _Vue.mixin({
    // beforeCreate vue初始化阶段
    // 在 beforeCreate 这个时候把 $store 挂载到 Vue 上
    beforeCreate() {
      // 判断 Vue 传递的对象是否有 store 需要挂载
      // this.$options  是new Vue() 传递的对象
      if (this.$options.store) {
        // 把 store 挂载到 Vue 原型上
        _Vue.prototype.$store = this.$options.store
      }
    },
  })
}

// mapState
const mapState = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function() {
      return this.$store.state[item]
    }
  })
  return obj
}

// mapMutations
const mapMutations = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function(params) {
      return this.$store.commit(item, params)
    }
  })
  return obj
}

// mapActions
const mapActions = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function(params) {
      return this.$store.dispatch(item, params)
    }
  })
  return obj
}

// mapGetters
const mapGetters = (params) => {
  // 这里我只写个数组的 起别名的就没弄哈
  if (!Array.isArray(params))
    throw new Error('抱歉,当前是丐版的Vuex,只支持数组参数')
  // 第一步就是要初始 obj ,不然[item] 会报错
  let obj = {}
  // 实现逻辑很简单,就是接收传递的的参数
  // 去this.$store寻找
  params.forEach((item) => {
    obj[item] = function() {
      return this.$store.getters[item]
    }
  })
  return obj
}
// 导出
export { mapState, mapMutations, mapActions, mapGetters }

// 导出 install 和 store
export default {
  install,
  Store,
}

复制代码

快速上手Vuex 到 手写简易 Vuex 的相似文章

30 道 Vue 面试题,内含详细讲解(涵盖入门到精通,自测 Vue 掌握程度)分析前端抢饭碗系列之Vue项目中如何做单元测试分析复制excel内容到input框并改变其格式分析虚拟列表分析前端开发中的长列表分析vue computed实现原理分析vue双向绑定原理分析实现一个最精简的响应式系统来学习Vue的data、computed、watch源码分析