最简实现vuex系列1 - state / commit

前言

昨天准备今天看看那道 vuex 面试题 - 为什么要在 actions 中使用异步,在 mutations 中可以使用异步吗? 本来想看看 vuex源码 中关于这方面的代码,发现这个 vuex 整体代码量并不多,于是冒出了一个根据源码「实现自己的 vuex 的想法」

由于是第一篇,所以只实现基本功能 - state / commit

vuex

先简单介绍一下今天要实现 vuex的用法,更多细节查看 -> vuex

js 复制代码
import { createStore } from 'vuex'

const store = createStore({
  state () {
    return {
      count: 0
    }
  },
  mutations: {
    increment (state) {
      state.count++
    }
  }
})

现在,你可以访问 store.state,也可以通过触发store.commit方法来改变状态

今天我们实现 上述代码所要完成的效果

准备

首先创建一个 vuex 模块,因为我用的 vite + ts,需要 vite 识别路径并且让 ts 识别

vite.config.ts

ts 复制代码
export default defineConfig({
  resolve:{
      vuex: resolve(__dirname, "src/store/")
  }
})

vite-env.d.ts

ts 复制代码
declare module "vuex" {
  const commit: (type:string,payload:any)=>void;
  const state:any
  
  export {
   commit,
   state
  }
 }

通过 vite 配置别名和 ts 配置全局模块,我们的准备工作就已经结束了

类型声明文件 - types/index.d.ts

配置别名文件 - examples/webpack.config.js

开始

根据刚刚创建的 vite.config 别名查找规则,需要创建一个 store 文件夹

store/index.ts

ts 复制代码
type StoreOtpions<T> = {
  state?: T
  mutations?: Record<string, (s: T, payload: any) => void>
}

export function createStore<T>(options: StoreOtpions<T> = {}) {
  return new Store(options)
}

使用工厂方法,在类 Store 中进行管理

state

因为 state 的变化可以引起 视图的 变化,所以我们可以使用 reactive 包裹

ts 复制代码
import { reactive } from "vue";
class Store<T> {

_state: {
    data: any
  } = {
      data: {}
    };
    
  constructor(options) { 
    this._state.data = reactive(options.state || {})
  }
  
  get state() {
    return this._state.data
  }
 } 

由于 state 必须通过 commit / dispatch 来修改,所以使用了属性访问器 get

store-util.js

store.js

commit

通过 boundCommit 的方法把 this 指向了 当前实例,这样不论在哪调用 commit,this 永远不会变, 是一个非常巧妙的办法

ts 复制代码
class Store {
constructor(){
    // bind commit and dispatch to self
    const store = this;
    const { commit } = this;
     
    this.commit = function boundCommit(type, payload) {
      return commit.call(store, type, payload)
    }
 }
 
 
 commit(type, payload) {}
}
   

在 commit 方法中,只需要执行对应的 方法即可,在源码中他使用了发布订阅模式

ts 复制代码
class Store<T> {
 _mutations: StoreOtpions<T>["mutations"][] | Record<string, []> = [];
 
 constructor(options){
    // 收集 mutations
   this._mutations = Object.create(null)
    // 订阅 所有的 mutations 
    Object.entries(options.mutations).forEach(([fnName, fnValue]) => {
      this.registerMutation(store, fnName, fnValue)
    })
 }
 
 // 注册 mutations
 registerMutation(store: Store<T>, type, handler) {
    const entry = this._mutations[type] || (this._mutations[type] = [])

    entry.push(function wrappedMutationHandler(payload) {
      handler.call(store, store.state, payload)
    })
  }
  
    commit(type, payload) {
      const entry = this._mutations[type]

      if (!entry) {
        return
      }
     // 发布事件
    entry.forEach(function commitIterator(handler) {
      handler(payload)
    })
  }
}

使用一个 this._mutations 注册所有 mutations,数据结构形如 {a:[()=>{},()=>{}]}

因为当你 使用 commit 方法触发事件 a 时,可以快速的触发在 this._mutations 对应的注册事件

store.js

store-util.js

测试

html 复制代码
 <div>
    {{ count }}
    <el-button @click="increment">
      +
    </el-button>

    <el-button @click="decrement">
      -
    </el-button>

  </div>
ts 复制代码
import { createStore } from "@/store"

const { commit, state } = createStore({
  state: {
    count: 0,
    age: "111"
  },
  mutations: {
    increment(state, payload) {
      state.count += payload
    },
    decrement(state, payload) {
      state.count -= payload
    }
  }
})

const count = computed(() => {
  return state.count
})

const increment = () => {
  commit('increment', 1)
  commit('increment', 1)
  commit('increment', 1)
}

const decrement = () => {
  commit('decrement', 1)
}

也有对应的类型提示,说明ts的类型推导功能也正常

结尾

最近看源码也有一些心得,在此记录一下

  1. 不要全篇通读,要挑重点
  2. 前期先走通流程,不要管某个类或者某个方法具体做了什么,只需要知道他可以干什么,最后再深入研究
  3. 如果遇到很难的代码,可以先看看网上找别人分析过
  4. 如果想要真的理解源码,只看没用,还要自己简单模仿写出来,我发现自己写出来的东西理解起来更深刻,但是看别人的代码很快就会忘记

以上只是我个人总结,每个人有不同的学习方法

附上源码

ts 复制代码
import { reactive } from "vue";

type StoreOtpions<T> = {
 state?: T
 mutations?: Record<string, (s: T, payload: any) => void>
}


export function createStore<T>(options: StoreOtpions<T> = {}) {
 return new Store<T>(options)
}

class Store<T> {
 _state: {
   data: any
 } = {
     data: {}
   };

 _mutations: StoreOtpions<T>["mutations"][] | Record<string, []> = [];

 constructor(options) {
   this._mutations = Object.create(null)
   const store = this

   const { commit } = this;

   this.commit = function boundCommit(type, payload) {
     return commit.call(store, type, payload)
   }

   this._state.data = reactive(options.state || {})

   Object.entries(options.mutations).forEach(([fnName, fnValue]) => {
     this.registerMutation(store, fnName, fnValue)
   })
   
 }


 registerMutation(store: Store<T>, type, handler) {

   const entry = this._mutations[type] || (this._mutations[type] = [])

   entry.push(function wrappedMutationHandler(payload) {
     handler.call(store, store.state, payload)
   })
 }

 get state() {
   return this._state.data
 }

 commit(type, payload) {
   const entry = this._mutations[type]

   if (!entry) {
     return
   }

   entry.forEach(function commitIterator(handler) {
     handler(payload)
   })

 }
}
相关推荐
熊的猫6 分钟前
DOM 规范 — MutationObserver 接口
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
天农学子6 分钟前
Easyui ComboBox 数据加载完成之后过滤数据
前端·javascript·easyui
mez_Blog7 分钟前
Vue之插槽(slot)
前端·javascript·vue.js·前端框架·插槽
爱睡D小猪10 分钟前
vue文本高亮处理
前端·javascript·vue.js
paopaokaka_luck10 分钟前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法
开心工作室_kaic13 分钟前
ssm102“魅力”繁峙宣传网站的设计与实现+vue(论文+源码)_kaic
前端·javascript·vue.js
放逐者-保持本心,方可放逐13 分钟前
vue3 中那些常用 靠copy 的内置函数
前端·javascript·vue.js·前端框架
IT古董14 分钟前
【前端】vue 如何完全销毁一个组件
前端·javascript·vue.js
Henry_Wu00116 分钟前
从swagger直接转 vue的api
前端·javascript·vue.js
奋飞安全17 分钟前
初试js反混淆
开发语言·javascript·ecmascript