Skip to content

Commit c4736a3

Browse files
authored
Merge pull request #2101 from didi/feat-media-screen
样式缓存&媒体查询&resize重新计算rpx和style
2 parents 7a30d58 + 1547d76 commit c4736a3

9 files changed

Lines changed: 269 additions & 88 deletions

File tree

docs-vitepress/guide/rn/style.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,38 @@ env(<environment-variable>, <fallback-value>?)
499499
| **作用域** | 全局生效 | 局部作用域 |
500500
| **用途** | 系统环境适配 | 样式变量管理 |
501501

502+
## 媒体查询
503+
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries
504+
### 媒体类型
505+
- print 不支持
506+
- **screen 支持**
507+
- **all 支持**
508+
### 媒体特性
509+
- **width-视口(包括纵向滚动条)的宽度,支持**
510+
- height-视口的高度,暂不支持
511+
- aspect-ratio-视口(viewport)的宽高比,暂不支持
512+
- orientation-视口的旋转方向,暂不支持
513+
- prefers-color-scheme 系统的主题色设置为亮色或者暗色,暂不支持
514+
### 逻辑运算符
515+
- **and 支持**
516+
- not 不支持
517+
- only 不支持
518+
- or 不支持
519+
### 使用示例
520+
```css
521+
/* 支持 */
522+
@media screen and (min-width: 900px) {}
523+
@media (max-width: 12450px) { }
524+
@media screen and (min-width: 320px) and (max-width: 480px) {}
525+
/* 不支持 */
526+
/* 单位仅支持px */
527+
@media (max-width: 30em) { }
528+
@media (min-width: 30em) and (max-width: 50em) { }
529+
/* 媒体查询 4 级规范,暂不支持 */
530+
@media (width <= 30em) { }
531+
@media (30em <= width <= 50em ) { }
532+
```
533+
502534
## 原子类
503535

504536
> 原子类功能正在开发中,敬请期待后续版本支持。

packages/core/src/platform/builtInMixins/styleHelperMixin.ios.js

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,75 @@
1-
import { isObject, isArray, dash2hump, cached, isEmptyObject, hasOwn } from '@mpxjs/utils'
2-
import { Dimensions, StyleSheet } from 'react-native'
1+
import { isObject, isArray, dash2hump, cached, isEmptyObject, hasOwn, getFocusedNavigation, noop } from '@mpxjs/utils'
2+
import { StyleSheet, Dimensions } from 'react-native'
3+
import { reactive } from '../../observer/reactive'
34
import Mpx from '../../index'
45

5-
const rawDimensions = {
6-
screen: Dimensions.get('screen'),
7-
window: Dimensions.get('window')
6+
global.__mpxAppDimensionsInfo = {
7+
window: Dimensions.get('window'),
8+
screen: Dimensions.get('screen')
89
}
9-
let width, height
10+
global.__mpxSizeCount = 0
11+
global.__mpxPageSizeCountMap = reactive({})
1012

11-
function customDimensions (dimensions) {
13+
global.__classCaches = []
14+
global.__GCC = function (className, classMap, classMapValueCache) {
15+
if (!classMapValueCache.get(className)) {
16+
const styleObj = classMap[className]?.()
17+
styleObj && classMapValueCache.set(className, styleObj)
18+
}
19+
return classMapValueCache.get(className)
20+
}
21+
22+
let dimensionsInfoInitialized = false
23+
function useDimensionsInfo (dimensions) {
24+
dimensionsInfoInitialized = true
1225
if (typeof Mpx.config.rnConfig?.customDimensions === 'function') {
1326
dimensions = Mpx.config.rnConfig.customDimensions(dimensions) || dimensions
1427
}
15-
width = dimensions.screen.width
16-
height = dimensions.screen.height
28+
global.__mpxAppDimensionsInfo.window = dimensions.window
29+
global.__mpxAppDimensionsInfo.screen = dimensions.screen
1730
}
1831

19-
Dimensions.addEventListener('change', customDimensions)
32+
function getPageSize (window = global.__mpxAppDimensionsInfo.screen) {
33+
return window.width + 'x' + window.height
34+
}
35+
36+
Dimensions.addEventListener('change', ({ window, screen }) => {
37+
const oldScreen = getPageSize(global.__mpxAppDimensionsInfo.screen)
38+
useDimensionsInfo({ window, screen })
39+
40+
// 对比 screen 高宽是否存在变化
41+
if (getPageSize(screen) === oldScreen) return
2042

43+
global.__classCaches?.forEach(cache => cache?.clear())
44+
45+
// 更新全局和栈顶页面的标记,其他后台页面的标记在show之后更新
46+
global.__mpxSizeCount++
47+
48+
const navigation = getFocusedNavigation()
49+
50+
if (navigation) {
51+
global.__mpxPageSizeCountMap[navigation.pageId] = global.__mpxSizeCount
52+
if (hasOwn(global.__mpxPageStatusMap, navigation.pageId)) {
53+
global.__mpxPageStatusMap[navigation.pageId] = `resize${global.__mpxSizeCount}`
54+
}
55+
}
56+
})
57+
58+
// TODO: 1 目前测试鸿蒙下折叠屏screen固定为展开状态下屏幕尺寸,仅window会变化,且window包含状态栏高度
59+
// TODO: 2 存在部分安卓折叠屏机型在折叠/展开切换时,Dimensions监听到的width/height尺寸错误,并触发多次问题
2160
function rpx (value) {
61+
const screenInfo = global.__mpxAppDimensionsInfo.screen
2262
// rn 单位 dp = 1(css)px = 1 物理像素 * pixelRatio(像素比)
2363
// px = rpx * (750 / 屏幕宽度)
24-
return value * width / 750
64+
return value * screenInfo.width / 750
2565
}
2666
function vw (value) {
27-
return value * width / 100
67+
const screenInfo = global.__mpxAppDimensionsInfo.screen
68+
return value * screenInfo.width / 100
2869
}
2970
function vh (value) {
30-
return value * height / 100
71+
const screenInfo = global.__mpxAppDimensionsInfo.screen
72+
return value * screenInfo.height / 100
3173
}
3274

3375
const unit = {
@@ -38,8 +80,14 @@ const unit = {
3880

3981
const empty = {}
4082

41-
function formatValue (value) {
42-
if (width === undefined) customDimensions(rawDimensions)
83+
function formatValue (value, unitType) {
84+
if (!dimensionsInfoInitialized) useDimensionsInfo(global.__mpxAppDimensionsInfo)
85+
if (unitType === 'hairlineWidth') {
86+
return StyleSheet.hairlineWidth
87+
}
88+
if (unitType && typeof unit[unitType] === 'function') {
89+
return unit[unitType](+value)
90+
}
4391
const matched = unitRegExp.exec(value)
4492
if (matched) {
4593
if (!matched[2] || matched[2] === 'px') {
@@ -180,29 +228,58 @@ function isNativeStyle (style) {
180228
)
181229
}
182230

231+
function getMediaStyle (media) {
232+
if (!media || !media.length) return {}
233+
const { width } = global.__mpxAppDimensionsInfo.screen
234+
return media.reduce((styleObj, item) => {
235+
const { options = {}, value = {} } = item
236+
const { minWidth, maxWidth } = options
237+
if (!isNaN(minWidth) && !isNaN(maxWidth) && width >= minWidth && width <= maxWidth) {
238+
Object.assign(styleObj, value)
239+
} else if (!isNaN(minWidth) && width >= minWidth) {
240+
Object.assign(styleObj, value)
241+
} else if (!isNaN(maxWidth) && width <= maxWidth) {
242+
Object.assign(styleObj, value)
243+
}
244+
return styleObj
245+
}, {})
246+
}
247+
183248
export default function styleHelperMixin () {
184249
return {
185250
methods: {
251+
__getSizeCount () {
252+
return global.__mpxPageSizeCountMap[this.__pageId]
253+
},
186254
__getClass (staticClass, dynamicClass) {
187255
return concat(staticClass, stringifyDynamicClass(dynamicClass))
188256
},
189257
__getStyle (staticClass, dynamicClass, staticStyle, dynamicStyle, hide) {
190258
const isNativeStaticStyle = staticStyle && isNativeStyle(staticStyle)
191259
let result = isNativeStaticStyle ? [] : {}
192-
const mergeResult = isNativeStaticStyle ? (o) => result.push(o) : (o) => Object.assign(result, o)
193-
194-
const classMap = this.__getClassMap?.() || {}
195-
const appClassMap = global.__getAppClassMap?.() || {}
260+
const mergeResult = isNativeStaticStyle ? (...args) => result.push(...args) : (...args) => Object.assign(result, ...args)
261+
// 使用一下 __getSizeCount 触发其 get
262+
this.__getSizeCount()
196263

197264
if (staticClass || dynamicClass) {
198265
// todo 当前为了复用小程序unocss产物,暂时进行mpEscape,等后续正式支持unocss后可不进行mpEscape
199266
const classString = mpEscape(concat(staticClass, stringifyDynamicClass(dynamicClass)))
267+
200268
classString.split(/\s+/).forEach((className) => {
201-
if (classMap[className]) {
202-
mergeResult(classMap[className])
203-
} else if (appClassMap[className]) {
204-
// todo 全局样式在每个页面和组件中生效,以支持全局原子类,后续支持样式模块复用后可考虑移除
205-
mergeResult(appClassMap[className])
269+
let localStyle, appStyle
270+
const getAppClassStyle = global.__getAppClassStyle || noop
271+
if (localStyle = this.__getClassStyle(className)) {
272+
if (localStyle._media?.length) {
273+
mergeResult(localStyle._default, getMediaStyle(localStyle._media))
274+
} else {
275+
mergeResult(localStyle._default)
276+
}
277+
} else if (appStyle = getAppClassStyle(className)) {
278+
if (appStyle._media?.length) {
279+
mergeResult(appStyle._default, getMediaStyle(appStyle._media))
280+
} else {
281+
mergeResult(appStyle._default)
282+
}
206283
} else if (isObject(this.__props[className])) {
207284
// externalClasses必定以对象形式传递下来
208285
mergeResult(this.__props[className])

packages/core/src/platform/createApp.ios.js

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ import MpxNav from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-
1414

1515
const appHooksMap = makeMap(mergeLifecycle(LIFECYCLE).app)
1616

17-
function getPageSize (window = ReactNative.Dimensions.get('window')) {
18-
return window.width + 'x' + window.height
19-
}
20-
2117
function filterOptions (options, appData) {
2218
const newOptions = {}
2319
Object.keys(options).forEach(key => {
@@ -208,22 +204,9 @@ export default function createApp (options) {
208204
if (Mpx.config.rnConfig.disableAppStateListener) return
209205
onAppStateChange(state)
210206
})
211-
212-
let count = 0
213-
let lastPageSize = getPageSize()
214-
const resizeSubScription = ReactNative.Dimensions.addEventListener('change', ({ window }) => {
215-
const pageSize = getPageSize(window)
216-
if (pageSize === lastPageSize) return
217-
lastPageSize = pageSize
218-
const navigation = getFocusedNavigation()
219-
if (navigation && hasOwn(global.__mpxPageStatusMap, navigation.pageId)) {
220-
global.__mpxPageStatusMap[navigation.pageId] = `resize${count++}`
221-
}
222-
})
223207
return () => {
224208
appState.state = 'exit'
225209
changeSubscription && changeSubscription.remove()
226-
resizeSubScription && resizeSubScription.remove()
227210
}
228211
}, [])
229212

packages/core/src/platform/patch/getDefaultOptions.ios.js

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import { PortalHost, useSafeAreaInsets } from '../env/navigationHelper'
2020
import { useInnerHeaderHeight } from '@mpxjs/webpack-plugin/lib/runtime/components/react/dist/mpx-nav'
2121

2222
function getSystemInfo () {
23-
const windowDimensions = ReactNative.Dimensions.get('window')
24-
const screenDimensions = ReactNative.Dimensions.get('screen')
23+
const windowDimensions = global.__mpxAppDimensionsInfo.window
24+
const screenDimensions = global.__mpxAppDimensionsInfo.screen
2525
return {
2626
deviceOrientation: windowDimensions.width > windowDimensions.height ? 'landscape' : 'portrait',
2727
size: {
@@ -302,6 +302,7 @@ function createInstance ({ propsRef, type, rawOptions, currentInject, validProps
302302
global.__mpxPagesMap = global.__mpxPagesMap || {}
303303
global.__mpxPagesMap[props.route.key] = [instance, props.navigation]
304304
setFocusedNavigation(props.navigation)
305+
set(global.__mpxPageSizeCountMap, pageId, global.__mpxSizeCount)
305306
// App onLaunch 在 Page created 之前执行
306307
if (!global.__mpxAppHotLaunched && global.__mpxAppOnLaunch) {
307308
global.__mpxAppOnLaunch(props.navigation)
@@ -375,9 +376,18 @@ const triggerPageStatusHook = (mpxProxy, event) => {
375376
}
376377
}
377378

378-
const triggerResizeEvent = (mpxProxy) => {
379-
const type = mpxProxy.options.__type__
379+
const triggerResizeEvent = (mpxProxy, sizeRef) => {
380+
const oldSize = sizeRef.current.size
380381
const systemInfo = getSystemInfo()
382+
const newSize = systemInfo.size
383+
384+
if (oldSize && oldSize.windowWidth === newSize.windowWidth && oldSize.windowHeight === newSize.windowHeight) {
385+
return
386+
}
387+
388+
Object.assign(sizeRef.current, systemInfo)
389+
390+
const type = mpxProxy.options.__type__
381391
const target = mpxProxy.target
382392
mpxProxy.callHook(ONRESIZE, [systemInfo])
383393
if (type === 'page') {
@@ -389,6 +399,8 @@ const triggerResizeEvent = (mpxProxy) => {
389399
}
390400

391401
function usePageEffect (mpxProxy, pageId) {
402+
const sizeRef = useRef(getSystemInfo())
403+
392404
useEffect(() => {
393405
let unWatch
394406
const hasShowHook = hasPageHook(mpxProxy, [ONSHOW, 'show'])
@@ -399,21 +411,29 @@ function usePageEffect (mpxProxy, pageId) {
399411
unWatch = watch(() => pageStatusMap[pageId], (newVal) => {
400412
if (newVal === 'show' || newVal === 'hide') {
401413
triggerPageStatusHook(mpxProxy, newVal)
414+
// 仅在尺寸确实变化时才触发resize事件
415+
triggerResizeEvent(mpxProxy, sizeRef)
416+
417+
// 如果当前全局size与pagesize不一致,在show之后触发一次resize事件
418+
if (newVal === 'show' && global.__mpxPageSizeCountMap[pageId] !== global.__mpxSizeCount) {
419+
// 刷新__mpxPageSizeCountMap, 每个页面仅会执行一次,直接驱动render刷新
420+
global.__mpxPageSizeCountMap[pageId] = global.__mpxSizeCount
421+
}
402422
} else if (/^resize/.test(newVal)) {
403-
triggerResizeEvent(mpxProxy)
423+
triggerResizeEvent(mpxProxy, sizeRef)
404424
}
405425
}, { sync: true })
406426
}
407427
}
408428
return () => {
409429
unWatch && unWatch()
430+
del(global.__mpxPageSizeCountMap, pageId)
410431
}
411432
}, [])
412433
}
413434

414435
let pageId = 0
415436
const pageStatusMap = global.__mpxPageStatusMap = reactive({})
416-
417437
function usePageStatus (navigation, pageId) {
418438
navigation.pageId = pageId
419439
if (!hasOwn(pageStatusMap, pageId)) {
@@ -519,7 +539,7 @@ export function PageWrapperHOC (WrappedComponent, pageConfig = {}) {
519539
navigation.layout = getLayoutData(headerHeight)
520540

521541
useEffect(() => {
522-
const dimensionListener = ReactNative.Dimensions.addEventListener('change', ({ screen }) => {
542+
const dimensionListener = ReactNative.Dimensions.addEventListener('change', ({ window, screen }) => {
523543
navigation.layout = getLayoutData(headerHeight)
524544
})
525545
return () => dimensionListener?.remove()

packages/webpack-plugin/lib/platform/style/wx/index.js

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const { hump2dash } = require('../../../utils/hump-dash')
2+
const { parseValues } = require('../../../utils/string')
23

34
module.exports = function getSpec ({ warn, error }) {
45
// React Native 双端都不支持的 CSS property
@@ -88,28 +89,6 @@ module.exports = function getSpec ({ warn, error }) {
8889
if (rule[1].test(prop)) return rule[0]
8990
}
9091
}
91-
// 多value解析
92-
const parseValues = (str, char = ' ') => {
93-
let stack = 0
94-
let temp = ''
95-
const result = []
96-
for (let i = 0; i < str.length; i++) {
97-
if (str[i] === '(') {
98-
stack++
99-
} else if (str[i] === ')') {
100-
stack--
101-
}
102-
// 非括号内 或者 非分隔字符且非空
103-
if (stack !== 0 || (str[i] !== char && str[i] !== ' ')) {
104-
temp += str[i]
105-
}
106-
if ((stack === 0 && str[i] === char) || i === str.length - 1) {
107-
result.push(temp)
108-
temp = ''
109-
}
110-
}
111-
return result
112-
}
11392
// const getDefaultValueFromVar = (str) => {
11493
// const totalVarExp = /^var\((.+)\)$/
11594
// if (!totalVarExp.test(str)) return str

0 commit comments

Comments
 (0)