主题
03 · 用 Learn React 补手感:组件 / props / state
目标:在 Next 项目里用 Client Component 手写一页完整交互,把「看过课」变成肌肉记忆。
官方主文:Describing the UI · Adding Interactivity · Managing State
1. 为什么这篇要「在 Next 里练 React」
App Router 默认文件是 Server Component(下篇再深讲)。带 useState / 点击事件的组件必须在文件顶加:
tsx
'use client'1
见 Server and Client Components。本篇只要求你:交互页 = Client Component;先别管优化。
2. 官方阅读地图(边读边敲)
按顺序打开,每节自己敲一小段,不要只收藏:
| 顺序 | 页面 | 你要带走的一句话 |
|---|---|---|
| 1 | Describing the UI | UI = 组件树;JSX 是表达式 |
| 2 | Adding Interactivity | 事件 = 传函数;state 触发重渲染 |
| 3 | Managing State | state 放哪、如何下传与上提 |
Vue 对照:
| Vue | React(本篇) |
|---|---|
<script setup> 组件 | 函数组件 function X(props) |
defineProps | 函数参数 / 解构 props |
ref / reactive | useState |
computed | 渲染时直接算:const filtered = items.filter(...) |
v-model | value + onChange 受控写法 |
3. 手敲练习:计数器 + 待办输入
在 02 的项目中新建 app/practice/page.tsx:
tsx
'use client'
import { useState } from 'react'
export default function PracticePage() {
const [count, setCount] = useState(0)
const [text, setText] = useState('')
const [items, setItems] = useState<string[]>([])
function addItem() {
const t = text.trim()
if (!t) return
setItems((prev) => [...prev, t])
setText('')
}
return (
<main style={{ padding: 24, maxWidth: 480 }}>
<h1>Practice</h1>
<section>
<p>count: {count}</p>
<button type="button" onClick={() => setCount((c) => c + 1)}>
+1
</button>
</section>
<section style={{ marginTop: 24 }}>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="新条目"
/>
<button type="button" onClick={addItem}>
添加
</button>
<ul>
{items.map((item, i) => (
<li key={`${item}-${i}`}>{item}</li>
))}
</ul>
</section>
</main>
)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
访问 /practice。
刻意观察:
- 点
+1→ 只有依赖count的 UI 更新(整页函数会再跑一遍,这是正常心智)。 items用函数式更新setItems((prev) => ...),避免闭包拿到旧 state。- 列表需要稳定一点的
key(以后有真实 id 时用 id,不要长期用 index 糊弄业务列表)。
4. 拆组件(练 props)
把列表抽成子组件,理解「数据向下、事件向上」:
tsx
function ItemList({
items,
onClear,
}: {
items: string[]
onClear: () => void
}) {
return (
<div>
<button type="button" onClick={onClear}>
清空
</button>
<ul>
{items.map((item, i) => (
<li key={`${item}-${i}`}>{item}</li>
))}
</ul>
</div>
)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
父组件持有 items,子组件只展示 + 回调——对齐 Vue 的 props + emit 思路。
5. 必做验收
- [ ] 通读 Learn 中上表三节(可扫读,但示例至少敲过一遍)
- [ ]
/practice能加减 count、添加条目 - [ ] 文件顶有
'use client',且能解释为什么 - [ ] 至少拆过一个子组件,props 类型用 TS 写明
- [ ] 能说出:
computed在 React 里常常「直接算」,不必useMemo起步
6. AI 提示词约束(本篇)
text
技术约束:
- 仅 React + Next App Router Client Component
- 不要 useEffect、不要外部状态库
- 对照 react.dev/learn/managing-state
任务:审查我的 /practice 页:props 是否单向数据流、
有无把派生值错误放进 state。只给最小改动。1
2
3
4
5
6
7
2
3
4
5
6
7
