我已经用React创建了一个待办事项清单。我希望仅通过单击即可将任务标记为已完成。我还希望能够通过单击按钮来清除已完成的任务。这两个函数无法正确使用我设置的代码。当我单击单个待办事项以将其标记为已完成时,列表上的每个待办事项都会被标记为已完成,因此显示为“直通”。然后,当我单击指定的按钮以清除已完成的任务时,绝对没有任何反应。有人可以帮我解决这两个问题吗?
来自App组件的代码:
class App extends React.Component {
constructor() {
super();
this.state = {
todos: [
{
task: "learn how to fly drone",
id: Date.now(),
completed: false
},
{
task: "learn React class components",
id: Date.now(),
completed: false
},
{
task: "practice editing videos",
id: Date.now(),
completed: false
},
{
task: "read Ten Years A Nomad",
id: Date.now(),
completed: false
}
],
todo: ''
}
}
inputChangeHandler = event => {
this.setState({[event.target.name]: event.target.value})
}
addTask = event => {
event.preventDefault();
let newTask = {
task: this.state.todo,
id: Date.now(),
completed: false
};
this.setState({
todos: [...this.state.todos, newTask],
todo: ''
})
}
toggleComplete = itemId => {
const todos = this.state.todos.map(todo => {
if (todo.id === itemId) {
todo.completed = !todo.completed
}
return todo
});
this.setState({todos, todo: ''})
}
clearCompleted = e => {
e.preventDefault();
return this.state.todos.filter(item => !item.completed)
}
render() {
return (
<div className="App">
<h2>Welcome to your Todo App!</h2>
<TodoList
todos={this.state.todos}
toggleComplete={this.toggleComplete} />
<TodoForm todos={this.state.todos} value={this.state.todo} inputChangeHandler={this.inputChangeHandler} addTask={this.addTask} clearCompleted={this.clearCompleted}/>
</div>
);
}
}
export default App;
TodoList:
const TodoList = props => {
return (
<div>
{props.todos.map((todo, id) => (
<Todo
todo={todo}
key={id}
toggleComplete={props.toggleComplete} />
))}
</div>
)
}
export default TodoList;
Todo:
const Todo = props => {
return (
<div
key={props.todo.id}
onClick={() => {
props.toggleComplete(props.todo.id)
}}>
<p
style={{textDecoration: props.todo.completed ? 'line-through' : 'none'}}>
{props.todo.task}
</p>
</div>
)
}
export default Todo;
TodoForm:
const TodoForm = props => {
return (
<form>
<input
name="todo"
value={props.value}
type="text"
onChange={props.inputChangeHandler}
placeholder="Enter new task" />
<button onClick={props.addTask}>Add Todo</button>
<button onClick={props.clearCompleted}>Clear Completed</button>
</form>
)
}
export default TodoForm;
1]标记每个项目的原因是因为todos
状态内的所有对象都具有相同的ID。因此,toggleComplete()
将最终将todos
中的所有对象标记为true
。
您可以为每个对象分配唯一的id
,而不是为所有id
分配相同的Date
对象。
这里是一个例子:
constructor() {
super();
this.state = {
todos: [
{
task: "learn how to fly drone",
id: 1,
completed: false
},
{
task: "learn React class components",
id: 2,
completed: false
},
{
task: "practice editing videos",
id: 3,
completed: false
},
{
task: "read Ten Years A Nomad",
id: 4,
completed: false
}
],
todo: ''
}
}
2]接下来,clearCompleted
没有调用setState()
,因此,没有清除任何任务。我假设您正在尝试将completed
设置为false?在这种情况下,您只需将所有对象的完成设置为false
,然后更新您的状态即可。
clearCompleted = e => {
e.preventDefault();
const todos = this.state.todos.map(todo => ({
...todo,
completed: false,
}));
this.setState({
todos,
});
}
我创建了一个demo,可以解决您的问题。
编辑:
@wentjun's answer应该被选择为可接受的答案。。尽管我仍然觉得它提供了价值,但我还是放弃了这个答案。详细说明:我更喜欢传递索引,因为索引要快一些,因此只需对每个待办事项进行map
,即可找到被单击的项目。 (即使您使用了this.state.todos.find(itm => itm.id === itemId
,传递索引也会更快,因为您已经知道单击了哪个项目。]
原始答案:
我修改了ToDo
组件,以在click事件中传递整个待办事项对象,并在ToDoList
组件内传递待办事项索引以及待办事项。这样,您可以获取被单击的待办事项的索引,以轻松更改状态内该特定待办事项的completed
属性。
即使我对通过click事件传递的待办事项对象没有做任何事情,我还是建议仍然传递整个对象,以防万一-使事情变得更加灵活。打开控制台,查看将被单击的待办事项对象。
编辑:我将clearCompleted
更新为:
clearCompleted = e => {
e.preventDefault();
let stateCopy = {...this.state};
stateCopy.todos = stateCopy.todos.reduce((acc, cur) =>
[...acc, {...cur, completed: false}], []
);
this.setState(stateCopy);
};
我还在点击处理程序中修改了您的setState
调用。最佳做法是始终制作状态副本,修改副本,然后使用副本更新状态。
演示:
class App extends React.Component {
constructor() {
super();
this.state = {
todos: [
{
task: "learn how to fly drone",
id: Date.now(),
completed: false
},
{
task: "learn React class components",
id: Date.now(),
completed: false
},
{
task: "practice editing videos",
id: Date.now(),
completed: false
},
{
task: "read Ten Years A Nomad",
id: Date.now(),
completed: false
}
],
todo: ""
};
}
inputChangeHandler = event => {
this.setState({ [event.target.name]: event.target.value });
};
addTask = event => {
event.preventDefault();
let newTask = {
task: this.state.todo,
id: Date.now(),
completed: false
};
this.setState({
todos: [...this.state.todos, newTask],
todo: ""
});
};
toggleComplete = (todoItem, todoItemIndex) => {
let stateCopy = { ...this.state };
let item = stateCopy.todos[todoItemIndex];
item.completed = !item.completed;
this.setState(stateCopy, () =>
console.log(this.state.todos[todoItemIndex])
);
};
clearCompleted = e => {
e.preventDefault();
let stateCopy = {...this.state};
stateCopy.todos = stateCopy.todos.reduce((acc, cur) =>
[...acc, {...cur, completed: false}], []
);
this.setState(stateCopy);
};
render() {
return (
<div className="App">
<h2>Welcome to your Todo App!</h2>
<TodoList
todos={this.state.todos}
toggleComplete={this.toggleComplete}
/>
<TodoForm
todos={this.state.todos}
value={this.state.todo}
inputChangeHandler={this.inputChangeHandler}
addTask={this.addTask}
clearCompleted={this.clearCompleted}
/>
</div>
);
}
}
const TodoList = ({ todos, toggleComplete }) => {
return (
<div>
{todos && todos.map((todo, index) => (
<Todo
todo={todo}
key={index}
toggleComplete={() => toggleComplete(todo, index)} /* <<--- Pass the item and index to the handler function */
/> /* Even though we are not using the actual todo item */
/* object, still not a bad idea to pass it thru */
))}
</div>
);
};
const Todo = props => {
return (
<div
key={props.todo.id}
onClick={() => { props.toggleComplete(props.todo) }}> {/* Pass entire todo object, just in case you need it */}
<p
style={{
cursor: 'pointer',
textDecoration: props.todo.completed ? "line-through" : "none"
}}>
{props.todo.task}
</p>
</div>
);
};
const TodoForm = props => {
return (
<form>
<input
name="todo"
value={props.value}
type="text"
onChange={props.inputChangeHandler}
placeholder="Enter new task"
/>
<button onClick={props.addTask}>Add Todo</button>
<button onClick={props.clearCompleted}>Clear Completed</button>
</form>
);
};
ReactDOM.render(<App />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script>