Skip to content

fix(rn): 修复响应式样式尺寸更新并支持配置计算基准 - #2418

Merged
hiyuki merged 30 commits into
masterfrom
fix-resize
Oct 21, 2026
Merged

hiyuki merged 30 commits into
masterfrom
fix-resize

Conversation

@mackwang112

@mackwang112 mackwang112 commented Jan 29, 2026 •

Copy link
Copy Markdown
Collaborator

背景

React Native 端此前基于 Dimensions.get('screen') 计算 rpx、vw、vh 和媒体查询。在分屏、折叠屏、横竖屏切换等窗口尺寸发生变化但物理屏幕尺寸不变的场景中,响应式样式可能不会按当前页面窗口更新;后台页面重新显示时也可能保留旧窗口下的样式。

直接切换为 Window 可能改变存量项目的布局效果,因此本次新增 rnConfig.dimensionsBase 配置:默认使用 'window',需要保持旧行为时可显式设置为 'screen'。

mpx.config.rnConfig.dimensionsBase = 'screen'

本次更新

  • rpx、vw、vh 和媒体查询默认使用 Window 作为计算基准:
    • rpx = value * window.width / 750
    • vw = value * window.width / 100
    • vh = value * window.height / 100
  • 新增 rnConfig.dimensionsBase: 'window' | 'screen',默认值为 'window';响应式单位、媒体查询、onResize 和 deviceOrientation 使用相同的尺寸基准。
  • 合并 PR feat: RN 新增 notifyDimensionsChange 配置,支持手动触发 dimensions 更新 #2485 的 notifyDimensionsChange(dimensions?) 全局方法:支持宿主主动通知尺寸变化;不传参数时重新读取 React Native Dimensions,并重新执行 customDimensions。
  • 新增全局 getStyleDimensions(),统一返回经过 customDimensions 处理、并由 dimensionsBase 选中的当前尺寸副本。
  • 所选尺寸发生变化时,以经过 rnConfig.customDimensions 处理后的有效尺寸判断是否需要刷新。
  • 补全动态 formatValue 路径的尺寸依赖标记,确保使用响应式单位的组件能够订阅并刷新。
  • 修复纯媒体查询声明泄漏到基础样式的问题。
  • 修复纯媒体查询首次计算前未执行 customDimensions 的问题。
  • 修复组合媒体查询的匹配逻辑;@media (min-width: 600px) and (max-width: 900px) 在宽度处于 [600, 900] 时生效。
  • 所选尺寸发生变化时清理 class 样式缓存,并只触发依赖尺寸的组件重新渲染。
  • 页面重新显示时同步页面尺寸版本,使后台页面在宽屏切换为窄屏后返回时能立即使用当前尺寸对应的样式;该同步不依赖页面是否声明 onShow、onHide 或 onResize。
  • 补充相关类型、文档及 Mpx2RN Skill 说明。

问题场景示例

以下示例均使用默认配置 dimensionsBase: 'window';设置为 'screen' 时,响应式单位、媒体查询、onResize 和 deviceOrientation 均改用 Screen。

1. 动态 rpx/vw/vh 未随 Window 更新

<view wx:style="{{rpxStyle}}"></view>
<view wx:style="{{vwStyle}}"></view>
<view wx:style="{{pxStyle}}"></view>
createComponent({
  data: {
    rpxStyle: { width: '600rpx' },
    vwStyle: { width: '50vw' },
    pxStyle: { width: '240px' },
    vhStyle: { height: '100vh' }
  }
})

修复前: 分屏、折叠或旋转导致 Window 变化但 Screen 不变时,不会触发尺寸版本更新;同时动态 formatValue 路径没有标记尺寸依赖,组件可能继续显示旧的 rpx/vw/vh 尺寸。

修复后: 默认配置下,600rpx、50vw 和 100vh 立即按新 Window 尺寸重新计算,240px 保持不变;配置为 'screen' 时继续按 Screen 尺寸计算。

2. 仅使用媒体查询的组件未刷新

.media-box {
  width: 280px;
  background-color: red;
}

@media (min-width: 600px) {
  .media-box {
    width: 520px;
    background-color: green;
  }
}

修复前: media 声明会被同时写入基础样式,导致宽度小于 600px 时也可能直接显示绿色 520px;并且仅使用媒体查询不会建立尺寸依赖,跨过断点时组件可能不刷新。

修复后: 所选尺寸跨过 600px 时立即在红色 280px 和绿色 520px 之间切换,无需依赖其他 rpx/vw/vh 样式触发刷新。

3. min-width 与 max-width 组合查询范围错误

.range-box {
  width: 280px;
  background-color: red;
}

@media (min-width: 600px) and (max-width: 900px) {
  .range-box {
    width: 520px;
    background-color: green;
  }
}

修复前: 两个条件未作为交集处理。宽度大于 900px 时会被 min-width 分支命中,宽度小于 600px 时会被 max-width 分支命中,因此该示例在区间外也会错误显示绿色 520px。

修复后: 仅当 600px <= 所选尺寸.width <= 900px 时使用绿色 520px,区间外使用红色 280px,两个边界均包含。

4. 后台页面返回后仍使用旧尺寸

<view class="rpx-bar"></view>
<view class="vw-bar"></view>
<view class="px-bar"></view>
// 页面刻意不声明 onShow / onHide / onResize
createPage({})
.rpx-bar { width: 600rpx; }
.vw-bar { width: 80vw; }
.px-bar { width: 240px; }

复现步骤:宽屏进入页面后跳转到下一页,在下一页切换为窄屏再返回。

修复前: 没有声明页面生命周期的后台页面不会监听 show,页面尺寸版本未追平全局版本,返回后 600rpx 和 80vw 仍可能保持宽屏下的旧宽度。

修复后: 返回页面时会先同步尺寸版本,600rpx 和 80vw 立即按当前计算基准重新计算,240px 保持不变。600rpx 与 80vw 按公式本来就相等。

5. 纯媒体查询首次计算未应用 customDimensions

mpx.config.rnConfig.customDimensions = (dimensions) => {
  dimensions.window.width /= 2
  return dimensions
}
@media (min-width: 600px) {
  .media-box { width: 520px; }
}

修复前: 如果组件没有 rpx、vw 或 vh,首次媒体查询计算可能直接使用未经 customDimensions 处理的原始尺寸,结果还会受到其他组件渲染顺序影响。

修复后: 媒体查询读取尺寸前会确保 customDimensions 已执行,首次渲染即可使用正确的有效尺寸。

vh 示例只验证所选尺寸的高度计算;非 custom navigation 下 100vh 是否扣除原生导航栏不在本次修改范围内。

notifyDimensionsChange 与 #2485 review 修复

// 重新读取当前 React Native Dimensions
notifyDimensionsChange()

// 或显式传入尺寸
notifyDimensionsChange({
  window: nextWindow,
  screen: nextScreen
})
  • 手动通知与原生 Dimensions change 事件复用同一更新流程,并按 dimensionsBase 选择 Window 或 Screen 作为刷新判断基准。
  • onResize 与 deviceOrientation 使用相同基准:Window 模式只响应 Window 尺寸变化,Screen 模式只响应 Screen 尺寸变化。
  • 调用入口会复制原始 Dimensions,避免 customDimensions 原地修改 React Native 返回的对象。
  • 所有尺寸应用入口都会统一维护初始化状态,修复“首次样式计算前已收到通知,随后重复执行 customDimensions”的问题。

getStyleDimensions

const dimensions = getStyleDimensions()
console.log(dimensions.width, dimensions.height)
  • 首次调用会确保尺寸完成初始化,避免其他调用方读到未经 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。
  • 相关文件 ESLint 检查通过。
  • npm run build:tsc 通过。
  • 手动覆盖动态 rpx/vw/vh、固定 px 对照、纯媒体查询、范围媒体查询,以及后台页面宽屏切换窄屏后返回的场景。

@mackwang112
mackwang112 marked this pull request as draft January 29, 2026 13:27
@mackwang112
mackwang112 marked this pull request as ready for review March 10, 2026 07:53
@mackwang112 mackwang112 changed the title fix: 修复rpx在屏幕尺寸变化时未跟随变化 fix(rn): 修复响应式样式尺寸更新并支持配置计算基准 Sep 10, 2026
Comment thread .agents/skills/mpx2rn/references/rn-script-reference.md Outdated
Comment thread .agents/skills/mpx2rn/references/rn-style-practice.md Outdated
Comment thread .agents/skills/mpx2rn/references/rn-style-reference.md Outdated
Comment thread packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js Outdated
Comment thread packages/webpack-plugin/lib/loader.js Outdated
Comment thread packages/webpack-plugin/lib/react/style-helper.js Outdated
Comment thread packages/webpack-plugin/lib/react/style-helper.js Outdated
Comment thread packages/core/src/platform/patch/getDefaultOptions.ios.js Outdated
Comment thread packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js Outdated
Comment thread packages/core/src/platform/patch/getDefaultOptions.ios.js Outdated
Comment thread packages/core/src/platform/patch/getDefaultOptions.ios.js Outdated
@mackwang112

Copy link
Copy Markdown
Collaborator Author

针对 该评审评论中的 P1:customDimensions 内读取系统信息导致初始化栈溢出 已修复(956606dc5)。现在 customDimensions 执行期间如果再次调用 getWindowInfo、getSystemInfo 或其他依赖 customDimensions 计算结果的 API,会立即给出明确错误,避免重入和栈溢出;通过 try/finally 确保异常后保护状态恢复。已补充首次尺寸初始化和后续尺寸同步两个场景的回归测试。考虑到这些 API 的结果本身依赖 customDimensions,本次选择明确禁止回调内调用,避免不同时机返回原始值或旧值。该评论中的 P2「媒体查询 !important 源码顺序」本次暂未处理。

Comment thread packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js Outdated
Comment thread packages/core/src/platform/dimensionsHelper.js
Comment thread packages/core/src/platform/dimensionsHelper.js Outdated
Comment thread packages/core/src/platform/dimensionsHelper.js Outdated
Comment thread packages/core/src/platform/dimensionsHelper.js Outdated
Comment thread packages/core/src/platform/dimensionsHelper.js Outdated
Comment thread packages/core/src/platform/dimensionsHelper.js Outdated
Comment thread packages/core/src/platform/dimensionsHelper.js Outdated
Comment thread packages/core/src/platform/dimensionsHelper.js Outdated
mackwang112 and others added 4 commits September 23, 2026 11:11
…-dimensions

# Conflicts:
#	.agents/skills/mpx2rn/references/rn-script-reference.md
#	.agents/skills/mpx2rn/references/rn-style-reference.md
#	packages/core/src/platform/patch/getDefaultOptions.ios.js
@hiyuki

hiyuki commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

[P2] 按需订阅遗漏 calc() / var() 中的响应式单位,建议补齐编译与运行时的依赖标记。

问题位置:styleHelperMixin.ios.js 的尺寸订阅逻辑。

当组件仅通过以下表达式使用响应式单位时,__getStyle 不会设置 dependentWindowSize,因此不会订阅页面尺寸版本:

.calc-box { width: calc(100rpx + 0px); }
.var-box { width: var(--unset, 100rpx); }

动态 style 中的同类表达式也受影响。内部单位直到内建组件的 useTransformStyle 阶段才解析,此时 Mpx render effect 已结束,不能补上响应式依赖。尺寸变化后,样式会保留旧值,直到其他更新触发重渲染。

此前在 ccb510a 上用真实 ReactiveEffect 和 useTransformStyle 做了最小复现:窗口宽度从 360 变为 720,普通 100rpx 触发一次更新,calc(100rpx + 0px) 和 var(--unset, 100rpx) 均未触发更新。发布前核对了当前 d1c4917 的 diff,这条依赖收集逻辑仍未补齐。

建议保留按需订阅,在现有两个 formatValue 入口补齐处理:

  1. 运行时 formatValue(packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js):识别表达式中包含的 rpx / vw / vh 数值,将 dependentWindowSize 标记为 true;表达式仍原样返回,由内建组件继续解析,现有直接单位换算逻辑保持不变。
  2. 编译侧 formatValue(packages/webpack-plugin/lib/react/style-helper.js):静态 CSS 中这些表达式目前直接输出字符串,绕过运行时入口。应让含响应式单位的表达式输出运行时调用,例如 _f('calc(100rpx + 0px)')。这样首次计算 class 时,现有 __GCC 就能记录 _dependentWindowSize,后续缓存命中也可以正确建立订阅。

这可以复用已有的编译遍历和 class 缓存标记,无需在每次渲染时额外深遍历整个样式对象。依赖识别可以保守一些:即使 var() 的响应式 fallback 实际未被使用,也允许订阅尺寸变化。仅在 useTransformStyle 中设置标记不能解决依赖收集时机的问题。

建议增加回归用例:分别覆盖静态 class、动态 style 中的 calc() 和 var() fallback,验证尺寸变化后实际样式更新(上述例子应从 48 变为 96);覆盖 class 缓存命中,并保留固定 px 不触发更新的对照。

@hiyuki

hiyuki commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

[P2] global.__externalClasses 仅在 App 入口初始化,导致独立组件构建丢失外部样式。

问题位置:

对于 hasApp: false 的独立组件构建,如果宿主没有先执行 Mpx App 入口,global.__externalClasses 就是 undefined。即使编译配置包含 custom-class,且传入对应样式对象,也不会进入外部样式合并分支。此前这里直接读取 this.__props[className],该场景可以生效。

此前在 ccb510a 上使用实际 buildGlobalParams 和 __getStyle 做了最小复现:

  1. 清除 global.__externalClasses,生成并执行 ctorType: 'component'、hasApp: false、构建配置 externalClasses: ['custom-class'] 的初始化代码。
  2. 给组件传入 custom-class: { color: 'red' },调用 __getStyle('custom-class'),结果为 {}。
  3. 补充 global.__externalClasses = ['custom-class'] 后,同样的调用才返回 { color: 'red' }。

发布前核对当前 d1c4917,上述初始化与消费逻辑仍然存在。另外,isExternalClassesChanged 也依赖这个全局配置,因此只恢复首次样式读取还不足以保证后续更新。

建议在编译产物中补齐无 App 的初始化路径:复用 loaderContext.getMpx().externalClasses,让独立组件/页面在使用样式前也能获得对应配置,不要仅依赖 ctorType === 'app'。如果继续使用全局变量,需要兼顾同一宿主加载多个独立产物的情况,避免后加载产物覆盖已有类名配置。外部样式读取、变更检测和 host 属性过滤应使用一致的配置来源。

建议增加回归用例:不预设 global.__externalClasses,从 hasApp: false 的编译初始化开始,验证外部样式首次传入、后续替换和移除;同时覆盖默认类名与自定义构建配置。当前样式测试的 beforeEach 直接设置了该全局变量,无法暴露这个初始化缺口。

@hiyuki

hiyuki commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

[P2] 按需订阅遗漏 calc() / var() 中的响应式单位,建议补齐编译与运行时的依赖标记。

问题位置:styleHelperMixin.ios.js 的尺寸订阅逻辑。

当组件仅通过以下表达式使用响应式单位时,__getStyle 不会设置 dependentWindowSize,因此不会订阅页面尺寸版本:

.calc-box { width: calc(100rpx + 0px); }
.var-box { width: var(--unset, 100rpx); }

动态 style 中的同类表达式也受影响。内部单位直到内建组件的 useTransformStyle 阶段才解析,此时 Mpx render effect 已结束,不能补上响应式依赖。尺寸变化后,样式会保留旧值,直到其他更新触发重渲染。

此前在 ccb510a 上用真实 ReactiveEffect 和 useTransformStyle 做了最小复现:窗口宽度从 360 变为 720,普通 100rpx 触发一次更新,calc(100rpx + 0px) 和 var(--unset, 100rpx) 均未触发更新。发布前核对了当前 d1c4917 的 diff,这条依赖收集逻辑仍未补齐。

建议保留按需订阅,在现有两个 formatValue 入口补齐处理:

  1. 运行时 formatValue(packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js):识别表达式中包含的 rpx / vw / vh 数值,将 dependentWindowSize 标记为 true;表达式仍原样返回,由内建组件继续解析,现有直接单位换算逻辑保持不变。
  2. 编译侧 formatValue(packages/webpack-plugin/lib/react/style-helper.js):静态 CSS 中这些表达式目前直接输出字符串,绕过运行时入口。应让含响应式单位的表达式输出运行时调用,例如 _f('calc(100rpx + 0px)')。这样首次计算 class 时,现有 __GCC 就能记录 _dependentWindowSize,后续缓存命中也可以正确建立订阅。

这可以复用已有的编译遍历和 class 缓存标记,无需在每次渲染时额外深遍历整个样式对象。依赖识别可以保守一些:即使 var() 的响应式 fallback 实际未被使用,也允许订阅尺寸变化。仅在 useTransformStyle 中设置标记不能解决依赖收集时机的问题。

建议增加回归用例:分别覆盖静态 class、动态 style 中的 calc() 和 var() fallback,验证尺寸变化后实际样式更新(上述例子应从 48 变为 96);覆盖 class 缓存命中,并保留固定 px 不触发更新的对照。

额外添加正则匹配会增大formatValue性能开销,该方法性能高度敏感,综合考虑影响面考虑先不处理这种情况。

@hiyuki
hiyuki merged commit a52d5f6 into master Oct 21, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants