首页 > 解决方案 > 使用 NEXT.js 和 Apollo 客户端 2 配置订阅

问题描述

我正在尝试使用 Apollo 2 和 NEXT.js 配置订阅。我可以让客户端连接到服务器,并且它们在 GraphQL 操场上工作,所以错误的配置必须在 withData 文件中,或者在处理订阅的组件中。

在 chrome 中检查网络面板上的套接字连接时,订阅有效负载不会像在 GraphQL 游乐场中那样添加为帧。

与数据:

import { ApolloLink, Observable } from 'apollo-link';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';

import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { SubscriptionClient } from 'subscriptions-transport-ws';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { onError } from 'apollo-link-error';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';

function createClient({ headers }) {
  const cache = new InMemoryCache();

  const request = async (operation) => {
    operation.setContext({
      http: {
        includeExtensions: true,
        includeQuery: false
      },
      headers
    });
  };

  const requestLink = new ApolloLink(
    (operation, forward) => new Observable((observer) => {
      let handle;
      Promise.resolve(operation)
        .then(oper => request(oper))
        .then(() => {
          handle = forward(operation).subscribe({
            next: observer.next.bind(observer),
            error: observer.error.bind(observer),
            complete: observer.complete.bind(observer)
          });
        })
        .catch(observer.error.bind(observer));

      return () => {
        if (handle) handle.unsubscribe();
      };
    })
  );

  return new ApolloClient({
    link: ApolloLink.from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          console.log({ graphQLErrors });
        }
        if (networkError) {
          console.log('Logout user');
        }
      }),
      requestLink,
      // link,
      withClientState({
        defaults: {
          isConnected: true
        },
        resolvers: {
          Mutation: {
            updateNetworkStatus: (_, { isConnected }, { cache }) => {
              cache.writeData({ data: { isConnected } });
              return null;
            }
          }
        },
        cache
      }),
      createPersistedQueryLink().concat(
        new BatchHttpLink({
          uri: GRAPHQL_ENDPOINT,
          credentials: 'include'
        }),
        process.browser
          ? new WebSocketLink({
            uri: WS_PATH,
            options: {
              reconnect: true
            }
          })
          : null
      )
    ]),
    cache
  });
}

export default withApollo(createClient);

订阅组件:

import { CONVERSATION_QUERY } from '../../constants/queries';
import { CONVERSATION_SUBSCRIPTION } from '../../constants/subscriptions';
import PropTypes from 'prop-types';
import { Query } from 'react-apollo';

const Conversation = props => (
  <Query
    {...props}
    query={CONVERSATION_QUERY}
    variables={{ input: { _id: props._id } }}
  >
    {(payload) => {
      const more = () => payload.subscribeToMore({
        document: CONVERSATION_SUBSCRIPTION,
        variables: { input: { conversation: props._id } },
        updateQuery: (prev, { subscriptionData }) => {
          console.log({ subscriptionData });

          if (!subscriptionData.data.messageSent) return prev;

          const data = subscriptionData;

          console.log({ data });

          return Object.assign({}, prev, {});
        },
        onError(error) {
          console.log(error);
        },
        onSubscriptionData: (data) => {
          console.log('onSubscriptionData ', data);
        }
      });

      return props.children({ ...payload, more });
    }}
  </Query>
);

Conversation.propTypes = {
  children: PropTypes.func.isRequired
};

export default Conversation;

已经在 GraphQL 操场上测试过的订阅:

import gql from 'graphql-tag';

export const CONVERSATION_SUBSCRIPTION = gql`
  subscription messageSent($input: messageSentInput) {
    messageSent(input: $input) {
      _id
      users {
        _id
        profile {
          firstName
          lastName
          jobTitle
          company
          picture
        }
      }
      messages {
        _id
        body
        createdAt
        read
        sender {
          _id
          profile {
            firstName
            lastName
            jobTitle
            company
            picture
          }
        }
      }
    }
  }
`;

然后在componentDidMount中执行more函数:

componentDidMount() {
    this.props.subscribeToMore();
  }

updateQuery 中的日志在控制台中的结果是:

{"data":{"messageSent":null}}

标签: graphqlapolloapollo-clientnext.jsgraphql-subscriptions

解决方案


我没有正确配置我的 withData 文件。您需要使用 apollo-link 包中的 split 来让 Apollo 确定是否应该使用 http 或 ws 处理请求。这是我的工作配置文件。

import { ApolloLink, Observable } from 'apollo-link';
import { ApolloClient } from 'apollo-client';
import { BatchHttpLink } from 'apollo-link-batch-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
import { WebSocketLink } from 'apollo-link-ws';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
import { getMainDefinition } from 'apollo-utilities';
import { onError } from 'apollo-link-error';
import { split } from 'apollo-link';
import withApollo from 'next-with-apollo';
import { withClientState } from 'apollo-link-state';
import { GRAPHQL_ENDPOINT, WS_PATH } from '../config/env';

function createClient({ headers }) {
  const cache = new InMemoryCache();

  const request = async (operation) => {
    operation.setContext({
      http: {
        includeExtensions: true,
        includeQuery: false
      },
      headers
    });
  };

  const requestLink = new ApolloLink(
    (operation, forward) => new Observable((observer) => {
      let handle;
      Promise.resolve(operation)
        .then(oper => request(oper))
        .then(() => {
          handle = forward(operation).subscribe({
            next: observer.next.bind(observer),
            error: observer.error.bind(observer),
            complete: observer.complete.bind(observer)
          });
        })
        .catch(observer.error.bind(observer));

      return () => {
        if (handle) handle.unsubscribe();
      };
    })
  );

  const httpLink = new BatchHttpLink({
    uri: GRAPHQL_ENDPOINT
  });

  // Make sure the wsLink is only created on the browser. The server doesn't have a native implemention for websockets
  const wsLink = process.browser
    ? new WebSocketLink({
      uri: WS_PATH,
      options: {
        reconnect: true
      }
    })
    : () => {
      console.log('SSR');
    };

  // Let Apollo figure out if the request is over ws or http
  const terminatingLink = split(
    ({ query }) => {
      const { kind, operation } = getMainDefinition(query);
      return (
        kind === 'OperationDefinition'
        && operation === 'subscription'
        && process.browser
      );
    },
    wsLink,
    httpLink
  );

  return new ApolloClient({
    link: ApolloLink.from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          console.error({ graphQLErrors });
        }
        if (networkError) {
          console.error({ networkError});
        }
      }),
      requestLink,
      // link,
      withClientState({
        defaults: {
          isConnected: true
        },
        resolvers: {
          Mutation: {
            updateNetworkStatus: (_, { isConnected }, { cache }) => {
              cache.writeData({ data: { isConnected } });
              return null;
            }
          }
        },
        cache
      }),

      // Push the links into the Apollo client
      createPersistedQueryLink().concat(
        // New config
        terminatingLink
        // Old config
        // new BatchHttpLink({
        //   uri: GRAPHQL_ENDPOINT,
        //   credentials: 'include'
        // })
      )
    ]),
    cache
  });
}

export default withApollo(createClient);

推荐阅读