React + WebGPU(二):合成事件与进度条组件封装

摘要:从DOM事件演进到React合成事件,onClick触发模型下载,setStatus驱动loading态,map遍历文件生成进度条列表,Progress子组件接收props展示每个文件的下载进度。一步步让页面活起来。


目录

  1. [从原生事件到 React 合成事件](#从原生事件到 React 合成事件 "#%E4%B8%80%E4%BB%8E%E5%8E%9F%E7%94%9F%E4%BA%8B%E4%BB%B6%E5%88%B0-react-%E5%90%88%E6%88%90%E4%BA%8B%E4%BB%B6")
  2. [Load Model 按钮:onClick 触发状态切换](#Load Model 按钮:onClick 触发状态切换 "#%E4%BA%8Cload-model-%E6%8C%89%E9%92%AEonclick-%E8%A7%A6%E5%8F%91%E7%8A%B6%E6%80%81%E5%88%87%E6%8D%A2")
  3. [map 列表渲染:让 Vue 开发者秒懂的 React 哲学](#map 列表渲染:让 Vue 开发者秒懂的 React 哲学 "#%E4%B8%89map-%E5%88%97%E8%A1%A8%E6%B8%B2%E6%9F%93%E8%AE%A9-vue-%E5%BC%80%E5%8F%91%E8%80%85%E7%A7%92%E6%87%82%E7%9A%84-react-%E5%93%B2%E5%AD%A6")
  4. [Progress 组件:抽离、传参、复用](#Progress 组件:抽离、传参、复用 "#%E5%9B%9Bprogress-%E7%BB%84%E4%BB%B6%E6%8A%BD%E7%A6%BB%E4%BC%A0%E5%8F%82%E5%A4%8D%E7%94%A8")
  5. 总结

一、从原生事件到 React 合成事件

第一篇搭好了静态页面骨架------标题、描述文字、模型介绍、空白的进度条区域。这一篇要让页面动起来:点击按钮 → 触发下载 → 进度条实时更新。

这一切的起点是事件处理 。在 React 里,按钮的点击事件写作 onClick,这背后有一段 DOM 事件标准的演进史。

DOM 事件的三代演化

html 复制代码
<!-- 第 0 代:内联属性 onclick -->
<button id="btn" onclick="console.log('点击')">按钮</button>

DOM 0 级事件直接写在 HTML 属性里,JS 与 HTML 耦合在一起,不符合模块化分离原则。

javascript 复制代码
// 第 2 代:addEventListener(DOM 2 级标准)
document.getElementById('btn')
    .addEventListener('click', function() {
        console.log('点击了按钮');
    });

addEventListener 将 JS 逻辑从 HTML 中分离出来,而且支持同一个元素多次监听同一个事件------这是 DOM 0 做不到的。

flowchart LR A[DOM 0<br/>onclick 属性] --> B[DOM 2<br/>addEventListener] B --> C[React<br/>onClick 合成事件] A ---|JS与HTML耦合| A B ---|分离但需手动管理| B C ---|自动委托 跨浏览器兼容| C

React 的取舍:不发明新概念

Vue 用 @clickv-on:click 这类自定义语法绑定事件。React 选择了另一条路------直接用 JS 里已有的概念onClick 就是 HTML 的 onclick 的驼峰写法,对有 JS 基础的开发者没有额外的学习成本。

但 React 的 onClick 不是原生 DOM 事件 。它是合成事件(Synthetic Event)------React 在底层将原生事件包装了一层:

arduino 复制代码
用户点击 → 原生 click 事件 → React 事件系统(事件委托) → 合成事件对象 → 你的 onClick 回调

合成事件做了三件事:跨浏览器兼容(抹平不同浏览器的差异)、事件委托(不在每个元素上单独绑定,统一在根节点管理)、自动清理(组件卸载时无需手动 removeEventListener)。


二、Load Model 按钮:onClick 触发状态切换

理解了事件机制,来看 App 组件的 Load Model 按钮------整个页面的"启动键":

tsx 复制代码
<button
    className="border px-4 py-2 rounded-lg bg-blue-400
               text-white hover:bg-blue-500 disabled:cursor-not-allowed
               select-none"
    disabled={status !== null || error !== null}
    onClick={() => {
        setStatus("loading");
    }}
>
    Load Model
</button>

这一个按钮包含了交互设计的三个关键细节:

disabled 状态控制

tsx 复制代码
disabled={status !== null || error !== null}
场景 status error disabled 效果
初始状态 null null false 按钮可点击
下载中 "loading" null true 按钮灰色,不可点击
下载完成 "ready" null true 按钮灰色,模型已就绪
下载失败 null "出错" true 按钮灰色,先处理错误

disabled:cursor-not-allowed 原子类在按钮禁用时将鼠标变为禁止样式------视觉上明确告知用户"现在不能点"。防止用户在模型下载过程中重复点击引发多个并发的下载请求。

onClick 的状态切换

tsx 复制代码
onClick={() => {
    setStatus("loading");
}}

整个页面的状态流转由这一行启动:

flowchart LR A[点击Load Model] --> B[onClick 触发] B --> C[setStatus loading] C --> D[React 重渲染] D --> E[status == loading] E --> F[显示进度条区域] E --> G[按钮disabled变灰] E --> H[useEffect 触发下载]

第一篇讲过 React 的"数据驱动视图"------setStatus("loading") 就是一个完美的例子。你不需要写 document.getElementById("progress").style.display = "block" 这样的 DOM 操作。你只需要修改一个状态变量,React 自动算出哪些 UI 需要更新。


三、map 列表渲染:让 Vue 开发者秒懂的 React 哲学

模型文件不是一个,而是一组------多个 .onnx 权重文件需要逐个下载。每个文件都需要独立的进度条。这种"数组 → 多个 UI 元素"的场景,Vue 用 v-for,React 用什么?

答案是:不用任何自定义指令。直接用 JS 原生方法 map

tsx 复制代码
{
    status === "loading" && (
        <div className="w-full max-w-[500px] text-left mx-auto p-4">
            <p className="text-center mb-1">{loadingMessage}</p>

            {progressItems.map(({ text, percentage, total }) => (
                <Progress
                    text={text}
                    percentage={percentage}
                    total={total}
                />
            ))}
        </div>
    )
}

progressItems 是一个对象数组:

typescript 复制代码
const [progressItems, setProgressItems] = useState([
    { text: 'model.onnx',    percentage: 0,  total: 34353543453 },
    { text: 'model2.onnx',   percentage: 10, total: 14353543453 },
]);

map 遍历这个数组,每一项都变成一个 <Progress/> 组件------JSON 数组直接被"映射"为 JSX 数组。 Vue 的 v-for 是模板语法,React 的 map 是纯 JS 逻辑,不需要学习额外的指令。

双层条件渲染

这里出现了第一篇提过的 && 短路表达式,而且是嵌套条件

ini 复制代码
status === "loading"          ← 外层条件:只有 loading 状态才渲染进度区域
    ↓
progressItems.map(...)        ← 内层循环:每个文件一个 Progress 组件

两层组合起来的效果:点击 Load Model → status 变为 loading → 进度条区域出现 → progressItems 数组动态更新 → map 自动重新渲染每个 Progress 组件。不需要手动增删 DOM 节点。


四、Progress 组件:抽离、传参、复用

进度条不是 App 的内部逻辑------它是可复用的独立业务模块。把它从 App 中抽离为一个独立组件,符合"组件作为开发最小单元"的前端发展趋势。

子组件定义

tsx 复制代码
const Progress = ({ text, percentage, total }) => {
    percentage ??= 0;
    return (
        <div>
            <p>{text}</p>
            <p>{percentage}%</p>
            <p>{total}</p>
        </div>
    );
};

export default Progress;

三个关键语法:

语法 含义 说明
{ text, percentage, total } 解构 props 从父组件传入的属性对象中一次性提取三个字段
percentage ??= 0 空值合并赋值 如果 percentagenullundefined,赋默认值 0
返回 JSX 组件 = 函数 函数接收 props,返回 UI 描述

父子组件数据流

flowchart LR A[&#34;App 父组件<br/>progressItems 数组&#34;] -->|map 遍历| B[&#34;Progress 子组件<br/>item 1<br/>text/model1.onnx<br/>percentage/0&#34;] A -->|map 遍历| C[&#34;Progress 子组件<br/>item 2<br/>text/model2.onnx<br/>percentage/10&#34;] B --> D[&#34;渲染: model.onnx 0%&#34;] C --> E[&#34;渲染: model2.onnx 10%&#34;]

父组件 (App) 拥有数据源 progressItems,通过 props 将每条数据注入子组件。子组件 (Progress) 只负责根据 props 渲染 UI,不关心数据从哪来------这就是数据单向流动

组件树:从 DOM 树到组件树

传统页面的结构靠 DOM 树表达。React 页面的结构靠的是组件树

scss 复制代码
App
 ├── h1 (标题)
 ├── h2 (副标题)
 ├── p  (模型介绍)
 ├── button (Load Model)
 ├── error 条件渲染
 └── loading 条件渲染
      ├── Progress (model.onnx)
      └── Progress (model2.onnx)

组件树一眼能看出页面的构成:组件化程度、嵌套层级、数据传递路径。页面越复杂,组件树的价值越大------团队协作靠组件拆分,代码维护靠组件边界。


五、总结

从一篇的静态骨架到这一篇的动态交互,三个核心机制让页面"活"了起来:

  1. React 合成事件onClick 不是原生事件,是跨浏览器兼容、事件委托、自动清理的包装层。React 不发明新概念,onClick 就是 onclick 的驼峰版。
  2. onClick → setStatus :一个点击 → 状态变更 → 重渲染 → UI 自动更新。disabled 防止重复点击,loading 状态驱动进度条区域显示,不需要任何 document.getElementById
  3. map 列表渲染 :Vue 用 v-for,React 直接用 JS 原生 map。JSON 数组 → JSX 数组,不引入额外指令。
  4. Progress 组件抽离 :函数接收 props 返回 JSX = 组件。??= 做默认值兜底。父组件持有数据,子组件接收渲染,数据单向流动。
  5. 组件树 > DOM 树:页面结构通过组件层级表达,一眼看懂数据流向和嵌套关系。组件是开发的最小单元------团队好协作、好复用、好维护。

第一篇把模型介绍和下载链路搭起来,这一篇把点击、进度、状态切换装上去。下一篇,LLM 该开始真正"说话"了。


------ 事件触发状态,状态驱动渲染,组件树承载一切。

相关推荐
我是大卫1 小时前
【图】React源码解析-从底层数据结构、渲染调度到闭包原理深挖useRef的核心机制
前端·react.js·源码
何时梦醒1 小时前
# ⚛️ React 19 + TypeScript 深度学习笔记 —— 从组件化思维到 WebGPU 端侧 AI 落地(续)
人工智能·react.js
光影少年1 小时前
RN导航与路由
前端·react native·react.js
smallcelebration1 小时前
139 @vueuse/core依赖介绍使用
前端·javascript·vue.js
蒜苔肉丝1 小时前
Vue3 动态表单:模块复用 + 动态增删行 + 数据回显
前端·javascript·vue.js
Cxiaomu2 小时前
从 TRTC Demo 到独立音视频服务:React + TRTC Web SDK 服务化实践
前端·react.js·音视频
索西引擎2 小时前
【React】Redux 中间件机制:副作用处理与数据流增强的形式化分析
前端·react.js·中间件
寒草2 小时前
「寒草呈献」工作六年,是否仍有创造未来的勇气 ✨
前端·后端
HackTwoHub3 小时前
解锁 AI 红队全新玩法!Claude-Red 攻防 Skill 库,内置 SQLi、XXE、文件上传等 Web 专项 Skill,一键导入快速落地渗透实战
前端·人工智能·web安全·网络安全·自动化·系统安全