Firestore问题:创建用户凭据后如何同步写入信息并保证事务性?
问题分析与解决方案
错误原因
- UID获取失败及类型错误:报错
Expected type 'th', but it was: a custom nh object通常是因为Firestore的doc函数接收的db实例初始化不正确,或是参数传递时的类型匹配问题。另外未正确定义credentials的类型,也可能导致访问user.uid时出现异常。 - 操作非原子性:原代码中用户账号创建成功后,若Firestore写入失败,已创建的账号不会被回滚,导致数据不一致。
修正后的代码
import { Container, Form, Button } from "react-bootstrap" import { Link } from "react-router-dom" import { auth, createUserWithEmailAndPassword, deleteUser, db, setDoc, doc, UserCredential } from "../../config/firebaseConfig" import { FormEvent, useRef, useContext } from "react" import { AuthContext } from "../../contexts/AuthContext" export const Signup = () => { const emailRef = useRef<HTMLInputElement>(null) const passwordRef = useRef<HTMLInputElement>(null) const passwordConRef = useRef<HTMLInputElement>(null) const usernameRef = useRef<HTMLInputElement>(null) const user = useContext(AuthContext) async function createUserAccount(e: FormEvent) { e.preventDefault() const email = emailRef.current?.value const password = passwordRef.current?.value const passwordConfirm = passwordConRef.current?.value const username = usernameRef.current?.value if (!email || !password || !passwordConfirm || !username) { alert("请填写所有必填字段") return } if (password !== passwordConfirm) { alert("两次输入的密码不一致") return } let userCredential: UserCredential | null = null try { // 1. 创建用户账号 userCredential = await createUserWithEmailAndPassword(auth, email, password) // 2. 写入用户信息到Firestore await setDoc(doc(db, "users", userCredential.user.uid), { name: username, email: email }) alert("账号创建成功") // 清空表单 emailRef.current!.value = "" passwordRef.current!.value = "" passwordConRef.current!.value = "" usernameRef.current!.value = "" } catch(e) { // 若Firestore写入失败,删除已创建的用户账号 if (userCredential) { await deleteUser(userCredential.user) } alert(`创建失败: ${(e as Error).message}`) } } return ( <Container fluid className="py-5 text-light bg-dark" id="signupContainer"> <h2 className="text-center">Sign Up</h2> <Form className="w-75 mx-auto" onSubmit={createUserAccount}> <Form.Group> <Form.Label>Username</Form.Label> <Form.Control required type="text" placeholder="AwesomeUser" maxLength={20} id="signupName" ref={usernameRef}></Form.Control> </Form.Group> <Form.Group> <Form.Label>Email Address</Form.Label> <Form.Control required type="email" placeholder="example@address.com" id="signupEmail" ref={emailRef}></Form.Control> </Form.Group> <Form.Group> <Form.Label>Password</Form.Label> <Form.Control required type="password" id="signupPassword" ref={passwordRef}></Form.Control> </Form.Group> <Form.Group> <Form.Label>Confirm password</Form.Label> <Form.Control required type="password" id="signupPasswordConf" ref={passwordConRef}></Form.Control> </Form.Group> <Button disabled={!!user} type="submit" className="w-100 mt-5 rounded-pill">Create Account</Button> </Form> <p className="text-center text-muted">Already have an account? <Link to="/space-image-app/login">Log in</Link></p> </Container> ) }
关键修改点
- 类型安全:将
credentials的类型从any改为Firebase官方的UserCredential,避免类型访问错误。 - 原子性保障:
- 先创建用户账号,再执行Firestore写入操作
- 若写入失败,调用
deleteUser删除已创建的用户,确保两个操作要么都成功,要么都回滚
- 参数校验:提前获取表单值并做非空校验,避免
null访问异常 - 错误处理优化:捕获错误时输出具体信息,同时处理回滚逻辑
- 代码简化:移除单独的
addUserToDB函数,将逻辑合并到主函数中,减少层级便于维护
额外检查项
- 确保
firebaseConfig中db初始化正确:// firebaseConfig.ts示例 import { initializeApp } from "firebase/app" import { getAuth } from "firebase/auth" import { getFirestore } from "firebase/firestore" const firebaseConfig = { /* 你的配置信息 */ } const app = initializeApp(firebaseConfig) export const auth = getAuth(app) export const db = getFirestore(app) - 确保Firestore规则允许创建用户文档(开发阶段可临时设置宽松规则,上线后需收紧):
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { match /users/{userId} { allow create: if request.auth != null && request.auth.uid == userId; } } }
内容的提问来源于stack exchange,提问作者Sean
相关产品推荐
相关产品推荐

