仿Trello - Apollo client cache 操作

本系列文以制作专案为主轴,纪录小弟学习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:


<<:  [Kata] Clojure - Day 28

>>:  第29篇:结合

前端工程师也能开发全端网页:挑战 30 天用 React 加上 Firebase 打造社群网站|Day13 文章页面

连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...

[DAY 02]环境建置 : 组出你的环境--前导

前言 从小到大我们都听过这样的一句话: 工欲善其事,必先利其器 ------ 书上写的 在我们开始执...

[Vue.js] 栏位输入後按 Enter 自动执行 (Enter Event)

示范一个简单的功能,在网页上有一个搜寻功能,搜寻栏右边有执行的图示。 标准的做法就是输入文字後,按下...

JS语法学习Day3

学习目标 object 物件、while 回圈、密码检验程序 object 物件 可以存放许多key...

在linux中看gcc产生出来的组合语言

环境:linux使用者直接用终端机即可,windows使用者可用WSL或是建一个linux的虚拟机 ...