接下來會將第十一天所實作的 TodoList 從 CodePen 移植過來,因此這章節也會整理一下 CRA 順便安裝 Tailwind CSS,讓我們的 TodoList 看起來更好看一點。
為了避免過度讓專案複雜化,所以這邊會稍微整理一下 CRA,這邊我們先輸入以下指令移除相關用不到的套件
npm uninstall @testing-library/jest-dom @testing-library/react @testing-library/user-event web-vitals
接下來刪除以下檔案
另外 App.css 可以先刪除,因為我們並不會使用到這一隻檔案
刪除之後,接下來打開 src/index.js 將內容改成以下
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 程式碼轉移過來。
由於我們在 CodePen 上面有使用到 Tailwind CSS,所以目前 CRA 的專案也要安裝 Tailwind CSS,否則程式碼移動進來也會無法正常呈現畫面的。
首先先安裝 Tailwind CSS
npm install -D tailwindcss postcss autoprefixer
安裝好後再來初始化 Tailwind CSS
npx tailwindcss init -p
初始化後你會看到專案底下多了 postcss.config.js 與 tailwind.config.js 兩個檔案

接下來打開 tailwind.config.js,我們要將內容調整成改成以下
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}",],
theme: {
extend: {},
},
plugins: [],
}
content 主要是告知 TailWind CSS 要檢測哪些檔案,好讓 PostCSS 除去沒有使用的 class,只保留有在使用的 class。
接下來打開 src/index.css 並且將裡面刪除加入以下
@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. 字樣刪除罷了
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 }) => {...} 貼進去
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 就好了
import List from './components/List';
到目前為止你只要輸入 npm start 就可以看到你前面章節練習的 TodoList 已經成功脫離 CodePen 環境轉移到真實的開發環境囉。
到目前為止我們已經將 ToDoList 的專案移轉到真實的開發環境,接下來我們要將專案部署到 GitHub Pages 上,讓大家可以透過網址來使用我們的 ToDoList。
而接下來我會盡可能一個一個步驟說明,讓你可以無痛的部署到 GitHub Pages。
所以這邊請打開你的 GitHub 新建一個儲存庫,名稱為 example-react-todolist

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

到這邊為止你就已經建立好儲存庫,然後請你複製畫面上下方「…or push an existing repository from the command line」的部分
git remote add origin 'url'
git branch -M main
git push -u origin main
稍後我們會用到這些指令,儲存庫的網址也請記得一下。
接下來我們要將我們的專案加入遠端儲存庫,所以請先打開你的終端機,並且切換到你的專案目錄,然後輸入剛剛複製的指令
git remote add origin 'url'
git branch -M main
git push -u origin main
以我的範例程式碼來講就是以下
git remote add origin git@github.com:hsiangfeng/example-react-todolist.git
git branch -M main
git push -u origin main
請注意,你的 url 跟我的不一樣,所以請你自行替換成你的 url。
除此之外你在貼上指令的時候,請逐行貼入,不要一次貼入全部,否則你可能會遇到一些問題,那麼由於我們剛剛搬移了專案,所以你的指令會是以下流程
git remote add origin git@github.com:hsiangfeng/example-react-todolist.git
git branch -M main
git add .
git commit -m 'first'
git push -u origin main

到目前為止你的專案原始碼應該已經上傳到 GitHub 儲存庫囉。
接下來的步驟非常重要,請不要漏掉任何一個步驟了,你只要漏掉一個步驟,你就無法部署到 GitHub Pages,因此請你仔細看完。
首先我們要編輯 package.json,請你打開你的專案目錄,然後找到 package.json,接著加入 homepage 屬性
{
"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 套件幫助我們部署到 GitHub Pages,請在終端機輸入以下指令
npm install --save gh-pages
接著回來打開 package.json 找到 scripts 加入兩個指令,分別是 predeploy 與 deploy 指令
{
"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 了。
請在終端機輸入以下指令
npm run deploy
這樣子你就成功部署到你剛剛的儲存庫。
那麼接著要如何知道部署成功呢?第一個地方你可以點開 branch 的部分,如果有部署成功的話,你可以看到分支變成兩個,一個是 main,另一個是 gh-pages。

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

那麼到目前網址你成功部署到 gh-page 上,而我這邊也附上我的 範例 給你觀看。
範例程式碼儲存庫:GitHub
範例 GitHub Pages:GitHub Pages
最後這邊也補一些資源,如果你對於 Git 指令沒有很熟悉的話,可以參考我這一篇基礎 Git 指令文章,我有列出常見的 Git 指令。
本文將會同步更新到我的部落格