
firebase 返回的用户对象并非普通 javascript 对象,而是包含不可枚举属性、原型链和内部私有字段(如 `_z`, `_x`)的特殊类实例;直接存入 redux store 会导致 `mapstatetoprops` 获取到被 proxy 或序列化包装后的非预期结构。
在 React Native + Firebase + Redux 架构中,你遇到的 user 状态被随机包裹在 _z、_x、_y 等键下的现象,并非 Redux 本身的问题,而是 Firebase SDK 返回的对象具有不可序列化的内部结构 —— 它们是 User 类的实例(继承自 FirebaseUser),内部使用了私有字段和 Proxy 拦截机制,用于延迟加载或安全封装(例如 getIdToken() 的响应对象也同理)。
当你执行:
props.setUser({
id: user['uid'],
email: user['email'],
token: token
});表面上看你在构造一个普通对象,但若 user['uid'] 或 user['email'] 实际引用的是 Firebase 内部的 getter/Proxy 属性(尤其在某些调试环境或 Hermes 引擎下行为更明显),该对象可能仍携带隐藏原型或不可枚举属性。Redux DevTools 或 react-redux 的 shallowEqual 检测机制在序列化/比较时会触发这些属性的展开,导致 mapStateToProps 接收到的 state.user 实际是一个被深度代理包装的结构(表现为 _z: { _z: { ... } } 等嵌套)。
✅ 正确做法:始终将 Firebase 对象“脱水”(hydrate → dehydrate)为纯 JSON 对象:
// ✅ 推荐:使用 toPlainObject 或手动解构(更可控)
const plainUser = {
id: user.uid, // 直接访问属性,而非 user['uid']
email: user.email,
displayName: user.displayName ?? null,
photoURL: user.photoURL ?? null,
isAnonymous: user.isAnonymous,
// 注意:token 需单独处理(它本身是 Promise 结果,非 Firebase 对象)
};
props.setUser(plainUser);⚠️ 特别注意:auth.currentUser?.getIdToken() 返回的是 Promise
? 进阶建议:在 reducer 中增加防御性校验
case 'SET_USER':
// 强制转换为 plain object,避免 Proxy/Class 实例污染 state
const safePayload = action.payload && typeof action.payload === 'object'
? JSON.parse(JSON.stringify(action.payload))
: action.payload;
console.log('Normalized user:', safePayload);
return safePayload;? 补充说明:.toJSON() 方法在 Firebase User 实例上确实存在(返回一个 plain object),但它并非所有版本都稳定支持,且部分字段(如 metadata)仍可能含 Date 实例。因此更稳妥的方式是显式白名单解构,而非依赖 .toJSON()。
? 总结:
- ❌ 不要将 firebase.User 实例、firebase.auth.UserCredential 等 SDK 类实例直接存入 Redux;
- ✅ 始终手动提取所需字段,构建 plain object;
- ✅ 在 mapStateToProps 中无需 hack 如 user._z._z,只需确保 store 中存储的是标准 POJO(Plain Old JavaScript Object);
- ?️ 若需持久化复杂数据,考虑搭配 redux-persist 并配置 stateReconciler 处理反序列化逻辑。
这样即可彻底规避神秘 _z 嵌套,让状态管理回归可预测、可调试、可维护的本质。










