apollo-cache-memory是apollo-client2.0的默认实现,InMemoryCache是一个规范化的数据存储store不需要依赖redux.有时我们可能需要直接操作缓存,例如更新state操作。
1、安装:
npm install apollo-cache-inmemory --save
安装完apollo-cache-inmemory之后我们 便可以初始化缓存const cache = new InMemoryCache(),并且把cache传递给new ApolloClient()
示例:
import { InMemoryCache } from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import ApolloClient from 'apollo-client';
const cache = new InMemoryCache();
const client = new ApolloClient({
link: new HttpLink(),
cache
});
2、设置InMemoryCache
我们可以在InMemeoryCache中配置参数来设置缓存
addTypename:一个布尔值,以确定是否添加__typename到文档(默认值:true)dataIdFromObject:一个函数它接受数据对象,返回在规范化存储时数据对应的唯一标识符。fragmentMatcher:默认情况下,InMemoryCache使用 heuristic fragment matcher。如果您在unions和interface上使用fragment,则需要使用IntrospectionFragmentMatcher。有关更多信息,请阅读我们的指南,为unions和interface设置fragment匹配。cacheRedirects(以前称为cacheResolvers或customResolvers):在请求发生之前将查询重定向到缓存中的另一个条目的函数映射。如果您有一个项目列表并希望在查询单个项目的详细信息页面上使用列表查询中的数据,这将非常有用。更多关于这一点。
3、规范化数据
InMemoryCache在保存数据之前会规范化数据。规范化数据流程:将要存储的数据分成多个单个对象,并为每个对象创建唯一的标识符,并且将这些对象扁平化的存储(没有结构)。如果存在主键id和__typename的话,InMemoryCache将默认使用主键id和__typename生成唯一标识符。如果没有指定id或者__typename的话,InMemoryCache会根据查询返回结果的自动编一个号。如果不希望通过上述方式获取唯一标识符,则可以将dataIdFromObject传递给InMemoryCache来自己指定编码方式(如你的查询结果中不是以id为主键,而是以其他字段为主键,则可以指定该字段为唯一标识符引用字段)。
示例,如果你希望把key字段作为唯一标识符引用字段:
const cache = new InMemoryCache({
dataIdFromObject: object => object.key || null
});
注意,dataIdFromObject并不会把__typename加到id上作为唯一标识符,而是只会使用object.key,如果object.key不是全局唯一的,您可以通过键入__typename附加到GraphQL键入的每个对象的属性,为不同的数据类型使用不同的唯一标识符。例如:
import { InMemoryCache, defaultDataIdFromObject } from 'apollo-cache-inmemory';
const cache = new InMemoryCache({
dataIdFromObject: object => {
switch (object.__typename) {
case 'foo': return object.key; // use `key` as the primary key
case 'bar': return `bar:${object.blah}`; // use `bar` prefix and `blah` as the primary key
default: return defaultDataIdFromObject(object); // fall back to default handling
}
}
});
4、缓存自动更新
假设我们执行了下面的查询:
{
post(id: '5') {
id
score
}
}
同时我们执行了下面的更新:
mutation {
upvotePost(id: '5') {
id
score
}
}
如果上面的两个id匹配的话,那么在所有组件中引用的score字段都将自动更新。为了能够有效利用自动更新,最好在所有的mutation后面跟上所有的字段。一个小技巧是使用fragment在query和mutation之间共享field.
5、直接访问缓存
我们可以使用Apollo Client类方法readQuery,readFragment,writeQuery和writeFragment来访问缓存。我们可以使用《ApolloProvider client={client}》来获取自定义的new ApolloClient.这样我们可以在所有被《ApolloProveider》组件包含的组件中使用client.readQurey,readFragment,writeQuery和writeFragment等方法。
(1)readQuery
readQuery方法非常像ApolloClient的query方法,只不过query方法在访问缓存未找到数据时将向服务器寻找数据,而readQuery仅向缓存查找数据,如果没有找到就会报错。示例:
const { todo } = client.readQuery({
query: gql`
query ReadTodo {
todo(id: 5) {
id
text
completed
}
}
`,
});
也可以向readQuery中传递参数:
const { todo } = client.readQuery({
query: gql`
query ReadTodo($id: Int!) {
todo(id: $id) {
id
text
completed
}
}
`,
variables: {
id: 5,
},
});
(2)readFragment
readQuery让我们仅能从type的根部节点开始读取。readFragment允许我们从已经查询的任意节点开始读取。
const todo = client.readFragment({
id: ..., // `id` is any id that could be returned by `dataIdFromObject`.
fragment: gql`
fragment myTodo on Todo {
id
text
completed
}
`,
});
此查询中的id是初始化InMemoryCache时dataIdFromObject返回的唯一标识符,如果你在唯一标示符中添加了__typename,那么在查询readFragment的时候也要加上__typename.这是id,id就是Todo_(id).
如果缓存中没有查找到Todo,则将返回null,如果查到了Todo,但是Todo没有text字段则将报错。
readFragment是todo可能来自任何地方!todo可能是单例查询的结果如({ todo(id: 5) { ... } }),也可能来自todos({ todos { ... } })列表查询,还可能来自一个变异(mutation { createTodo { ... } })。只要在某些时候你的GraphQL服务器给你提供的ID和待办事项id,text以及completed,你便可以从你代码的任何地方从换从中读取它。
(3)writeQuery和writeFragment
writeQuery和writeFragment可以改变缓存中的数据,但不会改变服务器端的值。因此,如果从新加载页面,那么使用writeQuery和writeFragment写的值都将消失。
writeQuery和writeFragment与readQuery和readFragment性质相同,只不过他们需要一个data参数来写入数据。
如我们想要更新一个id为5的todo的completed的状态:
client.writeFragment({
id: '5',
fragment: gql`
fragment myTodo on Todo {
completed
}
`,
data: {
completed: true,
},
});
任何订阅了缓存的组件如Query组件都将随着writeQuery和writeFragment而自动更新。
如果我们想向缓存中增加一个代办事项。
const query = gql`
query MyTodoAppQuery {
todos {
id
text
completed
}
}
`;
const data = client.readQuery({ query });
const myNewTodo = {
id: '6',
text: 'Start using Apollo Client.',
completed: false,
__typename: 'Todo',
};
client.writeQuery({
query,
data: {
todos: [...data.todos, myNewTodo],
},
});
6、使用缓存的技巧
(1)绕开缓存
我们可以设置fetchPolicy为network-only或者no-cache。network-only仍然将响应保存在缓存中以备以后使用,仅仅是不从缓存读取直接从网络读取。no-cache是既不读也不写。比如我们不想在缓存中保存密码,可以使用no-cache策略。
(2)mutation后更新
在某些情况下,仅使用dataIdFromObject不足以使应用程序UI正确更新。例如,如果要在不重新获取整个列表的情况下向对象列表添加内容,或者如果某些对象无法分配对象标识符,则Apollo Client无法为您更新现有查询。
refetchQueries是更新缓存的最简单方法。有了refetchQueries你可以指定你想要的突变是为了重新获取可能已受突变商店的部分完成后运行一个或多个查询
示例:
mutate({
//... insert comment mutation
refetchQueries: [{
query: gql`
query UpdateCache($repoName: String!) {
entry(repoFullName: $repoName) {
id
comments {
postedBy {
login
html_url
}
createdAt
content
}
}
}
`,
variables: { repoName: 'apollographql/apollo-client' },
}],
})
如果我们调用refetchQueries使用一组字符串,那么ApolloClient会查找之前的同名query,然后重新获取。
一种常见的调用是导入其他组件的query,当mutation之后,使用refetchQueries重新获取query,以保证组件的更新。示例:
import RepoCommentsQuery from '../queries/RepoCommentsQuery';
mutate({
//... insert comment mutation
refetchQueries: [{
query: RepoCommentsQuery,
variables: { repoFullName: 'apollographql/apollo-client' },
}],
})
使用update可让您完全控制缓存,允许您以任何您喜欢的方式更改数据模型以响应突变。update是查询后更新缓存的推荐方法。
示例:
import CommentAppQuery from '../queries/CommentAppQuery';
const SUBMIT_COMMENT_MUTATION = gql`
mutation SubmitComment($repoFullName: String!, $commentContent: String!) {
submitComment(
repoFullName: $repoFullName
commentContent: $commentContent
) {
postedBy {
login
html_url
}
createdAt
content
}
}
`;
const CommentsPageWithMutations = () => (
<Mutation mutation={SUBMIT_COMMENT_MUTATION}>
{mutate => {
<AddComment
submit={({ repoFullName, commentContent }) =>
mutate({
variables: { repoFullName, commentContent },
update: (store, { data: { submitComment } }) => {
// Read the data from our cache for this query.
const data = store.readQuery({ query: CommentAppQuery });
// Add our comment from the mutation to the end.
data.comments.push(submitComment);
// Write our data back to the cache.
store.writeQuery({ query: CommentAppQuery, data });
}
})
}
/>;
}}
</Mutation>
);
(3)增量加载
fetchMore可用于根据另一个查询返回的数据更新查询结果。大多数情况下,它用于处理无限滚动分页或其他情况,当您已经有一些数据时,您正在加载更多数据。
在我们的GitHunt示例中,我们有一个分页提要,显示GitHub存储库列表。当我们点击“加载更多”按钮时,我们不希望Apollo客户端丢弃它已加载的存储库信息。相反,它应该只是将新加载的存储库附加到Apollo Client已在商店中拥有的列表中。通过此更新,我们的UI组件应重新呈现并向我们显示所有可用的存储库。
示例:
const FEED_QUERY = gql`
query Feed($type: FeedType!, $offset: Int, $limit: Int) {
currentUser {
login
}
feed(type: $type, offset: $offset, limit: $limit) {
id
# ...
}
}
`;
const FeedWithData = ({ match }) => (
<Query
query={FEED_QUERY}
variables={{
type: match.params.type.toUpperCase() || "TOP",
offset: 0,
limit: 10
}}
fetchPolicy="cache-and-network"
>
{({ data, fetchMore }) => (
<Feed
entries={data.feed || []}
onLoadMore={() =>
fetchMore({
variables: {
offset: data.feed.length
},
updateQuery: (prev, { fetchMoreResult }) => {
if (!fetchMoreResult) return prev;
return Object.assign({}, prev, {
feed: [...prev.feed, ...fetchMoreResult.feed]
});
}
})
}
/>
)}
</Query>
);
fetchmore接受一组参数然后重新发送组件的请求(这里是FEED_QUERY ),这里仅更新了offset参数,那么type和limit将保持不变。
当我们调用fetchmore的时候,我们将要使用updateQuery来更新query的结果。他接受两个参数,第一个是先前query的结果,第二个是fetchmore的结果。
(4)@connection指令(不理解)
从根本上讲,分页查询和其他查询一样(除了fetchmore 更新相同的cache key)都是缓存了初始的query和对应的参数。一个问题是当我们再次向缓冲请求发送请求时我们并不需要关心这些参数。
(5)缓存重定向
