apollo-client:如何从缓存中获取反向关系?
Posted
技术标签:
【中文标题】apollo-client:如何从缓存中获取反向关系?【英文标题】:apollo-client: How to get inverse relation from cache? 【发布时间】:2019-02-18 23:23:35 【问题描述】:我有一个形状的 graphql 查询响应
table
id
legs
id
这会标准化为我的InMemoryCache
中的table
和leg
条目。
但是如果我的应用程序从缓存中检索leg
并且需要知道相应的table
,我将如何找到它?
我的两个想法是
当查询响应进入时,向每个leg
添加一个 table
属性 - 不确定这是否/如何工作(我有多个查询和突变,其中包含具有上述形状的 graphql 片段)
有一个合适的cache redirect
,但如果不搜索所有tables
以查找leg
,我不知道该怎么做。
apollo 是否提供任何适合实现这种反向查找的功能?
更新:澄清一下,leg
有一个 table
道具,但由于在解决该道具之前我已经在客户端中获得了信息,所以我想解决该道具客户端而不是服务器端。
【问题讨论】:
【参考方案1】:您应该为每个 leg
添加一个 table
属性。根据graphql.org documentation,您应该在图表中思考:
使用 GraphQL,您可以通过定义架构将业务领域建模为图形;在您的架构中,您定义不同类型的节点以及它们如何相互连接/关联。
在您的模型中,表格和腿是业务模型图中的节点。当您为每条腿添加一个表格道具时,您将在此图中创建一条新边,您的客户端代码可以遍历该边以获取相关数据。
澄清后编辑:
您可以使用writeFragment
并获得对 Apollo 缓存的细粒度控制。缓存填充查询完成后,计算逆关系并将其写入缓存,如下所示:
fetchTables = async () =>
const client = this.props.client
const result = await client.query(
query: ALL_TABLES_QUERY,
variables:
)
// compute the reverse link
const tablesByLeg =
for (const table of result.data.table)
for (const leg of table.legs)
if (!tablesByLeg[leg.id])
tablesByLeg[leg.id] =
leg: leg,
tables: []
tablesByLeg[leg.id].tables.push(table)
// write to the Apollo cache
for (const leg, tables of Object.values(tablesByLeg))
client.writeFragment(
id: dataIdFromObject(leg),
fragment: gql`
fragment reverseLink from Leg
id
tables
id
`,
data:
...leg,
tables
)
// update component state
this.setState(state => (
...state,
tables: Object.values(result)
))
演示
我在这里举了一个完整的例子:https://codesandbox.io/s/6vx0m346z 为了完整起见,我也将其放在下面。
index.js
import React from "react";
import ReactDOM from "react-dom";
import ApolloProvider from "react-apollo";
import createClient from "./client";
import Films from "./Films";
const client = createClient();
function App()
return (
<ApolloProvider client=client>
<Films />
</ApolloProvider>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
client.js
import ApolloClient from "apollo-client";
import InMemoryCache from "apollo-cache-inmemory";
import HttpLink from "apollo-link-http";
export function dataIdFromObject(object)
return object.id ? object.__typename + ":" + object.id : null;
export function createClient()
return new ApolloClient(
connectToDevTools: true,
s-s-rMode: false,
link: new HttpLink(
uri: "https://prevostc-swapi-graphql.herokuapp.com"
),
cache: new InMemoryCache(
dataIdFromObject,
cacheRedirects:
Query:
planet: (_, args, getCacheKey ) =>
getCacheKey( __typename: "Planet", id: args.id )
)
);
电影.js
import React from "react";
import gql from "graphql-tag";
import withApollo from "react-apollo";
import dataIdFromObject from "../src/client";
import Planet from "./Planet";
const ALL_FILMS_QUERY = gql`
query
allFilms
films
id
title
planetConnection
planets
id
name
`;
const REVERSE_LINK_FRAGMENT = gql`
fragment reverseLink on Planet
id
name
filmConnection
films
id
title
`;
class FilmsComponent extends React.Component
constructor()
super();
this.state = films: [], selectedPlanetId: null ;
componentDidMount()
this.fetchFilms();
fetchFilms = async () =>
const result = await this.props.client.query(
query: ALL_FILMS_QUERY,
variables:
);
// compute the reverse link
const filmByPlanet = ;
for (const film of result.data.allFilms.films)
for (const planet of film.planetConnection.planets)
if (!filmByPlanet[planet.id])
filmByPlanet[planet.id] =
planet: planet,
films: []
;
filmByPlanet[planet.id].films.push(film);
// write to the apollo cache
for (const planet, films of Object.values(filmByPlanet))
this.props.client.writeFragment(
id: dataIdFromObject(planet),
fragment: REVERSE_LINK_FRAGMENT,
data:
...planet,
filmConnection:
films,
__typename: "PlanetsFilmsConnection"
);
// update component state at last
this.setState(state => (
...state,
films: Object.values(result.data.allFilms.films)
));
;
render()
return (
<div>
this.state.selectedPlanetId && (
<div>
<h1>Planet query result</h1>
<Planet id=this.state.selectedPlanetId />
</div>
)
<h1>All films</h1>
this.state.films.map(f =>
return (
<ul key=f.id>
<li>id: f.id</li>
<li>
title: <strong>f.title</strong>
</li>
<li>__typename: f.__typename</li>
<li>
planets:
f.planetConnection.planets.map(p =>
return (
<ul key=p.id>
<li>id: p.id</li>
<li>
name: <strong>p.name</strong>
</li>
<li>__typename: p.__typename</li>
<li>
<button
onClick=() =>
this.setState(state => (
...state,
selectedPlanetId: p.id
))
>
select
</button>
</li>
<li> </li>
</ul>
);
)
</li>
</ul>
);
)
<h1>The current cache is:</h1>
<pre>JSON.stringify(this.props.client.extract(), null, 2)</pre>
</div>
);
export const Films = withApollo(FilmsComponent);
行星.js
import React from "react";
import gql from "graphql-tag";
import Query from "react-apollo";
const PLANET_QUERY = gql`
query ($id: ID!)
planet(id: $id)
id
name
filmConnection
films
id
title
`;
export function Planet( id )
return (
<Query query=PLANET_QUERY variables= id >
( loading, error, data ) =>
if (loading) return "Loading...";
if (error) return `Error! $error.message`;
const p = data.planet;
return (
<ul key=p.id>
<li>id: p.id</li>
<li>
name: <strong>p.name</strong>
</li>
<li>__typename: p.__typename</li>
p.filmConnection.films.map(f =>
return (
<ul key=f.id>
<li>id: f.id</li>
<li>
title: <strong>f.title</strong>
</li>
<li>__typename: f.__typename</li>
<li> </li>
</ul>
);
)
</ul>
);
</Query>
);
【讨论】:
legs
已经有一个 table
道具,我只想在客户端而不是服务器端解析该道具。
抱歉,我没有正确理解您的问题。然后我建议您使用第一个选项,但使用 Apollo 的 client.writeFragment
,因为这是将任意数据写入内存缓存的推荐方式
好的,但问题是,在上述情况下,调用writeFragment
的合适的生命周期钩子是什么? 1、<Query>
生命周期钩子可以用什么? 2. 如果多个查询共享片段,还有什么更好的吗?
1.我不会使用<Query>
,而是使用<ApolloConsumer>
,因为您需要对查询触发生命周期进行细粒度控制。 2. 如果我做对了,目标是尽快填充缓存,因此其他查询应该只使用缓存,所以在正确的图块上使用writeFragment
。我将创建一个代码框来演示
是的,尽快/对于其他查询是最好的。谢谢,我不知道 ApolloConsumer。期待看到您放置该组件的位置。 (也许围绕选择集导入该片段的任何查询/突变组合,或者更优雅?)以上是关于apollo-client:如何从缓存中获取反向关系?的主要内容,如果未能解决你的问题,请参考以下文章
在 Apollo-client 中,writeQuery 会更新缓存,但 UI 组件不会重新渲染
在 apollo-client 的 useQuery 中使用 fetchPolicy='cache-and-network' 不会更新 fetch 上的缓存更多
如何为带有 InMemoryCache 的 apollo-client 中的正则表达式匹配的任何数据指定最大缓存时间?