Vue3 仿vuex或者pinia自定义状态管理器

1. 状态管理器核心实现(ts写法)

typescript 复制代码
import { reactive, effectScope, computed, App } from 'vue'

type StoreState = Record<string, any>
type StoreActions = Record<string, Function>
type StoreGetters = Record<string, Function>

interface StoreOptions {
  id: string
  state?: () => StoreState
  actions?: StoreActions
  getters?: StoreGetters
}

interface StoreInstance {
  state: StoreState
  actions: StoreActions
  getters: Record<string, any>
  $reset: () => void
  $dispose: () => void
}

export function createPinia() {
  const stores: Record<string, StoreInstance> = {}
  const plugins: Array<(context: { store: StoreInstance }) => void> = []

  function install(app: App) {
    app.provide('pinia', this)
  }

  function use(plugin: (context: { store: StoreInstance }) => void) {
    plugins.push(plugin)
  }

  function defineStore(options: StoreOptions): StoreInstance {
    const state = reactive(options.state?.() || {})
    const scope = effectScope(true)

    const actions: StoreActions = {}
    const getters: Record<string, any> = {}

    // Process actions
    if (options.actions) {
      for (const [name, fn] of Object.entries(options.actions)) {
        actions[name] = function(...args: any[]) {
          return scope.run(() => fn.call(store, ...args))
        }
      }
    }

    // Process getters
    if (options.getters) {
      scope.run(() => {
        for (const [name, fn] of Object.entries(options.getters)) {
          getters[name] = computed(() => fn.call(store, state))
        }
      })
    }

    const store: StoreInstance = {
      state,
      actions,
      getters,
      $reset() {
        Object.assign(state, options.state?.() || {})
      },
      $dispose() {
        scope.stop()
        delete stores[options.id]
      }
    }

    // Apply plugins
    plugins.forEach(plugin => plugin({ store }))

    stores[options.id] = store
    return store
  }

  return { install, use, defineStore, _stores: stores }
}

export type Pinia = ReturnType<typeof createPinia>

JavaScript 版本

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

export function createPinia() {
  const stores = {}
  const plugins = []

  function install(app) {
    app.provide('pinia', this)
  }

  function use(plugin) {
    plugins.push(plugin)
  }

  function defineStore(options) {
    const state = reactive(options.state?.() || {})
    const scope = effectScope(true)

    const actions = {}
    const getters = {}

    // Process actions
    if (options.actions) {
      for (const [name, fn] of Object.entries(options.actions)) {
        actions[name] = function(...args) {
          return scope.run(() => fn.call(store, ...args))
        }
      }
    }

    // Process getters
    if (options.getters) {
      scope.run(() => {
        for (const [name, fn] of Object.entries(options.getters)) {
          getters[name] = computed(() => fn.call(store, state))
        }
      })
    }

    const store = {
      state,
      actions,
      getters,
      $reset() {
        Object.assign(state, options.state?.() || {})
      },
      $dispose() {
        scope.stop()
        delete stores[options.id]
      }
    }

    // Apply plugins
    plugins.forEach(plugin => plugin({ store }))

    stores[options.id] = store
    return store
  }

  return { install, use, defineStore, _stores: stores }
}

2. 项目中使用

2.1 初始化 Pinia

javascript 复制代码
import { createApp } from 'vue'
import { createPinia } from './store-core'
import App from './App.vue'

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

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

2.2 创建 Store

typescript 复制代码
import { defineStore } from '../store-core'

export const useCounterStore = defineStore({
  id: 'counter',
  state: () => ({
    count: 0,
    todos: [] as string[]
  }),
  actions: {
    increment() {
      this.count++
    },
    addTodo(todo: string) {
      this.todos.push(todo)
    },
    async fetchTodo() {
      const response = await fetch('https://jsonplaceholder.typicode.com/todos/1')
      const todo = await response.json()
      this.addTodo(todo.title)
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2
    },
    todoCount(state) {
      return state.todos.length
    }
  }
})

2.3 在组件中使用

xml 复制代码
<script setup lang="ts">
import { useCounterStore } from '../stores/counter'

const counter = useCounterStore()
</script>

<template>
  <div>
    <h2>Counter Store</h2>
    <p>Count: {{ counter.state.count }}</p>
    <p>Double Count: {{ counter.getters.doubleCount.value }}</p>
    <p>Todo Count: {{ counter.getters.todoCount.value }}</p>
    
    <button @click="counter.actions.increment()">Increment</button>
    <button @click="counter.actions.addTodo('New Todo')">Add Todo</button>
    <button @click="counter.actions.fetchTodo()">Fetch Todo</button>
    
    <ul>
      <li v-for="(todo, index) in counter.state.todos" :key="index">
        {{ todo }}
      </li>
    </ul>
  </div>
</template>

3. 高级用法 - 插件系统

3.1 创建一个持久化插件

typescript 复制代码
interface PersistOptions {
  key?: string
  storage?: Storage
}

export function createPersistPlugin(options: PersistOptions = {}) {
  const { key = 'pinia-state', storage = localStorage } = options
  
  return ({ store }) => {
    // 从存储中恢复状态
    const savedState = storage.getItem(`${key}:${store._id}`)
    if (savedState) {
      Object.assign(store.state, JSON.parse(savedState))
    }

    // 监听状态变化并保存
    store._scope.run(() => {
      watch(
        () => store.state,
        (state) => {
          storage.setItem(`${key}:${store._id}`, JSON.stringify(state))
        },
        { deep: true }
      )
    })
  }
}

3.2 使用插件

javascript 复制代码
import { createApp } from 'vue'
import { createPinia } from './store-core'
import { createPersistPlugin } from './plugins/persist'
import App from './App.vue'

const pinia = createPinia()
pinia.use(createPersistPlugin({
  key: 'my-app-state'
}))

const app = createApp(App)
app.use(pinia)
app.mount('#app')
相关推荐
一 乐12 分钟前
家政服务管理系统|基于springboot + vue家政服务管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·家政服务管理系统
IT_陈寒14 分钟前
Vite热更新失效?可能你在用Windows
前端·人工智能·后端
烬羽42 分钟前
后端返回的 JSON 字符串,浏览器怎么"看懂"的?——Ajax 全链路拆解
javascript
tedcloud1231 小时前
taste-skill部署教程:打造个性化AI推荐工作流
服务器·前端·人工智能·系统架构·edge
xinhuanjieyi1 小时前
html修复游戏种太阳错误
前端·游戏·html
半个落月1 小时前
一个新手用 Bun + Axios 调通 DeepSeek API 的实践记录
javascript
不好听6132 小时前
深入理解链表:线性数据结构的另一面
javascript·数据结构
林希_Rachel_傻希希2 小时前
学React治好了我的焦虑症,1小时速通React 前20分钟。
前端·javascript·面试
小林ixn2 小时前
从 Ajax 到异步编程:JSON 序列化、Event Loop 与 XHR 请求完全解析
javascript
Cache技术分享2 小时前
435. Java 日期时间 API - Clock 灵活获取当前时间
前端·后端