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')
相关推荐
Csvn14 小时前
🛡️ 前端水印方案全解析——从「能被 F12 删掉」到「删不掉」的反破解实战
前端
学网安的肆伍15 小时前
【046-WEB攻防篇】注入工具&SQLMAP&Tamper编写&指纹修改&高权限操作&目录架构
前端·架构
森林的尽头是阳光15 小时前
tree回显问题
javascript·vue.js·elementui
weixin_BYSJ198716 小时前
「课设设计」springboot校园超市助购系统26449 (附源码)
java·javascript·spring boot·python·django·flask·php
Highcharts.js16 小时前
如何下载使用Highcharts Grid 开发web可编辑表格
前端·人工智能·grid 表格·web 表格·在线编辑表格·highcharts表格安装·表格开发工具
用户629605932470517 小时前
从“浏览器根本区分不了”到真正实现:刷新不退出,关闭标签页自动退出,前端判断浏览器关闭和刷新
前端
qetfw17 小时前
MWU:Vue 3 + FastAPI 的 MaaFramework 跨平台 WebUI 源码
前端·vue.js·python·fastapi·开源项目·效率工具
mfxcyh17 小时前
Vue3+Ant Design Vue 实现手动异步文件上传(含大小校验、弹窗上传)
前端·javascript·vue.js
wordbaby17 小时前
Web端热敏标签打印完整解决方案(QZ Tray + 译维A42)
前端
swipe17 小时前
03|Axios 请求进了后端之后:Controller、Request、Response 是怎么接住它的?
前端·后端·全栈