React 快速入门 ⭐
了解 React 并搭建开发环境
学习目标
- 理解 React 是什么及核心特点
- 搭建 React 开发环境
- 掌握 JSX 语法、条件渲染、列表渲染和事件处理
- 创建第一个 React 组件
生活化比喻
React 就像"乐高积木":
比喻对应:
┌──────────────────────────────────────────────────────┐
│ 乐高积木 │
├──────────────────────────────────────────────────────┤
│ │
│ 组件 = 乐高积木块 │
│ ───────────── │
│ 每个积木块独立,可以拼装 │
│ 一块是窗户,一块是门,拼起来就是房子 │
│ │
│ JSX = 拼装说明书 │
│ ───────────── │
│ 用类似 HTML 的语法描述怎么拼装 │
│ 最终会被翻译成真正的拼装步骤 │
│ │
│ Props = 积木块的颜色/形状参数 │
│ ───────────── │
│ 同样的积木块,不同参数可以拼出不同效果 │
│ Props 从父组件传进来,不能自己改 │
│ │
│ State = 积木块当前状态 │
│ ───────────── │
│ 积木块自己内部的配置,可以改变 │
│ 改变了就会重新拼装 │
│ │
│ 声明式 = 描述最终效果 │
│ ───────────── │
│ 不说"先拿这块再放那块" │
│ 只说"我要一个红色窗户" │
│ │
└──────────────────────────────────────────────────────┘L1 理解层:会用
React 基础
最简示例(1-3行):
jsx
function App() {
return <h1>Hello React!</h1>
}详细示例 — 单文件组件:
jsx
// src/App.jsx
import { useState } from 'react'
function App() {
const [message, setMessage] = useState('Hello React!')
return (
<div className="app">
<h1>{message}</h1>
<button onClick={() => setMessage('Clicked!')}>
点击我
</button>
</div>
)
}
export default AppJSX 语法
语法结构图:
JSX 规则:
{{ 表达式 }} → 插入 JavaScript 表达式
className="..." → class 属性(不是 class)
onClick={fn} → 事件绑定(驼峰命名)
<tag /> → 自闭合标签
<>...</> → Fragment(不需要额外 div)
{condition && <X />} → 条件渲染
{items.map(item => ...)} → 列表渲染最简示例(1-3行):
jsx
const name = '张三'
const el = <h1>Hello, {name}</h1>详细示例:
jsx
function App() {
const title = '标题'
const imageUrl = 'photo.jpg'
const isActive = true
const items = [{ id: 1, name: '苹果' }, { id: 2, name: '香蕉' }]
return (
<div>
{/* 变量插值 */}
<h1>{title}</h1>
<p>明年:{25 + 1} 岁</p>
<p>状态:{isActive ? '开' : '关'}</p>
{/* 属性绑定 */}
<img src={imageUrl} alt="照片" />
<div className={isActive ? 'active' : ''}>内容</div>
<button disabled={!isActive}>按钮</button>
{/* 内联样式 */}
<div style={{ color: 'red', fontSize: '16px' }}>红色</div>
{/* Fragment — 不需要额外 div */}
<>
<h2>标题</h2>
<p>内容</p>
</>
{/* 条件渲染 */}
{items.length > 0 ? <p>有 {items.length} 项</p> : <p>空</p>}
{isActive && <span>活跃</span>}
{/* 列表渲染 */}
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
)
}事件处理
最简示例(1-3行):
jsx
<button onClick={() => setCount(c => c + 1)}>+1</button>
<form onSubmit={(e) => { e.preventDefault(); handleSubmit() }}>详细示例:
jsx
function App() {
const [count, setCount] = useState(0)
const handleClick = (msg) => {
console.log(msg)
setCount(prev => prev + 1)
}
return (
<div>
{/* 直接引用函数 */}
<button onClick={handleClick}>点击</button>
{/* 箭头函数传参 */}
<button onClick={() => handleClick('参数')}>传参</button>
{/* 阻止默认行为 */}
<form onSubmit={(e) => {
e.preventDefault()
console.log('提交')
}}>
<button type="submit">提交</button>
</form>
</div>
)
}L2 实践层:用好
反模式:不要这样做
jsx
// ❌ 错误:组件名小写
function myComponent() { return <div>错</div> }
// ✅ 正确:首字母大写
function MyComponent() { return <div>对</div> }jsx
// ❌ 错误:返回多个根元素
function Bad() {
return (
<h1>标题</h1>
<p>内容</p>
)
}
// ✅ 正确:用 Fragment 包裹
function Good() {
return (
<>
<h1>标题</h1>
<p>内容</p>
</>
)
}jsx
// ❌ 错误:用 index 做 key
{items.map((item, i) => <li key={i}>{item.name}</li>)}
// ✅ 正确:用唯一 ID
{items.map(item => <li key={item.id}>{item.name}</li>)}适用场景
| 场景 | 推荐方案 | 示例 |
|---|---|---|
| 条件简单 | 三元表达式 | {show ? <A /> : <B />} |
| 只显示一种 | && 短路 | {show && <A />} |
| 列表渲染 | map + key | items.map(item => <li key={item.id}>) |
| 阻止默认行为 | e.preventDefault() | 表单提交 |
L3 专家层:深入
JSX 编译原理
JSX 编译流程:
JSX 代码:
<div className="app">
<h1>Hello</h1>
</div>
编译为:
React.createElement('div', { className: 'app' },
React.createElement('h1', null, 'Hello')
)
运行时返回虚拟 DOM 对象:
{ type: 'div', props: { className: 'app', children: [...] } }知识关联
React 快速入门关联:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 环境搭建 │────→│ JSX 语法 │────→│ 事件处理 │
│ Vite/ │ │ 插值/ │ │ onClick/ │
│ 入口文件 │ │ 属性/ │ │ onSubmit │
└─────────────┘ │ 条件/ │ └─────────────┘
│ 列表 │
└─────────────┘术语定义
| 术语 | 定义 | 示例 |
|---|---|---|
| JSX | JavaScript 语法扩展,允许在 JS 中写 HTML -like 代码 | <h1>Hello</h1> |
| 组件 | 返回 JSX 的函数,首字母必须大写 | function App() { } |
| Props | 从父组件传递给子组件的数据(只读) | <Child name="张三" /> |
| State | 组件内部的可变数据 | const [count, setCount] = useState(0) |
| Fragment | 不产生额外 DOM 元素的包裹器 | <>...</> |
| 虚拟 DOM | 用 JS 对象描述的真实 DOM 结构 | React.createElement 返回的对象 |
实践练习
练习:Todo 列表
jsx
import { useState } from 'react'
function App() {
const [todos, setTodos] = useState([])
const [input, setInput] = useState('')
const addTodo = (e) => {
e.preventDefault()
if (!input.trim()) return
setTodos([...todos, { id: Date.now(), text: input }])
setInput('')
}
const removeTodo = (id) => {
setTodos(todos.filter(t => t.id !== id))
}
return (
<div>
<h1>Todo 列表</h1>
<form onSubmit={addTodo}>
<input value={input} onChange={e => setInput(e.target.value)} placeholder="输入任务..." />
<button type="submit">添加</button>
</form>
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo.text}
<button onClick={() => removeTodo(todo.id)}>删除</button>
</li>
))}
</ul>
{todos.length > 0 && <p>共 {todos.length} 项</p>}
</div>
)
}
export default App常见问题
Q1:组件名为什么必须大写?
React 通过首字母大小写区分 HTML 元素和自定义组件:
jsx
<div /> → HTML div 元素
<Div /> → 自定义 Div 组件Q2:JSX 和 HTML 有什么区别?
| HTML | JSX | 说明 |
|---|---|---|
class | className | 避免和 JS class 冲突 |
for | htmlFor | 避免和 JS for 循环冲突 |
style="color: red" | style={styleObj} | 接收对象字面量 |
onclick | onClick | 驼峰命名 |
<br> | <br /> | 必须闭合 |
Q3:为什么 v-for 需要 key?
key 帮助 React 识别列表中哪些元素变了,从而最小化 DOM 操作:
jsx
// ❌ 用 index 做 key,列表重排时会出错
{items.map((item, i) => <li key={i}>{item.name}</li>)}
// ✅ 用唯一 ID
{items.map(item => <li key={item.id}>{item.name}</li>)}学习资源
- React 官方文档 ⭐ 必读
- Vite 官方文档
- React Playground - 在线练习