@mpxjs/webpack-plugin 2.10.17-beta.7 → 2.10.17-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,437 @@
1
+ import React, { forwardRef, useRef, useState, useEffect, useMemo, createElement, useImperativeHandle, useCallback } from 'react'
2
+ import { SectionList, RefreshControl, NativeSyntheticEvent, NativeScrollEvent } from 'react-native'
3
+ import useInnerProps, { getCustomEvent } from './getInnerListeners'
4
+ import { extendObject, useLayout, useTransformStyle } from './utils'
5
+ interface ListItem {
6
+ isSectionHeader?: boolean;
7
+ _originalItemIndex?: number;
8
+ [key: string]: any;
9
+ }
10
+
11
+ interface Section {
12
+ headerData: ListItem | null;
13
+ data: ListItem[];
14
+ hasSectionHeader?: boolean;
15
+ _originalItemIndex?: number;
16
+ }
17
+
18
+ interface ItemHeightType {
19
+ value?: number;
20
+ getter?: (item: any, index: number) => number;
21
+ }
22
+
23
+ interface RecycleViewProps {
24
+ enhanced?: boolean;
25
+ bounces?: boolean;
26
+ scrollEventThrottle?: number;
27
+ height?: number | string;
28
+ width?: number | string;
29
+ listData?: ListItem[];
30
+ generichash?: string;
31
+ style?: Record<string, any>;
32
+ itemHeight?: ItemHeightType;
33
+ sectionHeaderHeight?: ItemHeightType;
34
+ listHeaderData?: any;
35
+ listHeaderHeight?: ItemHeightType;
36
+ useListHeader?: boolean;
37
+ 'genericrecycle-item'?: string;
38
+ 'genericsection-header'?: string;
39
+ 'genericlist-header'?: string;
40
+ 'enable-var'?: boolean;
41
+ 'external-var-context'?: any;
42
+ 'parent-font-size'?: number;
43
+ 'parent-width'?: number;
44
+ 'parent-height'?: number;
45
+ 'enable-sticky'?: boolean;
46
+ 'enable-back-to-top'?: boolean;
47
+ 'end-reached-threshold'?: number;
48
+ 'refresher-enabled'?: boolean;
49
+ 'show-scrollbar'?: boolean;
50
+ 'refresher-triggered'?: boolean;
51
+ bindrefresherrefresh?: (event: any) => void;
52
+ bindscrolltolower?: (event: any) => void;
53
+ bindscroll?: (event: any) => void;
54
+ [key: string]: any;
55
+ }
56
+
57
+ interface ScrollPositionParams {
58
+ index: number;
59
+ animated?: boolean;
60
+ viewOffset?: number;
61
+ viewPosition?: number;
62
+ }
63
+
64
+ const getGeneric = (generichash: string, generickey: string) => {
65
+ if (!generichash || !generickey) return null
66
+ const GenericComponent = global.__mpxGenericsMap?.[generichash]?.[generickey]?.()
67
+ if (!GenericComponent) return null
68
+
69
+ return forwardRef((props: any, ref: any) => {
70
+ return createElement(GenericComponent, extendObject({}, {
71
+ ref: ref
72
+ }, props))
73
+ })
74
+ }
75
+
76
+ const getListHeaderComponent = (generichash: string, generickey: string, data: any) => {
77
+ if (!generichash || !generickey) return undefined
78
+ const ListHeaderComponent = getGeneric(generichash, generickey)
79
+ return ListHeaderComponent ? createElement(ListHeaderComponent, { listHeaderData: data }) : null
80
+ }
81
+
82
+ const getSectionHeaderRenderer = (generichash: string, generickey: string) => {
83
+ if (!generichash || !generickey) return undefined
84
+ return (sectionData: { section: Section }) => {
85
+ if (!sectionData.section.hasSectionHeader) return null
86
+ const SectionHeaderComponent = getGeneric(generichash, generickey)
87
+ return SectionHeaderComponent ? createElement(SectionHeaderComponent, { itemData: sectionData.section.headerData }) : null
88
+ }
89
+ }
90
+
91
+ const getItemRenderer = (generichash: string, generickey: string) => {
92
+ if (!generichash || !generickey) return undefined
93
+ return ({ item }: { item: any }) => {
94
+ const ItemComponent = getGeneric(generichash, generickey)
95
+ return ItemComponent ? createElement(ItemComponent, { itemData: item }) : null
96
+ }
97
+ }
98
+
99
+ const RecycleView = forwardRef<any, RecycleViewProps>((props = {}, ref) => {
100
+ const {
101
+ enhanced = false,
102
+ bounces = true,
103
+ scrollEventThrottle = 0,
104
+ height,
105
+ width,
106
+ listData,
107
+ generichash,
108
+ style = {},
109
+ itemHeight = {},
110
+ sectionHeaderHeight = {},
111
+ listHeaderHeight = {},
112
+ listHeaderData = null,
113
+ useListHeader = false,
114
+ 'genericrecycle-item': genericrecycleItem,
115
+ 'genericsection-header': genericsectionHeader,
116
+ 'genericlist-header': genericListHeader,
117
+ 'enable-var': enableVar,
118
+ 'external-var-context': externalVarContext,
119
+ 'parent-font-size': parentFontSize,
120
+ 'parent-width': parentWidth,
121
+ 'parent-height': parentHeight,
122
+ 'enable-sticky': enableSticky = false,
123
+ 'enable-back-to-top': enableBackToTop = false,
124
+ 'end-reached-threshold': onEndReachedThreshold = 0.1,
125
+ 'refresher-enabled': refresherEnabled,
126
+ 'show-scrollbar': showScrollbar = true,
127
+ 'refresher-triggered': refresherTriggered
128
+ } = props
129
+
130
+ const [refreshing, setRefreshing] = useState(!!refresherTriggered)
131
+
132
+ const scrollViewRef = useRef<any>(null)
133
+
134
+ const indexMap = useRef<{ [key: string]: string | number }>({})
135
+
136
+ const reverseIndexMap = useRef<{ [key: string]: number }>({})
137
+
138
+ // 缓存 getGeneric 的结果,避免每次都创建新的组件引用
139
+ const genericComponentsCache = useRef<{ [key: string]: any }>({})
140
+ const getCachedGeneric = useCallback((generichash: string, generickey: string) => {
141
+ if (!generichash || !generickey) return null
142
+
143
+ const cacheKey = `${generichash}_${generickey}`
144
+ if (!genericComponentsCache.current[cacheKey]) {
145
+ genericComponentsCache.current[cacheKey] = getGeneric(generichash, generickey)
146
+ }
147
+ return genericComponentsCache.current[cacheKey]
148
+ }, [])
149
+
150
+ // 使用 ref 存储最新的 props,避免渲染函数引用变化导致组件重新挂载
151
+ const propsRef = useRef({ generichash, genericrecycleItem, genericsectionHeader, genericListHeader, listHeaderData, useListHeader })
152
+
153
+ propsRef.current = { generichash, genericrecycleItem, genericsectionHeader, genericListHeader, listHeaderData, useListHeader }
154
+
155
+ // 创建稳定的渲染函数引用,使用 getCachedGeneric 确保组件引用稳定
156
+ const stableItemRenderer = useCallback(({ item }: { item: any }) => {
157
+ const { generichash: hash, genericrecycleItem: key } = propsRef.current
158
+ const ItemComponent = getCachedGeneric(hash, key)
159
+ return ItemComponent ? createElement(ItemComponent, { itemData: item }) : null
160
+ }, [getCachedGeneric])
161
+
162
+ const stableSectionHeaderRenderer = useCallback((sectionData: { section: Section }) => {
163
+ if (!sectionData.section.hasSectionHeader) return null
164
+ const { generichash: hash, genericsectionHeader: key } = propsRef.current
165
+ const SectionHeaderComponent = getCachedGeneric(hash, key)
166
+ return SectionHeaderComponent ? createElement(SectionHeaderComponent, { itemData: sectionData.section.headerData }) : null
167
+ }, [getCachedGeneric])
168
+
169
+ // 创建稳定的 ListHeader 渲染函数,与 section-header 和 item 的处理方式一致
170
+ const getStableListHeader = useCallback(() => {
171
+ const { generichash: hash, genericListHeader: key, listHeaderData: data, useListHeader: use } = propsRef.current
172
+ if (!use || !hash || !key) return null
173
+ const ListHeaderComponent = getCachedGeneric(hash, key)
174
+ return ListHeaderComponent ? createElement(ListHeaderComponent, { listHeaderData: data }) : null
175
+ }, [getCachedGeneric])
176
+
177
+ const {
178
+ hasSelfPercent,
179
+ setWidth,
180
+ setHeight
181
+ } = useTransformStyle(style, { enableVar, externalVarContext, parentFontSize, parentWidth, parentHeight })
182
+
183
+ const { layoutRef, layoutStyle, layoutProps } = useLayout({ props, hasSelfPercent, setWidth, setHeight, nodeRef: scrollViewRef })
184
+
185
+ useEffect(() => {
186
+ if (refreshing !== refresherTriggered) {
187
+ setRefreshing(!!refresherTriggered)
188
+ }
189
+ }, [refresherTriggered])
190
+
191
+ const onRefresh = () => {
192
+ const { bindrefresherrefresh } = props
193
+ bindrefresherrefresh &&
194
+ bindrefresherrefresh(
195
+ getCustomEvent('refresherrefresh', {}, { layoutRef }, props)
196
+ )
197
+ }
198
+
199
+ const onEndReached = () => {
200
+ const { bindscrolltolower } = props
201
+ bindscrolltolower &&
202
+ bindscrolltolower(
203
+ getCustomEvent('scrolltolower', {}, { layoutRef }, props)
204
+ )
205
+ }
206
+
207
+ const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
208
+ const { bindscroll } = props
209
+ bindscroll &&
210
+ bindscroll(
211
+ getCustomEvent('scroll', event.nativeEvent, { layoutRef }, props)
212
+ )
213
+ }
214
+
215
+ // 通过sectionIndex和rowIndex获取原始索引
216
+ const getOriginalIndex = (sectionIndex: number, rowIndex: number | 'header'): number => {
217
+ const key = `${sectionIndex}_${rowIndex}`
218
+ return reverseIndexMap.current[key] ?? -1 // 如果找不到,返回-1
219
+ }
220
+
221
+ const scrollToIndex = ({ index, animated, viewOffset = 0, viewPosition = 0 }: ScrollPositionParams) => {
222
+ if (scrollViewRef.current) {
223
+ // 通过索引映射表快速定位位置
224
+ const position = indexMap.current[index]
225
+ const [sectionIndex, itemIndex] = (position as string).split('_')
226
+ scrollViewRef.current.scrollToLocation?.({
227
+ itemIndex: itemIndex === 'header' ? 0 : Number(itemIndex) + 1,
228
+ sectionIndex: Number(sectionIndex) || 0,
229
+ animated,
230
+ viewOffset,
231
+ viewPosition
232
+ })
233
+ }
234
+ }
235
+
236
+ const getItemHeight = ({ sectionIndex, rowIndex }: { sectionIndex: number, rowIndex: number }) => {
237
+ if (!itemHeight) {
238
+ return 0
239
+ }
240
+ if ((itemHeight as ItemHeightType).getter) {
241
+ const item = convertedListData[sectionIndex].data[rowIndex]
242
+ // 使用getOriginalIndex获取原始索引
243
+ const originalIndex = getOriginalIndex(sectionIndex, rowIndex)
244
+ return (itemHeight as ItemHeightType).getter?.(item, originalIndex) || 0
245
+ } else {
246
+ return (itemHeight as ItemHeightType).value || 0
247
+ }
248
+ }
249
+
250
+ const getSectionHeaderHeight = ({ sectionIndex }: { sectionIndex: number }) => {
251
+ const item = convertedListData[sectionIndex]
252
+ const { hasSectionHeader } = item
253
+ // 使用getOriginalIndex获取原始索引
254
+ const originalIndex = getOriginalIndex(sectionIndex, 'header')
255
+ if (!hasSectionHeader) return 0
256
+ if ((sectionHeaderHeight as ItemHeightType).getter) {
257
+ return (sectionHeaderHeight as ItemHeightType).getter?.(item, originalIndex) || 0
258
+ } else {
259
+ return (sectionHeaderHeight as ItemHeightType).value || 0
260
+ }
261
+ }
262
+
263
+ const convertedListData = useMemo(() => {
264
+ const sections: Section[] = []
265
+ let currentSection: Section | null = null
266
+ // 清空之前的索引映射
267
+ indexMap.current = {}
268
+ // 清空反向索引映射
269
+ reverseIndexMap.current = {}
270
+ listData.forEach((item: ListItem, index: number) => {
271
+ if (item.isSectionHeader) {
272
+ // 如果已经存在一个 section,先把它添加到 sections 中
273
+ if (currentSection) {
274
+ sections.push(currentSection)
275
+ }
276
+ // 创建新的 section
277
+ currentSection = {
278
+ headerData: item,
279
+ data: [],
280
+ hasSectionHeader: true,
281
+ _originalItemIndex: index
282
+ }
283
+ // 为 section header 添加索引映射
284
+ const sectionIndex = sections.length
285
+ indexMap.current[index] = `${sectionIndex}_header`
286
+ // 添加反向索引映射
287
+ reverseIndexMap.current[`${sectionIndex}_header`] = index
288
+ } else {
289
+ // 如果没有当前 section,创建一个默认的
290
+ if (!currentSection) {
291
+ // 创建默认section (无header的section)
292
+ currentSection = {
293
+ headerData: null,
294
+ data: [],
295
+ hasSectionHeader: false,
296
+ _originalItemIndex: -1
297
+ }
298
+ }
299
+ // 将 item 添加到当前 section 的 data 中
300
+ const itemIndex = currentSection.data.length
301
+ currentSection.data.push(extendObject({}, item, {
302
+ _originalItemIndex: index
303
+ }))
304
+ let sectionIndex
305
+ // 为 item 添加索引映射 - 存储格式为: "sectionIndex_itemIndex"
306
+ if (!currentSection.hasSectionHeader && sections.length === 0) {
307
+ // 在默认section中(第一个且无header)
308
+ sectionIndex = 0
309
+ indexMap.current[index] = `${sectionIndex}_${itemIndex}`
310
+ } else {
311
+ // 在普通section中
312
+ sectionIndex = sections.length
313
+ indexMap.current[index] = `${sectionIndex}_${itemIndex}`
314
+ }
315
+ // 添加反向索引映射
316
+ reverseIndexMap.current[`${sectionIndex}_${itemIndex}`] = index
317
+ }
318
+ })
319
+ // 添加最后一个 section
320
+ if (currentSection) {
321
+ sections.push(currentSection)
322
+ }
323
+ return sections
324
+ }, [listData])
325
+
326
+ const { getItemLayout } = useMemo(() => {
327
+ const layouts: Array<{ length: number, offset: number, index: number }> = []
328
+ let offset = 0
329
+
330
+ if (useListHeader) {
331
+ // 计算列表头部的高度
332
+ offset += listHeaderHeight.getter?.() || listHeaderHeight.value || 0
333
+ }
334
+
335
+ // 遍历所有 sections
336
+ convertedListData.forEach((section: Section, sectionIndex: number) => {
337
+ // 添加 section header 的位置信息
338
+ const headerHeight = getSectionHeaderHeight({ sectionIndex })
339
+ layouts.push({
340
+ length: headerHeight,
341
+ offset,
342
+ index: layouts.length
343
+ })
344
+ offset += headerHeight
345
+
346
+ // 添加该 section 中所有 items 的位置信息
347
+ section.data.forEach((item: ListItem, itemIndex: number) => {
348
+ const contenteight = getItemHeight({ sectionIndex, rowIndex: itemIndex })
349
+ layouts.push({
350
+ length: contenteight,
351
+ offset,
352
+ index: layouts.length
353
+ })
354
+ offset += contenteight
355
+ })
356
+
357
+ // 添加该 section 尾部位置信息
358
+ // 因为即使 sectionList 没传 renderSectionFooter,getItemLayout 中的 index 的计算也会包含尾部节点
359
+ layouts.push({
360
+ length: 0,
361
+ offset,
362
+ index: layouts.length
363
+ })
364
+ })
365
+ return {
366
+ itemLayouts: layouts,
367
+ getItemLayout: (data: any, index: number) => layouts[index]
368
+ }
369
+ }, [convertedListData, useListHeader, itemHeight.value, itemHeight.getter, sectionHeaderHeight.value, sectionHeaderHeight.getter, listHeaderHeight.value, listHeaderHeight.getter])
370
+
371
+ const scrollAdditionalProps = extendObject(
372
+ {
373
+ alwaysBounceVertical: false,
374
+ alwaysBounceHorizontal: false,
375
+ scrollEventThrottle: scrollEventThrottle,
376
+ scrollsToTop: enableBackToTop,
377
+ showsHorizontalScrollIndicator: showScrollbar,
378
+ onEndReachedThreshold,
379
+ ref: scrollViewRef,
380
+ bounces: false,
381
+ stickySectionHeadersEnabled: enableSticky,
382
+ onScroll: onScroll,
383
+ onEndReached: onEndReached
384
+ },
385
+ layoutProps
386
+ )
387
+
388
+ if (enhanced) {
389
+ Object.assign(scrollAdditionalProps, {
390
+ bounces
391
+ })
392
+ }
393
+ if (refresherEnabled) {
394
+ Object.assign(scrollAdditionalProps, {
395
+ refreshing: refreshing
396
+ })
397
+ }
398
+
399
+ useImperativeHandle(ref, () => {
400
+ return {
401
+ ...props,
402
+ scrollToIndex
403
+ }
404
+ })
405
+
406
+ const innerProps = useInnerProps(extendObject({}, props, scrollAdditionalProps), [
407
+ 'id',
408
+ 'show-scrollbar',
409
+ 'lower-threshold',
410
+ 'refresher-triggered',
411
+ 'refresher-enabled',
412
+ 'bindrefresherrefresh'
413
+ ], { layoutRef })
414
+
415
+ return createElement(
416
+ SectionList,
417
+ extendObject(
418
+ {
419
+ style: [{ height, width }, style, layoutStyle],
420
+ sections: convertedListData,
421
+ renderItem: stableItemRenderer,
422
+ getItemLayout: getItemLayout,
423
+ ListHeaderComponent: getStableListHeader(),
424
+ renderSectionHeader: stableSectionHeaderRenderer,
425
+ refreshControl: refresherEnabled
426
+ ? React.createElement(RefreshControl, {
427
+ onRefresh: onRefresh,
428
+ refreshing: refreshing
429
+ })
430
+ : undefined
431
+ },
432
+ innerProps
433
+ )
434
+ )
435
+ })
436
+
437
+ export default RecycleView
@@ -259,6 +259,7 @@ const _ScrollView = forwardRef<HandlerRef<ScrollView & View, ScrollViewProps>, S
259
259
 
260
260
  // layout 完成前先隐藏,避免安卓闪烁问题
261
261
  const refresherLayoutStyle = useMemo(() => { return !hasRefresherLayoutRef.current ? HIDDEN_STYLE : {} }, [hasRefresherLayoutRef.current])
262
+
262
263
  const lastOffset = useRef(0)
263
264
 
264
265
  if (scrollX && scrollY) {
@@ -1,9 +1,9 @@
1
-
2
1
  import { useRef, forwardRef, createElement, ReactNode, useCallback, useMemo } from 'react'
3
2
  import { View, ViewStyle } from 'react-native'
4
3
  import useNodesRef, { HandlerRef } from './useNodesRef'
5
4
  import { splitProps, splitStyle, useTransformStyle, wrapChildren, useLayout, extendObject } from './utils'
6
5
  import { StickyContext } from './context'
6
+
7
7
  import useInnerProps from './getInnerListeners'
8
8
 
9
9
  interface StickySectionProps {