React 第3周笔记-列表渲染 -表单-事件处理
路线图阶段:第一阶段(React 基础)第3周 项目:Todo List(添加 / 删除 / 标记完成) 核心心智模型:
UI = f(state)—— 状态变,UI 自动跟着变
1. 列表渲染:.map() + key
把数组变成一组 UI 元素。
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
key 是什么、为什么需要
key是给 React 内部用的,不是给数据用的。- 作用:列表增/删/排序时,React 靠
key认出”这是同一个元素”,避免无脑全部重渲染。 - 类比:Figma 里每个图层的唯一名字。都叫”图层1”就会乱套。
- 真实开发用
key={item.id}(数据自带 id),不要用数组下标 index,也不要用可能重复的内容。
小括号 vs 大括号(箭头函数返回值)
// 写法1:() 包住 → 自动 return(常用于多行 JSX)
(item) => (
<li>...</li>
)
// 写法2:{} 是函数体 → 必须手动 return
(item) => {
return <li>...</li>
}
⚠️ 小括号的作用是”让多行 JSX 自动被 return”,和”返回对象”无关。
2. 受控输入框(Controlled Input)
输入框的值存在 state 里,由 React 全权管理。
const [todo, setTodo] = useState("");
function handleChange(e) {
setTodo(e.target.value); // e.target 是 input 元素,.value 是它当前的值
}
<input type="text" value={todo} onChange={handleChange} />
关键点
onChange传进来的是事件对象(event object),不是值本身。- 用户输入的内容在
e.target.value。 value={todo}让输入框成为”受控”——React 是唯一数据源。
受控输入框的好处
- React 随时知道输入框里是什么,可以立刻拿来用(如实时过滤搜索)。
- 非受控输入框的值藏在 DOM 里,要用得用
ref去”查”。 - 类比:受控 = Figma 里绑定了变量的组件(值是活的);非受控 = 静态组件(值是死的)。
3. 不可变更新(Immutable Update)
React 的数组/对象不能直接改,要创建新的再 setState。
添加:展开运算符 ...
function handleAdd() {
const newItem = { id: Date.now(), name: todo, finish: false };
setList([...list, newItem]); // 复制旧数组 + 新项 → 全新数组
setTodo(""); // 顺手清空输入框
}
❌ 不要用
list.push(...)(会直接改原数组)
删除:.filter()
function handleDelete(id) {
const newList = list.filter((item) => item.id !== id); // 只保留 id 不等于目标的
setList(newList);
}
修改某一项:.map() + 取反
function handleToggle(id) {
const newList = list.map((item) =>
item.id === id ? { ...item, finish: !item.finish } : item
);
setList(newList);
}
.filter()→ 筛选,结果数组变短(用于删除).map()→ 加工每一项,结果数组等长(用于修改)!item.finish→ 取反,实现 true↔false 来回切换(toggle){ ...item, finish: !item.finish }→ 复制原对象,只改一个字段
4. 事件处理:箭头函数包裹
需要传参数时,必须用箭头函数包一层。
// ❌ 带括号 = 渲染时立刻执行(还没点就跑了)
<button onClick={handleDelete(item.id)}>删除</button>
// ✅ 箭头函数包裹 = 交给 React 一个待命函数,点击时才执行
<button onClick={() => handleDelete(item.id)}>删除</button>
// 不传参时,直接给函数名即可(不要括号)
<button onClick={handleAdd}>Add</button>
一句话:onClick 要的是”一个待命的函数”,不是”函数的执行结果”。
5. 事件冒泡(Event Bubbling)+ stopPropagation
点击事件会从内往外冒泡(像往水里扔石头的涟漪)。
按钮在 <li> 里 → 点按钮会同时触发按钮和 li 的 onClick。
<li onClick={() => handleToggle(item.id)}>
<p>{item.name}</p>
<button onClick={(e) => {
e.stopPropagation(); // 在内层(button)就拦住,不让冒泡到外层 li
handleDelete(item.id);
}}>删除</button>
</li>
⚠️
stopPropagation()必须加在内层(button),不能加在外层(li)—— 因为要在事件离开 button、到达 li 之前拦截。
6. 派生状态(Derived State):不要乱加 useState
什么时候才需要新的 useState?同时满足两条:
- 会随用户交互而改变
- 没法从现有 state 算出来
// finish 是数据源(source of truth),存在 state 里
// 删除线只是它的"视觉呈现",直接算,不要单独存
<p style={{ textDecoration: item.finish ? "line-through" : "none" }}>
{item.name}
</p>
- 能从现有 state 推导出来的值 = 派生状态,直接算,不要存。
- 类比:Figma 组件样式跟着变量走,不会每个状态都复制一份数据。
7. JSX 行内样式(Inline Style)
style={{ textDecoration: "line-through", backgroundColor: "red" }}
style接收一个 JS 对象(所以是双层大括号{{ }})。- 外层
{}= JSX 语法”插入 JS” - 内层
{}= 这个 JS 对象本身
- 外层
- CSS 属性名改成驼峰命名(camelCase):
text-decoration→textDecorationbackground-color→backgroundColorfont-size→fontSize
- 原因:JS 属性名不能带连字符。
完整代码:Todo List
import { useState } from "react";
function App() {
const [todo, setTodo] = useState("");
const [list, setList] = useState([]);
function handleChange(e) {
setTodo(e.target.value);
}
function handleAdd() {
const newItem = { id: Date.now(), name: todo, finish: false };
setList([...list, newItem]);
setTodo("");
}
function handleDelete(id) {
const newList = list.filter((item) => item.id !== id);
setList(newList);
}
function handleToggle(id) {
const newList = list.map((item) =>
item.id === id ? { ...item, finish: !item.finish } : item
);
setList(newList);
}
return (
<>
<input type="text" value={todo} onChange={handleChange} />
<button onClick={handleAdd}>Add</button>
<ul>
{list.map((item) => (
<li key={item.id} onClick={() => handleToggle(item.id)}>
<p style={{ textDecoration: item.finish ? "line-through" : "none" }}>
{item.name}
</p>
<button onClick={(e) => {
e.stopPropagation();
handleDelete(item.id);
}}>
删除
</button>
</li>
))}
</ul>
</>
);
}
export default App;
下一步(第4周预告)
useEffect处理副作用- 从真实 API 获取数据(加载中 / 报错状态)
- 项目:小相册(发挥摄影排版品味)
- ⚠️ 别超前学
useContext/ Redux / 自定义 hooks,按路线图走。