一、使用 JSX 书写标签语言
JSX 是一种 JavaScript 的语法扩展,React 使用它来描述用户界面。
什么是 JSX?
- JSX 是 JavaScript 的一种语法扩展。
- 看起来像 HTML,但它实际上是在 JavaScript 代码中写 XML/HTML。
- 浏览器并不能直接运行 JSX,需要通过打包工具(如 Babel)将其转译为 JavaScript。
示例:
const element = <h1>Hello, world!</h1>;
1、JSX 的基本规则
使用大写字母定义组件
function MyButton() {
return <button>I'm a button</button>;
}
- 小写字母开头的标签,如
<div>
被解析为 HTML 标签。 - 大写字母开头的标签,如
<MyButton>
被解析为 React 组件。
必须使用闭合标签
- 所有标签必须闭合(类似 XML 语法)
// 正确
<input />
<br />
<MyComponent />
// 错误
<input>
使用 {}
插入 JavaScript 表达式
const user = "小明";
const element = <h1>Hello, {user}!</h1>;
- 只能插入表达式(不是语句)
合法表达式:
{1 + 2}
{user.name}
{formatDate(date)}
非法语句:
{if (isTrue) { ... }}
{for (...) { ... }}
使用 className
代替 class
// HTML 写法
<div class="container"></div>
// JSX 写法
<div className="container"></div>
因为 class
是 JavaScript 的关键字,所以要使用 className
。
使用 camelCase
的属性名
// HTML 写法
<input tabindex="0" onclick="handleClick()" />
// JSX 写法
<input tabIndex={0} onClick={handleClick} />
2、条件渲染和列表渲染
条件渲染
使用三元表达式、逻辑与 &&
:
{isLoggedIn ? <LogoutButton /> : <LoginButton />}
{messages.length > 0 && <Notification messages={messages} />}
列表渲染
使用 map()
进行循环输出,并为每个子元素设置唯一的 key
const items = ['A', 'B', 'C'];
<ul>
{items.map(item => <li key={item}>{item}</li>)}
</ul>
3、JSX 转换成 JavaScript 的原理
JSX 会被转译为 React.createElement
调用:
const element = <h1 className="title">Hello</h1>;
// 会被转换为:
const element = React.createElement('h1', { className: 'title' }, 'Hello');
4、组合 JSX
JSX 支持嵌套结构:
function App() {
return (
<div>
<Header />
<Content />
<Footer />
</div>
);
}
可使用片段(Fragment)避免多余的 DOM 元素:
<>
<td>内容1</td>
<td>内容2</td>
</>
5、JSX Tips
注释写法:
{/* 这是注释 */}
多行 JSX 需要用括号包裹:
return (
<div>
<h1>Hello</h1>
</div>
);
二、组件(Component)
React 应用是由组件构成的,组件是可以复用的 UI 单元。
什么是组件?
- 组件(Component) 是 React 的核心概念。
- 本质上是一个返回 JSX 的函数。
- 组件名称必须以大写字母开头。
示例:
function MyButton() {
return <button>I'm a button</button>;
}
在 JSX 中使用:
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
</div>
);
}
组件命名规则
- 必须以大写字母开头,否则会被当成 HTML 标签。
- 使用 PascalCase 命名约定(每个单词首字母大写)。
1、组件是函数,不是标签
function MyButton() {
return <button>Click me</button>;
}
这个 MyButton
是一个函数,而 <MyButton />
是它的使用方式(调用)。
2、组件可以复用
你可以多次使用同一个组件,它们是互相独立的:
function MyApp() {
return (
<div>
<MyButton />
<MyButton />
</div>
);
}
每个 <MyButton />
都会渲染一个独立的按钮。
3、组件的结构建议
建议为每个组件建一个文件(例如 MyButton.jsx
),用于项目组织:
src/
├─ components/
│ └─ MyButton.jsx
└─ App.jsx
🧪 示例代码汇总
// MyButton.jsx
export default function MyButton() {
return <button>I'm a button</button>;
}
// App.jsx
import MyButton from './MyButton';
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
<MyButton />
</div>
);
}
三、State
React 的状态(state)允许组件“记住”信息。状态是让组件有“记忆”的机制,通常用于跟踪用户交互或界面变化。
1、什么是状态(State)?
- 状态是组件的“记忆”。
- 在每次重新渲染时,组件的状态保持不变。
- 状态的变化会 触发组件的重新渲染。
2、如何添加状态?
通过 useState
Hook:
import { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
}
useState
解释:
const [state, setState] = useState(initialValue);
名称 | 含义 |
---|---|
state | 当前状态值 |
setState | 用于更新状态的函数 |
initialValue | 初始状态值 |
3、状态的基本用法示例
import { useState } from 'react';
export default function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
4、每次点击发生了什么?
- 点击按钮时,
handleClick
被调用。 setCount(count + 1)
更新状态。- React 重新渲染组件。
- 新的
count
显示在界面上。
状态更新不会改变当前值,而是触发一次新的渲染,组件中的
count
会更新为新值。
5、状态在组件之间是隔离的
每个组件实例有自己独立的状态。
<MyButton />
<MyButton />
上面两个按钮互不影响,即使它们使用相同的 useState
。
6、不要直接修改 state 变量
// 错误写法(不会触发重新渲染)
count = count + 1;
// 正确写法
setCount(count + 1);
只有通过 setCount
这样的更新函数,React 才会触发重新渲染。
7、多个状态变量
可以在一个组件中使用多个 useState
:
const [count, setCount] = useState(0);
const [name, setName] = useState('React');
8、示例完整代码
import { useState } from 'react';
function MyButton() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(count + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
export default function MyApp() {
return (
<div>
<h1>Welcome to my app</h1>
<MyButton />
<MyButton />
</div>
);
}
四、响应事件
React 使用类似 HTML 的方式来处理用户交互事件,比如点击、输入、悬停等。但语法略有不同,并支持更强的逻辑功能。
1、事件绑定基础
React 使用 onClick
、onChange
等属性来绑定事件处理函数。
示例:
function MyButton() {
function handleClick() {
alert('你点击了我!');
}
return (
<button onClick={handleClick}>
点击我
</button>
);
}
注意:
- 使用驼峰命名(如
onClick
,而不是onclick
) - 事件处理函数是一个普通的 JavaScript 函数
- JSX 中不使用字符串绑定函数(不同于 HTML 的
onclick="handleClick()"
)
2、为什么使用函数名而不是函数调用?
// 正确
onClick={handleClick}
// 错误(会立即执行)
onClick={handleClick()}
你应传递函数的引用,而不是函数的执行结果。
3、使用箭头函数传参
有时你希望传递参数给事件处理函数,可以使用箭头函数:
function handleClick(name) {
alert(`Hello, ${name}!`);
}
<button onClick={() => handleClick('小明')}>
Say Hello
</button>
4、在组件中组合事件处理逻辑
React 鼓励你将组件拆成小块,事件处理函数可以在组件内部定义或向下传递:
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
function App() {
function handleClick() {
alert('Clicked!');
}
return (
<div>
<Button onClick={handleClick}>按钮1</Button>
<Button onClick={handleClick}>按钮2</Button>
</div>
);
}
5、常见事件类型
React 事件名 | 对应 HTML | 说明 |
---|---|---|
onClick | onclick | 点击事件 |
onChange | onchange | 输入/选择改变 |
onSubmit | onsubmit | 表单提交 |
onMouseEnter | onmouseenter | 鼠标进入 |
onKeyDown | onkeydown | 按键按下 |
6、阻止默认行为
可以在事件中调用 event.preventDefault()
:
function handleSubmit(e) {
e.preventDefault();
alert('提交已阻止');
}
<form onSubmit={handleSubmit}>
<button type="submit">提交</button>
</form>
7、React 与原生 DOM 事件的区别
项目 | React | 原生 HTML |
---|---|---|
命名方式 | 驼峰命名,如 onClick | 小写,如 onclick |
传递方式 | 传函数引用 | 传字符串或函数调用 |
自动阻止冒泡 | 否,你仍需手动阻止冒泡 | 同样需手动处理 |
8、示例完整代码
function Button({ message, children }) {
function handleClick() {
alert(message);
}
return (
<button onClick={handleClick}>
{children}
</button>
);
}
export default function App() {
return (
<div>
<Button message="你好!">点我</Button>
<Button message="再见!">再点我</Button>
</div>
);
}
五、React 哲学
“React 哲学” 教你如何从 UI 设计图开始,一步步将页面拆解为组件,再构建出数据驱动的交互式界面。
示例场景简介
我们要实现一个可搜索的商品表格(Searchable Product Table),它包含:
- 一个搜索框
- 一个是否只显示有库存商品的勾选框
- 一个根据品类分组的商品表格
构建步骤总览
React 官方建议采用 五步法:
- 将 UI 拆解为组件层级结构
- 构建组件的静态版本(无交互)
- 确定最小但完整的 UI 状态表示
- 确定哪些组件拥有状态(状态提升)
- 添加反向数据流(处理用户输入)
1、第一步:将 UI 拆解为组件层级
观察 UI,并根据界面结构拆出以下组件:
组件层级结构
FilterableProductTable (父组件)
├─ SearchBar
└─ ProductTable
├─ ProductCategoryRow
└─ ProductRow
每个组件的职责
组件名 | 作用描述 |
---|---|
FilterableProductTable | 管理所有状态,整合其他组件 |
SearchBar | 输入搜索文本与是否过滤库存 |
ProductTable | 接收数据与过滤条件,渲染表格 |
ProductCategoryRow | 显示每个品类的标题 |
ProductRow | 显示单个商品 |
2、第二步:构建静态版本(无交互)
- 使用父组件
FilterableProductTable
将假数据通过 props 传给子组件。 - 每个组件只关注如何显示数据,不包含状态或交互。
- 假数据示例:
const PRODUCTS = [
{category: "Fruits", price: "$1", stocked: true, name: "Apple"},
{category: "Fruits", price: "$1", stocked: true, name: "Dragonfruit"},
{category: "Fruits", price: "$2", stocked: false, name: "Passionfruit"},
{category: "Vegetables", price: "$2", stocked: true, name: "Spinach"},
{category: "Vegetables", price: "$4", stocked: false, name: "Pumpkin"},
{category: "Vegetables", price: "$1", stocked: true, name: "Peas"}
];
3、第三步:确定最小但完整的 UI 状态(State)
根据界面交互功能,确定需要驱动 UI 的状态:
- 搜索文本(
filterText
) - 是否只显示有库存商品(
inStockOnly
)
不是状态的内容(可由 props 或其他状态推导得出):
- 商品数据(是静态的)
- 分类标题(可从数据中提取)
- 筛选后的商品列表(由
filterText
+inStockOnly
计算)
4、第四步:决定状态的归属
状态应该放在最“靠上的共同祖先组件”中。
状态名 | 所属组件 | 原因 |
---|---|---|
filterText | FilterableProductTable | SearchBar 和 ProductTable 都使用它 |
inStockOnly | FilterableProductTable | 同上 |
5、第五步:添加反向数据流(提升状态 + 子传父)
让 SearchBar
接收 filterText
和 inStockOnly
作为 props,并通过 onChange
回调将用户输入传递给父组件修改状态。
function SearchBar({ filterText, inStockOnly, onFilterTextChange, onInStockChange }) {
return (
<form>
<input
type="text"
value={filterText}
onChange={(e) => onFilterTextChange(e.target.value)}
placeholder="Search..."
/>
<label>
<input
type="checkbox"
checked={inStockOnly}
onChange={(e) => onInStockChange(e.target.checked)}
/>
Only show products in stock
</label>
</form>
);
}
6、组件结构(最终)
<FilterableProductTable products={PRODUCTS} />
// 内部包含
└── <SearchBar
filterText={...}
inStockOnly={...}
onFilterTextChange={...}
onInStockChange={...}
/>
└── <ProductTable
products={...}
filterText={...}
inStockOnly={...}
/>