本文记录把 react-native-lightbox(点击缩略图放大看大图)适配到 HarmonyOS 的完整过程。

它做的事:包一层缩略图,点击后用 Modal 铺满屏幕显示大图,支持下拉手势关闭。

结论:完全可用 —— 而且这一轮推翻了一个我此前一直在用的判断:“measure() 在 RNOH 上拿不到值”。
实测它回调正常(w=200 h=80 正是我设的尺寸,px/py 也是正确的页面坐标),
于是这个库的核心链路(open() → measure → setState → Modal 显示)一路走通。

★★★★★★★★ 一、重要纠正:measure() 在 RNOH 上是可用的

为什么这个结论关键

Lightbox.js 的 open() 完全依赖 measure:

open = () => {
  this._root.measure((ox, oy, width, height, px, py) => {     // ★ L71
    this.props.onOpen();
    this.setState({
      isOpen: (this.props.navigator ? true : false),
      origin: {width, height, x: px, y: py},                   // ★ 用 measure 的结果做"起始位置"
    }, () => { … this.setState({isOpen: true}); … });
  });
}

⇒ 如果 measure 的回调不触发,open() 就什么都不做,lightbox 永远打不开 ✗

而我此前曾多次得出过"measure() 在 RNOH 上拿不到值"的结论(并据此推荐"用 onLayout 代替 measure")。
这一轮我直接测了它:

// 一个普通的 View:{width: 200, height: 80}
probeRef.current.measure((ox, oy, w, h, px, py) => { … });

实测结果:

NOTE | 探测用 View 实例是否可得 = true
NOTE | ★★★ measure 回调触发!参数 = {"ox":6,"oy":6,"w":200,"h":80,"px":18,"py":406.6666564941406}
PASS | ★★★ measure() 在 RNOH 上会回调(这决定 lightbox 能否打开) | 期望=true | 实际=true

逐项对照:

measure 返回值与我设定的对照
w / h200 / 80✅ 正是我设的 width:200, height:80
ox / oy6 / 6元素内偏移(这里是 0,6 是相对测量基准的偏移)
px / py18 / 406.67页面坐标(这个 View 确实在页面中部偏上)

⇒ ⇒ measure() 在 RNOH 上工作正常 ✓✓

所以"之前拿不到值"是怎么回事?

更准确的说法应该是:measure() 本身可用,但"时机"很关键。
这个库自己在 render() 里留了一行注释和一具空 onLayout:

// measure will not return anything useful if we dont attach a onLayout handler on android
<View ref={component => this._root = component} onLayout={() => {}}>

⇒ 也就是说:在没有 onLayout 参与的情况下,measure() 可能返回 0 或无效值。
这和"measure() 在 RNOH 上完全不可用"是两件不同的事 ✓

⇒ 修正后的结论(本文采用的版本):

measure() 在 RNOH 上是可用的;但在"元素刚挂载、尚未完成布局"时调用可能拿不到有效值。
稳妥做法仍然是 onLayout + 把测量结果存到 state;但如果一个库只能用 measure()(像这个 lightbox),
它也能正常工作 —— 前提是调用时机在布局完成之后(用户点击时显然已经完成了)。

★★★★★ 二、核心链路完全走通:open() → onOpen → didOpen → Modal 显示

我用 ref 编程调用 open()(不需要点击缩略图):

NOTE | Lightbox 实例是否可得 = true
EVENT | onOpen(1)                                    ← ★ measure 成功 ⇒ onOpen 被回调
NOTE | open() 调用 = ok
PASS | ★ open() 不抛错
EVENT | didOpen(1)                                   ← ★ didOpen 也被回调

更关键的是:Modal 里的内容真的渲染出来了:

✓ 找到 header 文本「LBX 点此关闭」 w=300 h=56 x=510 y=120

⇒ 这正是我通过 renderHeader 传入的关闭按钮 ✓✓
⇒ 而 LightboxOverlay 的浮层是用 <Modal visible={isOpen} transparent={true}> 承载的:

<Modal visible={isOpen} transparent={true} onRequestClose={() => this.close()}>
  …
</Modal>

⇒ ⇒ <Modal> 在 RNOH 上可用(这是第二次验证) ✓
⇒ ⇒ 所以整条链路是:open() → measure 拿到起始位置 → setState({isOpen:true}) → Modal 显示 → renderHeader 渲染 ✓✓

★★★★★ 三、契约 17 条全 PASS,含一个漂亮的"成员归属"印证

NOTE | 默认导出 Lightbox 的 typeof = function
PASS | ★ 默认导出是 class(function)
PASS | ★ 有 prototype.render
NOTE | prototype 上的成员 = render                    ← ★★ 只有 render!
PASS | ★★ 箭头类字段不在 prototype 上(open 不在)
PASS | ★★ 箭头类字段不在 prototype 上(close 不在)

NOTE | Lightbox.propTypes 的键数 = 11
NOTE | Lightbox.propTypes 的键 = activeProps, backgroundColor, didOpen, onClose, onOpen,
       renderContent, renderHeader, springConfig, swipeToDismiss, underlayColor, willClose
PASS | ★★ propTypes 恰好 11 项

NOTE | Lightbox.defaultProps 的键数 = 6
NOTE | Lightbox.defaultProps 的键 = didOpen, onClose, onLongPress, onOpen, swipeToDismiss, willClose
PASS | ★★ defaultProps 恰好 6 项
PASS | ★★ defaultProps.swipeToDismiss = true
PASS | ★ defaultProps.onOpen / didOpen / onLongPress 都是函数

NOTE | LightboxOverlay.defaultProps 的键 = backgroundColor, springConfig
PASS | ★★ LightboxOverlay.defaultProps 恰好 2 项
PASS | ★★ springConfig = {tension:30, friction:7}
PASS | ★★ backgroundColor = black

两个值得记下来的点:

① prototype 上只有 render,其它成员全在实例上

// Lightbox.js 里的写法
getContent = () => { … }
getOverlayProps = () => ({…})
open = () => { … }
close = () => { … }
onClose = () => { … }

实测实例上的 own keys:

props, context, refs, updater, state,
getContent, getOverlayProps, open, close, onClose,     ← ★ 箭头类字段在实例上
_reactInternals, _reactInternalInstance, _root

⇒ ⇒ 这是一条干净的判据:prototype 上只有 render,其余全在实例上 ✓
⇒ 对照:constructor 里 bind 的成员两边都有、箭头类字段只在实例上、原型方法只在 prototype 上 ✓

② close() 是故意抛错的(源码写死的)

close = () => {
  throw new Error('Lightbox.close method is deprecated. Use renderHeader(close) prop instead.');
}

实测:

NOTE | close() 抛出的信息 = Lightbox.close method is deprecated. Use renderHeader(close) prop instead.
PASS | ★★★ close() 抛出 deprecated 错误

⇒ 这是一个"可精确断言的故意失败" ✓ —— 它明确告诉使用者:关闭要走 renderHeader(close) 里拿到的那个 close:

renderHeader={(close) => <TouchableOpacity onPress={close}><Text>关闭</Text></TouchableOpacity>}

★★★★ 四、Navigator 的 9 处命中也是误报

预检时 Navigator 命中 9 处,但全是 this.props.navigator(用户可传入的一个可选 prop):

L75:  isOpen: (this.props.navigator ? true : false),
L85:  if(this.props.navigator) { … }
L90:  const routes = this.props.navigator.getCurrentRoutes();
L92:  this.props.navigator.immediatelyResetRouteStack(routes);
L138: {this.props.navigator ? false : <LightboxOverlay {...this.getOverlayProps()} />}

⇒ 不传 navigator ⇒ 走 “LightboxOverlay + Modal” 这条现代路径 ✓
⇒ 传了 navigator ⇒ 走老的"往 Navigator 路由栈里 push 一个 overlay 页面"的做法(getCurrentRoutes / immediatelyResetRouteStack 都是旧 Navigator 的 API)

⇒ 所以在 RN 0.84 上只要不传 navigator 就没问题 ✓ ——
这也解释了为什么这个 0.8.1 的老包还能在 RN 0.84 上工作:它把旧路径做成了"可选开关" ✓

⚠️ 这是继"listViewDisplayed / hideListView"之后,又一次"名字里含已移除 API"的误报。
扫描时一定要看真实使用形态(import {Navigator}、<Navigator、Navigator.SceneConfigs 之类),
而不是看到 Navigator 这个词就下结论。

★★★ 五、其余预检结论

预检项结果
dependenciesprop-types(宿主已有)
peerDependencies✅ 空
ListView / ViewPropTypes / Text.propTypes / createClass / this.refs / mixins / componentWillReceiveProps / componentWillMount / getDefaultProps / isRequired✅✅ 全部为空(用"真实使用形态"扫的)
Platform.OS2 处:STATUS_BAR_OFFSET = (Platform.OS === 'android' ? -25 : 0) ⇒ 鸿蒙取 0;isIOS ⇒ false
measure(1 处(open() 里)⇒ 实测可用(见第一节)
PanResponder / Animated11 处 / 14 处 ⇒ 纯 JS ✓
useNativeDriver✅ 全空 ⇒ 预计有警告,但实测屏上无 LogBox
NativeModules✅ 空
外部 import只有 prop-types / react / react-native ✓
交付包形态⚠️ 只有两个源文件(Lightbox.js 4017 B + LightboxOverlay.js 6956 B),无 src/、无 .d.ts

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


六、接入与预检(要点)

# 装库(dependencies 只有 prop-types,宿主通常已有)
npm install "git+https://atomgit.com/oh-react-native/react-native-lightbox.git#0.8.1-ohos-1.0.0" --ignore-scripts

七、交付包质量与基线说明

类别结果
交付包内容⚠️ 只有 Lightbox.js(4017 B)+ LightboxOverlay.js(6956 B) ⇒ 没有 src/、没有 .d.ts(11 文件里其余都是 README / LICENSE / 报告)
spec.json⚠️ 缺 upstreamCommit
validation⚠️ "build-pass-device-pending"
⚠️ 声明的基线reactNative: "0.88.0" + rnoh: "0.84.4" ⇒ 两字段自相矛盾
备注库代码零改动(本轮未修改库任何文件)

关于基线:本次验证在 RN 0.84.1 + RNOH 0.84.3 上完成。
库无已移除写法(Navigator 只是可选 prop),无平台分支风险 ⇒ 结论跨基线成立 ✓


八、已知限制与本次验证边界

8.1 用这个库要知道的

事项说明
★★★★★★ measure() 在 RNOH 上可用(本文纠正了旧结论)实测 w=200 h=80 精确、px/py 是正确页面坐标
★★★★★ open() 全链路走通open() → measure → onOpen → setState → didOpen → Modal 显示
★★★★★ 浮层用 <Modal transparent> 承载Modal 在 RNOH 上可用(第二次验证)
★★★★ close() 会抛错**源码写死 throw new Error('…deprecated…');关闭要走 renderHeader(close) 里的 close
★★★★ Navigator 是可选 prop不传则走现代 LightboxOverlay + Modal 路径 ✓;传了才走老路由栈路径
★★★ 成员归属:prototype 上只有 rendergetContent / getOverlayProps / open / close / onClose 都是箭头类字段(在实例上)
★★★ Lightbox.propTypes 11 项 / defaultProps 6 项swipeToDismiss: true + 5 个空函数
★★ LightboxOverlay.defaultProps 2 项springConfig: {tension:30, friction:7} / backgroundColor: 'black'
★★ useNativeDriver 全空但实测屏上无 LogBox(Animated.spring 没传该参数)
★ 只有两个源文件、无 .d.ts想看类型得自己写

8.2 本次验证的边界

  1. ★★★ 未验证真实点击缩略图打开(我用 ref.open() 编程调用,等价但非真实触摸路径);
  2. ★★★ 未验证下拉手势关闭(swipeToDismiss 的 PanResponder 逻辑);
  3. ★★★ 未验证 Modal 的 onRequestClose(物理返回键关闭);
  4. ★★ 未验证缩放/多图切换(本库只做"放大一张图",没有缩放);
  5. ★★ 未验证 renderContent / activeProps 两个自定义入口;
  6. 未验证 springConfig 自定义(只核对了默认值 {tension:30, friction:7});
  7. 未验证 navigator prop 那条老路径(RN 0.84 上已无 Navigator 组件);
  8. 未验证 renderHeader 里 close 的实际关闭效果(只验证了 header 文本渲染);
  9. 未在 RN 0.88.0 / RNOH 0.84.4 上复验;
  10. 未测其它屏幕宽度;未做跨平台对照。

库代码未改动。


九、常见问题

Q1:它在 RN 0.84 上能跑吗?

能,而且核心链路完全走通。 契约 17 条全 PASS;
open() → measure → onOpen → didOpen → Modal 显示,renderHeader 里传入的关闭按钮文本也出现在布局里。
LogBox 无通知。

Q2:measure() 在鸿蒙上真的能用?

能用。 实测一个 {width:200, height:80} 的 View,measure 回调返回 w=200 h=80(精确)、px=18 py=406.67(页面坐标)。
⚠️ 但时机很关键:元素刚挂载、布局未完成时调用可能拿到 0。
这个库自己在 render() 里就写了一句注释 + 一个空 onLayout 来规避:

// measure will not return anything useful if we dont attach a onLayout handler on android
<View ref={c => this._root = c} onLayout={() => {}}>

⇒ 所以更准确的结论是:measure() 可用,但"要在布局完成后调用"。

Q3:close() 调用就报错?

是的,源码写死的:

close = () => { throw new Error('Lightbox.close method is deprecated. Use renderHeader(close) prop instead.'); }

正确做法是用 renderHeader 提供的那个 close:

<Lightbox renderHeader={(close) => <TouchableOpacity onPress={close}><Text>关</Text></TouchableOpacity>}>
  <Image … />
</Lightbox>

Q4:为什么它在 RN 0.84 上还能工作(明明是个老包)?

因为老路径(往 Navigator 路由栈里 push overlay)被做成了可选开关:只有传 navigator prop 才走那条路。
不传 navigator ⇒ 用 LightboxOverlay + Modal 这条现代路径 ⇒ 在 RN 0.84 上没问题 ✓

Q5:Navigator 命中是不是说明它不兼容?

不是。 9 处全是 this.props.navigator(一个可选 prop 的读取),
没有 import {Navigator}、没有 <Navigator>。这和 listViewDisplayed 那类命名巧合是一样的坑。

Q6:为什么 prototype 上只有 render?

因为其它方法都写成箭头类字段:

open = () => {…};  close = () => {…};  getContent = () => {…};

⇒ 它们被挂在实例上,不在 prototype 上。
(对照:constructor 里 bind 的方法两边都有;普通原型方法只在 prototype 上。)

Q7:Modal 在鸿蒙上没问题吧?

没问题。 <Modal> 在这里再次得到验证:这次是通过 lightbox 的 overlay 间接验证的,
renderHeader 里传入的关闭按钮文本确实出现在布局树里(w=300 h=56 x=510 y=120)✓


小结

react-native-lightbox 是点击缩略图放大看大图的组件,
老写法九项全空、无原生依赖、dependencies 只有 prop-types,代码量很小(11 KB 两个文件)。

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

  1. ★★★★★★★★ 我推翻了自己一个旧结论:measure() 在 RNOH 上【可用】。
    实测一个 200×80 的 View:measure 回调返回 w=200 h=80(精确)、px=18 py=406.67(页面坐标)✓✓
    ⇒ 之前"拿不到值"的真正条件是"布局未完成时调用"(这个库自己在 render() 里就用一句注释 + 空 onLayout 说明了这件事)。
    ⇒ 修正后的说法:measure() 可用,但要在布局完成后调用;onLayout 仍然是更稳的选择,但不是唯一选择。
    ⇒ 这条纠正有实际价值:如果按旧结论,我会直接判这个库"核心链路不可用",而事实是它完全能跑 ✓

  2. ★★★★★ 核心链路真的走通了,而且是"可观测"的走通。
    open() → onOpen(1) → didOpen(1) → Modal 里 renderHeader 的文本出现在布局 dump 里(w=300 h=56 x=510 y=120)✓✓
    ⇒ 这不是"没崩",而是"每一步都有证据"。

  3. ★★★★★ 契约 17 条全 PASS,并印证了一条判据规则。
    prototype 上只有 render,而 getContent/getOverlayProps/open/close/onClose 全是箭头类字段(在实例上)✓
    ⇒ 三类成员的归属规则现在很清楚了:原型方法只在 prototype、bind 过的两边都有、箭头类字段只在实例上。

  4. ★★★★ close() 是"故意抛错"的 —— 这是个少见的、可以直接断言的行为契约,
    而且它的错误信息本身就是文档(“用 renderHeader(close) 代替”)✓

  5. ★★★ Navigator 命中 9 处又是误报(this.props.navigator 是可选 prop)。
    ⇒ 这是本月第三次"名字里含已移除 API"的误报(前两次是 listViewDisplayed、hideListView)⇒
    扫描必须看真实使用形态(import {...} / <Component / Component.xxx)✓

  6. ★★★ 老包能跑的一个通用原因:把旧路径做成"可选开关"。
    这个 0.8.1 的包同时保留了 navigator(旧)与 Modal(新)两条路,
    只要调用方不传 navigator 就走新路 ⇒ 在 RN 0.84 上照常工作 ✓
    ⇒ 遇到老包时,先看它有没有"新旧双路径",别急着判死。

  7. ★★ useNativeDriver 全空但 LogBox 干净 —— 又一次印证"预期会出现的警告"不一定真出现,只能写"未观察到" ✓


本篇用到的库

项内容
三方库react-native-lightbox(上游 0.8.1 的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-lightbox
适配 TAG0.8.1-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS)
库类型图片放大查看(Modal + PanResponder)
运行时依赖prop-types(宿主通常已有);peerDependencies 为空
导出形态默认导出 Lightbox;另有 LightboxOverlay
⚠️ 注意close() 故意抛错(用 renderHeader(close));navigator prop 是旧路径开关(不要传)
许可证MIT
上游仓库https://github.com/oblador/react-native-lightbox
宿主工程RNOH084Demo(测试页 rnAppKey = LightboxTestApp)
npm install "git+https://atomgit.com/oh-react-native/react-native-lightbox.git#0.8.1-ohos-1.0.0" --ignore-scripts
import Lightbox from 'react-native-lightbox';

// ① 基本用法:点击缩略图放大
<Lightbox
  swipeToDismiss={true}
  backgroundColor="black"
  renderHeader={(close) => (
    <TouchableOpacity onPress={close}><Text style={{color: '#fff'}}>关闭</Text></TouchableOpacity>
  )}
>
  <Image source={{uri: '…'}} style={{width: 220, height: 120}} />
</Lightbox>

// ② 也可以用 ref 编程打开(等价于点击)
const ref = useRef(null);
ref.current.open();

// ⚠️ close() 会抛错(源码写死 deprecated)⇒ 关闭必须用 renderHeader(close) 里的 close
// ⚠️ 不要传 navigator prop(那是 RN 旧 Navigator 的路径;不传则走 Modal)
// ⚠️ open() 依赖 measure():measure 在 RNOH 上【可用】,但要在布局完成后调用
// ⚠️ package 里只有两个源文件,没有 .d.ts
# 换页启动测试页 —— force-stop 不能省
hdc shell aa force-stop com.rnoh084.demo
hdc shell aa start -b com.rnoh084.demo -a EntryAbility --ps rnAppKey LightboxTestApp

判据的复现方式

# ① 契约 + measure 探测 + open() 链路
hdc shell "hilog -x | grep lbx-test" | Select-String 'PASS|FAIL|SUMMARY|NOTE|EVENT'
#    ⇒ measure 回调参数 {w:200,h:80,px:18,py:406.67};onOpen(1);didOpen(1)
# ② Modal 里的 header 文本(证明浮层真的渲染了)
devecocli ui layout | Select-String 'LBX 点此关闭'
# ③ measure 是否可用(核心结论)
#    ⇒ 见 ① 里的「measure 回调触发」那一行

验证环境

项版本
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 3 分 32 秒 ⇒ 一次成功(打包与构建退出码都检查了)
验证规模契约 17 条全 PASS;measure() 实测可用(w=200 h=80 精确);open() 全链路走通(onOpen + didOpen + Modal 内 renderHeader 文本可见);close() 抛 deprecated 错误;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

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐