21 分鐘學 apollo-client 系列:apollo store 存儲細節

21 分鐘學 apollo-client 是一個系列,簡單暴力,包學包會。javascript

搭建 Apollo client 端,集成 redux
使用 apollo-client 來獲取數據
修改本地的 apollo store 數據
提供定製方案html

寫入 store 的失敗緣由分析和解決方案java

Apollo 集成 Redux 的原理

Apollo 僅僅是在 Redux 下開闢了一個 reducer,好比就叫 apollo。apollo 內部經過本身的私有 action (沒有暴露給開發者)來更新這個 reducer 。
至關於這個 reducer 就是 Apollo 本身維護的 store ,它將全部經過 GraphQL query 獲得的數據保存在這裏。react

咱們只能經過如下幾種辦法來修改 apollo storeredux

  • query 成功後,經過 updateQuery 回調修改 store
  • 幾個有限的命令式接口
  • Mutation

第二種方式,雖然接口是命令式的,但並非直接修改 state 的值,背後本質是在調用它內部私有的 action ,最終仍是以 dispatch 的形式修改 store。只是這個過程對開發者是屏蔽的。
固然你必須提供對應的 GraphQL Schema (一段用 gql 語法描述的 query 或 fragment),最終的數據結構若是不符合 Schema ,會 靜默 失敗。
更具體的解釋和運用,看 修改本地的 apollo store 數據 一節。segmentfault

Apollo 的數據存儲

可能你會問,既然 Apollo 的 store 是存在 redux 的 store 中的,本身寫 reducer 去改不就行了嗎?
這很容易想到,但不容易實現。後端

咱們看看 apollo store 中數據存儲的結構:api

Apollo_Store_Preview.png

很像 normalizr 對不對?緩存

簡單說,apollo store 中存儲的是扁平化的緩存。數據結構

當你想要直接修改 reducer 數據時,你須要

  • 手動計算出對應想去修改的 reducer 的 key
  • 當須要處理一個多層嵌套的實體時,還須要根據其嵌套的其它 __typename 找出其它嵌套的 reducer。這個過程也是遞歸的。

因此,手動寫 reducer 去更新 apollo store 會至關麻煩。

扁平化數據

展開來講的話,Apollo 和 normalizr 之類的數據扁平化方案同樣,只是一切都被自動化了,省去了你用 normalizr 手寫的體力活,算是爲數很少的驚喜了。

若是你沒有接觸過 normalizr ,那硬要用 reducer 的術語來描述的話,咱們能夠把 apollo 這個 reducer 視爲一個 store。
在這個 store 中, 每個存入 store 的實體都以 __typename:id 的方式單獨存放到一個 reducer 中,__typename 取自於你請求時使用的 GraphQL Schema,如 UserTimeline:260
若是你從後端接收到一組 UserTimeline ,那麼其中每一項都會在 store 裏註冊一個 reducer ,可能會出現 UserTimeline:1 ~ UserTimeline:100 的盛景。當你在別的請求中再請求到 UserTimeline:260 的時候,就直接 merge 到原有的 reducer 中。

你可能說這樣很好啊,直接根據這個 key 訪問對應的 state 就能夠了。但問題是,凡是嵌套結構,都會被抽出來單獨做爲一個 reducer。
比方說,上圖中 UserTimeline 包含一個 userInfo, 它的 __typenameUserInfo,那麼 UserTimeline:260 下的 userInfo 中存儲只是對應的 reducer 索引,形如

{ id: 'UserInfo:1004', generated: false, ...}

真實的 UserTimeline:260.userInfo 存儲在一個名爲 UserInfo:1004 的 reducer 中。而 UserInfo:1004 可能也並不完整,由於它內部也可能存在嵌套,也須要經歷這樣的一次搜尋過程。要一直遞歸下去,咱們才能獲得最終的完整數據。

id 的生成規則

Updating the Store | Apollo React Docs

根據官方文檔的說法,apollo 在建立 apollo client 時,可選設置 dataIdFromObject。

const client = new ApolloClient({
    networkInterface,
    dataIdFromObject: x => `${x.__typename}:${x.id}`,
});

若是不設置 dataIdFromObject ,其默認就是 ${x.__typename}:${x.id}
若是 x 不存在 id,則可能出現 ${__typename}:${id}.${property}.${subProperty}

相關文章
相關標籤/搜索