Appearance
思路
- 把不需要登录的请求抽成“公共初始化(public bootstrap)”,在应用进入时就触发(只触发一次)。
- 把需要登录且命中鉴权路由时才需要的数据,保留在你现有的
watch分支里,但也确保只跑一次。 - 用模块级 once 标记避免多组件重复调用;必要时可加
online事件进行弱网重试(可选)。
需要调整的代码(仅片段)
放在你这个 composable 文件里,按位置插入即可;复杂逻辑我已在上一行加中文注释。
1)导入(在文件顶部补充)
ts
// 复杂逻辑:公私两段初始化需要生命周期/事件
import { watch, onMounted } from "vue";2)模块级 once 标记(在文件顶部、export function useAppBootstrap 外面)
ts
// 复杂逻辑:模块级“只执行一次”标记,防止多组件重复初始化
let __BOOT_PUBLIC_ONCE__ = false;
let __BOOT_PRIVATE_ONCE__ = false;3)在 useAppBootstrap 内部新增“公共初始化”方法与调用(建议贴在你定义完各个 store 之后)
ts
// 复杂逻辑:公共初始化——无需登录的数据,进入站点即预取,且只执行一次
const bootstrapPublic = async () => {
if (__BOOT_PUBLIC_ONCE__) return;
__BOOT_PUBLIC_ONCE__ = true;
await Promise.all([
// ⬇️ 把“无需 token 的初始化”放这里;按你项目实际取舍
country.init().catch(() => {}),
exchange.init({ keyword: "" }).catch(() => {}),
marketCalendar.init().catch(() => {}),
// 如果 server.init() 不依赖用户态,也可放到公共初始化
server.init().catch(() => {}),
// …你还有其它完全公开的数据,也可加进来
]);
};ts
// 复杂逻辑:进入应用即触发公共初始化(SSR/CSR 兼容性考虑,放到 onMounted 最稳妥)
onMounted(() => {
bootstrapPublic();
});4)给你原来的 watch 回调加“私有初始化只跑一次”的保护
ts
// 复杂逻辑:仅在“已登录 && 路由需要鉴权 && 未跑过私有初始化”时执行
if (ok && need && !__BOOT_PRIVATE_ONCE__) {
__BOOT_PRIVATE_ONCE__ = true;
await Promise.all([
user.fetchUserInfo().catch(() => {}),
account.init({ withCategory: true, withExternal: true }).catch(() => {}),
leverage.init().catch(() => {}),
accountCurrency.init().catch(() => {}),
externalServer.init().catch(() => {}),
payChannel.init({ opt_type: "deposit" }).catch(() => {}),
verifyStore.initVerifyInfo().catch(() => {}),
inviteStore.getInviteGroupOptions().catch(() => {}),
// ⚠️ server.init()/country/exchange/marketCalendar 已在公共初始化里跑过的可移除,避免重复
]);
}可选增强
A. 弱网场景“上线即补跑”
ts
// 复杂逻辑:若用户初次进入时离线,恢复网络后自动补跑公共初始化(最多触发一次)
onMounted(() => {
const handler = () => {
bootstrapPublic();
};
window.addEventListener("online", handler, { once: true });
});B. Store 级幂等(示例片段,放到各自的 Pinia store 里)
ts
// 复杂逻辑:在每个 init() 入口加幂等保护,彻底避免重复请求
if ((this as any)._inited) return;
(this as any)._inited = true;小结
- 公共数据:
onMounted里 立刻预取一次,全站可复用。 - 私有数据:沿用你的
watch,但加一次性保护,避免路由/状态抖动造成的重复请求。 - 若存在弱网:监听
online事件,一次性补跑公共初始化即可。
title: useAppBootstrap 放在哪?给你一份可直接复制的完整文件(含用法) date: 2025-09-06 tags:
- Vue3
- Vite
- Pinia
- composable categories:
- 前端工程化
位置推荐
把文件放到:
src/composables/useAppBootstrap.ts理由:可复用的启动逻辑属于“组合式函数(composable)”,与业务无关的初始化也方便在这里统一管理。
完整代码(可直接替换 src/composables/useAppBootstrap.ts)
ts
// src/composables/useAppBootstrap.ts
import { onMounted, watch } from "vue";
import { useRouter } from "vue-router";
import {
useAuthStore,
useUserStore,
useAccountStore,
useServerStore,
useLeverageStore,
useAccountCurrencyStore,
useCountryStore,
useExchangeStore,
useExternalServerStore,
useMarketCalendarStore,
usePayChannelStore,
} from "@/store";
import { useVerifyStore } from "@/store/verify";
import { useInviteStore } from "@/store/invite";
// 复杂逻辑:模块级“只执行一次”标记,避免多组件/多次进入页面重复请求
let __BOOT_PUBLIC_ONCE__ = false;
let __BOOT_PRIVATE_ONCE__ = false;
export function useAppBootstrap() {
const router = useRouter();
const auth = useAuthStore();
const user = useUserStore();
const account = useAccountStore();
const server = useServerStore();
const leverage = useLeverageStore();
const accountCurrency = useAccountCurrencyStore();
const country = useCountryStore();
const exchange = useExchangeStore();
const externalServer = useExternalServerStore();
const marketCalendar = useMarketCalendarStore();
const payChannel = usePayChannelStore();
const verifyStore = useVerifyStore();
const inviteStore = useInviteStore();
// 复杂逻辑:公共初始化——无需登录可获取的数据,进入站点即预取,且只执行一次
const bootstrapPublic = async () => {
if (__BOOT_PUBLIC_ONCE__) return;
__BOOT_PUBLIC_ONCE__ = true;
await Promise.all([
// ⬇️ 放“无需 token”的初始化
country.init().catch(() => {}),
exchange.init({ keyword: "" }).catch(() => {}),
marketCalendar.init().catch(() => {}),
server.init().catch(() => {}),
// 如有其它公开数据,也可加在这里
]);
};
// 复杂逻辑:进入应用即触发公共初始化;若首次访问时离线,恢复网络后补跑一次(仅一次)
onMounted(() => {
bootstrapPublic();
const handler = () => {
bootstrapPublic();
};
window.addEventListener("online", handler, { once: true });
});
// 复杂逻辑:仅在“已登录 && 目标路由需要鉴权 && 未跑过私有初始化”时执行一次性私有初始化
watch(
[
() => auth.isAuthenticated,
() => router.currentRoute.value.meta?.requiresAuth,
],
async ([ok, need]) => {
if (ok && need && !__BOOT_PRIVATE_ONCE__) {
__BOOT_PRIVATE_ONCE__ = true;
await Promise.all([
user.fetchUserInfo().catch(() => {}),
account
.init({ withCategory: true, withExternal: true })
.catch(() => {}),
leverage.init().catch(() => {}),
accountCurrency.init().catch(() => {}),
externalServer.init().catch(() => {}),
payChannel.init({ opt_type: "deposit" }).catch(() => {}),
verifyStore.initVerifyInfo().catch(() => {}),
inviteStore.getInviteGroupOptions().catch(() => {}),
// ⚠️ 已在公共初始化中完成的 server/country/exchange/marketCalendar 无需重复
]);
}
},
{ immediate: true }
);
}
// (可选)测试/登出时重置标记
export function __resetBootstrapFlagsForTestOnly() {
__BOOT_PUBLIC_ONCE__ = false;
__BOOT_PRIVATE_ONCE__ = false;
}如何使用(最小改动)
你不需要在每个页面手动调用,只需在 App.vue 顶层调用一次即可。
在 src/App.vue 的 <script setup> 里加入一行(增量修改):
ts
// 复杂逻辑:顶层调用一次,注册 watch + onMounted,即可全局生效
import { useAppBootstrap } from "@/composables/useAppBootstrap";
useAppBootstrap();如果你的项目还没在
main.ts里安装 Pinia/Router,请确认已安装(一般你已经有了):
ts
// 仅供核对:main.ts 应该已包含这些
// import { createApp } from 'vue'
// import { createPinia } from 'pinia'
// import router from './router'
// import App from './App.vue'
// createApp(App).use(createPinia()).use(router).mount('#app')小结
- 文件放在:
src/composables/useAppBootstrap.ts。 - 在
App.vue顶层调用一次useAppBootstrap()即全局生效。 - 公共初始化:站点进入即跑;私有初始化:登录且命中鉴权路由时只跑一次。