【问题标题】:How to refetch a query when a new subscription arrives in react-apollo当新订阅到达 react-apollo 时如何重新获取查询
【发布时间】:2018-12-31 10:38:48
【问题描述】:

我想知道当订阅接收到新数据时,是否有一种优雅的方法可以在 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>

【问题讨论】:

    标签: react-apollo


    【解决方案1】:

    如果你在订阅渲染道具函数渲染的组件中使用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 方法,可以更轻松地进行更清晰的高阶组合。

    【讨论】:

    • 谢谢佩德罗!我已经更新了我的答案并提供了一个简化的代码 sn-p。我认为您的建议是将 Query 组件包装到 Subscription 中,这将使 UIComponent 的第一次呈现为空。另外,如果从订阅中收到的数据保持不变,我认为 SubscriptionHandler 不会更新。
    • 即使订阅数据相同,您还希望它更新吗?您仍然可以在订阅 loadingerror 状态下呈现 UIComponent。如需对重新渲染进行更精细的控制,请查看 networkStatus
    • 是的,即使订阅数据相同,我也希望组件重新渲染,因为重新获取的数据可能不同。我使用订阅作为通知触发器,所以它只通知是否有新数据。如果有,则应调用重新获取以获取新数据。我发布了一个受您的答案启发的答案。谢谢!
    【解决方案2】:

    最后我从佩德罗的回答中得到了灵感。

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

    代码示例(简化版):

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

    【讨论】:

    • 你能描述一下最后export default graphql(GET_QUERY)(CustomComponent);行发生了什么
    • @Jonathan 当然,graphql() 创建了一个高阶组件(HOC),它包装了原始的 CustomComponet 并向其中注入了几个道具(例如数据、加载、错误等)。在我的原始问题中,您可以将其视为在渲染方法中使用 组件的简写。这是 apollo 关于如何使用方法 graphql 的文档:apollographql.com/docs/react/api/react-apollo.html#graphql
    猜你喜欢
    • 2018-10-14
    • 2019-07-11
    • 2021-03-03
    • 1970-01-01
    • 2019-01-22
    • 2017-11-28
    • 2019-12-20
    • 2018-05-04
    • 2019-12-13
    相关资源
    最近更新 更多