🔥 React 零基础入门(上):环境搭建 + JSX 深度解析
摘要:本文面向零基础开发者,从 Vite 环境搭建开始,带你掌握 React 组件思想和 JSX 的 9 条核心规则。JSX 作为 React 的灵魂,会从编译原理讲到 Fiber 架构,帮你彻底搞懂这个"看起来像 HTML 的 JavaScript"。
📌 前言
面试官:"请解释一下 JSX 的本质?"
我:"JSX 就是......在 JavaScript 里写 HTML?"
面试官:"那它和模板引擎有什么区别?为什么 React 要选它?编译后是什么?"
我:"......"
写了三个月 React,我连最基础的概念都没搞清楚。后来我花了两周时间,从零开始系统学习,终于把这些知识彻底理顺了。
这是React 零基础入门系列的上篇,重点讲 JSX------React 的灵魂。不讲废话,只讲你真正需要知道的东西。
📖 系列文章:本篇是上篇(环境搭建 + 组件 + JSX),下篇讲 Props、State、useEffect 生命周期、事件处理和实战项目。
🎯 本文适合谁
- 完全没接触过 React 的前端新手
- 写了 JSX 但不理解其本质的开发者
- 从 Vue 转 React,对"模板 → JSX"感到困惑的同学
- 想要在面试中讲清楚 JSX 的求职者
🚀 一、环境搭建(5 分钟搞定)
1.1 为什么选 Vite?
React 官方已不再推荐 create-react-app(CRA),现在推荐使用 Vite:
| 对比项 | CRA(旧方案) | Vite(新方案) |
|---|---|---|
| 启动速度 | 慢(Webpack 打包) | 快(ESBuild 原生编译) |
| 热更新 | 一般 | 极快(毫秒级) |
| 官方推荐 | ❌ 已废弃 | ✅ 推荐 |
1.2 创建项目
bash
# 创建 React 项目
npm create vite@latest my-first-react-app -- --template react
# 进入项目 → 安装依赖 → 启动
cd my-first-react-app
npm install
npm run dev
浏览器打开 http://localhost:5173,看到欢迎页面就成功了!
1.3 项目结构
perl
my-first-react-app/
├── src/ # 你写的代码都在这里
│ ├── App.tsx # 根组件
│ ├── main.tsx # 入口文件
│ └── index.css # 全局样式
├── index.html # HTML 模板
└── package.json # 项目配置
💡 你只需要关心
src/目录,其他文件暂时不用管。
📚 二、核心思想:一切皆为组件
React 应用由一个个独立的、可复用的组件构成,就像搭积木:
css
App(根组件)
├── Header(头部)
├── Sidebar(侧边栏)
│ ├── NavItem
│ └── NavItem
└── Content(内容区)
├── Card
└── Card
写一个最简单的组件
tsx
// Welcome.tsx --- 组件就是一个函数,返回 JSX
function Welcome() {
return <h1>你好,React!</h1>;
}
// App.tsx --- 使用组件,像 HTML 标签一样
function App() {
return (
<div>
<Welcome /> {/* 可以重复使用 */}
<Welcome />
</div>
);
}
export default App;
关键规则:
- 组件就是普通的 JavaScript 函数
- 函数返回 JSX(看起来像 HTML,实际是 JavaScript)
- 组件名必须大写开头(React 通过大小写区分组件和 HTML 标签)
📚 三、JSX 深度解析:React 的灵魂(重点)
JSX 是 React 最核心的概念,也是面试的高频考点。
3.1 JSX 到底是什么?
JSX = JavaScript XML,是一种 JavaScript 的语法扩展,让你可以在 JavaScript 里写"看起来像 HTML"的代码。
它不是 HTML! 虽然 JSX 看起来像 HTML,但它本质上是 JavaScript:
tsx
// 你写的 JSX
const element = <h1 className="title">Hello, World!</h1>;
// 编译后的 JavaScript(Babel 转换)
const element = React.createElement('h1', { className: 'title' }, 'Hello, World!');
每一个 JSX 标签,最终都会被编译成 React.createElement() 函数调用。
这意味着:
- JSX 不是模板引擎
- JSX 不是字符串
- JSX 是表达式,它会产生一个 JavaScript 对象(React Element)
3.2 为什么 React 选择 JSX?
| 对比维度 | 传统模板(Vue / Angular) | JSX |
|---|---|---|
| 本质 | HTML 模板 + 指令 | JavaScript 表达式 |
| 逻辑处理 | 需要 v-if、v-for 等指令 |
直接用 JS 的 if、map |
| 类型检查 | 模板层难以做 TS 检查 | 天然支持 TypeScript |
| 灵活性 | 受限于模板语法 | 完整的 JS 能力 |
💡 核心理念:React 团队认为 UI 逻辑和渲染逻辑天然耦合,不如把它们放在同一个地方------JavaScript。
3.3 JSX 的 9 条语法规则
规则 1:必须有一个根元素
tsx
// ❌ 错误:返回了两个并列的元素
function App() {
return (
<h1>标题</h1>
<p>段落</p>
);
}
// ✅ 正确:用 Fragment 避免多余的 DOM 节点
function App() {
return (
<>
<h1>标题</h1>
<p>段落</p>
</>
);
}
🔑 为什么? 因为 JSX 编译后是
React.createElement(),一个函数只能返回一个值。
规则 2:用 {} 嵌入 JavaScript 表达式
花括号 {} 是 JSX 的"入口",里面可以放任何 JavaScript 表达式:
tsx
function Greeting() {
const name = "小明";
const isLoggedIn = true;
return (
<div>
<h1>你好,{name}!</h1> {/* 变量 */}
<p>2 + 3 = {2 + 3}</p> {/* 算术运算 */}
<p>{isLoggedIn ? '已登录' : '请登录'}</p> {/* 三元表达式 */}
{isLoggedIn && <p>欢迎回来!</p>} {/* 逻辑与 */}
</div>
);
}
注意:{} 里只能放表达式,不能放语句!
💡 什么是表达式 vs 语句?
表达式(✅ 可以放) 语句(❌ 不能放) count + 1if (count > 0) {}isLoggedIn ? '是' : '否'for (let i = 0; i < 10; i++) {}items.map(...)switch (type) {}fn()let x = 1
规则 3:className 代替 class
tsx
// ❌ 错误:class 是 JavaScript 保留字
<div class="container">Hello</div>
// ✅ 正确:使用 className
<div className="container">Hello</div>
来看一个真实项目代码(使用 Tailwind CSS):
tsx
<div className="flex justify-center">
<h1 className="text-3xl font-bold underline">你好,世界</h1>
</div>
💡 同样的道理:
for→htmlFor(用在<label>上)
规则 4:style 接收对象,不是字符串
tsx
// ❌ 错误
<div style="color: red; font-size: 20px;">红色大字</div>
// ✅ 正确:style 是对象,属性名用驼峰
<div style={{ color: 'red', fontSize: '20px' }}>红色大字</div>
注意: 外层 {} 是 JSX 表达式入口,内层 {} 是 JavaScript 对象字面量。
规则 5:事件处理用驼峰命名
tsx
// ❌ 错误:HTML 风格
<button onclick="handleClick()">点我</button>
// ✅ 正确:React 风格,传入函数引用
<button onClick={handleClick}>点我</button>
// ✅ 箭头函数传参
<button onClick={() => handleDelete(item.id)}>删除</button>
| HTML 事件 | React 事件 |
|---|---|
onclick |
onClick |
onchange |
onChange |
onsubmit |
onSubmit |
规则 6:必须闭合所有标签
tsx
// ✅ JSX 要求所有标签必须闭合
<img src="photo.jpg" />
<input type="text" />
<br />
规则 7:组件名必须大写开头
tsx
// ❌ 小写开头 → React 认为是 HTML 标签
function greeting() { return <h1>Hello</h1>; }
// ✅ 大写开头 → React 知道这是组件
function Greeting() { return <h1>Hello</h1>; }
规则 8:用 map 渲染列表,必须有 key
tsx
const todos = [
{ id: 1, text: '学习 JSX' },
{ id: 2, text: '写个项目' },
];
<ul>
{todos.map(todo => (
// key 必须唯一且稳定(不要用 index!)
<li key={todo.id}>{todo.text}</li>
))}
</ul>
为什么需要 key? 帮助 React 识别哪些元素改变了。用 index 作为 key 在列表增删时会导致 Bug。
规则 9:条件渲染的 3 种方式
tsx
// 方式 1:三元运算符(最常用)
<span>{status === 'active' ? '✅ 在线' : '❌ 离线'}</span>
// 方式 2:逻辑与(只渲染"真"的情况)
{isLoggedIn && <p>欢迎回来!</p>}
// 方式 3:提前 return(适合复杂逻辑)
if (status === 'loading') return <Spinner />;
if (status === 'error') return <ErrorMsg />;
return <Content />;
3.4 JSX 性能优化
很多人不知道,JSX 的写法直接影响渲染性能:
tsx
// ❌ 每次渲染都会创建新对象,导致子组件不必要的重渲染
<Child style={{ color: 'red' }} />
// ✅ 将对象提取到组件外部
const childStyle = { color: 'red' };
<Child style={childStyle} />
// ❌ 每次渲染都创建新的箭头函数
<button onClick={() => console.log('clicked')} />
// ✅ 提取为独立函数
const handleClick = useCallback(() => console.log('clicked'), []);
<button onClick={handleClick} />
💡 为什么? React 用
Object.is()比较 Props。每次渲染创建新对象/函数,引用地址不同,React 就会认为 Props 变了,触发子组件重渲染。
3.5 JSX 与 Fiber 架构
React 16 引入了 Fiber 架构。你写的每一个 JSX 标签,编译后会经历这个链路:
php
JSX 标签 → React.createElement() → React Element → Fiber 节点 → 真实 DOM
Fiber 的核心思想是可中断的渲染:
- 旧架构:递归同步渲染整棵组件树,一旦开始就无法中断
- 新架构:把渲染工作拆分成小单元(Fiber 节点),可以暂停、恢复
这意味着:JSX 写得越"可拆分"(组件化),React 的调度就越灵活。
3.6 JSX 速记卡片
| 概念 | 一句话记忆 | 代码示例 |
|---|---|---|
| JSX 本质 | JS 的语法糖,编译成 createElement | <div> → React.createElement('div') |
| 花括号 | JSX 的 JS 入口,只能放表达式 | {count}、{a ? b : c} |
| className | class 的 React 版本 | className="box" |
| style | 接收对象,属性驼峰 | style={{ color: 'red' }} |
| 组件 | 大写开头的函数 | function App() {} |
| 列表 | map + key(不要用 index) | {arr.map(i => <li key={i.id}>)} |
| 条件渲染 | 三元 / && / 提前return | {ok ? <A/> : <B/>} |
| 事件 | 驼峰命名,传函数引用 | onClick={fn} |
| 性能 | 避免内联对象/函数 | 提取到组件外或用 useMemo/useCallback |
📚 四、常见踩坑清单
| 踩坑 | 原因 | 解决方案 |
|---|---|---|
class 报错 |
JS 保留字 | 用 className |
style="color:red" 报错 |
JSX 的 style 需要对象 | style={{ color: 'red' }} |
if-else 在 JSX 里报错 |
{} 里不能放语句 |
用三元表达式或提前 return |
for 循环在 JSX 里报错 |
同上 | 用 map() |
<!-- 注释 --> 报错 |
HTML 注释在 JSX 中无效 | 用 {/* 注释 */} |
| 返回两个并列元素报错 | 没有根元素 | 用 <>...</> 包裹 |
| 组件不渲染 | 首字母没大写 | 改成大写开头 |
| 列表渲染没有 key | React 需要 key 来追踪元素 | 添加唯一 key |
💡 上篇总结
| 概念 | 一句话理解 |
|---|---|
| 组件 | 返回 JSX 的函数,大写开头 |
| JSX | JS 的语法糖,编译成 React.createElement(),花括号里只能放表达式 |
| JSX 性能 | 避免内联对象/函数,提取到组件外 |
| Fiber | JSX → Element → Fiber → DOM,组件化让调度更灵活 |
📖 下篇预告
下一篇将讲解 React 的数据流和生命周期:
- Props:父组件如何给子组件传数据(包括 TypeScript 写法)
- State :
useState的正确用法和函数式更新 - useEffect:生命周期的终极答案(依赖数组、清理函数、竞态条件)
- 事件处理 + 列表渲染
- 实战:完整的待办事项 App
👉 关注我,下篇不迷路!
🔗 参考资料
💬 交流讨论
你在学 JSX 的过程中踩过什么坑?欢迎在评论区分享!
觉得有用?点个赞👍收藏⭐关注👆,下篇继续讲 React 生命周期和实战!