Skip to content

Commit

Permalink
feat: infiniteScroll组件
Browse files Browse the repository at this point in the history
  • Loading branch information
kongjing committed Mar 4, 2023
1 parent 33e8853 commit 4abc097
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 2 deletions.
4 changes: 2 additions & 2 deletions packages/vantui-demo/config/webpack/sync-mdcode-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const vantuiDemoDir = path.resolve(__dirname, '../../..')
const pagePath = path.join(__dirname, '../../src/pages')
const configPath = path.join(__dirname, '../../src/config.json')
const appConfigPath = path.join(__dirname, '../../src/app.config.js')
const withTabPages = ['icon', 'power-scroll-view']
const withTabPages = ['icon', 'power-scroll-view', 'infinite-scroll'] // 需要tab切换展示的组件
const markdownCodeSrc = path.join(vantuiDemoDir, '/vantui-doc/src')
const vantConfigPath = path.join(vantuiDemoDir, '/vant.config.js')
const fromTaroComps = ['View', 'Text', 'Input', 'Block']
Expand Down Expand Up @@ -212,7 +212,7 @@ async function createPageIndex(props) {
if (withTabPages && withTabPages.includes(targetPath)) {
lastJsx = `
<DemoPage title="${pageTile}" className="pages-${targetPath}-index">
<Tabs active={this.state.avtive} onChange={e => this.setState({ active: e.detail.index })}>
<Tabs active={this.state.avtive} onChange={e => this.setState({ active: e.detail.index })} sticky={true}>
${jsxStr}
</Tabs>
${pageIndexJsxPush}
Expand Down
12 changes: 12 additions & 0 deletions packages/vantui-demo/src/app.less
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,18 @@ taro-button-core + taro-button-core {
background-color: #ffffff;
}

.pages-infinite-scroll-index {
background-color: #ffffff;

.dataIndex {
background-color: #07c160;
color: #ffffff;
padding: 6px 12px;
border-radius: 6px;
margin-right: 12px;
}
}

// 添加pc端鼠标样式
.van-slider,
.van-picker,
Expand Down
95 changes: 95 additions & 0 deletions packages/vantui-doc/src/infinite-scroll/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# InfiniteScroll 无限滚动

### 介绍

InfiniteScroll 组件在可见区域时自动加载更多数据。

### 引入

在 Taro 文件中引入组件

```js
import { InfiniteScroll } from '@antmjs/vantui'
```

### 基本使用

- 可见区域为窗口,如果滚动区域不是窗体则通过`parentClassName`设置父元素类名

```jsx
function Demo() {
const [data, setdata] = react.useState([])
const mockRequest = COMMON.mockRequest

const loadMore = async () => {
return new Promise(async (resolve) => {
const reslult = await mockRequest()
const newData = [].concat(data, reslult)
setdata(newData)
// 以下是简单的模拟请求,正常请求请按条件执行`resolve('complete')`
resolve(newData.length > 60 ? 'complete' : 'loading')
})
}

return (
<View style={{ padding: '4px 6px' }} onClick={loadMore}>
{data.map((item, index) => (
<View
style={{ padding: '12px 6px', borderBottom: '1px solid #eee' }}
key={item}
>
<Text className="dataIndex">Index{index + 1}</Text>
{item}
</View>
))}
<InfiniteScroll loadMore={loadMore} />
</View>
)
}
```

模拟获取数据

```js common
const mockRequest = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve(new Array(20).fill('').map((item) => `数据:${Math.random()}`))
}, 1500)
})
}
```

### 请求异常

```jsx
function Demo() {
const [data, setdata] = react.useState([])
const mockRequest = COMMON.mockRequest

const loadMore = async () => {
return new Promise(async (resolve) => {
const reslult = await mockRequest()
const newData = [].concat(data, reslult)
setdata(newData)
// 以下是简单的模拟请求,正常请求请按条件执行`resolve('error')`
resolve(Math.random() > 0.3 ? 'error' : 'loading')
})
}

return (
<View style={{ padding: '4px 6px' }}>
{data.map((item, index) => (
<View
style={{ padding: '12px 6px', borderBottom: '1px solid #eee' }}
key={item}
>
<Text className="dataIndex">Index{index + 1}</Text>
{item}
</View>
))}
<InfiniteScroll loadMore={loadMore} />
</View>
)
}
```
4 changes: 4 additions & 0 deletions packages/vantui-doc/vant.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,10 @@ module.exports = {
path: 'number-keyboard',
title: 'NumberKeyboard 数字键盘',
},
{
path: 'infinite-scroll',
title: 'InfiniteScroll 无限滚动',
},
],
},
{
Expand Down
23 changes: 23 additions & 0 deletions packages/vantui/src/infinite-scroll/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@import '../style/var.less';

.van-infinite-scroll {
display: flex;
align-items: center;
justify-content: center;

&-loading,
&-complete,
&-error {
color: @infinite-scroll-color;
font-size: @infinite-scroll-font-size;
line-height: 40px;
padding: 24px 0;
}

&-error {
.reload-btn {
padding-left: 8px;
color: @infinite-scroll-primary-color;
}
}
}
5 changes: 5 additions & 0 deletions packages/vantui/src/infinite-scroll/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { InfiniteScroll } from './infinite-scroll'

export { InfiniteScroll }

export default InfiniteScroll
158 changes: 158 additions & 0 deletions packages/vantui/src/infinite-scroll/infinite-scroll.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { getCurrentPages, createIntersectionObserver } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { useState, useCallback, useRef, useEffect } from 'react'
import { Loading } from '../loading'
import { InfiniteScrollProps } from '../../types/index'

const clsPrefix = `van-infinite-scroll`
type IStatus = 'loading' | 'complete' | 'error'
let compInitIndex = 0

export function InfiniteScroll(props: InfiniteScrollProps) {
const {
renderLoading,
renderComplete,
renderError,
parentClassName,
loadMore,
className = '',
completeText = '没有更多了~',
loadingText = '加载中...',
errorText = '加载失败',
reloadText = '重新加载',
} = props
const [status, setStatus] = useState<IStatus>('loading')
const [onRequest, setOnRequest] = useState(false)
const contentObserver = useRef<any>()
const [compIndex] = useState(compInitIndex++)
const thisDom = useRef()
const [forceKey, setForceKey] = useState(0) // 解决IntersectionObserver中执行loadmore时无法拿到最新的俩是state

const reset = useCallback(() => {
setForceKey(0)
setStatus('loading')
setOnRequest(false)
}, [])

const _loadMore = useCallback(
async (immediately?: boolean) => {
if ((!onRequest && status === 'loading') || immediately) {
setOnRequest(true)
const status = await loadMore()
setStatus(status)
setOnRequest(false)
}
},
[loadMore, onRequest, status],
)

useEffect(() => {
return () => {
reset()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

useEffect(() => {
if (forceKey !== 0) _loadMore()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [forceKey])

const initObserveH5 = useCallback(() => {
const options: any = {
threshold: [0.6],
}
if (parentClassName)
options.root = document.getElementsByClassName(parentClassName)[0]
const contentObserver_ = new IntersectionObserver(function (res: any) {
const target = res[0]
if (target && target.intersectionRatio > 0.6) {
setForceKey(Math.random() + Math.random())
}
}, options)
contentObserver.current = contentObserver_
contentObserver.current.observe(thisDom.current)
}, [parentClassName])

const initObserve = useCallback(
function () {
if (contentObserver.current != null) {
contentObserver.current.disconnect()
}
if (process.env.TARO_ENV === 'h5') {
return initObserveH5()
}
const pages: any = getCurrentPages()
const curePage = pages[pages.length - 1]
let _createIntersectionObserver = curePage.createIntersectionObserver

if (process.env.TARO_ENV === 'alipay') {
_createIntersectionObserver = createIntersectionObserver
}

const contentObserver_ = _createIntersectionObserver({
thresholds: [1],
})
contentObserver.current = contentObserver_
if (parentClassName) {
contentObserver.current.relativeTo(parentClassName)
} else {
contentObserver.current.relativeToViewport({ bottom: 0 })
}
contentObserver.current.observe(`.${clsPrefix}${compIndex}`, () => {
setForceKey(Math.random() + Math.random())
})
},
[compIndex, initObserveH5, parentClassName],
)

useEffect(() => {
setTimeout(() => {
initObserve()
}, 33)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const retry = useCallback(() => {
reset()
_loadMore(true)
}, [_loadMore, reset])

return (
<View
className={`${clsPrefix} ${clsPrefix}${compIndex} ${className}`}
ref={thisDom}
>
{status === 'loading' && (
<>
{renderLoading || (
<Loading size={24} className={`${clsPrefix}-loading`}>
{loadingText}
</Loading>
)}
</>
)}
{status === 'complete' && (
<>
{renderComplete || (
<View className={`${clsPrefix}-complete`}>{completeText}</View>
)}
</>
)}
{status === 'error' && (
<>
{renderError || (
<View className={`${clsPrefix}-error`}>
<Text>{errorText}</Text>
<Text className="reload-btn" onClick={retry}>
{reloadText}
</Text>
</View>
)}
</>
)}
</View>
)
}

export default InfiniteScroll
5 changes: 5 additions & 0 deletions packages/vantui/src/style/var.less
Original file line number Diff line number Diff line change
Expand Up @@ -767,3 +767,8 @@
@signature-border-width: 1px;
@signature-height: 300px;
@signature-margin-bottom: 20px;

// infinite-scroll
@infinite-scroll-primary-color: #20c064;
@infinite-scroll-color: @gray-6;
@infinite-scroll-font-size: @font-size-md;
1 change: 1 addition & 0 deletions packages/vantui/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,4 @@ export { Cascader } from './cascader'
export { Sku } from './sku'
export { WaterMark } from './water-mark'
export { Ellipsis } from './ellipsis'
export { InfiniteScroll, InfiniteScrollProps } from './infinite-scroll'
Loading

0 comments on commit 4abc097

Please sign in to comment.