组件加载 3 个待办事项。如果你检查中间的那个,它应该有一条线穿过它。然后,如果您单击其上的 [x] 按钮,它会消失,但由于某种原因,它下面的待办事项会被选中。
有人看到原因吗?
const Todo = props => {
const markCompleted = (checked, index) => {
const newTodos = [...props.todos];
newTodos[index].isCompleted = checked;
props.setTodos(newTodos);
};
const deleteTodo = index => {
const newTodos = [...props.todos];
newTodos.splice(index, 1);
props.setTodos(newTodos);
};
return (
<div
style={{ textDecoration: props.todo.isCompleted ? 'line-through' : '' }}
className="todo"
>
<input
type="checkbox"
onChange={e => markCompleted(e.target.checked, props.index)}
/>
{props.todo.text}
<button onClick={() => deleteTodo(props.index)}>x</button>
</div>
);
};
const TodoForm = props => {
const [value, setValue] = React.useState('');
const addTodo = e => {
e.preventDefault();
if (!value) return;
const newTodos = [...props.todos, { text: value }];
props.setTodos(newTodos);
setValue('');
};
return (
<form onSubmit={addTodo}>
<input
type="text"
className="input"
value={value}
onChange={e => setValue(e.target.value)}
/>
</form>
);
};
const App = () => {
const [todos, setTodos] = React.useState([
{ text: 'Learn about React', isCompleted: false },
{ text: 'Meet friend for lunch', isCompleted: false },
{ text: 'Build really cool todo app', isCompleted: false }
]);
return (
<div className="app">
<div className="todo-list">
{todos.map((todo, index) => (
<Todo {...{ key: index, todo, index, todos, setTodos }} />
))}
<TodoForm {...{ todos, setTodos }} />
</div>
</div>
);
};
ReactDOM.render(
<App />
, document.querySelector('#react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
先前选中的复选框将保持呈现状态。你应该显式设置它的checked
状态,以便每次都从 props 中获取它,而不是可能从用户输入中获取:
checked={props.todo.isCompleted}
您还应该使用像filter
这样的函数式方法,而不是像splice
这样的变异方法,并且newTodos[index].isCompleted = checked;
正在改变todo对象 -[...props.todos]
只浅层克隆对象数组。将更改对象周围的待办事项分散到传递给setTodos
的数组中。
props.setTodos([
...todos.slice(0, index),
{ ...todos[index], isCompleted: checked },
...todos.slice(index + 1),
]);
const Todo = props => {
const markCompleted = (checked, index) => {
const { todos } = props;
props.setTodos([
...todos.slice(0, index),
{ ...todos[index], isCompleted: checked },
...todos.slice(index + 1),
]);
};
const deleteTodo = index => {
props.setTodos(props.todos.filter((todo, i) => i !== index));
};
return (
<div
style={{ textDecoration: props.todo.isCompleted ? 'line-through' : '' }}
className="todo"
>
<input
type="checkbox"
onChange={e => markCompleted(e.target.checked, props.index)}
checked={props.todo.isCompleted}
/>
{props.todo.text}
<button onClick={() => deleteTodo(props.index)}>x</button>
</div>
);
};
const TodoForm = props => {
const [value, setValue] = React.useState('');
const addTodo = e => {
e.preventDefault();
if (!value) return;
const newTodos = [...props.todos, { text: value }];
props.setTodos(newTodos);
setValue('');
};
return (
<form onSubmit={addTodo}>
<input
type="text"
className="input"
value={value}
onChange={e => setValue(e.target.value)}
/>
</form>
);
};
const App = () => {
const [todos, setTodos] = React.useState([
{ text: 'Learn about React', isCompleted: false },
{ text: 'Meet friend for lunch', isCompleted: false },
{ text: 'Build really cool todo app', isCompleted: false }
]);
return (
<div className="app">
<div className="todo-list">
{todos.map((todo, index) => (
<Todo {...{ key: index, todo, index, todos, setTodos }} />
))}
<TodoForm {...{ todos, setTodos }} />
</div>
</div>
);
};
ReactDOM.render(
<App />
, document.querySelector('#react'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>