如何使用 Apollo Query 构造 React 代码,以便随机查询的对象数组不会随着状态变化而重新呈现?

Posted

技术标签:

【中文标题】如何使用 Apollo Query 构造 React 代码,以便随机查询的对象数组不会随着状态变化而重新呈现?【英文标题】:How to structure React code with Apollo Query so that a random queried array of objects doesn't rerender with state change? 【发布时间】:2019-06-02 19:31:35 【问题描述】:

问题:我正在使用 Apollo 客户端,并且将牌组渲染为这样的“/deck/2”,我想随机洗牌,一次只显示一张一个按钮查看下一个。每次状态更改时,我都会遇到 React 重新渲染的问题(我的 onClick 索引计数器 +1),这会重新洗牌,因为 shuffledCards 变量在查询中。我不确定如何防止这种情况发生。

我怎样才能改变列表而不用担心它们会被按钮的“onClick”重新洗牌。我想有一种方法可以在渲染之外获取随机数组,我可以在常规反应中做到这一点,但是使用 Apollo 查询我很难理解。

由于我对 Apollo 的 React 和 Graphql 缺乏经验,这就是我遇到的问题,而且我还没有找到类似的 Apollo graphql 项目可以借鉴。我不能在对象上使用 map,但也许有一种方法可以使用 map 一次显示数组的 1 个对象?我还没有找到可行的解决方案。

我打算发生的事情:我只是想一次渲染一张洗牌的卡片数组,然后按下下一步按钮应该逐步浏览随机数组中的卡片,而无需重新每当我点击按钮时渲染,否则卡片将随机重复。

代码如下:

    import React,  Component, Fragment  from "react";
    ```
    import CardItem from "./CardItem";

    const CARDS_QUERY = gql`
      query CardsQuery($id: ID!) 
        ```
    `;

    export class Cards extends Component 
      constructor(props) 
        super(props);
        this.state = 
          index: 0
        ;

        this.goToNext = this.goToNext.bind(this);
      

      goToNext() 
        this.setState(
          index: this.state.index + 1
        );
      

      shuffle(array) 
        for (let i = array.length - 1; i > 0; i--) 
          const j = Math.floor(Math.random() * (i + 1));
          [array[i], array[j]] = [array[j], array[i]];
        
        return array;
      

      render() 
        let  id  = this.props.match.params;
        id = parseInt(id);

        return (
          <Fragment>
            <Query query=CARDS_QUERY variables= id >
              ( data, error, loading ) => 
                if (loading) 
                  return <Loading />;
                
                if (error)
                

                const CardsToRender = data.deck.cards;

                //This variable gets reshuffled at every re-render
                const shuffledCards = this.shuffle(CardsToRender);

                //Single item to be returned
                const item = shuffledCards[this.state.index];

                if (this.state.index >= shuffledCards.length) 
                  return (
                    <div>
                      <h1>Finished</h1>
                    </div>
                  );
                 else 
                  return (
                    <Fragment>
               // Here I can get one item to display, but if I press next, the state changes which fires a re-render, 
           //shuffling the cards once more.  My intention is to only shuffle cards at first render until the browser page is
           //refreshed or user navigates away
              <h1>item.front</h1>
              <h1>item.back</h1>

                   //My second attempt is to map out the cards, but I have only been able to render a list,
                  // but not one at a time.  Maybe there is a simple code solution in my .map to display
                    //one at a time without needing to change state?
                      shuffledCards.map(card => (
                        <CardItem key=card.id card=card />
                      ))
                      <p>
                        <button onClick=this.goToNext>Next</button>
                      </p>
                    </Fragment>
                  );
                
              
            </Query>
          </Fragment>
        );
      
    

    ```

我将不胜感激提供的任何帮助。谢谢!

【问题讨论】:

【参考方案1】:

您在渲染函数中调用 this.shuffle() - 因此它会在每次渲染时随机播放。

将它移到你的构造函数中,它只会被调用一次。

constructor(props) 
  super(props);
  this.state = 
    index: 0
  ;

  this.goToNext = this.goToNext.bind(this);
  const CardsToRender = data.deck.cards;

【讨论】:

【参考方案2】:

我不知道 Appolo Query,但您提到的问题与 React 更相关。您可以尝试以下一种,以避免在每次重新渲染时洗牌。

将您的组件重构为两部分。

1) ShuffleCards.js(随便取个名字:) ) - 将你的组件移动到“ShuffleCards”中,然后将打乱的绳索传递给子组件,在那里你可以更新状态以呈现下一张卡片。

// ShuffledCards.js

 import React,  Component, Fragment  from "react";
    ```
    import CardItem from "./CardItem";

    const CARDS_QUERY = gql`
      query CardsQuery($id: ID!) 
        ```
    `;

    export class ShuffleCards extends Component 
      constructor(props) 
        super(props);
      

      shuffle(array) 
        for (let i = array.length - 1; i > 0; i--) 
          const j = Math.floor(Math.random() * (i + 1));
          [array[i], array[j]] = [array[j], array[i]];
        
        return array;
      

      render() 
        let  id  = this.props.match.params;
        id = parseInt(id);
        return (
          <Fragment>
            <Query query=CARDS_QUERY variables= id >
              ( data, error, loading ) => 
                if (loading) 
                  return <Loading />;
                
                if (error) 
                
                const CardsToRender = data.deck.cards;
                const shuffledCards = this.shuffle(CardsToRender);
                return (
                    <Cards shuffledCards=shuffledCards /> 
                );
              
            </Query>
          </Fragment>
        );
      
    

将处理显示卡片和更新状态的代码移至“卡片”组件。

import React,  Component, Fragment  from "react";
import CardItem from "./CardItem";

export class Cards extends Component 
      constructor(props) 
        super(props);
        this.state = 
          index: 0
        ;

        this.goToNext = this.goToNext.bind(this);
      

      goToNext() 
        this.setState(
          index: this.state.index + 1
        );
      

    render() 
    const shuffledCards = this.props || [];
    return (
        <div>
        
            this.state.index >= shuffledCards.length ?
              <div>
                <h1>Finished</h1>
              </div>
              :
              <Fragment>
                  <h1>item.front</h1>
                  <h1>item.back</h1>
                  
                      shuffledCards.map(card => (
                      <CardItem key=card.id card=card />
                      ))
                  
                  <p>
                  <button onClick=this.goToNext>Next</button>
                  </p>
              </Fragment>
        
        </div>
        )
    

【讨论】:

以上是关于如何使用 Apollo Query 构造 React 代码,以便随机查询的对象数组不会随着状态变化而重新呈现?的主要内容,如果未能解决你的问题,请参考以下文章

NextJS + Apollo:如何在 getServerSideProps 内的 apolloClient.query 上获取多个查询?

如何从 Apollo 缓存中查询没有 ROOT_QUERY 上的查询

使用 Redux Thunk 和 Apollo Graphql 做出反应 - 如何访问 client.query?

如何从对象中提取数据到 Apollo Query 组件中

如何在调用 Apollo Graphql Query 之前等待服务器响应?

Nuxt(Vue) 使用 $apollo.query 处理 Apollo 错误