TypeError: unshift is not a function

TypeError: unshift is not a function

我将 objectstodos array 作为 props 传递给了 TodoList 组件。我正在尝试向 objectsarray 添加一个新的 object。我创建了一个名为 addTodo 的方法,它添加了第一个 todoobject & 当我尝试添加第二个待办事项时它失败并显示错误:TypeError: this.state.todos.unshift is not a function.

两个问题:

这是我的代码:

import React, { Component } from 'react';
import './TodoListStyles.css'

class TodoList extends Component {
  constructor(props){
    super(props);
    
    this.state = { 
      todos: this.props.todos,
      value: ''
    }

    this.handleChange = this.handleChange.bind(this);
  }

  addTodo(e) {
    e.preventDefault();
    this.setState({
      todos: this.state.todos.unshift({
        id: this.state.todos.length + 1,
        title: this.state.value,
        completed: true
      }),
      value: ''
    });

  }

  handleChange(e) {
    this.setState({ value: e.target.value });
  }

  render() {
    
    return (
      <div>
        <h1>Todos:</h1>
        <form onSubmit={(e) => this.addTodo(e)}>
          <input type='text' value={this.state.value} onChange={this.handleChange} />
        </form>
     
        <ul className='todo-list'>
          { 
            this.props.todos.map(
              todo => 
              <React.Fragment key={todo.id}>
                <li className={`todo-item ${todo.completed ? 'todo-item-completed' : ''}`}>{ todo.title }</li>
                <input type='checkbox' checked={todo.completed} />
              </React.Fragment>
            ) 
          }
        </ul>
      </div>
    )
  }
}

export default TodoList;

编辑:

我根据下面的解释答案重构了我的代码,改为使用 concat() 方法。我摆脱了错误,但它没有将 object 添加到 array。这是我的新代码:

addTodo(e) {
    e.preventDefault();
    
    const newItem = {
      id: this.state.todos.length + 1,
      title: this.state.value,
      completed: true
    };

    this.setState((prevState) => {
      prevState.todos.concat(newItem)
    })
    
    console.log(this.state.todos)
  }```

Array#unshift 是一个就地函数,它会改变它所调用的数组。 unshift returns 更新数组的长度。添加项目后失败的原因是因为您已经用数字替换了应该是数组的内容。 Number.map 不是问题所以程序崩溃了。

React 的黄金法则是 ,因为它混淆了 React 的差异算法,因为它试图找出要重新渲染的内容,结果不可预测。如果您需要使用像 unshift.

这样的变异函数,请复制一份

在这里尝试 Array#concat 而不是 unshiftconcat 是非变异的,returns 是调用它的数组的副本,因此 React 不会有问题,直接在传递给 [=20 的对象中使用内联是安全的=].

第二个问题是reading state values inside of the setState call,比如this.state.todos.length。使用接受先前状态作为参数的 setState 的回调版本:

this.setState(prevState => ({
  todos: [{
    id: prevState.todos.length + 1,
    title: prevState.value,
    completed: true
  }].concat(prevState.todos)
}));

Spread syntax 在这里代替 concat。下面的代码还修复了一个错误,其中使用 this.props.map 而不是 this.state.map 并且不使用 <input> 作为 <ul>.

的子元素

class TodoList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      todos: this.props.todos,
      value: ""
    };
    this.handleChange = this.handleChange.bind(this);
  }

  addTodo(e) {
    e.preventDefault();
    this.setState(prevState => ({
      todos: [{
        id: prevState.todos.length + 1,
        title: prevState.value,
        completed: true
      }, ...prevState.todos]
    }));
  }

  handleChange(e) {
    this.setState({value: e.target.value});
  }

  render() {    
    return (
      <div>
        <h1>Todos:</h1>
        <form onSubmit={(e) => this.addTodo(e)}>
          <input type='text' 
                 value={this.state.value} 
                 onChange={this.handleChange} />
        </form>
        <ul className='todo-list'>
          { 
            this.state.todos.map(todo =>              
              <React.Fragment key={todo.id}>
                <li className={
                  `todo-item ${todo.completed 
                   ? 'todo-item-completed' 
                   : ''}`
                }>
                  { todo.title }
                  <input type='checkbox' checked={todo.completed} />
                </li>
              </React.Fragment>
            ) 
          }
        </ul>
      </div>
    )
  }
}

ReactDOM.render(<TodoList todos={[]} />, document.body);
<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>

使用 function components and the state hook 将状态片段移动到不同的变量中,在大多数用例中简化状态管理。

附带说明一下,如果您引入删除待办事项的选项,prevState.todos.length + 1 可能不是生成 ID 的好方法。我可以添加一个 todo 并使长度为 1,添加第二个 todo 使长度为 2。然后,如果我删除 ID 为 1 的第一个项目并创建一个新项目,我有两个 ID 设置为 2 的项目。单调增加数字或 UUID 可能更好。