跳至主要内容

Apollo Client Cache

What's Apollo client cache?

Apollo client cache為Apollo client內建的cache,可以用來儲存一些重要資料,像是歷史的query response。

搭配適當的fetch policy後,對於一些不常變動的資料,我們可以把它存在apollo cache裡面,下次query的時候就不必再去跟server溝通。

Relationship Diagram

  • client, cache與server間的交互關係如下:

How data stored in cache?

文件提到Apollo cache把資料存成 flat lookup table,可以想成是由多組的key-value pair組合而成的。 因為apollo cache主要用來存取 query response,所以可以把key想成是query本身(包含query 的arguments),而value就是response。

E.g.

  • key: GetBook(bookId: "5")
  • value: { Books: { id: "3", name: "heaven" } }

除此之外,Apollo cache內建還有做一個優化,叫做 data normalization。 做了 data normalization 的好處為: 當程式中有不同query,但response內可能會有重複的object時,可以減少儲存空間。

E.g.

  • Query 1: Get All Books

    • Return
    {
    data: {
    "books": [
    {
    id: 1,
    name: "death"
    },
    {
    id: 2,
    name: "heaven"
    }
    ]
    }
    }
  • Query 2: Get one book that id equals to 2.

    • Return
    {
    data: {
    "books": {
    id: 2,
    name: "heaven"
    }
    }
    }
  • Both queries' response contains { Books: { id: "2", name: "heaven" } }.

  • 因此,Apollo cache在作完normalization後,儲存格式會變成類似下面這樣結構:

    (預設會使用object的id field當作key)

`query1`: [
{
"__ref": "Book:1"
},
{
"__ref": "Book:2"
}
]
`query2`: {
"__ref": "Book:2"
}

而在另外的地方(ROOT)則會存真正的object內容在一個flat lookup table

{ 
"Book:1" : {
id: "1",
name: "death"
}
},
{
"Book:2" : {
id: "2",
name: "heaven"
}
},

因此當cache讀到 __ref時,就會去ROOT lookup table找到相對應真正的object。這樣在存取相同object時,就避免了存取多個重複object、浪費資源。

Cache Fetch policy

講完了Cache的儲存方式,再來講Cache何時會被讀取。這裡就要提到重要的Fetch policy了

當我們在Query時,可以情境指定不同的fetch policy

  1. cache-first (default policy)
    1. Query先去cache查看是否有完整的資料,若有則立刻回傳。若沒有則再去query graphql server,並把結果更新回cache內。
  2. cache-only
    1. Query只會去cache拿資料。如果cache內沒有完整資料則拋出error。
  3. cache-and-network
    1. 同時去cache跟server拿資料。
    2. 若cache有資料則先回傳cache的資料
    3. 接著當server回傳資料後,比對cache內跟server回傳的資料。若有不同則以server的為主,並把結果更新回cache,再回傳client。
  4. network-only
    1. Query先跟server拿資料
    2. 拿到資料後更新回cache,並回傳給client
  5. no-cache
    1. Query跟server拿資料
    2. 拿到後直接回傳給client (不存cache)
  6. standby
    1. Query先去cache查看是否有完整的資料,若有則立刻回傳。若沒有則再去query graphql server。
    2. 與cache-first不同,不會把結果更新回cache內。

Cache behavior

那Apollo client是何時會被讀或是寫入呢?

當然,Apollo client有提供api,可以讓developer在application裡面可以對cache做讀寫等操作。

而為了方便使用,預設行為(Query / Mutation )就會自動對cache去做讀寫了。

  • Query
    • 若query使用了會存到cache的fetch-policy,則在Query拿到資料後就會把response寫到cache裡面。
  • Mutation
    • 若Mutation成功後,有回傳完整更新過後的object,則Apollo client會自動把更新後的object寫回cache裡面。
  • 更新的預設行為:
    • 若舊 object跟新object有重複的field,則以新object為主
    • 若舊 object或新object有不重複的field,則保留。

在某些情況下,我們可能需要去客製化資料要如何被存到、寫入cache裡面。

  • E.g. Pagination: 我們會希望第一頁的資料跟第二頁的資料都保留住,不會覆蓋彼此。 這時候可以使用Apollo client提供的功能: Field Policy
  • merge: 指定當寫入cache時該如何存取
  • read: 指定當讀取cache時該如何讀取

E.g. 讀取cache field時,自動轉換成大寫。

const cache = new InMemoryCache({
typePolicies: {
Person: {
fields: {
name: {
read(name) {
// Return the cached name, transformed to upper case
return name.toUpperCase();
}
}
},
},
},
});

E.g. 把新資料跟舊資料的陣列合併 (Pagination)

const cache = new InMemoryCache({
typePolicies: {
Agenda: {
fields: {
tasks: {
merge(existing = [], incoming: any[]) {
return [...existing, ...incoming];
},
},
},
},
},
});

最後關於cache要提的就是 key arguments。 預設行為中,Apollo cache會將每個不同query(包含arguments)視為不同的key。

假設今天想把同一類型的query response都存在同一個cache key裡面,,就必須要指定 key arguments。

E.g. 有個Pagination query,想把

Reference