深入分析 Vuex 或 Pinia 源码中 `devtools` 集成的工作原理,以及它们如何与浏览器扩展进行通信以提供调试功能。

好嘞,各位靓仔靓女,今天咱们来聊聊 Vuex 和 Pinia 里面那个神秘兮兮的 devtools 集成,看看它们是怎么勾搭上浏览器扩展,然后把咱们的代码扒个底朝天,方便咱们调试的。放心,保证通俗易懂,看完你也能自己写一个简易版的。

开场白:Debugger 的诞生,要感谢八卦的心

话说,程序员最喜欢干的事情除了写Bug,就是Debug了。但是,如果啥都看不见,全靠猜,那Debug就跟瞎猫碰死耗子一样,效率低到令人发指。所以,Debug工具就应运而生了。devtools 其实就是这样一个超级 Debugger,它能让你看到 Vue 应用的状态、数据流、甚至还能时光倒流。这背后,Vuex 和 Pinia 功不可没。

第一节:devtools 集成的核心思路:监听 + 通知

简单来说,devtools 集成的核心就是两件事:

  1. 监听: 监听 Vuex/Pinia 的状态变化、mutation/action 的触发、甚至组件的渲染。
  2. 通知: 把这些变化通知给浏览器扩展,让它能展示出来。

这个过程就像一个八卦记者(Vuex/Pinia),时刻关注着明星(Vue 应用)的一举一动,然后把这些八卦消息(状态变化)告诉吃瓜群众(浏览器扩展)。

第二节:Vuex 的 devtools 集成:传统艺能,稳定可靠

Vuex 的 devtools 集成相对来说比较成熟,它的实现方式也比较直接:

  • 依赖: @vue/devtools 这个 npm 包是关键,它提供了 connect 方法,用于连接 devtools 扩展。
  • 连接: 在 Vuex 的 store 创建的时候,会判断当前环境是否支持 devtools,如果支持,就调用 connect 方法建立连接。
  • 监听: Vuex 通过 subscribesubscribeAction 方法监听状态变化和 action 的触发。
  • 通知: 当状态变化或者 action 触发的时候,Vuex 会把相关信息通过 devtools 连接发送给浏览器扩展。

代码示例:Vuex 连接 devtools

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    incrementAsync ({ commit }) {
      setTimeout(() => {
        commit('increment')
      }, 1000)
    }
  },
  plugins: process.env.NODE_ENV !== 'production' && typeof window !== 'undefined' && window.__VUE_DEVTOOLS_GLOBAL_HOOK__
    ? [createVuexDevtools()]
    : []
})

function createVuexDevtools() {
  return store => {
    // vuex-devtools 的核心逻辑
    const devtools = window.__VUE_DEVTOOLS_GLOBAL_HOOK__;

    if (!devtools) return;

    devtools.emit('vuex:init', store);

    store.subscribe((mutation, state) => {
      devtools.emit('vuex:mutation', mutation, state);
    });

    store.subscribeAction((action, state) => {
      devtools.emit('vuex:action', action, state);
    });
  };
}

export default store

代码解析:

  • window.__VUE_DEVTOOLS_GLOBAL_HOOK__:这是 devtools 扩展注入到页面中的一个全局变量,是 Vuex 和 devtools 沟通的桥梁。
  • devtools.emit:这个方法用于向 devtools 扩展发送消息。
  • vuex:init:初始化事件,告诉 devtools 扩展有一个新的 Vuex store。
  • vuex:mutation:mutation 事件,告诉 devtools 扩展有一个 mutation 被提交了。
  • vuex:action:action 事件,告诉 devtools 扩展有一个 action 被触发了。

Vuex devtools 的数据结构:

事件类型 数据 描述
vuex:init store 对象 初始化事件,包含 store 的状态、mutations、actions 等信息。
vuex:mutation { type: string, payload: any }, state 对象 Mutation 事件,包含 mutation 的类型和 payload,以及修改后的 state。
vuex:action { type: string, payload: any }, state 对象 Action 事件,包含 action 的类型和 payload,以及执行 action 前后的 state。(注意: action 只能拿到触发前的 state)

第三节:Pinia 的 devtools 集成:后起之秀,更加灵活

Pinia 的 devtools 集成更加现代化,它利用了 Vue 3 的 Composition API,提供了更灵活的监听和通知机制。

  • 依赖: pinia 本身就内置了对 devtools 的支持,不需要额外的依赖。
  • 连接: Pinia 会自动检测当前环境是否支持 devtools,如果支持,就会建立连接。
  • 监听: Pinia 利用 effectScopeonBefore/onAfter 等 API 监听状态变化和 action 的触发。
  • 通知: Pinia 会把相关信息通过 devtools 连接发送给浏览器扩展。

代码示例:Pinia 连接 devtools

import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

app.use(pinia)
app.mount('#app')

// 在 store 中
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({ count: 0 }),
  getters: {
    doubleCount: (state) => state.count * 2,
  },
  actions: {
    increment() {
      this.count++
    },
  },
})

代码解析:

  • Pinia 默认就集成了 devtools,只要安装了 Vue Devtools 扩展,就可以直接使用。
  • Pinia 利用 Vue 3 的响应式系统,可以更精细地监听状态变化。
  • Pinia 的 API 更加简洁,使用起来更方便。

Pinia devtools 的数据结构:

事件类型 数据 描述
pinia:init pinia 实例 初始化事件,包含 pinia 的所有 store 信息。
pinia:store store 对象 单个 store 的信息,包含 state、getters、actions 等。
pinia:patch { storeId: string, type: 'direct' | 'merge', payload: Partial<State> } state 被直接修改或者通过 store.$patch 合并修改。
pinia:action { storeId: string, name: string, args: any[], result: any, error: any } action 被调用,包含 action 的名称、参数、返回值和错误信息。
pinia:getter { storeId: string, name: string, result: any } getter 被访问,包含 getter 的名称和返回值。

第四节:devtools 扩展的实现:接收 + 展示

浏览器扩展才是真正把这些数据展示出来的功臣。它的主要工作就是:

  • 接收: 接收 Vuex/Pinia 发送过来的消息。
  • 处理: 对接收到的消息进行处理,提取出关键信息。
  • 展示: 把这些信息以友好的方式展示出来,例如状态树、时间线等等。
  • 交互: 提供一些交互功能,例如时光倒流、修改状态等等。

简易版 devtools 扩展代码示例:

// background.js (扩展的后台脚本)

chrome.devtools.panels.create(
  "My Vuex/Pinia Debugger",
  "icon.png",
  "panel.html",
  function(panel) {
    console.log("Panel created");
  }
);

// panel.html (扩展的面板页面)
<!DOCTYPE html>
<html>
<head>
  <title>My Vuex/Pinia Debugger</title>
</head>
<body>
  <h1>Vuex/Pinia State</h1>
  <div id="state"></div>

  <script>
    // 从 content script 接收消息
    chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
      if (request.type === "vuex:mutation" || request.type === "pinia:patch") {
        document.getElementById("state").textContent = JSON.stringify(request.payload, null, 2);
      }
    });
  </script>
</body>
</html>

// content.js (注入到页面的脚本)
window.addEventListener('message', function(event) {
  if (event.source != window)
    return;

  if (event.data.type && (event.data.type.startsWith('vuex:') || event.data.type.startsWith('pinia:'))) {
    chrome.runtime.sendMessage(event.data);
  }
}, false);

// 在 Vuex/Pinia 中发送消息 (简化版)
// Vuex:
devtools.emit('vuex:mutation', { type: 'MY_MUTATION', payload: { data: 'some data' } }, store.state);

// Pinia:
window.postMessage({ type: 'pinia:patch', payload: { count: 10 } }, '*');

代码解析:

  • background.js: 创建 devtools 面板。
  • panel.html: devtools 面板的 UI,用于展示状态信息。
  • content.js: 注入到页面中的脚本,用于监听 Vuex/Pinia 发送的消息,并转发给 background.js
  • chrome.devtools.panels.create 创建 devtools 面板的 API。
  • chrome.runtime.onMessage.addListener 监听来自 content script 的消息。
  • chrome.runtime.sendMessagedevtools 面板发送消息。
  • window.postMessage 用于在Vue应用中向content.js发送消息。

第五节:细节深挖:时间旅行的秘密

devtools 最酷的功能之一就是时间旅行,它可以让你回到过去的状态,看看之前的代码是怎么运行的。这个功能的实现原理其实也很简单:

  1. 记录: 每次状态变化,都记录下当前的状态和对应的操作。
  2. 回放: 当你想回到过去的状态时,devtools 会把之前的操作重新执行一遍,直到达到目标状态。

这个过程就像录像带倒带一样,只不过录的是代码的状态。

第六节:总结与展望:devtools 的未来

devtools 集成是 Vuex 和 Pinia 的重要组成部分,它极大地提升了开发效率,让调试变得更加轻松。随着 Vue 生态的不断发展,devtools 也会变得更加强大,例如:

  • 更智能的分析: 自动检测性能瓶颈、代码错误等等。
  • 更强大的交互: 支持更复杂的调试操作,例如断点调试、单步执行等等。
  • 更友好的界面: 提供更直观、更易用的界面。

最后的彩蛋:自己动手,丰衣足食

如果你对 devtools 集成感兴趣,不妨自己动手写一个简易版的,这样可以更深入地理解它的工作原理。相信我,这绝对是一件很有趣的事情!

好了,今天的讲座就到这里。希望大家有所收获,下次再见!

表格总结:Vuex vs Pinia devtools 集成

特性 Vuex Pinia
依赖 @vue/devtools 内置
连接方式 手动连接 自动连接
监听机制 subscribesubscribeAction effectScopeonBefore/onAfter
数据结构 事件类型:vuex:initvuex:mutationvuex:action 事件类型:pinia:initpinia:storepinia:patchpinia:actionpinia:getter
灵活性 相对固定 更灵活
易用性 相对复杂 更简洁
是否支持TS 良好 更好(原生支持)
状态管理方式 通过 store 对象集中管理 通过 defineStore 定义多个 store,更模块化
Devtools 集成 需要手动配置,相对繁琐 开箱即用,自动集成

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注