张延森 пре 3 година
родитељ
комит
7d35060162

+ 3
- 2
src/components/List/index.jsx Прегледај датотеку

@@ -3,6 +3,7 @@ import SpinBox from "@/components/Spin/SpinBox";
3 3
 
4 4
 import { ScrollView } from '@tarojs/components';
5 5
 import Taro from '@tarojs/taro';
6
+import usePrevious from '@/utils/hooks/usePrevious';
6 7
 
7 8
 export default React.forwardRef((props, ref) => {
8 9
   const {
@@ -23,6 +24,7 @@ export default React.forwardRef((props, ref) => {
23 24
   const [list, setList] = useState([])
24 25
   const pageRef = useRef({ current: 1 })
25 26
   const [hasMore, setHasMore] = useState(false)
27
+  const preParams = usePrevious(params)
26 28
 
27 29
   // 滚动
28 30
   const handleScrollToLower = (e) => {
@@ -47,7 +49,7 @@ export default React.forwardRef((props, ref) => {
47 49
       setHasMore(pageInfo.current < pageInfo.pages)
48 50
 
49 51
       if (onDataChange) {
50
-        onDataChange(lst)
52
+        onDataChange(lst, { paramsChanged: preParams !== params })
51 53
       }
52 54
 
53 55
       pageRef.current = pageInfo
@@ -66,7 +68,6 @@ export default React.forwardRef((props, ref) => {
66 68
   const fetchRef = useRef()
67 69
   fetchRef.current = fetchList
68 70
 
69
-  
70 71
   useEffect(() => {
71 72
     pageRef.current.current = 1
72 73
     setHasMore(false)

+ 36
- 0
src/components/MasonryLayout/Column.jsx Прегледај датотеку

@@ -0,0 +1,36 @@
1
+import React, { useEffect, useRef } from 'react'
2
+import { View } from '@tarojs/components'
3
+import Item from './Item'
4
+import { classNames } from './utils'
5
+
6
+export default (props) => {
7
+  const { className, list = [], render, onRenderFinish, ...leftProps } = props
8
+
9
+  const heightRef = useRef(0)
10
+
11
+  const handleRenderFinish = (item, index) => (rect) => {
12
+    const { height } = rect;
13
+    item.__height = height
14
+    
15
+    if (index === 0) {
16
+      heightRef.current = height
17
+    } else {
18
+      heightRef.current += height
19
+    }
20
+
21
+    onRenderFinish(heightRef.current)
22
+  }
23
+
24
+  return (
25
+    <View {...leftProps} className={classNames(className, 'masonry-column')}>
26
+      {
27
+        list.map((item, index) => {
28
+
29
+          return (
30
+            <Item key={item.__vid} item={item} render={render} onRenderFinish={handleRenderFinish(item, index)} />
31
+          )
32
+        })
33
+      }
34
+    </View>
35
+  )
36
+}

+ 9
- 35
src/components/MasonryLayout/Item.jsx Прегледај датотеку

@@ -1,49 +1,23 @@
1 1
 
2
-import React, { useMemo, useEffect } from 'react'
2
+import React, { useMemo } from 'react'
3 3
 import Taro from '@tarojs/taro';
4 4
 import { View } from '@tarojs/components'
5
-import { classNames, getRect } from './utils';
5
+import { classNames } from './utils';
6 6
 import './style.less'
7 7
 
8 8
 export default (props) => {
9
-  const { className, item, render, onRenderFinish } = props
9
+  const { className, item, top, render, onRenderFinish } = props
10 10
 
11
+  // const uqClass = useMemo(() => `f-${Math.random().toString(36).substring(2)}`, [])
11 12
   const vid = item.__vid;
12 13
   
13
-  useEffect(() => {
14
-    Taro.nextTick(() => {
15
-      const calcHeight = () => {
16
-        getRect(`.${vid}`).then((res) => {
17
-          if (!res) {
18
-            // 找不到 node , 则一直重复查询
19
-            const t = setTimeout(() => {
20
-              clearTimeout(t)
21
-              calcHeight()
22
-            }, 100)
23
-          } else {
24
-            const t = setTimeout(() => {
25
-              if (Array.isArray(res)) {
26
-                onRenderFinish(res[0])
27
-              } else {
28
-                onRenderFinish(res)
29
-              }
30
-              clearTimeout(t)
31
-            }, 100)
32
-          }
33
-        })
34
-      }
35
-  
36
-      calcHeight()
37
-    })
38
-  })
39
-
14
+  const handleCallback = () => {
15
+    Taro.createSelectorQuery().select(`.${vid}`).boundingClientRect(rect => onRenderFinish(rect)).exec()
16
+  }
40 17
 
41 18
   return (
42
-    <View
43
-      key={vid}
44
-      className={classNames(className, 'masonry-item', vid)}
45
-    >
46
-      {render(item)}
19
+    <View className={classNames(className, 'masonry-item', vid)}>
20
+      {render(item, handleCallback)}
47 21
     </View>
48 22
   )
49 23
 }

+ 69
- 37
src/components/MasonryLayout/index.jsx Прегледај датотеку

@@ -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
 }

+ 2
- 5
src/components/MasonryLayout/style.less Прегледај датотеку

@@ -6,15 +6,12 @@
6 6
 
7 7
   .masonry-column {
8 8
     flex: 1;
9
+    overflow: hidden;
10
+    position: relative;
9 11
   }
10 12
   
11
-  .masonry-split {
12
-    flex: none;
13
-  }
14
-
15 13
   .masonry-item {
16 14
     width: 100%;
17
-    overflow-x: hidden;
18 15
   }
19 16
 }
20 17
 

+ 2
- 2
src/pages/index/components/Card/style.less Прегледај датотеку

@@ -45,7 +45,7 @@
45 45
       display: flex;
46 46
     
47 47
       .cCleft{
48
-        flex: 1;
48
+        flex: 3;
49 49
         position: relative;
50 50
         padding:40px 0 30px 0;
51 51
         .cCicon{
@@ -62,7 +62,7 @@
62 62
         }
63 63
       }
64 64
       .cCright{
65
-        flex: 1;
65
+        flex: 2;
66 66
         position: relative;
67 67
         padding:40px 0 30px 0;
68 68
         .price{          

+ 25
- 15
src/pages/index/tabs/Recommend.jsx Прегледај датотеку

@@ -22,9 +22,12 @@ export default (props) => {
22 22
   const listRef = useRef()
23 23
 
24 24
   const [queryParams, setQueryParams] = useState({ location, pageNum: 1, pageSize: 10, typeId: '' })
25
+  const rfTimes = useRef(0)
25 26
 
26 27
   // 获取资源表信息
27
-  const [alllist, setAllList] = useState([])
28
+  // const [alllist, setAllList] = useState([])
29
+  const [listData, setListData] = useState({ list: [], rfTimes: 0 })
30
+
28 31
   //分类标签
29 32
   const tabs = [{ title: '附近' }].concat(typeList.map(x => ({ ...x, title: x.typeName })))
30 33
   //切换上面的标签
@@ -44,6 +47,14 @@ export default (props) => {
44 47
     }
45 48
   }
46 49
 
50
+  const handleDataChange = (value, e) => {
51
+    if (e.paramsChanged) {
52
+      rfTimes.current += 1
53
+    }
54
+
55
+    setListData({ list: value, rfTimes: rfTimes.current })
56
+  }
57
+
47 58
   useEffect(() => {
48 59
     //查询分类标签表
49 60
     getIndexType({ pageSize: 20 }).then((res) => {
@@ -55,19 +66,17 @@ export default (props) => {
55 66
     // 用绝对路径
56 67
     Taro.navigateTo({ url: '/pages/search/search' });
57 68
   }
58
-  // 联动收藏
59
-  const likeLook = () => {
60
-    getResourceList().then(e => {
61
-      setAllList(e.records)
62
-    })
63
-
64
-  }
65
-
66
-  useDidShow(() => {
67
-    likeLook()
68
-  })
69
+  // // 联动收藏
70
+  // const likeLook = () => {
71
+  //   getResourceList().then(e => {
72
+  //     setAllList(e.records)
73
+  //   })
69 74
 
75
+  // }
70 76
 
77
+  // useDidShow(() => {
78
+  //   likeLook()
79
+  // })
71 80
 
72 81
   return (
73 82
     // <view style={{ height: '100%', overflow: 'hidden',display:'flex',flexDirection:'column' }}>
@@ -103,11 +112,12 @@ export default (props) => {
103 112
           style={listStyle}
104 113
           request={getResourceList}
105 114
           params={queryParams}
106
-          onDataChange={setAllList}
115
+          onDataChange={handleDataChange}
107 116
         >
108 117
           <view style={{ padding: '30rpx 15rpx' }}>
109
-            <Waterfall
110
-              list={alllist}
118
+            <MasonryLayout
119
+              itemKey='resourceNo'
120
+              listData={listData}
111 121
               render={(item, callback) => <Card item={item} onImageLoad={callback} />}
112 122
             />
113 123
           </view>