news 2026/10/14 3:34:46

React Native for OpenHarmony 实战:三方库 react-native-size-matters 的鸿蒙化适配指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
React Native for OpenHarmony 实战:三方库 react-native-size-matters 的鸿蒙化适配指南

本文记录把react-native-size-matters适配到 HarmonyOS 的完整过程。

这是一个尺寸缩放工具库:按屏幕的短边 / 长边把设计稿上的数值换算成当前设备上的 dp,并提供ScaledSheet让你直接在样式里写'100@s'这种注解。

它的核心只有18 行代码:

const{width,height}=Dimensions.get('window');const[shortDimension,longDimension]=width<height?[width,height]:[height,width];constguidelineBaseWidth=350;// 设计稿基准(约 5 吋屏)constguidelineBaseHeight=680;scale=size=>shortDimension/350*sizeverticalScale=size=>longDimension/680*sizemoderateScale=(size,factor=0.5)=>size+(scale(size)-size)*factor

本轮把公式逐项核对(用页面自己从Dimensions算出的期望值,不写死数字),并把换算结果放到屏幕上量像素:

表达式期望(vp)实测像素期望像素判定
scale(100)125.714377 px377.14✓
moderateScale(100)112.857339 px338.57✓
scale(50)62.857189 px188.57✓

设备侧断言31 / 31 全部通过,一次构建就过。

另外还实测到一个会让整个 bundle 打挂的坑:react-native-size-matters/extend这个入口依赖@env(react-native-dotenv的虚拟模块),没配置时直接打包失败。


一、先说结论

项结果
上游最新版0.4.2;license: MIT;上游 star2529
库类型纯 JS 工具库:scale/verticalScale/moderateScale/moderateVerticalScale+ScaledSheet
是否需要原生适配不需要(无harmony/、无NativeModules)
运行时依赖零(dependencies未声明);peerDependencies只有react-native: *
prepare/prepublish都没有(scripts只有test)
mainindex.js(源码,298 字节)
导出形态只有具名导出(ScaledSheet+ 9 个函数/别名),没有 default 导出
需要 HAR / 权限 / ohpm❌ 都不需要
设备侧断言✅31 / 31 全部通过(A 13 + B 9 + C 9)
★ 换算复核✅ 三条实测条377 / 339 / 189 px,与公式算出的 377.14 / 338.57 / 188.57逐像素吻合
★ 本机基准Dimensions.get('window')=440 × 744 vp(scale 3)⇒short=440、long=744
★★ 一个会让打包失败的坑react-native-size-matters/extend依赖@env(react-native-dotenv虚拟模块)⇒未配置时 Metro 直接Unable to resolve module @env,整个 bundle 出不来
★ 一个静默失效的边界ScaledSheet的注解正则只允许最多 3 位小数⇒'100.1234@s'不匹配 ⇒ 原样返回字符串(不报错)
★ 我自己的失误导入的别名s与文件底部自己的样式表const s = StyleSheet.create(...)重名⇒ Babel 报Duplicate declaration "s"、bundle 失败

二、判定与接入

步做法结果
①package.json里有没有harmony.autolinking没有
②仓库里有没有harmony/没有
③代码里有没有NativeModules/requireNativeComponent全无
npminstall"git+https://atomgit.com/oh-react-native/react-native-size-matters.git#0.4.2-ohos-1.0.0"--ignore-scripts
// metro.config.jswatchFolders:[path.resolve(__dirname,'../react-native-size-matters')],
importSizeMattersTestAppfrom'./SizeMattersTestApp';AppRegistry.registerComponent('SizeMattersTestApp',()=>SizeMattersTestApp);
hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey SizeMattersTestApp

不需要HAR、权限、ohpm、link-harmony,也不需要补装任何依赖。


三、读源码取到的真值

3.1 四个换算函数(lib/scaling-utils.js)

import{Dimensions}from'react-native';const{width,height}=Dimensions.get('window');// ★ 模块加载时取一次const[shortDimension,longDimension]=width<height?[width,height]:[height,width];constguidelineBaseWidth=350;// 设计稿基准宽(约 5 吋屏)constguidelineBaseHeight=680;// 设计稿基准高exportconstscale=size=>shortDimension/guidelineBaseWidth*size;exportconstverticalScale=size=>longDimension/guidelineBaseHeight*size;exportconstmoderateScale=(size,factor=0.5)=>size+(scale(size)-size)*factor;exportconstmoderateVerticalScale=(size,factor=0.5)=>size+(verticalScale(size)-size)*factor;exportconsts=scale;exportconstvs=verticalScale;exportconstms=moderateScale;exportconstmvs=moderateVerticalScale;

要点:

  1. 取的是"短边 / 长边"(min/max),不是固定的宽 / 高 ⇒横竖屏都会用同一套基准;
  2. shortDimension/longDimension在模块加载时取一次⇒旋转后不会自动更新;
  3. 不做任何取整——scale直接返回浮点(这与某些"会roundToNearestPixel"的换算库不同,用的时候要自己留意);
  4. moderateScale的factor默认 0.5⇒ 只在"原始值"与"完全缩放值"之间走一半(缓解大屏上缩放过度);
  5. s/vs/ms/mvs是同一引用(s === scale为真)。

3.2ScaledSheet:把注解字符串换成数值(lib/ScaledSheet.js)

constvalidScaleSheetRegex=/^(\-?\d+(?:\.\d{1,3})?)@(mv?s(\d+(?:\.\d{1,2})?)?|s|vs)r?$/;// └── 数字:最多 3 位小数 ──┘ └ 函数 ─┘ └─ factor,最多 2 位 ─┘ └ r = 四舍五入
  • '100@s'⇒scale(100)
  • '50@vs'⇒verticalScale(50)
  • '100@ms0.3'⇒moderateScale(100, '0.3')
  • '100@sr'⇒Math.round(scale(100))
  • ⚠️不匹配的字符串原样返回(return value)⇒静默失效,不会报错;
  • create用deepMap递归处理对象与数组⇒ 嵌套的transform: [{translateX: '10@s'}]也会被转换。

3.3 ★★extend入口依赖@env(本轮实测会让打包失败)

lib/extend/scaling-utils.extend.js第 2 行:

import{SIZE_MATTERS_BASE_WIDTH,SIZE_MATTERS_BASE_HEIGHT}from'@env';

@env不是真包,而是react-native-dotenv提供的虚拟模块。本宿主里:

  • node_modules/@env不存在;
  • babel.config.js里没有dotenv 相关插件。

⇒实测后果(Metro 打包直接失败):

error Unable to resolve module @env from E:\rnoh-work\react-native-size-matters\lib\extend\scaling-utils.extend.js > 2 | import { SIZE_MATTERS_BASE_WIDTH, SIZE_MATTERS_BASE_HEIGHT } from '@env';

注意这不是运行时错误,而是【打包阶段】就失败⇒ 只要你的代码里出现import 'react-native-size-matters/extend'(或任何指向extend.js的引用),整个 bundle 出不来。

要用extend入口的前提:先配好react-native-dotenv(或等价的、能把@env映射出来的 babel 插件)。本轮没有配置它,所以extend入口未做功能验证(见"验证边界")。


四、验证设计:公式逐项核 + 屏幕量像素

因为这是纯换算库,判据分两层:

  1. 公式层:期望值由页面自己按源码公式算(读Dimensions.get('window')现算),不写死数字⇒ 换设备也成立;
  2. 几何层:把scale(100)等直接当width渲染出来,再从布局 dump 量真实像素⇒ 证明"算出来的值真的被用上了"。

五、实测结果

5.1 断言 31 / 31

组内容结果
A 契约9 个导出都是函数、ScaledSheet.create存在、4 个别名与主函数同引用、无 default 导出13/13
B 换算4 个公式逐项对源码核、scale(0)=0、负数、不做取整、几何实测9/9
C ScaledSheet 与边界4 种注解 + 嵌套数组 +正则小数位边界+extend结论9/9
合计31/31

运行环境实测:Dimensions.get('window')=440 × 744 vp(scale 3)⇒short = 440、long = 744。

5.2 ★ 换算逐像素复核

表达式公式值(vp)实测像素公式值 × 3判定
scale(100)= 440/350×100125.714377 px377.14✓
moderateScale(100)= 100+(125.714−100)×0.5112.857339 px338.57✓
scale(50)= 440/350×5062.857189 px188.57✓

⇒三条全部吻合(±1px 属布局取整)✓

5.3 ★ScaledSheet的正则边界(静默失效)

注解结果说明
'100@s'scale(100)✓正常
'50@vs'verticalScale(50)✓正常
'100@ms0.3'moderateScale(100, '0.3')✓factor 是字符串,靠隐式转换参与乘法
'100@sr'Math.round(scale(100))✓r后缀四舍五入
'100.1234@s'(4 位小数)原样返回'100.1234@s'⚠️正则不匹配 ⇒ 静默失效
'100.123@s'(3 位)scale(100.123)✓3 位是允许的上限
'abc@s'原样返回 ✓非数字不匹配
嵌套transform: [{translateX: '10@s'}]scale(10)✓deepMap递归生效

⇒'100.1234@s'这种写法不会报错,但样式里会留下一个无效字符串—— 排查时很容易懵。

5.4 UI:标题不与状态栏重合

TITLE measureInWindow y=420 (状态栏高 117) ⇒ 420 > 117,不重合 ✓


六、我自己的一个失误

导入的别名s与我自己文件底部的样式表重名了:

import { …, s, vs, ms, mvs } from 'react-native-size-matters'; // ← 库的别名 s … const s = StyleSheet.create({ … }); // ← 我自己的样式表也叫 s

⇒ Babel 直接报错、bundle 失败(exit 1):

error SizeMattersTestApp.tsx: Duplicate declaration "s"

纠正:把导入的别名改成s as sScale(3 处),自己的样式表保持s。

⚠️教训:这类库的别名非常短(s/vs/ms/mvs),极易和业务代码里的局部变量撞名。用别名时要么加前缀导入,要么干脆只用全名scale/verticalScale。

另外这也再次印证了一条:npm run dev失败时绝不能接着assembleHap—— 那样装进 HAP 的是上一次的 bundle(见下)。

★ 顺带复查了"打包失败会静默装旧 bundle"这条

本轮第一次 bundle 失败时(Duplicate declaration "s"),我没有继续构建,而是先修好再打 ⇒ 避免"装旧 bundle 后误判结果"。
判据:npm run dev的退出码必须单独看;它是非 0 时,后面的assembleHap报成功也毫无意义。


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

7.1 用这个库要知道的

事项说明
基准在模块加载时取一次Dimensions.get('window')只在 import 时读 ⇒旋转 / 分屏后不会自动更新
不做取整scale返回浮点;要整数得自己Math.round,或用@sr注解
短边 / 长边区分scale用短边、verticalScale用长边 ⇒ 横屏时两者会互换语义(但缓存不更新)
注解小数位上限 3 位超过就静默失效(原样返回字符串)
extend入口依赖@env未配react-native-dotenv时打包直接失败
别名很短s/vs/ms/mvs极易与业务变量撞名
没有 default 导出必须具名导入

7.2 本次验证的边界

  1. extend入口未做功能验证—— 它需要@env(react-native-dotenv),本宿主未安装未配置 ⇒只能验证到"打包会失败"这一层;配好之后的自定义基准(SIZE_MATTERS_BASE_WIDTH/HEIGHT)未验证;
  2. 只在一台模拟器上验证(Pura X View,density 3,1320×2232),没有真机;window恰好是440×744vp,公式中的基准(350 / 680)未做更换验证;
  3. 横竖屏 / 分屏未验证(源码是加载时取一次,预期不会更新,但没有实测旋转);
  4. ScaledSheet的@msfactor 边界未穷举(只测了0.3;正则上限是 2 位小数);
  5. deepMap的其它嵌套形态未测(只测了transform: [{translateX}]一层数组 + 对象;深层嵌套 / 数组套数组未测);
  6. null/undefined/ 数字 / 布尔等非字符串值在ScaledSheet里的行为未测(源码if (!validScaleSheetRegex.test(value)) return value;—— 对数字会走String()隐式转换,未实测);
  7. 没有跨平台对照(未在 iOS/Android 上跑同一套用例)。

未改动库代码。


八、常见问题

Q1:scale(100)到底是多少?为什么不是整数?

scale(size) = min(屏宽, 屏高) / 350 * size

本机min = 440vp⇒scale(100) = 440/350×100 = 125.714...。这个库不做取整,所以拿到的是浮点;需要整数就用Math.round(scale(100))或注解里的r后缀('100@sr')。

Q2:s/vs/ms/mvs和全名有什么区别?

没有区别,是同一个函数的别名(实测s === scale为真)。⚠️ 但它们很短、极易与业务变量撞名(我就因为const s = StyleSheet.create(...)撞名导致 bundle 失败)。

Q3:旋转屏幕后尺寸没变?

因为shortDimension/longDimension是模块加载时取一次的模块级常量。⇒ 旋转后不会自动更新(这与它的设计定位有关:按"基准设计稿"等比缩放,通常不随旋转变)。

Q4:ScaledSheet.create里写了注解但没生效?

先看小数位:正则只允许最多 3 位小数(

/^(\-?\d+(?:\.\d{1,3})?)@(mv?s(\d+(?:\.\d{1,2})?)?|s|vs)r?$/

)⇒'12.3456@s'不匹配 ⇒ 原样返回字符串,样式里就是个无效值,而且不报错。

Q5:import 'react-native-size-matters/extend'报Unable to resolve module @env?

因为extend入口里有:

import{SIZE_MATTERS_BASE_WIDTH,SIZE_MATTERS_BASE_HEIGHT}from'@env';

@env是react-native-dotenv的虚拟模块。没装 / 没在 babel 里配置它时,Metro 在打包阶段就失败(不是运行时)。⇒ 要么配好react-native-dotenv,要么别用extend入口(用默认入口 + 需要时自己改基准)。

Q6:设计稿基准不是 350 / 680 怎么办?

默认入口把350/680写死在源码里 ⇒默认入口无法改。官方给的做法就是用extend入口(通过@env里的SIZE_MATTERS_BASE_WIDTH/SIZE_MATTERS_BASE_HEIGHT覆盖)。⚠️ 但那条入口需要先配@env(见 Q5)。

Q7:moderateScale的factor是什么?

在"原始值"和"完全缩放后的值"之间插值:

moderateScale(size, factor) = size + (scale(size) − size) × factor

factor = 0.5(默认)表示"只走一半",用于缓解大屏上缩放过度;factor = 1就等价于scale,factor = 0等于不缩放。

Q8:这个库需要鸿蒙侧做什么特殊处理吗?

不需要。纯 JS、零依赖、无原生模块、不需要 HAR / 权限 /ohpm。唯一要注意的是本文说的基准缓存、不取整、注解小数位上限和extend入口的@env依赖。


小结

react-native-size-matters是纯 JS 尺寸换算工具库(核心只有 18 行),零依赖、无原生接线。设备侧31 / 31断言通过,且一次构建就过(先把源码读透了)。三条换算在屏幕上逐像素复核吻合(377 / 339 / 189 px)。

值得记下来的是这几条:

  1. ★★extend入口会让整个 bundle 打挂。它import … from '@env',而@env是react-native-dotenv的虚拟模块 ⇒ 未配置时 Metro 在打包阶段就Unable to resolve module @env,不是运行时错误。⇒引用一个"可选入口/子路径"前,先确认它的依赖在本工程里能解析;这类失败一出现就是整个 bundle 没了,表现上像是"这个库不能用",实际只是缺一个 babel 插件。

  2. ★ "不匹配就原样返回"是静默失效的高发形态。ScaledSheet的正则只接受最多 3 位小数,超了就把字符串原样留下 ——不报错、不警告,样式里静静地放着一个无效值。⇒ 用注解式 API 时,边界(位数、格式、非法值)必须实测。

  3. 换算是"不做取整"的。scale()返回浮点,与某些会roundToNearestPixel的换算库行为不同;要整数得自己Math.round或用@sr。⇒用之前先确认它是取整还是浮点,否则布局会有 1px 级别的差异。

  4. 这类库的别名特别短(s/vs/ms/mvs),极易撞名。我因为const s = StyleSheet.create(...)与导入的s重名,直接Duplicate declaration打包失败。⇒短别名要么加前缀导入,要么只用全名。

  5. 打包失败绝不接着构建。本轮第一次 bundle 失败时我停下来先修,而不是继续assembleHap—— 否则装进 HAP 的会是上一次的 bundle,结果看起来"改了没生效"(否则"改了没生效"的假象会让你往错误方向排查)。


本篇用到的库

项内容
三方库react-native-size-matters(上游0.4.2的鸿蒙适配版)
适配仓库https://atomgit.com/oh-react-native/react-native-size-matters
适配 TAG0.4.2-ohos-1.0.0
需要 HAR / 权限 / ohpm都不需要(纯 JS,零依赖)
库类型纯 JS 工具库:按屏幕短边/长边缩放尺寸 +ScaledSheet注解式样式
运行时依赖零;peerDependencies只有react-native: *
prepare/prepublish都没有
许可证MIT
上游仓库https://github.com/nirsky/react-native-size-matters(★2529)
宿主工程RNOH084Demo(测试页rnAppKey = SizeMattersTestApp)
# 从适配仓库安装npminstall"git+https://atomgit.com/oh-react-native/react-native-size-matters.git#0.4.2-ohos-1.0.0"--ignore-scripts# 本地目录接入(开发常用)npminstall../react-native-size-matters --ignore-scripts
// metro.config.jswatchFolders:[path.resolve(__dirname,'../react-native-size-matters')],
// 用法:按设计稿(基准 350×680)等比缩放 import {scale, verticalScale, moderateScale, moderateVerticalScale, ScaledSheet} from 'react-native-size-matters'; // ① 函数式 const styles = StyleSheet.create({ box: {width: scale(100), height: verticalScale(50)}, text: {fontSize: moderateScale(16)}, // 只缩放一半,缓解大屏过度放大 }); // ② 注解式(ScaledSheet) const sheet = ScaledSheet.create({ box: {width: '100@s', height: '50@vs'}, pad: {paddingTop: '20@ms0.3'}, // moderateScale + factor 0.3 round: {width: '100@sr'}, // r 后缀 ⇒ Math.round nested: {transform: [{translateX: '10@s'}]}, // 嵌套数组也会被转换 }); // ⚠️ 注解的小数位最多 3 位:'12.3456@s' 会静默失效(原样返回字符串) // ③ ⚠️ 不要用 extend 入口(除非已配好 react-native-dotenv) // import {scale} from 'react-native-size-matters/extend'; // ⇒ Metro: Unable to resolve module @env(整个 bundle 打不出来)
# 换页启动测试页(force-stop 不能省,换页参数只在冷启动生效)hdc shell aa force-stop com.rnoh084.demo hdc shell aa start-bcom.rnoh084.demo-aEntryAbility--psrnAppKey SizeMattersTestApp

换算复核的复现方式

# ① 页面里按源码公式现算期望值(不写死数字)# short = min(window.width, window.height); expect = short / 350 * 100# ② 把 scale(100) 当 width 渲染出来,再从布局 dump 量真实像素devecocli ui layout|Select-String'MARK-S100'# 期望:125.714vp × 3 = 377.14px ⇒ 实测 377px ✓

验证环境

项版本
React Native0.84.1
React19.2.3
RNOH(npm / ohpm)@react-native-oh/react-native-harmony/@rnoh/react-native-openharmony0.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
本机Dimensions.get('window')440 × 744 vp(scale 3)
本机状态栏高度117px(页面留白取 48vp)
宿主 HAP 产物entry-default-signed.hap
本次构建assembleHap4 分 28 秒(一轮就过,无重编)
验证规模设备侧31 / 31断言;换算几何复核 3 条(377 / 339 / 189 pxvs 377.14 / 338.57 / 188.57);ScaledSheet注解 7 种(含正则小数位边界);extend入口打包失败实测复现 1 次

相关社区与项目地址

项目地址
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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 3:34:35

大模型网关限流新思路:从进程内漏桶到分布式漏桶矩阵

读完阿里百炼网关团队那篇《限流比降 10 倍&#xff1a;百炼网关如何用 RocketMQ LiteTopic 重构大模型限流》&#xff0c;给了我很大启发——网关还能这么玩。前半段复盘百炼限流的核心思路&#xff08;固定窗口漏桶、LiteTopic 分布式漏桶矩阵、Suspend 控速&#xff09;&…

作者头像 李华
网站建设 2026/10/14 3:34:08

领导推荐我用AI,一个月后我被辞退:法院判了

吴女士在一家保险公司做法务&#xff0c;管合规稽查和风险核查。2024年7月&#xff0c;为了快点把PPT和数据分析做完&#xff0c;经公司领导推荐&#xff0c;她把自己整理加工过的一份稽核问题数据表&#xff0c;传到了第三方AI平台上。 一个月后&#xff0c;公司给她下了《解除…

作者头像 李华
网站建设 2026/10/14 3:33:46

系统架构设计师备考01-综合错题记录

1 计算机系统基础 **1.1 操作系统定义了两种程序执行模式(用户模式和内核模式),应用程序在用户模式下使用特权指令引起的中断是() ** A 溢出中断 B 外部中断 C 访管中断 D 信号中断 解析 A. 溢出中断 由指令执行过程中的条件满足(如算术溢出等)引发的中断。与用户态下…

作者头像 李华
网站建设 2026/10/14 3:33:42

C++ 网络编程入门:从零实现 UDP 通信、网络字典与简易聊天室

&#x1f3ac; 个人主页&#xff1a;道尔柯南❄专栏传送门&#xff1a;《C语言》《C》《Linux操作系统》昙花一现&#xff0c;却等待了整个白昼&#xff1b;蝉鸣一夏&#xff0c;却蛰伏了好几个四季。 文章目录一、前言&#xff1a;学完 C&#xff0c;如何让程序真正实现网络通…

作者头像 李华
网站建设 2026/10/14 3:33:40

LLM_E2E端到端

EAI&#xff1a;Embodied Artificial Intelligent&#xff0c;E2E&#xff1a;End to End&#xff0c;LLM&#xff1a;Large Language Mode 人工智能【端到端 End-to-End】 一、端到端的定义 标准定义 端到端&#xff08;End-to-End&#xff09;&#xff1a;一种机器学习建模与…

作者头像 李华
网站建设 2026/10/14 3:33:24

图片与声音(云存储 + 音频播放 + 扩展)

本文面向微信小程序初学者&#xff0c;通过一个「图片与声音」案例&#xff0c;演示如何利用云开发存储突破主包 2M 大小限制。读者将学会 image 组件、InnerAudioContext 音频控制、数据与事件绑定&#xff0c;以及 onLoad 生命周期函数的综合运用&#xff0c;并掌握播放速率与…

作者头像 李华