學習筆記-前端工程師也能開發全端網頁:挑戰 30 天用 React 加上 Firebase 打造社群網站|Firebase 教學

vincentxu
16 min readMar 29, 2023

--

在鐵人賽看到的教學,感覺很適合跟著練習入門,總共有30天,由於內容蠻實用的,就來嘗試練習看看。

教學影片連結
範例網站
範例程式碼

Day1 系列影片介紹

這個教學是希望讓前端工程師不需要後端工程師架設伺服器也可以開發全端網頁,會使用到firebase提供的後端服務。

架構資訊

前端框架 : React
伺服器 : firebase(BAAS平台-Backend As A Service)
會用到5個服務以及應用

  • firestore
    後端資料庫,Nosql,類似JSON格式來儲存資料,用來發表文章、文章主題、留言、按讚、收藏,資料都會存在這。
  • Storage
    雲端空間(硬碟),把文章圖片、會員頭像存在這。
  • Authentication
    現成會員系統,用來做會員註冊、登入、登出、會員資料,還有第三方登入串接。
  • Hosting
    可以部署網站的空間。
  • Extensions
    打包好的模組外掛功能,就不用自己去寫後端邏輯,會用到留言通知(Email)跟文章搜尋(Algolia)。

UI 框架 : Semantic UI React

Day2 建立 Firebase 專案

主要是設定firebase,原則上跟著影片操作即可。

Day3 建立 React 網頁

安裝常用套件,來建立環境

react-scripts react react-dom

介紹檔案與功能

package.json

dependencies紀錄我們安裝的套件與版本

package-lock.json

更詳細記錄專案用到的所有套件與版本,還有相依性的關係

node_modules

所有套件檔案存放的地方

新增public資料夾,並在裡面新增一個index.html
因為React是SPA(Single Page Application)單頁式應用的架構,只會有一個HTML的檔案

新增src資料夾
所有React元件跟Javascript的檔案都會放在這個資料夾
App.js
最基本的React元件
index.js
整個網頁應用程式的進入點,要把App.js這個元件渲染到index.html裡id叫做root的div 裡面,因爲React版本更新要改成這樣

import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<App />
);

package.json裡的scripts,代表可以透過npm使用react-scripts提供的start指令

"scripts": {
"start": "react-scripts start"
},

網頁start後,終端機會即時監聽,畫面會跟著連動,如果關掉終端機,網頁就會失效,關掉網頁可以重新到 http://localhost:3003(看預設的port是哪一個),就可以開啟網頁,不需要重新下指令開啟

Day4 製作標題導覽列

安裝套件

react-router-dom
網頁切換使用

semantic-ui-react

ui套件

semantic-ui-css

ui套件

Day5 註冊登入頁面

在註冊登入這邊因為router套件有更新寫法,要改成下面的寫法。

import { BrowserRouter,Routes ,Route } from "react-router-dom";
import Header from "./Header";
import 'semantic-ui-css/semantic.min.css';

function App(){
return (
<BrowserRouter>
<Header />
<Routes>
<Route path="/" element="首頁"/>
<Route path="/signin" element="註冊/登入" />
</Routes>
</BrowserRouter>
);
}

export default App;

Hooks現在寫法也有調整了,然後要記得import進來

import React ,{useState}from "react";    
const[activeItem,setActiveItem] = useState();

Day6 註冊登入功能

在firebase設定這邊,要改成這樣

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


const firebaseConfig = {
apiKey: "AIzaSyB9X_RdMD9DRHpdxRwZW32YEW7vKQZE_5E",
authDomain: "nobodyclimb-blog.firebaseapp.com",
projectId: "nobodyclimb-blog",
storageBucket: "nobodyclimb-blog.appspot.com",
messagingSenderId: "162348966364",
appId: "1:162348966364:web:2e5bc501018f4ccb1aef19"
};


firebase.initializeApp(firebaseConfig);

export default firebase;

useNavigate取代了useHistory,用法有點差別

import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate("/");

Day7 處理註冊登入的細節

沒有需要特別調整的地方

Day8 文章主題列表

在map的使用上要記得傳進一個key做資料順序的識別,可以另外新增id做編號,影片中用name會報錯,但不影響畫面所以還是可以使用。

Day9 發表文章頁面

Hooks寫法調整了,要記得import進來

import React ,{ useEffect ,useState} from "react";
const [topic,setTopic] = useState([]);
useEffect(() => {
},[]);

Day10 發表文章功能

Hooks寫法調整了,要記得import進來

import React, { useState, useEffect } from "react";
const [title, setTitle] = useState("");
useEffect(() => {
},[]);

useNavigate取代了useHistory

import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate("/");

Day11 上傳文章圖片

沒有需要特別調整的地方

Day12 文章列表

沒有需要特別調整的地方

Day13 文章頁面

這邊有使用到useParams,要特別注意在route設定的postId要跟你在文章頁面使用的postId相同

//App.js
<Route path="/posts/:postId" element={<Post/>} />
//Post.js
const { postId } = useParams();

Day14 收藏文章

沒有需要特別調整的地方

Day15 對文章按讚

重構程式碼,將重複的部分整理成同一個function

function toggleCollected() {
const userID = firebase.auth().currentUser.uid;
if (isCollected) {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({
collectedBy: firebase.firestore.FieldValue.arrayRemove(userID),
});
} else {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({
collectedBy: firebase.firestore.FieldValue.arrayUnion(userID),
});
}
};
function toggleLiked() {
const userID = firebase.auth().currentUser.uid;
if (isLiked) {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({ likedBy: firebase.firestore.FieldValue.arrayRemove(userID) });
} else {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({ likedBy: firebase.firestore.FieldValue.arrayUnion(userID) });
}
};


<Icon
name={`thumbs up ${isLiked ? "" : "outline"}`}
color={isLiked ? "blue" : "grey"}
link
onClick={toggleLiked}
/>
·
<Icon
name={`bookmark ${isCollected ? "" : "outline"}`}
color={isCollected ? "blue" : "grey"}
link
onClick={toggleCollected}
/>
function toggle (isActive,filed){
const userID = firebase.auth().currentUser.uid;
if (isActive) {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({
[filed]: firebase.firestore.FieldValue.arrayRemove(userID),
});
} else {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({
[filed]: firebase.firestore.FieldValue.arrayUnion(userID),
});
}
};

<Icon
name={`thumbs up ${isLiked ? "" : "outline"}`}
color={isLiked ? "blue" : "grey"}
link
onClick={()=>toggle(isLiked,"likedBy")}
/>
·
<Icon
name={`bookmark ${isCollected ? "" : "outline"}`}
color={isCollected ? "blue" : "grey"}
link
onClick={()=>toggle(isCollected,"collectedBy")}
/>

最後再將if的判斷式重複的部分整理成條件運算子


if (isActive) {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({
[filed]: firebase.firestore.FieldValue.arrayRemove(userID),
});
} else {
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({
[filed]: firebase.firestore.FieldValue.arrayUnion(userID),
});
}
firebase
.firestore()
.collection("posts")
.doc(postId)
.update({
[filed]: isActive
? firebase.firestore.FieldValue.arrayRemove(userID)
: firebase.firestore.FieldValue.arrayUnion(userID),
});

Day16 文章留言區塊

沒有需要特別調整的地方

Day17 文章留言功能

沒有需要特別調整的地方

Day18 即時更新留言

影片教學map忘了放key值

<Comment key={post.id} >

Day19 巢狀路由

switch在Router v6更新後,不能使用,要改用別的方式

目前採用將兩個posts跟my元件另外獨立成一個元件來管控路徑,但不是最佳解,還是要花時間了解Router v6之後的用法

MyPost

import React, { useEffect, useState } from "react";
import { Header, Item } from "semantic-ui-react";
import Post from "../components/Post";
import firebase from "../utilis/firebase";

function MyPosts() {
const [posts, setPosts] = useState([]);
useEffect(() => {
let author
firebase
.firestore()
.collection("posts")
.where('author.uid', '==' , firebase.auth().currentUser.uid)
.get()
.then((collectionSnapshot) => {
const data = collectionSnapshot.docs.map((docSnapshot) => {
const id = docSnapshot.id;
return { ...docSnapshot.data(), id };
});
setPosts(data);
console.log(data);
});
}, []);
return (
<Item.Group>
<Header>我的文章</Header>
{posts.map((post) => {
return <Post post={post} key={post.id} />;
})}
</Item.Group>
);
}

export default MyPosts;

MyNavigate

import React, { useEffect, useState } from "react";
import { Routes, Route } from "react-router-dom";
import { Grid, Container } from "semantic-ui-react";
import MyPosts from "./MyPosts";
import MyCollections from "./MyCollections";
import MySettings from "./MySettings";
import MyMenu from "../components/MyMenu";
import PostNavigate from "./PostNavigate";
import firebase from "../utilis/firebase";

function MyNavigate({user}) {
return (
<Container>
<Grid>
<Grid.Row>
<Grid.Column width={3}>
<MyMenu />
</Grid.Column>
<Grid.Column width={10}>
<Routes>
<Route path="posts" element={<MyPosts />} />
<Route path="collections" element={<MyCollections />} />
<Route path="settings" element={<MySettings user={user}/>} />
</Routes>
</Grid.Column>
<Grid.Column width={3}></Grid.Column>
</Grid.Row>
</Grid>
</Container>
);
}

export default MyNavigate;

Day20 會員選單

沒有需要特別調整的地方

Day21 發表與收藏文章列表

沒有需要特別調整的地方

Day22 修改會員名稱

沒有需要特別調整的地方

Day23 上傳會員照片

沒有需要特別調整的地方

Day24 修改會員密碼

目前遇到登出後就會報錯的狀態

Day25 阻擋會員路由

因為需要阻擋路由,所以上面的問題在這會被解決

在很多元件都會需要監聽user登入狀態,可以改成用props從App.js傳入其他元件

暫時還沒解決巢狀路由底下的問題,現在在會員頁面跟文章頁面登出都會報錯

Day26 根據主題篩選文章列表

如果沒有新增文章圖片的話,就會報錯。

Day27 無限捲動讀取文章列表

沒有需要特別調整的地方

Day28 留言電子郵件通知

目前google帳戶上找不到應用程式密碼,兩階段驗證完後直接登入以下網址
https://myaccount.google.com/apppasswords?pli=1&rapt=AEjHL4MjqBs8aoJx39u--XrW7gqOnjFCcK9KsPDW-JBcEbHHxHj6KpGf2K0xqXcDorfTXQYJGTFhlnejVclyMKME_yV55QbADA

遇到寄送留言通知狀態維持在PROCESSING,待解決

Day29 搜尋文章

沒有需要特別調整的地方

Day30 社群網站上線啦!

成功完成部署,連結提供參考,找時間再來優化版面,解決一些小問題。

終於完成了,蠻有成就感的,雖然還有一些小問題待解決,不過練習了很多常見的基礎功能,底下整理一些心得。

  • React的useState元件狀態管理、uesEffect控制函式執行次數、Router檔案路徑、useNavigate導向頁面、使用上越來越熟悉
  • Firebase的服務與方法,auth、firestore、hosting、extension
  • Semantic UI 初步認識,跟之前用的MUI有點類似
  • React專案從建置環境到部署,完整地走過一次,對檔案的存放與用途,親手編寫過,和CRA預先準備好的就不太一樣。
  • 邏輯判斷的掌握度,?: ||的運用
  • 箭頭函式的熟悉度()=>{ }

--

--

vincentxu
vincentxu

Written by vincentxu

熱愛學習、戶外運動,關心教育,曾任公部門主計,教育工作者/軟體工程師_嘗試將科技與使用者經驗設計導入教育_共同創辦一個線上教育平台,推廣自主學習與民主教育https://www.daoedu.tw/

Responses (1)