📚 全栈开发学习系列 · 阶段三(前端深化)
17 React 入门:组件化思想与 JSX 语法(当前篇)
18 React 进阶:状态管理与副作用钩子
19 TypeScript 基础:类型系统与类型安全
20 Next.js 入门:SSR 与路由系统
21 Tailwind CSS:原子化样式与响应式设计
22 前端工程化:Vite 构建与前端测试
23 综合实战:React + TypeScript 重写待办事项

React 入门:组件化思想与 JSX 语法

难度:入门 | React 19.2.8 · Vite · JSX
读完本篇你将能:理解 React 的组件化思想,使用 Vite 搭建 React 19 项目,掌握 JSX 语法规则,编写函数组件并通过 Props 传递数据,实现事件处理、条件渲染和列表渲染——为后续状态管理和 Next.js 打下坚实基础。
📑 本文目录
01React 是什么:组件化革命
02环境搭建:Vite 创建 React 19 项目
03JSX 语法:在 JavaScript 中写 HTML
04组件与 Props:数据传递的艺术
05事件处理与条件渲染
06列表渲染与 key 的作用

01 React 是什么:组件化革命

在前两阶段的学习中,你用 HTML + CSS + JavaScript 写过静态网页和交互页面。那种方式的问题是:页面结构一旦复杂,HTML 变成一团乱麻,JavaScript 操作 DOM 的代码散落各处,修改一个按钮的样式可能牵连半页代码。

React 的核心思想是组件化——把页面拆成一个个独立、可复用的积木块。一个导航栏是一个组件,一个文章卡片是一个组件,一个点赞按钮也是一个组件。每个组件管理自己的结构和样式,组合起来就是完整页面。

React 由 Facebook(现 Meta)团队在 2013 年开源,目前最新稳定版是 React 19.2.8(2026 年 7 月发布)。React 19 带来了革命性的 React Compiler——一个编译时工具,自动分析组件依赖关系并插入优化代码,开发者不再需要手动编写 useMemo、useCallback 或 React.memo 来避免不必要的重渲染。

💡 小贴士:React 不只是一个框架
React 官方定位是"一个用于构建用户界面的 JavaScript 库",而非框架。它只负责视图层(View),路由、状态管理、数据获取等能力由生态库补充。这种"小核心 + 大生态"的设计让 React 极其灵活——你可以用它写一个按钮,也可以用它构建 Instagram 级别的应用。

传统 DOM 操作 vs React 组件化

用传统 JavaScript 写一个计数器,你需要手动获取 DOM 元素、绑定事件、更新文本:

traditional.js — 传统 DOM 操作
1
2
3
4
5
6
7
const btn = document.getElementById('btn');
let count = 0;
btn.addEventListener('click', () => {
  count++;
  const display = document.getElementById('count');
  display.textContent = `Clicked ${count} times`;
});

用 React 写同样的计数器,结构、逻辑和状态都在一个组件里,声明式地描述"UI 应该长什么样":

Counter.jsx — React 组件写法
1
2
3
4
5
6
7
function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      Clicked {count} times
    </button>
  );

注意区别:传统方式是"命令式"——告诉浏览器每一步做什么(获取元素、绑定事件、更新文本);React 是"声明式"——描述 UI 在不同状态下的样子,React 负责高效地更新 DOM。这个计数器的 useState 钩子下一篇会详细讲解,这里先感受一下组件的写法。

对比维度 传统 DOM 操作 React 组件化
编程范式 命令式:手动操作 DOM 声明式:描述 UI 状态
复用性 复制粘贴代码片段 组件像函数一样调用
状态管理 变量散落,手动同步 DOM 状态驱动 UI 自动更新
性能优化 手动操作 DOM,易出问题 虚拟 DOM + React Compiler 自动优化

02 环境搭建:Vite 创建 React 19 项目

你已经了解了组件化的概念,现在动手搭建第一个 React 项目。过去教程常用 Create React App(CRA),但它已被官方废弃——自 2025 年 2 月起 CRA 不再活跃维护,且与 React 19 存在依赖冲突。当前官方推荐使用 Vite 创建新项目。

⚠️ 常见错误
npx create-react-app my-app — CRA 已废弃,与 React 19 不兼容
✓ 正确:npm create vite@latest my-app -- --template react

前置条件

确保你的设备已安装 Node.js(建议 20 LTS 或更高版本)。在终端中验证:

Shell — 验证 Node.js 环境
node --version
v20.18.0
npm --version
10.8.2

如果未安装 Node.js,可访问 nodejs.org 下载安装包,macOS 也可用 Homebrew:brew install node

创建项目

在终端中执行以下命令,创建一个 React 19 项目:

Shell — 创建 React 项目
1
2
3
4
npm create vite@latest my-react-app -- --template react
cd my-react-app
npm install
npm run dev

执行 npm run dev 后,终端会显示开发服务器地址(通常是 http://localhost:5173)。用浏览器打开就能看到 React 欢迎页面。Vite 的热模块替换(HMR)能让你修改代码后即时看到更新,无需手动刷新。

项目结构解析

项目目录结构
1
2
3
4
5
6
7
8
9
10
11
my-react-app/
├── index.html        # HTML 入口,挂载点
├── package.json       # 依赖和脚本配置
├── vite.config.js      # Vite 构建配置
└── src/
    ├── main.jsx        # 应用入口,挂载 React
    ├── App.jsx         # 根组件
    ├── App.css         # 根组件样式
    ├── index.css       # 全局样式
    └── assets/        # 静态资源
        └── react.svg

index.html 中有一个 <div id="root">,这是 React 应用的挂载点。main.jsx 负责把 App 组件渲染到这个 div 中:

src/main.jsx — 应用入口
1
2
3
4
5
6
7
8
9
10
import { createRoot } from 'react-dom/client';
import App from './App.jsx';
import './index.css';
// 获取 DOM 挂载点
const root = createRoot(document.getElementById('root'));
// 将 App 组件渲染到挂载点
root.render(<App />);

createRoot 是 React 18 引入的 API(替代了旧的 ReactDOM.render),它启用了 React 的并发渲染能力。<App /> 是 JSX 语法,表示渲染 App 组件——下一节详细讲解。

03 JSX 语法:在 JavaScript 中写 HTML

环境就绪后,进入 React 最核心的语法——JSX。JSX 是 JavaScript 的语法扩展,让你在 JavaScript 代码中直接写类似 HTML 的标记。它不是模板语言(区别于 Vue 的 template),而是 JavaScript 的原生扩展——编译后就是普通的 JavaScript 函数调用。

JSX 基本规则

App.jsx — JSX 基本写法
1
2
3
4
5
6
7
8
9
function App() {
  const name = '全栈开发者';
  const isNew = true;
  return (
    <div className="app">
      <h1>你好,{name}!</h1>
      <p>{isNew ? '欢迎入坑前端' : '老朋友你好'}</p>
    </div>
  );

这段代码展示了 JSX 的几个关键规则:

• className 替代 class:因为 class 是 JavaScript 保留字,JSX 中用 className 指定 CSS 类名
• 花括号 {} 嵌入表达式:在 JSX 中,{} 内可以放任何有效的 JavaScript 表达式(变量、函数调用、三元运算等)
• 必须返回单个根元素:一个组件的 return 语句只能有一个根元素。多个元素需要用 <div> 或 <></>(Fragment)包裹
• 标签必须闭合:所有标签必须自闭合或有结束标签,包括 <img />、<br />、<input />

JSX 中的样式

JSX 中写内联样式时,style 属性接收一个对象而非字符串,CSS 属性名用驼峰命名法:

Style.jsx — JSX 内联样式
1
2
3
4
5
6
7
8
function StyledBox() {
  const boxStyle = {
    backgroundColor: '#2563eb',
    color: '#ffffff',
    padding: '16px 24px',
    borderRadius: '8px'
  };
  return <div style={boxStyle}>蓝色盒子</div>;
⚠️ 常见错误
<div style="color: red; font-size: 14px"> — JSX 中 style 不接受字符串
<div style={{ 'font-size': '14px' }}> — 虽然能用字符串 key,但不符合规范
✓ 正确:<div style={{ fontSize: '14px', color: 'red' }}> — 驼峰命名 + 对象写法

JSX 编译后的样子

理解 JSX 编译后的代码有助于你理解它的本质。下面的 JSX:

JSX 编译前后对比
1
2
3
4
5
6
7
// 你写的 JSX:
const element = <h1 className="title">Hello</h1>;
// Babel/Vite 编译后:
const element = createElement(
  'h1',
  { className: 'title' },
  'Hello'
);

JSX 本质上是 React.createElement() 的语法糖。你写 JSX,编译器帮你转换成 createElement 调用,生成虚拟 DOM 节点(一个普通 JS 对象),React 再根据虚拟 DOM 高效更新真实 DOM。

04 组件与 Props:数据传递的艺术

掌握了 JSX 后,你已能写出 React 组件的骨架。现在学习如何让组件之间传递数据——这就是 Props。Props 是 React 组件的输入参数,就像函数接收参数一样,组件接收 Props 并根据它们渲染不同的内容。

函数组件的基本写法

React 中定义组件只需写一个函数,函数名大写开头,返回 JSX:

UserCard.jsx — 接收 Props 的组件
1
2
3
4
5
6
7
8
9
10
11
function UserCard({ name, role, avatar }) {
  return (
    <div className="user-card">
      <img src={avatar} alt={name} />
      <div>
        <h3>{name}</h3>
        <span>{role}</span>
      </div>
    </div>
  );
}

这里 { name, role, avatar } 是 JavaScript 解构语法,从 props 对象中提取这三个属性。使用这个组件时,像写 HTML 标签一样传递属性:

App.jsx — 使用 UserCard 组件
1
2
3
4
5
6
7
8
function App() {
  return (
    <div>
      <UserCard
        name="张三"
        role="后端工程师"
        avatar="/avatar/zhangsan.png"
      />
    </div>
  );

Props 的特点

💡 小贴士:Props 是只读的
Props 的核心规则是"只读"——组件绝不能修改自己的 props。这就像函数参数:你接收它、使用它,但不能改它。需要变化的数据用 state(下一篇讲解),props 只负责把数据从父组件传到子组件。这条"单向数据流"规则是 React 架构可预测性的基石。

默认值与 children

组件可以设置默认 Props 值,也可以通过 children 接收标签之间的内容:

Button.jsx — 默认值与 children
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function Button({ children, type = 'primary', onClick }) {
  const colors = {
    primary: '#2563eb',
    danger: '#ef4444'
  };
  return (
    <button
      onClick={onClick}
      style={{ background: colors[type], color: '#fff' }}
    >
      {children}
    </button>
  );
}

使用方式:<Button onClick={handleClick}>点击我</Button>。这里 type 没传,自动取默认值 'primary';标签之间的"点击我"通过 children 传入。

05 事件处理与条件渲染

你已经能用组件和 Props 渲染静态内容了。但真实的页面需要交互——用户点击按钮、输入文字时 UI 要响应。React 的事件处理和条件渲染让组件"活"起来。

事件处理

React 的事件命名用驼峰法(onClick 而非 onclick),事件处理函数通过 JSX 属性传入:

EventDemo.jsx — 事件处理
1
2
3
4
5
6
7
8
9
10
11
12
function EventDemo() {
  const handleClick = (e) => {
    console.log('按钮被点击', e.target);
  };
  const handleInput = (e) => {
    console.log('输入内容:', e.target.value);
  };
  return (
    <div>
      <button onClick={handleClick}>点我</button>
      <input onChange={handleInput} placeholder="输入文字" />
    </div>
  );

事件对象 e 是 React 合成事件(SyntheticEvent),兼容所有浏览器。e.target 指向触发事件的 DOM 元素,e.target.value 获取输入框的当前值。

⚠️ 常见错误
<button onClick={handleClick()}> — 加了括号会立即执行,而非点击时执行
✓ 正确:<button onClick={handleClick}> — 传函数引用,点击时才调用

条件渲染

条件渲染是根据状态决定显示哪些内容。React 中没有 v-if 或 v-show 指令——因为 JSX 就是 JavaScript,直接用 if、三元运算符和 && 运算符即可:

Conditional.jsx — 三种条件渲染方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function Greeting({ isLoggedIn, name }) {
  // 方式1:三元运算符(二选一)
  return (
    <div>
      {isLoggedIn ? (
        <p>欢迎回来,{name}!</p>
      ) : (              // else 分支
        <p>请先登录</p>
      )}
      {/* 方式2:&& 运算符(满足条件才渲染)*/}
      {isLoggedIn && <span>✓ 已验证</span>}
    </div>
  );
  // 方式3:提前 return(不需要 else)
  if (!isLoggedIn) return <p>请先登录</p>;
  return <p>欢迎回来,{name}!</p>;
}
💡 小贴士:&& 运算符的陷阱
使用 && 做条件渲染时,左侧必须是布尔值。如果左侧是数字 0,React 会渲染出 "0"。例如 {count && <p>有数据</p>},当 count 为 0 时会显示 "0"。安全写法:{count > 0 && <p>有数据</p>}。

06 列表渲染与 key 的作用

学会了事件处理和条件渲染,最后一个基础技能是列表渲染——从数组数据生成一组 JSX 元素。这是实际开发中最常用的操作:渲染文章列表、渲染用户表格、渲染菜单项。

map 方法渲染列表

React 使用 JavaScript 的 map() 方法遍历数组,每个元素返回一个 JSX 节点:

PostList.jsx — 列表渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function PostList() {
  const posts = [
    { id: 1, title: 'React 入门指南', author: '张三' },
    { id: 2, title: 'FastAPI 实战', author: '李四' },
    { id: 3, title: 'PostgreSQL 优化', author: '王五' }
  ];
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <h3>{post.title}</h3>
          <span>作者:{post.author}</span>
        </li>
      ))}
    </ul>
  );
}

注意 map() 返回的每个 <li> 都有一个 key 属性——这是 React 识别列表项身份的关键。

key 的作用与规则

React 使用虚拟 DOM 进行高效更新。当列表数据变化时(增加、删除、排序),React 需要知道哪些项变了。key 就是每个列表项的唯一身份证,帮助 React 精确定位变化的元素,避免不必要的 DOM 操作。

⚠️ 常见错误:用数组索引做 key
<li key={index}> — 当列表顺序变化(插入、删除、排序)时,索引会错乱,导致 React 复用错误的 DOM,出现数据错位
✓ 正确:<li key={post.id}> — 用数据的唯一 ID 做 key,稳定且唯一
💡 小贴士:何时可以用索引做 key
只有满足以下全部条件时,用索引做 key 才安全:列表永不重新排序、列表项不会被删除或插入、列表项没有状态(如输入框内容)。实际开发中这些条件很难同时满足,所以养成习惯——永远用数据的唯一 ID 做 key。

key 原理示意

假设列表有 A、B、C 三项。如果在头部插入 D,React 的 diff 过程:

用 key=id
→
React 识别 D 是新项
只需插入 1 个 DOM 节点
用 key=index
→
React 认为 A→B→C 全变了
更新 3 个 + 插入 1 个 DOM 节点

用唯一 ID 做 key 时,React 知道 A、B、C 没变(它们的 key 没变),只需在头部插入 D。用索引做 key 时,D 的索引是 0(原来 A 的位置),React 认为索引 0 的内容变了,需要更新所有节点——性能差且可能导致状态错乱。

📖 知识回顾
组件化思想 Vite 创建项目 JSX 语法规则 函数组件 Props 数据传递 事件处理 条件渲染 列表渲染与 key
✏️ 动手练习
🟢 基础验证
请用 Vite 创建一个 React 项目,修改 App.jsx,渲染一个包含你姓名和一句自我介绍的卡片组件。要求:使用 className 和内联 style 对象设置样式,运行 npm run dev 在浏览器中查看效果。
参考解法:创建 App.jsx,定义函数组件,return 一个带 className 和 style 的 div,内含 h1 和 p 标签,通过 {} 插入变量。
🟡 组合应用
请编写一个 ProductList 组件,接收一个商品数组 props(含 id、name、price、inStock 字段),用 map 渲染列表。每个商品显示名称和价格,库存为 0 时显示"缺货"标签(条件渲染),有库存时显示"在售"标签。
提示:用三元运算符或 && 做条件渲染,key 用 product.id,标签颜色用条件表达式动态选择。
🔴 开放挑战
用 React 组件化思路设计一个可复用的 Tabs 标签页组件。要求:通过 Props 接收标签配置数组(如 { id, label, content }),点击标签头切换内容区域。提示:目前还没学 state,可以先用 props 传入当前激活的 tab id 来模拟——下一篇学完 useState 后再用内部状态实现完整版。
参考方向:组件结构分 TabHeader(渲染标签按钮列表)和 TabContent(渲染当前内容),通过 props.activeId 做条件判断。
下篇预告
18 React 进阶:状态管理与副作用钩子
将学习 useState 管理组件内部状态、useEffect 处理副作用(数据获取、定时器、订阅),理解依赖数组和清理函数,让你的 React 组件真正具备交互能力