vue-page-store 0.2.3 → 0.3.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/README.md +202 -196
- package/dist/index.cjs.js +110 -25
- package/dist/index.d.ts +27 -4
- package/dist/index.esm.js +110 -25
- package/dist/index.umd.js +110 -25
- package/dist/index.umd.min.js +11 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
1
|
# vue-page-store
|
|
2
2
|
|
|
3
|
-
> Vue 2.6
|
|
3
|
+
> Vue 2.6 页面级作用域运行时容器 —— 状态、生命周期、副作用、通信,一个作用域全收。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 它是什么
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`vue-page-store` 是面向 **复杂 Vue 2 业务页面** 的页面级运行时容器。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|------|------|
|
|
11
|
-
| **Vuex** | 全局 store,页面销毁后状态残留,命名冲突,不适合页面级生命周期 |
|
|
12
|
-
| **全局 EventBus** | 命名冲突、手动 $off 容易遗漏、事件扩散到全局 |
|
|
13
|
-
| **provide / inject** | 只传数据,不管通信和副作用 |
|
|
14
|
-
| **组件 data** | 跨组件共享困难,深层传递 props 地狱 |
|
|
9
|
+
一个 `definePageStore` 定义一个 **Page Scope** —— 它统一管理这个页面作用域内的:
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
- **state** — 响应式页面状态
|
|
12
|
+
- **getters** — 派生计算
|
|
13
|
+
- **actions** — 业务逻辑
|
|
14
|
+
- **watch** — 声明式副作用
|
|
15
|
+
- **lifecycle** — 页面生命周期(mount / unmount / activate / deactivate)
|
|
16
|
+
- **event bus** — 页面内作用域通信
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
页面销毁时 `$destroy` 一键回收,不污染全局。
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
## 它不是什么
|
|
21
|
+
|
|
22
|
+
- **不是 Vuex / Pinia 替代品** — 全局状态(用户信息、权限、路由)请继续用 Vuex
|
|
23
|
+
- **不是全局状态管理方案** — 它的作用域是"页面",不是"应用"
|
|
24
|
+
|
|
25
|
+
| | Vuex | vue-page-store |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| 作用域 | 全局 | 页面 |
|
|
28
|
+
| 生命周期 | 跟随应用 | 跟随页面组件 |
|
|
29
|
+
| 适合 | 用户信息、权限、路由状态 | 仪表盘、漏斗详情、大型配置页 |
|
|
30
|
+
| 销毁 | 通常不销毁 | 页面离开即回收 |
|
|
26
31
|
|
|
27
32
|
## 安装
|
|
28
33
|
|
|
@@ -30,263 +35,264 @@
|
|
|
30
35
|
npm install vue-page-store
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
|
|
38
|
+
要求 `vue@^2.6.0` 作为 peer dependency。
|
|
34
39
|
|
|
35
40
|
## 快速上手
|
|
36
41
|
|
|
37
|
-
### 1. 定义
|
|
42
|
+
### 1. 定义 store
|
|
38
43
|
|
|
39
|
-
```
|
|
40
|
-
|
|
44
|
+
```js
|
|
45
|
+
// stores/funnel.js
|
|
46
|
+
import { definePageStore } from 'vue-page-store'
|
|
41
47
|
|
|
42
48
|
export const useFunnelStore = definePageStore('funnelDetail', {
|
|
43
49
|
state: () => ({
|
|
44
|
-
filters: {
|
|
45
|
-
|
|
46
|
-
|
|
50
|
+
filters: {},
|
|
51
|
+
list: [],
|
|
52
|
+
loading: false
|
|
47
53
|
}),
|
|
48
54
|
|
|
49
55
|
getters: {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
},
|
|
56
|
+
isEmpty() { return this.list.length === 0 },
|
|
57
|
+
isReady() { return !this.loading }
|
|
53
58
|
},
|
|
54
59
|
|
|
55
60
|
actions: {
|
|
56
61
|
async fetchData() {
|
|
57
|
-
this.loading = true
|
|
62
|
+
this.loading = true
|
|
58
63
|
try {
|
|
59
|
-
this.
|
|
64
|
+
this.list = await api.getFunnelData(this.filters)
|
|
60
65
|
} finally {
|
|
61
|
-
this.loading = false
|
|
66
|
+
this.loading = false
|
|
62
67
|
}
|
|
63
|
-
}
|
|
68
|
+
}
|
|
64
69
|
},
|
|
65
70
|
|
|
66
71
|
watch: {
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
// 函数简写 — 默认浅监听
|
|
73
|
+
'filters.dateRange'(val) {
|
|
74
|
+
this.fetchData()
|
|
69
75
|
},
|
|
76
|
+
// 对象写法 — 显式 deep
|
|
77
|
+
'filters': {
|
|
78
|
+
handler(val) { this.fetchData() },
|
|
79
|
+
deep: true
|
|
80
|
+
}
|
|
70
81
|
},
|
|
71
82
|
|
|
72
|
-
// 0.2.0 新增:页面生命周期
|
|
73
83
|
lifecycle: {
|
|
74
|
-
mount()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
console.log('页面销毁');
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
});
|
|
84
|
+
mount() { this.fetchData() },
|
|
85
|
+
unmount() { console.log('funnel page destroyed') },
|
|
86
|
+
activate() { this.fetchData() },
|
|
87
|
+
}
|
|
88
|
+
})
|
|
83
89
|
```
|
|
84
90
|
|
|
85
|
-
### 2.
|
|
91
|
+
### 2. 页面组件中使用
|
|
92
|
+
|
|
93
|
+
```js
|
|
94
|
+
// FunnelPage.vue
|
|
95
|
+
import { useFunnelStore } from './stores/funnel'
|
|
86
96
|
|
|
87
|
-
```javascript
|
|
88
97
|
export default {
|
|
89
98
|
created() {
|
|
90
|
-
//
|
|
91
|
-
this.
|
|
92
|
-
}
|
|
99
|
+
// 传入 this → 自动绑定生命周期 + 自动 provide + 页面销毁时自动回收
|
|
100
|
+
this.pageStore = useFunnelStore(this)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
93
104
|
|
|
94
|
-
|
|
95
|
-
isReady() {
|
|
96
|
-
return this.store.isReady;
|
|
97
|
-
},
|
|
98
|
-
},
|
|
105
|
+
### 3. 子组件中使用
|
|
99
106
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
+
```js
|
|
108
|
+
// FilterPanel.vue — 不需要 import 任何 store 文件
|
|
109
|
+
export default {
|
|
110
|
+
inject: ['pageStore'],
|
|
111
|
+
mounted() {
|
|
112
|
+
this.pageStore.fetchData() // 直接用
|
|
113
|
+
}
|
|
114
|
+
}
|
|
107
115
|
```
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
### 3. 页面内通信
|
|
117
|
+
所有页面统一用 `this.pageStore`,所有子组件统一 `inject: ['pageStore']`。不需要知道父页面用的哪个 store 定义,零耦合。
|
|
112
118
|
|
|
113
|
-
|
|
114
|
-
// 组件 A —— 发射事件
|
|
115
|
-
this.store.$emit('filter:change', newFilters);
|
|
119
|
+
## API
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
const off = this.store.$on('filter:change', (filters) => {
|
|
119
|
-
this.applyFilters(filters);
|
|
120
|
-
});
|
|
121
|
+
### `definePageStore(id, options)`
|
|
121
122
|
|
|
122
|
-
|
|
123
|
+
定义一个页面级 store,返回 `useStore(componentVm?)` 函数。
|
|
124
|
+
|
|
125
|
+
**options:**
|
|
126
|
+
|
|
127
|
+
| 字段 | 类型 | 说明 |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `state` | `() => Object` | **必填**,状态工厂函数 |
|
|
130
|
+
| `getters` | `{ [key]: function }` | 派生计算,`this` 指向 store |
|
|
131
|
+
| `actions` | `{ [key]: function }` | 业务方法,`this` 指向 store |
|
|
132
|
+
| `watch` | `{ [path]: handler \| options }` | 声明式 watcher,支持 dot-path |
|
|
133
|
+
| `lifecycle` | `{ mount, unmount, activate, deactivate }` | 页面生命周期钩子 |
|
|
134
|
+
|
|
135
|
+
### Store 实例属性与方法
|
|
136
|
+
|
|
137
|
+
| 属性/方法 | 说明 |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `store.xxx` | 直接访问 state 字段 |
|
|
140
|
+
| `store.$state` | 原始响应式 state 对象 |
|
|
141
|
+
| `store.$status` | `{ mounted, active }` 响应式状态 |
|
|
142
|
+
| `store.$disposed` | store 是否已销毁 |
|
|
143
|
+
| `store.$id` | store 唯一标识 |
|
|
144
|
+
| `store.$patch(partial \| fn)` | 批量更新 state(浅合并) |
|
|
145
|
+
| `store.$reset()` | 重置到 `state()` 初始值,清除动态字段 |
|
|
146
|
+
| `store.$emit(event, payload)` | 发射事件(当前 store 作用域) |
|
|
147
|
+
| `store.$on(event, handler)` | 订阅事件,返回取消函数 |
|
|
148
|
+
| `store.$off(event, handler?)` | 取消订阅 |
|
|
149
|
+
| `store.bindTo(componentVm)` | 绑定组件生命周期 + 自动 provide,子组件 `inject: ['pageStore']` |
|
|
150
|
+
| `store.$destroy()` | 手动销毁 |
|
|
151
|
+
|
|
152
|
+
### watch 配置
|
|
153
|
+
|
|
154
|
+
```js
|
|
155
|
+
watch: {
|
|
156
|
+
// 函数写法 — 默认 shallow watch
|
|
157
|
+
'fieldName'(newVal, oldVal) { ... },
|
|
158
|
+
|
|
159
|
+
// 对象写法 — 可配置 deep / immediate
|
|
160
|
+
'filters': {
|
|
161
|
+
handler(newVal, oldVal) { ... },
|
|
162
|
+
deep: true, // 默认 false
|
|
163
|
+
immediate: true // 默认 false
|
|
164
|
+
}
|
|
165
|
+
}
|
|
123
166
|
```
|
|
124
167
|
|
|
125
|
-
##
|
|
168
|
+
## State Shape 规则
|
|
126
169
|
|
|
127
|
-
|
|
170
|
+
`state()` 返回值定义了推荐的状态边界:
|
|
128
171
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
| `activated` | 调用 `lifecycle.activate` | `active: true` |
|
|
133
|
-
| `deactivated` | 调用 `lifecycle.deactivate` | `active: false` |
|
|
134
|
-
| `beforeDestroy` | 自动调 `$destroy()`,触发 `lifecycle.unmount` | `mounted: false, active: false` |
|
|
172
|
+
- **推荐**:在 `state()` 中声明完整字段,即使初始值为 `null` 或空数组
|
|
173
|
+
- **允许**:通过 `$patch` 动态新增字段(会写入 `$state`,但不会自动成为 `store.xxx` 顶层代理)
|
|
174
|
+
- **注意**:`$reset()` 会清除所有不在 `state()` 中的动态字段
|
|
135
175
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
176
|
+
```js
|
|
177
|
+
state: () => ({
|
|
178
|
+
filters: {},
|
|
179
|
+
list: [],
|
|
180
|
+
detail: null // 推荐:先声明为 null,而不是运行时再 $patch 进去
|
|
181
|
+
})
|
|
140
182
|
```
|
|
141
183
|
|
|
142
|
-
|
|
184
|
+
## 实例模型:Singleton
|
|
143
185
|
|
|
144
|
-
|
|
145
|
-
mounted → lifecycle.mount
|
|
146
|
-
→ deactivated → lifecycle.deactivate
|
|
147
|
-
→ activated → lifecycle.activate
|
|
148
|
-
→ deactivated → lifecycle.deactivate
|
|
149
|
-
→ activated → lifecycle.activate
|
|
150
|
-
→ ... 反复切换 ...
|
|
151
|
-
→ beforeDestroy → lifecycle.unmount
|
|
152
|
-
```
|
|
186
|
+
当前版本采用 **id → singleton** 模型:
|
|
153
187
|
|
|
154
|
-
|
|
188
|
+
- 同一个 `id` 在整个应用中对应唯一一个 store 实例
|
|
189
|
+
- `useStore()` 多次调用返回同一实例
|
|
190
|
+
- `$destroy()` 后从 registry 移除,下次 `useStore()` 会创建新实例
|
|
155
191
|
|
|
156
|
-
|
|
157
|
-
export const useListStore = definePageStore('listPage', {
|
|
158
|
-
state: () => ({
|
|
159
|
-
list: [],
|
|
160
|
-
needRefresh: false,
|
|
161
|
-
}),
|
|
192
|
+
**适用场景:**
|
|
162
193
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
this.list = await api.getList();
|
|
166
|
-
this.needRefresh = false;
|
|
167
|
-
},
|
|
168
|
-
},
|
|
194
|
+
- 单页面单作用域(最常见)
|
|
195
|
+
- keep-alive 下的页面缓存
|
|
169
196
|
|
|
170
|
-
|
|
171
|
-
mount() {
|
|
172
|
-
this.fetchList();
|
|
173
|
-
},
|
|
174
|
-
activate() {
|
|
175
|
-
// 从缓存恢复时,按需刷新
|
|
176
|
-
if (this.needRefresh) this.fetchList();
|
|
177
|
-
},
|
|
178
|
-
},
|
|
179
|
-
});
|
|
180
|
-
```
|
|
197
|
+
**不适用场景:**
|
|
181
198
|
|
|
182
|
-
|
|
199
|
+
- 同一路由多开独立副本
|
|
200
|
+
- 需要按参数区分的多实例页面
|
|
183
201
|
|
|
184
|
-
|
|
202
|
+
> 多实例支持(keyed instance / scopeKey)将在未来版本演进。
|
|
185
203
|
|
|
186
|
-
|
|
187
|
-
<template>
|
|
188
|
-
<div v-if="store.$status.active">
|
|
189
|
-
<!-- 页面激活时才渲染 -->
|
|
190
|
-
</div>
|
|
191
|
-
</template>
|
|
192
|
-
```
|
|
204
|
+
## 适用场景
|
|
193
205
|
|
|
194
|
-
|
|
206
|
+
- 仪表盘页面 — 多模块共享筛选条件、加载状态
|
|
207
|
+
- 漏斗/留存等分析详情页 — 复杂交互 + 异步数据 + 生命周期管理
|
|
208
|
+
- 大型配置页 — 多 tab/多步骤表单的状态统一管理
|
|
209
|
+
- keep-alive 业务页 — 需要 activate/deactivate 感知的页面
|
|
210
|
+
- 微前端子应用 — 页面作用域隔离,不污染宿主全局状态
|
|
195
211
|
|
|
196
|
-
|
|
212
|
+
## 不适用场景
|
|
213
|
+
|
|
214
|
+
- 全局用户信息、权限、路由等 → 用 Vuex
|
|
215
|
+
- 简单页面的小 data 管理 → 用组件 data 就够了
|
|
216
|
+
- 需要同 id 多实例并存 → 当前版本不支持
|
|
217
|
+
|
|
218
|
+
## 异步安全
|
|
219
|
+
|
|
220
|
+
页面销毁后,异步请求可能仍在 pending。**不需要手动检查** —— store 在销毁后会自动忽略所有写操作:
|
|
197
221
|
|
|
198
|
-
|
|
222
|
+
```js
|
|
223
|
+
actions: {
|
|
224
|
+
async fetchData() {
|
|
225
|
+
this.loading = true
|
|
226
|
+
const data = await api.getData()
|
|
227
|
+
// 即使页面已销毁,下面的赋值也会被自动静默,不会报错
|
|
228
|
+
this.list = data
|
|
229
|
+
this.loading = false
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
199
233
|
|
|
200
|
-
|
|
201
|
-
|------|------|------|
|
|
202
|
-
| `id` | `string` | 唯一标识 |
|
|
203
|
-
| `options.state` | `() => Object` | 返回初始 state 的工厂函数 |
|
|
204
|
-
| `options.getters` | `Object` | 计算属性,`this` 指向 store |
|
|
205
|
-
| `options.actions` | `Object` | 方法,`this` 指向 store |
|
|
206
|
-
| `options.watch` | `Object` | 声明式侦听,支持点路径 `'a.b.c'` |
|
|
207
|
-
| `options.lifecycle` | `Object` | 页面生命周期钩子 |
|
|
234
|
+
底层原理:`$destroy()` 后,state 的 setter 和 `$patch` 都会检查 `$disposed`,写入直接跳过。开发环境下会打印 warning 帮助调试。
|
|
208
235
|
|
|
209
|
-
|
|
236
|
+
## 调试
|
|
210
237
|
|
|
211
|
-
|
|
238
|
+
`storeRegistry` 是导出的 Map,可以在控制台直接查看:
|
|
212
239
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
| `useStore()` | 获取/创建 store,手动管理生命周期(兼容 0.1.0) |
|
|
216
|
-
| `useStore(this)` | 获取/创建 store,自动绑定生命周期 + 自动销毁 |
|
|
240
|
+
```js
|
|
241
|
+
import { storeRegistry } from 'vue-page-store'
|
|
217
242
|
|
|
218
|
-
|
|
243
|
+
// 查看所有活跃 store
|
|
244
|
+
storeRegistry.forEach((store, id) => {
|
|
245
|
+
console.log(id, store.$status, store.$disposed)
|
|
246
|
+
})
|
|
247
|
+
```
|
|
219
248
|
|
|
220
|
-
|
|
221
|
-
|------|---------|------------|
|
|
222
|
-
| `mount` | 组件 `mounted` | store |
|
|
223
|
-
| `unmount` | 组件 `beforeDestroy`(`$destroy` 时触发) | store |
|
|
224
|
-
| `activate` | 组件 `activated`(keep-alive 恢复) | store |
|
|
225
|
-
| `deactivate` | 组件 `deactivated`(keep-alive 缓存) | store |
|
|
249
|
+
## 从 v0.2.x 升级
|
|
226
250
|
|
|
227
|
-
###
|
|
251
|
+
### Breaking Changes
|
|
228
252
|
|
|
229
|
-
|
|
230
|
-
|------|------|
|
|
231
|
-
| `$patch(partial)` | 浅合并更新 state,接受对象或 `(state) => Object` 函数 |
|
|
232
|
-
| `$reset()` | 重置 state 到初始值 |
|
|
233
|
-
| `$emit(event, payload?)` | 发射事件(仅当前 store 作用域) |
|
|
234
|
-
| `$on(event, handler)` | 订阅事件,返回取消函数 |
|
|
235
|
-
| `$off(event, handler?)` | 取消事件订阅(不传 handler 则取消该事件全部监听) |
|
|
236
|
-
| `$destroy()` | 销毁 store,回收所有资源 |
|
|
237
|
-
| `bindTo(vm)` | 手动绑定到组件实例(通常不需要,`useStore(this)` 内部调用) |
|
|
253
|
+
**1. `$reset()` 语义变严格**
|
|
238
254
|
|
|
239
|
-
|
|
255
|
+
v0.2.x:只恢复已有字段的值,动态新增字段会残留。
|
|
240
256
|
|
|
241
|
-
|
|
242
|
-
|------|------|
|
|
243
|
-
| `$id` | store 唯一标识 |
|
|
244
|
-
| `$state` | 原始响应式 state 对象 |
|
|
245
|
-
| `$status` | 响应式对象 `{ mounted: boolean, active: boolean }` |
|
|
257
|
+
v0.3.0:完全恢复到 `state()` 的 shape,动态字段会被移除。
|
|
246
258
|
|
|
247
|
-
|
|
259
|
+
**2. `watch` 默认不再 deep**
|
|
248
260
|
|
|
249
|
-
|
|
261
|
+
v0.2.x:所有 watcher 默认 `deep: true`。
|
|
250
262
|
|
|
251
|
-
|
|
263
|
+
v0.3.0:默认 `deep: false`。如果你的 watcher 依赖深层变化检测,需要显式加 `deep: true`。
|
|
252
264
|
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
beforeDestroy() {
|
|
258
|
-
this.store.$destroy();
|
|
265
|
+
```js
|
|
266
|
+
// v0.2.x — 这个能监听到 filters 内部变化
|
|
267
|
+
watch: {
|
|
268
|
+
'filters'(val) { this.fetchData() }
|
|
259
269
|
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
简化成:
|
|
263
270
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
271
|
+
// v0.3.0 — 需要显式声明 deep
|
|
272
|
+
watch: {
|
|
273
|
+
'filters': {
|
|
274
|
+
handler(val) { this.fetchData() },
|
|
275
|
+
deep: true
|
|
276
|
+
}
|
|
267
277
|
}
|
|
268
278
|
```
|
|
269
279
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
## 与 Pinia / Vuex 的关系
|
|
280
|
+
**3. `_disposed` → `$disposed`**
|
|
273
281
|
|
|
274
|
-
|
|
282
|
+
`_disposed` 改为公开属性 `$disposed`,语义不变。如果你之前用了 `store._disposed`,替换为 `store.$disposed`。
|
|
275
283
|
|
|
276
|
-
|
|
277
|
-
|---|---|---|---|
|
|
278
|
-
| 作用域 | 全局 | 全局 | 页面级 |
|
|
279
|
-
| 生命周期 | 应用级 | 应用级 | 页面级(自动绑定) |
|
|
280
|
-
| 事件通信 | 无 | 无 | 内置 $emit/$on |
|
|
281
|
-
| Vue 2.6 支持 | ✅ | ⚠️ 需 @vue/composition-api | ✅ 原生支持 |
|
|
282
|
-
| 适用场景 | 用户信息、权限、全局配置 | 同 Vuex | 复杂页面内部状态 |
|
|
284
|
+
### New Features
|
|
283
285
|
|
|
284
|
-
|
|
286
|
+
- `bindTo()` 自动 provide — 子组件 `inject: ['pageStore']` 即可获取,不需要 import store 文件
|
|
287
|
+
- `bindTo()` 重复绑定防护 — 同一个组件实例多次调用不会重复注册生命周期
|
|
288
|
+
- 开发环境 warning — watch 缺少 handler、definePageStore 参数错误等场景会有提示
|
|
285
289
|
|
|
286
|
-
##
|
|
290
|
+
## Roadmap
|
|
287
291
|
|
|
288
|
-
|
|
292
|
+
- **Plugin system** — 可扩展能力(logger、persist、loading tracker)
|
|
293
|
+
- **Keyed instance** — `useStore(vm, scopeKey)` 支持同定义多实例
|
|
294
|
+
- **Page cache strategy** — TTL、revalidate、stale-while-activate
|
|
289
295
|
|
|
290
296
|
## License
|
|
291
297
|
|
|
292
|
-
|
|
298
|
+
MIT © weijianjun
|