本文记录把react-native-textinput-effects(带入场动画的输入框)适配到 HarmonyOS 的完整过程。
这个库提供11 种风格华丽的输入框(Kaede / Hoshi / Jiro / Isao / Madoka / Akira / Hideo / Kohana / Makiko / Sae / Fumi),全部继承同一个基类BaseInput。
结论:组件本身在鸿蒙上能跑,而且它暴露了 5 个可以直接编程调用的方法(focus/blur/isFocused/clear/inputRef),非常好验证。
但本文要重点记录两件事:一个"必填 prop 缺失就整页崩"的库缺陷,以及我自己的一个错误断言。
★★★★★ 一、一个"必填 prop 缺失就整页崩"的缺陷
这 11 个组件里有几个(Kohana/Hideo/Makiko/Sae/Fumi)支持"图标",它们的propTypes里写着:
iconClass:PropTypes.func.isRequired,// ★ 必填iconName:PropTypes.string.isRequired,// ★ 必填但defaultProps里没有iconClass⇒ 不传就是undefined。而渲染时:
// Kohana.jsconst{iconClass:Icon,…}=this.props;// Icon = undefinedreturn(…<Icon name={iconName}…/>…);// ★ 渲染 undefined// Fumi.js / Hideo.js / Makiko.js / Sae.jsconstAnimatedIcon=Animated.createAnimatedComponent(iconClass);// ★ 传 undefined 进去第一次实测的结果(整页崩):
LogBox: "Cannot read property 'displayName' of undefined"⇒Animated.createAnimatedComponent(undefined)内部去读displayName就炸了。
我先把带图标的 4 个组件传了一个桩,但漏了Kohana(我判断它"不需要图标")⇒ 第二次实测:
LogBox: "Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports. Check the render method of `Kohana`."⇒注意最后那句Check the render method of 'Kohana'—— React 直接点名了出问题的组件✓
⇒⇒ 这是排查这类"整页崩"最有用的线索:不要猜,先看 LogBox 里点名的是哪个组件✓✓
修法(零依赖):用一个 5 行的纯 JS 组件当iconClass:
class IconStub extends React.Component { static displayName = 'IconStub'; render() { return <Text style={{fontSize: this.props.size ?? 20, color: this.props.color ?? '#00aeef'}}>★</Text>; } }然后给全部 11 个组件都传上(多传无害)⇒LogBox 清零,页面正常渲染✓
⚠️我在预检阶段漏看了这个风险:我搜"谁需要
iconClass"时只搜了const Icon/Icon =这类写法,
漏掉了const {iconClass: Icon} = this.props这种【解构重命名】⇒ 得出"只有 4 个需要"的错误结论 ✗
教训:判定"某 prop 被谁用",要搜propTypes里的声明(iconClass:),而不是搜变量名。
★★★★ 二、react-native-vector-icons不需要 —— 两条证据
这个库的dependencies里写的是@types/react-native-vector-icons(一个类型包),
而各组件注释里又反复出现:
/* * This is the icon component you are importing from react-native-vector-icons. * import FontAwesomeIcon from 'react-native-vector-icons/FontAwesome'; * iconClass={FontAwesomeIcon} */⇒很容易误以为必须装react-native-vector-icons✗ —— 但它只是"推荐用法示例"✓
证据 A(源码):13 个文件的全部外部 import只有 4 个:
prop-types react react-native react-native-vector-icons/FontAwesome ← 这一条出自【注释】证据 B(运行时):页面上主动require它 ⇒
NOTE | ★ require("react-native-vector-icons/FontAwesome") 是否成功 = false NOTE | ★ require 失败信息 = Cannot find module 'react-native-vector-icons/FontAwesome' PASS | ★★★★ 库本身不需要 react-native-vector-icons(iconClass 是用户传入的 prop)⇒⇒ 真正的运行时依赖只有prop-types(宿主本来就有)⇒零额外依赖✓✓
★★★★ 三、5 个 public methods 全部实测可用(ref 编程调用)
BaseInput暴露了 5 个方法,它们是很好的判据(不需要模拟点击/键盘输入):
inputRef(){returnthis.input.current;}focus(){if(this.props.editable!==false)this.inputRef().focus();}blur(){this.inputRef().blur();}isFocused(){returnthis.inputRef().isFocused();}clear(){this.inputRef().clear();}实测(拿ref直接调):
NOTE | Kaede.prototype 上的 public methods = inputRef, focus, blur, isFocused, clear PASS | ★★ 5 个 public methods 都在 prototype 上 NOTE | Kaede 实例是否可得 = true NOTE | 实例 state.width(来自 onLayout) = 404 ← ★ onLayout 生效 PASS | ★★ onLayout 已生效(state.width > 0) NOTE | inputRef() 返回 = 对象 PASS | ★ inputRef() 可调用 PASS | ★ focus() 不抛错 NOTE | isFocused() 返回 = true PASS | ★★ focus() 之后 isFocused() 为 true ← ★★ 状态真的变了 PASS | ★ blur() 不抛错 PASS | ★ clear() 不抛错⇒focus()之后isFocused()真的变true✓✓ —— 这比"不抛错"强得多,是行为级的证据
⇒state.width = 404说明内部的onLayout链路在 RNOH 上是通的✓(这个库量尺寸用的是onLayout,13 处)
★★★ 四、我自己的一个错误断言(值得记)
我在页面上断言:“BaseInput里 bind 过的方法(_onLayout/_onChange/_onBlur/_onFocus)不应该在 prototype 上”。
实测结果:
FAIL | ★★ bind 过的方法(_onLayout 等)都【不在】prototype 上 | 期望=0 | 实际=4⇒⇒ 我的断言错了✗ ——this._onLayout = this._onLayout.bind(this)只是给【实例】挂一份新函数,原型上的那个并不会被删掉✓
⇒⇒ 所以它们既在prototype上、也在实例上✓
⇒这条修正了一个我之前反复使用的判断方法:"bind 过的成员只存在于实例上"是错的;
真正只存在于实例上的是箭头类字段(foo = () => {}这种)✓
★★★ 五、其余预检结论
| 预检项 | 结果 |
|---|---|
| 老写法九项 | ✅✅全部为空 |
Text.propTypes的用法 | ✅用的是可选链:inputStyle: Text.propTypes?.style⇒ RN 0.84 没有Text.propTypes也不崩 ✓ |
platform分支 | ✅Platform.OS一处都没有 |
measure()/NativeModules | ✅全空(量尺寸用onLayout,13 处) |
useNativeDriver | 7 处,其中BaseInput是 `useNativeDriver: useNativeDriver |
dependencies | ✅ 只有类型包 ⇒运行时零额外依赖 |
prepack/postinstall | ✅无(scripts里只有测试) |
契约(反射实测):
NOTE | 具名导出个数 = 11 NOTE | 具名导出清单 = Akira, Fumi, Hideo, Hoshi, Isao, Jiro, Kaede, Kohana, Madoka, Makiko, Sae PASS | ★ 具名导出恰好 11 个 PASS | ★★ 11 个组件全部是 class(有 prototype.render) NOTE | Kaede.defaultProps 的键 = easing, height, inputPadding PASS | ★ Kaede.defaultProps.inputPadding = 16 / height = 60 / easing 是函数⇒这个库是"具名导出"结构(lib/index.js里export {Kaede, …})⇒ 导入时必须用花括号✓
关于 label 文本的可见性:我准备了 11 个唯一label(TIE-Kaede…TIE-Fumi)用来做文本级判据,
实测一次 dump 只能看到 4 个(TIE-Kaede/TIE-Hoshi/TIE-Jiro/TIE-Isao),
其余 7 个在屏幕下方(最后一个可见的在y=2182,屏高 2232)⇒布局 dump 只列可见节点✓
而且我没能滚下去:页面上有 11 个TextInput,滑动手势被它们吃掉了✗ ⇒这是本次的验证边界✓
六、接入与预检(要点)
# ① 装库(file: 接入)npminstall"git+https://atomgit.com/oh-react-native/react-native-textinput-effects.git#0.6.3-ohos-1.0.0"--ignore-scripts# ② 不需要装任何东西:运行时只依赖 prop-types(通常在宿主已有)# ③ 不需要 react-native-vector-icons(见第二节)七、交付包质量与基线说明
| 类别 | 结果 |
|---|---|
| 交付包内容 | 只有lib/(11 个组件 +BaseInput+index,共 13 个 js)⇒ 源码可读 ✓(无src/) |
spec.json | ⚠️缺upstreamCommit |
validation | ⚠️"build-pass-device-pending" |
| ⚠️ 声明的基线 | reactNative: "0.88.0"+rnoh: "0.84.4"⇒两字段自相矛盾 |
| 备注 | 实现零改动(本轮未修改库任何文件) |
关于基线:本次验证在RN0.84.1+ RNOH0.84.3上完成。库无已移除写法 ⇒结论跨基线成立✓
八、已知限制与本次验证边界
8.1 用这个库要知道的
| 事项 | 说明 |
|---|---|
★★★★★iconClass/iconName是必填但无默认值 | Kohana/Hideo/Makiko/Sae/Fumi不传就整页崩;给个纯 JS 组件当iconClass即可 |
★★★★ 不需要react-native-vector-icons | 它只是注释里的"推荐用法";dependencies里的也只是类型包 |
| ★★★★ 5 个 public methods 可用 | inputRef/focus/blur/isFocused/clear⇒ 可用ref编程控制 |
★★★ 用onLayout量尺寸 | state.width实测 404(容器宽);RNOH 上可靠 |
★★★useNativeDriver默认 false | BaseInput里是 `useNativeDriver |
| ★★ 是"具名导出"结构 | import {Kaede, Hoshi, …} from 'react-native-textinput-effects'(要花括号) |
★★ 页面上多个TextInput会吃掉滚动手势 | 想滚页面得从没有输入框的区域起滑(本次没成功,见边界) |
★bind不删除原型方法 | 所以_onLayout等既在 prototype 上也在实例上 |
8.2 本次验证的边界
- ★★★ 只验证了 4/11 个组件的可见渲染(其余 7 个在屏幕下方;滚动被 11 个
TextInput吃掉,没能滚下去); - ★★★ 未验证任何"动画效果"(
focusedAnim的插值动画只跑到"不崩",没有做视觉/数值级验证); - ★★ 未验证输入文字后的行为(
onChange/ 受控value的componentDidUpdate分支); - ★★ 未验证
iconName/iconColor/iconSize的实际绘制(桩只渲染一个 ★); - 未逐一验证 11 个组件各自的
defaultProps(只验了Kaede); - 未验证
labelStyle/inputStyle/iconContainerStyle等样式覆盖; - 未在
RN 0.88.0/RNOH 0.84.4上复验; - 未测其它屏幕宽度;未做跨平台对照。
未改动库代码。
九、常见问题
Q1:它在 RN 0.84 上能跑吗?
能,但有两个必填 prop 要留意。11 个具名导出都在、都是 class;5 个 public methods 实测可用
(focus()之后isFocused()真的变true);onLayout链路正常;LogBox清零。
但iconClass/iconName不传会导致整页崩(见第一节)。
Q2:装完一进页面就Cannot read property 'displayName' of undefined?
你用的是带图标的组件(Kohana/Hideo/Makiko/Sae/Fumi)而没传iconClass。Animated.createAnimatedComponent(undefined)会读displayName⇒ 崩。给个任意组件当iconClass即可:
class IconStub extends React.Component { render() { return <Text style={{fontSize: this.props.size ?? 20}}>★</Text>; } } <Kohana label="用户名" iconClass={IconStub} iconName="user" />Q3:报Element type is invalid … got: undefined?
同源问题(iconClass=undefined)。最关键的一点:看 LogBox 里Check the render method of 'XXX'那句话——
它会直接点名是哪个组件 ✓
Q4:一定要装react-native-vector-icons吗?
不需要。它只出现在注释里的"推荐用法示例"中;package.json里的@types/react-native-vector-icons是类型包。
实测运行时require('react-native-vector-icons/FontAwesome')抛Cannot find module,而组件照常工作 ✓
Q5:怎么在不点屏幕的情况下验证输入框状态?
用ref调那 5 个方法:
const ref = useRef(null); <Kaede ref={ref} label="用户名" /> // 然后: ref.current.focus(); // 聚焦 ref.current.isFocused(); // → true ref.current.blur(); // 失焦 ref.current.clear(); // 清空 ref.current.inputRef(); // 拿到内部 TextInput 实例Q6:为什么页面滚不动?
因为页面上有多个TextInput,滑动手势被输入框吃掉了。要么从没有输入框的区域起滑,要么给外层换一种滚动容器。
(本次验证就卡在这里 —— 所以只看到 4/11 个组件。)
Q7:它是默认导出还是具名导出?
具名导出:import {Kaede, Hoshi, Jiro, Isao, Madoka, Akira, Hideo, Kohana, Makiko, Sae, Fumi} from 'react-native-textinput-effects'(要花括号)✓
小结
react-native-textinput-effects是11 个继承同一基类的动效输入框,
九项老写法全空、无平台分支、无measure()、useNativeDriver默认 false,
5 个 public methods 实测可用、onLayout链路正常、LogBox最终清零。
最值得记下来的是这几条:
★★★★★ 一个"必填 prop 缺失就整页崩"的缺陷。
iconClass: PropTypes.func.isRequired没有默认值⇒ 不传 ⇒Animated.createAnimatedComponent(undefined)读displayName⇒整页崩;
漏传时错误变成Element type is invalid … got: undefined⇒
React 会在 LogBox 里点名Check the render method of 'XXX'✓✓这是最快的定位线索。★★★★★ 我的预检漏了一个风险 —— 搜 prop 要搜声明,不要搜变量名。
我搜const Icon/Icon =⇒ 得出"只有 4 个组件需要iconClass"✗
实际是const {iconClass: Icon} = this.props这种解构重命名⇒有 5 个组件需要⇒ 漏了Kohana⇒ 又崩一次。
⇒教训:判"某 prop 被谁用"要搜propTypes里的propName:声明✓★★★★ 又一个"我的断言错了":
bind不会删除原型方法。
我断言"bind 过的成员只存在于实例上" ⇒实测 4 个全都在 prototype 上✗
⇒ 真正只存在于实例上的是箭头类字段(foo = () => {})✓
⇒这修正了一个我之前反复使用的判断方法。★★★★
react-native-vector-icons不需要 —— 两条证据。
源码外部 import 只有 4 条(其中 1 条出自注释)+ 运行时Cannot find module但组件照常工作 ✓
⇒@types/xxx在dependencies里 ≠xxx的运行时依赖✓★★★ 5 个 public methods 是极好的判据形态。
focus()→isFocused()真的变true⇒ 行为级证据,且不需要模拟点击或键盘输入✓
(在 RNOH 上模拟输入框交互本来就不可靠 ⇒ 这类"编程入口"价值更高)★★★ 布局 dump 只列可见节点 ⇒ 一屏放不下就看不全。
11 个 label 一次只看到 4 个(最后一个在y=2182,屏高 2232)⇒别把"没找到"当成"没渲染"✓
⇒ 而这次想滚动补齐又碰上"TextInput吃掉滚动手势"⇒两个坑叠在一起⇒ 如实记为验证边界 ✓⚠️ 我自己的执行问题(都记下来):
① 漏看解构重命名 ⇒ 崩了两次、重建两次(每轮 ~9 分钟);
② 断言bind会删除原型方法 ⇒ 一条 FAIL 是我自己的错;
③ 滚动被TextInput吃掉 ⇒ 7 个组件没看到;
④ 有一张截图截成了 51 KB 的启动早期画面 ⇒ 重截。
⇒本轮耗时远超预估,主要浪费在"没先读清楚iconClass谁需要"上——预检的搜索模式写窄了,代价很大✓
本篇用到的库
| 项 | 内容 |
|---|---|
| 三方库 | react-native-textinput-effects(上游0.6.3的鸿蒙适配版) |
| 适配仓库 | https://atomgit.com/oh-react-native/react-native-textinput-effects |
| 适配 TAG | 0.6.3-ohos-1.0.0 |
| 需要 HAR / 权限 / ohpm | 都不需要 |
| 库类型 | 动效输入框(11 个组件,同一基类) |
| 运行时依赖 | ✅ 只有prop-types;不需要react-native-vector-icons |
prepare/postinstall | ✅无 |
| 许可证 | MIT |
| 上游仓库 | https://github.com/halilb/react-native-textinput-effects(★2978) |
| 宿主工程 | RNOH084Demo(测试页rnAppKey = TextInputEffectsTestApp) |
# 装库即可,无需其他依赖npminstall"git+https://atomgit.com/oh-react-native/react-native-textinput-effects.git#0.6.3-ohos-1.0.0"--ignore-scriptsimport {Kaede, Kohana, Fumi} from 'react-native-textinput-effects'; // ★ 具名导出,要花括号 // ★ 带图标的组件必须传 iconClass,否则整页崩(Animated.createAnimatedComponent(undefined)) class IconStub extends React.Component { static displayName = 'IconStub'; render() { return <Text style={{fontSize: this.props.size ?? 20, color: this.props.color ?? '#00aeef'}}>★</Text>; } } const ref = useRef(null); <Kaede ref={ref} label="用户名" /> <Kohana label="密码" iconClass={IconStub} iconName="lock" /> <Fumi label="邮箱" iconClass={IconStub} iconName="envelope" /> // 编程控制(无需点屏幕) ref.current.focus(); ref.current.isFocused(); ref.current.blur(); ref.current.clear(); // ⚠️ iconClass / iconName 是 isRequired 但没有默认值 ⇒ 不传就崩 // ⚠️ 不需要 react-native-vector-icons(注释里那句只是示例) // ⚠️ 页面上多个 TextInput 会吃掉滚动手势 // ⚠️ useNativeDriver 默认 false(BaseInput 里是 useNativeDriver || false)# 换页启动测试页 —— force-stop 不能省hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey TextInputEffectsTestApp判据的复现方式
# ① 契约 + ref 编程调用:逐条 PASS/FAIL 日志hdc shell"hilog -x | grep tie-test"|Select-String'PASS|FAIL|SUMMARY|NOTE'# ⇒ 11 个具名导出、5 个 public methods、state.width、focus()/isFocused() 等# ② 文本级判据:11 个唯一 label(一次 dump 只能看到屏内那 4 个)devecocli ui layout|Select-String'TIE-'# ③ 出错时先看 LogBox 里 "Check the render method of 'XXX'" 点名的是哪个组件环境搭建
本手册的验证环境是React Native for OpenHarmony(RNOH)。从零准备开发环境(DevEco Studio、HarmonyOS SDK API 26、Node.js、ohpm、hvigorw、模拟器与hdc调试)请按 CPF-RN 开发者指南的「环境初始化」操作:
- CPF-RN 开发者指南 · 02-搭建准备 · 环境初始化:https://atomgit.com/CPF-RN/docs/blob/main/开发者指南/02-搭建准备/环境初始化.md
环境就绪后,本次验证使用的具体版本组合见文末「验证环境」一节。
验证环境
| 项 | 版本 |
|---|---|
| React Native | 0.84.1 |
| React | 19.2.3 |
| RNOH(npm / ohpm) | @react-native-oh/react-native-harmony/@rnoh/react-native-openharmony0.84.3 |
| Node.js / npm | v24.14.0/11.9.0 |
| DevEco Studio | 26.0.0.621 |
| HarmonyOS SDK | API 26(26.0.0.32) |
| 设备 | HarmonyOS 7.0.0(26.0.0) Beta2 模拟器Pura X View(ohos-x64),1320×2232,density 3 |
| 本机状态栏高度 | 117px = 39vp |
| 宿主依赖 | ✅ 无需额外依赖(prop-types已有) |
| 宿主 HAP 产物 | entry-default-signed.hap |
| 本次构建 | assembleHap4 分 1 秒/ 修iconClass后4 分 12 秒/ 再修4 分 5 秒⇒ 三次均成功(打包与构建退出码都检查了) |
| 验证规模 | 契约判据 16 条(15 PASS + 1 条是我自己的错误断言);5 个 public methods 全部实测可用;state.width = 404;4/11 个 label 文本可见;LogBox最终清零;4 张截图互异 |
⚠️基线说明:交付包
spec.json声明RN 0.88.0 + RNOH 0.84.4(两字段自相矛盾),本次验证在RN 0.84.1 + RNOH 0.84.3上完成。
本库无任何已移除写法、无平台分支 ⇒结论跨基线成立。
相关社区与项目地址
| 项目 | 地址 |
|---|---|
| React Native 鸿蒙官方社区(CPF-RN) | https://atomgit.com/CPF-RN |
| RNOH 框架仓库 | https://atomgit.com/openharmony-RN/ohos_react_native |
| RNOH 三方库使用文档 | https://atomgit.com/OpenHarmony-RN/usage-docs |
| React Native for OpenHarmony 组织 | https://atomgit.com/oh-react-native |
| RN 三方库鸿蒙适配清单 | https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview |
欢迎加入 CPF-RN 鸿蒙社区:https://atomgit.com/CPF-RN
React Native for OpenHarmony 组织:https://atomgit.com/oh-react-native
RN 三方库鸿蒙适配清单:https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview