
本文记录把react-native-size-matters适配到 HarmonyOS 的完整过程。这是一个尺寸缩放工具库按屏幕的短边 / 长边把设计稿上的数值换算成当前设备上的 dp并提供ScaledSheet让你直接在样式里写100s这种注解。它的核心只有18 行代码const{width,height}Dimensions.get(window);const[shortDimension,longDimension]widthheight?[width,height]:[height,width];constguidelineBaseWidth350;// 设计稿基准约 5 吋屏constguidelineBaseHeight680;scalesizeshortDimension/350*sizeverticalScalesizelongDimension/680*sizemoderateScale(size,factor0.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这个入口依赖envreact-native-dotenv的虚拟模块没配置时直接打包失败。一、先说结论项结果上游最新版0.4.2license: MIT上游 star2529库类型纯 JS 工具库scale/verticalScale/moderateScale/moderateVerticalScaleScaledSheet是否需要原生适配不需要无harmony/、无NativeModules运行时依赖零dependencies未声明peerDependencies只有react-native: *prepare/prepublish都没有scripts只有testmainindex.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 vpscale 3⇒short440、long744★★ 一个会让打包失败的坑react-native-size-matters/extend依赖envreact-native-dotenv虚拟模块⇒未配置时 Metro 直接Unable to resolve module env整个 bundle 出不来★ 一个静默失效的边界ScaledSheet的注解正则只允许最多 3 位小数⇒100.1234s不匹配 ⇒ 原样返回字符串不报错★ 我自己的失误导入的别名s与文件底部自己的样式表const s StyleSheet.create(...)重名⇒ Babel 报Duplicate declaration s、bundle 失败二、判定与接入步做法结果①package.json里有没有harmony.autolinking没有②仓库里有没有harmony/没有③代码里有没有NativeModules/requireNativeComponent全无npminstallgithttps://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.jsimport{Dimensions}fromreact-native;const{width,height}Dimensions.get(window);// ★ 模块加载时取一次const[shortDimension,longDimension]widthheight?[width,height]:[height,width];constguidelineBaseWidth350;// 设计稿基准宽约 5 吋屏constguidelineBaseHeight680;// 设计稿基准高exportconstscalesizeshortDimension/guidelineBaseWidth*size;exportconstverticalScalesizelongDimension/guidelineBaseHeight*size;exportconstmoderateScale(size,factor0.5)size(scale(size)-size)*factor;exportconstmoderateVerticalScale(size,factor0.5)size(verticalScale(size)-size)*factor;exportconstsscale;exportconstvsverticalScale;exportconstmsmoderateScale;exportconstmvsmoderateVerticalScale;要点取的是短边 / 长边min/max不是固定的宽 / 高 ⇒横竖屏都会用同一套基准shortDimension/longDimension在模块加载时取一次⇒旋转后不会自动更新不做任何取整——scale直接返回浮点这与某些会roundToNearestPixel的换算库不同用的时候要自己留意moderateScale的factor默认 0.5⇒ 只在原始值与完全缩放值之间走一半缓解大屏上缩放过度s/vs/ms/mvs是同一引用s scale为真。3.2ScaledSheet把注解字符串换成数值lib/ScaledSheet.jsconstvalidScaleSheetRegex/^(\-?\d(?:\.\d{1,3})?)(mv?s(\d(?:\.\d{1,2})?)?|s|vs)r?$/;// └── 数字最多 3 位小数 ──┘ └ 函数 ─┘ └─ factor最多 2 位 ─┘ └ r 四舍五入100s⇒scale(100)50vs⇒verticalScale(50)100ms0.3⇒moderateScale(100, 0.3)100sr⇒Math.round(scale(100))⚠️不匹配的字符串原样返回return value⇒静默失效不会报错create用deepMap递归处理对象与数组⇒ 嵌套的transform: [{translateX: 10s}]也会被转换。3.3 ★★extend入口依赖env本轮实测会让打包失败lib/extend/scaling-utils.extend.js第 2 行import{SIZE_MATTERS_BASE_WIDTH,SIZE_MATTERS_BASE_HEIGHT}fromenv;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入口未做功能验证见验证边界。四、验证设计公式逐项核 屏幕量像素因为这是纯换算库判据分两层公式层期望值由页面自己按源码公式算读Dimensions.get(window)现算不写死数字⇒ 换设备也成立几何层把scale(100)等直接当width渲染出来再从布局 dump 量真实像素⇒ 证明算出来的值真的被用上了。五、实测结果5.1 断言 31 / 31组内容结果A 契约9 个导出都是函数、ScaledSheet.create存在、4 个别名与主函数同引用、无 default 导出13/13B 换算4 个公式逐项对源码核、scale(0)0、负数、不做取整、几何实测9/9C ScaledSheet 与边界4 种注解 嵌套数组 正则小数位边界extend结论9/9合计31/31运行环境实测Dimensions.get(window)440 × 744 vpscale 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的正则边界静默失效注解结果说明100sscale(100)✓正常50vsverticalScale(50)✓正常100ms0.3moderateScale(100, 0.3)✓factor 是字符串靠隐式转换参与乘法100srMath.round(scale(100))✓r后缀四舍五入100.1234s4 位小数原样返回100.1234s⚠️正则不匹配 ⇒ 静默失效100.123s3 位scale(100.123)✓3 位是允许的上限abcs原样返回 ✓非数字不匹配嵌套transform: [{translateX: 10s}]scale(10)✓deepMap递归生效⇒100.1234s这种写法不会报错但样式里会留下一个无效字符串—— 排查时很容易懵。5.4 UI标题不与状态栏重合TITLE measureInWindow y420 状态栏高 117 ⇒ 420 117不重合 ✓六、我自己的一个失误导入的别名s与我自己文件底部的样式表重名了import { …, s, vs, ms, mvs } from react-native-size-matters; // ← 库的别名 s … const s StyleSheet.create({ … }); // ← 我自己的样式表也叫 s⇒ Babel 直接报错、bundle 失败exit 1error SizeMattersTestApp.tsx: Duplicate declaration s纠正把导入的别名改成s as sScale3 处自己的样式表保持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 本次验证的边界extend入口未做功能验证—— 它需要envreact-native-dotenv本宿主未安装未配置 ⇒只能验证到打包会失败这一层配好之后的自定义基准SIZE_MATTERS_BASE_WIDTH/HEIGHT未验证只在一台模拟器上验证Pura X Viewdensity 31320×2232没有真机window恰好是440×744vp公式中的基准350 / 680未做更换验证横竖屏 / 分屏未验证源码是加载时取一次预期不会更新但没有实测旋转ScaledSheet的msfactor 边界未穷举只测了0.3正则上限是 2 位小数deepMap的其它嵌套形态未测只测了transform: [{translateX}]一层数组 对象深层嵌套 / 数组套数组未测null/undefined/ 数字 / 布尔等非字符串值在ScaledSheet里的行为未测源码if (!validScaleSheetRegex.test(value)) return value;—— 对数字会走String()隐式转换未实测没有跨平台对照未在 iOS/Android 上跑同一套用例。未改动库代码。八、常见问题Q1scale(100)到底是多少为什么不是整数scale(size) min(屏宽, 屏高) / 350 * size本机min 440vp⇒scale(100) 440/350×100 125.714...。这个库不做取整所以拿到的是浮点需要整数就用Math.round(scale(100))或注解里的r后缀100sr。Q2s/vs/ms/mvs和全名有什么区别没有区别是同一个函数的别名实测s scale为真。⚠️ 但它们很短、极易与业务变量撞名我就因为const s StyleSheet.create(...)撞名导致 bundle 失败。Q3旋转屏幕后尺寸没变因为shortDimension/longDimension是模块加载时取一次的模块级常量。⇒ 旋转后不会自动更新这与它的设计定位有关按基准设计稿等比缩放通常不随旋转变。Q4ScaledSheet.create里写了注解但没生效先看小数位正则只允许最多 3 位小数/^(\-?\d(?:\.\d{1,3})?)(mv?s(\d(?:\.\d{1,2})?)?|s|vs)r?$/⇒12.3456s不匹配 ⇒ 原样返回字符串样式里就是个无效值而且不报错。Q5import react-native-size-matters/extend报Unable to resolve module env因为extend入口里有import{SIZE_MATTERS_BASE_WIDTH,SIZE_MATTERS_BASE_HEIGHT}fromenv;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。Q7moderateScale的factor是什么在原始值和完全缩放后的值之间插值moderateScale(size, factor) size (scale(size) − size) × factorfactor 0.5默认表示只走一半用于缓解大屏上缩放过度factor 1就等价于scalefactor 0等于不缩放。Q8这个库需要鸿蒙侧做什么特殊处理吗不需要。纯 JS、零依赖、无原生模块、不需要 HAR / 权限 /ohpm。唯一要注意的是本文说的基准缓存、不取整、注解小数位上限和extend入口的env依赖。小结react-native-size-matters是纯 JS 尺寸换算工具库核心只有 18 行零依赖、无原生接线。设备侧31 / 31断言通过且一次构建就过先把源码读透了。三条换算在屏幕上逐像素复核吻合377 / 339 / 189 px。值得记下来的是这几条★★extend入口会让整个 bundle 打挂。它import … from env而env是react-native-dotenv的虚拟模块 ⇒ 未配置时 Metro 在打包阶段就Unable to resolve module env不是运行时错误。⇒引用一个可选入口/子路径前先确认它的依赖在本工程里能解析这类失败一出现就是整个 bundle 没了表现上像是这个库不能用实际只是缺一个 babel 插件。★ 不匹配就原样返回是静默失效的高发形态。ScaledSheet的正则只接受最多 3 位小数超了就把字符串原样留下 ——不报错、不警告样式里静静地放着一个无效值。⇒ 用注解式 API 时边界位数、格式、非法值必须实测。换算是不做取整的。scale()返回浮点与某些会roundToNearestPixel的换算库行为不同要整数得自己Math.round或用sr。⇒用之前先确认它是取整还是浮点否则布局会有 1px 级别的差异。这类库的别名特别短s/vs/ms/mvs极易撞名。我因为const s StyleSheet.create(...)与导入的s重名直接Duplicate declaration打包失败。⇒短别名要么加前缀导入要么只用全名。打包失败绝不接着构建。本轮第一次 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# 从适配仓库安装npminstallgithttps://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: 100s, height: 50vs}, pad: {paddingTop: 20ms0.3}, // moderateScale factor 0.3 round: {width: 100sr}, // r 后缀 ⇒ Math.round nested: {transform: [{translateX: 10s}]}, // 嵌套数组也会被转换 }); // ⚠️ 注解的小数位最多 3 位12.3456s 会静默失效原样返回字符串 // ③ ⚠️ 不要用 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-StringMARK-S100# 期望125.714vp × 3 377.14px ⇒ 实测 377px ✓验证环境项版本React Native0.84.1React19.2.3RNOHnpm / ohpmreact-native-oh/react-native-harmony/rnoh/react-native-openharmony0.84.3Node.js / npmv24.14.0/11.9.0DevEco Studio26.0.0.621HarmonyOS SDKAPI 2626.0.0.32设备HarmonyOS 7.0.0(26.0.0) Beta2 模拟器Pura X Viewohos-x641320×2232density 3本机Dimensions.get(window)440 × 744 vpscale 3本机状态栏高度117px页面留白取 48vp宿主 HAP 产物entry-default-signed.hap本次构建assembleHap4 分 28 秒一轮就过无重编验证规模设备侧31 / 31断言换算几何复核 3 条377 / 339 / 189 pxvs 377.14 / 338.57 / 188.57ScaledSheet注解 7 种含正则小数位边界extend入口打包失败实测复现 1 次相关社区与项目地址项目地址React Native 鸿蒙官方社区CPF-RNhttps://atomgit.com/CPF-RNRNOH 框架仓库https://atomgit.com/openharmony-RN/ohos_react_nativeRNOH 三方库使用文档https://atomgit.com/OpenHarmony-RN/usage-docsReact Native for OpenHarmony 组织https://atomgit.com/oh-react-nativeRN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview欢迎加入 CPF-RN 鸿蒙社区https://atomgit.com/CPF-RNReact Native for OpenHarmony 组织https://atomgit.com/oh-react-nativeRN 三方库鸿蒙适配清单https://atomgit.com/oh-react-native/rn-ohos-adaptation-overview