iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 29
0
Modern Web

React + GraphQL 全端練習筆記系列 第 29

仿Trello - Apollo client cache 操作

  • 分享至 

  • xImage
  •  

本系列文以製作專案為主軸,紀錄小弟學習React以及GrahQL的過程。主要是記下重點步驟以及我覺得需要記憶的部分,有覺得不明確的地方還請留言多多指教。

Apollo Client 在進行 query 後會在 client 端建立 cache,而在 mutation 後若希望畫面更新到最新資訊,Apollo Client 自有一套更新的方法,在介紹方法前先用 dev tools 讓 cache 可視化,並介紹一下 Cache 的構造。

Apollo Client Developer Tools

Chrome商店畫面

Apollo 提供的工具,安裝後在網頁工具中找到 Apollo

可以在 Cache 標籤看到目前的 cache 內容。

Cache 資料正規化

在Cache中可以看到像是 List:6 這樣的名稱,這是 InMemoryCache 這個方法幫每筆資料建立的
cache id,構造會像這樣:

[__typename]:[id]

這個 __typename 就是根據 Server 的 schema 建立的,Apollo Client 在 Query 時會自己偷偷請求這個欄位,可以在Queries標籤底下看到:

而每筆資料有了獨特id後就表列在 cache 中,至於資料間的關聯會以 __ref 參照:

Mutation 後更新單一筆資料

要在mutation後觸發單一筆 cache 資料的更新,方法是在 mutation 的回完值中帶上目標的 id 以及更新的欄位,以下用editTodo為例。

先補上 Server端的 schema:

editTodo(todoId: Int, name: String): Todo!

以及 reducer:

editTodo: (parent, args, context) => {
  return context.prisma.todo.update({
    where: { id: args.todoId },
    data: {
      name: args.name,
    },
  });
}

Client 端 Mutation 請求:

const EDIT_TODO = gql`
  mutation EditTodo($name: String!, $todoId: Int!) {
    editTodo(name: $name, todoId: $todoId) {
      id     //目標的id
      name   //更新的欄位
    }
  }
`;

宣告 editTodo方法:

const [editTodo] = useMutation(EDIT_TODO);

這樣當 editTodo 被執行後如果成功更新並回傳,Apollo Client就會找到對應的 Todo:id 資料並更新 name 欄位,同時通知 React 刷新畫面。

Mutation 後增加一筆資料到清單中

如果新增了資料,在 Cache 中沒有對應的 id 可以觸發更新,就要用 cache.modify 方法。

以 addList 方法為例,先補上 server schema 跟 resolvers。

  • schema:
addList(listTitle: String): List!
  • resolvers:
addList: (parent, args, context) => {
  return context.prisma.list.create({
    data: {
      title: args.listTitle,
    },
  });
}

Client 端 Mutation 請求:

const ADD_LIST = gql`
  mutation AddList($listTitle: String!) {
    addList(listTitle: $listTitle) {
      id
    }
  }
`;

宣告 addList 方法:

  const [addList] = useMutation(ADD_LIST, {
    update(cache, { data: { addList } }) {
      cache.modify({
        fields: {
          lists(
            existingLists = [] //預設為空陣列
          ) {
            const newListRef = cache.writeFragment({
              data: addList,
              fragment: gql`
                fragment NewList on List {
                  id
                  title
                }
              `,
            });
            return [...existingLists, newListRef];
          },
        },
      });
    },
  });

事情變得複雜了,一層層解釋吧。

update

useMutation 可接收兩個參數,第一個就是 gql指令,而第二個則是 option 物件。

option 中包含很多方法,像是 onError,onCompleted 等,可以更精確的操控 mutation 過程。
而 option 其中的 update 用於定義 mutation 完成,收到回傳值後操作 cache 的動作。

update( cache , respondedData ){...}

從 update 可以存取 cache 實例以及回傳的資訊,範例中 { data: { addList } } 只是利用解構賦值將回傳的 List 物件直接取出來用。

cache.modify

cache.modify 用於編輯已存在的 cache 資料,主要用以下參數指定編輯目標:

  • id : 用 id 指定目標 cache,像是 List:6。 如果沒宣告的話預設為 ROOT_QUERY。
  • fields : 編輯欄位的 function 物件清單,像是
    fields: {
        lists(){...}
    }
    
    用於編輯目標 cache 的 lists 欄位。
    像是 lists() 的這些方法被稱為 Modifiers

cache.modify 用 id 與 fields 指定好編輯目標後就撰寫Modifiers實作編輯動作。

Modifier

Modifier 方法包含兩個參數,第一個是目標欄位的目前值,第二個是一組 helper function 工具。

以 lists為例的話:

lists( existingLists = [] , {readField}) {...}
  1. 將lists的目前值參照為 existingLists,若值不存在預設為空陣列
  2. 可以從工具組中取出 readField ,用於存取物件中其他欄位的值,這次沒用到。

在這個 Modifier 裡,首先要新增一個 newList cache,並將 newList 的參照添加到現有的 lists 陣列中。

cache.writeFragment

const newListRef = cache.writeFragment({
          data: addList,
          fragment: gql`
            fragment NewList on List {
              id
              title
            }
          `,
        });

先說說 Fragment ,這是 GraphQL 語法的一部分,主要用來打包對象Type 的一組 Fields,並可重複利用。

fragment NewList on List {
  id
  title
}

NewList 只包含了 List 的 id 跟 title 欄位。

而 writeFragment 可以用 fragment 決定要寫入 cache 的 資料結構,並將 data 中的資料寫入 Cache,並回傳新的 cache 的 __ref。

Modifier 回傳

Modifier 的回傳值會成為 cache.modify 對象的新值,這邊將新取得的 __ref 加入 lists 陣列回傳,就成了新增後的 lists。

這次先簡單示範兩個常用的 cache 編輯方式,還有很多應用可以研究。

References:


上一篇
仿Trello - 前端 Apollo Client 串接 GraphQL API
下一篇
Apollo Client Network Errors
系列文
React + GraphQL 全端練習筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言