

子玥酱 (掘金 / 知乎 / CSDN / 简书 同名)
大家好,我是 子玥酱,一名长期深耕在一线的前端程序媛 👩💻。曾就职于多家知名互联网大厂,目前在某国企负责前端软件研发相关工作,主要聚焦于业务型系统的工程化建设与长期维护。
我持续输出和沉淀前端领域的实战经验,日常关注并分享的技术方向包括 前端工程化、小程序、React / RN、Flutter、跨端方案,
在复杂业务落地、组件抽象、性能优化以及多端协作方面积累了大量真实项目经验。
技术方向: 前端 / 跨端 / 小程序 / 移动端工程化 内容平台: 掘金、知乎、CSDN、简书 创作特点: 实战导向、源码拆解、少空谈多落地 **文章状态:**长期稳定更新,大量原创输出
我的内容主要围绕 前端技术实战、真实业务踩坑总结、框架与方案选型思考、行业趋势解读 展开。文章不会停留在"API 怎么用",而是更关注为什么这么设计、在什么场景下容易踩坑、真实项目中如何取舍,希望能帮你在实际工作中少走弯路。
子玥酱 · 前端成长记录官 ✨
👋 如果你正在做前端,或准备长期走前端这条路
📚 关注我,第一时间获取前端行业趋势与实践总结
🎁 可领取 11 类前端进阶学习资源 (工程化 / 框架 / 跨端 / 面试 / 架构)
💡 一起把技术学"明白",也用"到位"
持续写作,持续进阶。
愿我们都能在代码和生活里,走得更稳一点 🌱
文章目录
-
- 引言
- [第一层误判:把问题当成 UI Bug](#第一层误判:把问题当成 UI Bug)
- [第二层真相:PC 世界里,输入必须唯一](#第二层真相:PC 世界里,输入必须唯一)
-
- [更接近 PC 的方式:集中焦点模型](#更接近 PC 的方式:集中焦点模型)
- 第三层混乱来源:组件各自"抢焦点"
- 第四层关键:键盘事件不该由组件自己判断
-
- [PC 思维:输入先路由,再处理](#PC 思维:输入先路由,再处理)
- 第五层放大器:多窗口让一切问题显形
- 为什么焦点问题总在后期才爆发?
- 总结
引言
很多 HarmonyOS 应用在做 PC 形态时,第一阶段往往推进得很顺利:
- 布局适配完成
- 多窗口可以打开
- 鼠标键盘也基本能用
从"功能验收"的角度看,好像已经没有明显问题。
但只要真正进入长时间使用,体验却开始变得奇怪:
- 光标有时会突然消失
- 键盘输入偶尔没反应
- 切个窗口回来就不能打字
界面看起来没问题,用户却明显感觉:
哪里不对劲,但又说不出来。
这类问题有一个共同点:
它们几乎都和"焦点"有关。
第一层误判:把问题当成 UI Bug
很多排查一开始都会集中在界面层:
ts
TextInput({
focusable: true
})
或者反复尝试:
ts
this.controller.requestFocus()
短期可能"看起来修好了",但很快又在别的路径复现。
原因是:
焦点本质不是 UI 属性,而是输入所有权。
当你只在组件层修补时,真正的冲突仍然存在于系统更深处。
第二层真相:PC 世界里,输入必须唯一
在移动端,焦点问题不明显,因为:
- 屏幕通常只有一个主要输入区
- 键盘是临时弹出的
- 交互节奏高度线性
但在 PC 上完全不同:
- 可以同时存在多个可输入区域
- 键盘始终处于激活状态
- 用户频繁切换窗口与组件
如果系统里没有一个唯一可信的焦点源:
ts
let isFocused = false
那么多个组件各自维护状态时,冲突就是必然结果。
更接近 PC 的方式:集中焦点模型
ts
class FocusModel {
private currentId?: string
focus(id: string) {
this.currentId = id
}
current() {
return this.currentId
}
}
关键不是 API,而是:
全局只有一个地方,能回答"现在谁接收输入"。
第三层混乱来源:组件各自"抢焦点"
很多项目都会写出类似代码:
ts
onClick() {
this.requestFocus()
}
或者:
ts
onAppear() {
this.requestFocus()
}
在单页面阶段没问题,但一旦进入:
- 多窗口
- 弹层
- 异步渲染
焦点顺序就会变成不可预测,最终用户看到的是:
光标像在随机漂移。
更稳定的策略:统一调度
ts
class FocusController {
constructor(private model: FocusModel) {}
request(id: string) {
this.model.focus(id)
}
}
所有来源------点击、Tab、窗口激活------都必须走同一入口。只有这样,焦点才是"确定的"。
第四层关键:键盘事件不该由组件自己判断
一个非常常见、却很隐蔽的错误是:
ts
onKeyDown(e) {
if (this.isFocused) {
handle(e)
}
}
看似合理,实际上把系统切成了很多碎片。
PC 思维:输入先路由,再处理
ts
function onGlobalKey(e) {
const id = focusModel.current()
dispatch(id, e)
}
ts
function dispatch(id: string, e) {
registry.get(id)?.onKey(e)
}
组件不再决定:
"我要不要处理键盘"。
而是由系统决定:
"键盘属于谁"。
第五层放大器:多窗口让一切问题显形
在单窗口下,很多焦点问题还能被掩盖。但只要用户开始:
- 同时开两个编辑器
- 在窗口间来回切换
- 挂起再恢复
如果焦点仍是全局唯一变量:
ts
let focusedId
那错误输入几乎无法避免。
更合理的结构:窗口级焦点域
ts
class WindowFocus {
focusModel = new FocusModel()
}
窗口切换时只需要:
ts
activate(windowFocus)
你才真正拥有:
彼此独立的输入世界。
为什么焦点问题总在后期才爆发?
因为它具备三个典型特征:
- 早期不明显:单页面基本正常
- 复现困难:依赖复杂操作路径
- 体验致命:一旦出现就是"不能用"
这让很多团队误以为:
问题出在系统或框架。
但更常见的现实是:
应用从一开始,就没有真正设计过焦点系统。
总结
当 HarmonyOS 应用走向 PC,很多看似"界面问题"的异常,本质其实只有一件事:
输入所有权失控。
而要真正修复体验,不是继续调 UI,而是完成三件更底层的重建:
- 建立唯一可信的焦点模型
- 让所有输入先路由再处理
- 在多窗口下隔离输入域
只有当焦点重新被系统级掌控时,PC 体验才会真正从"勉强能用",走向:
稳定、连续、可预期。