iT邦幫忙

2024 iThome 鐵人賽

DAY 20
0
自我挑戰組

React 開發者的 TypeScript 探索之旅系列 第 20

【 Day 20 】useRef with TypeScript

  • 分享至 

  • xImage
  •  

本系列文章 GitHub

今天我們要將原本寫死的 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
https://ithelp.ithome.com.tw/upload/images/20240930/20169025BxSHlIIOO1.png

這是因為我們沒有為該 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:

https://ithelp.ithome.com.tw/upload/images/20240930/20169025RI6i5poTLb.png

目前新增的資料後,表單中的舊資料仍會保留在輸入欄位中。為了清空這些舊資料,我們可以使用內建方法 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,輸入欄位內的資料將會自動清空。


上一篇
【 Day 19 】FormEvent
下一篇
【 Day 21 】在 TypeScript 中使用 setState 進行狀態管理
系列文
React 開發者的 TypeScript 探索之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言