【问题标题】:Reorder array of objects重新排序对象数组
【发布时间】:2017-05-31 21:03:30
【问题描述】:

在我的 React 状态下,我想通过始终将选定的对象放在中间,同时保持其他对象的升序来重新排列 3 个对象的数组。

现在,我在每个对象中使用 order 属性来跟踪顺序,但这可能不是最好的方法。

例如:

this.state = {
  selected: 'item1',
  items: [
    {
      id: 'item1',
      order: 2
    },
    {
      id: 'item2'
      order: 1
    },
    {
      id: 'item3'
      order: 3
    }
  ]
}

结果数组:[item2, item1, item3]

现在,让我们假设用户选择了 item2。我将相应地更新 selected 状态属性,但是如何更新 items 属性以得到如下结果:

this.state = {
  selected: 'item2',
  items: [
    {
      id: 'item1',
      order: 1
    },
    {
      id: 'item2'
      order: 2
    },
    {
      id: 'item3'
      order: 3
    }
  ]
}

结果数组:[item1, item2, item3]

你会怎么做?我已经看到了一些 lodash 实用程序函数可以提供帮助,但我想在 vanilla JavaScript 中实现这一点。

【问题讨论】:

  • 如果有偶数个项目,你会把选中的项目放在哪里?
  • 仅供参考,您应该使用 this.setState() 来设置反应状态。除了在构造函数中之外,永远不要直接设置状态。
  • 嗯,这显然行不通,但这是需要考虑的事情。我猜一个很好的解决方案可以适用于任何奇数的项目,即使它可能会变得非常复杂。但是,在我的用例中,我只需要处理 3 个项目,所以我认为可能有一些聪明的解决方案可以让它工作!编辑@JamesKraus 确实!这只是所需结果的说明:)

标签: javascript reactjs


【解决方案1】:

你可以做这样粗暴的事情:

// Create a local shallow copy of the state
var items = this.state.items.slice()

// Find the index of the selected item within the current items array.
var selectedItemName = this.state.selected;
function isSelectedItem(element, index, array) {
    return element.id === selectedItemName;
};
var selectedIdx = items.findIndex(isSelectedItem);

// Extract that item
var selectedItem = items[selectedIdx];

// Delete the item from the items array
items.splice(selectedIdx, 1);

// Sort the items that are left over
items.sort(function(a, b) {
    return a.id < b.id ? -1 : 1;
});

// Insert the selected item back into the array
items.splice(1, 0, selectedItem);

// Set the state to the new array
this.setState({items: items});

这假定 items 数组的大小始终为 3!

【讨论】:

  • 您不应直接调用状态对象上的 .splice()。您需要调用 this.setState
  • 对不起,我不是 React 专家,所以我从“vanilla javascript”的角度回答。如果您需要执行 setState 之类的操作,请在我上面的答案中添加几个步骤:1)在开始时将状态复制到局部变量。 2)按照建议进行操作。 3) this.setState(newStateStuff)
  • @JamesKraus 建议的更新答案(据我所知)
  • 谢谢你的回答。我在之前使用 Immutable.js 的项目中使用了类似的方法,Gist with example。我希望有一个更聪明的算法来解决这个问题。但是,您的解决方案似乎很棒,这就是我将在我的项目中使用的解决方案!
【解决方案2】:

我会偷懒,只是概述您需要采取的步骤。

  • 将所选项目弹出起始数组
  • 将起始数组的第一项推入新数组
  • 将所选项目推入新数组中
  • 将起始数组的最后一项推入新数组中
  • 设置您的状态以使用新数组

【讨论】:

  • 这是否假设项目数组以 2 个项目开始?似乎 OP 表示项目列表始终是 3 个项目......
  • 是的,我可能会改进我的答案
  • 当起始数组有这个顺序[1,3,2]时不起作用,如果用户选择1,那么如果我们弹出1,然后将3推送到新数组然后推送1 最后是 3,会是 [3, 1, 2] 不正确
【解决方案3】:

你可以这样做:

注意:假设数组中有三个项目,这将起作用。但是,如果还有更多,我们只需要在插入函数中指定索引位置即可。

 this.state = {
      selected: 'item1',
      items: [
        {
          id: 'item1',
          order: 1
        },
        {
          id: 'item2',
          order: 2
        },
        {
          id: 'item3',
          order: 3
        }
      ]
    };

    // To avoid mutation.
    const insert = (list, index, newListItem) => [
      ...list.slice(0, index), // part of array before index arg
      newListItem,
      ...list.slice(index) // part of array after index arg
    ];

  // Get selected item object.
    const selectedValue = value => this.state.items.reduce((res, val) => {
      if (val.id === selectedValue) {
        res = val;
      }

      return res;
    }, {});

    const filtered = this.state.items.filter(i => i.id !== state.selected);
    const result = insert(filtered, 1, selectedValue(this.state.selected));

我们可以去掉额外的reduce,如果不是存储id,而是存储项目的索引或整个对象。

当然我们需要使用this.setState({ items: result })。该解决方案还可以确保我们不会在任何时候改变原始状态数组。

【讨论】:

    【解决方案4】:

    我整理了一个可以扩展的完整工作示例,以便您可以尝试不同的方法来实现您的预​​期用例。

    在这种情况下,我创建了一个按钮组件并渲染了其中的三个以提供一种更改选定状态的方法。

    要记住的重要事项,始终使用setState() 函数来更新 React 类状态。此外,始终使用克隆变量处理状态数组和对象,因为您需要一次更新整个对象/数组。不要修改指向状态对象或数组的指针变量的属性。

    通过引用状态对象/数组,然后通过修改引用对象的指针来更改它们的属性(偶然或无意),很有可能将错误添加到您的代码中。您将失去关于状态如何更新的所有保证,并且将 prevStatenextStatethis.state 进行比较可能无法按预期工作。

    /**
      *	@desc Sub-component that renders a button
      *	@returns {HTML} Button
      */
    class ChangeStateButton extends React.Component {
      constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    
        this.state = ({
          //any needed state here
        });
      }
      
      handleClick(e) {
        //calls parent method with the clicked button element and click state
        this.props.click(e.nativeEvent.toElement.id);
      }
    
      render() {
        
        return (
          <button 
            id = {this.props.id}
            name = {this.props.name}
            className = {this.props.className}
            onClick = {this.handleClick}  >
            Reorder to {this.props.id}!
          </button>        
        );
      }
      
    }
    
    
    
    /**
      *	@desc Creates button components to control items order in state
      *	@returns {HTML} Bound buttons
      */
    class ReorderArrayExample extends React.Component {
      constructor(props) {
        super(props);
        this.reorderItems = this.reorderItems.bind(this);
    
        this.state = ({
          selected: 'item1',
          //added to give option of where selected will insert
          selectedIndexChoice: 1,  
          items: [
            {
              id: 'item1',
              order: 2
            },
            {
              id: 'item2',
              order: 1
            },
            {
              id: 'item3',
              order: 3
            }
          ]
        });
      }
      
      reorderItems(selected) {
      
        const {items, selectedIndexChoice} = this.state,
          selectedObjectIndex = items.findIndex(el => el.id === selected);
        
        let orderedItems = items.filter(el => el.id !== selected);
        
        //You could make a faster reorder algo. This shows a working method.
        orderedItems.sort((a,b) => { return a.order - b.order })
          .splice(selectedIndexChoice, 0, items[selectedObjectIndex]);
        
        //always update state with setState function.
        this.setState({ selected, items: orderedItems });
        
        //logging results to show that this is working
        console.log('selected: ', selected);
        console.log('Ordered Items: ', JSON.stringify(orderedItems));
      }
      
      render() {
        //buttons added to show functionality
        return (
          <div>
            <ChangeStateButton 
              id='item1' 
              name='state-button-1'
              className='state-button'
              click={this.reorderItems} />
            <ChangeStateButton 
              id='item2' 
              name='state-button-2'
              className='state-button'
              click={this.reorderItems} />
            <ChangeStateButton
              id='item3' 
              name='state-button-2'
              className='state-button'
              click={this.reorderItems} />
          </div>
        );
      }
      
    }
    
    /**
      *	@desc React Class renders full page. Would have more components in a real app.
      *	@returns {HTML} full app
      */
    class App extends React.Component {
      render() {
        return (
          <div className='pg'>
            <ReorderArrayExample  />
          </div>
        );
      }
    }
    
    
    /**
      *	Render App to DOM
      */
    
    /**
      *	@desc ReactDOM renders app to HTML root node
      *	@returns {DOM} full page
      */
    ReactDOM.render(
      <App/>, document.getElementById('root')
    );
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    
    <div id="root">
      <!-- This div's content will be managed by React. -->
    </div>

    【讨论】:

    • 开车经过的人对我的答案投了反对票,甚至没有发表评论 - 为什么?有用。它回答了这个问题并使用了 React 文档推荐的设计模式。我什至添加了按钮来创建正在重新排序的数组的示例。说真的,如果没有评论进一步对话,它不会增加投票的价值。
    • 感谢伟大的 sn-p。这很好用,并且接近我想要的。但是,我认为这是因为我的问题不够清楚,添加了 order 属性只是为了跟踪排序。我认为这比对数组本身进行排序更容易。但是,如果您可以在不使用此属性的情况下重新排序数组,我很想知道可以解决问题的算法!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-04-15
    • 2013-11-30
    • 1970-01-01
    • 2012-08-16
    • 1970-01-01
    • 1970-01-01
    • 2018-08-31
    相关资源
    最近更新 更多