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')
相关推荐
LuckySusu几秒前
【CSS篇】什么是 Margin 重叠问题?如何解决?
前端·css
一依不舍几秒前
百度文心大模型 4.5 系列开源:从技术突破到全球生态的中国力量
前端
LaoZhangAI2 分钟前
Claude 4 vs Gemini 2.5 Pro:2025年顶级AI模型权威对比分析
前端·后端
Dream耀3 分钟前
Cookie:Web身份认证的基石
前端·javascript·http
LuckySusu3 分钟前
【CSS篇】元素的层叠顺序(Stacking Order)详解
前端·css
come112345 分钟前
npm 命令入门指南(前端小白版)
前端·npm·node.js
Qinana5 分钟前
今日前端学习总结
javascript
墨菲安全5 分钟前
NPM组件 alan-baileys 等窃取主机敏感信息
前端·npm·node.js·npm组件·恶意包·主机信息窃取
我在北京coding6 分钟前
npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree
前端·npm·node.js
拾光拾趣录6 分钟前
CSS高级技巧与实用伪类
前端·css