DrillLab
第 01 / 21 节LESSON 01 / 21约 11 分钟~11 min

组件就是一个返回界面的函数A component is a function that returns what you see on screen

从这个项目最短的两个文件开始:App.tsx 只有 5 行。Start with the two shortest files in this project: App.tsx is only 5 lines.

2 个练习2 exercisesReact · 第 1 部分React · Part 1
这一页有什么On this page7
学完这节你会After this lesson you can
  • 说清「组件」在 React 里到底是什么Explain what a component actually is in React
  • 看懂 JSX 里的标签、花括号、classNameRead the tags, the curly braces, and className in JSX
  • 知道组件名必须大写开头,以及为什么Know that a component name must start with a capital letter, and why
  • 能画出这个项目的组件树Draw the component tree of this project
这在考试里考什么What the exam does with this

Q1 的四个组件是给好的骨架,你要在里面填逻辑。填之前必须先看懂「谁渲染谁、数据从哪来」,否则会把代码写在错误的组件里。In Q1 the four components are already written for you, and you add the logic inside them. Before you add anything, you have to see which component renders which, and where the data comes from. Otherwise you put the code in the wrong component.

这节课要看的真实文件Real files this lesson looks at2 项 · 2 个可以展开看原文2 items · 2 can be opened
react-notes-app/src/App.tsx整个应用的根组件,只有 5 行The root component of the whole app, five lines long
TSXApp.tsx源项目From source
1import NoteManager from "./components/NoteManager";
2
3function App() {
4 return <NoteManager />;
5}
6
7export default App;
Source: react-notes-app/src/App.tsx
react-notes-app/src/components/NoteItem/index.tsx最简单的展示型组件The simplest presentational component
TSXindex.tsx源项目From source
1import React from "react";
2import type { Note } from "../../types/Note";
3
4export interface NoteItemProps {
5 note: Note;
6 onDelete: (id: number) => void;
7 onEdit: (note: Note) => void;
8}
9
10const NoteItem: React.FC<NoteItemProps> = ({ note, onDelete, onEdit }) => {
11 return (
12 <tr>
13 <td>{note.title}</td>
14 <td>{note.content}</td>
15 <td>
16 <button onClick={() => onEdit(note)} className="outlined">
17 Edit
18 </button>
19 </td>
20 <td>
21 <button onClick={() => onDelete(note.id)} className="danger">
22 Delete
23 </button>
24 </td>
25 </tr>
26 );
27};
28
29export default NoteItem;
Source: react-notes-app/src/components/NoteItem/index.tsx
§01

一个组件 = 一个返回 JSX 的函数A component is a function that returns JSX

没有别的了。它不是类、不是模板、不是配置。That is all it is. Not a class, not a template, not a config file.

看这个项目里最短的文件。App 是一个普通的 JavaScript 函数, 它没有参数,return 一个看起来像 HTML 的东西:

那个 <NoteManager /> 不是 HTML 标签 —— HTML 里没有这个元素。它是在说「把 NoteManager 这个组件渲染在这里」。 这种「在 JavaScript 里直接写标签」的语法叫JSX,它会被构建工具(这个项目里是 Vite) 翻译成普通的函数调用。

为什么组件名必须大写开头?因为 JSX 靠首字母区分两件事:小写开头(<div><input>)当成真实 HTML 标签; 大写开头(<NoteManager />)当成你的组件。 写成 <noteManager />,React 会去找一个叫 notemanager 的 HTML 标签,然后什么都不显示 —— 而且不报错

Look at the shortest file in this project. App is a plain JavaScript function. It takes no arguments and returns something that looks like HTML:

That <NoteManager /> is not an HTML tag — there is no such element in HTML. It says “render the NoteManager component right here”. Writing tags directly inside JavaScript like this is called JSX, and the build tool (Vite, in this project) turns it into ordinary function calls.

Why must a component name start with a capital?Because JSX tells two things apart by that first letter: lowercase (<div>, <input>) means a real HTML tag; uppercase (<NoteManager />) means your component. Write <noteManager /> and React goes hunting for an HTML tag called notemanager, then renders nothing — and says nothing about it.

TSXsrc/App.tsx(全文)src/App.tsx (full file)源项目From source
1import NoteManager from "./components/NoteManager";
2
3function App() {
4 return <NoteManager />;
5}
6
7export default App;
Source: react-notes-app/src/App.tsx
这个文件唯一的作用是「把根组件指向 NoteManager」。Q1 的所有逻辑都不在这里 —— 别在这个文件里改东西。This file does one thing: point the root component at NoteManager. None of the Q1 logic lives here, so do not change anything in it.
§02

JSX 的几条硬规则The rules JSX always enforces

NoteItem,这是这个项目里最纯粹的展示型组件。 它把这些规则都用到了:

  • 只能返回一个根元素。这里返回的是一个 <tr>,里面包着四个<td>。想并列返回两个同级元素, 要用 <>...</> 包起来。
  • 花括号 {} 是「切回 JavaScript」的开关。{note.title} 的意思是「这里放note.title 这个变量的值」。 写成 note.title(不带花括号)会原样显示这十个字符。
  • 属性名用 camelCase,class 要写 className因为 class 是 JavaScript 的保留字。同理onclick 要写 onClick
  • 自闭合标签必须带斜杠。HTML 里 <input> 可以不闭合,JSX 里必须写<input />

Look at NoteItem, the purest display component in this project. It uses every one of these rules:

  • One root element only. This one returns a single <tr> wrapping four<td>. To return two siblings side by side, wrap them in <>...</>.
  • Braces {} switch you back into JavaScript.{note.title} means “put the value of note.title here”. Written as note.title without braces, that text shows up on the page literally.
  • Attributes are camelCase, and class becomes className. Because class is a reserved word in JavaScript. Same reason onclick is written onClick.
  • Self-closing tags need the slash. HTML lets you leave <input> open; JSX makes you write <input />.
TSXsrc/components/NoteItem/index.tsx(全文)src/components/NoteItem/index.tsx (full file)源项目From source
1import React from "react";
2import type { Note } from "../../types/Note";
3
4export interface NoteItemProps {
5 note: Note;
6 onDelete: (id: number) => void;
7 onEdit: (note: Note) => void;
8}
9
10const NoteItem: React.FC<NoteItemProps> = ({ note, onDelete, onEdit }) => {
11 return (
12 <tr>
13 <td>{note.title}</td>
14 <td>{note.content}</td>
15 <td>
16 <button onClick={() => onEdit(note)} className="outlined">
17 Edit
18 </button>
19 </td>
20 <td>
21 <button onClick={() => onDelete(note.id)} className="danger">
22 Delete
23 </button>
24 </td>
25 </tr>
26 );
27};
28
29export default NoteItem;
Source: react-notes-app/src/components/NoteItem/index.tsx
§03

这个项目的组件树The component tree of this project

四个组件,一条主干。记住这张图,Q1 的三道题就都有落点了。Four components, one main line. Remember this picture and each of the three Q1 tasks has a place to go.

NoteManager 在中间,它同时是 NoteFormNoteTable 的父组件。这个位置决定了它是唯一能同时影响表单和表格的地方 —— 所以三道题的代码全都写在它里面。

NoteTableNoteItem 都是纯展示的: 它们不持有任何数据,只负责「把拿到的东西画出来」和 「把用户的点击原样上报给上面」。

NoteManager sits in the middle: it is the parent of both NoteForm and NoteTable. That position makes it the only place that can touch the form and the table at the same time — which is why all three tasks are written inside it.

NoteTable and NoteItem are pure display: they hold no data at all. They draw what they are handed, and pass the user’s clicks straight back up.

Text组件树Component tree已跑通Verified
1App 只渲染 NoteManager
2└── NoteManager ★ 拥有 notes[] 和 noteToEdit 两个 state
3 ├── NoteForm 拥有 title / content 两个局部 state
4 │ 向上:onSubmit(note)
5 │ 向下:noteToEdit(决定回填与按钮文字)
6 └── NoteTable 纯展示:把 notes 摊成表格
7 └── NoteItem × N 纯展示 + 上报 onEdit / onDelete
1App renders only NoteManager
2└── NoteManager ★ owns two states: notes[] and noteToEdit
3 ├── NoteForm owns two local states: title / content
4 │ up: onSubmit(note)
5 │ down: noteToEdit (prefill + button text)
6 └── NoteTable display only: lays notes out as a table
7 └── NoteItem × N display only + reports onEdit / onDelete
§04

React.FC 是什么What React.FC is

这个项目里所有组件都写成const X: React.FC<XProps> = ({...}) => {...}

React.FC 是 React 提供的一个类型, 全称 Function ComponentReact.FC<NoteItemProps> 的意思是: 「这是一个函数组件,它的 props 类型是 NoteItemProps」。

它不是必须的 —— 直接写function NoteItem(props: NoteItemProps) {...}完全等价。但这个项目统一用了 React.FC你写的代码应该跟着项目的风格。 考试不会因为风格扣分,但保持一致会让人觉得你读过代码。

Every component in this project is written as const X: React.FC<XProps> = ({...}) => {...}.

React.FC is a type that React ships, short for Function Component. React.FC<NoteItemProps> says: “this is a function component, and its props are typed NoteItemProps”.

It is not required —function NoteItem(props: NoteItemProps) {...}is exactly equivalent. But this project uses React.FC everywhere, and your code should follow the project. Nobody loses points for style, but matching it shows you read the code.

练习Practice

动手做Get your hands on it

填空只是过渡。真正掌握的标准,是在没有答案的时候从头写出来 —— 所以做完 L2 之后一定要往 L3、L4 走。Filling blanks is a stepping stone. The real bar is writing it from nothing, so once L2 is comfortable, push on to L3 and L4.

L1认出来Spot it哪一行会把变量的值显示出来Which line prints the value of a variable

下面哪一行会在页面上显示这条笔记的标题内容?

Which line below shows the title of this note on the page?

先选一个选项Pick an option first
L1认出来Spot it三道题的代码该写在哪个文件Which file the code for the three tasks belongs in

Q1 的三个任务(Add / Delete / Edit)都要改动笔记列表。 这些逻辑主要写在哪个文件里?

All three tasks in Q1 (Add / Delete / Edit) change the note list. Which file holds most of that logic?

先选一个选项Pick an option first
错例Wrong

初学者常见的几种写法错误Mistakes beginners actually make

下面每一段都是「能编译、但结果不对」或者「一跑就炸」的真实写法。先自己看出问题在哪,再看解释。Every snippet below either compiles and gives the wrong answer, or blows up on the first run. Spot the problem yourself before reading the explanation.

TSX示意Illustrative
1// ✗ 组件名小写开头 —— 页面上什么都不出现,而且不报错
2function noteItem() { return <tr>...</tr>; }
3
4export default function App() {
5 return <noteItem />; // React 当成 HTML 标签处理
6}
1// ✗ Name starts with a lowercase letter — nothing appears, and no error
2function noteItem() { return <tr>...</tr>; }
3
4export default function App() {
5 return <noteItem />; // React reads this as an HTML tag
6}
JSX 用首字母大小写区分「HTML 标签」和「你的组件」。 小写开头会被当成一个不存在的 HTML 元素, 浏览器默默忽略它。这类 bug 没有报错,只有空白 —— 看到「组件不显示但控制台干净」时,先检查首字母。JSX uses the first letter to tell an HTML tag apart from a component of your own. A lowercase name is read as an HTML element that does not exist, and the browser ignores it without saying anything. This kind of bug produces no error, only an empty area — when a component does not appear and the console is clean, check the first letter first.
TSX示意Illustrative
1// ✗ 返回了两个同级元素
2return (
3 <td>{note.title}</td>
4 <td>{note.content}</td>
5);
1// ✗ Two sibling elements returned
2return (
3 <td>{note.title}</td>
4 <td>{note.content}</td>
5);
JSX 只能返回一个根元素。这段会直接编译报错JSX expressions must have one parent element。 解法是用真正的父元素(这里是 <tr>) 或者空标签 <>...</> 包起来。JSX can return only one root element. This code fails to compile with JSX expressions must have one parent element. Fix it by wrapping the elements in a real parent (here that is<tr>) or in an empty tag<>...</>.
迁移Transfer

换一道题也能用Works on other problems too

考试不会原题重考。真正能带走的是「看到这种信号 → 伸手去拿这个解法」。The exam will not reuse the same question. What you take away is the reflex: see this signal, reach for that solution.

组件不显示但控制台干净Component does not appear, but the console is clean
检查组件名是否大写开头Check that the component name starts with a capital letter
变量名原样显示在页面上The variable name itself is printed on the page
漏了花括号The curly braces are missing
不知道逻辑该写在哪个组件Not sure which component the logic belongs in
找持有相关 state 的那个组件Find the component that holds the related state
JSX expressions must have one parentJSX expressions must have one parent
用 <>…</> 包住多个同级元素Wrap the sibling elements in <>…</>
这节的要点What to take away
  1. 组件就是返回 JSX 的普通函数,名字必须大写开头。A component is a plain function that returns JSX, and its name must start with a capital letter.
  2. 花括号是切回 JavaScript 的开关;class 要写 className。Curly braces switch back to JavaScript; write className, not class.
  3. JSX 只能返回一个根元素,需要并列时用 <>…</>。JSX can return only one root element. Use <>…</> when you need several elements side by side.
  4. 这个项目的组件树:App → NoteManager →(NoteForm + NoteTable → NoteItem)。The component tree here: App → NoteManager → (NoteForm + NoteTable → NoteItem).
  5. NoteManager 是唯一能同时影响表单和表格的地方,三道题都落在它里面。NoteManager is the only place that can affect the form and the table at the same time, so all three tasks land inside it.

接下来What next

  1. 把这一节的练习做掉Do this lesson’s exercises2 个,就在这一页上面 —— 别攒着最后一起做2 of them, further up this page — do not save them for later
    回到练习 ↑Back up to them ↑
  2. 接着看下一节Continue to the next lessonprops:数据往下流,事件往上报props: data flows down, events go back up
    下一节Next lesson
读完并且做过上面的练习了吗?Read it and worked through the exercises above?