如何将来自不同组件的 firebase auth 和 Cloud Firestore 用作单个 Firebase 应用程序

Posted

技术标签:

【中文标题】如何将来自不同组件的 firebase auth 和 Cloud Firestore 用作单个 Firebase 应用程序【英文标题】:How to use firebase auth and Cloud Firestore from different components as a single firebase App 【发布时间】:2019-12-05 16:16:01 【问题描述】:

我正在尝试在我的 React 项目中使用 firebase 来提供身份验证和数据库功能。

在我的App.js 我有

import app from "firebase/app";
import "firebase/auth";
app.initializeApp(firebaseConfig);

在由App.js 渲染的名为<Component /> 的其他组件中,我有这个来初始化数据库

import app from "firebase/app";
import "firebase/firestore";
const db = app.firestore();

但是这次我得到了这个错误

Uncaught FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app).

所以我也尝试将app.initializeApp(firebaseConfig); 放入此组件中,但我再次收到一个新错误,告诉我我实例化了两次。

Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).

所以我想出的一种解决方法是在App.jsapp.initializeApp(firebaseConfig); 之后创建一个上下文,我通过const db = app.firestore(); 创建了数据库并将值传递给上下文并让<Component /> 使用。但是我不知道这是否是一个好的解决方案。

我的问题与How to check if a Firebase App is already initialized on android 不同,原因之一。我没有尝试连接到第二个 Firebase 应用程序,因为它是针对那个问题的。我整个项目只有一个 Firebase App,提供两种服务:auth 和 database。

我尝试在<Component />中使用该问题的解决方案

if (!app.apps.length) 
  app.initializeApp(firebaseConfig);


const db = app.firestore();

但它没有工作它仍然给我Uncaught FirebaseError: Firebase: Firebase App named '[DEFAULT]' already exists (app/duplicate-app).错误

【问题讨论】:

能否请您添加有关在您的项目中使用 Firestore 的更多详细信息?我还在使用类似的用例,例如用于身份验证的 firebase 和用于存储的 firestore。 【参考方案1】:

您在应用和组件中使用不同的 Firebase 实例。

// firebaseApp.js
import firebase from 'firebase'
const config = 
    apiKey: "...",
    authDomain: "...",
    databaseURL: "....",
    projectId: "...",
    messagingSenderId: "..."
;
firebase.initializeApp(config);
export default firebase;

您可以从 firebaseApp.js 导入 firebase 并使用它。更多详情here

【讨论】:

【参考方案2】:

src/firebase 目录中创建一个文件firebaseConfig.js 用于firebase 配置:

import firebase from 'firebase/app'; // doing import firebase from 'firebase' or import * as firebase from firebase is not good practice. 
import 'firebase/auth';
import 'firebase/firestore';

// Initialize Firebase
let config = 
    apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
    authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
    databaseURL: process.env.REACT_APP_FIREBASE_DATABASE_URL,
    projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
    storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
    messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
;
firebase.initializeApp(config);

const auth = firebase.auth();
const db = firebase.firestore();

const googleAuthProvider = new firebase.auth.GoogleAuthProvider();
const emailAuthProvider = new firebase.auth.EmailAuthProvider();

export  auth, firebase, db, googleAuthProvider, emailAuthProvider ;

Component.js 中你所要做的就是:

import  db  from './firebase/firebaseConfig.js'; // Assuming Component.js is in the src folder

将api密钥存储在项目根文件夹(src的父级)的.env文件中:

REACT_APP_FIREBASE_API_KEY=<api-key>
REACT_APP_FIREBASE_AUTH_DOMAIN=<auth-domain>
REACT_APP_FIREBASE_DATABASE_URL=<db-url>
REACT_APP_FIREBASE_PROJECT_ID=<proj-name>
REACT_APP_FIREBASE_STORAGE_BUCKET=<storage-bucket>
REACT_APP_FIREBASE_MESSAGING_SENDER_ID=<message-sender-id>

【讨论】:

【参考方案3】:

您收到的错误消息是有效的,并且与您的模块的导入顺序有关。 预先解析 ES6 模块,以便在执行代码之前解析进一步的导入。

假设您的App.js 的最顶部看起来像这样:

import Component from '../component';
...

import app from "firebase/app";
import "firebase/auth";
app.initializeApp(firebaseConfig);

这里的问题在于import Component from '.../component';里面

import app from "firebase/app";
import "firebase/firestore";
const db = app.firestore();

该代码在您执行之前被执行:

app.initializeApp(firebaseConfig);

有很多方法可以解决这个问题,包括上面介绍的一些解决方案以及将您的 firebase 配置存储在 firebase-config.js 并导入 db 的建议 从此。

这个答案更多地是关于了解问题所在......就解决方案而言,我认为您的Context Provider 实际上非常好并且很常用。

更多关于 es6 模块here

Firebase React Setup

希望对您有所帮助。

【讨论】:

【参考方案4】:

您可以使用您所说的上下文或redux(使用中间件进行初始化,并使用全局状态来保留数据库):

// Main (for example index.js)
<FirebaseContext.Provider value=new Firebase()>
    <App />
</FirebaseContext.Provider>

Firebase.js:

import app from 'firebase/app'
import 'firebase/firestore'

const config = 
  apiKey: process.env.API_KEY,
  databaseURL: process.env.DATABASE_URL,
  projectId: process.env.PROJECT_ID,
  storageBucket: process.env.STORAGE_BUCKET


export default class Firebase 
  constructor() 
    app.initializeApp(config)

    // Firebase APIs
    this._db = app.firestore()
  

  // DB data API
  data = () => this._db.collection('yourdata')

  ...

FirebaseContext.js:

import React from 'react'

const FirebaseContext = React.createContext(null)

export const withFirebase = Component => props => (
  <FirebaseContext.Consumer>
    firebase => <Component ...props firebase=firebase />
  </FirebaseContext.Consumer>
)

然后您可以在容器组件中使用 withFirebase:

class YourContainerComponent extends React.PureComponent 
  state = 
    data: null,
    loading: false
  

  componentDidMount() 
    this._onListenForMessages()
  

  _onListenForMessages = () => 
    this.setState( loading: true , () => 
      this.unsubscribe = this.props.firebase
        .data()
        .limit(10)
        .onSnapshot(snapshot => 
          if (snapshot.size) 
            let data = []
            snapshot.forEach(doc =>
              data.push( ...doc.data(), uid: doc.id )
            )
            this.setState(
              data,
              loading: false
            )
           else 
            this.setState( data: null, loading: false )
          
        )
     )
   )
  

  componentWillUnmount() 
    if (this._unsubscribe) 
      this._unsubscribe()
    
  



export default withFirebase(YourContainerComponent)

您可以在此处查看完整代码:https://github.com/the-road-to-react-with-firebase/react-firestore-authentication 和此处的教程:https://www.robinwieruch.de/complete-firebase-authentication-react-tutorial/

如果你使用 redux 和 redux-thunk 来实现它,你可以隔离中间件、动作和 reducer 中的所有 firebase 东西(你可以在这里获取想法和示例:https://github.com/Canner/redux-firebase-middleware);并将业务逻辑保留在您的组件中,这样他们就不需要知道您的数据集合是如何存储和管理的。组件应该只知道状态和动作。

【讨论】:

您好,感谢您的及时回复。我看到你把firebase-config 文件放在.env 中。它被认为比将其存储在 js 文件 firebase-config.js 中并从中导入更好的做法? 还有,当组件需要db 时,为什么使用Redux 管理数据库实例(即db)比只使用import db from './db' 更好? 嗨。最好将您的凭据放在您不会上传到存储库的文件中(git、mercurial 等)。将其保存在环境文件中可以让您拥有不同的配置,例如,一个用于生产,另一个用于测试,另一个用于开发。您可以使用安全凭证管理器为要实施解决方案的服务器创建容器并提供凭证。 Redux 或任何你可以用来保持关注点分离 (SoC) 的东西。将容器中的组件(负责将数据和操作绑定到展示组件的组件)和展示组件分开是一种很好的做法。因此,您的逻辑是关于获取和存储数据,这些操作独立于持久性技术(db、localstorage 等),您可以将所有这些逻辑保存在一个地方,所以如果您明天可以更改 Firebase 以进行 API 调用(或其他技术),您可以在一个地方更换逻辑,您无需触摸组件。 您好,感谢您的及时回复。是否有一些教程可以让我学习如何在存储firebase-config 的情况下编写我的.env?现在我只是在需要时导入我的firebase-config.js,正如你所说的那样不安全。我还发现,当我部署我的 firebase 项目时,由于我使用 Web SDK 而不设置自己的服务器,人们仍然可以通过使用开发工具检查 Sources 选项卡来查看我的 firebase-config 中的内容。将其保存在环境文件中是否有助于防止这种情况发生?【参考方案5】:

我发现在 react 中使用 firebase 的最佳方法是首先初始化并导出 firebase 以执行所需的功能。

helper-firebase.js

import * as firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/firestore';

// Everyone can read client side javascript, no need to use an .env file
// I only used environment variables for firebase-admin
import  FIREBASE_CONFIG  from '../../config';

// Initialize Firebase
firebase.initializeApp(FIREBASE_CONFIG);
export const auth = firebase.auth();
export const provider = new firebase.auth.GoogleAuthProvider();
export const db = firebase.firestore();
export default firebase;

你的组件.js

import 
  auth,
  provider,
  db,
 from '../../../helpers/helper-firebase';

...
componentDidMount() 
  this.usersRef = db.collection('users');
  // Look for user changes
  auth.onAuthStateChanged(this.authChanged);


authChanged(user) 
  // Set value on the database
  this.usersRef.doc(user.uid).set(
    lastLogin: new Date(),
  ,  merge: true )
    .then(() => 
       console.log('User Updated');
    )
    .catch((error) => 
       console.error(error.message);
    );


login() 
  auth.signInWithPopup(provider)
    .then((res) => 
      console.log(newUser);
    )
    .catch((error) => 
      console.error(error.message);
    )

...

但我建议使用“redux-thunk”来存储状态数据:

redux-actions.js

import 
  auth,
 from '../../../helpers/helper-firebase';

export const setUser = payload => (
  type: AUTH_CHANGED,
  payload,
);

export const onAuthChange = () => (
  dispatch => auth.onAuthStateChanged((user) => 
    // console.log(user);
    if (user) 
      dispatch(setUser(user));
     else 
      dispatch(setUser());
    
  )
);

export const authLogout = () => (
  dispatch => (
    auth.signOut()
      .then(() => 
        dispatch(setUser());
      )
      .catch((error) => 
        console.error(error.message);
      )
  )
);

【讨论】:

【参考方案6】:

以下是将登录用户数据从 google OAuth 存储到 firestore 集合中的简单示例。

将 firebase 配置存储在单独的文件中

firebase.utils.js

import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/auth';

//replace your config here
const config = 
  apiKey: '*****',
  authDomain: '******',
  databaseURL: '******',
  projectId: '******,
  storageBucket: '********',
  messagingSenderId: '*******',
  appId: '**********'
;

firebase.initializeApp(config);

export const createUserProfileDocument = async (userAuth) => 
  if (!userAuth) return;

  const userRef = firestore.doc(`users/$userAuth.uid`);

  const snapShot = await userRef.get();

  if (!snapShot.exists) 
    const  displayName, email  = userAuth;
    const createdAt = new Date();
    try 
      await userRef.set(
        displayName,
        email,
        createdAt
      );
     catch (error) 
      console.log('error creating user', error.message);
    
  

  return userRef;
;

export const auth = firebase.auth();
export const firestore = firebase.firestore();

const provider = new firebase.auth.GoogleAuthProvider();
provider.setCustomParameters( prompt: 'select_account' );
export const signInWithGoogle = () => auth.signInWithPopup(provider);

App.js

 import React from 'react';
 import  auth, createUserProfileDocument, signInWithGoogle  from './firebase.utils';

 class App extends React.Component 
  constructor() 
    super();

    this.state = 
      currentUser: null
    ;

   

  unsubscribeFromAuth = null;

  componentDidMount() 
    this.unsubscribeFromAuth = auth.onAuthStateChanged(async userAuth => 
      if (userAuth) 
        const userRef = await createUserProfileDocument(userAuth);

        userRef.onSnapshot(snapShot => 
          this.setState(
            currentUser: 
              id: snapShot.id,
              ...snapShot.data()
            
          );

          console.log(this.state);
        );
      

      this.setState( currentUser: userAuth );
    );
  

  componentWillUnmount() 
    this.unsubscribeFromAuth();
  

  render() 
   return(
     <React.Fragment>
        this.state.currentUser ? 
          (<Button onClick=() => auth.signOut()>Sign Out</Button>) 
         : 
         (<Button onClick=signInWithGoogle > Sign in with Google </Button>) 
       

     </React.Fragment>
    )
   


export default App;

当你想在组件之间共享状态时,我建议你使用像 Redux 这样的存储管理库。在此示例中,我们在单个组件中完成了所有操作。但在实时情况下,您可能有一个复杂的组件架构,在这种用例中使用商店管理库可能会派上用场。

【讨论】:

以上是关于如何将来自不同组件的 firebase auth 和 Cloud Firestore 用作单个 Firebase 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何将 Firebase Auth Token 从客户端传递到服务器?

如何将来自 Firebase 的查询快照转换为不同的对象?

如何根据 Firebase Auth 错误消息在 Flutter 中显示自定义警报对话框?

Flutter:显示来自经过身份验证的 Firebase Auth 用户的带有 StreamBuilder 的一些 Firestore 集合

Firebase Auth用户角色[重复]

适用于Facebook Auth和数据库的Firebase