02 — HTML 基础回顾

你说你有 HTML 基础。本章不重复教 HTML,而是把项目里用到的、可能你没接触过的语法挑出来讲。
大约 5-10 分钟读完。


1. 入口 HTML:项目里的 index.html

文件agent/dodo-agent-frontend/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>豆豆 · 智能助理</title>
<meta name="theme-color" content="#0c0b09" />
<meta name="color-scheme" content="dark" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Sora..." rel="stylesheet" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,..." />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

重点解读

<div id="root"> — React 的挂载点

整个 React 应用最终会被「塞」进这个 div 里。
你看到的页面所有内容,其实都是 JS 动态生成的。

1
2
3
<body>
<div id="root"></div> <!-- 空壳 -->
</body>
1
2
3
// src/main.tsx
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);
// ↑ 把根组件 App 渲染到 #root 里

<script type="module" src="/src/main.tsx">

  • type="module" 表示这是 ES Module(现代 JS 模块)
  • 入口是 /src/main.tsx,不是 .js
  • 这是开发时的写法,Vite 会处理它
  • 浏览器里看到的 type="module" 行为:
    • 自动启用 import / export
    • 自动 defer(DOM 解析完才执行)

③ 动态 favicon:SVG 写在 href

1
2
<link rel="icon" type="image/svg+xml"
href="data:image/svg+xml,<svg xmlns=...><text>🌱</text></svg>" />

不需要单独的 .ico 文件,浏览器直接解析 data URL。


2. 项目里 React 组件最终生成什么 HTML?

React 组件返回的 JSX 会被转成真实的 DOM。例如:

1
2
3
4
5
6
7
// InputArea.tsx(简化)
<div className="area">
<textarea placeholder="输入..." />
<button>
<Send size={16} />
</button>
</div>

最终在浏览器里渲染的 HTML:

1
2
3
4
5
6
7
8
9
10
<div class="area">
<textarea placeholder="输入..."></textarea>
<button>
<!-- lucide-react 的 Send 组件渲染成: -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="22" y1="2" x2="11" y2="13"></line>
<polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
</svg>
</button>
</div>

💡 关键认知:React 写的不是 HTML,最终产物才是 HTML。开发体验是 JSX,但浏览器看到的是真实 DOM。


3. 项目里用到但比较特殊的 HTML 元素

3.1 <input type="file"> — 上传文件

1
2
3
4
5
6
7
8
9
10
// InputArea.tsx(简化)
<input
ref={fileInputRef}
type="file" <!-- 选一个本地文件 -->
onChange={onPickFile} <!-- 选中后触发 -->
style={{ display: 'none' }} <!-- 隐藏掉,自己控制点击 -->
/>
<button onClick={() => fileInputRef.current?.click()}>
上传
</button>

关键点:

  • <input type="file"> 长得丑,所以通常用 display: none 隐藏
  • 通过 ref 调用它的 click() 方法,触发文件选择对话框
  • onChange 中通过 e.target.files 拿到选中的文件
  • HTML 原生 input 受浏览器安全限制,没法「自动赋值」(要选文件才行)

3.2 <textarea> — 多行文本

1
2
3
4
5
6
7
8
<textarea
ref={textareaRef}
value={inputMessage} <!-- 受控:由 React state 决定 -->
onChange={(e) => setInput(e.target.value)}
onKeyDown={onKeyDown}
placeholder="输入消息..."
rows={1} <!-- 默认显示 1 行 -->
/>

要点:

  • 受控组件:用 value + onChange 配合 React state
  • e.target.value 获取当前内容
  • rows={1} + CSS 控制 max-height + JS 自动计算 = 自适应高度

3.3 <button type="button">

1
<button type="button" onClick={...}>...</button>

项目里每个 button 都显式写了 type="button"
原因:HTML 中 <button> 默认 type="submit",在 <form> 内点击会触发表单提交!
加上 type="button" 就只是普通按钮,不会提交表单。


4. 一些不写在 JSX 里但项目里用到的「属性」

4.1 data-* 属性(自定义数据)

HTML 允许你写 data- 开头的任意属性:

1
<div data-agent-id={chat.id} data-active={active}>

可以保存一些数据,但本项目没怎么用,因为 React 用 props 就够了。

4.2 tabIndex 控制 Tab 键顺序(a11y)

1
<button tabIndex={0}>...</button>

4.3 事件修饰符(HTML 没有这个,是 React 的)

React 事件没用 addEventListener,而是「驼峰命名 + 函数值」:

1
2
3
4
5
// ❌ HTML 原生写法
<button onclick="handleClick()"> <!-- 错!React 不是这个 -->

// ✅ React 写法
<button onClick={handleClick}> <!-- 驼峰,函数值 -->

5. 本项目 HTML 部分的总结

项目特点说明
入口 HTML 极简只有 <div id="root">,其他全部由 React 生成
没有路由SPA(单页应用),所有内容都是 React 渲染的
没有 <form>项目用自定义的 input + Enter 键触发
表单元素用得少只有 <textarea><input type="file">
所有 <button> 都加 type="button"防止意外提交

6. 一段话总结

React 项目里,HTML 文件只是个壳子。真正的页面结构是组件树生成的。
你看到的 HTML 知识主要是:

  1. <input type="file"> + ref 控制文件选择
  2. <textarea> + 受控组件
  3. <button type="button"> 的重要性

接下来

你已经熟悉 HTML 语法在 React 中的样子。
继续阅读 03-CSS基础回顾.md,了解本项目 CSS 中可能不熟悉的语法。