本文记录把 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 处)
useNativeDriver7 处,其中 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 默认 falseBaseInput 里是 `useNativeDriver
★★ 是"具名导出"结构import {Kaede, Hoshi, …} from 'react-native-textinput-effects'(要花括号)
★★ 页面上多个 TextInput 会吃掉滚动手势想滚页面得从没有输入框的区域起滑(本次没成功,见边界)
★ bind 不删除原型方法所以 _onLayout 等既在 prototype 上也在实例上

8.2 本次验证的边界

  1. ★★★ 只验证了 4/11 个组件的可见渲染(其余 7 个在屏幕下方;滚动被 11 个 TextInput 吃掉,没能滚下去);
  2. ★★★ 未验证任何"动画效果"(focusedAnim 的插值动画只跑到"不崩",没有做视觉/数值级验证);
  3. ★★ 未验证输入文字后的行为(onChange / 受控 value 的 componentDidUpdate 分支);
  4. ★★ 未验证 iconName / iconColor / iconSize 的实际绘制(桩只渲染一个 ★);
  5. 未逐一验证 11 个组件各自的 defaultProps(只验了 Kaede);
  6. 未验证 labelStyle / inputStyle / iconContainerStyle 等样式覆盖;
  7. 未在 RN 0.88.0 / RNOH 0.84.4 上复验;
  8. 未测其它屏幕宽度;未做跨平台对照。

未改动库代码。


九、常见问题

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 最终清零。

最值得记下来的是这几条:

  1. ★★★★★ 一个"必填 prop 缺失就整页崩"的缺陷。
    iconClass: PropTypes.func.isRequired 没有默认值 ⇒ 不传 ⇒
    Animated.createAnimatedComponent(undefined) 读 displayName ⇒ 整页崩;
    漏传时错误变成 Element type is invalid … got: undefined ⇒
    React 会在 LogBox 里点名 Check the render method of 'XXX' ✓✓ 这是最快的定位线索。

  2. ★★★★★ 我的预检漏了一个风险 —— 搜 prop 要搜声明,不要搜变量名。
    我搜 const Icon / Icon = ⇒ 得出"只有 4 个组件需要 iconClass"✗
    实际是 const {iconClass: Icon} = this.props 这种解构重命名 ⇒ 有 5 个组件需要 ⇒ 漏了 Kohana ⇒ 又崩一次。
    ⇒ 教训:判"某 prop 被谁用"要搜 propTypes 里的 propName: 声明 ✓

  3. ★★★★ 又一个"我的断言错了":bind 不会删除原型方法。
    我断言"bind 过的成员只存在于实例上" ⇒ 实测 4 个全都在 prototype 上 ✗
    ⇒ 真正只存在于实例上的是箭头类字段(foo = () => {})✓
    ⇒ 这修正了一个我之前反复使用的判断方法。

  4. ★★★★ react-native-vector-icons 不需要 —— 两条证据。
    源码外部 import 只有 4 条(其中 1 条出自注释)+ 运行时 Cannot find module 但组件照常工作 ✓
    ⇒ @types/xxx 在 dependencies 里 ≠ xxx 的运行时依赖 ✓

  5. ★★★ 5 个 public methods 是极好的判据形态。
    focus() → isFocused() 真的变 true ⇒ 行为级证据,且不需要模拟点击或键盘输入 ✓
    (在 RNOH 上模拟输入框交互本来就不可靠 ⇒ 这类"编程入口"价值更高)

  6. ★★★ 布局 dump 只列可见节点 ⇒ 一屏放不下就看不全。
    11 个 label 一次只看到 4 个(最后一个在 y=2182,屏高 2232)⇒ 别把"没找到"当成"没渲染" ✓
    ⇒ 而这次想滚动补齐又碰上"TextInput 吃掉滚动手势"⇒ 两个坑叠在一起 ⇒ 如实记为验证边界 ✓

  7. ⚠️ 我自己的执行问题(都记下来):
    ① 漏看解构重命名 ⇒ 崩了两次、重建两次(每轮 ~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
适配 TAG0.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 Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony / @rnoh/react-native-openharmony 0.84.3
Node.js / npmv24.14.0 / 11.9.0
DevEco Studio26.0.0.621
HarmonyOS SDKAPI 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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐