【OpenHarmony/HarmonyOs 】从 React 原型迁移到 ArkUI:组件、状态、动画与导航如何对应
【OpenHarmony/HarmonyOs 】从 React 原型迁移到 ArkUI:组件、状态、动画与导航如何对应
前言
LinkOS 项目同时保留了 React + TypeScript + Vite 原型和 HarmonyOS ArkTS 原生实现。这提供了一个很有代表性的迁移案例:产品结构和交互意图可以复用,但组件、生命周期、导航、资源和平台能力不能机械翻译。本文给出一套迁移方法。🔄
一、两套技术栈
Web 原型主要使用:
React 18 + TypeScript + Vite
Tailwind CSS + Motion + Lucide React + Sonner
HarmonyOS 原生实现使用:
ArkTS + ArkUI + Stage 模型
UIAbility + Preferences + ArkWeb + Want
Web 版本擅长快速验证视觉和交互,ArkUI 版本负责系统级能力、原生生命周期、多设备和应用分发。
二、组件对应关系
| React | ArkUI |
|---|---|
| Function Component | @Component struct |
| JSX | build() 声明式语法 |
| Props | @Prop 或普通参数 |
| useState | @State |
| useEffect | 生命周期回调与状态监听 |
| map | ForEach |
| 条件 JSX | if/else 组件分支 |
| CSS Grid/Flex | Grid、Row、Column、Flex |
React 原型:
const [searchQuery, setSearchQuery] = useState('');
<input
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
/>
ArkUI:
@State searchQuery: string = '';
TextInput({ text: this.searchQuery })
.onChange((value: string) => {
this.searchQuery = value;
})
概念相似,但不能照搬浏览器事件对象。
列表渲染对比
React 使用数组 map():
{apps.map((app) => (
<AppCard key={app.id} app={app} />
))}
ArkUI 使用 ForEach:
ForEach(this.apps, (app: UrlItem) => {
GridItem() {
this.AppGridItem(app)
}
}, (app: UrlItem) => app.id)
两者都需要稳定 ID。不要用数组下标作为长期 Key,否则排序或删除后,组件状态可能错误复用。
Props 与回调
React 常用 Props 向下传值、回调向上传事件。ArkUI 也可以让子组件接收 @Prop 和事件函数,但可变状态的所有权要明确。网址列表应由页面或 ViewModel 持有,卡片只发出“打开、编辑、删除”意图,不应各自复制一份数据。
三、useEffect 如何迁移
Web 首页用 useEffect 创建时钟并返回清理函数:
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
ArkUI 应把开始和结束映射到页面或组件生命周期:
aboutToAppear(): void {
this.timerId = setInterval(() => {
this.currentTime = new Date();
}, 1000);
}
aboutToDisappear(): void {
clearInterval(this.timerId);
}
迁移时最容易漏掉清理,因为 React 的返回函数和 ArkUI 的离开回调写在不同位置。
React useEffect 同时覆盖“首次挂载”“依赖变化”和“卸载清理”,ArkUI 中这些场景可能对应不同机制。迁移前应先写清 Effect 的真实目的:
| React Effect 用途 | ArkUI 迁移方向 |
|---|---|
| 首次加载数据 | aboutToAppear() 或页面显示回调 |
| 页面重新可见时刷新 | onPageShow() |
| 离开时释放资源 | aboutToDisappear() |
| 某状态变化后计算 | 派生函数或状态监听机制 |
| DOM 测量 | 使用 ArkUI 布局回调和窗口信息 |
不要把所有 Effect 机械塞进 aboutToAppear(),否则状态变化后的逻辑会丢失。
四、样式不能逐条翻译 Tailwind
Web 原型使用 bg-white/20 backdrop-blur-2xl rounded-[24px]。ArkUI 需要重新映射为背景、透明度、模糊、圆角和阴影能力。
迁移原则是提取设计语义:
主色、背景色、弱文本色
小/中/大圆角
卡片和浮层阴影
8/12/16/24 间距系统
然后用 UiTokens 实现,而不是逐个复制 CSS 数字。浏览器的 backdrop-filter 与原生模糊在渲染、性能和层级上也可能不同,需要真机调校。
从 CSS 变量到资源系统
Web 端主题通常在 theme.css 中定义变量;HarmonyOS 更适合把基础颜色、字符串和媒体放进资源限定目录:
.backgroundColor($r('app.color.page_background'))
.fontColor($r('app.color.text_primary'))
UiTokens 可以保存间距、圆角与少量品牌语义,系统资源负责深色模式、语言和设备限定。两者配合比把所有十六进制颜色写进一个类更完整。
px 不能直接等同于 vp
Web 原型的 24px 圆角或 320px 面板宽度是浏览器上下文中的设计结果。迁移时要以触控尺寸、屏幕密度和窗口宽度重新验证,而不是机械替换单位名称。
五、动画迁移
React 原型通过 Motion 实现 hover、点击缩放和页面进入动画。移动端没有鼠标 hover,应该转换为更符合触屏的反馈:
- hover → 按压态或焦点态;
- 鼠标提示 → 长按菜单或 tooltip;
- 大幅悬浮动画 → 短促的点击缩放;
- 页面动画 → 与系统导航一致;
- 尊重系统减少动态效果设置。
迁移不是追求视觉一模一样,而是保持相同交互意图。
Motion 的动画可能通过组件卸载自动清理,ArkUI 中手写的 setInterval、setTimeout 和动画序列则要主动管理生命周期。v1 欢迎页包含多组序列状态,迁移到 v2 时更适合封装动画组件,避免视觉状态与角色保存逻辑纠缠。
六、导航模型完全不同
React 原型在 App.tsx 用字符串状态切换屏幕:
const [currentScreen, setCurrentScreen] = useState<Screen>('role-selection');
ArkUI 版本使用页面路由和 Ability 启动首屏:
windowStage.loadContent(entryPage);
router.replaceUrl({ url: AppRoutes.HOME });
原生应用需要考虑系统返回键、页面栈、冷启动、后台恢复和跨 Ability,不应继续只用一个全局字符串模拟导航。
React 原型中的 AI 助手是覆盖在当前屏幕上的 Modal,而 ArkUI v2 将它实现为一级页面。这不是语法差异,而是产品信息架构差异。迁移前应决定它到底是随时呼出的临时工具,还是拥有独立历史和导航状态的主页面。
同样,Web 端“底部导航”只是改变 currentScreen;原生端必须明确使用 Tabs、统一容器还是多个 router 页面,并验证返回键不会穿过一长串 Tab 历史。
七、浏览器存储到 ArkData
Web 原型规划使用 LocalStorage/IndexedDB;ArkUI 使用 Preferences 保存轻量设置,并可继续迁移到 RDB/Cloud DB。迁移数据层时,应先抽象 Repository,而不是在页面里替换 API 名称。
| 数据特征 | Web | HarmonyOS 建议 |
|---|---|---|
| 少量设置 | localStorage | Preferences |
| 大量结构化收藏 | IndexedDB | ArkData RDB |
| 文件与图片 | Cache/File API | 应用文件目录或 Cloud Storage |
| 多设备账号数据 | Web 后端 | Cloud DB + 本地缓存 |
Web 原型当前多数状态只存在内存,例如收藏和设置开关。迁移时应先判断哪些状态需要跨启动保留,不能把所有 useState 都写入 Preferences。
八、Web 能力到系统能力
| Web 原型 | HarmonyOS 原生 |
|---|---|
window.open() |
ArkWeb 或 Want |
| localStorage | Preferences |
| IndexedDB | ArkData RDB |
| Web Notification | 系统通知能力 |
| 浏览器响应式 | 窗口断点与多设备适配 |
| fetch/axios | @ohos/axios 或系统网络能力 |
| Web modal | ArkUI 自定义弹层/系统对话框 |
Toast 与错误反馈
Web 原型使用 Sonner 展示“正在打开”等提示。ArkUI 迁移时应根据反馈严重程度选择:轻量成功提示、表单行内错误、AlertDialog 或独立错误页。不要把所有 Toast 原样替换成 AlertDialog,否则会造成频繁阻断。
图标系统
Web 使用 Lucide React,ArkUI 原型中部分位置使用 Emoji 或字符。正式迁移应建立统一图标资源和尺寸规范,并验证深色模式、像素密度与无障碍描述。字符图标适合快速原型,却不适合承担全部生产图标。
九、搜索功能迁移示例
React 使用 Effect 根据输入更新过滤数组:
useEffect(() => {
const q = searchQuery.trim().toLowerCase();
setFilteredApps(q
? apps.filter(app => app.name.toLowerCase().includes(q))
: apps
);
}, [searchQuery]);
ArkUI 可以直接把结果作为派生数据:
private getFilteredSites(): UrlItem[] {
const q = this.searchText.trim().toLowerCase();
if (!q) return this.sites;
return this.sites.filter((item: UrlItem) =>
item.title.toLowerCase().includes(q) ||
item.url.toLowerCase().includes(q)
);
}
如果结果可以从现有状态计算,就不必再维护一份 filteredSites @State,减少同步错误。只有计算昂贵或结果来自异步请求时,才需要缓存状态。
十、响应式设计迁移
Web 端依靠媒体查询和 CSS Grid;ArkUI 应根据窗口尺寸和设备形态调整。迁移不是复制 md:grid-cols-4,而是建立断点决策:
窄屏:两列卡片 + 底部导航
中等窗口:三列卡片 + 更宽搜索栏
宽屏:四列以上 + 侧栏或限制内容最大宽度
2in1 还要支持鼠标、键盘焦点和窗口自由缩放。移动 Web 的“响应式”与原生多设备的“自适应 + 响应式”并不完全相同。
十一、推荐迁移步骤
- 固化页面信息架构与核心用户旅程;
- 提取与框架无关的数据模型;
- 建立 ArkUI 设计 Token;
- 先迁移静态页面,再迁移状态;
- 将路由改为原生页面栈;
- 接入 Preferences、ArkWeb 和 Want;
- 补齐生命周期与权限;
- 在手机、平板和 2in1 真机验证;
- 最后调整动画和视觉细节。
每一步都应有可验收结果。例如“迁移状态”不是代码能编译,而是选择身份后冷启动仍能恢复;“迁移导航”不是按钮能点击,而是系统返回键、Deep Link 和底部 Tab 行为一致。
十二、哪些代码可以共享
两个工程都是 TypeScript 家族语法,但 UI 运行时不同,不能直接共享 React 组件。更适合共享的是框架无关内容:
- URL、角色、分类等数据协议;
- 搜索评分和 URL 规范化规则;
- 错误码定义;
- 云 API 请求/响应 Schema;
- 测试用例和验收数据;
- 设计 Token 的语义名称。
共享方式可以是规范文档或生成代码,而不是强行让 ArkTS 依赖 Web 组件包。
十三、常见误区 ⚠️
- 把 JSX 逐行改成 ArkUI,而不重构数据边界;
- 把所有 CSS 像素原样搬到 vp;
- 忘记取消 timer 和网络请求;
- 继续用内存字符串模拟原生路由;
- 把 hover 交互搬到触屏;
- 用客户端保存第三方密钥;
- 只在一种手机尺寸验证。
十四、迁移验收清单 ✅
- 角色选择、搜索、收藏和设置功能与原型意图一致;
- 冷启动后关键状态能够恢复;
- 页面退出后计时器和请求正确释放;
- 返回键和主导航符合原生预期;
- WebView 与 Want 均经过安全检查;
- 深色模式和长文本没有溢出;
- 手机、平板、2in1 均完成窗口测试;
- 动画不会遮挡操作,并尊重减少动态效果;
- AI 与语音模拟能力明确标注,未伪装成真实接口;
- Release 包而不只是预览器完成验证。
十五、总结
React 到 ArkUI 的迁移应分三层:产品意图可以保留,数据模型可以复用,平台实现必须重做。把 useState 映射到 @State、useEffect 映射到生命周期只是起点;真正的原生化还包括页面栈、Ability、ArkData、Want、安全区和多设备交互。🚀

更多推荐


所有评论(0)