首页 > 解决方案 > apollo-client:如何从缓存中获取反向关系?

问题描述

我有一个形状的graphql查询响应

{
  table {
    id
    legs {
      id
    }
  }

这将我table的.legInMemoryCache

但是,如果我的应用程序leg从缓存中检索 a 并且需要知道相应的table,我将如何找到它?

我的两个想法是

apollo 是否提供任何适合实现这种反向查找的功能?

更新:澄清一下,leg有一个table道具,但由于在解析该道具之前我已经在客户端中获得了信息,所以我想在客户端而不是服务器端解析该道具。

标签: graphqlapolloapollo-clientapollostack

解决方案


您应该table为每个leg. 根据graphql.org 文档,您应该在图表中思考:

使用 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);

客户端.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,
    ssrMode: 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>
  );
}

推荐阅读