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