apollo-client: как получить обратную связь из кеша? - PullRequest
0 голосов
/ 14 сентября 2018

У меня есть ответ на запрос graphql формы

{
  table {
    id
    legs {
      id
    }
  }

Это нормализуется до table и leg записей в моем InMemoryCache.

Но тогда, если мое приложение извлекает leg из кэша и ему нужно знать соответствующий table, как я могу найти это?

У меня были две идеи:

  • добавление table реквизита к каждому leg, когда приходит ответ на запрос - не уверен, что / как это будет работать (у меня есть несколько запросов и мутаций, содержащих фрагмент graphql с вышеуказанной формой)
  • с подходящим cache redirect, но я не знаю, как это сделать без поиска всех tables для leg.

Предоставляет ли apollo какие-либо функции, подходящие для этого обратного поиска?

Обновление : чтобы уточнить, leg имеет table реквизит, но я, поскольку у меня уже есть информация в клиенте, прежде чем разрешить эту реквизит, я бы хотел разрешить этот реквизит со стороны сервера.

1 Ответ

0 голосов
/ 15 сентября 2018

Вы должны добавить 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);

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,
    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 })
        }
      }
    })
  });
}

Films.js

<code>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)}
);}} export const Films = withApollo (FilmsComponent);

Planet.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>
  );
}
...