iT邦幫忙

2026 iThome 鐵人賽

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

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

Day 12|Input 明明清空了,為什麼 PATCH 後資料還在?

  • 分享至 

  • xImage
  •  

上一篇講到:

undefined
null
""
0

看起來都像「空」,但其實意思完全不同。

而這件事到了表單送 API 時,會直接影響資料到底有沒有被更新。

我以前就很容易遇到這種狀況:

使用者把欄位清空
↓
按 Save
↓
Request 看起來有送
↓
Status 200
↓
重新整理
↓
舊資料又回來

第一個反應通常是:

「是不是 Input 沒有真的清掉?」

但問題其實可能根本不在 Input。

而是在:

Frontend 最後送給 Backend 的值到底是什麼?


先看最簡單的情況

假設原本資料是:

description = "Original text";

使用者把 Input 清空。

前端現在可能拿到:

description = "";

這時候如果送:

{
  description: ""
}

Backend 可能會理解成:

把 description 更新成空字串。

但如果前端最後整理 Request 時,把它轉成:

{
  description: undefined
}

事情就可能完全不一樣。


undefined 有可能讓欄位根本沒被送出去

假設:

const payload = {
  description: undefined,
  name: "Example",
};

在很多 JSON Request 的情況下,序列化:

JSON.stringify(payload);

會得到:

{
  "name": "Example"
}

注意:

description

整個不見了。

也就是 Backend 實際收到的可能不是:

{
  "description": null
}

而是:

{
}

根本沒有 description 這個欄位。


PATCH 最容易在這裡產生誤會

PATCH 通常是在表達:

只修改我這次有提供的欄位。

假設資料庫原本:

description = "Original text"

Request:

{
  "description": "New text"
}

Backend 可以理解:

有 description
↓
更新它
↓
"Original text" → "New text"

但是如果 Request 變成:

{}

Backend 很可能理解成:

這次沒有要求修改 description
↓
保留原值

所以重新 GET:

description = "Original text"

就又回來了。

這時候前端看起來就會像:

明明清空了,為什麼沒有存成功?


null 的意思可能完全不同

如果 API 規格支援:

{
  "description": null
}

Backend 可能會把它理解成:

請明確把這個欄位清空。

資料流就變成:

Database

description = "Original text"

↓ PATCH

{
  "description": null
}

↓ Backend

description = null

這和:

{}

是完全不同的意思。

可以先這樣理解:

undefined / 欄位不存在
→ 我這次沒有提供它
→ 可能代表不要修改

null
→ 我明確提供這個欄位
→ 而且我要它沒有值

""
→ 我明確提供一個空字串

但真正語意還是要看 Backend API 的 contract。


所以清空 Input 不等於清空 Database

這裡是我覺得最容易誤會的地方。

畫面:

Input = ""

只是代表:

Frontend Form 現在顯示空的。

接下來還要經過:

Form Value
↓
Submit Handler
↓
資料整理
↓
Request Payload
↓
Backend
↓
Database

其中任何一步把:

""

轉成:

undefined

最後 Request 就可能不再包含這個欄位。

所以:

UI 清空成功,不代表 Request 一定有送「清空」這個意思。


一個很常見的資料整理邏輯

例如:

const payload = {
  description:
    values.description === ""
      ? undefined
      : values.description,
};

如果使用者清空:

values.description = "";

最後:

description = undefined;

接著 Request 序列化後:

description 可能直接消失

Backend 就不知道:

使用者是想清空它。

它只知道:

這次沒有 description。


如果需求是「清空就傳 null」

那邏輯可能要改成:

const payload = {
  description:
    values.description === ""
      ? null
      : values.description,
};

這時:

Input 清空
↓
values.description = ""
↓
轉成 null
↓
Request

Payload:

{
  "description": null
}

Backend 才有機會理解:

明確清空這個欄位。


但不能看到空字串就全部轉 null

這裡也不能直接下結論:

那以後所有空字串都轉 null 就好了。

不一定。

因為不同 API Contract 可能有不同定義。

例如:

欄位 A

null
→ 清除資料

欄位 B

""
→ 合法的空字串

欄位 C

undefined
→ 不修改

欄位 D

null
→ Backend 不接受

所以真正要先確認的是:

這支 API 對這個欄位的規格到底是什麼?


Debug 時,我現在會直接看 Network Payload

以前我會一直盯:

Input 有沒有清掉?

現在遇到這種問題,我會先開 DevTools:

Network
↓
找到 PATCH Request
↓
看 Request Payload

確認實際送的是:

{
  "description": ""
}

還是:

{
  "description": null
}

還是根本變成:

{}

這一步很重要。

因為它直接把問題切成兩半。


如果 Payload 就已經不對

例如使用者明明要清空,但 Request:

{}

那問題大概率還在 Frontend。

可以繼續找:

Form Value
↓
onFinish / Submit
↓
資料整理 function
↓
payload

看看是哪一段把值吃掉。


如果 Payload 是對的,但資料還是沒清掉

假設 Request:

{
  "description": null
}

完全符合 API 規格。

但重新 GET:

{
  "description": "Original text"
}

那就不用一直改 Input 了。

接下來應該往 Backend 查:

Controller 有收到 null 嗎?
↓
Request DTO 接得住嗎?
↓
Service 有沒有忽略 null?
↓
Entity 有更新嗎?
↓
Repository 有 save 嗎?
↓
Database 最後是什麼?

這就接回 Day 10 的整條資料流。


Backend 也可能故意忽略 null

例如後端邏輯如果寫成類似:

request.description?.let {
    entity.description = it
}

可以先把它理解成:

description 有值
↓
才更新

description = null
↓
這段不執行

這種寫法對某些欄位很合理。

因為 Backend 可能把:

null

理解成:

不更新。

但如果需求本來就是:

null 代表清空,

那這段邏輯就可能需要另一種處理方式。

所以同樣是:

null

前後端一定要對它的意思有共同約定。


這就是 API Contract 的重要性

以前我會把 API 想成:

Frontend 丟資料
↓
Backend 收資料

但真正合作後,會發現中間其實有一份很重要的契約:

這個欄位是必填嗎?

可以 null 嗎?

空字串代表什麼?

欄位沒送代表什麼?

PATCH 時 null 是清除還是不修改?

這些都是:

API Contract

的一部分。

所以如果 Frontend 覺得:

null = 清空

但 Backend 覺得:

null = 不修改

兩邊的程式可能都「沒有語法錯誤」。

結果功能還是錯的。


我現在會怎麼排查這種問題?

假設需求是:

清空 description 並儲存。

我會從最靠近使用者的地方開始:

① Form Value

使用者清空後
到底是:
""
null
undefined?

接著:

② Submit Handler

有沒有重新轉換資料?

再來:

③ Request Payload

DevTools Network
真正送出去的是什麼?

再往後:

④ Backend

API 規格期待什麼?

最後:

⑤ GET Response

儲存之後重新取得
實際回來的是什麼?

完整就是:

UI
↓
Form Value
↓
Submit
↓
Payload
↓
Backend
↓
Database
↓
GET Response
↓
UI

只要一層一層確認,就比一直猜:

「是不是 React 沒更新?」

有效很多。


今天最想記住的是這三個差異

undefined
→ 欄位可能不會出現在 JSON Payload
→ PATCH 時常可能等於「沒要求修改」

null
→ 欄位有送出去
→ 明確表示沒有值
→ 是否代表清空,要看 API Contract

""
→ 一個真正的空字串
→ Backend 是否接受,也要看 API Contract

所以:

Input 清空,只是整條資料流的第一步。

真正要知道有沒有清空成功,還得一路追到:

Request Payload
↓
Backend
↓
Database

而我現在遇到這種 Bug,也不會再只問:

「畫面有沒有清掉?」

而會問:

「清空這件事,在這支 API 裡到底用什麼值表示?」

下一篇可以繼續接另一個我以前很容易踩的坑:

||、??、三元運算子看起來都在給預設值,到底什麼時候該用哪一個?


上一篇
Day 11|null、undefined、空字串、0 到底差在哪?前端為什麼一直在防空值?
下一篇
Day 13|||、??、三元運算子都像在給預設值,到底怎麼選?
系列文
從 UI/UX 麻瓜到工程魔法師|從讀懂程式開始 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言