React實作
從實作中,學習React。
參考影片:Learn React With This One Project
(涵蓋vite, className, hooks(useState&useEffect), components, props)
建立一個 React Todo App,完成品如下:
此篇文章架構:
- step by step 打造 Todo app
- 拆分成components
- 部署至GitHub
Step by step 打造 Todo app
step1 使用 Vite 建構專案
Vite是一種新型前端構建工具,能夠顯著提升前端開發體驗。它主要由兩部分組成:
一個開發服務器,它基於原生ES 模塊提供了豐富的內建功能,如速度快到驚人的模塊熱更新。
一套構建指令,它使用Rollup打包你的代碼,可輸出用於生產環境的高度優化過的靜態資源。
在終端機數入:
npm create vite@latest
> 選擇React
> Project name 輸入 react-todo-app
> 選擇JavaScript
> cd react-todo-app
> npm install
> npm run dev
此時 http://localhost:5173/ 畫面出現:
生成的專案架構如下:
main.jsx檔案
step2 加入 css 檔案
由於 css 部分非此練習重點,可點擊此以取得 style.css 檔案,下載加至src資料夾中。
在 App.jsx 檔案上方加入
import './style.css'
step3 加入JSX
將原本的 App.jsx 內容移除,加上下方程式碼:
從程式碼中可看到把 list 列出三項(task1, task2, task3) 因為上個步驟已加入 style.css,所以目前畫面如下:
step4 加入React icon
React Icon 可以參考 https://react-icons.github.io/react-icons
於終端機輸入指令安裝套件
npm install react-icons --save
找尋到適合的icon
在 App.jsx 上方 import 要用到的icon
import {FaTrashAlt} from 'react-icons/fa'
把<button>Delete</button>
換成<button><FaTrashAlt /></button>
原本的 Delete 字樣成功換成垃圾桶的 icon了
step5 加入useState
又稱作 State Hook ,實現在 Function Component 中使用 State 狀態的一個方法。
在 React 16.8 之前,若想使用到生命週期的方法或狀態 (state),只能使用 React 的 class component。但 React hooks 的出現,讓開發者使用 functional component時,也能夠操作 React 的功能和狀態。在 React 中,用 use
開頭的函式稱之為 Hook,有些 Hooks 是 React 內建的功能,例如:useState
、useEffect
。
Hook的規則:
1.只能在最頂層使用 Hook(不能在 for 循環、if…else 或巢狀中(如: map )中使用 Hook)
2.只能在 React 函式中使用 Hook
採用 JavaScript ES6 解構賦值的方式來宣告兩個變數,第一個變數放state
變數名稱,第二個變數放要setState()
這個函式的名稱。
useState(“20%”)是代表初始值。
React 當中,更新 state 時要用 immutable (不可改變)的寫法。因為物件是傳址 (pass by reference),如果直接改物件本身,該物件記憶體的位置沒有變,React會不知道該物件有改,即不會使用新的值去渲染畫面造成錯誤。
在 App.jsx 檔案上方加入
import { useState } from 'react'
function App中加入
const [newItem, setNewItem] = useState('task123')
並在 <input>
加上 value={newItem}
此時畫面會看到:
由於目前的預設 input 是 task123
實際上是由使用者輸入
所以把 useState
改成 useState('')
<input> 加上 onChange={e => setNewItem(e.target.value)}
當使用者操作input框,有修改時會響應在畫面上。
step6 加入todos邏輯
function App中加入
const [todos, setTodos] = useState([])
寫一個 handleSubmit function
function handleSubmit(e) {
e.preventDefault()
setTodos((currentTodos) => {
return [
...currentTodos,
{id: crypto.randomUUID(), title: newItem, completed: false}
]
})
setNewItem('')
}
這邊的 setTodos 內代入 currentTodos
是當有使用到“目前的值”就要代入
setNewItem('')
是把輸入框的內容清空
在 Add button 上加上 onCLick={handleSubmit}
在<ul> tag中
把原本寫死的<li>們註解掉(之後可以刪掉)
{/* <li>
<label>
<input type="checkbox"/>
task 1
</label>
<button className='trash-icon'><FaTrashAlt /></button>
</li> */}
{todos.map(todo => {
return (
<li key={todo.id}>
<label>
<input type="checkbox" checked={todo.completed}/>
{todo.title}
</label>
<button className='trash-icon'><FaTrashAlt /></button>
</li>
)
})}
使用map去生出todos
加上 toggle checkbox
label中 <input> 加上 onChange={e => toggleTodo(todo.id, e.target.checked)}
寫一個 toggleTodo function
function toggleTodo(id, completed) {
setTodos(currentTodos => {
return currentTodos.map(todo => {
if (todo.id === id) {
return { ...todo, completed }
}
return todo
})
})
}
加上 delete function,先實作單個 delete
delete btn上加上 onClick={() => deleteTodo(todo.id)}
(這邊不能改寫成 onClick={deleteTodo(todo.id)}
,因為這樣是直接回傳 deleteTodo(todo.id)
執行結果而非call deleteTodo
function)
寫一個 deleteTodo function
function deleteTodo(id) {
setTodos(currentTodos => {
return currentTodos.filter(todo => todo.id !== id)
})
}
再加上 deleteAll function
Delete All btn上加上 onClick={deleteAll}
function deleteAll() {
setTodos([])
}
step7 加上No Todos
在 todo 區塊加上
{todos.length === 0 && "No Todos"}
當 todos.length
為true時,會出現 No Todos
字樣
拆分成components
拆分出NewTodoForm.jsx
新增一個jsx檔案,名為 NewTodoForm.jsx (component的結尾必須是jsx)
把App.jsx中 form部分搬移過去也把 handleSubmit function搬移過去
接著把 const [newItem, setNewItem] = useState('')
搬移過去
記得在 NewTodoForm.jsx 檔案 import useState
回到App.jsx檔案,在上方import {NewTodoForm} from './NewTodoForm'
並在原本 App.jsx form的位置,加上 <NewTodoForm />
元件
此時,NewTodoForm.jsx 中 handleSubmit function 會用到 todo,而App.jsx也會用到,此時需要用props去達成元件溝通。
在App.jsx檔案中新增 addTodo function
function addTodo(title) {
setTodos((currentTodos) => {
return [
...currentTodos,
{id: crypto.randomUUID(), title: title, completed: false}
]
})
}
而NewTodoForm.jsx 中 handleSubmit function改成
function handleSubmit(e) {
e.preventDefault()
if (newItem === "") return
props.onSubmit(newItem)
setNewItem('')
}
props 用於父傳子組件溝通
App.jsx
其中onSubmit跟onDeleteAll是用來傳遞function
NewTodoForm.jsx
傳入props即可使用
拆分出TodoList.jsx & TodoItem.jsx
參考上述做法,拆分 component。
App.jsx
TodoList.jsx
TodoItem.jsx
此時,沒有存在 local storage,要存在local storage可以藉由 useEffect 協助。有關useEffect可以參考這篇:React介紹2,其中的生命週期部分。
在 App.jsx 檔案中加入
當todos改變時,localStorage隨之改變。新增刪除todo項目,按下重新整理按鈕時,資料不會不見了。
部署至GitHub
參考這支影片:
由於使用vite建構app,在package.json中deploy部分改成:
"deploy": "gh-pages -d dist"
在部署過程,若不能順利使用Https推上code,可以參考這篇使用SSH推東西上github。
部署結束,點開網址沒有看到內容可以參考這篇:
修改完config檔案即完成
React 實作小結
附上 react-todo-app 專案的 GitHub 檔案連結 🔗 ,專案資料夾名稱:react-todo-app
。有任何指點或建議都可以在下方留言~🙏