前面認識 useState() 時,看過這種寫法:
const [text, setText] = useState('')
不過當 State 要保存使用者資料時,可能會看到:
const [user, setUser] = useState<User | null>(null)
useState 後面多出來的 <User | null>,不是 JSX,也不是一個叫作 User 的 Component,而是 TypeScript 的 Generic,也就是「泛型」寫法。
目前可以先把它理解成:告訴 useState(),這份 State 準備存放什麼型別的資料。
User 是什麼在使用 User 之前,需要先定義它的資料格式:
interface User {
id: number
name: string
}
這表示一份 User 資料必須包含數字型別的 id,以及字串型別的 name。
把它交給 useState() 之後:
const [user, setUser] = useState<User | null>(null)
user 就可能是「一份符合 User 格式的資料」,也可能是 null,代表目前還沒有使用者資料。
這樣之後不管是:
setUser({ id: 1, name: '小明' })
還是:
setUser(null)
都符合這個型別。但如果傳進去的物件少了 name,TypeScript 就會提醒缺少這個屬性。
useState() 可以保存很多不同種類的資料,字串、數字、布林值,甚至是 User 物件都可以。
如果 useState() 只能綁定某一種固定型別,它就很難在不同情況下重複使用。
泛型讓 useState() 可以先保留一個型別的位置,再由使用它的人決定這次要放入什麼型別。
可以把它想成一個標示內容物的盒子:
useState<string>:放字串useState<number>:放數字useState<User | null>:放 User 或 null
所以 <User> 並不是建立一份 User 資料,而是在指定這次 useState() 使用的型別。
TypeScript 官方文件:Generics
useState() 都要寫尖括號嗎?不一定。TypeScript 具有型別推論能力,可以根據初始值判斷 State 的型別。
例如 useState('') 的初始值是字串,TypeScript 就能推論出 string;同樣 useState(0)、useState(false) 也能分別推論出 number 和 boolean。
型別很明確時就不需要特地寫成:
useState<string>('')
null 時要寫型別?如果只寫成:
useState(null)
TypeScript 看到的初始值只有 null,可能會把這份 State 推論成只能存放 null。之後想放入 User 資料時,就會被 TypeScript 標示型別錯誤。
因此需要明確告訴 TypeScript,這份 State 不只可以是 null,之後也可以放入 User:
const [user, setUser] = useState<User | null>(null)
這也是 <User | null> 常見的使用情境:初始狀態還沒有資料,但之後會放入一份物件。
React 官方文件:在 React 中使用 TypeScript
useState<Todo[]>([])const [todos, setTodos] = useState<Todo[]>([])
Todo[] 表示這是一個只能放入 Todo 資料的陣列,[] 則是 State 的初始值,代表一開始還沒有任何 Todo。
如果只寫:
useState([])
空陣列裡沒有任何資料,TypeScript 很難從內容判斷之後要放入哪種型別的項目,所以專案才明確寫上:
useState<Todo[]>([])
這樣之後呼叫 setTodos() 時,TypeScript 就會檢查陣列中的每個項目是否符合 Todo 的格式。
<User> 和 JSX 的標籤不一樣第一次看到 <User>,很容易聯想到 React Component 的 <User />,但兩者的位置與用途不同。
寫在函式名稱後面的:
useState<User>
其中的 <User> 是 TypeScript 的型別資訊,用來指定這份 State 保存的資料型別。
寫成:
<User />
則是 JSX,表示要使用名為 User 的 Component。
只要注意它出現的位置,就能分辨這兩種不同的用途。
useState<User> 裡的 <User> 是 TypeScript 的泛型寫法,用來告訴 useState() 這份 State 要保存什麼型別<User> 不會建立 User 資料,也不是 JSX Componentnull 時,可以使用 User | null 表示之後也能放入 UseruseState<Todo[]>([]) 表示建立一份初始值為空陣列的 Todo StateuseState() 後面的尖括號,是在提前告訴 TypeScript:「這份 State 準備保存什麼型別的資料。」
所以即使初始值只有 null 或空陣列,TypeScript 也能知道之後可以放入 User 或 Todo 資料。
下篇見~