React Native for OpenHarmony 实战:三方库 react-native-textinput-effects 的鸿蒙化适配指南
本文记录把 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.js
const {iconClass: Icon, …} = this.props; // Icon = undefined
return (… <Icon name={iconName} … /> …); // ★ 渲染 undefined
// Fumi.js / Hideo.js / Makiko.js / Sae.js
const AnimatedIcon = 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() { return this.input.current; }
focus() { if (this.props.editable !== false) this.inputRef().focus(); }
blur() { this.inputRef().blur(); }
isFocused() { return this.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: 接入)
npm install "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" ⇒ 两字段自相矛盾 |
| 备注 | 实现零改动(本轮未修改库任何文件) |
关于基线:本次验证在 RN 0.84.1 + RNOH 0.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) |
# 装库即可,无需其他依赖
npm install "git+https://atomgit.com/oh-react-native/react-native-textinput-effects.git#0.6.3-ohos-1.0.0" --ignore-scripts
import {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 -b com.rnoh084.demo -a EntryAbility --ps rnAppKey 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 | 0.84.1 |
| React | 19.2.3 |
| RNOH(npm / ohpm) | @react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.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 |
| 本次构建 | assembleHap 4 分 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
更多推荐



所有评论(0)