第 4 章 · React 18 入门:JSX 与函数组件
本章目标:用 Vite 创建 academy-demo-react;掌握 JSX 语法规则、条件/列表渲染、函数组件、props 与 children;实现与 Vue 版等价的课程卡片列表。
学时建议:3~4 小时
前置:完成 frontend-web ch01~ch13;已学 Vue ch01~ch02(便于对比)。
4.1 创建 React 项目
npm create vite@latest academy-demo-react -- --template react
cd academy-demo-react
npm install
npm run dev
academy-demo-react/
├── index.html
├── vite.config.js
├── public/mock/courses.json
└── src/
├── main.jsx
├── App.jsx
├── App.css
├── components/
│ ├── CourseCard.jsx
│ └── CourseList.jsx
└── data/
└── courses.js
// src/main.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
| 对比 Vue | React |
|---|---|
createApp(App).mount('#app') | createRoot().render(<App />) |
.vue SFC | .jsx 组件即函数 |
| 模板编译 | JSX 编译为 React.createElement |
行业案例 · 贤紫数据看板:2025 年内部 BI 看板选用 React + ECharts,因团队招聘池中 React 简历占比更高,且与 Ant Design 表格生态契合;同一数据集通过 REST 同时服务 Vue 商户端与 React 看板,接口契约由 OpenAPI 统一。
4.2 JSX 规则
JSX 是 JavaScript 的语法扩展,不是 HTML。
| 规则 | 正确 | 错误 |
|---|---|---|
| 单根节点(或 Fragment) | <>...</> | 相邻多个根元素 |
| 闭合标签 | <img />、<br /> | <img> 不闭合 |
| class 用 className | className="card" | class="card" |
| 样式对象 | style={{ color: 'red' }} | style="color:red" |
JS 表达式用 {} | {course.title} | 裸写变量 |
| 注释 | {/ 注释 /} | <!-- --> |
| 布尔属性 | disabled 或 disabled={true} | disabled="false" 仍为真 |
const element = (
<>
<h1 className="title">{title}</h1>
<p style={{ marginTop: '1rem', color: 'var(--color-muted)' }}>
共 {count} 门课程
</p>
</>
)
安全:JSX 默认转义文本,防 XSS;dangerouslySetInnerHTML 仅在消毒后使用(对齐 ch16 escape-html)。
4.3 函数组件
React 组件是以大写字母开头的函数,接收 props 返回 JSX。
// 函数声明
function Greeting({ name = '学员' }) {
return <p>你好,{name}</p>
}
// 箭头函数(项目内常用)
const Badge = ({ children, variant = 'default' }) => (
<span className={`badge badge--${variant}`}>{children}</span>
)
| 约定 | 说明 |
|---|---|
| 组件名 PascalCase | CourseCard 非 courseCard |
| props 只读 | 不修改 props.course.title |
| 默认导出页面级 | export default function App() |
| 命名导出可复用 | export function CourseCard() |
4.4 Props 与 children
// CourseCard.jsx
export function CourseCard({ course, favorited, onToggleFavorite, onOpenDetail }) {
const { id, title, desc, hours, level, tags = [], cover } = course
return (
<article className="course-card">
{cover && <img src={cover} alt={title} className="course-card__cover" loading="lazy" />}
<h3 className="course-card__title">{title}</h3>
<p className="course-card__desc">{desc}</p>
<div className="course-card__tags">
{tags.map((tag) => (
<Badge key={tag} variant="tag">{tag}</Badge>
))}
</div>
<footer className="course-card__footer">
<span>{hours} 学时</span>
<span>{level}</span>
<button
type="button"
aria-pressed={favorited}
onClick={() => onToggleFavorite(id)}
>
{favorited ? '已收藏' : '收藏'}
</button>
<button type="button" onClick={() => onOpenDetail(id)}>
详情
</button>
</footer>
</article>
)
}