如何在新订阅到达 react-apollo 时重新获取查询

How to refetch a query when a new subscription arrives in react-apollo

我想知道是否有一种优雅的方式可以在订阅收到新数据时触发 react-apollo 中查询的重新获取(这里的数据不重要,与之前的数据相同)。我只是在这里使用订阅作为通知触发器,告诉 Query 重新获取。

我尝试使用 Subscription 组件和 subscribeToMore 来调用 Query 子组件中的 "refetch" 方法,但这两种方法都会导致无限次重新获取。

注意:我使用的是 react-apollo v2.1.3 和 apollo-client v2.3.5

这是代码的简化版本

<Query
  query={GET_QUERY}
  variables={{ blah: 'test' }}
>
  {({ data, refetch }) => (
    <CustomComponent data={data} />
    //put subscription here? It'll cause infinite re-rendering/refetch loop
  )}
<Query>

如果在 Subscription render props 函数渲染的组件中使用 componentDidMountcomponentDidUpdate 是可能的。

该示例使用 recompose 高阶组件以避免过多的样板化。看起来像:

 /*
 * Component rendered when there's data from subscription
 */
export const SubscriptionHandler = compose(
  // This would be the query you want to refetch
  graphql(QUERY_GQL, { 
    name: 'queryName'
  }),
  lifecycle({

    refetchQuery() {
      // condition to refetch based on subscription data received
      if (this.props.data) {  
        this.props.queryName.refetch()
      }
    },

    componentDidMount() {
      this.refetchQuery();
    },

    componentDidUpdate() {
      this.refetchQuery();
    }
  })
)(UIComponent);


/*
 * Component that creates the subscription operation
 */
const Subscriber = ({ username }) => {
  return (
    <Subscription
      subscription={SUBSCRIPTION_GQL}
      variables={{ ...variables }}
    >
      {({ data, loading, error }) => {
        if (loading || error) {
          return null;
        }
        return <SubscriptionHandler data={data} />;
      }}
    </Subscription>
  );
});

在完全分离查询和订阅组件的同时避免重新渲染循环的另一种方法是使用 Apollo Automatic Cache updates:

                 +------------------------------------------+
                 |                                          |
    +----------->|  Apollo Store                            |
    |            |                                          |
    |            +------------------------------+-----------+
    +                                           |
client.query                                    |
    ^            +-----------------+  +---------v-----------+
    |            |                 |  |                     |
    |            | Subscription    |  | Query               |
    |            |                 |  |                     |
    |            |                 |  | +-----------------+ |
    |            |  renderNothing  |  | |                 | |
    +------------+                 |  | | Component       | |
                 |                 |  | |                 | |
                 |                 |  | +-----------------+ |
                 |                 |  |                     |
                 +-----------------+  +---------------------+
const Component =() => (
  <div>
    <Subscriber />
    <QueryComponent />
  </div>
)

/*
 * Component that only renders Query data 
 * updated automatically on query cache updates thanks to 
 * apollo automatic cache updates
 */
const QueryComponent = graphql(QUERY_GQL, { 
  name: 'queryName'
})(() => {  
  return (
    <JSX />
  );
});

/*
 * Component that creates the subscription operation
 */
const Subscriber = ({ username }) => {
  return (
    <Subscription
      subscription={SUBSCRIPTION_GQL}
      variables={{ ...variables }}
    >
      {({ data, loading, error }) => {
        if (loading || error) {
          return null;
        }
        return <SubscriptionHandler data={data} />;
      }}
    </Subscription>
  );
});

/*
* Component rendered when there's data from subscription
*/
const SubscriptionHandler = compose(

  // This would be the query you want to refetch
  lifecycle({

    refetchQuery() {
      // condition to refetch based on subscription data received
      if (this.props.data) {  
        var variables = {
            ...this.props.data // if you need subscription data for the variables
        };

        // Fetch the query, will automatically update the cache
        // and cause QueryComponent re-render
        this.client.query(QUERY_GQL, {
          variables: {
            ...variables
          }
        });
      }
    },

    componentDidMount() {
      this.refetchQuery();
    },

    componentDidUpdate() {
      this.refetchQuery();
    }
  }),        
  renderNothing
)();


/*
* Component that creates the subscription operation
*/
const Subscriber = ({ username }) => {
    return (
        <Subscription
        subscription={SUBSCRIPTION_GQL}
        variables={{ ...variables }}
        >
        {({ data, loading, error }) => {
            if (loading || error) {
            return null;
            }
            return <SubscriptionHandler data={data} />;
        }}
        </Subscription>
    );
});

注意: composelifecyclerecompose 方法,可以更轻松地实现更清晰的高阶组合。

最后我在佩德罗的回答中得到了启发。

思考:我面临的问题是我想在Subscription中调用Query的refetch方法,然而,Query和订阅组件只能在 render 方法中访问。那就是无限refetch/re-rendering的根本原因。为了解决这个问题,我们需要将订阅逻辑从 render 方法中移出,并将其放在生命周期方法中的某个地方(即 componentDidMount),在重新获取后不会再次调用它触发。然后我决定使用 graphql hoc 而不是查询组件,这样我就可以注入像 refetchsubscribeToMore[=25= 这样的道具] 在我的组件的顶层,这使得它们可以从任何生命周期方法访问。

代码示例(简化版):

class CustomComponent extends React.Component {
  componentDidMount() {
    const { data: { refetch, subscribeToMore }} = this.props;

    this.unsubscribe = subscribeToMore({
      document: <SUBSCRIBE_GRAPHQL>,
      variables: { test: 'blah' },
      updateQuery: (prev) => {
        refetch();
        return prev;
      },     
    });
  }

  componentWillUnmount() {
    this.unsubscribe();
  }

  render() {
    const { data: queryResults, loading, error } } = this.props;

    if (loading || error) return null;

    return <WhatEverYouWant with={queryResults} />
  }
}

export default graphql(GET_QUERY)(CustomComponent);