自己实现一个简单版 Pinia

我们做一个极简 Pinia,实现:

  • 创建 store
  • 响应式 state
  • 支持 actions
  • 支持在组件中使用

创建 store

createPinia() ------ 创建一个"全局容器"

js 复制代码
export function createPinia() {
  const stores = new Map();
  return {
    install(app) {
      app.provide(piniaSymbol, this);
    },
    _stores: stores,
    useStore(storeId, createStoreFn) {
      if (!this._stores.has(storeId)) {
        const store = createStoreFn();
        this._stores.set(storeId, store);
      }
      return this._stores.get(storeId);
    },
  };
}

相当于 Pinia 实例,持有所有的 store(用 Map 存储)

提供 useStore 方法,确保每个 store 是单例

app.use(pinia) 时通过 providepinia 实例挂进 Vue 的上下文中(用于 inject())

🔁 store 是单例的,怎么实现的?

js 复制代码
if (!this._stores.has(storeId)) {
  const store = createStoreFn();
  this._stores.set(storeId, store);
}

每个 storeId 对应的 store 只会初始化一次

后续所有 useStore() 都返回同一个对象

defineStore() ------ 定义 store 的注册逻辑

js 复制代码
export function defineStore(id, setupFn) {
  return function useStore() {
    const pinia = inject(piniaSymbol);
    if (!pinia) throw new Error("Pinia not installed");
    return pinia.useStore(id, () => setupFn());
  };
}

使用者写:defineStore('counter', () => { ... })

返回一个函数 useStore(),用于组件中调用

内部调用 pinia 的 useStore(),传入 storeIdsetup 逻辑

注入 pinia 实例,从 provide 中获取,确保 store 可以在全局访问

setupFn() ------ 创建响应式的 state 和 actions

js 复制代码
const state = reactive({ count: 0 });
function increment() {
  state.count++;
}

使用 Vue 3 的 reactive() 创建响应式数据

组合式 API 的方式将 state 和方法组织成一个 store

返回给组件使用

useStore() in 组件中 ------ 获取 store 实例

js 复制代码
const counter = useCounterStore();

整体代码

🔧 miniPinia.js

js 复制代码
// miniPinia.js
import { reactive, inject, provide } from "vue";

const piniaSymbol = Symbol("miniPinia");

export function createPinia() {
  const stores = new Map();
  return {
    install(app) {
      app.provide(piniaSymbol, this);
    },
    _stores: stores,
    useStore(storeId, createStoreFn) {
      if (!this._stores.has(storeId)) {
        const store = createStoreFn();
        this._stores.set(storeId, store);
      }
      return this._stores.get(storeId);
    },
  };
}

export function defineStore(id, setupFn) {
  return function useStore() {
    const pinia = inject(piniaSymbol);
    if (!pinia) throw new Error("Pinia not installed");
    return pinia.useStore(id, () => setupFn());
  };
}
js 复制代码
// counterStore.js
import { reactive } from "vue";
import { defineStore } from "./miniPinia";

export const useCounterStore = defineStore("counter", () => {
  const state = reactive({
    count: 0,
  });

  function increment() {
    state.count++;
  }

  return {
    state,
    increment,
  };
});
html 复制代码
<!-- App.vue -->
<template>
  <div>
    <h2>Count: {{ counter.state.count }}</h2>
    <button @click="counter.increment()">+1</button>
  </div>
</template>

<script setup>
  import { useCounterStore } from "./counterStore";

  const counter = useCounterStore();
</script>
js 复制代码
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import { createPinia } from "./miniPinia";

const app = createApp(App);
const pinia = createPinia();
app.use(pinia);
app.mount("#app");
相关推荐
. . . . .38 分钟前
shadcn组件库
前端
2501_944711431 小时前
JS 对象遍历全解析
开发语言·前端·javascript
发现一只大呆瓜1 小时前
虚拟列表:支持“向上加载”的历史消息(Vue 3 & React 双版本)
前端·javascript·面试
css趣多多2 小时前
ctx 上下文对象控制新增 / 编辑表单显示隐藏的逻辑
前端
_codemonster2 小时前
Vue的三种使用方式对比
前端·javascript·vue.js
寻找奶酪的mouse2 小时前
30岁技术人对职业和生活的思考
前端·后端·年终总结
梦想很大很大2 小时前
使用 Go + Gin + Fx 构建工程化后端服务模板(gin-app 实践)
前端·后端·go
We་ct2 小时前
LeetCode 56. 合并区间:区间重叠问题的核心解法与代码解析
前端·算法·leetcode·typescript
张3蜂2 小时前
深入理解 Python 的 frozenset:为什么要有“不可变集合”?
前端·python·spring
无小道2 小时前
Qt——事件简单介绍
开发语言·前端·qt