You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue 3 Composable最佳实践咨询:useAuth调用逻辑与传参问题

Vue.js Composable 实践问题解答

用户场景与问题

我正在学习Vue.js Composable的最佳实践,目前开发一款使用Firebase Firestore和Auth的应用,已创建两个Composable:

  • 用于用户认证的useAuth
  • 用于在Firestore中创建用户的useUser

useAuth会在认证完成后调用useUser并更新store状态,我想了解该流程是否合理?另外,我想知道能否为Composable函数传递数据(如useAuth(someData))?由于Mock Firebase系统难度较大,我希望在Composable中注入服务,但看到有说法称这种做法不利于排查Bug,特此咨询。

现有代码

useAuth.ts

import { ref } from 'vue'

import { auth } from '@/modules/repository/implementation/firebase/firebase'
import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth'
import { useAuthStore } from '@/stores/auth'
import type User from '@/modules/model/User'
import { useUser } from './useUser'

export const useAuth = () => {
  const { login } = useAuthStore()
  const { getUserByEmail, addUser } = useUser()

  const error = ref<string>()

  const onLogin = async (email: string, password: string) => {
    try {
      const cred = await signInWithEmailAndPassword(auth, email, password)
      const user = await getUserByEmail(email)
      if (user) {
        login(user)
        return true
      }
      error.value = 'The user should connect but no data found for it'
      return false
    } catch (err) {
      if ((err as any)?.code === 'auth/invalid-credential') {
        error.value = 'No user were found for the given credentials'
      } else if ((err as any)?.name === 'FirebaseError') {
        error.value = 'An error occured with an external service'
      } else {
        error.value = 'An unknow error occured'
      }
      return false
    }
  }

  const onRegister = async (user: User) => {
    try {
      const userCred = await createUserWithEmailAndPassword(auth, user.email, user.password)
      login(await addUser(user, userCred.user.uid))
      return true
    } catch (err) {
      if ((err as any)?.name === 'FirebaseError') {
        error.value = 'An error occured with an external service'
      } else {
        error.value = 'An unknow error occured'
      }
      return false
    }
  }

  return { error, onLogin, onRegister }
}

useUser.ts

import User from '@/modules/model/User'
import { usersCollection } from '@/modules/repository/implementation/firebase/firebase'
import { doc, getDocs, query, setDoc, where } from 'firebase/firestore'

export const useUser = () => {
  const getUserByEmail = async (email: string): Promise<User | null> => {
    const userDoc = (await getDocs(query(usersCollection, where('email', '==', email)))).docs[0]
    if (userDoc) {
      return {
        ...(userDoc.data() as User),
        uid: userDoc.id
      }
    }
    return null
  }

  const addUser = async (user: User, userId: string): Promise<User> => {
    const createdDoc = await doc(usersCollection, userId)
    const userToAdd = { ...user, uid: userId }
    await setDoc(createdDoc, userToAdd)
    return userToAdd
  }

  return { getUserByEmail, addUser }
}

注:我提前做过相关调研但未找到有效信息,最初采用注入服务的方式,后认为使用Composable更契合Vue组合式API。

解答

1. 认证流程的合理性

你的认证流程整体符合职责分离原则,是合理的:useAuth专注处理Firebase Auth的登录/注册逻辑,useUser专注处理Firestore的用户数据操作,两者配合完成从身份验证到用户数据同步的完整链路。

可优化的细节点:

  • 登录数据查询精准度:当前用邮箱查询用户数据,建议改为用Firebase Auth返回的cred.user.uid作为查询条件——Firebase Auth会保证用户UID唯一,避免Firestore中出现异常重复数据时导致的错误。
  • 错误提示细化:注册时的错误处理可以拆分具体Firebase错误码(如auth/email-already-in-use),给用户更明确的操作指引。
  • 加载状态补充:登录/注册过程中可添加加载状态,避免UI无响应,提升用户体验。

2. Composable传递数据的可行性

完全可以为Composable函数传递参数,这是Vue组合式API的常规用法,能大幅提升Composable的灵活性与复用性。

示例改造:

// 允许传入自定义Auth实例,默认使用全局auth
export const useAuth = (authInstance = auth) => {
  const onLogin = async (email: string, password: string) => {
    // 使用传入的实例替代全局依赖
    const cred = await signInWithEmailAndPassword(authInstance, email, password)
    // ...其余逻辑
  }
  // ...
}

这种方式在测试场景中尤为实用,你可以传入Mock的Auth实例,无需依赖真实Firebase服务即可完成单元测试。

3. 服务注入与Bug排查的权衡

在Composable中注入服务(通过参数传递或Vue的inject)是推荐实践,反而有助于Bug排查与维护:

  • 解耦依赖:将Firebase服务作为依赖注入,而非硬编码导入,能让Composable不绑定到特定实现,方便替换为Mock服务进行测试。
  • 依赖关系透明:注入方式让依赖来源更清晰,出现问题时可快速定位是服务本身的问题,还是Composable逻辑的问题。
  • 避免全局依赖陷阱:硬编码导入全局服务会导致Composable难以独立测试,而注入方式可在测试时完全隔离外部依赖。

你当前代码中直接导入auth和usersCollection属于硬编码依赖,建议改为注入方式:

// useAuth.ts
import { inject } from 'vue'

export const useAuth = () => {
  // 优先使用注入的实例, fallback到全局auth
  const authInstance = inject('firebaseAuth') || auth
  // ...
}

在应用初始化时提供注入的服务:

// main.ts
import { createApp } from 'vue'
import { auth } from '@/modules/repository/implementation/firebase/firebase'

const app = createApp(App)
app.provide('firebaseAuth', auth)
// ...

这种方式既保留了开发便捷性,又提升了可测试性与可维护性,不会增加Bug排查难度,反而能让问题定位更高效。

内容的提问来源于stack exchange,提问作者Cyril Auquier

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 13:17:03