07 — React 是什么与 JSX

这一章是整个 React 学习的核心。
我会从问题出发讲:为什么要用 React → JSX 是什么 → 第一个组件。


1. 没有 React 时,你是怎么写网页的?

假设要做一个简单的「聊天输入框」。

1.1 纯 HTML + JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- index.html -->
<div id="app"></div>
<button id="btn">点击我</button>
<p id="count">0</p>

<script>
let count = 0;
const btn = document.getElementById('btn');
const countEl = document.getElementById('count');

btn.addEventListener('click', () => {
count++;
countEl.textContent = count; // 手动更新 DOM
});
</script>

问题

  1. 命令式:每改一次数据,要手动写代码改 DOM
  2. 难维护:UI 复杂时,DOM 操作散落各处
  3. 难复用:想在不同地方复用同样 UI,得复制粘贴 HTML
  4. 没有组件概念:所有代码混在一个文件里

1.2 jQuery 时代(曾经的解决方案)

1
$('#count').text(count);            // 用 jQuery 简化 DOM 操作

但还是命令式,逻辑和 DOM 操作分离不干净。

1.3 React 的解法

核心转变:声明式 UI

1
2
3
4
5
6
7
8
9
10
function App() {
const [count, setCount] = useState(0);

return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

你只声明「UI 应该长什么样」(数据 → UI 的映射),React 自动帮你更新 DOM


2. React 是什么?(从外到内看)

2.1 三层定义

层级定义
比喻UI 的 Excel 表格:你只改数据(state),UI 自动跟着变
功能把「数据」渲染成「HTML」,并在数据变化时自动重新渲染
实质一个 JavaScript 库(import React from 'react'

2.2 React 的核心三个概念

1
2
3
1. 组件 (Component)     — UI 的可复用单元
2. Props — 组件的输入参数
3. State — 组件内部会变化的数据

整本书就讲这三件事 + 它们的组合方式。


3. 第一个 React 程序(跟着写一遍)

3.1 创建 App.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// App.tsx
import { useState } from 'react';

function App() {
// 1. 声明一个会变化的数据
const [count, setCount] = useState(0);

// 2. 声明 UI 长什么样
return (
<div>
<h1>计数器</h1>
<p>当前数字:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}

export default App;

3.2 在 main.tsx 中挂载

1
2
3
4
5
6
7
8
9
10
// main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

3.3 发生了什么?

  1. 浏览器加载 index.html
  2. 加载 main.tsx
  3. createRoot(...).render(<App />) 把 App 组件渲染到 <div id="root">
  4. React 调用 App() 函数,得到返回的 JSX
  5. React 把 JSX 转成真实 DOM,塞进 #root
  6. 用户点按钮 → setCount(count + 1) → 状态变了
  7. React 自动重新调用 App(),得到新的 JSX
  8. React 自动对比新旧 JSX,只更新变化的部分(这里只更新 <p> 的文字)
  9. 浏览器看到新的 DOM

关键洞察:你只写了「数据怎么变」(setCount),没写「DOM 怎么变」。React 自动帮你做了。


4. JSX 是什么?

4.1 一句话

JSX 是「在 JavaScript 里写 HTML」的语法糖

1
2
const element = <h1>Hello, world!</h1>;
// ↑ 这是 JSX

浏览器不认识 JSX。Vite/TypeScript 编译时把它转成 React.createElement(...) 调用:

1
2
3
4
// 编译后:
const element = React.createElement('h1', null, 'Hello, world!');
// 返回的是一个「虚拟 DOM 对象」:
// { type: 'h1', props: { children: 'Hello, world!' } }

4.2 JSX vs HTML:5 个关键区别

区别 1:classclassName

1
2
3
4
5
// ❌ 错的(class 是 JS 关键字)
<div class="container">

// ✅ 对的
<div className="container">

区别 2:所有标签必须闭合

1
2
3
4
5
6
7
8
9
// HTML 里可以这样:
<input type="text">
<br>
<img src="...">

// JSX 里必须自闭合或成对闭合:
<input type="text" />
<br />
<img src="..." />

区别 3:用 {} 插入 JavaScript 表达式

1
2
3
4
5
6
7
8
9
const name = '豆豆';
const count = 3;

<div>
<h1>{name}</h1> {/* 变量 */}
<p>{count + 1}</p> {/* 表达式 */}
<p>{count > 5 ? '大' : '小'}</p> {/* 条件 */}
<p>{[1, 2, 3].map(x => x * 2)}</p> {/* 任意 JS 表达式 */}
</div>

⚠️ {} 里只能放表达式,不能放语句(如 ifforfunction() {...})。

1
2
3
4
5
6
7
8
// ❌ 错的:if 语句
<div>{if (a) { return 'x' }}</div>

// ✅ 对的:用三元
<div>{a ? 'x' : ''}</div>

// ✅ 对的:用立即执行函数(不优雅)
<div>{(() => { if (a) return 'x'; return ''; })()}</div>

区别 4:事件名是驼峰式

1
2
3
4
5
6
7
// HTML
<button onclick="handleClick()">

// JSX(驼峰,函数值,不是字符串)
<button onClick={handleClick}>
<input onChange={(e) => setValue(e.target.value)} />
<form onSubmit={handleSubmit}>

区别 5:style 接受对象

1
2
3
4
5
6
7
// HTML
<div style="color: red; background: blue;">

// JSX(驼峰,JS 对象)
<div style={{ color: 'red', background: 'blue' }}>
// ↑↑ 外层 {} 是「插入 JS 表达式」
// ↑↑ 内层 {} 是对象字面量

4.3 JSX 必须有根节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ❌ 错的:两个根元素
return (
<h1>标题</h1>
<p>内容</p>
);

// ✅ 对的:用 div 包裹
return (
<div>
<h1>标题</h1>
<p>内容</p>
</div>
);

// ✅ 也对:用 Fragment(不会渲染额外节点)
return (
<>
<h1>标题</h1>
<p>内容</p>
</>
);

项目里基本都用 Fragment <>...</>,避免无意义的 div 嵌套。

4.4 JSX 中的注释

1
2
3
4
5
{/* 这是 JSX 注释 */}

{
// 也可以这样(用 JS 注释语法)
}

5. React.createElement 是什么?(理解 JSX 的本质)

JSX 是语法糖,本质是 React.createElement(type, props, ...children)

1
2
3
<button onClick={handleClick} className="btn">
点击 <strong>这里</strong>
</button>

等价于:

1
2
3
4
5
6
React.createElement(
'button', // 标签名
{ onClick: handleClick, className: 'btn' }, // 属性对象
'点击 ', // 子节点 1(字符串)
React.createElement('strong', null, '这里'), // 子节点 2(嵌套元素)
);

返回的是一个普通对象(叫「React 元素」或「虚拟 DOM」):

1
2
3
4
5
6
7
8
{
type: 'button',
props: {
onClick: handleClick,
className: 'btn',
children: ['点击 ', { type: 'strong', props: { children: '这里' } }],
},
}

React 维护了一棵这样的对象树(虚拟 DOM)
当数据变化时,React 用 diff 算法对比新旧树,找出真正需要改动的部分,更新到真实 DOM。


6. 虚拟 DOM 是什么?(概念性了解)

虚拟 DOM = 用 JS 对象描述的「DOM 长什么样」

1
2
3
4
5
// 真实 DOM(浏览器渲染的)— 复杂、有大量属性
<button class="btn">点击</button>

// 虚拟 DOM(JS 对象)— 简洁
{ type: 'button', props: { className: 'btn', children: '点击' } }

为什么需要虚拟 DOM?

直接操作真实 DOM 很慢(每次改动都触发浏览器重排/重绘)。
React 用虚拟 DOM 做中间层:

  1. 数据变了,生成新的虚拟 DOM 树
  2. 对比新旧树(diff 算法)
  3. 只把变化的部分应用到真实 DOM(最小化更新)

7. 项目里第一个组件:main.tsx + App.tsx

7.1 main.tsx — 应用入口

1
2
3
4
5
6
7
8
9
10
11
12
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './styles/global.css'; // 全局样式
import './styles/tokens.css'; // CSS 变量
import 'highlight.js/styles/github-dark.css';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);

逐行解释

  • import App from './App':导入根组件
  • import './styles/global.css':导入全局 CSS(Vite 会处理)
  • ReactDOM.createRoot(...):创建一个 React 根
  • .render(<App />):把 App 组件渲染进去
  • ! 非空断言(告诉 TS root 元素一定存在)
  • <React.StrictMode>:开发模式下的额外检查(生产环境无效果)

7.2 App.tsx — 根组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import { useEffect } from 'react';
import { Sidebar } from './components/Sidebar/Sidebar';
import { MessageList } from './components/MessageList/MessageList';
import { InputArea } from './components/InputArea/InputArea';
import { ConfirmDialog } from './components/ConfirmDialog/ConfirmDialog';
import { ConnectionError } from './components/ConnectionError/ConnectionError';
import { useChatStore } from './store/chatStore';
import styles from './App.module.css';

export default function App() {
const testConnection = useChatStore((s) => s.testConnection);
const loadChats = useChatStore((s) => s.loadChats);
const createNewChat = useChatStore((s) => s.createNewChat);

useEffect(() => {
(async () => {
await testConnection();
await loadChats();
createNewChat();
})();
}, [testConnection, loadChats, createNewChat]);

return (
<>
<div className="bg-blob bg-blob-1" />
<div className="bg-blob bg-blob-2" />
<div className={styles.app}>
<Sidebar />
<div className={styles.main}>
<div className={styles.top} />
<div className={styles.body}>
<MessageList />
<InputArea />
</div>
</div>
</div>
<ConfirmDialog />
<ConnectionError />
</>
);
}

逐行解释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 1. 导入 React 的 useEffect(后面讲)
import { useEffect } from 'react';

// 2. 导入其他组件(项目里用 ./components/... 相对路径 或 @/ 别名)
import { Sidebar } from './components/Sidebar/Sidebar';
// ↑ import { 名字 } from '路径' (Sidebar 是命名导出,不是 default)

// 3. 导入 Zustand store(全局状态)
import { useChatStore } from './store/chatStore';

// 4. 导入 CSS Module
import styles from './App.module.css';
// ↑ styles.item、styles.main 等

// 5. 函数组件(默认导出)
export default function App() {
// 5.1 从 Store 取数据
const testConnection = useChatStore((s) => s.testConnection);
// ↑ useChatStore 是 Hook,s 是整个 store 状态
// ↑ (s) => s.testConnection 是「选择器」:只取 testConnection 这一个字段

// 5.2 useEffect — 副作用(应用启动时执行一次)
useEffect(() => {
(async () => {
await testConnection(); // 测试连接
await loadChats(); // 加载会话列表
createNewChat(); // 新建会话
})();
}, [testConnection, loadChats, createNewChat]);
// ↑ 依赖数组:这些函数不变就不重新执行
// ↑ 因为这三个是 Store 提供的函数,引用稳定,所以这里其实只执行一次

// 5.3 返回 JSX
return (
<>
<div className="bg-blob bg-blob-1" /> {/* 背景光晕 1 */}
<div className="bg-blob bg-blob-2" /> {/* 背景光晕 2 */}
<div className={styles.app}> {/* 应用容器 */}
<Sidebar /> {/* 侧边栏 */}
<div className={styles.main}> {/* 主区域 */}
<div className={styles.top} /> {/* 顶部装饰条 */}
<div className={styles.body}> {/* 主内容区 */}
<MessageList /> {/* 消息列表 */}
<InputArea /> {/* 输入区域 */}
</div>
</div>
</div>
<ConfirmDialog /> {/* 全局确认弹窗 */}
<ConnectionError /> {/* 全局错误提示 */}
</>
);
}

8. 项目里所有 JSX 写法的「说明书」

JSX 写法含义项目里例子
<div className="x">普通元素<div className="bg-blob" />
<Component />自闭合组件<Sidebar /><MessageList />
<Component>...</Component>有子内容的组件(少)
<div className={styles.x}>CSS Module<div className={styles.app}>
<button onClick={fn}>点击事件<button onClick={createNewChat}>
{condition && <X/>}条件渲染{isSending && <StopBtn />}
{items.map(item => ...)}列表渲染{chatList.map(c => <ChatItem ... />)}
{value}插入 JS<span>{chat.title}</span>
<>...</>Fragment(无根节点)App.tsx 的根

9. 一段话总结

React = 用 JS 写 UI、自动更新 DOM。
JSX = 在 JS 里写 HTML 的语法糖,编译成 React.createElement
组件 = 返回 JSX 的函数。
核心思路:你只声明 UI 长什么样,数据变 → UI 自动更新。


接下来

你已经理解 React 的核心思想。接下来: