fix(rn): 修复响应式样式尺寸更新并支持配置计算基准 - #2418
mackwang112 wants to merge 20 commits into
Conversation
# Conflicts: # packages/webpack-plugin/lib/react/processScript.js
Integrate PR #2485 and address its review feedback.
| | `setAppShow()` | 手动触发应用「进入前台」逻辑,驱动已注册的 `onShow`。 | | ||
| | `setAppHide()` | 手动触发应用「进入后台」逻辑,驱动已注册的 `onHide`。 | | ||
| | `notifyDimensionsChange(dimensions?)` | 主动通知框架 Dimensions 发生变化,触发 `rpx`、`vw`、`vh`、媒体查询和 `onResize` 等能力重新计算。不传参数时通过 `Dimensions.get` 读取当前原始尺寸;两种调用方式都会重新执行 `customDimensions`,并按 `dimensionsBase` 指定的尺寸判断是否刷新。 | | ||
| | `getStyleDimensions()` | 返回经过 `customDimensions` 处理后、由 `dimensionsBase` 选中的当前只读 `ScaledSize` 副本;首次调用及更换 `customDimensions` 后会确保重新处理原始尺寸。 | |
| RN 不支持 `rem` 和 `em` 单位。需将其转换为 `rpx` 以实现响应式布局。 | ||
|
|
||
| **转换说明:** `rpx` 是小程序和 Mpx RN 的响应式单位(规定屏幕宽为 750rpx)。若原项目使用 `rem` 进行响应式适配,通常存在固定的换算比例。例如: | ||
| **转换说明:** `rpx` 是小程序和 Mpx RN 的响应式单位(RN 默认以 Window 宽度为 750rpx;配置 `dimensionsBase: "screen"` 时以 Screen 宽度为准)。若原项目使用 `rem` 进行响应式适配,通常存在固定的换算比例。例如: |
| | --- | --- | --- | | ||
| | `px` | 绝对像素 | 直接转换为 RN 的无单位数值 | | ||
| | `rpx` | 响应式像素 | `rpx值 × 屏幕宽度 / 750` | | ||
| | `rpx` | 响应式像素 | 默认按 `rpx值 × window.width / 750` 转换 | |
| // TODO: 存在部分安卓折叠屏机型在折叠/展开切换时,Dimensions 监听到的 width/height 尺寸错误,并触发多次问题 | ||
| function rpx (value) { | ||
| const screenInfo = global.__mpxAppDimensionsInfo.screen | ||
| const dimensionsInfo = getStyleDimensions() |
| } | ||
| const { resourcePath, queryObj } = parseRequest(this.resource) | ||
|
|
||
| const externalClasses = mpx.externalClasses || [] |
| classMap[key] = val | ||
|
|
||
| // Media declarations only take effect when their query matches. | ||
| if (!isMedia) { |
| // set css defalut value | ||
| const val = classMap[key] || {} | ||
| classMap[key] = Object.assign(val, classMapValue) | ||
| classMap[key] = val |
There was a problem hiding this comment.
classMap[key] = classMap[key] || {}
不需要val变量了
| const validProps = Object.assign({}, rawOptions.props, rawOptions.properties) | ||
| if (global.__externalClasses && global.__externalClasses.length > 0) { | ||
| global.__externalClasses.forEach((name) => { | ||
| validProps[name] = null |
There was a problem hiding this comment.
以及加入到validProps中会产生一些隐式的冲突,比如和data以及和methods的冲突,可以单独比对一下,如果externalClasses发生变化时,变更一个固定响应数据驱动视图更新
hiyuki
left a comment
There was a problem hiding this comment.
补充两个通过最小用例复现的问题。当前提交原有相关 92 项测试通过,但以下场景尚未覆盖。
| classMap[key] = val | ||
|
|
||
| // Media declarations only take effect when their query matches. | ||
| if (!isMedia) { |
There was a problem hiding this comment.
[P2] 媒体查询中的 !important 声明会失效
这里将 media 声明移出基础样式后,重要声明只保存在 _media[].value._inlineLayer.important 中,但运行时 createLayer().mergeToLayer() 仅对基础 style 调用 checkInlineLayer,没有处理命中的 mediaStyle._inlineLayer;最后 genResult() 又删除了 _inlineLayer,导致这些声明完全丢失。
最小复现:
.box { width: 100px; }
@media (min-width: 300px) {
.box { width: 200px !important; }
}在 window.width = 360 时,通过实际编译产物调用 __getStyle,预期 width 为 200,实际为 100。建议让命中的媒体样式也进入 _inlineLayer 优先级合并流程,并补充这个用例。
|
|
||
| export function getStyleDimensions (dimensionsBase = getDimensionsBase()) { | ||
| if (!dimensionsInfoInitialized || appliedCustomDimensions !== Mpx.config.rnConfig?.customDimensions) { | ||
| applyDimensionsInfo(rawDimensionsInfo) |
There was a problem hiding this comment.
[P2] 更换 customDimensions 后读取尺寸,会让后续通知漏掉缓存刷新
这里会在 getter 中直接更新 __mpxAppDimensionsInfo,但没有同步清理 class 缓存、更新尺寸版本。随后 notifyDimensionsChange() 读取到的旧尺寸已经是处理后的新值,新旧尺寸相等会直接 return,因此即使主动通知也无法刷新之前缓存的样式。
已复现的调用顺序:
- window.width = 360,先渲染并缓存
.box { width: 750rpx },结果为 360。 - 更换
customDimensions,令dimensions.window.width /= 2。 - 调用
getStyleDimensions(),返回 width = 180。 - 调用
notifyDimensionsChange(),再计算同一个 class,结果仍为 360,预期为 180。
文档明确支持更换 customDimensions 后重新读取尺寸,因此建议统一有效尺寸变化与缓存失效、版本更新的处理,避免 getter 提前覆盖用于变化判断的旧快照,并补充上述顺序的测试。
| if (localStyle = this.__getClassStyle?.(className)) { | ||
| mergeToLayer(localStyle._layer || 'normal', localStyle, getMediaStyle(localStyle._media)) | ||
| // class style 计算可能触发缓存,需要单独在结果中记录是否依赖窗口尺寸,不能直接使用全局变量。 | ||
| this.__dependentWindowSize = this.__dependentWindowSize || localStyle._dependentWindowSize |
There was a problem hiding this comment.
dependentWindowSize = dependentWindowSize || localStyle._dependentWindowSize
背景
React Native 端此前基于
Dimensions.get('screen')计算rpx、vw、vh和媒体查询。在分屏、折叠屏、横竖屏切换等窗口尺寸发生变化但物理屏幕尺寸不变的场景中,响应式样式可能不会按当前页面窗口更新;后台页面重新显示时也可能保留旧窗口下的样式。直接切换为 Window 可能改变存量项目的布局效果,因此本次新增
rnConfig.dimensionsBase配置:默认使用'window',需要保持旧行为时可显式设置为'screen'。本次更新
rpx、vw、vh和媒体查询默认使用 Window 作为计算基准:rpx = value * window.width / 750vw = value * window.width / 100vh = value * window.height / 100rnConfig.dimensionsBase: 'window' | 'screen',默认值为'window';响应式单位、媒体查询、onResize和deviceOrientation使用相同的尺寸基准。notifyDimensionsChange(dimensions?)全局方法:支持宿主主动通知尺寸变化;不传参数时重新读取 React Native Dimensions,并重新执行customDimensions。getStyleDimensions(),统一返回经过customDimensions处理、并由dimensionsBase选中的当前尺寸副本。rnConfig.customDimensions处理后的有效尺寸判断是否需要刷新。formatValue路径的尺寸依赖标记,确保使用响应式单位的组件能够订阅并刷新。customDimensions的问题。@media (min-width: 600px) and (max-width: 900px)在宽度处于[600, 900]时生效。onShow、onHide或onResize。问题场景示例
以下示例均使用默认配置
dimensionsBase: 'window';设置为'screen'时,响应式单位、媒体查询、onResize和deviceOrientation均改用 Screen。1. 动态 rpx/vw/vh 未随 Window 更新
修复前: 分屏、折叠或旋转导致 Window 变化但 Screen 不变时,不会触发尺寸版本更新;同时动态
formatValue路径没有标记尺寸依赖,组件可能继续显示旧的rpx/vw/vh尺寸。修复后: 默认配置下,
600rpx、50vw和100vh立即按新 Window 尺寸重新计算,240px保持不变;配置为'screen'时继续按 Screen 尺寸计算。2. 仅使用媒体查询的组件未刷新
修复前: media 声明会被同时写入基础样式,导致宽度小于
600px时也可能直接显示绿色520px;并且仅使用媒体查询不会建立尺寸依赖,跨过断点时组件可能不刷新。修复后: 所选尺寸跨过
600px时立即在红色280px和绿色520px之间切换,无需依赖其他rpx/vw/vh样式触发刷新。3. min-width 与 max-width 组合查询范围错误
修复前: 两个条件未作为交集处理。宽度大于
900px时会被min-width分支命中,宽度小于600px时会被max-width分支命中,因此该示例在区间外也会错误显示绿色520px。修复后: 仅当
600px <= 所选尺寸.width <= 900px时使用绿色520px,区间外使用红色280px,两个边界均包含。4. 后台页面返回后仍使用旧尺寸
复现步骤:宽屏进入页面后跳转到下一页,在下一页切换为窄屏再返回。
修复前: 没有声明页面生命周期的后台页面不会监听 show,页面尺寸版本未追平全局版本,返回后
600rpx和80vw仍可能保持宽屏下的旧宽度。修复后: 返回页面时会先同步尺寸版本,
600rpx和80vw立即按当前计算基准重新计算,240px保持不变。600rpx与80vw按公式本来就相等。5. 纯媒体查询首次计算未应用 customDimensions
修复前: 如果组件没有
rpx、vw或vh,首次媒体查询计算可能直接使用未经customDimensions处理的原始尺寸,结果还会受到其他组件渲染顺序影响。修复后: 媒体查询读取尺寸前会确保
customDimensions已执行,首次渲染即可使用正确的有效尺寸。notifyDimensionsChange 与 #2485 review 修复
dimensionsBase选择 Window 或 Screen 作为刷新判断基准。onResize与deviceOrientation使用相同基准:Window 模式只响应 Window 尺寸变化,Screen 模式只响应 Screen 尺寸变化。customDimensions原地修改 React Native 返回的对象。customDimensions”的问题。getStyleDimensions
customDimensions处理的原始尺寸。customDimensions,下次读取会基于最近一次原始 Dimensions 重新处理。其他已有修复
修复组件销毁后导致 rpx/vw/vh 响应式单位失效
现状:页面上的组件销毁后,尺寸变化可能不再触发使用响应式单位的组件刷新。
原因:
del(global.__mpxPageSizeCountMap, pageId)应只在页面销毁时触发,此前组件销毁时也会触发。尺寸变化时仅刷新依赖响应式单位的组件
由刷新所有组件改为仅刷新依赖
rpx、vw、vh或媒体查询的组件。修复 RN externalClasses 变化时未触发组件 render
externalClasses配置保留到运行时并加入validProps,使其与普通 props 一样具有响应性。__getStyle从this.__mpxProxy.props[className]获取 externalClasses,使样式计算与响应式数据建立关联。验证
./node_modules/.bin/jest packages/core/__tests__/common/dimensionsHelper.spec.js packages/core/__tests__/common/styleHelperMixin.ios.spec.js packages/webpack-plugin/test/platform/wx/style/style-rn.spec.js --runInBand --watchman=false:3 suites、92 tests passed。npm run build:tsc通过。rpx/vw/vh、固定px对照、纯媒体查询、范围媒体查询,以及后台页面宽屏切换窄屏后返回的场景。