Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
37a76e0
fix: 修复组件销毁后导致rpx/vw/vh相应式单位失效
mackwang112 Mar 10, 2026
69ea9c1
feat: 输出RN 屏幕尺寸变化时仅刷新依赖rpx/vw/vh响应式单位的组件
mackwang112 Mar 10, 2026
97b32fc
fix: 修复输出RN externalClasses 变化时未触发组件render
mackwang112 Mar 10, 2026
b055a0b
Merge branch 'master' into fix-resize
mackwang112 Jul 31, 2026
098ec29
fix(core): update RN external class styles reactively
mackwang112 Jul 31, 2026
8c39dcf
Merge branch 'master' into fix-resize
hiyuki Aug 3, 2026
918b916
Merge remote-tracking branch 'origin/master' into codex/pr2418-conflict
mackwang112 Sep 3, 2026
fa43f6d
fix(rn): update responsive styles with window dimensions
mackwang112 Sep 4, 2026
718e8e6
feat(rn): allow configuring responsive style dimensions
mackwang112 Sep 10, 2026
cd4bfd4
feat(rn): add manual dimensions change notification
mackwang112 Sep 10, 2026
6b1ab55
docs(rn): clarify dimensions notification timing
mackwang112 Sep 10, 2026
64fe2f3
docs(rn): clarify dimensions API availability
mackwang112 Sep 10, 2026
b20f42c
docs(rn): remove dimensions API timing note
mackwang112 Sep 10, 2026
d8352e3
fix(rn): align dimensions base behavior
mackwang112 Sep 10, 2026
370f3cd
fix(core): expose processed style dimensions
mackwang112 Sep 11, 2026
8a98485
补充api-proxy支持customDimensions联动
wangxiaokou Sep 11, 2026
77f2590
fix: 完善 RN 尺寸变化的样式刷新
mackwang112 Sep 17, 2026
04ca711
fix: 保留媒体查询中的 important 样式
mackwang112 Sep 17, 2026
1247d03
fix: address RN dimensions review feedback
mackwang112 Sep 17, 2026
0a0e1c2
fix: update RN external class styles reactively
mackwang112 Sep 18, 2026
a557969
fix: address RN style review feedback
mackwang112 Sep 22, 2026
956606d
fix: guard custom dimensions reentry
mackwang112 Sep 22, 2026
7629fa4
fix: address remaining RN review feedback
mackwang112 Sep 22, 2026
6eba519
refactor(rn): simplify dimensions synchronization
mackwang112 Sep 23, 2026
688ec43
chore: restore loader formatting
mackwang112 Sep 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/skills/mpx2rn/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: mpx2rn
description: Mpx 跨端输出 RN(简称 Mpx2RN 或 Mpx2DRN)的开发适配指南,覆盖模板、脚本、样式、JSON 配置四大维度。当用户进行 Mpx2RN 相关任务时强制调用,包括但不限于:技术方案设计、页面 / 组件的开发迭代、旧项目跨端适配改造、编译和运行时报错排查、Code Review 等。当用户问题不涉及 Mpx2RN 时不应调用,如 Mpx 小程序开发问题,RN 原生开发问题、Mpx2Web 相关问题等。
metadata:
version: "2.12.7"
version: "2.12.9"
author: donghongping
---

Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/mpx2rn/references/rn-api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ mpx.use(apiProxy, {

同步获取窗口、屏幕与安全区信息,常用于布局与刘海区域避让。

可通过 `Mpx.config.rnConfig.customDimensions` 自定义尺寸,`getWindowInfo`、`getSystemInfo` 和 `getSystemInfoSync` 会基于处理后的尺寸计算相关字段。

#### 入参

无。
Expand Down
2 changes: 2 additions & 0 deletions .agents/skills/mpx2rn/references/rn-script-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ import Mpx from "@mpxjs/core"

// 须在 createApp 与页面脚本执行前完成赋值
Mpx.config.rnConfig = {
dimensionsBase: "window",
parseAppProps(props) {
return {
initialRouteName: "pages/index",
Expand Down Expand Up @@ -721,6 +722,7 @@ Mpx.config.rnConfig = {
| `onStateChange` | 导航 state 变化时回调。 |
| `disablePageTransition` | 为 `true` 时禁用 RN 页面转场动画,框架内部映射为 `animation: "none"`。 |
| `disableAppStateListener` | 为 `true` 时不注册 `AppState` 监听(避免与宿主 App 重复)。 |
| `dimensionsBase` | `rpx` / `vw` / `vh`、媒体查询与 `onResize` 使用的尺寸基准,可选 `"window"` 或 `"screen"`,默认为 `"window"`。首次尺寸计算后该配置固定,运行期间修改不会生效。 |
| `openTypeHandler` | 对象,注册 `button` 组件在 RN 上 `open-type` 的容器侧实现,未注册对应键时点击会告警。 |
| `openTypeHandler.onShareAppMessage` | 对应模板中 `open-type="share"`:框架会先取当前页 `onShareAppMessage` 的返回(含与默认 `title` / `path` 的合并及可选 `promise` 异步结果),再调用本回调,入参为 `{ title, path, imageUrl? }`,由宿主调起系统分享等能力。 |
| `openTypeHandler.onUserInfo` | 对应模板中 `open-type="getUserInfo"`:由宿主实现获取用户信息的逻辑,结果需满足按钮侧对 `bindgetuserinfo` 的约定(以 `@mpxjs/webpack-plugin` 中 `mpx-button` 运行时为准)。 |
Expand Down
25 changes: 15 additions & 10 deletions .agents/skills/mpx2rn/references/rn-style-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,15 +168,23 @@ Mpx 在 RN 平台支持多种 CSS 单位,并在运行时进行转换。
| 单位 | 说明 | 转换规则 |
| --- | --- | --- |
| `px` | 绝对像素 | 直接转换为 RN 的无单位数值 |
| `rpx` | 响应式像素 | `rpx值 × 屏幕宽度 / 750` |
| `rpx` | 响应式像素 | 按 `rpx值 × 视口基准宽度 / 750` 转换 |
| `%` | 百分比 | 转换为字符串形式(如 `'50%'`),由 RN 原生支持或框架处理 |
| `vw` | 视口宽度百分比 | `vw值 × 屏幕宽度 / 100` |
| `vh` | 视口高度百分比 | `vh值 × 屏幕高度 / 100` |
| `vw` | 视口宽度百分比 | 按 `vw值 × 视口基准宽度 / 100` 转换 |
| `vh` | 视口高度百分比 | 按 `vh值 × 视口基准高度 / 100` 转换 |
| `hairlineWidth` | RN 特有极细线 | `StyleSheet.hairlineWidth` |

#### 样式计算基准与自定义

`rpx`、`vw`、`vh` 的计算默认基于运行时的 `screen.width` 和 `screen.height`。
`rpx`、`vw`、`vh` 与媒体查询的计算默认基于运行时的 `window.width` 和 `window.height`。如需保持旧版本基于 Screen 尺寸计算的效果,可将 `Mpx.config.rnConfig.dimensionsBase` 设置为 `"screen"`:

`dimensionsBase` 应在首次尺寸计算前完成配置,首次读取后将保持不变。`customDimensions` 也建议在应用初始化阶段完成配置。

```javascript
Mpx.config.rnConfig.dimensionsBase = "screen"
```

该配置支持 `"window"` 和 `"screen"`,默认值为 `"window"`。所选尺寸发生变化时,依赖响应式单位或媒体查询的组件会重新计算样式。

同时支持通过运行时配置 `Mpx.config.rnConfig.customDimensions` 自定义样式计算基准:

Expand All @@ -188,18 +196,15 @@ mpx.config.rnConfig = Object.assign({}, mpx.config.rnConfig, {
const nextWindow = Object.assign({}, dimensions.window, {
height: dimensions.window.height - 44
})
const nextScreen = Object.assign({}, dimensions.screen, {
height: dimensions.screen.height - 44
})
return {
window: nextWindow,
screen: nextScreen
screen: dimensions.screen
}
}
})
```

配置生效后,`rpx`、`vw`、`vh` 会按自定义后的 `screen` 宽高进行计算。
配置生效后,`rpx`、`vw`、`vh` 与媒体查询会按自定义后的 `dimensionsBase` 对应尺寸进行计算。

#### 百分比计算规则

Expand Down Expand Up @@ -408,7 +413,7 @@ Mpx 在 RN 平台支持 `@media` 规则,但能力受限。

**限制:**

- 媒体查询中的宽度条件仅支持 `px` 单位,并基于运行时 `screen.width` 判断。
- 媒体查询中的宽度条件仅支持 `px` 单位,并基于 `dimensionsBase` 选中尺寸的运行时 `width` 判断,默认使用 `window.width`。
- 不支持 `width` 精确匹配、`height`、`orientation`、`all` 等其他特性。

### 动画支持
Expand Down
4 changes: 3 additions & 1 deletion docs-vitepress/api-proxy/base/system/getWindowInfo.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

支持情况: 微信、支付宝、web、RN

> RN 下可通过 `Mpx.config.rnConfig.customDimensions` 自定义尺寸,`getWindowInfo`、`getSystemInfo` 和 `getSystemInfoSync` 会基于处理后的尺寸计算相关字段。

[参考文档](https://developers.weixin.qq.com/miniprogram/dev/api/base/system/wx.getWindowInfo.html)

### 参数 {#parameters}
Expand Down Expand Up @@ -60,4 +62,4 @@ console.log(deviceInfo.brand)
console.log(deviceInfo.model)
console.log(deviceInfo.platform)
console.log(deviceInfo.system)
```
```
38 changes: 35 additions & 3 deletions docs-vitepress/guide/rn/application-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -468,17 +468,49 @@ mpx.config.rnConfig.disablePageTransition = true

### 折叠屏适配 {#foldable-screen-adaption}

#### mpx.config.rnConfig.dimensionsBase

```ts
'window' | 'screen'
```

控制 `rpx` / `vw` / `vh`、媒体查询与 `onResize` 使用的尺寸基准,默认值为 `'window'`。

如果需要保持旧版本基于屏幕尺寸计算的效果,可配置为 `'screen'`:

```js
mpx.config.rnConfig.dimensionsBase = 'screen'
```

配置后,响应式单位和媒体查询将使用 `Dimensions.get('screen')` 的宽高,并在 Screen 尺寸变化时重新计算相关样式及触发 `onResize`。

该配置应在首次尺寸计算前完成,首次读取后将保持不变。

#### mpx.config.rnConfig.customDimensions

```ts
(dimensions: { window: ScaledSize; screen: ScaledSize }) => { window: ScaledSize; screen: ScaledSize } | void
```

在某些情况下,我们可能不希望当前应用全屏展示,Mpx 内部基于 ScreenWidth 与 ScreenHeight 作为 rpx、vh、vw、媒体查询、onResize等特性的依赖内容,此时可在 `mpx.config.rnConfig.customDimensions` 中自定义 screen 尺寸信息来得到想要的渲染效果。
在某些情况下,我们可能不希望当前应用全屏展示。此时可在 `mpx.config.rnConfig.customDimensions` 中自定义 window 或 screen 尺寸信息,`rpx` / `vh` / `vw`、媒体查询与 `onResize` 会使用 `dimensionsBase` 指定的尺寸,并在该尺寸变化时触发相关更新。

可在此方法中返回修改后的 dimensions,如果无返回或返回 `undefined`,则使用原始入参。

例如,使用默认的 `dimensionsBase: 'window'` 时,折叠屏展开后如果期望应用只在一半窗口中展示,可在 `customDimensions` 中将 `window.width` 设为原来的一半。

可在此方法中返回修改后的 dimensions,如果无返回或返回 undefined,则以入参作为返回值
#### getDimensionsInfo

例如: 在折叠屏中我们期望只在其中一半屏上展示,可在 customDimensions 中判断当前是否为折叠屏展开状态,如果是则将 ScreenWidth 设置为原来的一半。
```ts
(dimensionsBase?: 'window' | 'screen') => ScaledSize
```

返回最近一次初始化或 React Native Dimensions 变化后生效的尺寸副本。可传入 `'window'` 或 `'screen'` 指定本次获取的尺寸;不传时使用首次读取的 `rnConfig.dimensionsBase`,传参不会修改该配置。修改返回值不会影响框架内部缓存。

```js
const dimensions = getDimensionsInfo()
console.log(dimensions.width, dimensions.height)
const screenDimensions = getDimensionsInfo('screen')
```


### 前后台切换 {#app-state-change}
Expand Down
17 changes: 6 additions & 11 deletions docs-vitepress/guide/rn/style.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,19 +103,14 @@ Mpx 转 RN 支持以下单位,部分单位在特定情况下存在使用限制
|------|---------|----------|
| `%` | ✅ 支持 | 百分比单位参考 [百分比单位说明](#percentage-unit-explanation) |
| `px` | ✅ 支持 | 绝对像素单位 |
| `rpx` | ✅ 支持 | 响应式像素,根据屏幕宽度动态计算 |
| `vh` | ✅ 支持 | 相对于视口的高度 |
| `vw` | ✅ 支持 | 相对视口的宽度 |
| `rpx` | ✅ 支持 | 响应式像素,默认根据 Window 宽度动态计算 |
| `vh` | ✅ 支持 | 默认使用 Window 高度计算 |
| `vw` | ✅ 支持 | 默认使用 Window 宽度计算 |

> [!tip] vh 单位使用注意
> [!tip] 尺寸基准说明
>
> - **问题**:使用系统默认导航栏时,`vh` 的计算基准可能会发生变化
> - 页面首次加载:`100vh = 屏幕总高度`
> - 状态更新后:`100vh = 屏幕高度 - 导航栏高度`
>
> - **影响**:可能导致布局在运行时突然变化
>
> - **建议**:如需使用 `vh` 单位,推荐配合自定义导航栏使用,以确保计算基准始终一致
> `rpx` / `vw` / `vh` 和媒体查询默认使用 React Native `Dimensions.get('window')` 的尺寸。可将 `mpx.config.rnConfig.dimensionsBase` 设置为 `'screen'`,恢复基于 Screen 尺寸计算的旧版本效果。所选尺寸变化时,依赖这些能力的组件会重新计算样式。

### 百分比单位说明 {#percentage-unit-explanation}

RN 原生较多属性不支持百分比,或对百分比的支持存在 bug(如 `font-size`、`translate` 等),但这些属性在编写 Web/小程序代码时使用较多,所以框架进行了抹平支持。
Expand Down
2 changes: 1 addition & 1 deletion packages/api-proxy/src/platform/api/system/rnSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { initialWindowMetrics } from 'react-native-safe-area-context'
import { getFocusedNavigation } from '../../../common/js'

const getWindowInfo = function () {
const dimensionsScreen = Dimensions.get('screen')
const dimensionsScreen = global.getDimensionsInfo?.('screen') || Dimensions.get('screen')
const navigation = getFocusedNavigation() || {}
const initialWindowMetricsInset = initialWindowMetrics?.insets || {}
const navigationInsets = navigation.insets || {}
Expand Down
2 changes: 2 additions & 0 deletions packages/core/@types/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ declare module '*?resolve' {

declare let setAppShow: () => void
declare let setAppHide: () => void

declare let getDimensionsInfo: (dimensionsBase?: 'window' | 'screen') => Readonly<import('react-native').ScaledSize>
9 changes: 8 additions & 1 deletion packages/core/@types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@ export interface RnConfig {
}) => void

/**
* 自定义屏幕尺寸信息,用于 mpx style 渲染等依赖尺寸的功能。
* 自定义窗口和屏幕尺寸信息,用于 mpx style 渲染等依赖尺寸的功能。
*
* @param dimensions 包含 window 和 screen 的尺寸信息
* @returns 返回修改后的尺寸对象,或 void 表示不修改
Expand All @@ -485,6 +485,13 @@ export interface RnConfig {
dimensions: T
) => T | void

/**
* rpx、vw、vh、媒体查询与 onResize 使用的尺寸基准。
* 首次尺寸计算后该配置固定,运行期间修改不会生效。
* @default 'window'
*/
dimensionsBase?: 'window' | 'screen'

/**
* 加载并执行异步分包的方法。
*
Expand Down
145 changes: 145 additions & 0 deletions packages/core/__tests__/common/dimensionsHelper.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
jest.mock('../../src/index', () => ({
__esModule: true,
default: {
config: {
rnConfig: {
dimensionsBase: 'window'
}
}
}
}))

jest.mock('@mpxjs/utils', () => ({
getFocusedNavigation: jest.fn(),
hasOwn: (value, key) => Object.prototype.hasOwnProperty.call(value, key)
}))

jest.mock('react-native', () => ({
Dimensions: {
get: jest.fn(type => type === 'window'
? { width: 360, height: 640 }
: { width: 720, height: 1280 })
}
}), { virtual: true })

let Mpx
let getDimensionsInfo
let syncDimensions

const dimensions = {
window: { width: 360, height: 640 },
screen: { width: 720, height: 1280 }
}

describe('RN dimensions helper', () => {
beforeEach(() => {
jest.resetModules()
Mpx = require('../../src/index').default
const dimensionsHelper = require('../../src/platform/dimensionsHelper')
getDimensionsInfo = dimensionsHelper.getDimensionsInfo
syncDimensions = dimensionsHelper.syncDimensions
Mpx.config.rnConfig = {
dimensionsBase: 'window'
}
global.__classCaches = new Set()
global.__mpxSizeCount = 0
global.__mpxPageSizeCountMap = {}
global.__mpxPageStatusMap = {}
})

it('uses customDimensions for the initial style dimensions', () => {
const customDimensions = jest.fn((dimensions) => {
dimensions.window.width /= 2
return dimensions
})
Mpx.config.rnConfig.customDimensions = customDimensions

expect(getDimensionsInfo().width).toBe(180)
expect(customDimensions).toHaveBeenCalledTimes(1)
})

it('handles Dimensions changes before the first dimensions read', () => {
syncDimensions({
window: { width: 400, height: 700 },
screen: { width: 800, height: 1400 }
})

expect(getDimensionsInfo('window')).toEqual({ width: 400, height: 700 })
expect(getDimensionsInfo('screen')).toEqual({ width: 800, height: 1400 })
})

it('applies runtime dimension config only after dimensions are synchronized', () => {
expect(getDimensionsInfo().width).toBe(360)
const customDimensions = jest.fn((dimensions) => {
dimensions.window.width /= 2
return dimensions
})

Mpx.config.rnConfig.customDimensions = customDimensions

expect(getDimensionsInfo().width).toBe(360)
expect(customDimensions).not.toHaveBeenCalled()

syncDimensions(dimensions)

expect(getDimensionsInfo().width).toBe(180)
expect(customDimensions).toHaveBeenCalledTimes(1)
})

it('does not expose the dimensions object retained by customDimensions', () => {
let retainedDimensions
Mpx.config.rnConfig.customDimensions = (dimensions) => {
retainedDimensions = dimensions
return dimensions
}

expect(getDimensionsInfo().width).toBe(360)

retainedDimensions.window.width = 180

expect(getDimensionsInfo().width).toBe(360)
})

it('keeps the last effective dimensions when customDimensions throws', () => {
expect(getDimensionsInfo().width).toBe(360)
Mpx.config.rnConfig.customDimensions = () => {
throw new Error('custom dimensions failed')
}

expect(() => syncDimensions(dimensions)).toThrow('custom dimensions failed')
expect(getDimensionsInfo().width).toBe(360)
})

it('rejects dimensions-dependent APIs called while applying customDimensions', () => {
Mpx.config.rnConfig.customDimensions = () => getDimensionsInfo()

expect(() => getDimensionsInfo()).toThrow(
'Do not call getDimensionsInfo, getWindowInfo, getSystemInfo, or other APIs that depend on customDimensions results inside rnConfig.customDimensions.'
)

delete Mpx.config.rnConfig.customDimensions
expect(getDimensionsInfo().width).toBe(360)
})

it('rejects dimensions-dependent APIs during later customDimensions synchronization', () => {
expect(getDimensionsInfo().width).toBe(360)
Mpx.config.rnConfig.customDimensions = () => getDimensionsInfo()

expect(() => syncDimensions(dimensions)).toThrow(
'Do not call getDimensionsInfo, getWindowInfo, getSystemInfo, or other APIs that depend on customDimensions results inside rnConfig.customDimensions.'
)
expect(getDimensionsInfo().width).toBe(360)
})

it('keeps the initial dimensions base after the config changes', () => {
expect(getDimensionsInfo().width).toBe(360)

Mpx.config.rnConfig.dimensionsBase = 'screen'
syncDimensions({
window: dimensions.window,
screen: { width: 1400, height: 800 }
})

expect(getDimensionsInfo().width).toBe(360)
})
})
Loading
Loading