React UI 基础:重新思考学习 React 的意义
在 AI 快速发展的时代,重新思考学习 React 的意义
AI 时代为什么还要学 React
2026 年学 React,很多人会问:AI 都能写代码了,还有必要学框架吗?
我的答案是:比以前更有必要。但学习的方式和目的变了。
现在有了 Copilot、Cursor 这些工具,写组件的速度确实快了很多。
但我发现,如果不理解 React 的核心概念,AI 生成的代码往往会有隐藏的 bug。
工具可以加速,但不能替代理解。
前端开发的现状
前端开发正在经历两个趋势:
- 框架的成熟:React、Vue、Svelte 等框架已经相对稳定,核心概念不再频繁变化
- AI 的介入:AI 工具可以生成大量样板代码,但需要人来把控架构和质量
在这个背景下,理解原理比记忆 API 更重要。
React 的组件化思维、单向数据流、纯函数理念,这些不会因为 AI 的出现而过时。
相反,它们是你判断 AI 生成代码质量的标准。
React 在 AI 时代的生态优势
值得一提的是,React 在 AI 时代有一个显著的优势:AI 模型的训练数据主要来自 React 生态。
这意味着:
- AI 工具对 React 代码的理解和生成质量更高
- 大量优秀的组件库(如 shadcn/ui、Radix UI、Chakra UI)都是为 React 设计的
- 当你用 AI 生成代码时,React 的代码质量通常比其他框架更好
这不是说其他框架不好,而是一个现实:React 的社区规模和代码量决定了 AI 对它的支持更好。在选择技术栈时,这是一个不容忽视的因素。
本文的定位
这不是一篇从零开始的教程,而是一个有经验的工程师对 React 基础的总结和思考。
本文涉及以下内容:
- 讲述 React 的设计理念,而不只是语法
- 分享实战中的经验和常见错误
- 探讨在 AI 辅助下如何更好地使用 React
如果你有一定的 JavaScript 基础,想快速掌握 React 的核心概念,这篇文章适合你。
Part 1: 组件化思维
组件的本质
React 的组件本质上是函数。
输入 props,输出 UI。
这种函数式的思维方式,让代码更容易测试和维护。
jsx
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3Am.jpg"
alt="Katherine Johnson"
/>
);
}
这个 Profile 组件就是一个函数,返回一段 JSX。你可以在任何地方调用它:
jsx
function Gallery() {
return (
<section>
<h1>了不起的科学家</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
为什么选择组件化
React 选择组件化不是偶然的。在传统的 Web 开发中,HTML、CSS、JavaScript 是分离的。
但在现代应用中,UI 逻辑和标记是紧密相关的:按钮的点击事件和按钮本身是一体的,表单的验证逻辑和表单结构是一体的。
组件化让你可以在同一个地方处理这些相关的逻辑,而不是在多个文件之间跳来跳去。这不是技术上的限制,而是对现实问题的务实解决方案。
组件的拆分原则
刚开始写 React 时,我也纠结过什么时候该拆分组件。
后来发现,与其纠结规则,不如问自己:这段代码会不会在其他地方用到?
如果会,就拆;如果不会,先别急着拆。
过度抽象比重复代码更难维护。我见过太多项目,为了"复用"而创建了一堆只用一次的组件,反而增加了理解成本。
实际项目中的经验:
- 如果一个组件超过 200 行,考虑拆分
- 如果一个组件做了太多事情(数据获取、状态管理、UI 渲染),考虑拆分
- 如果一段代码在两个地方用到,可以考虑提取;三个地方用到,一定要提取
组件的命名和组织
React 要求组件名必须以大写字母开头。
这不是为了好看,而是为了区分组件和普通 HTML 标签:
jsx
// React 知道 <Profile /> 是组件
<Profile />
// React 知道 <div> 是 HTML 标签
<div></div>
关于文件组织:
- 一个文件一个主要组件,文件名和组件名保持一致
- 如果有多个紧密相关的小组件,可以放在同一个文件中
- 不要在组件内部定义组件,这会导致每次渲染都创建新的组件定义,破坏性能优化
模块化的实践
React 使用标准的 JavaScript 模块系统。
这里需要注意默认导出和具名导出的区别:
jsx
// 默认导出 - 一个文件一个主要组件
export default function Button() {
return <button>点击我</button>;
}
// 导入时可以使用任意名称
import MyButton from './Button.js';
jsx
// 具名导出 - 一个文件多个组件
export function Button() {
return <button>点击我</button>;
}
export function Input() {
return <input />;
}
// 导入时必须使用相同的名称
import { Button, Input } from './Components.js';
建议:团队内保持一致即可。
个人倾向于默认导出,因为它让导入语句更简洁,也更容易重构。
Part 2: JSX 的设计哲学
JSX 解决了什么问题
在 JSX 出现之前,React 使用 React.createElement() 来创建元素:
javascript
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
);
这种方式的问题是:代码结构和最终的 UI 结构差距太大,难以理解和维护。JSX 让代码更接近最终的 UI 结构:
jsx
const element = <h1 className="greeting">Hello, world!</h1>;
JSX 不是必需的,但它让代码更直观。这是一个务实的选择。
JSX 的三条规则
JSX 看起来像 HTML,但它更严格。这些规则背后都有技术原因。
规则 1:只能返回一个根元素
jsx
// 错误:返回了两个元素
function AboutPage() {
return (
<h1>关于我们</h1>
<p>欢迎来到我们的网站</p>
);
}
// 正确:用 Fragment 包裹
function AboutPage() {
return (
<>
<h1>关于我们</h1>
<p>欢迎来到我们的网站</p>
</>
);
}
为什么?因为 JSX 会被转换成 JavaScript 函数调用,而函数只能返回一个值。
<>...</>是 Fragment 的简写,它不会在 DOM 中创建额外的节点。
规则 2:所有标签必须闭合
在 HTML 中,<img> 和 <br> 可以不闭合。但在 JSX 中,所有标签都必须闭合:
jsx
<img src="avatar.jpg" />
<br />
这是因为 JSX 是 JavaScript,需要明确的语法边界。
规则 3:使用驼峰命名法
JSX 会转换成 JavaScript,所以属性名需要遵循 JavaScript 的命名规则:
jsx
// HTML 中
<div class="container" tabindex="0"></div>
// JSX 中
<div className="container" tabIndex={0}></div>
class 和 for 是 JavaScript 保留字,所以 JSX 使用 className 和 htmlFor。其他属性使用驼峰命名法,如 onClick、strokeWidth。
JSX 中的 JavaScript
JSX 的强大之处在于,你可以在标记中嵌入 JavaScript 表达式。使用大括号 {} 就可以"回到" JavaScript:
jsx
function Profile() {
const name = "Katherine Johnson";
const avatar = "https://i.imgur.com/MK3eW3Am.jpg";
return (
<div>
<h1>{name}的个人资料</h1>
<img src={avatar} alt={name} />
</div>
);
}
你可以在大括号中使用任何 JavaScript 表达式:
jsx
function TodoList() {
const tasks = 3;
return (
<div>
<h1>待办事项</h1>
<p>你还有 {tasks} 个任务</p>
<p>完成度:{(tasks / 10) * 100}%</p>
</div>
);
}
双大括号的秘密
你可能会看到这样的代码:
jsx
<div style={{ backgroundColor: 'black', color: 'pink' }}>
内容
</div>
这不是特殊语法。外层的 {} 表示"这是 JavaScript 表达式",内层的 {} 表示"这是一个 JavaScript 对象"。
JSX vs 模板语法
有些框架(如 Vue)使用模板语法,提供了 v-if、v-for 这样的指令。React 选择了 JSX,让你直接使用 JavaScript 的 if、map 等语法。
这两种方式各有优劣:
- 模板语法更容易学习,但功能受限
- JSX 更灵活,但需要更好的 JavaScript 基础
React 的选择是:相信开发者的 JavaScript 能力,不创造新的语法。
这在 AI 时代更有意义,因为 AI 更容易理解标准的 JavaScript,而不是框架特定的语法。
Part 3: Props 与数据流
Props 的设计理念
Props 是 React 实现单向数据流的核心机制。
父组件通过 props 向子组件传递数据,子组件不能修改 props。
jsx
// 父组件传递 props
function Profile() {
return (
<Avatar
name="Lin Lanying"
imageUrl="https://i.imgur.com/1bX5QH6.jpg"
size={100}
/>
);
}
// 子组件接收 props
function Avatar({ name, imageUrl, size }) {
return (
<img
src={imageUrl}
alt={name}
width={size}
height={size}
/>
);
}
这里使用了解构语法,让代码更简洁。你也可以使用 props 对象:
jsx
function Avatar(props) {
return (
<img
src={props.imageUrl}
alt={props.name}
width={props.size}
height={props.size}
/>
);
}
我建议使用解构语法,因为它让组件的 API 一目了然。
如何设计 Props API
设计 Props API 是一门艺术。好的 Props API 应该:
- 命名清晰,见名知意
- 数量适中,不超过 5-6 个
- 有合理的默认值
jsx
function Avatar({ name, imageUrl, size = 100 }) {
// size 有默认值,调用时可以省略
return (
<img
src={imageUrl}
alt={name}
width={size}
height={size}
/>
);
}
实际项目中,如果一个组件需要太多 props,通常意味着它做了太多事情,应该考虑拆分。
children 的使用场景
children 是一个特殊的 prop,用于传递组件标签之间的内容:
jsx
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
// 使用时
function Profile() {
return (
<Card>
<h1>用户资料</h1>
<p>这是卡片的内容</p>
<button>编辑</button>
</Card>
);
}
children 特别适合创建布局组件。我在项目中经常用它来创建 Modal、Card、Container 这样的组件。
Props 不可变性的意义
Props 是只读的,这是 React 的核心原则之一:
jsx
// 错误:不要修改 props
function Clock({ color }) {
color = 'red'; // 错误!
return <div style={{ color }}>当前时间</div>;
}
为什么?因为 Props 代表父组件传递的数据。如果子组件可以随意修改,会导致数据流混乱,难以追踪 bug。
Props 就像函数的参数,你不会在函数内部修改参数的值。如果需要修改数据,应该使用 state(这是下一个主题)。
这种单向数据流的设计,让 React 应用更容易理解和调试。在大型项目中,这个优势尤其明显。
Part 4: 条件渲染与列表渲染
四种条件渲染方式
React 没有提供 v-if 这样的指令,而是让你直接使用 JavaScript 的条件语法。这给了你更大的灵活性。
方式 1:if 语句(提前返回)
jsx
function PackingItem({ name, isPacked }) {
if (isPacked) {
return <li className="item">{name} ✔</li>;
}
return <li className="item">{name}</li>;
}
适用场景:两种情况的 UI 差异较大时。
方式 2:三元运算符
jsx
function PackingItem({ name, isPacked }) {
return (
<li className="item">
{isPacked ? name + ' ✔' : name}
</li>
);
}
适用场景:需要在两个选项之间选择时。
方式 3:逻辑与运算符 &&
jsx
function Notification({ message, hasNewMessage }) {
return (
<div>
<h1>通知中心</h1>
{hasNewMessage && <p>你有新消息:{message}</p>}
</div>
);
}
适用场景:条件为假时不需要显示任何内容。
注意 :不要在 && 左侧放数字!
jsx
// 错误:当 messageCount 为 0 时,会显示 0
{messageCount && <p>有 {messageCount} 条新消息</p>}
// 正确:确保左侧是布尔值
{messageCount > 0 && <p>有 {messageCount} 条新消息</p>}
为什么?因为在 JavaScript 中,0 && something 会返回 0,而 React 会渲染这个 0。
方式 4:条件赋值给变量
jsx
function PackingItem({ name, isPacked }) {
let itemContent = name;
if (isPacked) {
itemContent = <del>{name} ✔</del>;
}
return (
<li className="item">
{itemContent}
</li>
);
}
适用场景:条件逻辑复杂,或者需要多次使用条件结果时。
选择哪种方式
这取决于具体场景:
- 简单的二选一:使用三元运算符
- 只在条件为真时显示:使用 &&
- 复杂的条件逻辑:使用 if 语句或变量赋值
- 完全不同的 UI:使用 if 提前返回
在实际项目中,我倾向于使用 if 提前返回,因为它让代码更容易理解。
个人偏好,团队内保持一致即可。
列表渲染的性能考虑
在 React 中,使用 map() 方法来渲染列表:
jsx
const scientists = [
'凯瑟琳·约翰逊',
'马里奥·莫利纳',
'穆罕默德·阿卜杜勒·萨拉姆'
];
function ScientistList() {
return (
<ul>
{scientists.map(scientist =>
<li>{scientist}</li>
)}
</ul>
);
}
如果需要过滤数据,可以先用 filter(),再用 map():
jsx
const people = [
{ id: 0, name: '凯瑟琳·约翰逊', profession: '数学家' },
{ id: 1, name: '马里奥·莫利纳', profession: '化学家' },
{ id: 2, name: '穆罕默德·阿卜杜勒·萨拉姆', profession: '物理学家' },
{ id: 3, name: '珀西·朱利安', profession: '化学家' },
];
function ChemistList() {
const chemists = people.filter(person =>
person.profession === '化学家'
);
return (
<ul>
{chemists.map(person =>
<li key={person.id}>
<b>{person.name}</b>: {person.profession}
</li>
)}
</ul>
);
}
Key 的作用机制
你可能注意到上面的代码中有 key={person.id}。这个 key 非常重要。
Key 告诉 React 每个组件对应数组中的哪个项。
当列表发生变化时,React 通过 key 来判断哪些项是新增的、删除的或移动的,从而只更新必要的部分。
jsx
// 好:使用数据的唯一 ID
<li key={person.id}>{person.name}</li>
// 可以但不推荐:使用索引
<li key={index}>{person.name}</li>
// 错误:没有 key
<li>{person.name}</li>
何时可以使用索引作为 key?
只有当列表满足以下所有条件时:
- 列表是静态的(不会增删改)
- 列表不会重新排序
- 列表项没有 ID
否则,使用索引作为 key 可能导致性能问题或 bug。
实战中的常见错误
我见过很多项目在列表渲染时出现问题,主要是:
- 忘记添加 key
- 使用索引作为 key,导致列表更新时出现 bug
- 使用
Math.random()生成 key,导致每次渲染都重新创建组件
记住:key 应该来自数据本身,而不是生成的。如果数据没有 ID,考虑在获取数据时生成一个唯一标识。
Part 5: 纯函数与副作用
为什么 React 强调纯函数
React 假设你编写的每个组件都是纯函数。这意味着:
- 给定相同的 props,总是返回相同的 JSX
- 不修改渲染前就存在的变量或对象
jsx
// 纯组件
function Recipe({ drinkers }) {
return (
<ol>
<li>烧开 {drinkers} 杯水。</li>
<li>加入 {drinkers} 勺茶和 {0.5 * drinkers} 勺香料。</li>
<li>加入 {0.5 * drinkers} 杯牛奶和糖调味。</li>
</ol>
);
}
这个组件是纯粹的:无论调用多少次,相同的 drinkers 总是返回相同的结果。
不纯组件的问题
jsx
// 不纯组件:修改了外部变量
let guest = 0;
function Cup() {
guest = guest + 1; // 错误!
return <h2>茶杯 #{guest}</h2>;
}
function TeaSet() {
return (
<>
<Cup />
<Cup />
<Cup />
</>
);
}
这个组件有什么问题?每次渲染 Cup 都会修改 guest 变量。在开发模式下,React 会渲染每个组件两次(用于检测副作用),所以实际输出可能是:茶杯 #2, 茶杯 #4, 茶杯 #6。
正确的做法是通过 props 传递数据:
jsx
function Cup({ guest }) {
return <h2>茶杯 #{guest}</h2>;
}
function TeaSet() {
return (
<>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</>
);
}
纯函数对性能优化的意义
纯函数的好处不仅仅是可预测性。它还让 React 可以进行性能优化:
- 如果 props 没有变化,React 可以跳过渲染
- React 可以安全地中断和重新开始渲染
- React 可以并发渲染多个组件
这些优化都依赖于组件的纯粹性。如果组件有副作用,这些优化就无法进行。
副作用的正确处理方式
有些操作必须产生副作用,比如:
- 发送网络请求
- 更新 DOM
- 启动动画
这些副作用应该放在事件处理函数中,而不是渲染逻辑中:
jsx
function Button() {
function handleClick() {
// 副作用:显示提示
alert('你点击了我!');
}
return <button onClick={handleClick}>点击我</button>;
}
如果需要在渲染时执行副作用(比如数据获取),应该使用 useEffect hook。但这是另一个主题了。
StrictMode 的作用
React 提供了严格模式,在开发环境中会调用每个组件两次,帮助发现不纯的组件:
jsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(
<StrictMode>
<App />
</StrictMode>
);
如果你的组件是纯粹的,调用两次不会有任何问题。如果不纯,你会立即发现问题。
我建议始终开启严格模式。它能帮你在开发阶段发现很多潜在的问题。
学习路径与思考
下一步应该学什么
掌握了 UI 基础后,接下来应该学习:
- 添加交互:事件处理、state 管理
- 状态管理:如何设计 state 结构,如何在组件间共享 state
- 副作用处理:useEffect 的使用和常见陷阱
- 性能优化:memo、useMemo、useCallback 的使用场景
这些主题都建立在 UI 基础之上。
如果你理解了组件、props、纯函数这些概念,后续的学习会容易很多。
如何在实战中提升
理论学习很重要,但实战才能真正掌握。我的建议是:
- 从小项目开始,逐步挑战更复杂的应用
- 阅读优秀的 React 项目代码,学习他们的组件设计
- 遇到问题时,先思考为什么,再查文档
- 不要过度设计,先让代码工作,再优化
AI 辅助开发的正确姿势
现在有了 AI 工具,开发效率确实提高了。但我发现,AI 最适合做的是:
- 生成样板代码
- 实现明确的功能
- 重构和优化代码
AI 不擅长的是:
- 架构设计
- 性能优化
- 复杂的状态管理
所以,理解原理仍然很重要。AI 可以帮你写代码,但不能帮你做决策。
保持技术敏感度的建议
前端技术变化很快,但核心概念变化不大。我的建议是:
- 关注核心概念,而不是追逐新工具
- 理解技术选择背后的权衡
- 保持好奇心,但不要盲目跟风
- 在实际项目中验证新技术,而不是为了用而用
React 的组件化思维、单向数据流、纯函数理念,这些概念在其他框架中也有体现。
掌握了这些,学习其他框架会容易很多。
总结
文章总结了 React UI 基础的核心概念:
- 组件化思维:函数式的 UI 构建方式
- JSX:声明式的 UI 描述语言
- Props:单向数据流的实现
- 条件渲染与列表渲染:动态 UI 的构建方式
- 纯函数:可预测、可优化的组件设计
这些概念是 React 开发的基石。掌握它们后,你已经可以构建简单但完整的 React 应用了。
在 AI 时代,理解这些原理比记忆 API 更重要。它们是你判断 AI 生成代码质量的标准,也是你做技术决策的依据。
相关资源
本文基于 React 官方文档 "描述 UI" 章节。