Резолвер Apollo Client срабатывает только один раз

В настоящее время я работаю над приложением для реагирования, которое использует серверную часть GraphQL и имеет дополнительное локальное состояние. Я использую преобразователь для разрешения локального поля, которое изменяется со временем, но преобразователь запускается только один раз.

Я попытался использовать cache.readQuery для повторного запуска запроса в случае изменения локального поля, но похоже, он работает не так, как я ожидал.

export const resolvers = {
  Query: {
    resolvedDevice: (obj, args, { cache }, info) => {
      const data = cache.readQuery({
        query: gql`
          query {
            selectedDevice @client
          }
        `
      });

      // do stuff with the data
    }
  },
  Mutation: {
    selectDevice: (_, { id }, { cache }) => {
      cache.writeData({ data: { selectedDevice: id } });
    }
  }
};

const query = gql`
  query GetResolvedDevice {
    resolvedDevice @client
  }
`;

В этом случае «resolvedDevice» в преобразователе выполняется только один раз, даже если я изменяю кеш с помощью мутации «selectDevice». Я ожидал, что при изменении локального состояния через мутацию резолверы также снова запустятся, потому что кеш меняется.

Вот код, который выполняет запрос:

const ModalContainer = props => {
  const { loading, error, data } = useQuery(query);

  if (loading || error) {
    return null;
  }

  return (
    <Modal
      device={data.resolvedDevice}
    />
  );
};

И в этом компоненте я запускаю мутацию на selectedDevice:

export const SELECT_DEVICE = gql`
  mutation SelectDevice($id: String!) {
    selectDevice(id: $id) @client
  }
`;

const DevicesNoGeoContainer = () => {
  const [selectDevice] = useMutation(SELECT_DEVICE);

  return (
    <DevicesNoGeo
      onGeoClick={id => {
        selectDevice({ variables: { id } });
      }}
    />
  );
};

person Christoph Weise-Onnen    schedule 02.10.2019    source источник
comment
Запросы не будут обновляться автоматически сами по себе. Можете ли вы опубликовать код, в котором вы выполняете свой запрос / мутацию? И чтобы было ясно, все задействованные запросы являются локальными или вы получаете данные из своей серверной части?   -  person Chris B.    schedule 02.10.2019
comment
Это помогает?   -  person Christoph Weise-Onnen    schedule 02.10.2019


Ответы (1)


Apollo знает, что нужно обновлять отслеживаемые запросы, поля которых извлекаются из кеша, когда эти значения в кеше изменяются. В этом случае вместо этого поле в запросе выполняется локальным преобразователем. Это означает, что у Apollo нет записи в кеше, на которую можно было бы подписаться и реагировать на этот конкретный запрос. Таким образом, первый запрос выполнен, и вы не получите никаких обновлений для запроса, если вы не активируете его явно с помощью refetch в результате перехвата.

Один из способов решения этой проблемы - «сохранение» производных полей в кэше и использование заполненных кешем полей в компонентных запросах. Мы можем сделать это, явно наблюдая за исходным полем (selectedDevice) и в обработчике записывая производное поле (resolvedDevice) обратно в кеш (я буду продолжать использовать ваше имя поля, хотя вы можете подумать о его переименовании, если сделаете это route, как кажется, назван так, как он определен).

Доказательство концепции

export const resolvers = {
  Mutation: {
    selectDevice: (_, { id }, { cache }) => {
      cache.writeData({ data: { selectedDevice: id } });
    }
  }
};

const client = new ApolloClient({
  resolvers
});

const sourceQuery = gql`
  query {
    selectedDevice @client
  }`;

// watch the source field query and write resolvedDevice back to the cache at top-level
client.watchQuery({ query: sourceQuery }).subscribe(value =>
  client.writeData({ data: { resolvedDevice: doStuffWithTheData(value.data.selectedDevice) } });

const query = gql`
  query GetResolvedDevice {
    resolvedDevice @client
  }
`;

Поскольку поле в запросе, переданном в watchQuery, находится в кэше, ваш обработчик будет вызываться при каждом изменении, и в ответ мы запишем производное поле в кеш. И поскольку resolvedDevice теперь находится в кэше, компонент, который запрашивает его, теперь будет получать обновления при каждом изменении (что будет при изменении поля "upsteam" selectedDevice).

Теперь вы, вероятно, не хотите помещать этот запрос просмотра исходного поля на верхний уровень, поскольку он будет запускаться и смотреть, когда ваше приложение запускается, независимо от того, используете ли вы компонент рендеринга или нет. Это было бы особенно плохо, если бы вы применили этот подход для множества полей локальных состояний. Мы работаем над методом, в котором вы можете декларативно определять производные поля и их функции выполнения:

export const derivedFields: {
  resolvedDevice: {
    fulfill: () => client.watchQuery({ query: sourceQuery }).subscribe(value =>
      client.writeData({ data: { resolvedDevice: doStuffWithTheData(value.data.selectedDevice),
  }
};

а затем используйте HOC, чтобы заставить их включиться:

import { derivedFields } from './the-file-above';

export const withResolvedField = field => RenderingComponent => {
  return class ResolvedFieldWatcher extends Component {
    componentDidMount() {
      this.subscription = derivedFields[field].fulfill();
    }
    componentDidUnmount() {
      // I don't think this is actually how you unsubscribe, but there's
      // some way to do it
      this.subscription.cancel();
    }
    render() {
      return (
        <RenderingComponent {...this.props } />
      );
    }
  };
};

и, наконец, оберните свой модальный контейнер:

export default withDerivedField('resolvedDevice')(ModalContainer);

Обратите внимание, что в конце я становлюсь довольно гипотетическим, я просто напечатал его вместо того, чтобы вытащить наш фактический код. Мы также вернулись к Apollo 2.5 и React 2.6, поэтому вам, возможно, придется адаптировать подход к хукам и т. Д. Принцип должен быть тем же: определять производные поля, просматривая запросы к исходным полям в кеше и запишите производные поля обратно в кеш. Затем у вас есть реактивный каскад от ваших исходных данных к пользовательскому интерфейсу рендеринга компонента на основе производного поля.

person Christopher Best    schedule 07.10.2019
comment
Извините за долгую задержку. Большое спасибо за ваш ответ, очень полезно. - person Christoph Weise-Onnen; 03.12.2019