diff --git a/src/config.json b/src/config.json
index 72f67895889530c1b9855433891c32cd2f89f1b7..9c3a8ce710d698fe86ea76ea3d9b244aacf7eb7f 100644
--- a/src/config.json
+++ b/src/config.json
@@ -1039,8 +1039,9 @@
"cName": "虚拟列表",
"desc": "长列表渲染",
"sort": 20,
+ "tarodoc":true,
"show": true,
- "taro": false,
+ "taro": true,
"author": "hx"
},
{
diff --git a/src/packages/textarea/demo.taro.tsx b/src/packages/textarea/demo.taro.tsx
index 16001fe5873a255c706e5890195630e851661994..6dd95189dc92a03ef3ada9457ed19e61ceae8d4f 100644
--- a/src/packages/textarea/demo.taro.tsx
+++ b/src/packages/textarea/demo.taro.tsx
@@ -1,8 +1,8 @@
import React, { useEffect, useState } from 'react'
+import Taro from '@tarojs/taro'
import { useTranslate } from '@/sites/assets/locale/taro'
import { TextArea } from '@/packages/nutui.react.taro'
import Header from '@/sites/components/header'
-import Taro from '@tarojs/taro'
interface T {
basic: string
diff --git a/src/packages/virtuallist/demo.taro.tsx b/src/packages/virtuallist/demo.taro.tsx
index 194d1012c0071bc1d4c8efc41041a68db4788ea4..6b9934e83fed8c0e2fa0786ed041beb25c908a39 100644
--- a/src/packages/virtuallist/demo.taro.tsx
+++ b/src/packages/virtuallist/demo.taro.tsx
@@ -1,9 +1,13 @@
-import React, { useCallback, useEffect, useState } from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
import { useTranslate } from '@/sites/assets/locale/taro'
-import { Cell, CellGroup, Radio } from '@/packages/nutui.react.taro'
+import {
+ Cell,
+ CellGroup,
+ Radio,
+ VirtualList,
+} from '@/packages/nutui.react.taro'
import Header from '@/sites/components/header'
import Taro from '@tarojs/taro'
-import VirtualList from './index'
const { RadioGroup } = Radio
@@ -32,6 +36,25 @@ const ListDemo = () => {
const [pageNo, setPageNo] = useState(1)
const [radioVal, setRadioVal] = useState('1')
const [isLoading, setIsLoading] = useState(false)
+
+ const itemStyle = {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100%',
+ height: '50px',
+ background: '#fff',
+ borderRadius: '10px',
+ }
+ const itemStyel2 = {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100%',
+ background: '#fff',
+ borderRadius: '10px',
+ }
+
const handleChange = (v: any) => {
setRadioVal(v)
setPageNo(1)
@@ -53,16 +76,26 @@ const ListDemo = () => {
})
}
}, [pageNo])
+
+ useEffect(() => {
+ getData()
+ }, [getData])
+
const ItemRender = ({ data }: any) => {
- return
{data}
+ return {data}
}
const ItemRenderMemo = React.memo(ItemRender)
const ItemVariable = ({ data, index }: any) => {
return (
-
+
{data}
-
+
)
}
const ItemVariableDemo = React.memo(ItemVariable)
@@ -74,16 +107,13 @@ const ListDemo = () => {
setIsLoading(false)
}, 30)
}
- useEffect(() => {
- getData()
- }, [getData])
+
const showNode = () => {
switch (radioVal) {
case '1':
return (
{
case '2':
return (
- )
- case '3':
- return (
-
- )
- case '4':
- return (
-
)
default:
return (
{
>
{translated.text1}
{translated.text2}
- {translated.text3}
- {translated.text4}
-
- {showNode()}
-
+ {showNode()}
>
)
diff --git a/src/packages/virtuallist/doc.taro.md b/src/packages/virtuallist/doc.taro.md
index e1a0a71450b9ee4a2183345d2d706f78bfeb14d6..94457d22ca0f9c25fef7fd66fadd94bd87444969 100644
--- a/src/packages/virtuallist/doc.taro.md
+++ b/src/packages/virtuallist/doc.taro.md
@@ -15,83 +15,58 @@ import { VirtualList } from '@nutui/nutui-react-taro';
:::demo
``` tsx
-import React, { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
import { VirtualList } from '@nutui/nutui-react-taro';
const App =() => {
const [sourceData, setsourceData] = useState([])
+ const [pageNo, setPageNo] = useState(1)
+ const [isLoading, setIsLoading] = useState(false)
+
+ const itemStyle = {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100%',
+ height: '50px',
+ background: '#fff',
+ borderRadius: '10px',
+ }
const getData = useCallback(() => {
const datas = []
const pageSize = 90
for (let i = 10; i < pageSize; i++) {
- datas.push(`${i} Item`)
+ datas.push(`${i} Item`)
}
setsourceData((sourceData) => {
- return [...sourceData, ...datas]
+ return [...sourceData, ...datas]
})
}, [])
+
useEffect(() => {
getData()
}, [getData])
- const ItemRender = ({ data,index }) => {
- return 自定义-{data}-{index}
+
+ const ItemRender = ({ data }: any) => {
+ return {data}
}
const ItemRenderMemo = React.memo(ItemRender)
- return (
-
-
-
- )
-}
-export default App;
-```
-:::
-### 2、垂直不等高&无限下滑
-
-:::demo
-``` tsx
-import React, { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
-import { VirtualList } from '@nutui/nutui-react-taro';
-const App =() => {
- const [sourceData, setsourceData] = useState([])
- const [pageNo, setPageNo] = useState(1)
- const getData = useCallback(() => {
- const datas = []
- const pageSize = 90
- for (let i = 10; i < pageSize; i++) {
- datas.push(`${i} Item`)
- }
- setsourceData((sourceData) => {
- return [...sourceData, ...datas]
- })
- }, [])
- const onScroll = () => {
- if (pageNo > 100) return
- setPageNo(pageNo + 1)
- }
- useEffect(() => {
- getData()
- }, [getData])
- const ItemVariable = ({ data, index }) => {
- return (
- 可变大小隔行展示-{data}
- )
+ const onScroll = () => {
+ if (pageNo > 50 || isLoading) return
+ setIsLoading(true)
+ setTimeout(() => {
+ setPageNo(pageNo + 1)
+ setIsLoading(false)
+ }, 30)
}
- /** itemSize为首屏最大元素大小 */
- const ItemVariableDemo = React.memo(ItemVariable)
return (
-
-
+
@@ -100,91 +75,72 @@ const App =() => {
export default App;
```
:::
-
-### 3、水平等宽
+### 2、垂直不等高&无限下滑
:::demo
``` tsx
-import React, { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
+import React, { useState, useEffect, useCallback } from 'react'
import { VirtualList } from '@nutui/nutui-react-taro';
const App =() => {
const [sourceData, setsourceData] = useState([])
const [pageNo, setPageNo] = useState(1)
- const getData = useCallback(() => {
- const datas = []
- const pageSize = 90
- for (let i = 10; i < pageSize; i++) {
- datas.push(`${i} Item`)
- }
- setsourceData((sourceData) => {
- return [...sourceData, ...datas]
- })
- }, [])
- useEffect(() => {
- getData()
- }, [getData])
- const ItemRender = ({ data,index }) => {
- return 自定义-{data}-{index}
+ const [isLoading, setIsLoading] = useState(false)
+
+
+ const itemStyel2 = {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100%',
+ background: '#fff',
+ borderRadius: '10px',
}
- const ItemRenderMemo = React.memo(ItemRender)
- return (
-
-
-
- )
-}
-export default App;
-```
-:::
-### 4、水平不等宽&无限滑动
-
-:::demo
-``` tsx
-import React, { FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'
-import { VirtualList } from '@nutui/nutui-react-taro';
-const App =() => {
- const [sourceData, setsourceData] = useState([])
- const [pageNo, setPageNo] = useState(1)
const getData = useCallback(() => {
const datas = []
const pageSize = 90
for (let i = 10; i < pageSize; i++) {
- datas.push(`${i} Item`)
+ datas.push(`${i} Item`)
}
setsourceData((sourceData) => {
- return [...sourceData, ...datas]
+ return [...sourceData, ...datas]
})
}, [])
- const onScroll = () => {
- if (pageNo > 100) return
- setPageNo(pageNo + 1)
- }
+
useEffect(() => {
getData()
}, [getData])
- const ItemVariable = ({ data, index }) => {
+
+ const ItemVariable = ({ data, index }: any) => {
return (
- 可变大小隔行展示-{data}
+ {data}
)
}
- /** itemSize为首屏最大元素大小 */
const ItemVariableDemo = React.memo(ItemVariable)
+
+ const onScroll = () => {
+ if (pageNo > 50 || isLoading) return
+ setIsLoading(true)
+ setTimeout(() => {
+ setPageNo(pageNo + 1)
+ setIsLoading(false)
+ }, 30)
+ }
return (
-
+
)
@@ -192,6 +148,7 @@ const App =() => {
export default App;
```
:::
+
## API
### Props
@@ -199,17 +156,16 @@ export default App;
| 参数 | 说明 | 类型 | 默认值 |
|---------------|----------------------------------|----------|---------------------------------------|
| sourceData | 获取数据 | Array | - |
-| containerSize | 容器高度 | Number | 获取元素的 offsetWidth 或 offsetHeight,需要 css 给出 |
+| containerSize | 容器高度 | Number | 获取元素的 offsetHeight,需要 css 给出 |
| ItemRender | virtual 列表父节点渲染的函数 | React.FC | - |
-| itemSize | item高度,如果不定高,则为首屏单个最大size | String | - |
| itemEqualSize | item大小是否一致 | Boolean | true |
+| itemSize | item高度,如果不定高,会走默认高度 | String | 66 |
| overscan | 除了视窗里面默认的元素, 还需要额外渲染的item个数 | Number | 2 |
| key | 唯一值 ,Item(sourceData)具体的某个唯一值的字段 | string | index |
-| horizontal | 决定列表是横向的还是纵向的 | Boolean | false |
+
## Events
| 方法名 | 说明 | 参数 | 返回值 |
|------------------|---------------------| --------------- | ---------- |
-| handleScroll`废弃` | 滑动到底(右)的事件,可以实现无限滚动 | - | - |
| onScroll`v1.3.8` | 滑动到底(右)的事件,可以实现无限滚动 | - | - |
diff --git a/src/packages/virtuallist/virtuallist.scss b/src/packages/virtuallist/virtuallist.scss
index 34f9d49bd910809213cbca0fb7261db3e294f5bd..ed574d42d40b180932354834d3df774443435567 100644
--- a/src/packages/virtuallist/virtuallist.scss
+++ b/src/packages/virtuallist/virtuallist.scss
@@ -61,3 +61,27 @@
}
}
}
+
+.nut-virtuallist {
+ width: 100%;
+ overflow: scroll;
+ position: relative;
+ -webkit-overflow-scrolling: touch;
+ &-phantom {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ z-index: -1;
+ }
+ &-container {
+ left: 0;
+ right: 0;
+ top: 0;
+ position: absolute;
+ }
+ &-item {
+ overflow: hidden;
+ margin: $list-item-margin;
+ }
+}
diff --git a/src/packages/virtuallist/virtuallist.taro.tsx b/src/packages/virtuallist/virtuallist.taro.tsx
index 4bdf18caf909221973a4b48f686feccb8bcff19a..17000a60eb9c47ed9b5bee5ea92455720b265604 100644
--- a/src/packages/virtuallist/virtuallist.taro.tsx
+++ b/src/packages/virtuallist/virtuallist.taro.tsx
@@ -1,22 +1,38 @@
import React, {
FunctionComponent,
- useCallback,
useEffect,
useRef,
useState,
+ useCallback,
} from 'react'
+import { ScrollView } from '@tarojs/components'
+import Taro from '@tarojs/taro'
import { useConfig } from '@/packages/configprovider/configprovider.taro'
-import { BasicVirtualListProps, VirtualListState, PositionType } from './type'
-import {
- initPositinoCache,
- getListTotalSize,
- binarySearch,
- getEndIndex,
- updateItemSize,
-} from './utils'
-
-export type VirtualListProps = BasicVirtualListProps
-const defaultProps = {} as VirtualListProps
+import { VirtualListState, PositionType } from './type'
+import { initPositinoCache, binarySearch, updateItemSize } from './utils'
+
+export type VirtualListProps = {
+ className?: string | any
+ style?: React.CSSProperties
+ sourceData: any // 获取数据
+ containerSize?: number // 容器大小
+ ItemRender?: any // virtual 列表父节点渲染的函数,默认为 (items, ref) =>
{items}
+ itemEqualSize?: boolean // item 固定大小,默认是true
+ itemSize?: number // 预估元素高度
+ overscan?: number // 除了视窗里面默认的元素, 还需要额外渲染的, 避免滚动过快, 渲染不及时,默认是2
+ onScroll?: (...args: any[]) => any // 滑动到底部执行的函数
+ key?: any // 遍历时生成item 的唯一key,默认是index,建议传入resources的数据具体的某个唯一值的字段
+ locale?: any
+}
+const defaultProps = {
+ sourceData: [],
+ itemSize: 66,
+ itemEqualSize: true,
+ overscan: 2,
+} as VirtualListProps
+
+const clientHeight = Taro.getSystemInfoSync().windowHeight - 5 || 667
+const clientWidth = Taro.getSystemInfoSync().windowWidth || 375
export const VirtualList: FunctionComponent<
VirtualListProps & React.HTMLAttributes
@@ -24,27 +40,29 @@ export const VirtualList: FunctionComponent<
const {
sourceData = [],
ItemRender,
+ itemSize = 66,
itemEqualSize = true,
- itemSize = 200,
- horizontal = false,
overscan = 2,
key,
- handleScroll,
onScroll,
className,
- containerSize,
+ containerSize = clientHeight,
...rest
} = props
- const sizeKey = horizontal ? 'width' : 'height'
- const scrollKey = horizontal ? 'scrollLeft' : 'scrollTop'
- const offsetKey = horizontal ? 'left' : 'top'
+ // const sizeKey = horizontal ? 'width' : 'height'
+ // const scrollKey = horizontal ? 'scrollLeft' : 'scrollTop'
+ // const offsetKey = horizontal ? 'left' : 'top'
+
+ const [startOffset, setStartOffset] = useState(0)
+ const [start, setStart] = useState(0)
+ const [list, setList] = useState(sourceData.slice())
const { locale } = useConfig()
// 虚拟列表容器ref
const scrollRef = useRef(null)
// 虚拟列表显示区域ref
- const itemsRef = useRef(null)
- const firstItemRef = useRef(null)
+ const itemsRef = useRef(null)
+ const firstItemRef = useRef(null)
// 列表位置信息
const [positions, setPositions] = useState([
{
@@ -57,10 +75,7 @@ export const VirtualList: FunctionComponent<
right: 0,
},
])
- // 列表总大小
- const [listTotalSize, setListTotalSize] = useState(99999999)
- // 可视区域条数
- const [visibleCount, setVisibleCount] = useState(0)
+
const [offSetSize, setOffSetSize] = useState(containerSize || 0)
const [options, setOptions] = useState({
startOffset: 0, // 可视区域距离顶部的偏移量
@@ -69,97 +84,88 @@ export const VirtualList: FunctionComponent<
endIndex: 10, // 可视区域结束索引
})
- // 列表位置信息
useEffect(() => {
- const pos = initPositinoCache(itemSize, sourceData.length)
- setPositions(pos)
- const totalSize = getListTotalSize(pos, horizontal)
- setListTotalSize(totalSize)
- }, [sourceData, itemSize, horizontal])
- const getElement = useCallback(() => {
- return scrollRef.current?.parentElement || document.body
- }, [])
+ if (sourceData.length) {
+ setList(sourceData.slice())
+ }
+ }, [sourceData])
+
+ // 初始计算可视区域展示数量
+ useEffect(() => {
+ setPositions((options) => {
+ return { ...options, endIndex: visibleCount() }
+ })
+ }, [itemSize, overscan, offSetSize])
+
useEffect(() => {
if (containerSize) return
- const size = horizontal
- ? getElement().offsetWidth
- : getElement().offsetHeight
- setOffSetSize(size)
- }, [getElement, horizontal, containerSize])
+
+ setOffSetSize(getContainerHeight())
+ }, [containerSize])
+
useEffect(() => {
- // 初始-计算visibleCount
- if (offSetSize === 0) return
- const count = Math.ceil(offSetSize / itemSize) + overscan
+ const pos = initPositinoCache(itemSize, sourceData.length)
- setVisibleCount(count)
- setOptions((options) => {
- return { ...options, endIndex: count }
- })
- }, [getElement, horizontal, itemSize, overscan, offSetSize])
+ setPositions(pos)
+ }, [itemSize, sourceData])
+
+ // 可视区域总高度
+ const getContainerHeight = () => {
+ //初始首页列表高度
+ const initH = itemSize * sourceData.length
+ //未设置containerSize高度,判断首页高度小于设备高度时,滚动容器高度为首页数据高度,减5为分页触发的偏移量
+ let containerH =
+ initH < clientHeight
+ ? initH + overscan * itemSize - 5
+ : Math.min(containerSize, clientHeight)
+
+ return containerH // Math.min(containerSize, clientHeight)
+ }
+ // 可视区域条数
+ const visibleCount = () => {
+ return Math.ceil(getContainerHeight() / itemSize) + overscan
+ }
+
+ const end = () => {
+ return start + visibleCount()
+ }
+
+ const listHeight = () => {
+ return list.length * itemSize
+ }
+
+ const visibleData = () => {
+ return list.slice(start, Math.min(end(), list.length))
+ }
const updateTotalSize = useCallback(() => {
if (!itemsRef.current) return
const items: HTMLCollection = itemsRef.current.children
if (!items.length) return
// 更新缓存
- updateItemSize(positions, items, sizeKey)
- const totalSize = getListTotalSize(positions, horizontal)
- setListTotalSize(totalSize)
- }, [positions, sizeKey, horizontal])
-
- const scroll = useCallback(() => {
- requestAnimationFrame((e) => {
- const scrollSize = getElement()[scrollKey]
- const startIndex = binarySearch(positions, scrollSize, horizontal)
- const overStart = startIndex - overscan > -1 ? startIndex - overscan : 0
- // const offSetSize = horizontal ? getElement().offsetWidth : getElement().offsetHeight
- if (!itemEqualSize) {
- updateTotalSize()
- }
- const endIndex = getEndIndex({
- sourceData,
- startIndex,
- visibleCount,
- itemEqualSize,
- positions,
- offSetSize,
- sizeKey,
- overscan,
- })
- const startOffset = positions[startIndex][offsetKey] as number
- setOptions({ startOffset, startIndex, overStart, endIndex })
- // 无限下滑
- if (endIndex > sourceData.length - 1) {
- if (onScroll) {
- onScroll()
- } else if (handleScroll) {
- handleScroll()
- }
- }
- })
- }, [
- positions,
- getElement,
- sourceData,
- visibleCount,
- itemEqualSize,
- updateTotalSize,
- offsetKey,
- sizeKey,
- scrollKey,
- horizontal,
- overscan,
- handleScroll,
- offSetSize,
- ])
+ updateItemSize(positions, items, 'height')
+ }, [positions])
- useEffect(() => {
- const element = getElement()
- element.addEventListener('scroll', scroll, false)
- return () => {
- element.removeEventListener('scroll', scroll, false)
+ // 滚动监听
+ const listScroll = (e: any) => {
+ const scrollTop = e.detail ? e.detail.scrollTop : e.target.scrollTop
+ const scrollSize = Math.floor(scrollTop)
+ const startIndex = binarySearch(positions, scrollSize, false)
+
+ const overStart = startIndex - overscan > -1 ? startIndex - overscan : 0
+ const endIndex = end()
+ if (!itemEqualSize) {
+ updateTotalSize()
+ }
+ setStart(Math.floor(scrollTop / itemSize))
+
+ setOptions({ startOffset, startIndex, overStart, endIndex })
+
+ if (end() > list.length - 1) {
+ onScroll && onScroll()
}
- }, [getElement, scroll])
+ setStartOffset(scrollTop - (scrollTop % itemSize))
+ }
return (
-
-
+
- {sourceData
- .slice(options.overStart, options.endIndex)
- .map((data, index) => {
- const { startIndex, overStart } = options
- const dataIndex = overStart + index
- const styleVal = dataIndex < startIndex ? 'none' : 'block'
- const keyVal = key && data[key] ? data[key] : dataIndex
-
- return (
-
- {ItemRender ? (
-
- ) : (
- data
- )}
-
- )
- })}
-
-
+ {visibleData().map((data: any, index: number) => {
+ const { overStart } = options
+ const dataIndex = overStart + index
+ const keyVal = key && data[key] ? data[key] : dataIndex
+ return (
+
+ {ItemRender ? (
+
+ ) : (
+ data
+ )}
+
+ )
+ })}
+
+
)
}
diff --git a/src/sites/mobile-taro/src/app.config.ts b/src/sites/mobile-taro/src/app.config.ts
index ae4f70ba0589329684356788a0081d3755f177c0..003674e4f874fcc7f00f243366be8ae41090cc22 100644
--- a/src/sites/mobile-taro/src/app.config.ts
+++ b/src/sites/mobile-taro/src/app.config.ts
@@ -87,6 +87,7 @@ const subPackages = [
'pages/table/index',
'pages/tag/index',
'pages/trendarrow/index',
+ 'pages/virtuallist/index',
'pages/watermark/index',
],
},