iT邦幫忙

2024 iThome 鐵人賽

DAY 16
0
自我挑戰組

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

【 Day 16 】使用 useState 儲存 Todo 項目

  • 分享至 

  • xImage
  •  

本系列文章 GitHub

今天我們將為 Todo List 加入新增 Todo 的功能。暫時先將此功能直接放在 App.tsx 中,後續我們會進行優化,將其獨立成一個元件。請先將 App.tsx 的內容修改如下:

import './App.css'
import Header from './components/Header'
import Todo from './components/Todo'
import logo from './assets/logo.png'

function App() {
  const createTodoHandler = () => {
  }
  
  return (
    <main className='w-[500px] h-[100dvh] portrait:w-[90%] flex flex-col'>
      <Header image={{ src: logo, alt: 'logo' }}>
        <h1>Todo List</h1>
      </Header>
      <div className='w-full mb-[20px] h-fit flex'>
        <label htmlFor='newTodo'></label>
        <input
          type='text'
          id='newTodo'
          name='newTodo'
          className='flex-1 mr-[16px] px-3 rounded-[5px] focus:outline-none'
        />
        <button onClick={createTodoHandler}>Create</button>
      </div>
      <Todo isFinished={false}>
        <p>Learn typeScript</p>
      </Todo>
    </main>
  )
}

export default App

這裡我們將焦點放在 TypeScript 的應用上,對於 React 語法和樣式部分就不再詳述。

當前畫面:
https://ithelp.ithome.com.tw/upload/images/20240926/20169025dyRdegqxNF.png

接下來,我們將使用 useState 來儲存 todos。首先,將狀態初始值定義為一個空字串,試著觀察它的型別推斷:

const [todos, setTodos] = useState('')

將滑鼠移到 todos 上方,可以看到 IDE 推斷其型別為字串:
https://ithelp.ithome.com.tw/upload/images/20240926/20169025uIafv6clWH.png

todos 應該是多筆資料的集合,每筆資料包含 idtitleisFinished 屬性。因此,我們需要將狀態的初始值設定為一個陣列:

const [todos, setTodos] = useState([])

這邊需要設定 id 是因為我們會使用 map 方法來處理資料,而使用 map 產生列表,需要指定唯一的 key,這是 React 本身的規範,這邊的 id 便是為了 key 而設。
如需了解更多 React key 的相關內容,可以參照官方文件

當我們宣告這樣的狀態後,將滑鼠移到 todos 上,會發現 IDE 顯示 todos 的型別為 never[]
https://ithelp.ithome.com.tw/upload/images/20240926/20169025EJrkLg6UIy.png

這是因為我們只給了空陣列,並未指定陣列內元素的型別。因此,我們需要為 useState 提供一個泛型,告訴它應該存儲什麼型別的資料。定義 TodoItem 型別:

type TodoItem = {
  id: number
  title: string
  isFinished: boolean
}

接著,將 TodoItem 型別放入 useState 的泛型參數中:

const [todos, setTodos] = useState<TodoItem[]>([])

這也可以寫成:

const [todos, setTodos] = useState<Array<TodoItem>>([])

雖然兩種寫法都正確,但基於可讀性,我們會採用第一種方式。

現在,讓我們在 createTodoHandler 中建立一筆假資料,並更新 todos 狀態:

const createTodoHandler = () => {
  const newTodo: TodoItem = {
    id: Math.random(),
    title: 'Learn JavaScript',
    isFinished: false,
  }
  setTodos((prevTodos) => [...prevTodos, newTodo])
}

原先我們渲染 Todo 元件的方式是:

<Todo isFinished={false}>
  <p>Learn typeScript</p>
</Todo>

現在,我們需要用 map 方法來渲染多筆 Todo 資料:

<ul>
  {todos.map((todo) => (
    <li key={todo.id} className='list-none'>
      <Todo isFinished={todo.isFinished}>
        <p>{todo.title}</p>
      </Todo>
    </li>
  ))}
</ul>

重新整理頁面後,你會看到一個空的 Todo List:

https://ithelp.ithome.com.tw/upload/images/20240926/20169025kwZGWpkRXW.png

點擊 Create 按鈕後,剛才建立的假資料會顯示在列表中:

https://ithelp.ithome.com.tw/upload/images/20240926/20169025JbiPTOyPPH.png


上一篇
【 Day 15 】為 Todo List 加上 Header
下一篇
【 Day 17 】匯出型別
系列文
React 開發者的 TypeScript 探索之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言