导读(Introduction)
欢迎来到 Apache Airflow 源码深度解析系列的第十八课。
在前面的课程中,我们深入学习了 Airflow 后端的核心系统——从 REST API 架构到数据库模型,再到序列化系统。这些后端基础设施最终都服务于一个目标:为用户提供直观、高效的操作界面。本课将从后端转向前端,探索 Airflow 3.x 全新重写的 Web UI 是如何构建的。
Airflow 3.x 的 UI 经历了一次完全的架构重构。从 Airflow 2.x 时代基于 Flask + Jinja2 模板的服务端渲染(SSR)架构,演进为现代的React + TypeScript + Vite单页应用(SPA)架构。这次重构不仅带来了更流畅的用户体验,还建立了更清晰的前后端分离边界——前端通过 OpenAPI 自动生成的客户端与后端 REST API 通信,实现了类型安全的端到端数据流。
本课将系统地剖析这套前端架构:从构建工具链到组件设计模式,从路由体系到数据查询层,从主题系统到国际化方案。通过阅读真实的源代码,你将理解一个工业级 React 应用是如何组织和运作的。
学习目标(Learning Objectives)
完成本课学习后,你将能够:
- 掌握 Airflow UI 技术栈——理解 React 19 + TypeScript + Vite 8 + Chakra UI 3 的技术选型逻辑
- 理解 OpenAPI 自动生成客户端机制——从后端 API 定义到前端类型安全调用的完整链路
- 掌握页面架构与路由设计——嵌套路由、Layout 组件、Tab 导航的实现模式
- 理解数据查询层设计——TanStack React Query 的缓存策略、自动刷新和乐观更新
- 理解 UI 组件化设计——原子组件、复合组件、业务组件的分层模式
- 掌握主题系统与设计令牌——Chakra UI 的 Token 体系和动态主题切换
正文内容(Main Content)
1. Airflow UI 技术栈:React + TypeScript + Vite
1.1 技术选型概览
Airflow 3.x 前端采用了现代 Web 开发的主流技术栈:
| 技术 | 版本 | 职责 |
|---|---|---|
| React | 19.x | UI 框架,组件化渲染 |
| TypeScript | 5.9 | 类型安全,编译期错误检查 |
| Vite | 8.x | 构建工具,开发服务器,HMR |
| Chakra UI | 3.x | 组件库,设计系统 |
| TanStack React Query | 5.x | 服务端状态管理,数据缓存 |
| React Router | 7.x | 客户端路由 |
| Axios | 1.x | HTTP 客户端 |
| Zustand | 5.x | 客户端状态管理 |
| i18next | 25.x | 国际化 |
| @xyflow/react | 12.x | DAG 图形可视化 |
| Chart.js | 4.x | 数据图表 |
| Vitest | 4.x | 单元测试 |
| Playwright | 1.x | 端到端测试 |
这套技术栈的选择反映了几个关键设计原则:
- 类型安全优先:TypeScript + OpenAPI 生成确保前后端接口类型一致
- 性能导向:Vite 的即时 HMR、React 19 的编译器优化、虚拟滚动
- 开发体验:热重载、自动代码生成、丰富的开发工具链
- 可维护性:组件化、设计系统、规范化的代码组织
1.2 项目结构
前端代码位于airflow-core/src/airflow/ui/,目录结构如下:
airflow-core/src/airflow/ui/ ├── package.json # 依赖管理与脚本定义 ├── vite.config.ts # Vite 构建配置 ├── tsconfig.app.json # TypeScript 配置 ├── openapi-gen/ # OpenAPI 自动生成的客户端代码 │ ├── queries/ # React Query hooks │ │ ├── queries.ts # useQuery hooks │ │ ├── common.ts # 共用类型与 queryKey 生成 │ │ ├── suspense.ts # Suspense 版本 │ │ └── prefetch.ts # 预取数据 │ └── requests/ # API 请求层 │ ├── services.gen.ts # Service 类(API 方法) │ ├── types.gen.ts # TypeScript 类型定义 │ └── core/ # 请求基础设施 ├── src/ │ ├── main.tsx # 应用入口 │ ├── router.tsx # 路由配置 │ ├── theme.ts # 主题系统 │ ├── queryClient.ts # React Query 客户端配置 │ ├── i18n/ # 国际化配置 │ ├── context/ # React Context(主题、时区等) │ ├── layouts/ # 布局组件 │ │ ├── BaseLayout.tsx # 基础布局(导航 + 内容区) │ │ ├── DagsLayout.tsx # DAG 列表布局 │ │ ├── Nav/ # 导航栏组件 │ │ └── Details/ # 详情页布局 │ ├── pages/ # 页面组件 │ │ ├── Dashboard/ # 仪表板 │ │ ├── DagsList/ # DAG 列表 │ │ ├── Dag/ # DAG 详情 │ │ ├── Run/ # DagRun 详情 │ │ ├── TaskInstance/ # 任务实例详情 │ │ └── ... # 其他页面 │ ├── components/ # 通用组件 │ │ ├── DataTable/ # 数据表格 │ │ ├── Graph/ # 图形组件(DAG 可视化) │ │ ├── TriggerDag/ # DAG 触发器 │ │ └── ui/ # 基础 UI 组件 │ ├── queries/ # 自定义 Query hooks │ └── utils/ # 工具函数 └── tests/ # 测试文件1.3 Vite 构建配置
// airflow-core/src/airflow/ui/vite.config.tsimportbabelfrom"@rolldown/plugin-babel";importreact,{reactCompilerPreset}from"@vitejs/plugin-react";importcssInjectedByJsPluginfrom"vite-plugin-css-injected-by-js";import{defineConfig}from"vitest/config";exportdefaultdefineConfig({base:"./",build:{chunkSizeWarningLimit:1600,manifest:true},optimizeDeps:{exclude:["@guanmingchiu/sqlparser-ts"],// WASM 包需要排除预打包},plugins:[react(),babel({presets:[reactCompilerPreset()],// React Compiler 优化}),// 替换路径以配合 Flask 静态文件服务{name:"transform-url-src",transformIndexHtml:(html)=>html.replace(`src="./assets/`,`src="./static/assets/`).replace(`href="/`,`href="./`),},cssInjectedByJsPlugin(),// CSS 注入到 JS 中],resolve:{alias:{openapi:"/openapi-gen",src:"/src"}},server:{cors:true,// 开发服务器允许跨域proxy:{"/hitl-review":{changeOrigin:true,target:"http://localhost:28080",},},},test:{environment:"happy-dom",globals:true,setupFiles:"./testsSetup.ts",},});关键配置解析:
base: "./":使用相对路径,适配部署在子路径下的场景- React Compiler:通过 Babel 插件启用 React Compiler,自动优化组件渲染
- 路径别名:
openapi指向 OpenAPI 生成代码,src指向源码目录 - CSS 注入:使用
cssInjectedByJsPlugin将 CSS 打包到 JS 中,简化部署 - 代理配置:开发时将 HITL(Human-In-The-Loop)请求代理到本地服务
1.4 package.json 脚本
{"scripts":{"dev":"vite --port 5173 --strictPort","build":"vite build","lint":"eslint --quiet && tsc --p tsconfig.app.json","codegen":"openapi-merge-cli && openapi-rq -i openapi.merged.json -c axios --format prettier -o openapi-gen --operationId","test":"vitest run","test:e2e":"playwright test"}}codegen脚本是前后端集成的关键:它合并 OpenAPI 规范文件,然后自动生成类型安全的 React Query hooks。
2. 前后端交互:OpenAPI 自动生成客户端
2.1 代码生成流程
Airflow 前端的 API 调用代码不是手写的,而是从后端 OpenAPI 规范自动生成的。这确保了前后端接口的类型安全和同步更新:
┌─────────────────┐ openapi-merge-cli ┌─────────────────────┐ │ 后端 FastAPI │ ──────────────────────> │ openapi.merged.json │ │ 自动生成 OpenAPI │ │ (合并后的规范文件) │ └─────────────────┘ └─────────────────────┘ │ openapi-rq (codegen) │ ▼ ┌─────────────────────────────┐ │ openapi-gen/ │ │