React + Firebase: простой провайдер аутентификации на основе контекста
В этом посте демонстрируется быстрый и простой способ сделать пользователя, прошедшего проверку подлинности Firebase, доступным во всем веб-приложении React.
Мы используем здесь простой React с Typescript и не задействуем дополнительную библиотеку управления состоянием, такую как Redux.
Firebase предлагает нам зарегистрировать обратный вызов, который вызывается каждый раз, когда пользователь аутентифицируется или выходит из системы, чтобы получать уведомления о текущей ситуации аутентификации.
import firebase from "firebase/app";
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log("authenticated", user);
} else {
console.log("signed out");
}
});
Таким образом, мы могли бы довольно просто реализовать компонент React, который заинтересован в аутентифицированном пользователе:
import * as React from "react";
import firebase from "firebase/app";
function CurrentUser() {
const [user, setUser] = React.useState<firebase.User | null>(null);
React.useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(setUser);
return unsubscribe;
}, []);
return <div>{user?.displayName || "unauthenticated"}</div>;
}
Наш компонент React облегчает React.useEffect
для регистрации обратного вызова Firebase onAuthStateChanged
один раз после его установки. Эффект возвращает обратный вызов отказа от подписки onAuthStateChanged
, гарантируя, что мы не запустимся при каких-либо утечках памяти.
Кроме того, у нас есть состояние для текущего пользователя, сеттер которого идеально соответствует сигнатуре обратного вызова.
Это отлично работает, если только один компонент в вашем приложении React заинтересован в состоянии аутентификации. Дублирование состояния и эффекта для каждого другого компонента было бы обременительным.
Но что еще более важно, этот подход работает только для постоянных (не визуализируемых условно) компонентов в дереве визуализации нашего приложения, поскольку в противном случае они могут пропустить начальное состояние аутентификации, поскольку onAuthStateChanged
уведомляет только об изменениях.
Один из способов решить эту проблему - предоставить состояние аутентификации глобально, используя контекст React и сопутствующий хук. Начнем сначала с контекста:
// FirebaseAuthContext.tsx
import * as React from "react";
import firebase from "firebase/app";
type User = firebase.User | null;
type ContextState = { user: User };
const FirebaseAuthContext =
React.createContext<ContextState | undefined>(undefined);
const FirebaseAuthProvider: React.FC = ({ children }) => {
const [user, setUser] = React.useState<User>(null);
const value = { user };
React.useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(setUser);
return unsubscribe;
}, []);
return (
<FirebaseAuthContext.Provider value={value}>
{children}
</FirebaseAuthContext.Provider>
);
};
export { FirebaseAuthProvider };
Здесь следует отметить несколько моментов:
User
- это псевдоним типа для аутентифицированного пользователя Firebase, возвращаемыйonAuthStateChanged
. Обратный вызов вызываетсяnull
, если ни один пользователь не аутентифицирован.ContextState
- это псевдоним типа для значения, предоставляемого нашим контекстомFirebaseAuthContext
.- Мы не выставляем
FirebaseAuthContext
напрямую. Вместо этого мы предоставляемFirebaseAuthProvider
который инкапсулируетFirebaseAuthContext.Provider
и подпискуonAuthStateChanged
. Это очень похоже на реализациюCurrentUser
выше.
Теперь давайте определим простой хук, который дает компонентам, заинтересованным в аутентифицированном пользователе, доступ к нему:
// FirebaseAuthContext.tsx
// ...
function useFirebaseAuth() {
const context = React.useContext(FirebaseAuthContext);
if (context === undefined) {
throw new Error(
"useFirebaseAuth must be used within a FirebaseAuthProvider"
);
}
return context.user;
}
export { FirebaseAuthProvider, useFirebaseAuth };
Наш хук useFirebaseAuth
просто облегчает React.useContext
для доступа к ранее определенному контексту. Мы тщательно проверяем undefined
, чтобы как можно раньше выявить возможные злоупотребления.
FirebaseAuthProvider
создается только один раз в приложении, обычно рядом с корнем, чтобы предоставить всем компонентам, расположенным ниже, возможность доступа к пользователю через useFirebaseAuth
. Вот простой (надуманный) пример:
// example.ts
import * as React from "react";
import { FirebaseAuthProvider, useFirebaseAuth } from "./FirebaseAuthContext";
// ...initialize firebase
function App() {
return (
<FirebaseAuthProvider>
<UserName />
<UserEmail />
</FirebaseAuthProvider>
);
}
function UserName() {
const user = useFirebaseAuth();
return <div>{user?.displayName || "unauthenticated"}</div>;
}
function UserEmail() {
const user = useFirebaseAuth();
return <div>{user?.email || "-"}</div>;
}
Несколько замечаний:
- Инициализация Firebase не указана для краткости. Вы можете проверить её здесь, если вы еще этого не сделали.
- Хук может использоваться любым нижеуказанным компонентом
FirebaseAuthProvider
независимо от уровня вложенности. - Каждое уведомление о
onAuthStateChange
запускает повторный рендеринг. - Если ваше приложение управляет состоянием с помощью Redux или подобной библиотеки, вам может быть лучше обработать состояние аутентификации и там