【问题标题】:React list index is not correct反应列表索引不正确
【发布时间】:2018-08-20 00:49:33
【问题描述】:

我正在通过制作一个简单的待办事项列表来学习 React。一直很喜欢。

当我删除一个待办事项时,它会删除上方点击删除的列表项。我的索引必须关闭,但我找不到原因。

没有奇怪的 css 不对齐每个项目的渲染删除按钮。索引似乎只是针对它上面的项目。

代码(缩短)

  handleSubmit(event) {
    let newTodos = this.state.todos;
    let index = newTodos.length;
    if (this.state.input !== "") {
      newTodos.unshift({
        text: this.state.input,
        key: this.state.input
      });
    }
    this.setState({todos: newTodos, input: ""});    
    event.preventDefault();
    this.setLocalStorage();
  }

  deleteItem(index) {
    let todoItems = this.state.todos;
    todoItems.splice(index, 1); 

    this.setState({
      todos: todoItems
    });
    this.setLocalStorage();
  }

  render() {
    return (
      <div className="todo-holder">
        <form className="todoInput-holder" onSubmit={this.handleSubmit}>
          <input type="text" placeholder="To do..." value={this.state.input} onChange={this.handleChange} />
        </form>
        <ul id="todo-list">
          <FlipMove duration={250} easing="ease-out"> 
          {
              this.state.todos.map(todos => {
                return <li key={todos.key} className="todo-li-item" >
                  <span className="todo-item">{todos.text}</span>
                  <span onClick={this.deleteItem} className="delet-todo">&#10005;</span>
                </li>;
              })
            } 
            </FlipMove>
        </ul>
      </div>
    );
  } 

想法?

【问题讨论】:

  • 另外需要指出的是,你不应该直接操纵状态。在您的 deleteItem 方法中,将行从 let todoItems = this.state.todos 更改为 let todoItems = Object.assign({}, this.state.todos);,这将创建状态的副本而不是引用。
  • 我发现的错误是:1.let newTodos = this.state.todos不会创建深拷贝。因此,当使用unshift 时,您尝试更改不可变状态。 2. 您需要为映射功能提供唯一键。如果 key 与 input 相同,则可能不唯一,请尝试(new Date()).getTime()。
  • 谢谢@FisNaN,我以为我是通过创建一个新变量来间接操纵我的状态——显然不是。使用日期作为键对我来说似乎有点奇怪,但我现在认为它是最好的解决方案。这是终极独特的哈哈

标签: javascript reactjs indexing


【解决方案1】:

原因其实很简单:onClick 期望你传入一个参数为event 的函数(参见ReactJS documentation)。因此,您期望 deleteItem(index) 的 index 的位置放置了 event 对象。 JavaScript 在 splice 调用中进行了秘密类型转换,因为 event 不是数字,这导致您遇到奇怪的行为。

event =&gt; event is an object =&gt; convert to valid Number is impossible =&gt; event becomes NaN =&gt; todos.splice(NaN, 1) =&gt; somehow splice treats NaN as 0 always

解决问题,你可以把deleteItem改成(index, event)作为2个参数(其实event不打算用可以忽略),也可以bind(@ 987654322@)index(通过todos.map(el, index =&gt; { ... })检索)从todos到它,绕过默认的event参数。

以下代码是可运行的,我删除了您的 localStorage 代码以使其工作,您可以根据需要将它们添加回来。

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      input: '',
      todos: []
    }
  }

  handleSubmit = (event) => {
    let newTodos = this.state.todos;
    let index = newTodos.length;
    if (this.state.input !== "") {
      newTodos.unshift({
        text: this.state.input,
        key: this.state.input
      });
    }
    this.setState({todos: newTodos, input: ""});    
    event.preventDefault();
  }

  deleteItem = (index, e) => {
    let todoItems = this.state.todos;
    todoItems.splice(index, 1); 

    this.setState({
      todos: todoItems // other answers complains about deep copy, but it is not the core issue here
    });
  }
  
  handleChange = (e) => {
    this.setState({
      input: e.target.value
    })
  }

  render() {
    return (
      <div className="todo-holder">
        <form className="todoInput-holder" onSubmit={this.handleSubmit}>
          <input type="text" placeholder="To do..." value={this.state.input} onChange={this.handleChange} />
          <button type="submit">submit</button>
        </form>
        <ul id="todo-list"> 
          {
              this.state.todos.map((todos, i) => { // i is the index of current todo entry
                return <li key={todos.key} className="todo-li-item" >
                  <span className="todo-item">{todos.text}</span>
                  <span onClick={this.deleteItem.bind(this, i) /* this binds `i` to the first parameter of deleteItem(i, event) */} className="delet-todo">&#10005;</span>
                </li>;
              })
            } 
        </ul>
      </div>
    );
  } 
}

ReactDOM.render(<App />, document.getElementById('app'))
<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="app"></div>

【讨论】:

  • 这里有很多很好的答案,我将根据所有这些对我的代码进行许多更改,但是您在整个代码中的 cmets - 特别是关于深层副本的信息非常丰富。我会经常回来看这篇文章,谢谢你的帮助。
【解决方案2】:

在 render() 方法中,您不会将索引传递给项目对象

this.state.todos.map((todos, index) => 
  <li key={todos.key} className="todo-li-item" >
    <span className="todo-item">{todos.text}</span>
    <span onClick={() => this.deleteItem(index)} className="delet-todo">&#10005;</span>
  </li>
);

【讨论】:

    【解决方案3】:

    您实际上并未将索引传递给 deleteItem 方法。

    首先,将索引传递给方法:

              this.state.todos.map((todos, index) => {
                return <li key={todos.key} className="todo-li-item" >
                  <span className="todo-item">{todos.text}</span>
                  <span onClick={() => this.deleteItem(index)} className="delet-todo">&#10005;</span>
                </li>;
              })
    

    其次,不要用 splice 之类的方法改变 React 的状态。 React 状态中的任何内容都应该是不可变的,并且只能与 setState 接触。如果要使用拼接,请确保在数组的副本上进行,例如

      deleteItem(index) {
        // .slice() will make a copy of the array
        let todoItems = this.state.todos.slice();
        todoItems.splice(index, 1); 
    
        this.setState({
          todos: todoItems
        });
        this.setLocalStorage();
      }
    

    但是,更清洁的解决方案是使用 Array 的 filter 方法:

      deleteItem(index) {
        let todoItems = this.state.todos.filter((_, i) => i !== index)
        this.setState({
          todos: todoItems
        });
        this.setLocalStorage();
      }
    

    或者,使用您在 React 中为 key 使用的任何属性:

      this.state.todos.map((todos) => {
        return <li key={todos.key} className="todo-li-item" >
          <span className="todo-item">{todos.text}</span>
          <span onClick={() => this.deleteItem(todos.key)} className="delet-todo">&#10005;</span>
        </li>;
      })
    
      deleteItem(key) {
        let todoItems = this.state.todos.filter((todo) => todo.key !== key)
        this.setState({
          todos: todoItems
        });
        this.setLocalStorage();
      }
    

    【讨论】:

    • 我会在未来几年使用您建议的技术来删除项目,谢谢
    【解决方案4】:

    这里有两个问题:

    deleteItem(index) {
        //index is the Event Object here
        let todoItems = this.state.todos;
        todoItems.splice(index, 1); 
    
        this.setState({
          todos: todoItems
        });
        this.setLocalStorage();
      }
    
    <span onClick={this.deleteItem} className="delet-todo">&#10005;</span>
    //Here React pass the event object as parameter to deleteItem
    

    所以,基本上您需要将调用函数的方式更改为这样的方式

    <span onClick={() => this.deleteItem(index)} className="delet-todo">&#10005;</span>
    

    希望对你有帮助!

    【讨论】:

      猜你喜欢
      • 2021-11-12
      • 2013-11-19
      • 1970-01-01
      • 2018-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-27
      相关资源
      最近更新 更多