iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
佛心分享-IT 人職涯歷練

從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始系列 第 9

Day 09|useRequest 到底在幹嘛?先追 API 從哪裡出去、資料又跑去哪裡

  • 分享至 

  • xImage
  •  

上一篇提到 useEffect 時,我一直在問:

這段程式到底是在什麼時候做事情?

到了實際 React 專案,又很常看到另一種寫法:

const { loading, run } = useRequest(
  async () => {
    ...
  },
  {
    manual: true,
    onSuccess: result => {
      ...
    },
  }
);

以前看到這種程式碼,我的第一個反應通常是:

useRequest 是什麼?
loading 是什麼?
run 又是什麼?
manual 又是什麼?
onSuccess 又跑去哪?

結果每一個都查完之後,還是不知道:

這支 API 到底什麼時候被打出去?

後來我才慢慢改成另一種讀法。

先不要把每一個參數都研究完。

先追:

誰觸發 Request?
↓
Request 打哪支 API?
↓
Response 回來之後去哪裡?
↓
最後影響什麼畫面?

先看一個實際很常見的結構

例如:

const { loading: loadingInvoice, run: loadInvoice } =
  useRequest(
    async () => {
      const id = resolveInvoiceId(location.search);

      if (id) {
        return InvoiceService.getById(id);
      }

      throw Error("No Invoice Id");
    },
    {
      manual: true,
      onSuccess: result => {
        setInvoice(result);

        form.setFieldsValue({
          discount: result.discount,
        });
      },
      onError: error => {
        notification.error({
          message: error.message,
        });
      },
    }
  );

第一次看到這整段,真的很容易直接放棄。

所以還是先不要逐行讀。

先找最重要的四個位置:

① Request 怎麼被觸發?

② API 在哪裡?

③ Response 是誰?

④ Response 最後去了哪?

第一步:先找 API 在哪裡

我通常會先掃這一段:

return InvoiceService.getById(id);

看到:

Service.getById(...)

就可以先猜到:

這裡很可能是在取得某一筆資料。

所以先畫:

InvoiceService.getById(id)
↓
發出 Request
↓
取得 Invoice 資料

至於:

InvoiceService

裡面真正的 URL 是什麼、用的是 axios 還是其他 HTTP Client,

可以等真的需要時再往下追。

第一輪先知道:

API 從這裡出去。

就夠了。


id 又是哪裡來的?

往上一點:

const id = resolveInvoiceId(location.search);

所以:

location.search
↓
resolveInvoiceId()
↓
id

如果網址可能是:

...?invoiceId=123

那這裡的目的就是從目前 URL 資訊中取得需要的 id。

接著:

if (id) {
  return InvoiceService.getById(id);
}

就變成:

網址
↓
取出 id
↓
有 id
↓
getById(id)
↓
呼叫 API

原本一大段程式突然就剩一條線。


return 回來的東西去哪?

接下來這個問題很重要。

return InvoiceService.getById(id);

API 成功後取得的結果,會進到:

onSuccess: result => {
  ...
}

所以可以先畫:

InvoiceService.getById(id)
↓
API Response
↓
result
↓
onSuccess

這時候:

result

就是我們從 Request 成功結果裡拿到的資料。


Response 回來之後做了兩件事

onSuccess

onSuccess: result => {
  setInvoice(result);

  form.setFieldsValue({
    discount: result.discount,
  });
}

先拆第一個:

setInvoice(result);

可以理解成:

API Response
↓
result
↓
setInvoice()
↓
存進 React State

所以:

invoice

之後可能會被畫面其他地方使用。


第二個:

form.setFieldsValue({
  discount: result.discount,
});

這裡不是把整份 Response 放進 Form。

而是從:

result

裡面拿:

result.discount

再設定到表單的:

discount

欄位。

所以:

API Response
↓
result.discount
↓
form.setFieldsValue()
↓
Form 的 discount 欄位

到這裡其實已經能畫出完整資料流:

URL
↓
resolveInvoiceId()
↓
id
↓
InvoiceService.getById(id)
↓
API
↓
Response
↓
result
├─→ setInvoice(result)
│    ↓
│   State
│
└─→ result.discount
     ↓
   setFieldsValue()
     ↓
   Form

這張圖比背 useRequest 語法重要很多。


run 是什麼?

前面有:

const {
  loading: loadingInvoice,
  run: loadInvoice,
} = useRequest(...);

這裡先看:

run: loadInvoice

意思是把 useRequest 提供的:

run

重新命名成:

loadInvoice

所以程式其他地方可能會看到:

loadInvoice();

這時候我現在就知道:

這很可能是在手動觸發剛才那個 Request。

也就是:

loadInvoice()
↓
執行 useRequest 裡的 async function
↓
取得 id
↓
getById(id)
↓
API Request

為什麼需要 manual: true

這個例子裡還有:

manual: true

在這類 useRequest 寫法中,可以先把它理解成:

不要建立後就立刻自動執行,我要自己決定什麼時候 run()

所以:

manual: true

搭配:

loadInvoice();

資料流會比較像:

建立 useRequest
↓
先不打 API

某個地方呼叫
loadInvoice()
↓
Request 才開始

這就跟上一篇 useEffect(() => fetchData(), []) 的概念有點不同。

上一篇:

Component Render
↓
Effect
↓
Request

這裡可能是:

某個事件 / 某段流程
↓
loadInvoice()
↓
Request

所以看到 useRequest 時,我現在會特別去找:

loadInvoice()

到底出現在哪裡。

因為:

找到 run(),通常就找到「誰真正觸發這支 API」的重要線索。


loading 又是哪裡來的?

這段:

loading: loadingInvoice

同樣是在重新命名。

可以先理解成:

useRequest 提供 loading
↓
重新命名
↓
loadingInvoice

它通常用來表示:

Request 還在進行中嗎?

畫面可能會用:

loading={loadingInvoice}

或:

<Button loading={loadingInvoice}>
  Submit
</Button>

所以:

Request 開始
↓
loading = true

Request 結束
↓
loading = false

先理解用途即可,不用一開始研究它背後怎麼實作。


如果 Request 失敗呢?

這個例子還有:

onError: error => {
  notification.error({
    message: error.message,
  });
}

所以整體不只有成功路線。

還有:

Request
↓
成功?
├─ Yes
│   ↓
│ onSuccess
│   ↓
│ 更新 State / Form
│
└─ No
    ↓
  onError
    ↓
  顯示錯誤訊息

這也是讀 Request 時很好用的一個方式:

不要只追 Happy Path,也找一下錯誤去哪裡。


我現在讀 useRequest,不會從第一行一路硬啃

以前:

useRequest 是什麼?
↓
async 是什麼?
↓
manual 是什麼?
↓
formatResult 是什麼?
↓
onSuccess 是什麼?
↓
onError 是什麼?

查完一輪,知識增加了,理解沒有增加。

現在我比較會先找:

① 誰觸發?
→ run / loadInvoice

② 打哪支 API?
→ InvoiceService.getById(id)

③ API 需要什麼?
→ id

④ Response 回來叫什麼?
→ result

⑤ result 去哪?
→ setInvoice()
→ form.setFieldsValue()

⑥ 失敗去哪?
→ onError

先把主幹畫出來:

loadInvoice()
↓
取得 id
↓
Service.getById(id)
↓
Response
↓
onSuccess
├─ State
└─ Form

再回頭補不熟的語法。


真實專案裡,我會繼續往下追 Service

如果今天問題變成:

「到底實際打了哪個 API?」

那才繼續進:

InvoiceService.getById(id)

找到 Service 裡真正的 Request。

可能會長得類似:

getById(id) {
  return request(`/invoices/${id}`);
}

這時資料流再往下多一層:

Component
↓
useRequest
↓
InvoiceService.getById(id)
↓
HTTP Request
↓
Backend API

所以我現在不會看到 Service 就停住。

而是看目前 Debug 的問題需要追到哪一層。


今天真正要學的不是 useRequest API 大全

不同專案使用的 Request 工具、設定方式可能不同。

今天真正想記的是:

Trigger
↓
Request Function
↓
Service
↓
API
↓
Response
↓
Success / Error
↓
State / Form / UI

所以以後看到:

useRequest(...)

我會先問:

誰讓它開始跑?

接著:

它打去哪裡?

最後:

Response 回來之後,被放去哪裡?

只要這三個問題先找到,

原本看起來很大的 useRequest,就會開始變成一條可以追的資料流。

下一篇可以繼續沿著這條線往 Backend 前進:

Service.getById() 裡到底藏了什麼?一支 API 到底要怎麼從前端一路追到後端?


上一篇
Day 08|useEffect 到底什麼時候執行?為什麼一進頁面 API 就自己打了?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言