前情提要 本文不會介紹 Firebase 與 React . 依照這篇文章完成後,你會
透過 CRA 建立一個專案
透過 MUI 建立符合 Material Design 的登入畫面
建立 Firsbase 專案並且透過 Firsbase 完成登入登出的功能
建立登入後才能瀏覽的頁面,並且實作登入/登出功能,登出後將無法瀏覽
Firebase 設定 建立 Firebase 專案 這裡的操作非常簡單,可以直接參考下面的影片。
VIDEO
詳細的步驟如下
進入 firebase console
選擇建立專案(Add Project),並且為你的專案取一個名字
進入剛剛建立的專案後,選擇建立應用程式(Add App), 在這裡我們選擇 Web 類型的應用程式 建立完成後我們會得到一個範例檔如下, 部份機敏資料先上遮罩
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { initializeApp } from "firebase/app" ;import { getAnalytics } from "firebase/analytics" ;const firebaseConfig = { apiKey : "A****************" , authDomain : "******.firebaseapp.com" , projectId : "******" , storageBucket : "******.appspot.com" , messagingSenderId : "******" , appId : "1:******:web:******" , measurementId : "G-*********" , }; const app = initializeApp (firebaseConfig);const analytics = getAnalytics (app);
抽出環境變數 在 React 當中的機敏資料,建議的作法是使用環境變數, 我們可以建立一個.env.local
提供給開發測試用, 而 Production 環境請直接設定在環境變數中, 相關資料不會進版控. 以下幾點注意事項
必需使用 REACT_APP_*
開頭才可讓 REACT APP 使用 ex:REACT_APP_APIKEY
一般的建議是全大寫
環境變數的更新不適用 Hot Reload,請重啟環境
啟用 Firebase 認証(Authentication)
首先要建立 Sign-in method > 原生供應商 > 電子郵件/密碼
建立第一個使用者 Users > Add User
React Create React Application 我們透過 TypeScript 建立 React 專案
1 npx create-react-app my-app --template typescript
我們可以試著啟動專案看一下,
安裝相關的套件 emotion 相關 1 npm i @emotion/react @emotion/styled
material 相關 1 npm i @mui/material @mui/icons-material
firebase 相關 1 npm i firebase react-firebase-hooks
react-router-dom 相關 1 npm i react-router-dom@6
建立登入頁面 登入頁面與路由 在 src 中建立 pages
資料夾, 之後我們再建立 Login.tsx
檔案如下
1 2 3 4 5 import React from "react" ;export const Login = ( ) => { return <> Login Page</> ; };
接下來我們要透過 react-router-dom 來建立路由 開啟 index.tsx
如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import React from 'react' ;import ReactDOM from 'react-dom' ;import './index.css' ;import App from './App' ;import reportWebVitals from './reportWebVitals' ;ReactDOM .render ( <React.StrictMode > <App /> </React.StrictMode > , document .getElementById ('root' ) ); reportWebVitals ();
修改如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import React from "react" ;import ReactDOM from "react-dom" ;import "./index.css" ;import App from "./App" ;import reportWebVitals from "./reportWebVitals" ;import { BrowserRouter , Route , Routes } from "react-router-dom" ;import { Login } from "./pages/Login" ;ReactDOM .render ( <React.StrictMode > <BrowserRouter > <Routes > <Route path ="" element ={ <App /> } /> <Route path ="login" element ={ <Login /> } /> </Routes > </BrowserRouter > </React.StrictMode > , document .getElementById ("root" ) ); reportWebVitals ();
重新啟動專案,在瀏覽器輸入不同的網址將會看到不同的畫面,如此我們完成了頁面的路由。
快速建立符合 MUI 的登入頁面 我們可以透過 MUI 提供的 Template 來建立 Login 頁面 實作細節如下:
參考 Source Code 建立 src/components/SigninSide
組件
修改 src/pages/Login.tsx
如下:
1 2 3 4 5 import SignInSide from "../components/SignInSide" ;export const Login = ( ) => { return <SignInSide /> ; };
重新瀏覽登入畫面,就可以看到一個美觀的登入頁 .
實作登入功能 首先請花點時間看一下 Firebase 帳號密碼登入的文件 。 接下來我們將依照文件的解釋修改我們的 SigninSide 組件。 打開 SigninSide 組件,找到handleSubmit
函數如下:
1 2 3 4 5 6 7 8 9 const handleSubmit = (event: React.FormEvent<HTMLFormElement> ) => { event.preventDefault (); const data = new FormData (event.currentTarget ); console .log ({ email : data.get ("email" ), password : data.get ("password" ), }); };
修改如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import { getAuth, signInWithEmailAndPassword } from "firebase/auth" ;import { app } from "../firebase-config" ;.... const handleSubmit = (event: React.FormEvent<HTMLFormElement> ) => { event.preventDefault (); const data = new FormData (event.currentTarget ); const auth = getAuth (app); signInWithEmailAndPassword ( auth, data.get ("email" )!.toString (), data.get ("password" )!.toString () ) .then ((userCredential ) => { const user = userCredential.user ; console .log ("user" , userCredential.user ); }) .catch ((error ) => { const errorCode = error.code ; const errorMessage = error.message ; console .log ("error" , error); }); };
題外話,data.get(“email”)!.toString() 當中的 !.
運算子可以參考 TypeScript Non-null assertion operator 的說明
用之前在 firebase 建立的帳密登入,登入成功的話可以在 Console 看到類似以下的資訊
1 2 UserImpl {providerId: 'firebase', emailVerified: false, isAnonymous: false, tenantId: null, providerData: Array(1), …}
實作登入後進頁面的跳轉 這裡要使用 react-dom-router
提供的 hook useNavigate
, 注意 React Hook 的使用限制
CRA 建立專案時同時會幫我們安裝檢查用的 Lint 所以不用太擔心。 同時我們把 getAuth(app)
也提取到 SignInSide
Component 之外
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const auth = getAuth (app);export default function SignInSide ( ) { const navigate = useNavigate (); const handleSubmit = (event: React.FormEvent<HTMLFormElement> ) => { event.preventDefault (); const data = new FormData (event.currentTarget ); signInWithEmailAndPassword ( auth, data.get ("email" )!.toString (), data.get ("password" )!.toString () ) .then ((userCredential ) => { navigate ("/" );
實作登出的功能 調整 App.tsx
如下,移除不相關的程式,建立一個 Button 並準備好 onClick 事件
1 2 3 4 5 6 7 8 9 10 11 12 13 import { Button } from "@mui/material" ;import "./App.css" ;function App ( ) { return ( <div className ="App" > <Button variant ="outlined" onClick ={() => {}}> LOGOUT </Button > </div > ); } export default App ;
路由設定,只有登入者才能看的頁面 透過 getAuth().currentUser 取得目前的登入者資料, 如果沒有登入者轉導到 Login 頁面,
1 2 3 4 5 6 7 8 9 10 11 12 13 import { getAuth } from "firebase/auth" ;.... <React .StrictMode > <BrowserRouter > <Routes > <Route path ="" element ={getAuth().currentUser ? <App /> : <Navigate to ="/login" /> } /> <Route path ="login" element ={ <Login /> } /> </Routes > </BrowserRouter > </React .StrictMode >
不過這樣的作法會有問題, getAuth().currentUser 是非同步取得的資訊, 所以你很有可能都會拿到 null 值, 在官方的建議作法是透過 onAuthStateChanged 註冊 Observer, index.tsx
會類似下面這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const auth = getAuth ();onAuthStateChanged (auth, (user ) => { ReactDOM .render ( <React.StrictMode > <BrowserRouter > <Routes > <Route path ="" element ={user ? <App /> : <Navigate to ="login" /> } /> <Route path ="login" element ={ <Login /> } /> </Routes > </BrowserRouter > </React.StrictMode > , document .getElementById ("root" ) ); reportWebVitals (); });
Todo
Firebase Email link(passwordless sign-in)
Firebase Host Project
PageNotFound
管理 Routes
more…
(つづく)