vue-page-store 0.1.0 → 0.2.1
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 +136 -24
- package/dist/index.cjs.js +118 -77
- package/dist/index.esm.js +119 -77
- package/dist/index.umd.js +118 -77
- package/dist/index.umd.min.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
| **provide / inject** | 只传数据,不管通信和副作用 |
|
|
14
14
|
| **组件 data** | 跨组件共享困难,深层传递 props 地狱 |
|
|
15
15
|
|
|
16
|
-
`vue-page-store` 解决的就是这个中间地带:**页面内部需要共享状态 + 通信 +
|
|
16
|
+
`vue-page-store` 解决的就是这个中间地带:**页面内部需要共享状态 + 通信 + 生命周期管理,但不应该污染全局。**
|
|
17
17
|
|
|
18
18
|
## 核心特性
|
|
19
19
|
|
|
20
20
|
- **页面级作用域隔离** — 每个 store 独立一份 state 和事件,互不干扰
|
|
21
|
-
-
|
|
22
|
-
-
|
|
21
|
+
- **`useStore(this)` 一行绑定** — 自动挂载生命周期,自动销毁,零手动清理
|
|
22
|
+
- **页面生命周期** — `mount` / `unmount` / `activate` / `deactivate`,keep-alive 原生支持
|
|
23
|
+
- **API 对齐 Pinia** — `state` / `getters` / `actions` / `$patch` / `$reset`,零学习成本
|
|
23
24
|
- **内置作用域事件** — `$emit` / `$on` / `$off` 限定在当前 store 内,替代全局 EventBus
|
|
24
25
|
- **声明式 watch** — 页面级副作用自动绑定生命周期
|
|
25
|
-
- **TypeScript 支持** — 开箱即用的类型定义
|
|
26
26
|
|
|
27
27
|
## 安装
|
|
28
28
|
|
|
@@ -37,7 +37,6 @@ npm install vue-page-store
|
|
|
37
37
|
### 1. 定义 Store
|
|
38
38
|
|
|
39
39
|
```javascript
|
|
40
|
-
// store.js
|
|
41
40
|
import { definePageStore } from 'vue-page-store';
|
|
42
41
|
|
|
43
42
|
export const useFunnelStore = definePageStore('funnelDetail', {
|
|
@@ -66,26 +65,38 @@ export const useFunnelStore = definePageStore('funnelDetail', {
|
|
|
66
65
|
|
|
67
66
|
watch: {
|
|
68
67
|
'filters.platform'(newVal) {
|
|
69
|
-
// platform 变了自动重新拉数据
|
|
70
68
|
this.fetchData();
|
|
71
69
|
},
|
|
72
70
|
},
|
|
71
|
+
|
|
72
|
+
// 0.2.0 新增:页面生命周期
|
|
73
|
+
lifecycle: {
|
|
74
|
+
mount() {
|
|
75
|
+
// 页面挂载时自动拉数据
|
|
76
|
+
this.fetchData();
|
|
77
|
+
},
|
|
78
|
+
unmount() {
|
|
79
|
+
console.log('页面销毁');
|
|
80
|
+
},
|
|
81
|
+
},
|
|
73
82
|
});
|
|
74
83
|
```
|
|
75
84
|
|
|
76
85
|
### 2. 组件中使用
|
|
77
86
|
|
|
78
87
|
```javascript
|
|
79
|
-
// 任意子组件
|
|
80
88
|
export default {
|
|
89
|
+
created() {
|
|
90
|
+
// 传 this → 自动绑定生命周期 + 自动销毁,不需要 beforeDestroy
|
|
91
|
+
this.store = useFunnelStore(this);
|
|
92
|
+
},
|
|
93
|
+
|
|
81
94
|
computed: {
|
|
82
|
-
store() {
|
|
83
|
-
return useFunnelStore();
|
|
84
|
-
},
|
|
85
95
|
isReady() {
|
|
86
96
|
return this.store.isReady;
|
|
87
97
|
},
|
|
88
98
|
},
|
|
99
|
+
|
|
89
100
|
methods: {
|
|
90
101
|
handleSearch() {
|
|
91
102
|
this.store.$patch({ filters: this.localFilters });
|
|
@@ -95,6 +106,8 @@ export default {
|
|
|
95
106
|
};
|
|
96
107
|
```
|
|
97
108
|
|
|
109
|
+
> **不传 `this`** 也完全可以,行为和 0.1.0 一样,需要自己在 `beforeDestroy` 里调 `$destroy()`。
|
|
110
|
+
|
|
98
111
|
### 3. 页面内通信
|
|
99
112
|
|
|
100
113
|
```javascript
|
|
@@ -109,15 +122,73 @@ const off = this.store.$on('filter:change', (filters) => {
|
|
|
109
122
|
// 无需手动清理 —— $destroy 时自动回收
|
|
110
123
|
```
|
|
111
124
|
|
|
112
|
-
|
|
125
|
+
## 页面生命周期(0.2.0)
|
|
126
|
+
|
|
127
|
+
通过 `useStore(this)` 传入组件实例,store 会自动绑定到组件的生命周期:
|
|
128
|
+
|
|
129
|
+
| 组件钩子 | store 行为 | `$status` 变化 |
|
|
130
|
+
|----------|-----------|---------------|
|
|
131
|
+
| `mounted` | 调用 `lifecycle.mount` | `mounted: true, active: true` |
|
|
132
|
+
| `activated` | 调用 `lifecycle.activate` | `active: true` |
|
|
133
|
+
| `deactivated` | 调用 `lifecycle.deactivate` | `active: false` |
|
|
134
|
+
| `beforeDestroy` | 自动调 `$destroy()`,触发 `lifecycle.unmount` | `mounted: false, active: false` |
|
|
135
|
+
|
|
136
|
+
### 无 keep-alive
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
mounted → lifecycle.mount → ... → beforeDestroy → lifecycle.unmount
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 有 keep-alive
|
|
143
|
+
|
|
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
|
+
```
|
|
153
|
+
|
|
154
|
+
### keep-alive 示例
|
|
113
155
|
|
|
114
156
|
```javascript
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
157
|
+
export const useListStore = definePageStore('listPage', {
|
|
158
|
+
state: () => ({
|
|
159
|
+
list: [],
|
|
160
|
+
needRefresh: false,
|
|
161
|
+
}),
|
|
162
|
+
|
|
163
|
+
actions: {
|
|
164
|
+
async fetchList() {
|
|
165
|
+
this.list = await api.getList();
|
|
166
|
+
this.needRefresh = false;
|
|
167
|
+
},
|
|
119
168
|
},
|
|
120
|
-
|
|
169
|
+
|
|
170
|
+
lifecycle: {
|
|
171
|
+
mount() {
|
|
172
|
+
this.fetchList();
|
|
173
|
+
},
|
|
174
|
+
activate() {
|
|
175
|
+
// 从缓存恢复时,按需刷新
|
|
176
|
+
if (this.needRefresh) this.fetchList();
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `$status` 响应式状态
|
|
183
|
+
|
|
184
|
+
`$status` 是响应式对象,可以直接在模板中使用:
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<template>
|
|
188
|
+
<div v-if="store.$status.active">
|
|
189
|
+
<!-- 页面激活时才渲染 -->
|
|
190
|
+
</div>
|
|
191
|
+
</template>
|
|
121
192
|
```
|
|
122
193
|
|
|
123
194
|
## API
|
|
@@ -133,30 +204,71 @@ export default {
|
|
|
133
204
|
| `options.getters` | `Object` | 计算属性,`this` 指向 store |
|
|
134
205
|
| `options.actions` | `Object` | 方法,`this` 指向 store |
|
|
135
206
|
| `options.watch` | `Object` | 声明式侦听,支持点路径 `'a.b.c'` |
|
|
207
|
+
| `options.lifecycle` | `Object` | 页面生命周期钩子 |
|
|
208
|
+
|
|
209
|
+
### `useStore(vm?)`
|
|
210
|
+
|
|
211
|
+
调用 `definePageStore` 返回的函数。
|
|
212
|
+
|
|
213
|
+
| 用法 | 行为 |
|
|
214
|
+
|------|------|
|
|
215
|
+
| `useStore()` | 获取/创建 store,手动管理生命周期(兼容 0.1.0) |
|
|
216
|
+
| `useStore(this)` | 获取/创建 store,自动绑定生命周期 + 自动销毁 |
|
|
217
|
+
|
|
218
|
+
### `lifecycle` 钩子
|
|
219
|
+
|
|
220
|
+
| 钩子 | 触发时机 | `this` 指向 |
|
|
221
|
+
|------|---------|------------|
|
|
222
|
+
| `mount` | 组件 `mounted` | store |
|
|
223
|
+
| `unmount` | 组件 `beforeDestroy`(`$destroy` 时触发) | store |
|
|
224
|
+
| `activate` | 组件 `activated`(keep-alive 恢复) | store |
|
|
225
|
+
| `deactivate` | 组件 `deactivated`(keep-alive 缓存) | store |
|
|
136
226
|
|
|
137
227
|
### Store 实例方法
|
|
138
228
|
|
|
139
229
|
| 方法 | 说明 |
|
|
140
230
|
|------|------|
|
|
141
231
|
| `$patch(partial)` | 浅合并更新 state,接受对象或 `(state) => Object` 函数 |
|
|
142
|
-
| `$subscribe(cb)` | 订阅 state 变化,返回取消函数 |
|
|
143
232
|
| `$reset()` | 重置 state 到初始值 |
|
|
144
233
|
| `$emit(event, payload?)` | 发射事件(仅当前 store 作用域) |
|
|
145
234
|
| `$on(event, handler)` | 订阅事件,返回取消函数 |
|
|
146
235
|
| `$off(event, handler?)` | 取消事件订阅(不传 handler 则取消该事件全部监听) |
|
|
147
236
|
| `$destroy()` | 销毁 store,回收所有资源 |
|
|
237
|
+
| `bindTo(vm)` | 手动绑定到组件实例(通常不需要,`useStore(this)` 内部调用) |
|
|
238
|
+
|
|
239
|
+
### Store 实例属性
|
|
240
|
+
|
|
241
|
+
| 属性 | 说明 |
|
|
242
|
+
|------|------|
|
|
243
|
+
| `$id` | store 唯一标识 |
|
|
244
|
+
| `$state` | 原始响应式 state 对象 |
|
|
245
|
+
| `$status` | 响应式对象 `{ mounted: boolean, active: boolean }` |
|
|
246
|
+
|
|
247
|
+
## 从 0.1.0 升级
|
|
148
248
|
|
|
149
|
-
|
|
249
|
+
**零破坏性变更**。0.1.0 的代码不改一行也能跑。
|
|
150
250
|
|
|
151
|
-
|
|
251
|
+
区别只在于你现在可以把:
|
|
152
252
|
|
|
153
253
|
```javascript
|
|
154
|
-
|
|
254
|
+
created() {
|
|
255
|
+
this.store = useSomeStore();
|
|
256
|
+
},
|
|
257
|
+
beforeDestroy() {
|
|
258
|
+
this.store.$destroy();
|
|
259
|
+
}
|
|
260
|
+
```
|
|
155
261
|
|
|
156
|
-
|
|
157
|
-
|
|
262
|
+
简化成:
|
|
263
|
+
|
|
264
|
+
```javascript
|
|
265
|
+
created() {
|
|
266
|
+
this.store = useSomeStore(this);
|
|
267
|
+
}
|
|
158
268
|
```
|
|
159
269
|
|
|
270
|
+
> `storeToRefs` 和 `$subscribe` 在 0.2.0 中移除——实际项目中无使用场景,精简 API。
|
|
271
|
+
|
|
160
272
|
## 与 Pinia / Vuex 的关系
|
|
161
273
|
|
|
162
274
|
这不是 Pinia 或 Vuex 的替代品,而是补充:
|
|
@@ -164,7 +276,7 @@ const { filters, loading } = storeToRefs(store);
|
|
|
164
276
|
| | Vuex | Pinia | vue-page-store |
|
|
165
277
|
|---|---|---|---|
|
|
166
278
|
| 作用域 | 全局 | 全局 | 页面级 |
|
|
167
|
-
| 生命周期 | 应用级 | 应用级 |
|
|
279
|
+
| 生命周期 | 应用级 | 应用级 | 页面级(自动绑定) |
|
|
168
280
|
| 事件通信 | 无 | 无 | 内置 $emit/$on |
|
|
169
281
|
| Vue 2.6 支持 | ✅ | ⚠️ 需 @vue/composition-api | ✅ 原生支持 |
|
|
170
282
|
| 适用场景 | 用户信息、权限、全局配置 | 同 Vuex | 复杂页面内部状态 |
|
|
@@ -177,4 +289,4 @@ const { filters, loading } = storeToRefs(store);
|
|
|
177
289
|
|
|
178
290
|
## License
|
|
179
291
|
|
|
180
|
-
[MIT](./LICENSE)
|
|
292
|
+
[MIT](./LICENSE)
|
package/dist/index.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* vue-page-store v0.1
|
|
2
|
+
* vue-page-store v0.2.1
|
|
3
3
|
* (c) 2026 weijianjun
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* vue-page-store
|
|
11
|
+
* vue-page-store 0.2.0 — Vue 2.6 页面级 Store
|
|
12
12
|
*
|
|
13
13
|
* 状态、通信、生命周期,一个作用域全收。
|
|
14
14
|
*
|
|
@@ -22,16 +22,17 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
// Store 注册表(导出供调试 / devtools 使用)
|
|
25
|
-
|
|
25
|
+
var storeRegistry = new Map();
|
|
26
26
|
|
|
27
27
|
function createStoreInstance(Vue, id, options) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
var initialState = options.state();
|
|
29
|
+
var getters = options.getters || {};
|
|
30
|
+
var actions = options.actions || {};
|
|
31
|
+
var lifecycles = options.lifecycle || {};
|
|
31
32
|
|
|
32
33
|
// --- 用一个隐藏的 Vue 实例承载响应式 state + computed getters ---
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
var computedDefs = {};
|
|
35
|
+
var store = { _disposed: false };
|
|
35
36
|
|
|
36
37
|
Object.keys(getters).forEach(function (key) {
|
|
37
38
|
computedDefs[key] = function () {
|
|
@@ -39,14 +40,21 @@ function createStoreInstance(Vue, id, options) {
|
|
|
39
40
|
};
|
|
40
41
|
});
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
var vm = new Vue({
|
|
43
44
|
data: function () {
|
|
44
|
-
return {
|
|
45
|
+
return {
|
|
46
|
+
$$state: initialState,
|
|
47
|
+
$$status: {
|
|
48
|
+
mounted: false,
|
|
49
|
+
active: false
|
|
50
|
+
}
|
|
51
|
+
};
|
|
45
52
|
},
|
|
46
53
|
computed: computedDefs,
|
|
47
54
|
});
|
|
48
55
|
|
|
49
|
-
|
|
56
|
+
var rawState = vm.$data.$$state;
|
|
57
|
+
var rawStatus = vm.$data.$$status;
|
|
50
58
|
|
|
51
59
|
// ====== state —— 代理到 vm.$$state ======
|
|
52
60
|
Object.keys(initialState).forEach(function (key) {
|
|
@@ -71,14 +79,14 @@ function createStoreInstance(Vue, id, options) {
|
|
|
71
79
|
});
|
|
72
80
|
|
|
73
81
|
// ====== watch —— 声明式副作用,生命周期自动回收 ======
|
|
74
|
-
|
|
82
|
+
var watches = options.watch || {};
|
|
75
83
|
Object.entries(watches).forEach(function (_ref) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
var path = _ref[0];
|
|
85
|
+
var def = _ref[1];
|
|
86
|
+
var handler = typeof def === 'function' ? def : def.handler;
|
|
87
|
+
var watchOpts = { deep: true };
|
|
80
88
|
if (typeof def === 'object' && def.immediate) watchOpts.immediate = true;
|
|
81
|
-
|
|
89
|
+
var expr = function () {
|
|
82
90
|
return path.split('.').reduce(function (obj, k) { return obj && obj[k]; }, store);
|
|
83
91
|
};
|
|
84
92
|
vm.$watch(expr, handler.bind(store), watchOpts);
|
|
@@ -86,6 +94,7 @@ function createStoreInstance(Vue, id, options) {
|
|
|
86
94
|
|
|
87
95
|
// ====== 内置方法 ======
|
|
88
96
|
store.$state = rawState;
|
|
97
|
+
store.$status = rawStatus;
|
|
89
98
|
store.$id = id;
|
|
90
99
|
|
|
91
100
|
/**
|
|
@@ -93,37 +102,24 @@ function createStoreInstance(Vue, id, options) {
|
|
|
93
102
|
* @param {Object|Function} partial - 要合并的对象,或 (state) => Object 函数
|
|
94
103
|
*/
|
|
95
104
|
store.$patch = function (partial) {
|
|
96
|
-
|
|
105
|
+
var obj = typeof partial === 'function' ? partial(rawState) : partial;
|
|
97
106
|
Object.keys(obj).forEach(function (key) {
|
|
98
107
|
Vue.set(rawState, key, obj[key]);
|
|
99
108
|
});
|
|
100
109
|
};
|
|
101
110
|
|
|
102
|
-
/**
|
|
103
|
-
* 订阅 state 变化
|
|
104
|
-
* @param {Function} callback - (newState) => void
|
|
105
|
-
* @returns {Function} 取消订阅函数
|
|
106
|
-
*/
|
|
107
|
-
store.$subscribe = function (callback) {
|
|
108
|
-
return vm.$watch(
|
|
109
|
-
function () { return Object.assign({}, rawState); },
|
|
110
|
-
callback,
|
|
111
|
-
{ deep: true }
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
111
|
/**
|
|
116
112
|
* 重置 state 到初始值
|
|
117
113
|
*/
|
|
118
114
|
store.$reset = function () {
|
|
119
|
-
|
|
115
|
+
var fresh = options.state();
|
|
120
116
|
Object.keys(fresh).forEach(function (key) {
|
|
121
117
|
rawState[key] = fresh[key];
|
|
122
118
|
});
|
|
123
119
|
};
|
|
124
120
|
|
|
125
121
|
// ====== 内置事件总线(页面作用域隔离通信) ======
|
|
126
|
-
|
|
122
|
+
var _listeners = {};
|
|
127
123
|
|
|
128
124
|
/**
|
|
129
125
|
* 发射事件(仅当前 store 作用域内)
|
|
@@ -131,7 +127,7 @@ function createStoreInstance(Vue, id, options) {
|
|
|
131
127
|
* @param {*} payload - 事件数据
|
|
132
128
|
*/
|
|
133
129
|
store.$emit = function (event, payload) {
|
|
134
|
-
|
|
130
|
+
var fns = _listeners[event];
|
|
135
131
|
if (fns) fns.slice().forEach(function (fn) { fn(payload); });
|
|
136
132
|
};
|
|
137
133
|
|
|
@@ -145,7 +141,7 @@ function createStoreInstance(Vue, id, options) {
|
|
|
145
141
|
if (!_listeners[event]) _listeners[event] = [];
|
|
146
142
|
_listeners[event].push(handler);
|
|
147
143
|
return function () {
|
|
148
|
-
|
|
144
|
+
var idx = _listeners[event].indexOf(handler);
|
|
149
145
|
if (idx > -1) _listeners[event].splice(idx, 1);
|
|
150
146
|
};
|
|
151
147
|
};
|
|
@@ -158,17 +154,77 @@ function createStoreInstance(Vue, id, options) {
|
|
|
158
154
|
store.$off = function (event, handler) {
|
|
159
155
|
if (!_listeners[event]) return;
|
|
160
156
|
if (handler) {
|
|
161
|
-
|
|
157
|
+
var idx = _listeners[event].indexOf(handler);
|
|
162
158
|
if (idx > -1) _listeners[event].splice(idx, 1);
|
|
163
159
|
} else {
|
|
164
160
|
delete _listeners[event];
|
|
165
161
|
}
|
|
166
162
|
};
|
|
167
163
|
|
|
164
|
+
// ====== 页面生命周期 ======
|
|
165
|
+
|
|
166
|
+
/** 触发生命周期钩子 + 广播事件 */
|
|
167
|
+
function runHook(name, payload) {
|
|
168
|
+
var hook = lifecycles[name];
|
|
169
|
+
if (typeof hook === 'function') {
|
|
170
|
+
hook.call(store, payload);
|
|
171
|
+
}
|
|
172
|
+
store.$emit('page:' + name, payload);
|
|
173
|
+
}
|
|
174
|
+
|
|
168
175
|
/**
|
|
169
|
-
*
|
|
176
|
+
* 绑定到组件实例,自动挂载生命周期
|
|
177
|
+
*
|
|
178
|
+
* 原理:Vue 2 的 vm.$on('hook:xxx') 可监听组件自身生命周期事件,
|
|
179
|
+
* 这是 Vue 2 内置能力,不是 hack。
|
|
180
|
+
*
|
|
181
|
+
* 时序保证:
|
|
182
|
+
* 无 keep-alive → mounted → beforeDestroy
|
|
183
|
+
* 有 keep-alive → mounted → activated ⇄ deactivated → beforeDestroy
|
|
184
|
+
*
|
|
185
|
+
* 必须在 created 中调用(mounted 之前),否则 hook:mounted 捕获不到。
|
|
186
|
+
*
|
|
187
|
+
* @param {Vue} componentVm - 组件实例(通常传 this)
|
|
188
|
+
* @returns {Object} store - 支持链式调用
|
|
189
|
+
*/
|
|
190
|
+
store.bindTo = function (componentVm) {
|
|
191
|
+
if (store._disposed) return store;
|
|
192
|
+
|
|
193
|
+
componentVm.$on('hook:mounted', function () {
|
|
194
|
+
rawStatus.mounted = true;
|
|
195
|
+
rawStatus.active = true;
|
|
196
|
+
runHook('mount');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
componentVm.$on('hook:activated', function () {
|
|
200
|
+
rawStatus.active = true;
|
|
201
|
+
runHook('activate');
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
componentVm.$on('hook:deactivated', function () {
|
|
205
|
+
rawStatus.active = false;
|
|
206
|
+
runHook('deactivate');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
componentVm.$on('hook:beforeDestroy', function () {
|
|
210
|
+
store.$destroy();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return store;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
// ====== $destroy ======
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* 销毁 store —— 触发 unmount 钩子、清空事件、销毁 vm、移除注册
|
|
170
220
|
*/
|
|
171
221
|
store.$destroy = function () {
|
|
222
|
+
if (store._disposed) return;
|
|
223
|
+
|
|
224
|
+
rawStatus.mounted = false;
|
|
225
|
+
rawStatus.active = false;
|
|
226
|
+
runHook('unmount');
|
|
227
|
+
|
|
172
228
|
store._disposed = true;
|
|
173
229
|
Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
|
|
174
230
|
vm.$destroy();
|
|
@@ -184,41 +240,47 @@ function createStoreInstance(Vue, id, options) {
|
|
|
184
240
|
* 定义页面级 Store
|
|
185
241
|
*
|
|
186
242
|
* @param {string} id - 唯一标识
|
|
187
|
-
* @param {Object} options - { state, getters, actions, watch }
|
|
188
|
-
* @returns {Function} useStore - 调用即获取 / 创建 store 实例
|
|
243
|
+
* @param {Object} options - { state, getters, actions, watch, lifecycle }
|
|
244
|
+
* @returns {Function} useStore(vm?) - 调用即获取 / 创建 store 实例
|
|
189
245
|
*
|
|
190
246
|
* @example
|
|
191
|
-
*
|
|
192
|
-
* state: ()
|
|
247
|
+
* var useFunnelStore = definePageStore('funnelDetail', {
|
|
248
|
+
* state: function () { return { filters: {}, loading: false }; },
|
|
193
249
|
* getters: {
|
|
194
|
-
* isReady() { return !this.loading; }
|
|
250
|
+
* isReady: function () { return !this.loading; }
|
|
195
251
|
* },
|
|
196
252
|
* actions: {
|
|
197
|
-
* async
|
|
253
|
+
* fetchData: async function () { ... }
|
|
254
|
+
* },
|
|
255
|
+
* lifecycle: {
|
|
256
|
+
* mount: function () { this.fetchData(); },
|
|
257
|
+
* unmount: function () { console.log('bye'); },
|
|
258
|
+
* activate: function () { this.needRefresh && this.fetchData(); }
|
|
198
259
|
* }
|
|
199
260
|
* });
|
|
200
261
|
*
|
|
201
|
-
* //
|
|
202
|
-
*
|
|
203
|
-
*
|
|
262
|
+
* // 组件中(created 里传 this,自动绑定全部生命周期 + 自动销毁)
|
|
263
|
+
* created() {
|
|
264
|
+
* this.store = useFunnelStore(this);
|
|
265
|
+
* }
|
|
204
266
|
*
|
|
205
|
-
* //
|
|
206
|
-
* store
|
|
267
|
+
* // 不需要 lifecycle 时,不传参数,和 0.1.0 完全一样
|
|
268
|
+
* this.store = useFunnelStore();
|
|
207
269
|
*/
|
|
208
270
|
function definePageStore(id, options) {
|
|
209
271
|
if (!id || typeof options.state !== 'function') {
|
|
210
272
|
throw new Error('[vue-page-store] 需要 id 和 state 函数');
|
|
211
273
|
}
|
|
212
274
|
|
|
213
|
-
|
|
214
|
-
let _Vue = null;
|
|
275
|
+
var _Vue = null;
|
|
215
276
|
|
|
216
|
-
return function useStore() {
|
|
277
|
+
return function useStore(componentVm) {
|
|
217
278
|
if (storeRegistry.has(id)) {
|
|
218
|
-
|
|
279
|
+
var existing = storeRegistry.get(id);
|
|
280
|
+
if (componentVm) existing.bindTo(componentVm);
|
|
281
|
+
return existing;
|
|
219
282
|
}
|
|
220
283
|
|
|
221
|
-
// 自动获取 Vue 构造函数
|
|
222
284
|
if (!_Vue) {
|
|
223
285
|
try {
|
|
224
286
|
_Vue = require('vue');
|
|
@@ -230,35 +292,14 @@ function definePageStore(id, options) {
|
|
|
230
292
|
}
|
|
231
293
|
}
|
|
232
294
|
|
|
233
|
-
|
|
295
|
+
var store = createStoreInstance(_Vue, id, options);
|
|
234
296
|
storeRegistry.set(id, store);
|
|
297
|
+
if (componentVm) store.bindTo(componentVm);
|
|
235
298
|
return store;
|
|
236
299
|
};
|
|
237
300
|
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* 将 store 的 state 属性转为可在模板中使用的 refs 对象
|
|
241
|
-
*
|
|
242
|
-
* @param {Object} store - pageStore 实例
|
|
243
|
-
* @returns {Object} refs 对象(可解构赋值到 computed)
|
|
244
|
-
*
|
|
245
|
-
* @example
|
|
246
|
-
* const { filters, loading } = storeToRefs(store);
|
|
247
|
-
*/
|
|
248
|
-
function storeToRefs(store) {
|
|
249
|
-
const refs = {};
|
|
250
|
-
Object.keys(store.$state).forEach(function (key) {
|
|
251
|
-
Object.defineProperty(refs, key, {
|
|
252
|
-
enumerable: true,
|
|
253
|
-
get: function () { return store[key]; },
|
|
254
|
-
set: function (val) { store[key] = val; },
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
return refs;
|
|
258
|
-
}
|
|
259
|
-
var index = { definePageStore, storeToRefs, storeRegistry };
|
|
301
|
+
var index = { definePageStore, storeRegistry };
|
|
260
302
|
|
|
261
303
|
exports.default = index;
|
|
262
304
|
exports.definePageStore = definePageStore;
|
|
263
305
|
exports.storeRegistry = storeRegistry;
|
|
264
|
-
exports.storeToRefs = storeToRefs;
|
package/dist/index.esm.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* vue-page-store v0.1
|
|
2
|
+
* vue-page-store v0.2.1
|
|
3
3
|
* (c) 2026 weijianjun
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
* vue-page-store
|
|
7
|
+
* vue-page-store 0.2.0 — Vue 2.6 页面级 Store
|
|
8
8
|
*
|
|
9
9
|
* 状态、通信、生命周期,一个作用域全收。
|
|
10
10
|
*
|
|
@@ -18,16 +18,17 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
// Store 注册表(导出供调试 / devtools 使用)
|
|
21
|
-
|
|
21
|
+
var storeRegistry = new Map();
|
|
22
22
|
|
|
23
23
|
function createStoreInstance(Vue, id, options) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
var initialState = options.state();
|
|
25
|
+
var getters = options.getters || {};
|
|
26
|
+
var actions = options.actions || {};
|
|
27
|
+
var lifecycles = options.lifecycle || {};
|
|
27
28
|
|
|
28
29
|
// --- 用一个隐藏的 Vue 实例承载响应式 state + computed getters ---
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
var computedDefs = {};
|
|
31
|
+
var store = { _disposed: false };
|
|
31
32
|
|
|
32
33
|
Object.keys(getters).forEach(function (key) {
|
|
33
34
|
computedDefs[key] = function () {
|
|
@@ -35,14 +36,21 @@ function createStoreInstance(Vue, id, options) {
|
|
|
35
36
|
};
|
|
36
37
|
});
|
|
37
38
|
|
|
38
|
-
|
|
39
|
+
var vm = new Vue({
|
|
39
40
|
data: function () {
|
|
40
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
$$state: initialState,
|
|
43
|
+
$$status: {
|
|
44
|
+
mounted: false,
|
|
45
|
+
active: false
|
|
46
|
+
}
|
|
47
|
+
};
|
|
41
48
|
},
|
|
42
49
|
computed: computedDefs,
|
|
43
50
|
});
|
|
44
51
|
|
|
45
|
-
|
|
52
|
+
var rawState = vm.$data.$$state;
|
|
53
|
+
var rawStatus = vm.$data.$$status;
|
|
46
54
|
|
|
47
55
|
// ====== state —— 代理到 vm.$$state ======
|
|
48
56
|
Object.keys(initialState).forEach(function (key) {
|
|
@@ -67,14 +75,14 @@ function createStoreInstance(Vue, id, options) {
|
|
|
67
75
|
});
|
|
68
76
|
|
|
69
77
|
// ====== watch —— 声明式副作用,生命周期自动回收 ======
|
|
70
|
-
|
|
78
|
+
var watches = options.watch || {};
|
|
71
79
|
Object.entries(watches).forEach(function (_ref) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
80
|
+
var path = _ref[0];
|
|
81
|
+
var def = _ref[1];
|
|
82
|
+
var handler = typeof def === 'function' ? def : def.handler;
|
|
83
|
+
var watchOpts = { deep: true };
|
|
76
84
|
if (typeof def === 'object' && def.immediate) watchOpts.immediate = true;
|
|
77
|
-
|
|
85
|
+
var expr = function () {
|
|
78
86
|
return path.split('.').reduce(function (obj, k) { return obj && obj[k]; }, store);
|
|
79
87
|
};
|
|
80
88
|
vm.$watch(expr, handler.bind(store), watchOpts);
|
|
@@ -82,6 +90,7 @@ function createStoreInstance(Vue, id, options) {
|
|
|
82
90
|
|
|
83
91
|
// ====== 内置方法 ======
|
|
84
92
|
store.$state = rawState;
|
|
93
|
+
store.$status = rawStatus;
|
|
85
94
|
store.$id = id;
|
|
86
95
|
|
|
87
96
|
/**
|
|
@@ -89,37 +98,24 @@ function createStoreInstance(Vue, id, options) {
|
|
|
89
98
|
* @param {Object|Function} partial - 要合并的对象,或 (state) => Object 函数
|
|
90
99
|
*/
|
|
91
100
|
store.$patch = function (partial) {
|
|
92
|
-
|
|
101
|
+
var obj = typeof partial === 'function' ? partial(rawState) : partial;
|
|
93
102
|
Object.keys(obj).forEach(function (key) {
|
|
94
103
|
Vue.set(rawState, key, obj[key]);
|
|
95
104
|
});
|
|
96
105
|
};
|
|
97
106
|
|
|
98
|
-
/**
|
|
99
|
-
* 订阅 state 变化
|
|
100
|
-
* @param {Function} callback - (newState) => void
|
|
101
|
-
* @returns {Function} 取消订阅函数
|
|
102
|
-
*/
|
|
103
|
-
store.$subscribe = function (callback) {
|
|
104
|
-
return vm.$watch(
|
|
105
|
-
function () { return Object.assign({}, rawState); },
|
|
106
|
-
callback,
|
|
107
|
-
{ deep: true }
|
|
108
|
-
);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
107
|
/**
|
|
112
108
|
* 重置 state 到初始值
|
|
113
109
|
*/
|
|
114
110
|
store.$reset = function () {
|
|
115
|
-
|
|
111
|
+
var fresh = options.state();
|
|
116
112
|
Object.keys(fresh).forEach(function (key) {
|
|
117
113
|
rawState[key] = fresh[key];
|
|
118
114
|
});
|
|
119
115
|
};
|
|
120
116
|
|
|
121
117
|
// ====== 内置事件总线(页面作用域隔离通信) ======
|
|
122
|
-
|
|
118
|
+
var _listeners = {};
|
|
123
119
|
|
|
124
120
|
/**
|
|
125
121
|
* 发射事件(仅当前 store 作用域内)
|
|
@@ -127,7 +123,7 @@ function createStoreInstance(Vue, id, options) {
|
|
|
127
123
|
* @param {*} payload - 事件数据
|
|
128
124
|
*/
|
|
129
125
|
store.$emit = function (event, payload) {
|
|
130
|
-
|
|
126
|
+
var fns = _listeners[event];
|
|
131
127
|
if (fns) fns.slice().forEach(function (fn) { fn(payload); });
|
|
132
128
|
};
|
|
133
129
|
|
|
@@ -141,7 +137,7 @@ function createStoreInstance(Vue, id, options) {
|
|
|
141
137
|
if (!_listeners[event]) _listeners[event] = [];
|
|
142
138
|
_listeners[event].push(handler);
|
|
143
139
|
return function () {
|
|
144
|
-
|
|
140
|
+
var idx = _listeners[event].indexOf(handler);
|
|
145
141
|
if (idx > -1) _listeners[event].splice(idx, 1);
|
|
146
142
|
};
|
|
147
143
|
};
|
|
@@ -154,17 +150,77 @@ function createStoreInstance(Vue, id, options) {
|
|
|
154
150
|
store.$off = function (event, handler) {
|
|
155
151
|
if (!_listeners[event]) return;
|
|
156
152
|
if (handler) {
|
|
157
|
-
|
|
153
|
+
var idx = _listeners[event].indexOf(handler);
|
|
158
154
|
if (idx > -1) _listeners[event].splice(idx, 1);
|
|
159
155
|
} else {
|
|
160
156
|
delete _listeners[event];
|
|
161
157
|
}
|
|
162
158
|
};
|
|
163
159
|
|
|
160
|
+
// ====== 页面生命周期 ======
|
|
161
|
+
|
|
162
|
+
/** 触发生命周期钩子 + 广播事件 */
|
|
163
|
+
function runHook(name, payload) {
|
|
164
|
+
var hook = lifecycles[name];
|
|
165
|
+
if (typeof hook === 'function') {
|
|
166
|
+
hook.call(store, payload);
|
|
167
|
+
}
|
|
168
|
+
store.$emit('page:' + name, payload);
|
|
169
|
+
}
|
|
170
|
+
|
|
164
171
|
/**
|
|
165
|
-
*
|
|
172
|
+
* 绑定到组件实例,自动挂载生命周期
|
|
173
|
+
*
|
|
174
|
+
* 原理:Vue 2 的 vm.$on('hook:xxx') 可监听组件自身生命周期事件,
|
|
175
|
+
* 这是 Vue 2 内置能力,不是 hack。
|
|
176
|
+
*
|
|
177
|
+
* 时序保证:
|
|
178
|
+
* 无 keep-alive → mounted → beforeDestroy
|
|
179
|
+
* 有 keep-alive → mounted → activated ⇄ deactivated → beforeDestroy
|
|
180
|
+
*
|
|
181
|
+
* 必须在 created 中调用(mounted 之前),否则 hook:mounted 捕获不到。
|
|
182
|
+
*
|
|
183
|
+
* @param {Vue} componentVm - 组件实例(通常传 this)
|
|
184
|
+
* @returns {Object} store - 支持链式调用
|
|
185
|
+
*/
|
|
186
|
+
store.bindTo = function (componentVm) {
|
|
187
|
+
if (store._disposed) return store;
|
|
188
|
+
|
|
189
|
+
componentVm.$on('hook:mounted', function () {
|
|
190
|
+
rawStatus.mounted = true;
|
|
191
|
+
rawStatus.active = true;
|
|
192
|
+
runHook('mount');
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
componentVm.$on('hook:activated', function () {
|
|
196
|
+
rawStatus.active = true;
|
|
197
|
+
runHook('activate');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
componentVm.$on('hook:deactivated', function () {
|
|
201
|
+
rawStatus.active = false;
|
|
202
|
+
runHook('deactivate');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
componentVm.$on('hook:beforeDestroy', function () {
|
|
206
|
+
store.$destroy();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return store;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// ====== $destroy ======
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 销毁 store —— 触发 unmount 钩子、清空事件、销毁 vm、移除注册
|
|
166
216
|
*/
|
|
167
217
|
store.$destroy = function () {
|
|
218
|
+
if (store._disposed) return;
|
|
219
|
+
|
|
220
|
+
rawStatus.mounted = false;
|
|
221
|
+
rawStatus.active = false;
|
|
222
|
+
runHook('unmount');
|
|
223
|
+
|
|
168
224
|
store._disposed = true;
|
|
169
225
|
Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
|
|
170
226
|
vm.$destroy();
|
|
@@ -180,41 +236,47 @@ function createStoreInstance(Vue, id, options) {
|
|
|
180
236
|
* 定义页面级 Store
|
|
181
237
|
*
|
|
182
238
|
* @param {string} id - 唯一标识
|
|
183
|
-
* @param {Object} options - { state, getters, actions, watch }
|
|
184
|
-
* @returns {Function} useStore - 调用即获取 / 创建 store 实例
|
|
239
|
+
* @param {Object} options - { state, getters, actions, watch, lifecycle }
|
|
240
|
+
* @returns {Function} useStore(vm?) - 调用即获取 / 创建 store 实例
|
|
185
241
|
*
|
|
186
242
|
* @example
|
|
187
|
-
*
|
|
188
|
-
* state: ()
|
|
243
|
+
* var useFunnelStore = definePageStore('funnelDetail', {
|
|
244
|
+
* state: function () { return { filters: {}, loading: false }; },
|
|
189
245
|
* getters: {
|
|
190
|
-
* isReady() { return !this.loading; }
|
|
246
|
+
* isReady: function () { return !this.loading; }
|
|
191
247
|
* },
|
|
192
248
|
* actions: {
|
|
193
|
-
* async
|
|
249
|
+
* fetchData: async function () { ... }
|
|
250
|
+
* },
|
|
251
|
+
* lifecycle: {
|
|
252
|
+
* mount: function () { this.fetchData(); },
|
|
253
|
+
* unmount: function () { console.log('bye'); },
|
|
254
|
+
* activate: function () { this.needRefresh && this.fetchData(); }
|
|
194
255
|
* }
|
|
195
256
|
* });
|
|
196
257
|
*
|
|
197
|
-
* //
|
|
198
|
-
*
|
|
199
|
-
*
|
|
258
|
+
* // 组件中(created 里传 this,自动绑定全部生命周期 + 自动销毁)
|
|
259
|
+
* created() {
|
|
260
|
+
* this.store = useFunnelStore(this);
|
|
261
|
+
* }
|
|
200
262
|
*
|
|
201
|
-
* //
|
|
202
|
-
* store
|
|
263
|
+
* // 不需要 lifecycle 时,不传参数,和 0.1.0 完全一样
|
|
264
|
+
* this.store = useFunnelStore();
|
|
203
265
|
*/
|
|
204
266
|
function definePageStore(id, options) {
|
|
205
267
|
if (!id || typeof options.state !== 'function') {
|
|
206
268
|
throw new Error('[vue-page-store] 需要 id 和 state 函数');
|
|
207
269
|
}
|
|
208
270
|
|
|
209
|
-
|
|
210
|
-
let _Vue = null;
|
|
271
|
+
var _Vue = null;
|
|
211
272
|
|
|
212
|
-
return function useStore() {
|
|
273
|
+
return function useStore(componentVm) {
|
|
213
274
|
if (storeRegistry.has(id)) {
|
|
214
|
-
|
|
275
|
+
var existing = storeRegistry.get(id);
|
|
276
|
+
if (componentVm) existing.bindTo(componentVm);
|
|
277
|
+
return existing;
|
|
215
278
|
}
|
|
216
279
|
|
|
217
|
-
// 自动获取 Vue 构造函数
|
|
218
280
|
if (!_Vue) {
|
|
219
281
|
try {
|
|
220
282
|
_Vue = require('vue');
|
|
@@ -226,32 +288,12 @@ function definePageStore(id, options) {
|
|
|
226
288
|
}
|
|
227
289
|
}
|
|
228
290
|
|
|
229
|
-
|
|
291
|
+
var store = createStoreInstance(_Vue, id, options);
|
|
230
292
|
storeRegistry.set(id, store);
|
|
293
|
+
if (componentVm) store.bindTo(componentVm);
|
|
231
294
|
return store;
|
|
232
295
|
};
|
|
233
296
|
}
|
|
297
|
+
var index = { definePageStore, storeRegistry };
|
|
234
298
|
|
|
235
|
-
|
|
236
|
-
* 将 store 的 state 属性转为可在模板中使用的 refs 对象
|
|
237
|
-
*
|
|
238
|
-
* @param {Object} store - pageStore 实例
|
|
239
|
-
* @returns {Object} refs 对象(可解构赋值到 computed)
|
|
240
|
-
*
|
|
241
|
-
* @example
|
|
242
|
-
* const { filters, loading } = storeToRefs(store);
|
|
243
|
-
*/
|
|
244
|
-
function storeToRefs(store) {
|
|
245
|
-
const refs = {};
|
|
246
|
-
Object.keys(store.$state).forEach(function (key) {
|
|
247
|
-
Object.defineProperty(refs, key, {
|
|
248
|
-
enumerable: true,
|
|
249
|
-
get: function () { return store[key]; },
|
|
250
|
-
set: function (val) { store[key] = val; },
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
return refs;
|
|
254
|
-
}
|
|
255
|
-
var index = { definePageStore, storeToRefs, storeRegistry };
|
|
256
|
-
|
|
257
|
-
export { index as default, definePageStore, storeRegistry, storeToRefs };
|
|
299
|
+
export { index as default, definePageStore, storeRegistry };
|
package/dist/index.umd.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* vue-page-store v0.1
|
|
2
|
+
* vue-page-store v0.2.1
|
|
3
3
|
* (c) 2026 weijianjun
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
})(this, (function (exports) { 'use strict';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* vue-page-store
|
|
13
|
+
* vue-page-store 0.2.0 — Vue 2.6 页面级 Store
|
|
14
14
|
*
|
|
15
15
|
* 状态、通信、生命周期,一个作用域全收。
|
|
16
16
|
*
|
|
@@ -24,16 +24,17 @@
|
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
// Store 注册表(导出供调试 / devtools 使用)
|
|
27
|
-
|
|
27
|
+
var storeRegistry = new Map();
|
|
28
28
|
|
|
29
29
|
function createStoreInstance(Vue, id, options) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
var initialState = options.state();
|
|
31
|
+
var getters = options.getters || {};
|
|
32
|
+
var actions = options.actions || {};
|
|
33
|
+
var lifecycles = options.lifecycle || {};
|
|
33
34
|
|
|
34
35
|
// --- 用一个隐藏的 Vue 实例承载响应式 state + computed getters ---
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
var computedDefs = {};
|
|
37
|
+
var store = { _disposed: false };
|
|
37
38
|
|
|
38
39
|
Object.keys(getters).forEach(function (key) {
|
|
39
40
|
computedDefs[key] = function () {
|
|
@@ -41,14 +42,21 @@
|
|
|
41
42
|
};
|
|
42
43
|
});
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
var vm = new Vue({
|
|
45
46
|
data: function () {
|
|
46
|
-
return {
|
|
47
|
+
return {
|
|
48
|
+
$$state: initialState,
|
|
49
|
+
$$status: {
|
|
50
|
+
mounted: false,
|
|
51
|
+
active: false
|
|
52
|
+
}
|
|
53
|
+
};
|
|
47
54
|
},
|
|
48
55
|
computed: computedDefs,
|
|
49
56
|
});
|
|
50
57
|
|
|
51
|
-
|
|
58
|
+
var rawState = vm.$data.$$state;
|
|
59
|
+
var rawStatus = vm.$data.$$status;
|
|
52
60
|
|
|
53
61
|
// ====== state —— 代理到 vm.$$state ======
|
|
54
62
|
Object.keys(initialState).forEach(function (key) {
|
|
@@ -73,14 +81,14 @@
|
|
|
73
81
|
});
|
|
74
82
|
|
|
75
83
|
// ====== watch —— 声明式副作用,生命周期自动回收 ======
|
|
76
|
-
|
|
84
|
+
var watches = options.watch || {};
|
|
77
85
|
Object.entries(watches).forEach(function (_ref) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
86
|
+
var path = _ref[0];
|
|
87
|
+
var def = _ref[1];
|
|
88
|
+
var handler = typeof def === 'function' ? def : def.handler;
|
|
89
|
+
var watchOpts = { deep: true };
|
|
82
90
|
if (typeof def === 'object' && def.immediate) watchOpts.immediate = true;
|
|
83
|
-
|
|
91
|
+
var expr = function () {
|
|
84
92
|
return path.split('.').reduce(function (obj, k) { return obj && obj[k]; }, store);
|
|
85
93
|
};
|
|
86
94
|
vm.$watch(expr, handler.bind(store), watchOpts);
|
|
@@ -88,6 +96,7 @@
|
|
|
88
96
|
|
|
89
97
|
// ====== 内置方法 ======
|
|
90
98
|
store.$state = rawState;
|
|
99
|
+
store.$status = rawStatus;
|
|
91
100
|
store.$id = id;
|
|
92
101
|
|
|
93
102
|
/**
|
|
@@ -95,37 +104,24 @@
|
|
|
95
104
|
* @param {Object|Function} partial - 要合并的对象,或 (state) => Object 函数
|
|
96
105
|
*/
|
|
97
106
|
store.$patch = function (partial) {
|
|
98
|
-
|
|
107
|
+
var obj = typeof partial === 'function' ? partial(rawState) : partial;
|
|
99
108
|
Object.keys(obj).forEach(function (key) {
|
|
100
109
|
Vue.set(rawState, key, obj[key]);
|
|
101
110
|
});
|
|
102
111
|
};
|
|
103
112
|
|
|
104
|
-
/**
|
|
105
|
-
* 订阅 state 变化
|
|
106
|
-
* @param {Function} callback - (newState) => void
|
|
107
|
-
* @returns {Function} 取消订阅函数
|
|
108
|
-
*/
|
|
109
|
-
store.$subscribe = function (callback) {
|
|
110
|
-
return vm.$watch(
|
|
111
|
-
function () { return Object.assign({}, rawState); },
|
|
112
|
-
callback,
|
|
113
|
-
{ deep: true }
|
|
114
|
-
);
|
|
115
|
-
};
|
|
116
|
-
|
|
117
113
|
/**
|
|
118
114
|
* 重置 state 到初始值
|
|
119
115
|
*/
|
|
120
116
|
store.$reset = function () {
|
|
121
|
-
|
|
117
|
+
var fresh = options.state();
|
|
122
118
|
Object.keys(fresh).forEach(function (key) {
|
|
123
119
|
rawState[key] = fresh[key];
|
|
124
120
|
});
|
|
125
121
|
};
|
|
126
122
|
|
|
127
123
|
// ====== 内置事件总线(页面作用域隔离通信) ======
|
|
128
|
-
|
|
124
|
+
var _listeners = {};
|
|
129
125
|
|
|
130
126
|
/**
|
|
131
127
|
* 发射事件(仅当前 store 作用域内)
|
|
@@ -133,7 +129,7 @@
|
|
|
133
129
|
* @param {*} payload - 事件数据
|
|
134
130
|
*/
|
|
135
131
|
store.$emit = function (event, payload) {
|
|
136
|
-
|
|
132
|
+
var fns = _listeners[event];
|
|
137
133
|
if (fns) fns.slice().forEach(function (fn) { fn(payload); });
|
|
138
134
|
};
|
|
139
135
|
|
|
@@ -147,7 +143,7 @@
|
|
|
147
143
|
if (!_listeners[event]) _listeners[event] = [];
|
|
148
144
|
_listeners[event].push(handler);
|
|
149
145
|
return function () {
|
|
150
|
-
|
|
146
|
+
var idx = _listeners[event].indexOf(handler);
|
|
151
147
|
if (idx > -1) _listeners[event].splice(idx, 1);
|
|
152
148
|
};
|
|
153
149
|
};
|
|
@@ -160,17 +156,77 @@
|
|
|
160
156
|
store.$off = function (event, handler) {
|
|
161
157
|
if (!_listeners[event]) return;
|
|
162
158
|
if (handler) {
|
|
163
|
-
|
|
159
|
+
var idx = _listeners[event].indexOf(handler);
|
|
164
160
|
if (idx > -1) _listeners[event].splice(idx, 1);
|
|
165
161
|
} else {
|
|
166
162
|
delete _listeners[event];
|
|
167
163
|
}
|
|
168
164
|
};
|
|
169
165
|
|
|
166
|
+
// ====== 页面生命周期 ======
|
|
167
|
+
|
|
168
|
+
/** 触发生命周期钩子 + 广播事件 */
|
|
169
|
+
function runHook(name, payload) {
|
|
170
|
+
var hook = lifecycles[name];
|
|
171
|
+
if (typeof hook === 'function') {
|
|
172
|
+
hook.call(store, payload);
|
|
173
|
+
}
|
|
174
|
+
store.$emit('page:' + name, payload);
|
|
175
|
+
}
|
|
176
|
+
|
|
170
177
|
/**
|
|
171
|
-
*
|
|
178
|
+
* 绑定到组件实例,自动挂载生命周期
|
|
179
|
+
*
|
|
180
|
+
* 原理:Vue 2 的 vm.$on('hook:xxx') 可监听组件自身生命周期事件,
|
|
181
|
+
* 这是 Vue 2 内置能力,不是 hack。
|
|
182
|
+
*
|
|
183
|
+
* 时序保证:
|
|
184
|
+
* 无 keep-alive → mounted → beforeDestroy
|
|
185
|
+
* 有 keep-alive → mounted → activated ⇄ deactivated → beforeDestroy
|
|
186
|
+
*
|
|
187
|
+
* 必须在 created 中调用(mounted 之前),否则 hook:mounted 捕获不到。
|
|
188
|
+
*
|
|
189
|
+
* @param {Vue} componentVm - 组件实例(通常传 this)
|
|
190
|
+
* @returns {Object} store - 支持链式调用
|
|
191
|
+
*/
|
|
192
|
+
store.bindTo = function (componentVm) {
|
|
193
|
+
if (store._disposed) return store;
|
|
194
|
+
|
|
195
|
+
componentVm.$on('hook:mounted', function () {
|
|
196
|
+
rawStatus.mounted = true;
|
|
197
|
+
rawStatus.active = true;
|
|
198
|
+
runHook('mount');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
componentVm.$on('hook:activated', function () {
|
|
202
|
+
rawStatus.active = true;
|
|
203
|
+
runHook('activate');
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
componentVm.$on('hook:deactivated', function () {
|
|
207
|
+
rawStatus.active = false;
|
|
208
|
+
runHook('deactivate');
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
componentVm.$on('hook:beforeDestroy', function () {
|
|
212
|
+
store.$destroy();
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return store;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// ====== $destroy ======
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 销毁 store —— 触发 unmount 钩子、清空事件、销毁 vm、移除注册
|
|
172
222
|
*/
|
|
173
223
|
store.$destroy = function () {
|
|
224
|
+
if (store._disposed) return;
|
|
225
|
+
|
|
226
|
+
rawStatus.mounted = false;
|
|
227
|
+
rawStatus.active = false;
|
|
228
|
+
runHook('unmount');
|
|
229
|
+
|
|
174
230
|
store._disposed = true;
|
|
175
231
|
Object.keys(_listeners).forEach(function (key) { delete _listeners[key]; });
|
|
176
232
|
vm.$destroy();
|
|
@@ -186,41 +242,47 @@
|
|
|
186
242
|
* 定义页面级 Store
|
|
187
243
|
*
|
|
188
244
|
* @param {string} id - 唯一标识
|
|
189
|
-
* @param {Object} options - { state, getters, actions, watch }
|
|
190
|
-
* @returns {Function} useStore - 调用即获取 / 创建 store 实例
|
|
245
|
+
* @param {Object} options - { state, getters, actions, watch, lifecycle }
|
|
246
|
+
* @returns {Function} useStore(vm?) - 调用即获取 / 创建 store 实例
|
|
191
247
|
*
|
|
192
248
|
* @example
|
|
193
|
-
*
|
|
194
|
-
* state: ()
|
|
249
|
+
* var useFunnelStore = definePageStore('funnelDetail', {
|
|
250
|
+
* state: function () { return { filters: {}, loading: false }; },
|
|
195
251
|
* getters: {
|
|
196
|
-
* isReady() { return !this.loading; }
|
|
252
|
+
* isReady: function () { return !this.loading; }
|
|
197
253
|
* },
|
|
198
254
|
* actions: {
|
|
199
|
-
* async
|
|
255
|
+
* fetchData: async function () { ... }
|
|
256
|
+
* },
|
|
257
|
+
* lifecycle: {
|
|
258
|
+
* mount: function () { this.fetchData(); },
|
|
259
|
+
* unmount: function () { console.log('bye'); },
|
|
260
|
+
* activate: function () { this.needRefresh && this.fetchData(); }
|
|
200
261
|
* }
|
|
201
262
|
* });
|
|
202
263
|
*
|
|
203
|
-
* //
|
|
204
|
-
*
|
|
205
|
-
*
|
|
264
|
+
* // 组件中(created 里传 this,自动绑定全部生命周期 + 自动销毁)
|
|
265
|
+
* created() {
|
|
266
|
+
* this.store = useFunnelStore(this);
|
|
267
|
+
* }
|
|
206
268
|
*
|
|
207
|
-
* //
|
|
208
|
-
* store
|
|
269
|
+
* // 不需要 lifecycle 时,不传参数,和 0.1.0 完全一样
|
|
270
|
+
* this.store = useFunnelStore();
|
|
209
271
|
*/
|
|
210
272
|
function definePageStore(id, options) {
|
|
211
273
|
if (!id || typeof options.state !== 'function') {
|
|
212
274
|
throw new Error('[vue-page-store] 需要 id 和 state 函数');
|
|
213
275
|
}
|
|
214
276
|
|
|
215
|
-
|
|
216
|
-
let _Vue = null;
|
|
277
|
+
var _Vue = null;
|
|
217
278
|
|
|
218
|
-
return function useStore() {
|
|
279
|
+
return function useStore(componentVm) {
|
|
219
280
|
if (storeRegistry.has(id)) {
|
|
220
|
-
|
|
281
|
+
var existing = storeRegistry.get(id);
|
|
282
|
+
if (componentVm) existing.bindTo(componentVm);
|
|
283
|
+
return existing;
|
|
221
284
|
}
|
|
222
285
|
|
|
223
|
-
// 自动获取 Vue 构造函数
|
|
224
286
|
if (!_Vue) {
|
|
225
287
|
try {
|
|
226
288
|
_Vue = require('vue');
|
|
@@ -232,38 +294,17 @@
|
|
|
232
294
|
}
|
|
233
295
|
}
|
|
234
296
|
|
|
235
|
-
|
|
297
|
+
var store = createStoreInstance(_Vue, id, options);
|
|
236
298
|
storeRegistry.set(id, store);
|
|
299
|
+
if (componentVm) store.bindTo(componentVm);
|
|
237
300
|
return store;
|
|
238
301
|
};
|
|
239
302
|
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* 将 store 的 state 属性转为可在模板中使用的 refs 对象
|
|
243
|
-
*
|
|
244
|
-
* @param {Object} store - pageStore 实例
|
|
245
|
-
* @returns {Object} refs 对象(可解构赋值到 computed)
|
|
246
|
-
*
|
|
247
|
-
* @example
|
|
248
|
-
* const { filters, loading } = storeToRefs(store);
|
|
249
|
-
*/
|
|
250
|
-
function storeToRefs(store) {
|
|
251
|
-
const refs = {};
|
|
252
|
-
Object.keys(store.$state).forEach(function (key) {
|
|
253
|
-
Object.defineProperty(refs, key, {
|
|
254
|
-
enumerable: true,
|
|
255
|
-
get: function () { return store[key]; },
|
|
256
|
-
set: function (val) { store[key] = val; },
|
|
257
|
-
});
|
|
258
|
-
});
|
|
259
|
-
return refs;
|
|
260
|
-
}
|
|
261
|
-
var index = { definePageStore, storeToRefs, storeRegistry };
|
|
303
|
+
var index = { definePageStore, storeRegistry };
|
|
262
304
|
|
|
263
305
|
exports.default = index;
|
|
264
306
|
exports.definePageStore = definePageStore;
|
|
265
307
|
exports.storeRegistry = storeRegistry;
|
|
266
|
-
exports.storeToRefs = storeToRefs;
|
|
267
308
|
|
|
268
309
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
269
310
|
|
package/dist/index.umd.min.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* vue-page-store v0.1
|
|
2
|
+
* vue-page-store v0.2.1
|
|
3
3
|
* (c) 2026 weijianjun
|
|
4
4
|
* @license MIT
|
|
5
5
|
*/
|
|
6
6
|
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).VuePageStore={})}(this,function(e){"use strict";
|
|
7
7
|
/**
|
|
8
|
-
* vue-page-store
|
|
8
|
+
* vue-page-store 0.2.0 — Vue 2.6 页面级 Store
|
|
9
9
|
*
|
|
10
10
|
* 状态、通信、生命周期,一个作用域全收。
|
|
11
11
|
*
|
|
@@ -16,4 +16,4 @@
|
|
|
16
16
|
*
|
|
17
17
|
* @author weijianjun
|
|
18
18
|
* @license MIT
|
|
19
|
-
*/
|
|
19
|
+
*/var t=new Map;function n(e,n){if(!e||"function"!=typeof n.state)throw new Error("[vue-page-store] 需要 id 和 state 函数");var o=null;return function(i){if(t.has(e)){var r=t.get(e);return i&&r.bindTo(i),r}if(!o)try{(o=require("vue")).default&&(o=o.default)}catch(e){throw new Error("[vue-page-store] 无法自动获取 Vue,请确保 vue 已安装")}var c=function(e,n,o){var i=o.state(),r=o.getters||{},c=o.actions||{},u=o.lifecycle||{},f={},a={_disposed:!1};Object.keys(r).forEach(function(e){f[e]=function(){return r[e].call(a)}});var s=new e({data:function(){return{$$state:i,$$status:{mounted:!1,active:!1}}},computed:f}),d=s.$data.$$state,v=s.$data.$$status;Object.keys(i).forEach(function(e){Object.defineProperty(a,e,{enumerable:!0,get:function(){return d[e]},set:function(t){d[e]=t}})}),Object.keys(r).forEach(function(e){Object.defineProperty(a,e,{enumerable:!0,get:function(){return s[e]}})}),Object.keys(c).forEach(function(e){a[e]=c[e].bind(a)});var l=o.watch||{};Object.entries(l).forEach(function(e){var t=e[0],n=e[1],o="function"==typeof n?n:n.handler,i={deep:!0};"object"==typeof n&&n.immediate&&(i.immediate=!0),s.$watch(function(){return t.split(".").reduce(function(e,t){return e&&e[t]},a)},o.bind(a),i)}),a.$state=d,a.$status=v,a.$id=n,a.$patch=function(t){var n="function"==typeof t?t(d):t;Object.keys(n).forEach(function(t){e.set(d,t,n[t])})},a.$reset=function(){var e=o.state();Object.keys(e).forEach(function(t){d[t]=e[t]})};var p={};function y(e,t){var n=u[e];"function"==typeof n&&n.call(a,t),a.$emit("page:"+e,t)}return a.$emit=function(e,t){var n=p[e];n&&n.slice().forEach(function(e){e(t)})},a.$on=function(e,t){return p[e]||(p[e]=[]),p[e].push(t),function(){var n=p[e].indexOf(t);n>-1&&p[e].splice(n,1)}},a.$off=function(e,t){if(p[e])if(t){var n=p[e].indexOf(t);n>-1&&p[e].splice(n,1)}else delete p[e]},a.bindTo=function(e){return a._disposed||(e.$on("hook:mounted",function(){v.mounted=!0,v.active=!0,y("mount")}),e.$on("hook:activated",function(){v.active=!0,y("activate")}),e.$on("hook:deactivated",function(){v.active=!1,y("deactivate")}),e.$on("hook:beforeDestroy",function(){a.$destroy()})),a},a.$destroy=function(){a._disposed||(v.mounted=!1,v.active=!1,y("unmount"),a._disposed=!0,Object.keys(p).forEach(function(e){delete p[e]}),s.$destroy(),t.delete(n))},a._vm=s,a}(o,e,n);return t.set(e,c),i&&c.bindTo(i),c}}var o={definePageStore:n,storeRegistry:t};e.default=o,e.definePageStore=n,e.storeRegistry=t,Object.defineProperty(e,"__esModule",{value:!0})});
|