本地状态
过时
本指南已过时,需要针对 Vue 3 和 vue-apollo 4 进行重写。欢迎贡献!
为什么要使用 Apollo 本地状态管理?
当您使用 Apollo 执行 GraphQL 查询时,API 调用的结果将存储在 **Apollo 缓存** 中。现在想象一下,您还需要存储某种本地应用程序状态,并使其可供不同的组件使用。通常,在 Vue 应用程序中,我们可以使用 Vuex 来实现这一点。但是,同时使用 Apollo 和 Vuex 意味着您将数据存储在两个不同的位置,因此您将拥有 *两个真相来源*。
好消息是 Apollo 有一种机制可以将本地应用程序数据存储到缓存中。以前,它使用 apollo-link-state 库来实现这一点。从 Apollo 2.5 版本开始,此功能已包含在 Apollo 核心库中。
创建本地模式
就像创建 GraphQL 模式是我们在服务器上定义数据模型的第一步一样,编写本地模式是我们客户端采取的第一步。
让我们创建一个本地模式来描述一个项目,该项目将作为待办事项列表的单个元素。此项目应包含一些文本,一些属性来定义它是否已完成,以及一个 ID 来区分不同的待办事项。因此,它应该是一个包含三个属性的对象
{
id: 'uniqueId',
text: 'some text',
done: false
}
现在,我们准备将 Item
类型添加到我们的本地 GraphQL 模式中。
//main.js
import gql from 'graphql-tag';
export const typeDefs = gql`
type Item {
id: ID!
text: String!
done: Boolean!
}
`;
gql
这里代表 JavaScript 模板字面量标签,它解析 GraphQL 查询字符串。
现在,我们需要将 typeDefs
添加到我们的 Apollo 客户端中。
// main.js
const apolloClient = new ApolloClient({
typeDefs,
resolvers: {},
});
警告
如您所见,我们还在这里添加了一个空的 resolvers
对象:如果我们不将其分配给 Apollo 客户端选项,它将无法识别对本地状态的查询,并尝试向远程 URL 发送请求。
在本地扩展远程 GraphQL 模式
您不仅可以从头开始创建本地模式,还可以将本地 **虚拟字段** 添加到您现有的远程模式中。这些字段仅存在于客户端,对于使用本地状态装饰服务器数据很有用。
想象一下,我们在远程模式中有一个 User
类型
type User {
name: String!
age: Int!
}
我们想将一个仅限本地的属性添加到 User
export const schema = gql`
extend type User {
twitter: String
}
`;
现在,当查询用户时,我们需要指定 twitter
字段是本地的
const userQuery = gql`
user {
name
age
twitter @client
}
`;
初始化 Apollo 缓存
要在您的应用程序中初始化 Apollo 缓存,您需要使用 InMemoryCache
构造函数。首先,让我们将其导入到您的主文件中
// main.js
import ApolloClient from 'apollo-boost';
import { InMemoryCache } from 'apollo-cache-inmemory';
const cache = new InMemoryCache();
现在,我们需要将缓存添加到我们的 Apollo 客户端选项中
//main.js
const apolloClient = new ApolloClient({
cache,
typeDefs,
resolvers: {},
});
现在缓存为空。要将一些初始数据添加到缓存中,我们需要使用 writeData
方法
// main.js
const apolloClient = new ApolloClient({
cache,
typeDefs,
resolvers: {},
});
cache.writeData({
data: {
todoItems: [
{
__typename: 'Item',
id: 'dqdBHJGgjgjg',
text: 'test',
done: true,
},
],
},
});
我们刚刚将一个 todoItems
数组添加到我们的缓存数据中,并且我们定义了每个项目都具有 Item
的类型名称(在我们的本地模式中指定)。
查询本地数据
查询本地缓存与 向远程服务器发送 GraphQL 查询 非常相似。首先,我们需要创建一个查询
// App.vue
import gql from 'graphql-tag';
const todoItemsQuery = gql`
{
todoItems @client {
id
text
done
}
}
`;
与向远程 API 发送查询的主要区别在于 @client
指令。此指令指定此查询不应针对远程 GraqhQL API 执行。相反,Apollo 客户端应从本地缓存中获取结果。
现在,我们可以在 Vue 组件中像使用普通的 Apollo 查询一样使用此查询
// App.vue
apollo: {
todoItems: {
query: todoItemsQuery
}
},
使用变异更改本地数据
我们有两种不同的方法可以更改本地数据
- 直接使用
writeData
方法写入,就像我们在 缓存初始化 期间所做的那样; - 调用 GraphQL 变异。
让我们将一些变异添加到我们的 本地 GraphQL 模式 中
// main.js
export const typeDefs = gql`
type Item {
id: ID!
text: String!
done: Boolean!
}
type Mutation {
checkItem(id: ID!): Boolean
addItem(text: String!): Item
}
`;
checkItem
变异将把特定项目的布尔值 done
属性设置为相反的值。让我们使用 gql
创建它
// App.vue
const checkItemMutation = gql`
mutation($id: ID!) {
checkItem(id: $id) @client
}
`;
我们定义了一个 *本地* 变异(因为我们这里有一个 @client
指令),它将接受一个唯一标识符作为参数。现在,我们需要一个 *解析器*:一个函数,它为模式中的类型或字段解析一个值。
在我们的例子中,解析器将定义当我们有一个特定变异时,我们想要对我们的本地 Apollo 缓存进行哪些更改。本地解析器与远程解析器具有相同的函数签名((parent, args, context, info) => data
)。实际上,我们只需要参数(传递给变异的参数)和上下文(我们将需要它的缓存属性来读取和写入数据)。
让我们将解析器添加到我们的主文件中
// main.js
const resolvers = {
Mutation: {
checkItem: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: todoItemsQuery });
const currentItem = data.todoItems.find(item => item.id === id);
currentItem.done = !currentItem.done;
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
};
我们在这里做了什么?
- 从我们的缓存中读取
todoItemsQuery
,以查看我们现在有哪些todoItems
; - 查找具有给定 ID 的项目;
- 将找到的项目的
done
属性更改为相反的值; - 将我们更改后的
todoItems
写回缓存; - 将
done
属性作为变异结果返回。
现在,我们需要用新创建的 resolvers
替换 Apollo 客户端选项中的空 resolvers
对象
// main.js
const resolvers = {
Mutation: {
checkItem: (_, { id }, { cache }) => {
const data = cache.readQuery({ query: todoItemsQuery });
const currentItem = data.todoItems.find(item => item.id === id);
currentItem.done = !currentItem.done;
cache.writeQuery({ query: todoItemsQuery, data });
return currentItem.done;
},
};
const apolloClient = new ApolloClient({
cache,
typeDefs,
resolvers,
});
在此之后,我们可以在 Vue 组件中像使用普通的 变异 一样使用该变异
// App.vue
methods: {
checkItem(id) {
this.$apollo.mutate({
mutation: checkItemMutation,
variables: { id }
});
},
}