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