Apollo Client
基本用法小整理
複習 Apollo 的 cache
Apollo 預設會把 query 的結果 cache 起來
query {
items {
id
name
amount
}
}
之後的 query 如果每個 field 都有在 cache 裡了,預設就不會重抓
如果某個 field 有參數,就會連 query 時的參數一起記下來
query {
items(categoryId: "1") {
id
name
amount
}
}
不一樣的參數就不會搞混
Apollo 預設會把 server 回傳的 objects 都記起來
(包含 Query 這個 object,所以可以記得所有 query)
預設的 cache key 是 object 的 __typename + id
(Query 的話則是 ROOT_QUERY)
每個 object 會有一個唯一的 cache key,
用來辨別 object 是不是同一個,以保持資料的一致性
(也可以在 initialize 的時候給 dataIdFromObject,
指定 object 要對應的 cache key)
像這樣 component 裡的舊 query 結果就會被更新,然後 re-render
query {
items(categoryId: "1") {
id
name
amount
}
}
{
"data": {
"items": [
{
"id": "1",
"name": "A",
"amount": 5
},
{
"id": "2",
"name": "B",
"amount": 5
}
]
}
}
mutation {
consumeItem(id: "1") {
id
name
amount
}
}
{
"data": {
"consumeItem": {
"id": "1",
"name": "A",
"amount": 4
}
}
}
query -> response -> mutation -> response
cache 起來的 data 大概長這樣
{
ROOT_QUERY: {
'items({"categoryId":"1"})': [
{
type: 'id',
id: 'Item:1'
},
{
type: 'id',
id: 'Item:2',
},
],
},
'Item:1': {
id: '1',
name: 'A',
amount: 5,
},
'Item:2': {
id: '2',
name: 'B',
amount: 4,
},
}
object 會被擺到第一層
@apollo/react-hooks
useQuery
useLazyQuery
useMutation
useSubscription
useApolloClient
useQuery
function Hello() {
const {
loading,
error,
data,
refetch,
} = useQuery(ITEM_LIST, {
variables: {
categoryId: '1',
},
fetchPolicy: "cache-first",
});
if (loading) return <Loading />;
if (error) return <Error />;
return (
<>
<Button onPress={() => refetch()} />
<List data={data.items} />
</>
);
}
useQuery 的 fetchPolicy
cache-first
先看 cache 有沒有資料,有的話就不重新抓
cache-and-network
先看 cache 有沒有資料,有的話就先用,但還是重新抓
network-only
重新抓
no-cache
重新抓,結果不會 cache 起來
說的都是第一次 render~
cache-only
只從 cache 找,沒有就報錯~
network-only 的話,資料會自動被更新嗎

做實驗的結果是會
-> 除了 no-cache 之外其他的都會和 cache 連動
useLazyQuery
const [getItems, { loading, error, data, refetch }] = useLazyQuery(ITEM_LIST);
如果是 component 的某些狀態不需要 query
也可以用 useQuery 然後設 skip 這個 option
const isQueryNecessary = ...
const { loading, data } = useQuery(ITEM_LIST, { skip: !isQueryNecessary });
先呼叫 function 後才使用
fetchMore & updateQuery
const { data, fetchMore } = useQuery(ITEM_LIST, {
variables: {
offset: 0,
limit: 20
},
});
const onLoadMore = () => fetchMore({
variables: {
offset: data.items.length
limit: 20.
},
updateQuery: (previousResult, { fetchMoreResult }) => {
if (!fetchMoreResult) return previousResult;
return Object.assign({}, previousResult, {
items: [...previousResult.items, ...fetchMoreResult.items]
});
}
});
The @connection directive
呼叫 fetchMore 的時候我們可能會給跟一開始不一樣的 variables (例如 offset)
updateQuery 會 update query 的結果,
但是這個 query 的 cache 還是用一開始的參數作 cache key
items({"offset":0,"limit":20}): Array(40)
如果之後其它地方用
{ variables: { offset: 0, limit: 21 } (參數不一樣)
就用不到這個 cache 會重抓
The @connection directive
const ITEM_LIST = gql`
query ItemList (
$categoryId: ID!
$offset: Int
$limit: Int
) {
items (
categoryId: $categoryId
offset: $offset
limit: $limit
) @connection(key: "items", filter: ["categoryId"]) {
id
name
amount
}}
`;
這樣 cache key 就不會包含 offset 跟 limit
useMutation
const [deleteItem, { data }] = useMutation(DELETE_ITEM, {
variables: {
id: '2',
},
refetchQueries: [{
query: ITEM_LIST,
}],
});
useApolloClient
const client = useApolloClient();
直接拿 client instance 用的
client 是用 Context 的方式給的
const App = () => (
<ApolloProvider client={client}>
<RootComponent />
</ApolloProvider>
);
*client 也可以透過其他 hook 回傳的 object 拿到,而且可以在 option 指定不同的 client
讀寫 cache
client.readQuery
client.readFragment
client.writeQuery
client.writeFragment
client 除了有 client.query, client.mutate 兩個方法可以直接用之外,還可以對 cache 做事情
client.readQuery
const { items } = client.readQuery({
query: ITEM_LIST,
});
如果 cache 裡面沒有足夠資料就會報錯
client.readFragment
Fragment 的用途 -> 把某種 Object type 的某些 fields 整理起來
const ITEM_FILEDS = gql`
fragment itemFields on Item {
id
name
amount
}
`;
const ITEM_LIST = gql`
query itemList {
items {
...itemFields
}
${ITEM_FIELDS}
}
`;
這樣定 query 的時候可以 reuse,不用很多地方都寫一樣的
client.readFragment
-> 可以直接讀 cache 裡的 object
const item = client.readFragment({
id: 'Item:2',
fragment: gql`
fragment itemFields on Item {
id
name
amount
}
`,
});
如果 cache 裡面沒有這個 object 就回傳 null
如果有 object 但有的欄位沒資料就會報錯
client.writeQuery
const data = client.readQuery({ query: ITEM_LIST });
client.writeQuery({
query: ITEM_LIST,
data: {
items: data.items.filter(item => item.id !== '2'),
},
});
client.writeFragment
client.writeFragment({
id: 'Item:2',
fragment: gql`
fragment itemFields on Item {
id
name
amount
}
`,
data: {
id: '2',
name: 'B',
amount: 4,
},
});
mutation 的 update
const [deleteItem] = useMutation(DELETE_ITEM, {
variables: { id, '3' },
update: (store, { data: { deleteItem } }) => {
const items = store.readQuery({ query: ITEM_LIST });
store.writeQuery({
query: ITEM_LIST,
data: items.filter(item => item.id !== deleteItem),
});
},
});
整理一下更新 cache 的方式
Query
refetch
polling
Mutation
refetchQueries
update (也是 writeQuery)
用 client 操作
client.writeQuery
client.writeFragment
(Query 或 Mutation 有 onComplete
可能也可以在那時候做事情)
optimistic UI
const [consumeItem] = useMutation(CONSUME_ITEM, {
variables: { id: '2' },
optimisticResponse: {
__typename: "Mutation",
consumeItem: {
__typename: "Item",
id: '2',
name: 'B',
amount: 4,
},
},
});
直接先寫一個假的 response
參考資料:
data normalization
有一些詳細的說明是寫在 hoc 這邊
react-hooks
操作 cache 的方法
apollo client
By luyunghsien
apollo client
- 589