App.jsx
import React, { useState } from "react";
import "../styles.css";
// 引入其他组件
import NewTodoForm from "./NewTodoForm";
import TodoList from "./TodoList";
// 抽离添加任务表单
export default function App() {
// useState 用于在函数组件中添加状态
// 它返回一个数组,其中包含当前状态的值和一个用于更新状态的函数
// 通常,数组的第一个元素是当前状态的值,第二个元素是更新该状态的函数
// 语法:const [state, setState] = useState(初始值或初始化回调)
// 这种语法叫作解构,是一个JavaScript概念,可以搜索一下什么叫解构
const [todos, setTodos] = useState([]);
// 这个方法用来添加Todo
// 虽然声明在父组件中,但其实是在子组件NewTodoForm中被调用的
// 因为调用的时候传入了title,所以好像我们从NewTodoForm中获得了title一样
function addTodos(title) {
setTodos((current) => {
return [
...current,
// { id: crypto.randomUUID(), title: title, completed: false },
{ id: crypto.randomUUID(), title, completed: false },
];
});
}
// 用来更新给定id的Todo的完成状态为completed
function toggleTodo(id, completed) {
// 用setState的回调形式来更新,current是当前todos的值,记住它可是个数组
setTodos((current) =>
// 所以我们先对这个数组来进行遍历,每个todo是current中的每一项
current.map((todo) => {
// 如果遍历的过程中找到了我们所选的那个id,就更新它
// 也就是返回更新后的值,...是传播运算符可以搜索一下什么是传播运算符
if (todo.id === id) return { ...todo, completed };
// 不是我们选的那个todo就不管它
return todo;
})
);
}
// 删除任务
function deleteTodo(id) {
setTodos((current) => {
return current.filter((todo) => {
return todo.id !== id;
});
});
}
return (
// 这些内容叫JSX,是一种很类似html的东西但不是html
// 一种简单的理解是你把它理解成其中可以直接使用JavaScript的html
// 语法也和html有许多不一样的地方,组件允许我们自定义,每个组件都是一个function
// 可以手动搜索一下什么是JSX
// 另外所有JSX在返回的时候都要保证只有一个根,这里我使用<></>来确保
// <></>其实是<React.Fragment></React.Fragment>的简写
<>
<NewTodoForm addTodos={addTodos} />
<h1 className="header">Todo List</h1>
<TodoList todos={todos} toggleTodo={toggleTodo} deleteTodo={deleteTodo} />
</>
);
}
NewTodoForm.jsx
import { useState } from "react";
export default function NewTodoForm({ addTodos }) {
const [newItem, setNewItem] = useState("");
// 添加任务
function handleSubmit(e) {
e.preventDefault();
console.log(e.target.value);
if (newItem === "") {
return;
}
addTodos(newItem);
setNewItem("");
}
return (
<form className="new-item-form" onSubmit={handleSubmit}>
<div className="form-row">
<label htmlFor="item">newItem</label>
<input
type="text"
id="item"
value={newItem}
onChange={(e) => setNewItem(e.target.value)}
/>
</div>
<button className="btn" type="submit">
add task
</button>
</form>
);
}
TodoList.jsx
import TodoItem from "./TodoItem";
export default function TodoList({ todos, toggleTodo, deleteTodo }) {
return (
<>
{todos.length === 0 && <div>No Todos</div>}
{/* 展示任务清单 */}
<ul className="list">
{todos.map((todo) => (
// 因为传入的props是一个对象(键值对),同名又可以合并键值
// 所以{...todo}就等同于将整个todo对象作为props对象传入
// 在TodoItem中就可以顺理成章地解构出todo的所有字段
// <TodoItem
// id={todo.id}
// title={todo.title}
// completed={todo.completed}
// key={todo.id}
// toggleTodo={toggleTodo}
// deleteTodo={deleteTodo}
// />
// 键和值一样,可以简写
<TodoItem
{...todo}
key={todo.id}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
))}
</ul>
</>
);
}
TodoItem.jsx
import React from "react";
// 结构todo的所有字段,传入是用{...todo}的形式
export default function TodoItem(id, title, completed, toggleTodo, deleteTodo) {
return (
<li>
<label>
<input
type="checkbox"
checked={completed}
onChange={(e) => toggleTodo(id, e.target.checked)}
/>
{title}
</label>
<button className="btn btn-danger" onClick={() => deleteTodo(id)}>
delete task
</button>
</li>
);
}
Q1:
Q2: