likes
comments
collection
share

基于vue3,从零手写实现一个mini-vuex。

作者站长头像
站长
· 阅读数 12

基于vue3,从零手写实现一个mini-vuex。

前言:

  • 最近学习了vuex源码,为了巩固所学到的知识,因此准备从零实现一个mini-vuex。

  • 所需要实现的mini-vuex包括了vuex最核心的代码存储功能,但是与完整版的相比,不具备模块化相关的功能,因此更加小巧,适用于作为练手项目。

最终效果展示:

基于vue3,从零手写实现一个mini-vuex。

github地址:github.com/SpacesoulsL…

1. 搭建基本项目结构

使用vite创建一个vue项目,目录结构如下:

基于vue3,从零手写实现一个mini-vuex。

其中,store文件夹存放基本store实例,vuex文件夹为我们对于mini-vuex的实现。

2. 基本options的配置

import { createStore } from "../vuex/createStore"

export default createStore({
  state: {
    num: 0
  },
  getters: {
    add(state) {
      return num + 1
    }
  },
  mutations: {
    changeNum(state, newNum) {
      state.num = newNum
    }
  },
  actions: {
    asyncChangeNum(store, newNum) {
      setTimeout(() => {
        store.commit('changeNum', newNum)
      }, 2000)
    }
  }
})

3. Store类的创建

Store类为mini-vuex最核心的类,其核心的属性方法都存在于此类。

后续将通过 createStore 函数来创建Store实例

export class Store {
  constructor(options) {
    const store = this
  }
}

install方法的实现

为了vue能通过 app.use(store) 使用到我们写的插件,需要在Store类中实现一个install方法。

vue3提供了 provideinject 两个重要的方法帮助我们将实例注册到组件当中去

install(app) {
    app.provide('store', this)   //vue3提供的方法,为所有组件提供一个可以使用的实例,后续通过inject方法注入到组件当中。
    app.config.globalProperties.$store = this //将$store添加到全局当中,可以通过$store.xxx来使用mini-vuex
  }

4. createStore和useStore方法实现

createStore 方法 传入options,返回一个Store实例

import { Store } from "./index"

export function createStore(options) {
  return new Store(options)
}

在组件当中使用 useStore 方法,注入Store实例到组件当中。

import { inject } from "vue";

export function useStore() {
  return inject('store')	
}

5. 基本属性的创建

export class Store {
  constructor(options) {
    const store = this
    const state = options.state		//此时的state不是响应式的
    store._wrapperGetters = options.getters
    store._mutations = options.mutations
    store._actions = options.actions
  }

  install(app) {
    app.provide("store", this) //vue3提供的方法,为所有组件提供一个可以使用的实例,后续通过inject方法注入到组件当中。
    app.config.globalProperties.$store = this //将$store添加到全局当中,可以通过$store.xxx来使用mini-vuex
  }
}

6. 实现state和getter的响应式

到这里为止,我们已经实现了state的基本响应式原理。

import { reactive } from "vue"

//1.创建一个工具函数
function forEachValue(obj, fn) {
  Object.keys(obj).forEach(key => fn(obj[key], key))
}

//2. 用于实现state和getters的响应式函数
function resetState(store, state) {
  store._state = reactive({data: state})  //vue3提供的方法,使用reactive包裹的数据会变为响应式
  store.getters = {}
  forEachValue(store._wrapperGetters, (getter, key) => {
    Object.defineProperty(store.getters, key, {
      get: () => getter(store.state)
    })
  })
}

export class Store {
  constructor(options) {
    const store = this
    const state = options.state
    store._wrapperGetters = options.getters
    store._mutations = options.mutations
    store._actions = options.actions
    //3. 调用resetState方法实现state的响应式
    resetState(store, state)
  }
  //4.定义访问器 使用$store.state的时候可以访问到store._state
  get state() {
    return this._state.data
  }

  install(app) {
    app.provide("store", this) //vue3提供的方法,为所有组件提供一个可以使用的实例,后续通过inject方法注入到组件当中。
    app.config.globalProperties.$store = this //将$store添加到全局当中,可以通过$store.xxx来使用mini-vuex
  }
}

7. mutations和actions的注册

//用于安装mutations和actions
function installModule(store, options) {
  const mutations = options.mutations
  forEachValue(mutations, (mutation, key) => {
    store._mutations[key] = (payload) => {
      mutation(store.state, payload)
    }
  })

  const actions = options.actions
  forEachValue(actions, (action, key) => {
    store._actions[key] = (payload) => {
      action(store, payload)
    }
  })
}

export class Store {
  constructor(options) {
	...
    //调用installModule方法,来安装mutations和actions
    installModule(store, options)
	...
}

我们打印一下store实例看看结果,可以看到我们已经具备了实例的基本属性了,离项目最终完成已经不远。

基于vue3,从零手写实现一个mini-vuex。

8. dispatch和commit方法的实现

export class Store {
	...
  commit = (type, payload) => {
    if (!this._mutations[type]) return //判断当前是否有该类型的mutation,没有的话结束函数
    this._mutations[type](payload)
  }

  dispatch = (type, payload) => {
    if (!this._actions[type]) return
    this._actions[type](payload)
  }
	...
}

9. 测试

app.vue

<template>
  <div class="app" style="margin-top: 50px">
    <div>num: {{ num }}</div>
    <div>getters/add: {{ numAdd }}</div>
    <div class="btn">
      <button @click="$store.state.num++">num + 1</button>
      <button @click="changeNum">changeNum</button>
      <button @click="asycnChangeNum">asyncChangeNum</button>
    </div>
  </div>
</template>

<script setup>
import { computed } from "vue"
import { useStore } from "./vuex/useStore"

const store = useStore()
const num = computed(() => store.state.num)
const numAdd = computed(() => store.getters.add)

function changeNum() {
  store.commit("changeNum", 1000)
}

function asycnChangeNum() {
  store.dispatch("asyncChangeNum", 2000)
}
</script>

<style>
.btn button {
  margin: 0 10px;
}
</style>

基于vue3,从零手写实现一个mini-vuex。

10. 完善

  • 通过测试,我们已经可以看到state成功实现了响应式,dispatch和commit方法也能正常运行。

  • 但是我们都知道使用vuex的时候,异步的函数通常会放在actions当中,调用dispatch会返回一个promise,但是我们并没有完成此功能,因此仍然需要对此进行完善。

installModule 函数进行修改

function installModule(store, options) {
    ...
  const actions = options.actions
  forEachValue(actions, (action, key) => {
    store._actions[key] = (payload) => {
      let res = action(store, payload) //获取action函数的返回值
      if (!isPromise(res)) {
        return Promise.resolve(res) //如果使用者并没有给定Promise作为返回值,则在此手动创建一个并返回
      }
      return res
    }
  })
}

//用于判断参数是否为promise
function isPromise(res) {
  return res && typeof res?.then === 'function'
}

修改 dispatch 方法

  dispatch = (type, payload) => {
    if (!this._actions[type]) return
    let res = this._actions[type](payload)
    return res	//返回一个Promise
  }