終究都要學 React 何不現在學呢? - React CRA - TodoList 與 GitHub 部署 - (21)

前言

接下來會將第十一天所實作的 TodoList 從 CodePen 移植過來,因此這章節也會整理一下 CRA 順便安裝 Tailwind CSS,讓我們的 TodoList 看起來更好看一點。

刪除多餘檔案

為了避免過度讓專案複雜化,所以這邊會稍微整理一下 CRA,這邊我們先輸入以下指令移除相關用不到的套件

1
npm uninstall @testing-library/jest-dom @testing-library/react @testing-library/user-event web-vitals

接下來刪除以下檔案

  • setupTests.js
  • reportWebVitals.js
  • logo.svg
  • App.test.js

另外 App.css 可以先刪除,因為我們並不會使用到這一隻檔案

刪除之後,接下來打開 src/index.js 將內容改成以下

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';


const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

這樣子整體程式碼就會乾淨一點,稍後比較可以方便你將 CodePen 程式碼轉移過來。

加入 Tailwind CSS

由於我們在 CodePen 上面有使用到 Tailwind CSS,所以目前 CRA 的專案也要安裝 Tailwind CSS,否則程式碼移動進來也會無法正常呈現畫面的。

首先先安裝 Tailwind CSS

1
npm install -D tailwindcss postcss autoprefixer

安裝好後再來初始化 Tailwind CSS

1
npx tailwindcss init -p

初始化後你會看到專案底下多了 postcss.config.js 與 tailwind.config.js 兩個檔案

init

接下來打開 tailwind.config.js,我們要將內容調整成改成以下

1
2
3
4
5
6
7
8
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}",],
theme: {
extend: {},
},
plugins: [],
}

content 主要是告知 TailWind CSS 要檢測哪些檔案,好讓 PostCSS 除去沒有使用的 class,只保留有在使用的 class。

接下來打開 src/index.css 並且將裡面刪除加入以下

1
2
3
@tailwind base;
@tailwind components;
@tailwind utilities;

這樣子就大功告成啦~

詳細的 TailWind CSS 細節就不多說了,畢竟不是這一系列文章要講的內容因此只是帶到而已,到目前為止 TailWind CSS 就成功加入到 CRA 囉~

只是如果你有發現「Unknown at rule @tailwind」這個錯誤訊息的話,可以參考我這一篇「VSCode 噴 Unknown at rule @tailwind 解決方式」文章的解決方式。

後面就讓我們繼續將原本放在 CodePen 的程式碼轉移到當前專案上吧。

轉移程式碼

接下來這邊我會建議將 App.js 改成 App.jsx,雖然副檔名不論是 .js 或是 .jsx 都沒有差異也不會發生任何錯誤,但是為了好辨別裡面是否有使用到 JSX 語法,因此這邊會建議將 App.js 改成 App.jsx 會比較好,未來當你看到這個副檔名時也就可以立刻知道裡面有使用到 JSX 語法。

接下來只需要將 App.js 裡面全部刪除,然後貼上 CodePen 中 const App = () => {} 的部分,但是這邊有一些小細節要注意,就是原本我們是這樣子撰寫 const [ todoList, setTodoList ] = React.useState(JSON.parse(localStorage.getItem('todoList')) || []); 相關 Hook,可是在 CRA 中因為改使用了另一種方式引入 Hook 因此語法要改成另一種寫法 const [ todoList, setTodoList ] = useState(JSON.parse(localStorage.getItem('todoList')) || []);,就僅僅只是將 React. 字樣刪除罷了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import { useState, useEffect } from 'react';

const App = () => {
const [ todoList, setTodoList ] = useState(JSON.parse(localStorage.getItem('todoList')) || []);

const addTodo = (event) => {
setTodoList([
...todoList,
{
id: Date.now(),
name: event.target.previousElementSibling.value,
status: false
}
]);
event.target.previousElementSibling.value = '';
};

useEffect(() => {
localStorage.setItem('todoList', JSON.stringify(todoList));
}, [ todoList ]);

const remoteAllTodo = () => {
setTodoList([]);
};

return (
<div>
<div className="bg-indigo-500 p-5 h-screen">
<div className="max-w-[768px] m-auto bg-white p-5">
<h1 className="text-center text-2xl mb-4">React ToDoList</h1>
<div className="flex">
<input type="text" className="w-full rounded-l-lg border-l-2 border-y-2 border-indigo-300 pl-4 focus:outline-indigo-500 focus:outline-none focus:outline-offset-0" placeholder="請輸入你的代辦事項" />
<button onClick={ addTodo } className="w-[50px] h-[50px] border-0 bg-sky-500 hover:bg-sky-600 rounded-r-lg text-white transition duration-700">+</button>
</div>

<List todoList={ todoList } setTodoList={ setTodoList }/>

<div className="flex justify-between items-center mt-5">
<p>
目前有 <span className="font-medium">{ todoList.length }</span> 個事項待完成
</p>

<button onClick={ remoteAllTodo } type="button" className="bg-red-300 p-2 rounded-md hover:bg-red-400 transition duration-700">Clear All Todo</button>
</div>
</div>
</div>
</div>
)
}

export default App;

接下來在 src 底下建立一個 components 資料夾,然後也順便建立一個 List 資料夾與 index.jsx,並將 const List = ({ todoList, setTodoList }) => {...} 貼進去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const List = ({ todoList, setTodoList }) => {

const updateTodo = (event) => {
const { id } = event.target.dataset;
const newTodoList = todoList.map((todo) => {
if(todo.id === Number(id)) {
todo.status = !todo.status;
}
return todo;
});

setTodoList([ ...newTodoList ]);
}

const template = (todo) => {
return (
<li className="py-4" key={ todo.id }>
<label className={ todo.status ? 'line-through' : ''}>
<input type="checkbox" className="mr-2" onChange={ updateTodo } data-id={ todo.id } checked={ todo.status }/>
{ todo.name }
</label>
</li>
)
}

return ( <ul> { todoList.map((todo) => template(todo)) } </ul> )
}

export default List;

接下來只要將 List 元件的引入寫到 App.jsx 就好了

1
import List from './components/List';

到目前為止你只要輸入 npm start 就可以看到你前面章節練習的 TodoList 已經成功脫離 CodePen 環境轉移到真實的開發環境囉。

部署到 GitHub Pages

到目前為止我們已經將 ToDoList 的專案移轉到真實的開發環境,接下來我們要將專案部署到 GitHub Pages 上,讓大家可以透過網址來使用我們的 ToDoList。

而接下來我會盡可能一個一個步驟說明,讓你可以無痛的部署到 GitHub Pages。

建立 GitHub 專案

所以這邊請打開你的 GitHub 新建一個儲存庫,名稱為 example-react-todolist

新建儲存庫

新建成功後你會跳到另一個頁面,如下圖:

example-react-todolist

到這邊為止你就已經建立好儲存庫,然後請你複製畫面上下方「…or push an existing repository from the command line」的部分

1
2
3
git remote add origin 'url'
git branch -M main
git push -u origin main

稍後我們會用到這些指令,儲存庫的網址也請記得一下。

替專案加入遠端儲存庫

接下來我們要將我們的專案加入遠端儲存庫,所以請先打開你的終端機,並且切換到你的專案目錄,然後輸入剛剛複製的指令

1
2
3
git remote add origin 'url'
git branch -M main
git push -u origin main

以我的範例程式碼來講就是以下

1
2
3
git remote add origin [email protected]:hsiangfeng/example-react-todolist.git
git branch -M main
git push -u origin main

請注意,你的 url 跟我的不一樣,所以請你自行替換成你的 url。

除此之外你在貼上指令的時候,請逐行貼入,不要一次貼入全部,否則你可能會遇到一些問題,那麼由於我們剛剛搬移了專案,所以你的指令會是以下流程

1
2
3
4
5
git remote add origin [email protected]:hsiangfeng/example-react-todolist.git
git branch -M main
git add .
git commit -m 'first'
git push -u origin main

指令

到目前為止你的專案原始碼應該已經上傳到 GitHub 儲存庫囉。

調整 CRA 專案

接下來的步驟非常重要,請不要漏掉任何一個步驟了,你只要漏掉一個步驟,你就無法部署到 GitHub Pages,因此請你仔細看完。

第一步驟:編輯 package.json

首先我們要編輯 package.json,請你打開你的專案目錄,然後找到 package.json,接著加入 homepage 屬性

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "example-react-todolist",
"version": "0.1.0",
"private": true,
"homepage": "https://hsiangfeng.github.io/example-react-todolist",
"dependencies": {
// ... 略過
},
"scripts": {
// ... 略過
},
// ... 略過
}

React 的 CRA(Create React App) 將會讀取 homepage 屬性來決定你的專案的網址,請一定要加入這個屬性。

第二步驟:安裝 gh-pages

接著要安裝 gh-pages 套件幫助我們部署到 GitHub Pages,請在終端機輸入以下指令

1
npm install --save gh-pages

第三步驟:增加 deploy 指令

接著回來打開 package.json 找到 scripts 加入兩個指令,分別是 predeploydeploy 指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "example-react-todolist",
"version": "0.1.0",
"private": true,
"homepage": "https://hsiangfeng.github.io/example-react-todolist",
"dependencies": {
// ... 略過
},
"scripts": {
"predeploy": "npm run build",
"deploy": "gh-pages -d build",
// ... 略過
},
// ... 略過
}

predeploy 指令是當你輸入 npm run build 的時候,會優先執行 predeploy 指令,然後再執行 deploy 指令,所以你可以在 predeploy 指令中加入你要執行的指令,例如我們要執行 npm run build,所以我們就加入了 npm run build

到目前為止你就可以部署到 GitHub Pages 了。

請在終端機輸入以下指令

1
npm run deploy

這樣子你就成功部署到你剛剛的儲存庫。

第四步驟:觀看部署結果

那麼接著要如何知道部署成功呢?第一個地方你可以點開 branch 的部分,如果有部署成功的話,你可以看到分支變成兩個,一個是 main,另一個是 gh-pages。

branch

第二個地方你可以點開 Settings 的部分,然後往下拉,你可以看到 GitHub Pages 的部分,如果有部署成功的話,你可以看到 GitHub Pages 的網址。

gh-pages

那麼到目前網址你成功部署到 gh-page 上,而我這邊也附上我的 範例 給你觀看。

範例程式碼儲存庫:GitHub
範例 GitHub Pages:GitHub Pages

最後這邊也補一些資源,如果你對於 Git 指令沒有很熟悉的話,可以參考我這一篇基礎 Git 指令文章,我有列出常見的 Git 指令。

後記

本文將會同步更新到我的部落格