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 中的tableleg 条目。

但是如果我的应用程序从缓存中检索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>&nbsp;</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>&nbsp;</li>
                </ul>
              );
            )
          </ul>
        );
      
    </Query>
  );

【讨论】:

legs 已经有一个 table 道具,我只想在客户端而不是服务器端解析该道具。 抱歉,我没有正确理解您的问题。然后我建议您使用第一个选项,但使用 Apollo 的 client.writeFragment,因为这是将任意数据写入内存缓存的推荐方式 好的,但问题是,在上述情况下,调用writeFragment 的合适的生命周期钩子是什么? 1、&lt;Query&gt;生命周期钩子可以用什么? 2. 如果多个查询共享片段,还有什么更好的吗? 1.我不会使用&lt;Query&gt;,而是使用&lt;ApolloConsumer&gt;,因为您需要对查询触发生命周期进行细粒度控制。 2. 如果我做对了,目标是尽快填充缓存,因此其他查询应该只使用缓存,所以在正确的图块上使用writeFragment。我将创建一个代码框来演示 是的,尽快/对于其他查询是最好的。谢谢,我不知道 ApolloConsumer。期待看到您放置该组件的位置。 (也许围绕选择集导入该片段的任何查询/突变组合,或者更优雅?)

以上是关于apollo-client:如何从缓存中获取反向关系?的主要内容,如果未能解决你的问题,请参考以下文章

在 Apollo-client 中,writeQuery 会更新缓存,但 UI 组件不会重新渲染

在 apollo-client 的 useQuery 中使用 fetchPolicy='cache-and-network' 不会更新 fetch 上的缓存更多

如何为带有 InMemoryCache 的 apollo-client 中的正则表达式匹配的任何数据指定最大缓存时间?

apollo-client 没有响应的标头

如何从 Laravel 8 Eloquent 中的嵌套关​​系中获取数据?

使用 Apollo-Client 重新获取查询会引发错误