|
@@ -1,75 +1,107 @@
|
1
|
1
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
2
|
2
|
import Taro from '@tarojs/taro';
|
3
|
3
|
import { View } from '@tarojs/components';
|
4
|
|
-import Item from './Item';
|
|
4
|
+import usePrevious from '@/utils/hooks/usePrevious';
|
|
5
|
+import Column from './Column';
|
5
|
6
|
import { useList } from './hooks';
|
6
|
7
|
import { classNames, getRect } from './utils';
|
7
|
8
|
|
8
|
9
|
import './style.less'
|
9
|
10
|
|
|
11
|
+/**
|
|
12
|
+ * 核心原理, 左右2栏,一个个放元素,同时记录高度,优先放入低高度区域
|
|
13
|
+ * 元素高度获取,主要是通过图片的 onLoad 来确保加载完成
|
|
14
|
+ *
|
|
15
|
+ * 复杂度, 多数据公用 list,
|
|
16
|
+ * 需要区分是否更换数据源,是否未更换数据源,只是新增了数据
|
|
17
|
+ */
|
|
18
|
+
|
10
|
19
|
export default (props) => {
|
11
|
|
- const { className, itemClassName, style, gutter = 0, list, render, ...leftProps } = props
|
|
20
|
+ const { className, listData, itemKey, render } = props
|
|
21
|
+
|
|
22
|
+ // rfTimes 用来区分是否更换数据源
|
|
23
|
+ const { list, rfTimes: refresh } = listData
|
|
24
|
+
|
|
25
|
+ // 用来绘制页面的数据
|
|
26
|
+ const renderListRef = useRef([])
|
12
|
27
|
|
13
|
|
- const splitStyle = { width: `${gutter}rpx` }
|
|
28
|
+ const lastList = usePrevious(list)
|
|
29
|
+ const lastRf = usePrevious(refresh)
|
14
|
30
|
|
15
|
|
- const leftBottom = useRef(0)
|
16
|
|
- const rightBottom = useRef(0)
|
|
31
|
+ // 记录左右2栏的数据及高度
|
|
32
|
+ const leftHeight = useRef(0)
|
|
33
|
+ const rightHeight = useRef(0)
|
17
|
34
|
const [leftList, leftRef, setLeftList] = useList()
|
18
|
35
|
const [rightList, rightRef, setRightList] = useList()
|
19
|
|
- const [cursor, setCursor] = useState(0)
|
20
|
36
|
|
21
|
|
- const listRef = useRef([])
|
22
|
|
- listRef.current = list
|
23
|
|
-
|
24
|
|
- const handleRenderFinish = (direct) => (rect) => {
|
25
|
|
- const { bottom } = rect
|
|
37
|
+ // 用来强制更新组件
|
|
38
|
+ const [cursor, setCursor] = useState(-1)
|
26
|
39
|
|
|
40
|
+ // 每个元素渲染完成, 会拿到当前栏的高度
|
|
41
|
+ const handleRenderFinish = (direct) => (height) => {
|
27
|
42
|
if (direct === 'left') {
|
28
|
|
- leftBottom.current = bottom
|
|
43
|
+ leftHeight.current = height
|
29
|
44
|
} else {
|
30
|
|
- rightBottom.current = bottom
|
|
45
|
+ rightHeight.current = height
|
31
|
46
|
}
|
32
|
47
|
|
|
48
|
+ // 触发强制渲染
|
33
|
49
|
setCursor(cursor + 1)
|
34
|
50
|
}
|
35
|
51
|
|
36
|
52
|
useEffect(() => {
|
37
|
|
- setCursor(0)
|
38
|
|
- setLeftList([])
|
39
|
|
- setRightList([])
|
40
|
|
- listRef.current = list ? list.slice() : []
|
|
53
|
+ // 新数据源 = 首次进入 或者 切换数据源
|
|
54
|
+ const isNew = (!lastList?.length && list.length) || (lastRf !== refresh)
|
|
55
|
+
|
|
56
|
+ if (!isNew) {
|
|
57
|
+ // 非新数据源, list 变更那就说明是执行了上拉加载
|
|
58
|
+ const addedList = []
|
|
59
|
+ for (let item of list) {
|
|
60
|
+ const found = lastList.filter(x => x[itemKey] === item[itemKey])[0]
|
|
61
|
+ if (!found) {
|
|
62
|
+ addedList.push(item)
|
|
63
|
+ }
|
|
64
|
+ }
|
|
65
|
+
|
|
66
|
+ // 于是, 只继续绘制变更的那部分
|
|
67
|
+ renderListRef.current = addedList
|
|
68
|
+ setCursor(-2)
|
|
69
|
+ } else {
|
|
70
|
+ setCursor(0)
|
|
71
|
+ setLeftList([])
|
|
72
|
+ setRightList([])
|
|
73
|
+ renderListRef.current = list ? list.slice() : []
|
|
74
|
+ leftHeight.current = 0
|
|
75
|
+ rightHeight.current = 0
|
|
76
|
+
|
|
77
|
+ if (renderListRef.current.length) {
|
|
78
|
+ setCursor(0)
|
|
79
|
+ } else {
|
|
80
|
+ setCursor(-1)
|
|
81
|
+ }
|
|
82
|
+ }
|
41
|
83
|
}, [list])
|
42
|
84
|
|
43
|
85
|
useEffect(() => {
|
44
|
|
- const len = !list ? 0 : list.length;
|
45
|
|
- if (leftRef.current.length + rightRef.current.length >= len) return;
|
|
86
|
+ // 如果当前渲染数据为空
|
|
87
|
+ if (!renderListRef.current.length) return;
|
46
|
88
|
|
47
|
|
- const item = listRef.current.shift()
|
|
89
|
+ // 每次把数组最上面的一个拿出来
|
|
90
|
+ const item = renderListRef.current.shift()
|
48
|
91
|
item.__vid = `f-${Math.random().toString(36).substring(2)}`
|
49
|
92
|
|
50
|
|
- if (leftBottom.current <= rightBottom.current) {
|
|
93
|
+ // 优先放入低高度的栏里面
|
|
94
|
+ if (leftHeight.current <= rightHeight.current) {
|
51
|
95
|
setLeftList([...leftRef.current, item])
|
52
|
96
|
} else {
|
53
|
97
|
setRightList([...rightRef.current, item])
|
54
|
98
|
}
|
55
|
|
- }, [cursor, list])
|
|
99
|
+ }, [cursor])
|
56
|
100
|
|
57
|
101
|
return (
|
58
|
|
- <View
|
59
|
|
- style={style}
|
60
|
|
- className={classNames(className, 'masonry-layout')}
|
61
|
|
- >
|
62
|
|
- <View className='masonry-column'>
|
63
|
|
- {
|
64
|
|
- leftList.map(item => <Item key={item.__vid} className={itemClassName} item={item} render={render} onRenderFinish={handleRenderFinish('left')} />)
|
65
|
|
- }
|
66
|
|
- </View>
|
67
|
|
- <View className='masonry-split' style={splitStyle} />
|
68
|
|
- <View className='masonry-column'>
|
69
|
|
- {
|
70
|
|
- rightList.map(item => <Item key={item.__vid} className={itemClassName} item={item} render={render} onRenderFinish={handleRenderFinish('right')} />)
|
71
|
|
- }
|
72
|
|
- </View>
|
|
102
|
+ <View className={classNames(className, 'masonry-layout')}>
|
|
103
|
+ <Column list={leftList} render={render} onRenderFinish={handleRenderFinish('left')} />
|
|
104
|
+ <Column list={rightList} render={render} onRenderFinish={handleRenderFinish('right')} />
|
73
|
105
|
</View>
|
74
|
106
|
)
|
75
|
107
|
}
|