@free-walk/svelte-store 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +185 -0
- package/dist/svelte-store.cjs +1 -0
- package/dist/svelte-store.mjs +1 -0
- package/package.json +45 -0
- package/src/index.ts +434 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 free-walk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# svelte-store
|
|
2
|
+
|
|
3
|
+
Pinia 风格的 Svelte 5 状态管理库,基于 `svelte/store` 构建。
|
|
4
|
+
|
|
5
|
+
## 特性
|
|
6
|
+
|
|
7
|
+
- 🏪 **Pinia 风格 API** — `defineStore` 定义 store,支持 Options API 和 Setup 风格
|
|
8
|
+
- 📦 **基于 svelte/store** — 完全兼容 Svelte 生态,支持 `$store` 自动订阅
|
|
9
|
+
- 🔌 **插件系统** — 支持全局插件,可扩展持久化、日志等功能
|
|
10
|
+
- 🎯 **TypeScript** — 完整类型推导
|
|
11
|
+
- ⚡ **轻量** — 零额外依赖,仅依赖 `svelte/store`
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install svelte-store
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 快速开始
|
|
20
|
+
|
|
21
|
+
### Options API 风格
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { defineStore } from 'svelte-store'
|
|
25
|
+
|
|
26
|
+
export const useCounterStore = defineStore('counter', {
|
|
27
|
+
state: () => ({
|
|
28
|
+
count: 0,
|
|
29
|
+
name: 'Counter',
|
|
30
|
+
}),
|
|
31
|
+
getters: {
|
|
32
|
+
doubleCount: (state) => state.count * 2,
|
|
33
|
+
displayName: (state) => `${state.name}: ${state.count}`,
|
|
34
|
+
},
|
|
35
|
+
actions: {
|
|
36
|
+
increment() {
|
|
37
|
+
this.$patch({ count: this.$state.count + 1 })
|
|
38
|
+
},
|
|
39
|
+
decrement() {
|
|
40
|
+
this.$patch({ count: this.$state.count - 1 })
|
|
41
|
+
},
|
|
42
|
+
incrementBy(amount: number) {
|
|
43
|
+
this.$patch({ count: this.$state.count + amount })
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Setup 风格
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { defineStore, writable, derived } from 'svelte-store'
|
|
53
|
+
|
|
54
|
+
export const useCounterStore = defineStore('counter', () => {
|
|
55
|
+
const count = writable(0)
|
|
56
|
+
const doubleCount = derived(count, ($count) => $count * 2)
|
|
57
|
+
|
|
58
|
+
function increment() {
|
|
59
|
+
count.update((n) => n + 1)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function decrement() {
|
|
63
|
+
count.update((n) => n - 1)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { count, doubleCount, increment, decrement }
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 在组件中使用
|
|
71
|
+
|
|
72
|
+
```svelte
|
|
73
|
+
<script>
|
|
74
|
+
import { useCounterStore } from '../stores/counter'
|
|
75
|
+
|
|
76
|
+
const counter = useCounterStore()
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<p>Count: {counter.$state.count}</p>
|
|
80
|
+
<p>Double: {counter.doubleCount}</p>
|
|
81
|
+
|
|
82
|
+
<button onclick={() => counter.increment()}>+</button>
|
|
83
|
+
<button onclick={() => counter.decrement()}>-</button>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Store 实例 API
|
|
87
|
+
|
|
88
|
+
| 方法 / 属性 | 说明 |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `$id` | Store 的唯一标识字符串 |
|
|
91
|
+
| `$state` | 获取当前 state 快照 |
|
|
92
|
+
| `$patch(partial)` | 批量更新 state(对象或函数) |
|
|
93
|
+
| `$reset()` | 重置 state 到初始值(仅 Options API) |
|
|
94
|
+
| `$subscribe(callback)` | 监听 state 变化,返回取消订阅函数 |
|
|
95
|
+
| `subscribe(run)` | svelte/store 标准订阅接口 |
|
|
96
|
+
|
|
97
|
+
### $patch 用法
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// 对象方式
|
|
101
|
+
counter.$patch({ count: 10 })
|
|
102
|
+
|
|
103
|
+
// 函数方式
|
|
104
|
+
counter.$patch((state) => {
|
|
105
|
+
state.count++
|
|
106
|
+
state.name = 'Updated'
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### $subscribe 用法
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
const unsubscribe = counter.$subscribe((state) => {
|
|
114
|
+
console.log('state 变化:', state)
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// 需要时取消订阅
|
|
118
|
+
unsubscribe()
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## 插件系统
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { addPlugin } from 'svelte-store'
|
|
125
|
+
|
|
126
|
+
// 持久化插件示例
|
|
127
|
+
addPlugin(({ store, storeId }) => {
|
|
128
|
+
// 恢复持久化数据
|
|
129
|
+
const key = `svelte-store-${storeId}`
|
|
130
|
+
const saved = localStorage.getItem(key)
|
|
131
|
+
if (saved) {
|
|
132
|
+
try {
|
|
133
|
+
store.$patch(JSON.parse(saved))
|
|
134
|
+
} catch {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// 监听变化并持久化
|
|
138
|
+
store.$subscribe((state) => {
|
|
139
|
+
localStorage.setItem(key, JSON.stringify(state))
|
|
140
|
+
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// 日志插件示例
|
|
144
|
+
addPlugin(({ store, storeId }) => {
|
|
145
|
+
store.$subscribe((state) => {
|
|
146
|
+
console.log(`[${storeId}]`, state)
|
|
147
|
+
})
|
|
148
|
+
})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 辅助函数
|
|
152
|
+
|
|
153
|
+
### mapState
|
|
154
|
+
|
|
155
|
+
从 store 中提取状态为独立的 Readable store:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { mapState } from 'svelte-store'
|
|
159
|
+
|
|
160
|
+
const { count, name } = mapState(useCounterStore, ['count', 'name'])
|
|
161
|
+
// count 和 name 是 Readable<number> 和 Readable<string>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### mapActions
|
|
165
|
+
|
|
166
|
+
从 store 中提取 actions 为独立函数:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
import { mapActions } from 'svelte-store'
|
|
170
|
+
|
|
171
|
+
const { increment, decrement } = mapActions(useCounterStore, ['increment', 'decrement'])
|
|
172
|
+
increment() // 直接调用
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## 兼容性
|
|
176
|
+
|
|
177
|
+
| 格式 | 文件 | 用途 |
|
|
178
|
+
|---|---|---|
|
|
179
|
+
| ESM | `dist/svelte-store.mjs` | `import` 语法 |
|
|
180
|
+
| CJS | `dist/svelte-store.cjs` | `require()` 语法 |
|
|
181
|
+
| Svelte 源码 | `src/index.ts` | Svelte 项目直接引用 |
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var l=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var v=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var j=(t,e)=>{for(var a in e)l(t,a,{get:e[a],enumerable:!0})},$=(t,e,a,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of v(e))!k.call(t,n)&&n!==a&&l(t,n,{get:()=>e[n],enumerable:!(r=R(e,n))||r.enumerable});return t};var O=t=>$(l({},"__esModule",{value:!0}),t);var W={};j(W,{addPlugin:()=>P,clearStores:()=>h,defineStore:()=>A,derived:()=>u.derived,get:()=>u.get,getRegisteredStore:()=>m,mapActions:()=>K,mapState:()=>D,readable:()=>u.readable,readonly:()=>u.readonly,writable:()=>u.writable});module.exports=O(W);var c=require("svelte/store"),u=require("svelte/store"),b=new Map,x=[];function A(t,e){return function(){if(b.has(t))return b.get(t);let r;typeof e=="function"?r=I(t,e):r=G(t,e),b.set(t,r);for(let n of x){let i=n({store:r,storeId:t,options:typeof e=="function"?{id:t}:e});i&&Object.assign(r,i)}return r}}function G(t,e){let a=e.state?e.state():{},r=(0,c.writable)({...a}),n={$id:t,subscribe:r.subscribe,get $state(){return(0,c.get)(r)},$patch(i){r.update(y=>typeof i=="function"?(i(y),{...y}):{...y,...i})},$reset(){let i=e.state?e.state():{};r.set({...i})},$subscribe(i){return r.subscribe(i)}};if(e.getters)for(let[i,y]of Object.entries(e.getters))Object.defineProperty(n,i,{get(){return y((0,c.get)(r))},enumerable:!0});if(e.actions)for(let[i,y]of Object.entries(e.actions))n[i]=(...d)=>y.apply(n,d);return n}function I(t,e){let a=e(),r=(0,c.writable)({}),n={},i={},y={};for(let[o,s]of Object.entries(a))typeof s=="function"?y[o]=s:s&&typeof s=="object"&&"subscribe"in s&&("set"in s?n[o]=s:i[o]=s);function d(){let o={};for(let[s,f]of Object.entries(n))o[s]=(0,c.get)(f);for(let[s,f]of Object.entries(i))o[s]=(0,c.get)(f);r.set(o)}let p=[];for(let o of Object.values(n))p.push(o.subscribe(()=>d()));for(let o of Object.values(i))p.push(o.subscribe(()=>d()));let g={$id:t,subscribe:r.subscribe,get $state(){return(0,c.get)(r)},$patch(o){if(typeof o=="function"){let s={};for(let[f,S]of Object.entries(n))s[f]=(0,c.get)(S);o(s);for(let[f,S]of Object.entries(n))f in s&&S.set(s[f])}else for(let[s,f]of Object.entries(o))s in n&&n[s].set(f)},$reset(){console.warn(`[svelte-store] Setup store "${t}" \u4E0D\u652F\u6301 $reset\uFF0C\u8BF7\u624B\u52A8\u91CD\u7F6E\u72B6\u6001`)},$subscribe(o){return r.subscribe(o)}};for(let[o,s]of Object.entries(y))g[o]=s;for(let[o,s]of Object.entries(n))Object.defineProperty(g,o,{get(){return(0,c.get)(s)},set(f){s.set(f)},enumerable:!0});for(let[o,s]of Object.entries(i))Object.defineProperty(g,o,{get(){return(0,c.get)(s)},enumerable:!0});return g}function P(t){x.push(t)}function m(t){return b.get(t)}function h(){b.clear()}function D(t,e){let a=t(),r={};for(let n of e)r[n]=(0,c.derived)({subscribe:a.subscribe},i=>i[n]);return r}function K(t,e){let a=t(),r={};for(let n of e)typeof a[n]=="function"&&(r[n]=a[n].bind(a));return r}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{writable as l,derived as x,get as y}from"svelte/store";import{writable as D,readable as K,derived as W,get as F,readonly as w}from"svelte/store";var u=new Map,p=[];function $(o,i){return function(){if(u.has(o))return u.get(o);let t;typeof i=="function"?t=v(o,i):t=R(o,i),u.set(o,t);for(let r of p){let s=r({store:t,storeId:o,options:typeof i=="function"?{id:o}:i});s&&Object.assign(t,s)}return t}}function R(o,i){let c=i.state?i.state():{},t=l({...c}),r={$id:o,subscribe:t.subscribe,get $state(){return y(t)},$patch(s){t.update(f=>typeof s=="function"?(s(f),{...f}):{...f,...s})},$reset(){let s=i.state?i.state():{};t.set({...s})},$subscribe(s){return t.subscribe(s)}};if(i.getters)for(let[s,f]of Object.entries(i.getters))Object.defineProperty(r,s,{get(){return f(y(t))},enumerable:!0});if(i.actions)for(let[s,f]of Object.entries(i.actions))r[s]=(...b)=>f.apply(r,b);return r}function v(o,i){let c=i(),t=l({}),r={},s={},f={};for(let[n,e]of Object.entries(c))typeof e=="function"?f[n]=e:e&&typeof e=="object"&&"subscribe"in e&&("set"in e?r[n]=e:s[n]=e);function b(){let n={};for(let[e,a]of Object.entries(r))n[e]=y(a);for(let[e,a]of Object.entries(s))n[e]=y(a);t.set(n)}let S=[];for(let n of Object.values(r))S.push(n.subscribe(()=>b()));for(let n of Object.values(s))S.push(n.subscribe(()=>b()));let d={$id:o,subscribe:t.subscribe,get $state(){return y(t)},$patch(n){if(typeof n=="function"){let e={};for(let[a,g]of Object.entries(r))e[a]=y(g);n(e);for(let[a,g]of Object.entries(r))a in e&&g.set(e[a])}else for(let[e,a]of Object.entries(n))e in r&&r[e].set(a)},$reset(){console.warn(`[svelte-store] Setup store "${o}" \u4E0D\u652F\u6301 $reset\uFF0C\u8BF7\u624B\u52A8\u91CD\u7F6E\u72B6\u6001`)},$subscribe(n){return t.subscribe(n)}};for(let[n,e]of Object.entries(f))d[n]=e;for(let[n,e]of Object.entries(r))Object.defineProperty(d,n,{get(){return y(e)},set(a){e.set(a)},enumerable:!0});for(let[n,e]of Object.entries(s))Object.defineProperty(d,n,{get(){return y(e)},enumerable:!0});return d}function O(o){p.push(o)}function A(o){return u.get(o)}function G(){u.clear()}function I(o,i){let c=o(),t={};for(let r of i)t[r]=x({subscribe:c.subscribe},s=>s[r]);return t}function P(o,i){let c=o(),t={};for(let r of i)typeof c[r]=="function"&&(t[r]=c[r].bind(c));return t}export{O as addPlugin,G as clearStores,$ as defineStore,W as derived,F as get,A as getRegisteredStore,P as mapActions,I as mapState,K as readable,w as readonly,D as writable};
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@free-walk/svelte-store",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pinia 风格的 Svelte 5 状态管理库,基于 svelte/store 构建",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"svelte",
|
|
8
|
+
"svelte5",
|
|
9
|
+
"store",
|
|
10
|
+
"state-management",
|
|
11
|
+
"pinia"
|
|
12
|
+
],
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"author": "free-walk",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/webcodingnpc/svelte-store.git"
|
|
18
|
+
},
|
|
19
|
+
"svelte": "./src/index.ts",
|
|
20
|
+
"main": "./dist/svelte-store.cjs",
|
|
21
|
+
"module": "./dist/svelte-store.mjs",
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"svelte": "./src/index.ts",
|
|
25
|
+
"import": "./dist/svelte-store.mjs",
|
|
26
|
+
"require": "./dist/svelte-store.cjs",
|
|
27
|
+
"default": "./dist/svelte-store.mjs"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"src",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "node scripts/build.js"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"svelte": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"esbuild": "^0.27.7",
|
|
43
|
+
"svelte": "^5.55.1"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* svelte-store — Pinia 风格的 Svelte 5 状态管理库
|
|
3
|
+
*
|
|
4
|
+
* 核心概念:
|
|
5
|
+
* - defineStore:定义一个 store(类似 Pinia 的 defineStore)
|
|
6
|
+
* - state:响应式数据
|
|
7
|
+
* - getters:派生计算属性
|
|
8
|
+
* - actions:修改 state 的方法
|
|
9
|
+
* - plugins:全局插件系统
|
|
10
|
+
* - $subscribe:监听 state 变化
|
|
11
|
+
* - $patch:批量更新 state
|
|
12
|
+
* - $reset:重置 state 到初始值
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { writable, derived, get, readonly } from 'svelte/store'
|
|
16
|
+
import type { Writable, Readable, Unsubscriber } from 'svelte/store'
|
|
17
|
+
|
|
18
|
+
// ==================== 类型定义 ====================
|
|
19
|
+
|
|
20
|
+
/** Store 的 state 工厂函数 */
|
|
21
|
+
export type StateFactory<S> = () => S
|
|
22
|
+
|
|
23
|
+
/** Store 的 getters 定义 */
|
|
24
|
+
export type GettersDefinition<S, G> = {
|
|
25
|
+
[K in keyof G]: (state: S) => G[K]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Store 的 actions 定义 */
|
|
29
|
+
export type ActionsDefinition<S, A> = {
|
|
30
|
+
[K in keyof A]: A[K] extends (...args: infer P) => infer R
|
|
31
|
+
? (this: StoreInstance<S, any, A>, ...args: P) => R
|
|
32
|
+
: never
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Store Options API 风格定义 */
|
|
36
|
+
export interface StoreOptionsDefinition<
|
|
37
|
+
Id extends string,
|
|
38
|
+
S extends Record<string, any>,
|
|
39
|
+
G extends Record<string, any>,
|
|
40
|
+
A extends Record<string, (...args: any[]) => any>,
|
|
41
|
+
> {
|
|
42
|
+
id?: Id
|
|
43
|
+
state?: StateFactory<S>
|
|
44
|
+
getters?: GettersDefinition<S, G>
|
|
45
|
+
actions?: A
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Setup 风格返回值 */
|
|
49
|
+
export type SetupReturn = Record<string, any>
|
|
50
|
+
|
|
51
|
+
/** Store 实例 */
|
|
52
|
+
export interface StoreInstance<
|
|
53
|
+
S extends Record<string, any>,
|
|
54
|
+
G extends Record<string, any>,
|
|
55
|
+
A extends Record<string, (...args: any[]) => any>,
|
|
56
|
+
> {
|
|
57
|
+
/** Store 唯一标识 */
|
|
58
|
+
$id: string
|
|
59
|
+
/** 订阅 state 变化 */
|
|
60
|
+
$subscribe: (callback: (state: S) => void) => Unsubscriber
|
|
61
|
+
/** 批量更新 state */
|
|
62
|
+
$patch: (partialOrUpdater: Partial<S> | ((state: S) => void)) => void
|
|
63
|
+
/** 重置 state 到初始值 */
|
|
64
|
+
$reset: () => void
|
|
65
|
+
/** 获取当前 state 快照 */
|
|
66
|
+
$state: S
|
|
67
|
+
/** svelte/store 订阅接口 */
|
|
68
|
+
subscribe: (run: (value: S) => void) => Unsubscriber
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 插件上下文 */
|
|
72
|
+
export interface PluginContext<S = any> {
|
|
73
|
+
store: StoreInstance<S, any, any>
|
|
74
|
+
storeId: string
|
|
75
|
+
options: StoreOptionsDefinition<string, S, any, any>
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** 插件类型 */
|
|
79
|
+
export type StorePlugin = (context: PluginContext) => void | Record<string, any>
|
|
80
|
+
|
|
81
|
+
// ==================== 全局状态 ====================
|
|
82
|
+
|
|
83
|
+
/** 已注册的所有 store */
|
|
84
|
+
const storeRegistry = new Map<string, any>()
|
|
85
|
+
|
|
86
|
+
/** 已注册的插件 */
|
|
87
|
+
const plugins: StorePlugin[] = []
|
|
88
|
+
|
|
89
|
+
// ==================== 核心 API ====================
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 定义一个 Store(Options API 风格)
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* const useCounterStore = defineStore('counter', {
|
|
97
|
+
* state: () => ({ count: 0 }),
|
|
98
|
+
* getters: {
|
|
99
|
+
* double: (state) => state.count * 2,
|
|
100
|
+
* },
|
|
101
|
+
* actions: {
|
|
102
|
+
* increment() { this.$patch({ count: this.$state.count + 1 }) },
|
|
103
|
+
* decrement() { this.$patch({ count: this.$state.count - 1 }) },
|
|
104
|
+
* },
|
|
105
|
+
* })
|
|
106
|
+
*
|
|
107
|
+
* // 在组件中使用
|
|
108
|
+
* const counter = useCounterStore()
|
|
109
|
+
* $: console.log($counter) // { count: 0 }
|
|
110
|
+
* counter.increment()
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export function defineStore<
|
|
114
|
+
Id extends string,
|
|
115
|
+
S extends Record<string, any> = {},
|
|
116
|
+
G extends Record<string, any> = {},
|
|
117
|
+
A extends Record<string, (...args: any[]) => any> = {},
|
|
118
|
+
>(
|
|
119
|
+
id: Id,
|
|
120
|
+
options: StoreOptionsDefinition<Id, S, G, A>,
|
|
121
|
+
): () => StoreInstance<S, G, A> & S & { [K in keyof G]: G[K] } & A
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 定义一个 Store(Setup 风格)
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* const useCounterStore = defineStore('counter', () => {
|
|
129
|
+
* let count = writable(0)
|
|
130
|
+
* const double = derived(count, $c => $c * 2)
|
|
131
|
+
* function increment() { count.update(n => n + 1) }
|
|
132
|
+
* return { count, double, increment }
|
|
133
|
+
* })
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export function defineStore<Id extends string>(
|
|
137
|
+
id: Id,
|
|
138
|
+
setup: () => SetupReturn,
|
|
139
|
+
): () => any
|
|
140
|
+
|
|
141
|
+
export function defineStore(
|
|
142
|
+
id: string,
|
|
143
|
+
optionsOrSetup: StoreOptionsDefinition<string, any, any, any> | (() => SetupReturn),
|
|
144
|
+
) {
|
|
145
|
+
return function useStore() {
|
|
146
|
+
// 单例:同一 id 只创建一次
|
|
147
|
+
if (storeRegistry.has(id)) {
|
|
148
|
+
return storeRegistry.get(id)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let store: any
|
|
152
|
+
|
|
153
|
+
if (typeof optionsOrSetup === 'function') {
|
|
154
|
+
// Setup 风格
|
|
155
|
+
store = createSetupStore(id, optionsOrSetup)
|
|
156
|
+
} else {
|
|
157
|
+
// Options API 风格
|
|
158
|
+
store = createOptionsStore(id, optionsOrSetup)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
storeRegistry.set(id, store)
|
|
162
|
+
|
|
163
|
+
// 执行插件
|
|
164
|
+
for (const plugin of plugins) {
|
|
165
|
+
const extensions = plugin({
|
|
166
|
+
store,
|
|
167
|
+
storeId: id,
|
|
168
|
+
options: typeof optionsOrSetup === 'function' ? { id } : optionsOrSetup,
|
|
169
|
+
})
|
|
170
|
+
if (extensions) {
|
|
171
|
+
Object.assign(store, extensions)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return store
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ==================== Options Store 创建 ====================
|
|
180
|
+
|
|
181
|
+
function createOptionsStore(
|
|
182
|
+
id: string,
|
|
183
|
+
options: StoreOptionsDefinition<string, any, any, any>,
|
|
184
|
+
) {
|
|
185
|
+
const initialState = options.state ? options.state() : {}
|
|
186
|
+
const stateStore: Writable<any> = writable({ ...initialState })
|
|
187
|
+
|
|
188
|
+
// 构建 store 实例
|
|
189
|
+
const store: any = {
|
|
190
|
+
$id: id,
|
|
191
|
+
subscribe: stateStore.subscribe,
|
|
192
|
+
|
|
193
|
+
get $state() {
|
|
194
|
+
return get(stateStore)
|
|
195
|
+
},
|
|
196
|
+
|
|
197
|
+
$patch(partialOrUpdater: any) {
|
|
198
|
+
stateStore.update((current: any) => {
|
|
199
|
+
if (typeof partialOrUpdater === 'function') {
|
|
200
|
+
partialOrUpdater(current)
|
|
201
|
+
return { ...current }
|
|
202
|
+
}
|
|
203
|
+
return { ...current, ...partialOrUpdater }
|
|
204
|
+
})
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
$reset() {
|
|
208
|
+
const freshState = options.state ? options.state() : {}
|
|
209
|
+
stateStore.set({ ...freshState })
|
|
210
|
+
},
|
|
211
|
+
|
|
212
|
+
$subscribe(callback: (state: any) => void) {
|
|
213
|
+
return stateStore.subscribe(callback)
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 绑定 getters
|
|
218
|
+
if (options.getters) {
|
|
219
|
+
for (const [key, getter] of Object.entries(options.getters)) {
|
|
220
|
+
Object.defineProperty(store, key, {
|
|
221
|
+
get() {
|
|
222
|
+
return (getter as Function)(get(stateStore))
|
|
223
|
+
},
|
|
224
|
+
enumerable: true,
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 绑定 actions(this 指向 store 实例)
|
|
230
|
+
if (options.actions) {
|
|
231
|
+
for (const [key, action] of Object.entries(options.actions)) {
|
|
232
|
+
store[key] = (...args: any[]) => (action as Function).apply(store, args)
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return store
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ==================== Setup Store 创建 ====================
|
|
240
|
+
|
|
241
|
+
function createSetupStore(id: string, setup: () => SetupReturn) {
|
|
242
|
+
const result = setup()
|
|
243
|
+
const stateStore: Writable<any> = writable({})
|
|
244
|
+
|
|
245
|
+
// 分离 stores、computed 和 actions
|
|
246
|
+
const storeEntries: Record<string, Writable<any>> = {}
|
|
247
|
+
const readableEntries: Record<string, Readable<any>> = {}
|
|
248
|
+
const actionEntries: Record<string, Function> = {}
|
|
249
|
+
|
|
250
|
+
for (const [key, value] of Object.entries(result)) {
|
|
251
|
+
if (typeof value === 'function') {
|
|
252
|
+
actionEntries[key] = value
|
|
253
|
+
} else if (value && typeof value === 'object' && 'subscribe' in value) {
|
|
254
|
+
if ('set' in value) {
|
|
255
|
+
storeEntries[key] = value as Writable<any>
|
|
256
|
+
} else {
|
|
257
|
+
readableEntries[key] = value as Readable<any>
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 同步 state snapshot
|
|
263
|
+
function syncState() {
|
|
264
|
+
const state: any = {}
|
|
265
|
+
for (const [key, s] of Object.entries(storeEntries)) {
|
|
266
|
+
state[key] = get(s)
|
|
267
|
+
}
|
|
268
|
+
for (const [key, s] of Object.entries(readableEntries)) {
|
|
269
|
+
state[key] = get(s)
|
|
270
|
+
}
|
|
271
|
+
stateStore.set(state)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// 订阅所有 writable stores 的变化
|
|
275
|
+
const unsubs: Unsubscriber[] = []
|
|
276
|
+
for (const s of Object.values(storeEntries)) {
|
|
277
|
+
unsubs.push(s.subscribe(() => syncState()))
|
|
278
|
+
}
|
|
279
|
+
for (const s of Object.values(readableEntries)) {
|
|
280
|
+
unsubs.push(s.subscribe(() => syncState()))
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const store: any = {
|
|
284
|
+
$id: id,
|
|
285
|
+
subscribe: stateStore.subscribe,
|
|
286
|
+
|
|
287
|
+
get $state() {
|
|
288
|
+
return get(stateStore)
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
$patch(partialOrUpdater: any) {
|
|
292
|
+
if (typeof partialOrUpdater === 'function') {
|
|
293
|
+
const current: any = {}
|
|
294
|
+
for (const [key, s] of Object.entries(storeEntries)) {
|
|
295
|
+
current[key] = get(s)
|
|
296
|
+
}
|
|
297
|
+
partialOrUpdater(current)
|
|
298
|
+
for (const [key, s] of Object.entries(storeEntries)) {
|
|
299
|
+
if (key in current) s.set(current[key])
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
for (const [key, value] of Object.entries(partialOrUpdater)) {
|
|
303
|
+
if (key in storeEntries) {
|
|
304
|
+
storeEntries[key].set(value)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
$reset() {
|
|
311
|
+
// Setup store 没有初始工厂,不支持 $reset
|
|
312
|
+
console.warn(`[svelte-store] Setup store "${id}" 不支持 $reset,请手动重置状态`)
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
$subscribe(callback: (state: any) => void) {
|
|
316
|
+
return stateStore.subscribe(callback)
|
|
317
|
+
},
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 暴露 actions
|
|
321
|
+
for (const [key, action] of Object.entries(actionEntries)) {
|
|
322
|
+
store[key] = action
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 暴露 writable stores(通过 getter/setter 代理)
|
|
326
|
+
for (const [key, s] of Object.entries(storeEntries)) {
|
|
327
|
+
Object.defineProperty(store, key, {
|
|
328
|
+
get() { return get(s) },
|
|
329
|
+
set(value: any) { s.set(value) },
|
|
330
|
+
enumerable: true,
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 暴露 readable stores(通过 getter)
|
|
335
|
+
for (const [key, s] of Object.entries(readableEntries)) {
|
|
336
|
+
Object.defineProperty(store, key, {
|
|
337
|
+
get() { return get(s) },
|
|
338
|
+
enumerable: true,
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return store
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// ==================== 插件系统 ====================
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* 注册全局插件
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* import { addPlugin } from 'svelte-store'
|
|
353
|
+
*
|
|
354
|
+
* // 持久化插件
|
|
355
|
+
* addPlugin(({ store, storeId }) => {
|
|
356
|
+
* const saved = localStorage.getItem(`store-${storeId}`)
|
|
357
|
+
* if (saved) store.$patch(JSON.parse(saved))
|
|
358
|
+
* store.$subscribe((state) => {
|
|
359
|
+
* localStorage.setItem(`store-${storeId}`, JSON.stringify(state))
|
|
360
|
+
* })
|
|
361
|
+
* })
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
export function addPlugin(plugin: StorePlugin): void {
|
|
365
|
+
plugins.push(plugin)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ==================== 工具函数 ====================
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 获取已注册的 store 实例(需先调用过 useStore)
|
|
372
|
+
*/
|
|
373
|
+
export function getRegisteredStore(id: string): any | undefined {
|
|
374
|
+
return storeRegistry.get(id)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* 清除所有已注册的 store(测试用)
|
|
379
|
+
*/
|
|
380
|
+
export function clearStores(): void {
|
|
381
|
+
storeRegistry.clear()
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 创建 store 映射辅助函数
|
|
386
|
+
* 类似 Pinia 的 mapState
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* const useCounter = defineStore('counter', {
|
|
391
|
+
* state: () => ({ count: 0, name: 'Counter' }),
|
|
392
|
+
* })
|
|
393
|
+
*
|
|
394
|
+
* // mapState 提取部分 state
|
|
395
|
+
* const { count, name } = mapState(useCounter, ['count', 'name'])
|
|
396
|
+
* ```
|
|
397
|
+
*/
|
|
398
|
+
export function mapState<S extends Record<string, any>>(
|
|
399
|
+
useStore: () => any,
|
|
400
|
+
keys: (keyof S)[],
|
|
401
|
+
): Record<keyof S, Readable<any>> {
|
|
402
|
+
const store = useStore()
|
|
403
|
+
const result: any = {}
|
|
404
|
+
for (const key of keys) {
|
|
405
|
+
result[key] = derived({ subscribe: store.subscribe }, ($state: S) => $state[key])
|
|
406
|
+
}
|
|
407
|
+
return result
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* 转发 store 中的 actions
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```ts
|
|
415
|
+
* const { increment, decrement } = mapActions(useCounter, ['increment', 'decrement'])
|
|
416
|
+
* ```
|
|
417
|
+
*/
|
|
418
|
+
export function mapActions(
|
|
419
|
+
useStore: () => any,
|
|
420
|
+
keys: string[],
|
|
421
|
+
): Record<string, (...args: any[]) => any> {
|
|
422
|
+
const store = useStore()
|
|
423
|
+
const result: any = {}
|
|
424
|
+
for (const key of keys) {
|
|
425
|
+
if (typeof store[key] === 'function') {
|
|
426
|
+
result[key] = store[key].bind(store)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return result
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// 重新导出 svelte/store 常用 API
|
|
433
|
+
export { writable, readable, derived, get, readonly } from 'svelte/store'
|
|
434
|
+
export type { Writable, Readable, Unsubscriber } from 'svelte/store'
|