一、前言:React 解决了什么根本问题?
回顾一下传统前端(jQuery 时代)的代码:
javascript
// 一个计数器------你需要手动描述每一步 DOM 操作
$('#btn').on('click', function() {
let count = parseInt($('#result').text()) || 0
count++ // 数据变了
$('#result').text(count) // 手动更新 DOM
if (count > 10) {
$('#result').css('color', 'red') // 手动改样式
}
})
这段代码有一个根本问题:数据存在 DOM 里。整个应用的真实状态分散在数十个 DOM 节点中,没有任何一个地方能完整地回答"当前页面处于什么状态?"。
React 提出了一种完全不同的思考方式:
tsx
function Counter() {
const [count, setCount] = useState(0) // 数据是"一等公民"
return (
<div>
<button onClick={() => setCount(count + 1)}>点击</button>
<div className={count > 10 ? 'text-red-500' : ''}>
{count} // 数据变了,界面自动变
</div>
</div>
)
}
React 的核心承诺:你只需要描述每个状态下界面长什么样,当数据变化时,React 负责让界面跟上。你不需要知道 DOM 是怎么更新的。
二、JSX:在 JavaScript 里写 HTML
JSX 是什么?
tsx
// 你写的 JSX
const element = <h1 className="title">Hello, {name}!</h1>
// 编译后等价于 ------
const element = React.createElement(
'h1',
{ className: 'title' },
'Hello, ',
name,
'!'
)
JSX =
React.createElement()的语法糖
JSX 不是 HTML,不是模板字符串,不是一门新语言。它就是 JavaScript------用 HTML 的书写方式来描述 UI 结构,然后在编译时被转换成纯函数调用。
三条核心规则
tsx
// 规则1:{} 内可以写任意 JS 表达式
<div>{isLoggedIn ? `欢迎, ${user.name}` : '请登录'}</div>
<div>{items.map(item => <li key={item.id}>{item.text}</li>)}</div>
// 规则2:className 不是 class
// 因为 class 是 JavaScript 的保留关键字
<div className="flex items-center">
// 规则3:每个 JSX 标签 = 一个 Virtual DOM 节点
// 多个节点组合 = 一棵 Virtual DOM 树
TSX:带类型的 JSX
如果你的项目使用 TypeScript,文件后缀就是 .tsx:
tsx
// .tsx 文件:TypeScript + JSX
// 你可以享受完整的类型检查 + JSX 语法
interface ButtonProps {
label: string;
onClick: () => void;
variant?: 'primary' | 'secondary';
}
function Button({ label, onClick, variant = 'primary' }: ButtonProps) {
// ...
}
三、组件:UI 的基本单元
一个函数就是一个组件
React 组件的独特之处在于------一个函数就是一个组件:
tsx
function App() {
// ─── ① 逻辑区:return 之前 ───
// 数据定义、状态管理、事件处理、副作用注册
const [status, setStatus] = useState(null)
const [error, setError] = useState(null)
// ─── ② UI 区:return 之后 ───
// JSX 描述界面长什么样,用 {} 引用逻辑区的变量
return (
<div className="flex flex-col h-screen mx-auto items-center justify-center">
{error ? (
<div className="text-red-500">{error}</div>
) : (
<h1 className="text-4xl font-bold">一切正常</h1>
)}
</div>
)
}
// ─── ③ 样式 ───
// Tailwind class 直接写在 className 上,或者 import CSS 文件
组件的核心公式
ini
UI = f(state + props)
组件函数 = f()
state = 内部数据(useState)
props = 外部输入(父组件传入)
UI = return 返回的 JSX
这个公式的含义:
- 相同的 state + props 一定产生相同的 UI(可预测)
- 状态变了,UI 自动跟上(数据驱动)
- 你可以像测试纯函数一样测试组件
组件首字母必须大写
tsx
// ✅ 这是组件(首字母大写)
function App() { return <div>...</div> }
<App />
// ❌ 这是普通 HTML 标签
function app() { return <div>...</div> }
<app /> // React 会将 <app /> 当作原生 HTML 标签处理
四、数据驱动:声明式 vs 命令式
这是 React 相对于传统前端开发最本质的区别:
erlang
传统方式(jQuery/原生JS) React 方式
───────────────────────── ─────────────────────────
命令式 (How) 声明式 (What)
描述"怎么做": 描述"是什么":
"找到 #result 元素, "当 count = 5 时,
把内容改成 5, 这个 div 的文本就是 5,
再修改颜色为红色" 颜色自动变红"
一个操作 = 一串步骤 一个状态 = 一个界面
一个对比案例
假设做一个搜索功能:
传统方式(你需要手动管理每一步):
javascript
// 监听输入
$('#search').on('input', function(e) {
const keyword = e.target.value
// 发起请求
$.get('/api/search', { keyword }, function(results) {
// 手动更新 UI
$('#results').empty()
if (results.length === 0) {
$('#results').append('<li>无结果</li>')
} else {
results.forEach(r => $('#results').append(`<li>${r.title}</li>`))
}
// 手动处理 loading 状态
$('#loading').hide()
})
// 手动显示 loading
$('#loading').show()
})
React 方式(你只描述每个状态):
tsx
function Search() {
const [keyword, setKeyword] = useState('')
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
useEffect(() => {
if (!keyword) return
setLoading(true)
fetch(`/api/search?keyword=${keyword}`)
.then(r => r.json())
.then(data => {
setResults(data)
setLoading(false)
})
}, [keyword])
return (
<div>
<input value={keyword} onChange={e => setKeyword(e.target.value)} />
{loading && <div>搜索中...</div>}
{!loading && results.length === 0 && <div>无结果</div>}
<ul>
{results.map(r => <li key={r.id}>{r.title}</li>)}
</ul>
</div>
)
}
关键区别:React 版本中,你不需要关心 DOM 在什么时机更新、怎么更新------你只需要定义"每个状态下界面是什么样"。
五、Virtual DOM:界面更新的"调度器"
为什么不直接操作真实 DOM?
真实 DOM 操作很昂贵。每改一个节点的内容,浏览器要做大量工作:重新计算布局、重绘、合成图层。
React 的做法:
scss
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ 1. 你的代码 │ → │ 2. Virtual │ → │ 3. Diff │
│ setCount(5) │ │ DOM 重新生成 │ │ 新旧对比 │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ 4. 最小更新 │
│ 只改变化部分 │
└─────────────┘
Virtual DOM 不是让 React 更快,而是让 React 在大多数情况下"够快",同时让你不必手动优化 DOM 操作。
六、TypeScript + React:类型安全的最佳实践
你的项目使用 TypeScript,它和 React 的配合非常自然:
tsx
// 定义 Props 类型
interface AppState {
status: 'loading' | 'ready' | 'error' | null
error: string | null
loadingMessage: string
}
function App() {
// TypeScript 自动推断类型
const [status, setStatus] = useState<AppState['status']>(null)
const [error, setError] = useState<AppState['error']>(null)
// 如果你尝试 setStatus(123),TypeScript 会直接报错
// Type 'number' is not assignable to type...
}
TypeScript + React 的价值:
- 组件的 props 有明确类型,传错了 IDE 立刻标红
- 重构组件时,TypeScript 告诉你所有需要同步修改的地方
- 团队协作时,代码就是文档(看接口定义就知道怎么用)
七、总结
| 概念 | 一句话 |
|---|---|
| JSX | 在 JS 里写 HTML,编译为 React.createElement() |
| TSX | JSX + TypeScript 类型检查 |
| 组件 | 一个返回 JSX 的函数 = HTML + CSS + JS 的封装 |
| 数据驱动 | UI = f(state),描述状态而非操作步骤 |
| Virtual DOM | JS 层面的 UI 树,Diff 后最小化更新真实 DOM |
| 声明式 | "当 count=5 时界面长这样",而非"先改文字再改颜色" |
React 的根本价值不在于性能或语法,而在于它把 UI 开发从过程式思维 (找到元素 → 改属性 → 改内容)转变为声明式思维(这个状态下的界面长这样)。这是心智模型的彻底转变。