cbvirtua 1.0.8 → 1.0.9
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 +270 -0
- package/package.json +1 -1
- package/pre/index.js +93 -0
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# vue-presetup
|
|
2
|
+
|
|
3
|
+
> 仅适用于 Vue3
|
|
4
|
+
|
|
5
|
+
vue-presetup 可以将指定的 Vue 组件进行提前加载,并在组件“准备好”后再进行下一步操作,
|
|
6
|
+
|
|
7
|
+
如从当前路由 /foo 跳转至 /bar 时,可以先提前在 /foo 页面加载 /bar 对应的组件,随后再进行路由切换
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
通过 npm 安装
|
|
12
|
+
|
|
13
|
+
```shell
|
|
14
|
+
npm install vue-presetup -S
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
通过 yarn 安装
|
|
18
|
+
|
|
19
|
+
```shell
|
|
20
|
+
yarn add vue-presetup -S
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
通过 pnpm 安装
|
|
24
|
+
|
|
25
|
+
```shell
|
|
26
|
+
pnpm install vue-presetup -S
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 基础使用示例
|
|
30
|
+
|
|
31
|
+
有一个简单的使用示例,这个示例是摘自 [playground](./playground) 中的
|
|
32
|
+
|
|
33
|
+
1. 有以下的路由配置
|
|
34
|
+
|
|
35
|
+
**router.ts**
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { createRouter, createWebHashHistory } from 'vue-router'
|
|
39
|
+
import Foo from './views/Foo.vue'
|
|
40
|
+
import Bar from './views/Bar.vue'
|
|
41
|
+
|
|
42
|
+
const router = createRouter({
|
|
43
|
+
history: createWebHashHistory(),
|
|
44
|
+
routes: [
|
|
45
|
+
{
|
|
46
|
+
path: '/',
|
|
47
|
+
redirect: '/foo'
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
path: '/foo',
|
|
51
|
+
component: Foo
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: '/bar',
|
|
55
|
+
component: Bar
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export default router
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
2. 在 App.vue 使用路由并放置 vue-presetup 需要的载体
|
|
64
|
+
|
|
65
|
+
**App.vue**
|
|
66
|
+
|
|
67
|
+
```vue
|
|
68
|
+
<script setup lang="ts">
|
|
69
|
+
import { PresetupView } from 'vue-presetup'
|
|
70
|
+
</script>
|
|
71
|
+
|
|
72
|
+
<template>
|
|
73
|
+
<RouterView v-slot="{ Component }">
|
|
74
|
+
<PresetupView :active="Component"></PresetupView>
|
|
75
|
+
</RouterView>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
3. 在可能被提前加载的 /bar 组件需要做一些反馈,来告知当前组件什么时候准备好(实际情况可能是首屏数据加载好了啥的)
|
|
80
|
+
|
|
81
|
+
**Bar.vue**
|
|
82
|
+
|
|
83
|
+
```vue
|
|
84
|
+
<script lang="ts" setup>
|
|
85
|
+
import { ref } from 'vue'
|
|
86
|
+
import { useContext } from 'vue-presetup'
|
|
87
|
+
|
|
88
|
+
const context = useContext()
|
|
89
|
+
|
|
90
|
+
window.setTimeout(() => {
|
|
91
|
+
// 在 1500ms 后,这个组件准备好了!
|
|
92
|
+
context.resolve()
|
|
93
|
+
}, 1500)
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<template>
|
|
97
|
+
<div class="Bar">
|
|
98
|
+
<h1>Bar.vue</h1>
|
|
99
|
+
</div>
|
|
100
|
+
</template>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
4. 接下来,就可以实现在 /foo 中提前加载 /bar 对应的组件
|
|
104
|
+
|
|
105
|
+
**Foo.bar**
|
|
106
|
+
|
|
107
|
+
```vue
|
|
108
|
+
<script lang="ts" setup>
|
|
109
|
+
import { ref } from 'vue'
|
|
110
|
+
import { useSetupComponent } from 'vue-presetup'
|
|
111
|
+
import router from '../router'
|
|
112
|
+
import Bar from './Bar.vue'
|
|
113
|
+
|
|
114
|
+
const { setupComponent } = useSetupComponent()
|
|
115
|
+
|
|
116
|
+
const go = async () => {
|
|
117
|
+
// 等待组件加载完成
|
|
118
|
+
await setupComponent(Bar)
|
|
119
|
+
router.push('/bar')
|
|
120
|
+
}
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<template>
|
|
124
|
+
<div class="Foo">
|
|
125
|
+
<h1>Foo.vue</h1>
|
|
126
|
+
<button @click="go">Go</ElButton>
|
|
127
|
+
</div>
|
|
128
|
+
</template>
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 与 Transition、KeepAlive 一起使用
|
|
132
|
+
|
|
133
|
+
编辑上述示例中 **App.vue**
|
|
134
|
+
|
|
135
|
+
```vue
|
|
136
|
+
<script> /* ... */ </script>
|
|
137
|
+
<template>
|
|
138
|
+
<RouterView v-slot="{ Component }">
|
|
139
|
+
<PresetupView :active="Component" v-slot="{ Component: Component2 }">
|
|
140
|
+
<Transition name="fade" mode="out-in">
|
|
141
|
+
<KeepAlive include="foo">
|
|
142
|
+
<component :is="Component2"></component>
|
|
143
|
+
</KeepAlive>
|
|
144
|
+
</Transition>
|
|
145
|
+
</PresetupView>
|
|
146
|
+
</RouterView>
|
|
147
|
+
</template>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## API
|
|
151
|
+
|
|
152
|
+
### PresetupView
|
|
153
|
+
|
|
154
|
+
Type: Component
|
|
155
|
+
|
|
156
|
+
Props:
|
|
157
|
+
|
|
158
|
+
+ `active`: 期望渲染的 VNode
|
|
159
|
+
|
|
160
|
+
### useSetupComponent
|
|
161
|
+
|
|
162
|
+
Type: Hook
|
|
163
|
+
|
|
164
|
+
Return: { setupComponent }
|
|
165
|
+
|
|
166
|
+
> 这是一个 Vue3 Hook,因此只能在 setup 函数中使用
|
|
167
|
+
|
|
168
|
+
```vue
|
|
169
|
+
<script setup lang="ts">
|
|
170
|
+
import { useSetupComponent } from 'vue-presetup'
|
|
171
|
+
import Bar from './Bar.vue'
|
|
172
|
+
|
|
173
|
+
const { setupComponent } = useSetupComponent()
|
|
174
|
+
|
|
175
|
+
const next = async () => {
|
|
176
|
+
await setupComponent(Bar, /* props */)
|
|
177
|
+
}
|
|
178
|
+
</script>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
#### setupComponent
|
|
182
|
+
|
|
183
|
+
Params:
|
|
184
|
+
|
|
185
|
+
+ Component
|
|
186
|
+
|
|
187
|
+
需要加载的组件对象,这个组件必须有 `name` 属性,否则会收到一个被 reject 的 Promise;
|
|
188
|
+
|
|
189
|
+
同一组件(相同的 `name` ,后同)可预加载的数量是有限的(一个),在后面预加载的组件会覆盖之前已加载过的同一组件。
|
|
190
|
+
|
|
191
|
+
+ props
|
|
192
|
+
|
|
193
|
+
传递给组件的参数,在 Vue 组件中是区分 Props 和 Attrs 的,因此在组件内部接收这个参数时候需要声明 Props,否则会给 Vue 理解为 Attrs,如下面这个示例,在 Comp.vue 中,p1,p2 在组件的 props 中,p3 是 attrs 中:
|
|
194
|
+
|
|
195
|
+
Comp.vue
|
|
196
|
+
|
|
197
|
+
```vue
|
|
198
|
+
<script setup lang="ts">
|
|
199
|
+
const props = deineProps(['p1', 'p2'])
|
|
200
|
+
</script>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Foo.vue
|
|
204
|
+
|
|
205
|
+
```vue
|
|
206
|
+
<script setup lang="ts">
|
|
207
|
+
import { useSetupComponent } from 'vue-presetup'
|
|
208
|
+
import Comp from './Comp.vue'
|
|
209
|
+
|
|
210
|
+
const { setupComponent } = useSetupComponent()
|
|
211
|
+
|
|
212
|
+
const next = async () => {
|
|
213
|
+
await setupComponent(Comp, {
|
|
214
|
+
p1: 1,
|
|
215
|
+
p2: 2,
|
|
216
|
+
p3: 3
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
</script>
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### useContext
|
|
223
|
+
|
|
224
|
+
Type: Hook
|
|
225
|
+
|
|
226
|
+
Return Context
|
|
227
|
+
|
|
228
|
+
> 这是一个 Vue3 Hook,因此只能在 setup 函数中使用
|
|
229
|
+
|
|
230
|
+
用在可能会被预加载的组件中,如基础使用示例的 Bar.vue
|
|
231
|
+
|
|
232
|
+
#### Context
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
interface UseContextResult {
|
|
236
|
+
// 在当前组件“准备好”调用,以告知预加载完成。如当前组件不是通过预加载,此方法什么都不会发生。
|
|
237
|
+
resolve: Callback
|
|
238
|
+
// 在当前组件中预加载发生错误时候调用。如当前组件不是通过预加载,此方法什么都不会发生。
|
|
239
|
+
reject: Callback
|
|
240
|
+
// 获取在 setupComponent 中传递的参数。如当前组件不是通过预加载,此方法没有办法获取任何值,除非你提供了 defaultValue
|
|
241
|
+
get<R = unknown>(key: string): R | undefined
|
|
242
|
+
get<R = unknown>(key: string, defaultValue: R): R
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### removeComponent
|
|
247
|
+
|
|
248
|
+
将一个已预加载的组件移除
|
|
249
|
+
|
|
250
|
+
Params:
|
|
251
|
+
|
|
252
|
+
+ `name`: 被移除组件的 name
|
|
253
|
+
|
|
254
|
+
```vue
|
|
255
|
+
<script setup lang="ts">
|
|
256
|
+
import { useSetupComponent, removeComponent } from 'vue-presetup'
|
|
257
|
+
import Bar from './Bar.vue'
|
|
258
|
+
|
|
259
|
+
const { setupComponent } = useSetupComponent()
|
|
260
|
+
|
|
261
|
+
const next = async () => {
|
|
262
|
+
await setupComponent(Bar, /* props */)
|
|
263
|
+
removeComponent(Bar.name)
|
|
264
|
+
}
|
|
265
|
+
</script>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
[MIT](https://github.com/haiya6/vite-plugin-html-resolve-alias/blob/main/LICENSE)
|
package/package.json
CHANGED
package/pre/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { h, render, defineComponent, getCurrentInstance } from 'vue';
|
|
2
|
+
const COMPONENT_KEPT_ALIVE = 1 << 9;
|
|
3
|
+
const CONTEXT_KEY = Symbol('VUE_PRESETUP_CONTEXT_KEY');
|
|
4
|
+
const NOOP = () => { };
|
|
5
|
+
const cache = new Map();
|
|
6
|
+
export const PresetupView = defineComponent({
|
|
7
|
+
name: 'PresetupView',
|
|
8
|
+
props: {
|
|
9
|
+
active: {
|
|
10
|
+
type: Object
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
setup(props, { slots }) {
|
|
14
|
+
const instance = getCurrentInstance();
|
|
15
|
+
// @ts-expect-error
|
|
16
|
+
instance.ctx.activate = (vnode, container, anchor) => {
|
|
17
|
+
container.insertBefore(vnode.component.subTree.el, anchor);
|
|
18
|
+
};
|
|
19
|
+
return () => {
|
|
20
|
+
const { active } = props;
|
|
21
|
+
const normalizeSlot = (vnode) => {
|
|
22
|
+
if (slots.default) {
|
|
23
|
+
return slots.default({ Component: vnode });
|
|
24
|
+
}
|
|
25
|
+
return vnode;
|
|
26
|
+
};
|
|
27
|
+
if (!active)
|
|
28
|
+
return null;
|
|
29
|
+
const name = typeof active.type === 'object' ? active.type.name : '';
|
|
30
|
+
if (!name || !cache.has(name))
|
|
31
|
+
return normalizeSlot(active);
|
|
32
|
+
const data = cache.get(name);
|
|
33
|
+
cache.delete(name);
|
|
34
|
+
return normalizeSlot(data.vnode);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
export function useContext() {
|
|
39
|
+
const instance = getCurrentInstance();
|
|
40
|
+
if (!instance)
|
|
41
|
+
console.warn(`[vue-presetup] useContext() called without active instance.`);
|
|
42
|
+
// @ts-expect-error
|
|
43
|
+
const context = instance?.vnode?.[CONTEXT_KEY] ?? {};
|
|
44
|
+
const get = (key, defaultValue) => {
|
|
45
|
+
const props = context?.props ?? {};
|
|
46
|
+
if ((key in props) && props[key] !== void 0)
|
|
47
|
+
return props[key];
|
|
48
|
+
else
|
|
49
|
+
return defaultValue;
|
|
50
|
+
};
|
|
51
|
+
return {
|
|
52
|
+
resolve: context?.resolve ?? NOOP,
|
|
53
|
+
reject: context?.resolve ?? NOOP,
|
|
54
|
+
get
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function useSetupComponent() {
|
|
58
|
+
const instance = getCurrentInstance();
|
|
59
|
+
if (!instance)
|
|
60
|
+
throw new Error(`[vue-presetup] useSetupComponent() called without active instance.`);
|
|
61
|
+
return {
|
|
62
|
+
setupComponent(component, props = {}) {
|
|
63
|
+
if (!component.name)
|
|
64
|
+
return Promise.reject(new Error(`[vue-presetup] The component must have \`name\` attribute.`));
|
|
65
|
+
return new Promise((resolve, reject) => {
|
|
66
|
+
const container = window.document.createElement('div');
|
|
67
|
+
const vnode = h(component, props);
|
|
68
|
+
// bind appContext
|
|
69
|
+
vnode.appContext = instance.appContext;
|
|
70
|
+
// bind presetup context
|
|
71
|
+
const context = { props, resolve, reject };
|
|
72
|
+
// @ts-expect-error
|
|
73
|
+
vnode[CONTEXT_KEY] = context;
|
|
74
|
+
if (cache.has(component.name))
|
|
75
|
+
removeComponent(component.name);
|
|
76
|
+
cache.set(component.name, { vnode, container });
|
|
77
|
+
render(vnode, container);
|
|
78
|
+
vnode.shapeFlag |= COMPONENT_KEPT_ALIVE;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function removeComponent(name) {
|
|
84
|
+
const data = cache.get(name);
|
|
85
|
+
if (!data) {
|
|
86
|
+
if (process.env.NODE_ENV === 'development') {
|
|
87
|
+
console.warn(`[vue-presetup] No component with name \`${name}\` found.`);
|
|
88
|
+
}
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
render(null, data.container);
|
|
92
|
+
cache.delete(name);
|
|
93
|
+
}
|