如何解决类型错误:unshift 不是函数
我已将 todos
array
的 objects
作为 props
传递给 TodoList
组件。我正在尝试向 object
的 array
添加新的 objects
。我创建了一个名为 addTodo
的方法,它添加了第一个 todo
的 object
,当我尝试添加第二个待办事项时,它失败并显示错误: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,completed: true
};
this.setState((prevstate) => {
prevstate.todos.concat(newItem)
})
console.log(this.state.todos)
}```
解决方法
Array#unshift
是一个就地函数,它改变它被调用的数组。 unshift
返回更新后数组的长度。添加项目后失败的原因是因为您已经用数字替换了应该是数组的内容。 Number.map
不是一个东西,所以程序崩溃了。
React 的黄金法则是 "never mutate state",因为它混淆了 React 的差异算法,因为它试图找出要重新渲染的内容,结果不可预测。如果您需要使用诸如 unshift
之类的变异函数,请制作一份副本。
在此处尝试使用 Array#concat
而不是 unshift
。 concat
是非变异的,并返回调用它的数组的副本,因此 React 不会出现问题,并且直接在传递给 setState
的对象中使用内联是安全的。
第二个问题是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,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 和 state hook 将状态片段移动到不同的变量中,从而在大多数用例中简化状态管理。
附带说明,如果您引入删除待办事项的选项,prevState.todos.length + 1
可能不是生成 ID 的糟糕方法。我可以添加一个todo并使长度为1,添加第二个todo使长度为2。然后,如果我删除id为1的第一个项目并创建一个新项目,我有两个id设置为2的项目。单调增加数字或 UUID 可能会更好。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。