最近正在學 React,看見課程中要做出一個類似便條紙的頁面,老師說靈感來自 Google Keep,紀錄自己的想法後丟上去。
課程裡做出來的:
主要是練習 React 中的 props 跟怎麼使用狀態,以下是寫出來的 component 之間的關係。
<App>
<Header />
<CreateNote />
<Note />
<Footer />
<App />
首先是新增便條紙 <CreateNote />
的部分,要將不可控的元素轉成可控制元素(controlled elements),因為表單元素是由 DOM 管理輸入的狀態,轉成受控元素就能允許 React 保持對輸入的控制。
步驟:
const [note, setNote] = useState({ title: '', content: '' })
function handleChange(e) {
const { name, value } = e.target
setNote((note) => {
return { ...note, [name]: value }
})
}
<input
// ...
onChange={handleChange}
value={note.title}/>
<textarea
// ...
onChange={handleChange}
value={note.content}/>
這時候元素的輸入值就可以預測(因為是我們在控制的),也能進行動態更新。
使用者在新增便條紙後,接收值的元件是 <Note />
,它不是 <CreateNote />
的子元件。
由於 React 中資料只能由上往下流動,當父元件或其他兄弟元件需要使用同個狀態時,必須將狀態提升到上一層的公共父元件,在這裡就是 <App />
。
步驟:
App
元件const [notes, setNotes] = useState([])
function handleAddNote(newNote) {
setNotes((notes) => [...prevNotes, newNote])
}
function handleDeleteNote(id) {
setNotes((notes) => notes.filter((note, i) => i !== id))
}
CreateNote
及 Note
元件<CreateNote onAddNote={handleAddNote} />
注意 notes 是陣列 要用 map 迭代資料給 Note
{notes.map((note, i) => {
return (
<Note
key={i}
id={i}
title={note.title}
content={note.content}
onDeleteNote={handleDeleteNote}/>
)
})}
function CreateNote({ onAddNote }) {
// ...
function handleClick(e) {
e.preventDefault()
onAddNote(note)
}
return (
// ...
<button onClick={handleClick}>Add</button>
)
}
function Note({ id, title, content, onDeleteNote }) {
return (
<div className="note">
<h2>{title}</h2>
<p>{content}</p>
<button onClick={() => onDeleteNote(id)}>DELETE</button>
</div>
)
}
現在新增便條紙後,發現就算沒有寫任何內容還是可以新增,所以回到 CreateNote
加上一個預防動作:
function handleClick(e) {
e.preventDefault()
// 新增以下這行
if (note.title === '' || note.content === '') return
// 當沒有內容時 就不再執行
onAddNote(note)
}
新增完便條紙後,如果重新整理網頁,狀態就會回到初始狀態,也就是空的陣列:
const [notes, setNotes] = useState([]) // 初始狀態為空陣列
於是就想到可以把資料存在 localStorage,讓使用者下次打開頁面時,可以再將儲存的資料渲染出來。
步驟:
const [notes, setNotes] = useState(function () {
const storageValue = localStorage.getItem('notes')
return storageValue ? JSON.parse(storageValue) : []
// 如果 localStorage 沒有資料 返回空陣列
})
useEffect(
function () {
localStorage.setItem('notes', JSON.stringify(notes))
},
[notes]
// 當 notes 更新時 執行上面的函數
)
完成!
很多時候,將資料儲存到 localStorage 是常有的事,如果可以把上面那一大段程式碼寫成自定義的 Hook,未來其他專案要使用時,也能輕鬆移植過去。
最後就是要來寫 Custom Hook:
use
命名 Custom Hookexport function useLocalStorageState(initialValue, key) {}
export function useLocalStorageState(initialValue, key) {
const [value, setValue] = useState(function () {
const storageValue = localStorage.getItem(key)
return storageValue ? JSON.parse(storageValue) : initialValue
})
useEffect(
function () {
localStorage.setItem(key, JSON.stringify(value))
},
[value, key]
)
}
export function useLocalStorageState(initialValue, key) {
const [value, setValue] = useState(function () {
const storageValue = localStorage.getItem(key)
return storageValue ? JSON.parse(storageValue) : initialValue
})
useEffect(
function () {
localStorage.setItem(key, JSON.stringify(value))
},
[value, key]
)
return [value, setValue] // 返回陣列
}
const [notes, setNotes] = useLocalStorageState([], 'notes')
這樣就大功告成啦!
練習 repo 頁面 ▶ Post-it - build with React