更新於 2024/03/06閱讀時間約 9 分鐘

React 練習 - 便條紙

最近正在學 React,看見課程中要做出一個類似便條紙的頁面,老師說靈感來自 Google Keep,紀錄自己的想法後丟上去。

google keep頁面

google keep頁面

課程裡做出來的:

post-it 頁面


主要是練習 React 中的 props 跟怎麼使用狀態,以下是寫出來的 component 之間的關係。

<App>
<Header />
<CreateNote />
<Note />
<Footer />
<App />

使用 useState 將表單元素轉成可控制元件

首先是新增便條紙 <CreateNote /> 的部分,要將不可控的元素轉成可控制元素(controlled elements),因為表單元素是由 DOM 管理輸入的狀態,​轉成受控元素就能允許 React 保持對輸入的控制。

步驟:

  • 使用 useState 來初始化狀態
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}/>

這時候元素的輸入值就可以預測(因為是我們在控制的),也能進行動態更新。

提升狀態到父元件 Lift states up

使用者在新增便條紙後,接收值的元件是 <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))
}
  • 用 props 傳遞給 CreateNoteNote 元件
<CreateNote onAddNote={handleAddNote} />
注意 notes 是陣列 要用 map 迭代資料給 Note
{notes.map((note, i) => {
return (
<Note
key={i}
id={i}
title={note.title}
content={note.content}
onDeleteNote={handleDeleteNote}/>
)
})}
  • 使用 props
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)
}

使用 useEffect 將資料存在 localStorage

新增完便條紙後,如果重新整理網頁,狀態就會回到初始狀態,也就是空的陣列:

const [notes, setNotes] = useState([]) // 初始狀態為空陣列

於是就想到可以把資料存在 localStorage,讓使用者下次打開頁面時,可以再將儲存的資料渲染出來。

步驟:

  • 將上面那段改寫
const [notes, setNotes] = useState(function () {
const storageValue = localStorage.getItem('notes')
return storageValue ? JSON.parse(storageValue) : []
// 如果 localStorage 沒有資料 返回空陣列
})
  • 新增 useEffect
useEffect(
function () {
localStorage.setItem('notes', JSON.stringify(notes))
},
[notes]
// 當 notes 更新時 執行上面的函數
)

完成!

為了提高重用度 改寫成 Custom Hook

很多時候,將資料儲存到 localStorage 是常有的事,如果可以把上面那一大段程式碼寫成自定義的 Hook,未來其他專案要使用時,也能輕鬆移植過去。

最後就是要來寫 Custom Hook:

  • 程式碼寫在另一個檔案 使用 use 命名 Custom Hook
export function useLocalStorageState(initialValue, key) {}
  • 將剛剛寫的 useState、useEffect 剪下貼過來
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] // 返回陣列
}
  • 在元件中導入 Custom Hook
const [notes, setNotes] = useLocalStorageState([], 'notes')

這樣就大功告成啦!

重新整理也不會遺失寫好的便條紙



練習 repo 頁面 ▶ Post-it - build with React

分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.