Skip to content

思路

  • 不需要登录的请求抽成“公共初始化(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() 即全局生效。
  • 公共初始化:站点进入即跑;私有初始化:登录且命中鉴权路由时只跑一次。

本站总访问