首页 > 解决方案 > 在 React 中从数组中渲染随机元素

问题描述

我在 Potter-API 的帮助下制作了一个小型反应应用程序,用户可以通过它搜索特定的字符或咒语。从 API 获取数据后,我正在渲染 6 个随机项目(字符/咒语),单击它们会导致项目(字符/咒语)的详细视图,我还添加了一个名为 randomize 的按钮,单击该按钮会呈现一个新集合的随机元素。

我面临的问题是这个“随机化”按钮,在反复单击它时发生的事情不是只渲染 6 个元素,而是开始渲染 7、8 ......并且在某些时候中断导致错误。

我想知道是什么原因造成的以及对此的修复。

class RandomItems extends React.Component {

    // this.props.randomNums contain the number of random characters to display
    // and the max limit of the items (this.props.data.length) and this.props.subUrl contains
    // the detailed-view URL(characters or spells) this.props.data is an array of item objects(characters/spells) out of
    // which some characters(some = this.props.randomNums) are chosen and rendered by this component
    constructor(props) {
        super(props);
        this.state = {
            itemsList: [],
            loading: true
        }

        this.handleRandoms = this.handleRandoms.bind(this)
    }


    componentDidMount() {
        const items = this.getRandomItems()
        this.setState({itemsList: items, loading: false})
    }

    handleRandoms(){
        const items = this.getRandomItems()
        this.setState({itemsList: items})
    }

    getRandomItems() {
        function getRandomNumbers(num, limit) {
            let randoms = []
            for (let i = 0; i < num; i++) {
                randoms.push(Math.floor(Math.random() * (limit + 1)))
            }
            return randoms
        }

        const randoms = getRandomNumbers(this.props.randomNums, this.props.data.length)
        return randoms.map(value => this.props.data[value])
    }


    // Each of the returned character should be a Link to the detail view of that character
    // Using the same component for both the spells/characters page so since the object attributes
    // are different for both categories I'm using a prop accessKey that is a string(name/spell) for 
    // accessing the specific attribute based on the item type(character/spell) 
    render() {
        if (this.state.itemsList && !this.state.loading) {
            return (
                <div style={{marginTop: '6em'}}>
                    <h2>Have Some Random {(this.props.subUrl)}!</h2>
                    <br/>
                    {this.state.itemsList.map((item, index) => {
                        return (
                            <div className={'characterDesign'} key={item._id}>


                                <Link className={'highlight-link'}
                                      to={`/${this.props.subUrl}/${item._id}`}
                                >
                                    {(index + 1) + '. ' + item[this.props.accessKey]}
                                </Link>

                            </div>
                        )
                    })}
                    <button className={'fill'} onClick={this.handleRandoms}>Randomize!</button>
                </div>
            )
        } else {
            return (<h1>Loading...</h1>)
        }
    }
}

所需的数据对象数组从父组件发送

  1. 在点击了一些随机化后 在点击了一些随机化后
  2. 多次点击随机化按钮后 多次点击随机化按钮后

PS。我查看了呈现这些项目的数组,并且每次它都包含 6 个元素(即使正在呈现更多数量的元素)

标签: javascriptreactjsreact-router

解决方案


您的getRandomItems函数可以多次返回同一个项目,因此当 react 渲染项目时,可能会有多个相同的项目_id(这是因为key多个项目可以有相同的key)。

当您有多个<div>具有相同key属性的 s 时, react 会感到困惑。的全部意义在于key独一无二。如果您有多个具有相同的键,则在再次渲染时,react 只会清理最后一个(对于任何给定键)。

这是根本问题的极简示例:

class RandomItems extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            itemsList: [],
            loading: true
        };
    }

    componentDidMount() {
        const items = [
          this.props.data[0],
          this.props.data[0],
          this.props.data[0]
        ];
        this.setState({
          itemsList: items
        });
    }
  
    onClickTest = () => {
      const items = [
        this.props.data[1],
        this.props.data[2]
      ];
      this.setState({
        itemsList: items
      });
    };

    render() {
        return (
          <div>
            {this.state.itemsList.map((item, index) => {
              return (
                <div key={item.id}>
                  {item.name}
                </div>
              )
            })}
            <button onClick={this.onClickTest}>Test</button>
          </div>
        )
    }
}

/////////////////////////

ReactDOM.render(
  <RandomItems randomNums={3} data={[
      {id: 0, name: 'Zeroth'},
      {id: 1, name: 'First'},
      {id: 2, name: 'Second'}
  ]}></RandomItems>,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

请注意,当您单击“测试”时,三个“0 Zeroth”div 中的最后一个被删除(应该如此),但其他两个没有(因为 react 不期望多个 div 具有相同的 div key)。

在您的情况下,最好的解决方案可能是修复您的 randomize 函数,这样它就不会多次返回相同的项目。例子:

getRandomItems = () => {
  let allItems = [...this.props.data];
  const randomCount = this.props.randomNums;
  
  const randomItems = [];
  for (let i = 0; i < randomCount; i++) {
    const randomIndex = Math.floor(Math.random() * allItems.length);
    const randomItem = allItems.splice(randomIndex, 1)[0];
    randomItems.push(randomItem);
  }
  
  return randomItems;
};

或者,您可以将keyfrom更改item._idindex也可以解决问题,因为index永远是唯一的。


推荐阅读