Pinia 核心源码简易实现

一、Pinia 的设计理念

Pinia 是一个专为 Vue.js 设计的状态管理库,其设计目标是提供一种更轻量级且类型友好的状态管理解决方案。以下是 Pinia 的主要设计理念:

1. 简洁性

Pinia 的 API 设计非常简洁,易于学习和使用。它摒弃了 Vuex 中的一些复杂概念(如 mutations),仅保留 actions 和 getters。

2. 类型友好

Pinia 完全支持 TypeScript,并且在设计时就考虑了类型推导,使得开发者可以享受到更好的类型提示和安全性。

3. 模块化

Pinia 支持模块化设计,每个 store 都是一个独立的模块,可以单独创建、管理和使用,避免了传统全局状态树的复杂性。

4. 可扩展性

Pinia 提供了插件系统,允许开发者通过插件来扩展 store 的功能,比如添加持久化存储、日志记录等功能。

5. 性能优化

Pinia 利用了 Vue 3 的响应式系统,确保状态更新的高效性和最小的性能开销。

二、Pinia 核心源码分析

1. 创建 Store

Pinia 使用 defineStore 函数来定义 store。这个函数返回一个可组合的函数,用于在组件中使用 store。

javascript 复制代码
import { defineStore } from 'pinia'

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

在内部,defineStore 实际上是在创建一个 reactive 对象,并将其包装成一个可组合函数。Pinia 利用了 Vue 3 的 reactivecomputed 来实现响应式状态和计算属性。

2. 响应式系统

Pinia 依赖于 Vue 3 的响应式系统来追踪依赖并更新视图。当 store 的状态发生变化时,所有依赖该状态的 computed 属性和组件都会自动更新。

vbnet 复制代码
import { reactive, computed } from 'vue'

function createReactiveStore(options) {
  const state = reactive(options.state())
  const getters = {}

  for (const key in options.getters) {
    getters[key] = computed(() => options.getters[key](state))
  }

  const actions = {}
  for (const key in options.actions) {
    actions[key] = options.actions[key].bind({ ...state, ...getters, ...actions })
  }

  return {
    ...state,
    ...getters,
    ...actions,
  }
}

3. 插件系统

Pinia 提供了一个灵活的插件系统,允许开发者通过插件来扩展 store 的功能。插件可以通过 useStore 方法访问到 store 实例,并对其进行修改或增强。

javascript 复制代码
export function myPlugin() {
  return (store) => {
    store.$onAction(({ name, store, args, onError, after }) => {
      console.log(`Action ${name} is triggered with args:`, args)
      
      after(() => {
        console.log(`Action ${name} has finished`)
      })

      onError((error) => {
        console.error(`Action ${name} failed with error:`, error)
      })
    })
  }
}

// 在创建 pinia 时使用插件
const pinia = createPinia().use(myPlugin())

插件机制的核心在于 use 方法,它接受一个插件函数作为参数,并将其应用到所有的 store 上。

4. Devtools 集成

Pinia 提供了对 Vue Devtools 的深度集成,包括时间旅行调试、动作跟踪等功能。这些功能是通过 $onAction 钩子实现的,它允许开发者监听 store 上的所有 action 调用,并记录相关的上下文信息。

javascript 复制代码
store.$onAction(({ name, store, args, onError, after }) => {
  // 记录 action 调用
  devtools.emit('action-start', { name, args })

  after(() => {
    // 记录 action 结束
    devtools.emit('action-end', { name, result: store.$state })
  })

  onError((error) => {
    // 记录错误
    devtools.emit('action-error', { name, error })
  })
})

5. 持久化存储

虽然 Pinia 本身不直接提供持久化存储的功能,但可以通过插件轻松实现。例如,可以使用 localStorage 来保存 store 的状态,并在页面加载时恢复。

javascript 复制代码
export function persistStatePlugin() {
  return (store) => {
    // 从 localStorage 加载初始状态
    const savedState = localStorage.getItem(store.$id)
    if (savedState) {
      store.$patch(JSON.parse(savedState))
    }

    // 监听状态变化并保存到 localStorage
    store.$subscribe((mutation, state) => {
      localStorage.setItem(store.$id, JSON.stringify(state))
    })
  }
}

6. 错误处理

Pinia 提供了完善的错误处理机制,特别是在 action 执行过程中发生的错误。开发者可以通过 $onAction 钩子捕获并处理这些错误。

javascript 复制代码
store.$onAction(({ name, store, args, onError, after }) => {
  onError((error) => {
    console.error(`Action ${name} failed with error:`, error)
    // 处理错误,例如显示错误提示给用户
  })
})

三、总结

Pinia 的设计理念强调了简洁性、类型友好性和模块化,使其成为 Vue.js 应用程序的理想状态管理方案。通过深入分析其核心源码,我们可以看到 Pinia 如何利用 Vue 3 的响应式系统来实现高效的 state 管理,并通过插件系统提供了丰富的扩展能力。

最后简易版本实现:

ini 复制代码
class Store {
  constructor(id, options) {
    this.id = id;
    this.state = reactive(options.state());
    this._getters = {};
    this._actions = {};

    // 初始化 getters
    if (options.getters) {
      Object.keys(options.getters).forEach(key => {
        const getterFn = options.getters[key];
        Object.defineProperty(this, key, {
          get: () => getterFn(this.state)
        });
        this._getters[key] = getterFn;
      });
    }

    // 初始化 actions
    if (options.actions) {
      Object.keys(options.actions).forEach(key => {
        const actionFn = options.actions[key].bind(this);
        this[key] = actionFn;
        this._actions[key] = actionFn;
      });
    }
  }

  $reset() {
    this.state = reactive(this.$options.state());
  }

  $subscribe(callback) {
    watch(() => this.state, callback, { deep: true });
  }

  $onAction(listener) {
    return addListener(this, listener);
  }
}

function defineStore(id, options) {
  let store;

  function useStore() {
    if (!store) {
      store = new Store(id, options);
      store.$id = id;
      store.$options = options;
    }
    return store;
  }

  useStore.$id = id;

  return useStore;
}

// 辅助函数
function reactive(obj) {
  return new Proxy(obj, {
    set(target, key, value) {
      target[key] = value;
      console.log(`Updated ${key} to ${value}`);
      return true;
    },
    get(target, key) {
      console.log(`Getting ${key}`);
      return target[key];
    }
  });
}

function watch(source, cb, options = {}) {
  const effect = () => {
    cleanup();
    activeEffect = effect;
    const newValue = source();
    activeEffect = null;
    if (deepCompare(newValue, oldValue)) {
      cb(newValue, oldValue);
      oldValue = newValue;
    }
  };

  let oldValue, cleanup;
  effect();

  return () => {
    cleanup && cleanup();
  };
}

let activeEffect;
function track(target, key) {
  if (activeEffect) {
    let depsMap = targetMap.get(target);
    if (!depsMap) {
      targetMap.set(target, (depsMap = new Map()));
    }
    let dep = depsMap.get(key);
    if (!dep) {
      depsMap.set(key, (dep = new Set()));
    }
    dep.add(activeEffect);
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target);
  if (!depsMap) {
    return;
  }
  const dep = depsMap.get(key);
  if (dep) {
    dep.forEach(effect => effect());
  }
}

const targetMap = new WeakMap();

function deepCompare(a, b) {
  if (a === b) return false;
  return JSON.stringify(a) !== JSON.stringify(b);
}

function addListener(store, listener) {
  const queue = [];
  const unsubscribe = store.$subscribe((mutation, state) => {
    queue.push({ mutation, state });
  });

  const stop = () => {
    unsubscribe();
    queue.length = 0;
  };

  const flush = () => {
    for (const entry of queue) {
      listener(entry.mutation, entry.state);
    }
    queue.length = 0;
  };

  return { stop, flush };
}
相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60614 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅5 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment5 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax