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')
相关推荐
excel2 分钟前
如何将 MP4 文件转换为 M3U8 格式并实现流媒体播放
前端
T***16075 分钟前
Three.js 3D可视化实战,创建交互式3D场景
开发语言·javascript·ecmascript
秋氘渔7 分钟前
Vue基础语法及项目相关指令详解
前端·javascript·vue.js
IT_陈寒10 分钟前
React性能优化实战:我用这5个技巧将组件渲染速度提升了70%
前端·人工智能·后端
邱泽贤16 分钟前
uniapp 当前页调用上一页的方法
前端·javascript·uni-app
不一样的少年_17 分钟前
大部分人都错了!这才是chrome插件多脚本通信的正确姿势
前端·javascript·浏览器
Moment23 分钟前
Angular v21 无 Zone 模式前瞻:新特性、性能提升与迁移方案
前端·javascript·angular.js
yqcoder26 分钟前
vue2 和 vue3 中,provide 和 inject 用法
前端·javascript·vue.js
艾小码27 分钟前
Vue组件开发避坑指南:循环引用、更新控制与模板替代
前端·javascript·vue.js
合作小小程序员小小店32 分钟前
web开发,在线%农业产品销售管理%系统,基于idea,html,css,vue.js,layui,java,jdk,ssm
java·前端·jdk·intellij-idea·layui·数据库管理员