你有没有过这种经历:盯着一个六格验证码框,短信迟迟不来,翻垃圾箱、点重发、再输一遍,最后承认双重验证是当代互联网最重要也最烦人的发明。OTP RS 想做的,就是让这件事稍微不那么烦——至少在 Rust 这一侧。
它是一个可组合、无障碍、带动画的一次性密码输入组件,面向 Yew、Dioxus 和 Leptos 三个框架。完整实现了验证器流程:逐格输入、键盘导航、激活/已填/无效/禁用状态、光标闪烁动画、数字入场弹跳动画,以及 RFC 4226(HOTP) 和 RFC 6238(TOTP) 校验。全部用 Rust 写,全部跑在 WASM 上,一行 JavaScript 都不碰。
![]()
为什么会有这个东西
如果你从零搭过一个六格验证码框,就知道套路有多深。一开始想的是"不就是六个输入框嘛"。两小时后你在调焦点管理、退格键传播、键盘事件边界情况、浏览器自动填充劫持状态,还有 iOS 上那个死活不出来的隐形数字键盘。这就是 OTP RS 的由来。
它的组件结构和 Open SASS Kit 里其他组件一样,按需组合:
- Otp:根容器,持有内部值状态、焦点协调和 RFC 校验回调
- Group:一组视觉上聚拢的格子,比如三位数字、一个分隔符、再来三位
- Slot:单个格子,在不可见的覆盖层输入框之上渲染可见字符
- Separator:组与组之间那道小横杠
用起来就是这样:声明 Otp 的 max_length 为 6,里面放两个 Group,中间夹一个 Separator,每个 Group 里按索引摆好 Slot。焦点管理、数字传播、状态聚合、ARIA 属性全都处理好了,你只负责组合格子。
那个"隐形"输入框
整个 OTP RS 里最重要的一个设计决定是:接收键盘事件的输入框元素完全不可见。
每个 Slot 渲染两层。一层是不可见的输入框,通过 input-rs 的 otp_mode=true 实现,样式为绝对定位、铺满、透明度为零,它捕获所有键盘焦点和输入事件。另一层是可见的 span,负责显示数字字符和闪烁的光标。
这样就把输入机制和视觉呈现拆开了,格子的外观可以完全自己控制,不用再跟浏览器给输入框的默认样式较劲。
但这个设计有个前提:隐形输入框的 CSS 必须作为内联 style 属性应用,而不是 class 属性。组件最初把覆盖层 CSS 字符串传给了 input_class,也就是设置 HTML 的 class="" 属性。
浏览器不会根据塞在 class 属性里的 CSS 属性字符串去渲染元素,它只会在样式表里查类名。结果就是,本该隐形的覆盖层变成了一个白色矩形,气势汹汹地盖住整个 OTP 组件,像电视直播里打码的表情包。
修复办法是给三个框架实现里的 input-rs 都加上一个 input_style: &'static str 属性,应用到裸的 otp_mode 输入框上。每个框架一行,_style 和 _class 只差六个字符,三个框架,三份 PR 的痛苦。
编译器是敌人,也是最好的朋友
这次 Rust 跟开发者交手了好几个回合。
第一回合:在宏里调用 Hook。有人试图在 html! 宏的属性里调用 use_state(...),Yew 禁止在函数组件体之外调用 Hook。宏会展开成闭包,而 Hook 调用必须在顶层。把 use_state 提取成 let 绑定放在 html! 之前,问题解决。
第二回合:Box::leak 和 &'static str 的强制要求。Dioxus 和 Leptos 的 input-rs 属性要求 &'static str,但像 "Digit 3" 这种跟格子相关的字符串是运行时算出来的。想在不引入字符串驻留器的前提下,把运行时 String 变成 &'static str,唯一的办法就是 Box::leak。
代价是每次 Slot 渲染都会泄漏大约 15 字节内存。理想吗?不理想。能编译吗?能。长期正确的方案是给 input-rs 加一个 Signal 属性类型,那是未来的 PR。眼下:先 leak,再往前走。
第三回合:那个搞崩 Leptos 的 as 类型转换。Leptos 的 view! 宏没法内联解析 Rust 的类型转换表达式,它看到 as 关键字就分不清哪个是属性名、哪个是类型表达式。修复办法是在 view! 调用之前提取成 let 绑定。
三行代码,五轮编译,希望这能帮你省下十分钟。
第四回合:Dioxus rsx! 里的 String 与 &str。Dioxus 的 rsx! 宏对属性类型很严格,当 slot_class 是 String 时,宏会拒绝,报"期望 &str,找到 String"。而 format! 产出的正是 String。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.