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')
相关推荐
子兮曰1 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰1 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万1 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝1 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
A黄俊辉A1 天前
uniapp webview中实现 app和内嵌的H5双向通信
vue.js·json
三十而立洋1 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁1 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
李少兄1 天前
JavaScript 隐式全局变量解析
javascript
honkun61 天前
vue 表格组件 vxe-table 配置 ajax 请求自动加载数据与表单查询
vue.js·vxe-table
kybs19911 天前
全球灾害数据分析可视化 毕业设计-附源码66794
vue.js·spring boot·mysql·安全·django·c#·asp.net