21 分鐘學 apollo-client 是一個系列,簡單暴力,包學包會。
搭建 Apollo client 端,整合 redux
使用 apollo-client 來獲取資料
修改本地的 apollo store 資料
提供定製方案
大坑 - 寫入資料失敗
同志們注意啦!整個 apollo 的坑都集中在這裡啦!
一旦返回的資料寫入 apollo store 失敗,會 靜默失敗 ,最終你發現怎麼資料沒有發生任何變化? (坑爹啊!)
一個簡單的辦法是,你去 node_modules 下,把報錯資訊暴露出來。是的,經過閱讀原始碼,我發現這已經是最簡單的辦法了...
具體程式碼在 node_modules\apollo-client\data\writeToStore.js
如果這解決了你的問題,請給我打賞,謝謝。
寫入失敗的原因
那什麼時候會寫入失敗呢?
1. 寫入的資料,其結構不符合 query schema
這意味著你不能基於 query schema 新增一些你本地需要的資料,比如 isSelected 什麼的
2. 丟失 __typename
資訊
不能丟失任何層級上的 __typename
!
當你 log fetchMoreResult
你會發現,它在很多層級上附帶了 __typename
,這個資料用於檢查寫入資料是否匹配 query schema。
一旦你丟失了 __typename
,可能會導致寫入失敗,或者儘管寫入了,但本該攜帶 __typename
的那一層的資料沒有寫入。
你可能說,那我當心一點,總是使用 Object.assign
或者 { ...obj, a: 1 }
這種 spread 賦值的方式吧。
年輕人,你還是太天真啊!
如果你得到一組資料,巢狀的某一個值為 null
,那麼它本來就不攜帶 __typename
!
如果這個值為 null
的資料在你的 query schema 中確實需要 __typename
,那即使你對這個資料重新賦值,也無法被寫入到 apollo store 中,因為缺少 __typename
資訊。
上面這段話比較繞,給你看個例子。
假設你有這樣一個 query
query {
user { # typename 為 User
id
nickname
statistic { # typename 為 UserStatistic
upvoted
}
}
}
然後,得到一組資料
const prev = {
user: {
__typename: 'User',
id: 1,
nickname: 'apollo 真是坑爹啊',
statistic: null
}
}
為了 ui 顯示正常,你會給 statistic 設定一些預設值
const next = {
...prev,
user: {
...prev.user,
statistic: {
upvoted: false,
}
}
}
如果你將 next 資料 writeToStore 你就會就發現 statistic 還是為 null
,這些資料還是沒有被寫入 apollo store。
這是因為,user.statistic
在 schema 中定義的 type 是 UserStatistic
,而原始資料由於為 null
,所以本該自動附加的 __typename
屬性也不存在。這導致你在寫入 store 的時候,缺少了必要的 __typename
資訊,apollo 將拒絕接受。
很扯吧。
要讓上面的程式碼 work 起來,你需要
const next = {
...prev,
user: {
...prev.user,
statistic: {
upvoted: false,
+ __typename: 'UserStatistic',
}
}
}
總結
如果沒有報錯訊息的話,上面這種情況是極其難以排查的,尤其是我們特別容易遇到缺少 __typename
的場景。
為了減少此類事故的發生,你可以
- 和後端協商,不要返回 null 值
- 不論何時何地,何種層級,永遠給資料手動新增
__typename
- 不用 apollo