React Native Firebase 子集合写入问题排查与正确实践

本文详解 react native 中使用 firebase 创建订单子集合时数据丢失的常见原因,重点指出混用 `await` 和 `.then()` 的陷阱,并提供符合最佳实践的异步写入方案。

在 React Native 项目中通过 Firebase Firestore 为订单(Orders)文档创建子集合(如 Products)时,若仅写入了部分商品(例如购物车有 2 件商品却只存入 1 条),往往并非 Firebase 本身限制,而是异步控制逻辑存在隐患——最典型的问题是错误地混合使用 await 和 .then(),导致执行流失控或变量作用域异常。

回顾原始代码中的关键问题:

await addDoc(ordrRef, { /* ... */ }).then((doc) => {
    orderID = doc.id; // ❌ 在 .then() 回调中赋值,但外部无法保证同步获取
});
// 此时 orderID 可能仍为 undefined,后续循环将失败或跳过

该写法不仅破坏了 async/await 的线性可读性,更因 orderID 赋值时机不可控,极易引发 orderId 未定义、子集合路径错误(如写入到 Orders/undefined/Products)等静默失败,最终表现为“只写入一个商品”——实际可能是循环根本未正确执行,或多次复用同一无效 doc 引用。

✅ 正确做法:全程统一使用 async/await,确保顺序执行与变量可靠性

const placeOrder = async () => {
  try {
    // 1. 创建主订单文档,可靠获取 orderId
    const orderDoc = await firestore()
      .collection('Orders')
      .add({
        totalPrice: route?.params?.orderPrice,
        userId: loggedUser,
        createdAt: firestore.FieldValue.serverTimestamp(),
      });

    const orderId = orderDoc.id;
    console.log('Created order with ID:', orderId);

    // 2. 遍历购物车,逐条写入子集合(注意:key 是字符串索引,建议用 for...of 或 Object.values)
    for (const product of Object.values(cartProducts)) {
      await firestore()
        .collection('Orders')
        .doc(orderId)
        .collection('Products')
        .add({
          productTitle: product.title,
          productImage: product.image?.[0] || '',
          productQuantity: product.quantity || 1,
          productPrice: product.price,
          productOfferPrice: product.offerPrice,
        });
      console.log('Added product:', product.title);
    }

    // 3. 成功后清空购物车(建议放在 finally 或独立 dispatch 中)
    dispatch(cartActions.clearCart());
    console.log('Order placed successfully!');
  } catch (error) {
    console.error('Failed to place order:', error);
    Alert.alert('下单失败', error.message || '请检查网络并重试');
  }
};

? 关键改进点说明

  • 移除 .then() 混用:所有异步操作均通过 await 线性等待,避免竞态与变量失效;
  • 安全遍历 cartProducts:使用 Object.values(cartProducts) 替代 for...in(后者遍历键名,易受原型链干扰);
  • 增强健壮性:添加 try/catch 全局错误捕获、空值防护(如 product.image?.[0])、用户反馈;
  • 语义清晰:区分 orderDoc(主文档引用)与子集合写入动作,避免变量复用歧义。

⚠️ 额外注意事项

  • 若购物车数据结构为数组(而非对象),请直接使用 for...of 或 map();
  • 高并发场景下,考虑使用 writeBatch() 批量写入提升性能与原子性;
  • Expo 项目能正常运行,可能因其底层封装(如 @react-native-firebase/firestore 版本差异或 polyfill 补全)掩盖了问题,但 React Native 原生环境要求更严格的异步规范。

遵循以上实践,即可稳定写入全部子文档,彻底解决“子集合数据丢失”的表象问题。