今天我們要將原本寫死的 Todo List 資料轉為使用實際輸入的內容。由於在先前的篇章已經使用過 useState
,這次我們將透過 useRef
來提取資料,藉此練習不同的狀態管理方法。
首先,先引入 useRef
並將變數指定給對應的 input
元素:
import { useRef, type FormEvent } from 'react'
export default function CreateTodo() {
const newTodo = useRef()
const createTodoHandler = (event: FormEvent) => {
event?.preventDefault()
}
return (
<form className='w-full mb-[20px] h-fit flex' onSubmit={createTodoHandler}>
<label htmlFor='newTodo'></label>
<input
ref={newTodo}
type='text'
id='newTodo'
name='newTodo'
className='flex-1 mr-[16px] px-3 rounded-[5px] focus:outline-none'
/>
<button>Create</button>
</form>
)
}
當滑鼠移至 ref
上時,可以發現目前的型別是 undefined
:
這是因為我們沒有為該 ref
指定初始值,指定初始值後即可解決該報錯:
const newTodo = useRef(null)
接著,在 createTodoHandler
中建立一個 enteredTodo
變數來儲存輸入的內容:
const createTodoHandler = (event: FormEvent) => {
event?.preventDefault()
const enteredTodo = newTodo.current.value
}
這時會出現第一個錯誤訊息:'newTodo.current' is possibly 'null'.
。由於我們確定這個值會存在,所以可以在 newTodo
後加上 !
,這表示我們保證 newTodo.current
不會是 null
。但使用 !
要特別謹慎,因為當值為 null
時會導致錯誤:
const enteredTodo = newTodo.current!.value
接著,第二個錯誤訊息出現:Property 'value' does not exist on type 'never'.
。雖然我們知道 newTodo
會指向 <input>
元素,但 TypeScript 不知道。這時我們需要在 useRef
中傳入正確的型別,即 HTMLInputElement
:
const newTodo = useRef<HTMLInputElement>(null)
現在我們已經處理好輸入內容的接收。接下來,我們要讓這些內容真正成為 Todo List 上的項目。 還記得我們在 App.tsx
中的 createTodoHandler
嗎?
const createTodoHandler = () => {
const newTodo: TodoItem = {
id: Math.random(),
title: 'Learn JavaScript',
isFinished: false,
}
setTodos((prevTodos) => [...prevTodos, newTodo])
}
我們要將這個函式傳入 CreateTodo
元件,用來更新 Todo List 的資料。 首先,為 CreateTodo
創建一個 props
的型別:
type CreateTodoProps = {
onCreateTodo: (title: string) => void
}
接著,將 onCreateTodo
放入提交表單的函式中:
const createTodoHandler = (event: FormEvent) => {
event?.preventDefault()
const enteredTodo = newTodo.current!.value
onCreateTodo(enteredTodo)
}
回到 App.tsx
,為 createTodoHandler
添加 title
參數,並將寫死的內容替換成傳入的 title
:
const createTodoHandler = (title: string) => {
const newTodo: TodoItem = {
id: Math.random(),
title: title,
isFinished: false,
}
setTodos((prevTodos) => [...prevTodos, newTodo])
}
最後,將 createTodoHandler
傳遞給 CreateTodo
元件:
<CreateTodo onCreateTodo={createTodoHandler} />
打開瀏覽器,現在你應該可以根據輸入的內容更新 Todo List:
目前新增的資料後,表單中的舊資料仍會保留在輸入欄位中。為了清空這些舊資料,我們可以使用內建方法 reset()
來重置表單:
event.currentTarget.reset()
錯誤訊息 Property 'reset' does not exist on type 'EventTarget & Element'.
表示 TypeScript 不知道 event
是表單元素。為了解決這個問題,我們需要將事件的型別明確定義為 HTMLFormElement
:
const createTodoHandler = (event: FormEvent<HTMLFormElement>) => {
event?.preventDefault()
const enteredTodo = newTodo.current!.value
onCreateTodo(enteredTodo)
event.currentTarget.reset()
}
經過這些設定後,每當新增一個 todo
,輸入欄位內的資料將會自動清空。