Skip to content

useAppInit 应用初始化

介绍

useAppInit 是应用级别的初始化组合式函数,负责在应用启动时根据不同平台执行相应的初始化流程。它是整个应用的启动入口,处理租户ID获取、用户自动登录、WebSocket连接等核心初始化逻辑。

该组合函数采用单例模式设计,确保初始化流程在整个应用生命周期中只执行一次。同时提供 Promise 缓存机制,避免并发调用导致的重复初始化问题。初始化过程中会根据当前运行平台自动选择合适的策略,实现多平台的统一入口和差异化处理。

核心特性:

  • 多平台适配 - 根据运行平台自动选择初始化策略(APP、H5、小程序)
  • 租户ID管理 - 小程序和公众号H5自动通过 appid 获取租户ID
  • 自动登录 - 支持小程序静默登录和微信H5授权登录
  • WebSocket初始化 - 用户登录后自动建立WebSocket实时连接
  • 单例模式 - 确保初始化流程只执行一次,避免重复调用
  • Promise缓存 - 并发调用时复用同一个初始化Promise
  • 错误容错 - 登录失败不影响应用启动,租户ID失败为致命错误
  • 状态追踪 - 提供 isInitialized 和 isInitializing 状态

架构设计

整体架构

text
┌─────────────────────────────────────────────────────────────────────────┐
│                           应用启动层                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                      App.vue (onLaunch)                                  │
│                             │                                            │
│                    featureStore.initFeatures()                          │
│                             │                                            │
│                        initializeApp()                                   │
└─────────────────────────────┬───────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────────────┐
│                         初始化控制层                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                        useAppInit Composable                             │
│   ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐   │
│   │ 单例检查     │  │ 平台判断     │  │ Promise缓存 │  │ 状态管理     │   │
│   │(singleton)  │  │(platform)   │  │(initPromise)│  │ (states)    │   │
│   └─────────────┘  └─────────────┘  └─────────────┘  └─────────────┘   │
└─────────────────────────────┬───────────────────────────────────────────┘

          ┌───────────────────┼───────────────────┐
          │                   │                   │
          ▼                   ▼                   ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│   APP/普通H5     │ │  小程序平台      │ │  公众号H5        │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ • 跳过租户ID     │ │ • 获取租户ID     │ │ • 获取租户ID     │
│ • 检查Token     │ │ • 静默登录       │ │ • 授权登录       │
│ • 获取用户信息   │ │ • 获取用户信息   │ │ • 获取用户信息   │
│ • WebSocket    │ │ • WebSocket     │ │ • WebSocket     │
└─────────────────┘ └─────────────────┘ └─────────────────┘


┌─────────────────────────────────────────────────────────────────────────┐
│                          依赖服务层                                       │
├─────────────────────────────────────────────────────────────────────────┤
│  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐ │
│  │ userStore│  │   cache  │  │  webSocket│  │homeApi   │  │ platform │ │
│  └──────────┘  └──────────┘  └──────────┘  └──────────┘  └──────────┘ │
└─────────────────────────────────────────────────────────────────────────┘

初始化流程状态机

text
┌──────────────────────────────────────────────────────────────────────┐
│                        初始化状态流转                                  │
├──────────────────────────────────────────────────────────────────────┤
│                                                                      │
│    ┌──────────────────┐                                              │
│    │   初始状态        │ isInitialized = false                       │
│    │                  │ isInitializing = false                       │
│    │                  │ initPromise = null                           │
│    └────────┬─────────┘                                              │
│             │                                                        │
│        initializeApp()                                               │
│             │                                                        │
│             ▼                                                        │
│    ┌──────────────────┐                                              │
│    │ 已初始化?         │──── 是 ────→ 直接返回 Promise.resolve()      │
│    └────────┬─────────┘                                              │
│             │ 否                                                      │
│             ▼                                                        │
│    ┌──────────────────┐                                              │
│    │ 有缓存Promise?    │──── 是 ────→ 返回缓存的 Promise              │
│    └────────┬─────────┘                                              │
│             │ 否                                                      │
│             ▼                                                        │
│    ┌──────────────────┐                                              │
│    │   初始化中        │ isInitialized = false                       │
│    │                  │ isInitializing = true                        │
│    │                  │ initPromise = new Promise()                  │
│    └────────┬─────────┘                                              │
│             │                                                        │
│        执行初始化流程                                                  │
│             │                                                        │
│             ▼                                                        │
│    ┌──────────────────┐                                              │
│    │   初始化完成      │ isInitialized = true                        │
│    │                  │ isInitializing = false                       │
│    │                  │ initPromise = null                           │
│    └──────────────────┘                                              │
│                                                                      │
└──────────────────────────────────────────────────────────────────────┘

核心模块结构

模块说明关键函数
状态管理管理初始化状态isInitialized, isInitializing, initPromise
平台判断判断当前运行平台shouldInitialize(), getCurrentPlatformName()
租户管理获取和存储租户IDgetTenantIdByAppid(), cache.set()
登录管理处理不同平台的登录loginWithMiniapp(), loginWithMp()
WebSocket初始化实时连接initializeWebSocket()
错误处理区分致命错误和非致命错误try-catch 错误分类

平台初始化策略

不同平台的初始化行为不同,组件内部通过 shouldInitialize() 方法判断是否需要执行完整初始化流程:

平台租户ID初始化自动登录WebSocket说明
APP否 跳过否 手动登录是 登录后连接无需租户ID,用户手动登录
普通H5否 跳过否 手动登录是 登录后连接非公众号H5无需初始化
微信公众号H5是 执行是 授权登录是 登录后连接检测URL中的code和state
支付宝H5是 执行否 手动登录是 登录后连接仅获取租户ID
微信小程序是 执行是 静默登录是 登录后连接wx.login获取code登录
支付宝小程序是 执行是 静默登录是 登录后连接my.getAuthCode登录
百度小程序是 执行是 静默登录是 登录后连接swan.login登录
抖音小程序是 执行是 静默登录是 登录后连接tt.login登录
QQ小程序是 执行是 静默登录是 登录后连接qq.login登录

平台判断逻辑

typescript
// 内部实现的平台判断
const shouldInitialize = (): boolean => {
  // APP环境不需要初始化租户ID
  if (isApp) return false

  // 微信/支付宝公众号H5需要初始化
  if (isWechatOfficialH5 || isAlipayOfficialH5) return true

  // 小程序环境需要初始化
  if (isMp) return true

  // 普通H5不需要初始化
  return false
}

平台名称映射

组件内部会根据平台返回友好的平台名称用于日志输出:

typescript
const getCurrentPlatformName = (): string => {
  if (isApp) return 'APP'
  if (isWechatOfficialH5) return '微信公众号H5'
  if (isAlipayOfficialH5) return '支付宝H5'
  if (isMp) {
    switch (platform) {
      case 'mp-weixin': return '微信小程序'
      case 'mp-alipay': return '支付宝小程序'
      case 'mp-baidu': return '百度小程序'
      case 'mp-toutiao': return '字节跳动小程序'
      case 'mp-qq': return 'QQ小程序'
      default: return '小程序'
    }
  }
  return platform === 'h5' ? '普通H5' : '未知平台'
}

基本用法

在 App.vue 中使用

应用初始化通常在 App.vueonLaunch 生命周期中调用:

vue
<script setup lang="ts">
import { initializeApp } from '@/composables/useAppInit'
import { useFeatureStore } from '@/stores/modules/feature'

const featureStore = useFeatureStore()

onLaunch(async () => {
  // 先初始化系统功能配置(WebSocket需要依赖此配置)
  await featureStore.initFeatures()
  // 开始应用初始化
  await initializeApp()
})

onShow(() => {
  console.log('应用显示')
})

onHide(() => {
  console.log('应用隐藏')
})
</script>

等待初始化完成

在需要确保初始化完成后再执行的场景中使用 waitForInit

typescript
import { waitForInit } from '@/composables/useAppInit'

// 在页面或组件中等待初始化完成
const loadData = async () => {
  // 等待应用初始化完成(最多等待10秒)
  await waitForInit(10000)

  // 初始化完成后执行业务逻辑
  const data = await fetchUserData()
}

使用组合函数形式

typescript
import { useAppInit } from '@/composables/useAppInit'

const { initializeApp, waitForInit } = useAppInit()

// 手动触发初始化
await initializeApp()

// 或等待初始化完成
await waitForInit()

使用全局导出方法

除了组合函数形式,还可以直接导入全局方法:

typescript
import { initializeApp, waitForInit } from '@/composables/useAppInit'
import globalAppInit from '@/composables/useAppInit'

// 方式1: 直接使用导出的方法
await initializeApp()

// 方式2: 使用默认导出的全局实例
await globalAppInit.initializeApp()

初始化流程详解

完整初始化流程

text
应用启动 (onLaunch)


判断平台类型 (shouldInitialize)

    ├─── APP/普通H5 ──────────────────┐
    │                                  │
    │    检查是否有Token               │
    │         │                        │
    │    ├─ 有Token → 获取用户信息     │
    │    │              │              │
    │    │         初始化WebSocket     │
    │    │                             │
    │    └─ 无Token → 跳过登录         │
    │                                  │
    │    设置 isInitialized = true    │
    │                                  ▼
    │                            初始化完成

    └─── 小程序/公众号H5 ─────────────┐

         第一步: 获取租户ID            │
              │                        │
         ├─ 成功 → 存储到Cache         │
         │                             │
         └─ 失败 → 抛出致命错误        │

         第二步: 检查Token/自动登录    │
              │                        │
         ├─ 已有Token → 获取用户信息   │
         │                             │
         ├─ 小程序 → 静默登录          │
         │              │              │
         │    ├─ 成功 → 获取用户信息   │
         │    └─ 失败 → 允许继续启动   │
         │                             │
         └─ 微信H5 + code → 授权登录   │
                    │                  │
              ├─ 成功 → 清除URL参数    │
              │         获取用户信息   │
              │                        │
              └─ 失败 → 抛出错误       │

         登录成功后初始化WebSocket     │
         设置 isInitialized = true    │

                                 初始化完成

错误处理流程

text
┌─────────────────────────────────────────────────────────────────────────┐
│                           错误处理流程                                    │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│    初始化过程中发生错误                                                   │
│              │                                                          │
│              ▼                                                          │
│    ┌──────────────────┐                                                 │
│    │ 错误类型判断      │                                                 │
│    └────────┬─────────┘                                                 │
│             │                                                           │
│    ┌────────┴────────┐                                                  │
│    │                 │                                                  │
│    ▼                 ▼                                                  │
│ ┌────────────┐  ┌────────────┐                                          │
│ │ 租户ID错误  │  │ 其他错误    │                                          │
│ │ (致命错误)  │  │ (非致命)    │                                          │
│ └─────┬──────┘  └─────┬──────┘                                          │
│       │               │                                                 │
│       ▼               ▼                                                 │
│   抛出异常        记录警告日志                                            │
│   应用终止        isInitialized = true                                  │
│                  应用继续运行                                            │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

租户ID获取流程

租户ID是多租户系统的核心标识,通过平台的 appid 获取:

typescript
// 内部实现
console.log(`[应用初始化] ${platformName}开始获取租户ID...`)

// 获取当前平台的appid
const appid = userStore.getCurrentPlatformAppid()

// 调用API获取租户ID
const [err, data] = await getTenantIdByAppid(appid)

if (!err && data) {
  // 存储租户ID到缓存
  cache.set(TENANT_STORAGE_KEY, data)
  console.log(`[应用初始化] ${platformName}租户ID获取成功: ${data}`)
} else {
  // 租户ID获取失败是致命错误
  const error = err || new Error('获取租户ID失败:返回数据为空')
  console.error(`[应用初始化] ${platformName}获取租户ID失败:`, error.message)
  throw error
}

小程序静默登录

小程序平台会自动执行静默登录,无需用户手动操作:

typescript
// 内部实现逻辑
if (isMp) {
  console.log(`[应用初始化] ${platformName}开始静默登录...`)

  // 调用 userStore 的小程序登录方法
  const [loginErr] = await userStore.loginWithMiniapp()

  if (!loginErr) {
    console.log(`[应用初始化] ${platformName}静默登录成功`)

    // 获取用户信息
    userStore.fetchUserInfo()
      .then(() => {
        console.log(`[应用初始化] ${platformName}用户信息获取成功`)
        // 用户信息获取成功后初始化WebSocket
        initializeWebSocket()
      })
      .catch((err) => {
        console.warn(`[应用初始化] ${platformName}获取用户信息失败:`, err.message)
      })
  } else {
    // 静默登录失败不抛出错误,允许用户手动登录
    console.warn(`[应用初始化] ${platformName}静默登录失败:`, loginErr.message)
  }
}

微信公众号H5授权登录

微信公众号H5会检测URL中的授权回调参数:

typescript
// 内部实现逻辑
if (isWechatOfficialH5) {
  // 解析URL参数
  const urlParams = new URLSearchParams(window.location.search)
  const code = urlParams.get('code')
  const state = urlParams.get('state')

  if (code && state) {
    console.log(`[应用初始化] ${platformName}检测到授权回调,开始登录...`)

    // 使用code和state进行登录
    const [loginErr] = await userStore.loginWithMp({ code, state })

    if (!loginErr) {
      console.log(`[应用初始化] ${platformName}授权登录成功`)

      // 清除URL中的授权参数,避免刷新页面重复登录
      const newUrl = window.location.pathname + window.location.hash
      window.history.replaceState({}, document.title, newUrl)

      // 获取用户信息
      userStore.fetchUserInfo()
        .then(() => {
          console.log(`[应用初始化] ${platformName}用户信息获取成功`)
          initializeWebSocket()
        })
        .catch((err) => {
          console.warn(`[应用初始化] ${platformName}获取用户信息失败:`, err.message)
        })
    } else {
      // 授权登录失败是致命错误
      console.error(`[应用初始化] ${platformName}授权登录失败:`, loginErr.message)
      throw loginErr
    }
  } else {
    console.log(`[应用初始化] ${platformName}无授权回调参数,跳过自动登录`)
  }
}

WebSocket初始化

登录成功后会自动初始化WebSocket连接:

typescript
// 内部实现
const initializeWebSocket = () => {
  // 所有检查逻辑都在 webSocket.initialize() 内部处理
  const wsInstance = webSocket.initialize(undefined, {
    onConnected: () => {
      // WebSocket连接建立成功
    },
    onDisconnected: (code, reason) => {
      // WebSocket连接断开
    },
    onError: (error) => {
      console.error('[应用初始化] WebSocket连接错误', error)
    },
    onMessage: (data) => {
      // 收到WebSocket消息
      // 可以在这里添加应用级别的消息处理逻辑
    },
  })

  // 如果初始化成功,建立连接
  if (wsInstance) {
    webSocket.connect()
  }
}

内部实现

单例模式与Promise缓存

typescript
/** 是否已完成初始化 */
const isInitialized = ref(false)

/** 是否正在初始化中 */
const isInitializing = ref(false)

/** 初始化Promise缓存 - 确保同一时间只有一个初始化过程 */
let initPromise: Promise<void> | null = null

const initializeApp = async (): Promise<void> => {
  const platformName = getCurrentPlatformName()

  // 单例检查:已初始化则直接返回
  if (isInitialized.value) {
    console.log(`[应用初始化] ${platformName}环境已完成,跳过重复初始化`)
    return Promise.resolve()
  }

  // Promise缓存:正在初始化则返回缓存的Promise
  if (initPromise) {
    console.log(`[应用初始化] ${platformName}环境正在初始化中,等待完成...`)
    return initPromise
  }

  // 创建新的初始化Promise并缓存
  isInitializing.value = true
  initPromise = (async () => {
    // ... 初始化逻辑
  })()

  return initPromise
}

APP/普通H5平台处理

typescript
// 不需要租户ID初始化的平台,但仍需检查用户状态
if (!shouldInitialize()) {
  console.log(`[应用初始化] ${platformName}环境无需租户ID初始化`)

  const userStore = useUserStore()

  // 检查是否有token,有则获取用户信息
  if (userStore.token) {
    console.log(`[应用初始化] ${platformName}检测到token,获取用户信息`)
    try {
      userStore.fetchUserInfo().then(() => {
        console.log(`[应用初始化] ${platformName}用户信息获取成功`)
        // 用户信息获取成功后初始化WebSocket
        initializeWebSocket()
      })
    } catch (err) {
      console.warn(`[应用初始化] ${platformName}获取用户信息失败:`, err.message)
    }
  } else {
    console.log(`[应用初始化] ${platformName}无token,跳过用户信息获取`)
  }

  isInitialized.value = true
  return Promise.resolve()
}

waitForInit 实现

typescript
const waitForInit = async (timeout: number = 10000): Promise<void> => {
  // 已初始化直接返回
  if (isInitialized.value) return Promise.resolve()

  // 不需要初始化的平台直接标记完成
  if (!shouldInitialize()) {
    isInitialized.value = true
    return Promise.resolve()
  }

  // 有缓存Promise则等待,带超时处理
  if (initPromise) {
    await Promise.race([
      initPromise,
      new Promise((_, reject) =>
        setTimeout(() => reject(new Error('等待初始化超时')), timeout)
      ),
    ])
    return
  }

  // 否则触发初始化
  await initializeApp()
}

错误分类处理

typescript
catch (error) {
  console.error(`[应用初始化] ${platformName}环境初始化失败:`, error)

  // 根据错误类型决定是否抛出异常
  if (error instanceof Error && error.message.includes('租户ID')) {
    // 租户ID获取失败是致命错误,抛出异常
    throw error
  } else {
    // 登录失败不是致命错误,只记录日志,允许应用继续启动
    console.warn(`[应用初始化] ${platformName}登录失败,但允许应用继续启动`)
    isInitialized.value = true
  }
} finally {
  isInitializing.value = false
  initPromise = null
}

API

useAppInit 返回值

方法说明参数返回值
initializeApp执行应用初始化-Promise<void>
waitForInit等待初始化完成timeout?: numberPromise<void>

导出的全局方法

方法说明参数返回值
initializeApp全局初始化方法-Promise<void>
waitForInit全局等待方法timeout?: numberPromise<void>

waitForInit 参数

参数说明类型默认值
timeout等待超时时间(毫秒)number10000

类型定义

typescript
/**
 * useAppInit 组合函数返回类型
 */
interface UseAppInitReturn {
  /** 执行应用初始化 */
  initializeApp: () => Promise<void>
  /** 等待初始化完成 */
  waitForInit: (timeout?: number) => Promise<void>
}

/**
 * useAppInit 组合函数
 */
declare function useAppInit(): UseAppInitReturn

/**
 * 全局初始化方法
 */
declare const initializeApp: () => Promise<void>

/**
 * 全局等待初始化方法
 */
declare const waitForInit: (timeout?: number) => Promise<void>

/**
 * 默认导出的全局实例
 */
declare const globalAppInit: UseAppInitReturn
export default globalAppInit

内部状态

组件内部维护以下状态,用于控制初始化流程:

状态说明类型初始值
isInitialized是否已完成初始化Ref<boolean>false
isInitializing是否正在初始化中Ref<boolean>false
initPromise初始化Promise缓存Promise<void> | nullnull

状态流转

text
初始状态:
  isInitialized = false
  isInitializing = false
  initPromise = null

调用 initializeApp():
  isInitialized = false
  isInitializing = true
  initPromise = Promise

初始化完成:
  isInitialized = true
  isInitializing = false
  initPromise = null

最佳实践

1. 配合功能配置使用

在初始化应用前,先加载系统功能配置:

vue
<script setup lang="ts">
import { initializeApp } from '@/composables/useAppInit'
import { useFeatureStore } from '@/stores/modules/feature'

const featureStore = useFeatureStore()

onLaunch(async () => {
  try {
    // 先加载功能配置(WebSocket等功能依赖此配置)
    await featureStore.initFeatures()
    // 再执行应用初始化
    await initializeApp()
    console.log('应用初始化完成')
  } catch (error) {
    console.error('应用初始化失败:', error)
  }
})
</script>

2. 在路由守卫中使用

确保页面加载前初始化完成:

typescript
// router/guards.ts
import { waitForInit } from '@/composables/useAppInit'

export const beforeEach = async (to, from) => {
  // 等待应用初始化完成
  try {
    await waitForInit(5000)
  } catch (error) {
    console.error('应用初始化超时')
    // 可以选择跳转到错误页面
    return { name: 'error', query: { msg: '初始化超时' } }
  }

  // 继续路由逻辑
  return true
}

3. 在请求拦截器中使用

确保请求发送前初始化完成,保证租户ID已获取:

typescript
// 在 useHttp 中的实现
import { waitForInit } from '@/composables/useAppInit'

const request = async (config) => {
  // 等待应用初始化完成(确保租户ID已获取)
  await waitForInit()

  // 获取租户ID并添加到请求头
  const tenantId = cache.get(TENANT_STORAGE_KEY)
  if (tenantId) {
    config.header['tenant-id'] = tenantId
  }

  // 发送请求
  return http.request(config)
}

4. 处理初始化失败

根据错误类型分别处理:

typescript
onLaunch(async () => {
  try {
    await initializeApp()
  } catch (error) {
    // 租户ID获取失败是致命错误
    if (error.message.includes('租户ID')) {
      uni.showModal({
        title: '初始化失败',
        content: '无法获取应用配置,请检查网络后重试',
        showCancel: false,
        success: () => {
          // 可以选择退出应用或重试
          // #ifdef APP-PLUS
          plus.runtime.quit()
          // #endif
        }
      })
    } else {
      // 其他错误记录日志,应用继续运行
      console.error('初始化异常:', error)
    }
  }
})

5. 在页面中确保初始化完成

vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { waitForInit } from '@/composables/useAppInit'

const loading = ref(true)
const userData = ref(null)

onMounted(async () => {
  try {
    // 确保初始化完成
    await waitForInit()

    // 加载页面数据
    userData.value = await fetchUserData()
  } catch (error) {
    uni.showToast({ title: '加载失败', icon: 'error' })
  } finally {
    loading.value = false
  }
})
</script>

6. 调试初始化流程

通过控制台日志追踪初始化流程:

typescript
// 初始化日志示例
// [应用初始化] 微信小程序环境开始初始化...
// [应用初始化] 微信小程序开始获取租户ID...
// [应用初始化] 微信小程序租户ID获取成功: 1
// [应用初始化] 微信小程序开始静默登录...
// [应用初始化] 微信小程序静默登录成功
// [应用初始化] 微信小程序用户信息获取成功
// [应用初始化] 微信小程序环境初始化完成

7. 分环境配置超时时间

typescript
// 根据环境配置不同的超时时间
const getInitTimeout = () => {
  // #ifdef H5
  return 5000  // H5 网络较快,5秒超时
  // #endif

  // #ifdef MP
  return 10000  // 小程序网络可能较慢,10秒超时
  // #endif

  // #ifdef APP-PLUS
  return 8000  // APP 中等超时
  // #endif

  return 10000  // 默认10秒
}

await waitForInit(getInitTimeout())

8. 结合页面生命周期

vue
<script setup lang="ts">
import { waitForInit } from '@/composables/useAppInit'

// 页面加载时等待初始化
onLoad(async () => {
  await waitForInit()
  // 初始化完成后执行页面逻辑
})

// 页面显示时检查状态
onShow(() => {
  // 可以在这里检查用户登录状态等
})
</script>

常见问题

1. 初始化被重复调用怎么办?

useAppInit 内部实现了单例模式和Promise缓存机制,即使多次调用也只会执行一次初始化:

typescript
// 内部实现
if (isInitialized.value) {
  console.log(`[应用初始化] ${platformName}环境已完成,跳过重复初始化`)
  return Promise.resolve()
}

if (initPromise) {
  console.log(`[应用初始化] ${platformName}环境正在初始化中,等待完成...`)
  return initPromise
}

2. 如何判断当前平台?

可以使用 @/utils/platform 中的工具函数:

typescript
import { isApp, isMp, isWechatOfficialH5, isAlipayOfficialH5, platform } from '@/utils/platform'

if (isApp) {
  // APP 环境
} else if (isMp) {
  // 小程序环境
  console.log('小程序平台:', platform) // mp-weixin, mp-alipay 等
} else if (isWechatOfficialH5) {
  // 微信公众号H5
} else if (isAlipayOfficialH5) {
  // 支付宝H5
}

3. 登录失败会阻止应用启动吗?

不会。登录失败被视为非致命错误,应用会继续启动,用户可以手动登录。但租户ID获取失败是致命错误,会抛出异常:

typescript
// 内部实现
catch (error) {
  // 租户ID获取失败是致命错误
  if (error instanceof Error && error.message.includes('租户ID')) {
    throw error
  } else {
    // 登录失败不是致命错误,允许应用继续启动
    console.warn(`[应用初始化] ${platformName}登录失败,但允许应用继续启动`)
    isInitialized.value = true
  }
}

4. WebSocket 何时连接?

WebSocket 在用户登录成功并获取用户信息后自动连接:

typescript
// 用户信息获取成功后初始化WebSocket
userStore.fetchUserInfo().then(() => {
  initializeWebSocket()
})

5. waitForInit 超时后会怎样?

超时后会抛出错误,需要在调用处捕获处理:

typescript
try {
  await waitForInit(5000) // 5秒超时
} catch (error) {
  if (error.message === '等待初始化超时') {
    // 处理超时情况
    console.error('初始化超时,请检查网络')
    // 可以选择重试或显示错误页面
  }
}

6. 如何在初始化完成前显示加载状态?

可以使用 loading 状态配合初始化:

vue
<template>
  <view v-if="appLoading" class="loading-page">
    <wd-loading size="64" />
    <text>应用加载中...</text>
  </view>
  <view v-else>
    <!-- 应用主体内容 -->
  </view>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { initializeApp } from '@/composables/useAppInit'

const appLoading = ref(true)

onLaunch(async () => {
  try {
    await initializeApp()
  } finally {
    appLoading.value = false
  }
})
</script>

7. 如何手动重新初始化?

由于单例模式限制,不支持直接重新初始化。如需重新执行,可以刷新应用:

typescript
// 小程序中可以使用 reLaunch
uni.reLaunch({ url: '/pages/index/index' })

// H5 中可以刷新页面
// #ifdef H5
window.location.reload()
// #endif

8. 微信H5授权参数会一直保留在URL中吗?

不会。登录成功后会自动清除URL中的授权参数:

typescript
// 清除URL中的授权参数
const newUrl = window.location.pathname + window.location.hash
window.history.replaceState({}, document.title, newUrl)

9. 多个页面同时调用 waitForInit 会怎样?

由于使用 Promise 缓存机制,多个页面同时调用 waitForInit 会共享同一个 Promise,不会触发多次初始化:

typescript
// 页面A
await waitForInit()  // 触发初始化

// 页面B(几乎同时)
await waitForInit()  // 等待页面A触发的初始化完成

// 页面C(稍后)
await waitForInit()  // 初始化已完成,直接返回

10. 初始化顺序有什么要求?

必须先调用 featureStore.initFeatures() 再调用 initializeApp(),因为 WebSocket 初始化依赖功能配置:

typescript
// 正确顺序
await featureStore.initFeatures()  // 1. 先加载功能配置
await initializeApp()               // 2. 再执行应用初始化

// 错误顺序 - WebSocket可能无法正常初始化
await initializeApp()               // ❌ 此时功能配置还未加载
await featureStore.initFeatures()

注意事项

  1. 初始化顺序 - 确保在调用 initializeApp 前已完成必要的前置配置(如功能配置 featureStore.initFeatures()

  2. 错误处理 - 租户ID获取失败是致命错误,需要妥善处理并给用户提示

  3. 平台差异 - 不同平台的初始化行为不同,注意在各平台上充分测试

  4. Token持久化 - 初始化时会检查本地存储的Token,确保Token存储逻辑正确

  5. WebSocket依赖 - WebSocket连接依赖功能配置(featureStore)和用户登录状态

  6. 异步操作 - 所有初始化方法都是异步的,需要使用 await.then() 处理

  7. 超时设置 - waitForInit 默认超时10秒,对于网络较慢的环境可适当增加

  8. 日志级别 - 生产环境建议关闭或减少初始化日志输出

  9. 单例限制 - 由于单例模式,无法在运行时重新初始化,需要重启应用

  10. 并发安全 - Promise缓存机制确保并发调用安全,不会产生竞态条件