iT邦幫忙

2022 iThome 鐵人賽

DAY 27
0

昨天看了有趣的useReducer,今天讓我們看看React當中可以說是最方便的工具:Custom hook(客製化hook)吧。

Custom hook是個必須以"use"作為開頭的函式,在這函式內部我們可以使用各式React hook如useStateuseEffect,且我們不需回傳jsx,可以只回傳值就好,以便在整個codebase當中重複利用這個hook(鉤子,所以希望能"掛"在任何元件上)。

BTW,如果你在一個不是以"use"開頭的函式(ex: function sayHello)內部呼叫useState/useEffect等hook,你會得到這類錯誤,說明你如果要寫元件,開頭就要大寫,如果是要寫hook,則開頭一定要用use:

React Hook "useState" is called in function "sayHello" that is neither a React function component nor a custom React Hook function. React component names must start with an uppercase letter. React Hook names must start with the word "use".

讓我們開始今天的內容吧:

Custom hook!

直接用個最淺白的例子:"打api",來說明吧!

我們有很高的頻率會在多個元件內打api,依照拿回來的資料去渲染UI,但如果我們都得在每個元件內的useEffect當中去輸入
fetch/axios("randomurl").then.then.catch.finally
(我沒有正確打完,但你應該知道這有多繁複)
想必是件累人的事,就讓我們用custom hook包裝重複的code吧!

我們今天的範例因為是打api,會借助下面這個公開api的資源,如果你在練習時發現api沒能正常運作的話,就只能請你找找其他免費api來試試囉,邏輯都相通的:

https://www.boredapi.com/api/activity/

我們可以繼續在App.tsx內空白處宣告hook,但更常見的做法是拉到獨立的資料夾內,創一個獨立的檔案來放置code,這給你決定,我一樣放在App.tsx

import {useState, useEffect} from 'react'

const useFetchData = (url) => {
  const [data, setData] = useState(null)
  const [done, setDone] = useState(false)

  useEffect(() => {
    fetch(url)
      .then(resp => resp.json())
      .then(data => {
        setData(data)
        setDone(true)
      })
  }, [url])

  return { data, done }
}

先來個基本版的,我們宣告useFetchData這個hook,他會根據使用者提供的url去打api,內部使用data陣列來放置api回傳的資料(初始值是null),再用一個flagdone來標示打完api與否。這邊一定都很簡單,讓我們加上TypeScript吧!

//略過import

//打過一次api就會知道回傳物件的形狀
//可以直接宣告一個interface,晚點使用
interface Activity {
  activity: string
  type: string
  participants: number
  price: number
  link: string
  key: string
  accessibility: number
}

//url一定會是字串,所以這邊宣告型別為字串,沒問題
//回傳值的部分,我們知道會是完整的Activity或是null,所以也放進來
//done理所當然是boolean值
const useFetchData = (url: string): { data: Activity | null; done: boolean } => {
    //data接受Activity型別或null
  const [data, setData] = useState<Activity | null>(null)
  const [done, setDone] = useState(false)

  useEffect(() => {
    fetch(url)
      .then((resp) => resp.json())
    //這邊也確定拿到的型別一定會是Activity
      .then((data: Activity) => {
        setData(data)
        setDone(true)
      })
  }, [url])

  return { data, done }
}

再讓我們將useFetchData掛進App元件吧:

const App = () => {
  const { data, done } = useFetchData("https://www.boredapi.com/api/activity/")
    //因為done是true,表示一定有資料回來
    //所以將data進行非null斷言,加上驚嘆號
  return <>{done && <div>{data!.activity}</div>}</>
}

打完api後的UI

就這樣,你順利將custom hook加上了TypeScript!
不過很明顯的,這個custom hook並沒有這麼好用,只能一直拿來fetch activity,不能拿來fetch其他東西,畢竟我們已經限制了他回傳的型別了。既然這樣,就使用我們前陣子提到的泛型吧!

我們等等就把Activity型別從hook中拆出來,給他一個泛型型別,這樣我們就能在"呼叫"時,才來決定回傳的型別應該長怎樣!

//這邊我被衝康好久
//在jsx當中如果只將泛型以<T>呈現,他會說你沒有closing tag
//(編輯器以為你在寫jsx)
//所以要在型別T後面加個逗號",",讓編輯器安靜
//並把剛剛有明確寫出Activity的地方都換成T
//T可以隨你命名,跟函式的參數一樣
const useFetchData = <T,>(url: string): { data: T | null; done: boolean } => {
  const [data, setData] = useState<T | null>(null)
  const [done, setDone] = useState(false)

  useEffect(() => {
    fetch(url)
      .then((resp) => resp.json())
      .then((data: T) => {
        setData(data)
        setDone(true)
      })
  }, [url])

  return { data, done }
}

const App = () => {
    //噢耶,我們在呼叫時再決定型別就好
  const { data, done } = useFetchData<Activity>(
    "https://www.boredapi.com/api/activity/"
  )

  return <>{done && <div>{data!.activity}</div>}</>
}

就這樣,你不僅學會怎麼為custom hook加上TypeScript,甚至還複習到了泛型,還不錯吧!
特定將型別從Activity改為T後,我們甚至不用局限於單一個物件,也能帶入如Dish[]這樣的型別,呈現出一整個陣列的data,用途更加的廣泛了!

那我們明天見啦,剩幾天而已了!


上一篇
第26天!TypeScript與useReducer!
下一篇
第28天!function/arrow function component與泛型組合技練習!
系列文
你也對開始使用typescript感到無力嗎?我也是 - 30天初探typescript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言