Skip to content

Commit

Permalink
feat: pullToRefresh组件
Browse files Browse the repository at this point in the history
  • Loading branch information
kongjing committed Mar 5, 2023
1 parent 4abc097 commit 8c18c7e
Show file tree
Hide file tree
Showing 9 changed files with 274 additions and 19 deletions.
36 changes: 24 additions & 12 deletions packages/vantui-doc/src/infinite-scroll/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { InfiniteScroll } from '@antmjs/vantui'
function Demo() {
const [data, setdata] = react.useState([])
const mockRequest = COMMON.mockRequest
const InfiniteScrollInstance = react.useRef()

const loadMore = async () => {
return new Promise(async (resolve) => {
Expand All @@ -31,19 +32,30 @@ function Demo() {
})
}

const onRefresh = () => {
return new Promise(async (resolve) => {
const reslult = await mockRequest()
setdata(reslult)
InfiniteScrollInstance.current.reset()
resolve()
})
}

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>
<PullToRefresh onRefresh={onRefresh}>
<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} ref={InfiniteScrollInstance} />
</View>
</PullToRefresh>
)
}
```
Expand Down
40 changes: 40 additions & 0 deletions packages/vantui-doc/src/pull-to-refresh/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# PullToRefresh 下拉刷新

### 介绍

在列表中通过手指下拉刷新加载新内容的交互操作。

### 引用

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

### 基本使用

```jsx
function Demo() {
const onRefresh = () => {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, 1000)
})
}

return (
<PullToRefresh onRefresh={onRefresh}>
<View style={{ padding: '0 12px' }}>
{new Array(10).fill(1).map((item, index) => (
<View
style={{ padding: 12, background: '#fff', marginBottom: 12 }}
key={`PullToRefresh${index}`}
>
{index}
</View>
))}
</View>
</PullToRefresh>
)
}
```
4 changes: 4 additions & 0 deletions packages/vantui-doc/vant.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ module.exports = {
path: 'infinite-scroll',
title: 'InfiniteScroll 无限滚动',
},
{
path: 'pull-to-refresh',
title: 'PullToRefresh 下拉刷新',
},
],
},
{
Expand Down
36 changes: 30 additions & 6 deletions packages/vantui/src/infinite-scroll/infinite-scroll.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
import { getCurrentPages, createIntersectionObserver } from '@tarojs/taro'
import {
getCurrentPages,
createIntersectionObserver,
nextTick,
} from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { useState, useCallback, useRef, useEffect } from 'react'
import {
useState,
useCallback,
useRef,
useEffect,
useImperativeHandle,
forwardRef,
} from 'react'
import { Loading } from '../loading'
import { InfiniteScrollProps } from '../../types/index'
import { InfiniteScrollProps, InfiniteScrollInstance } from '../../types/index'

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

export function InfiniteScroll(props: InfiniteScrollProps) {
function InfiniteScroll_(
props: InfiniteScrollProps,
ref: React.ForwardedRef<InfiniteScrollInstance>,
) {
const {
renderLoading,
renderComplete,
Expand Down Expand Up @@ -40,7 +54,9 @@ export function InfiniteScroll(props: InfiniteScrollProps) {
setOnRequest(true)
const status = await loadMore()
setStatus(status)
setOnRequest(false)
nextTick(() => {
setOnRequest(false)
})
}
},
[loadMore, onRequest, status],
Expand Down Expand Up @@ -109,7 +125,7 @@ export function InfiniteScroll(props: InfiniteScrollProps) {
useEffect(() => {
setTimeout(() => {
initObserve()
}, 33)
}, 66)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

Expand All @@ -118,6 +134,12 @@ export function InfiniteScroll(props: InfiniteScrollProps) {
_loadMore(true)
}, [_loadMore, reset])

useImperativeHandle(ref, () => {
return {
reset,
}
})

return (
<View
className={`${clsPrefix} ${clsPrefix}${compIndex} ${className}`}
Expand Down Expand Up @@ -155,4 +177,6 @@ export function InfiniteScroll(props: InfiniteScrollProps) {
)
}

export const InfiniteScroll = forwardRef(InfiniteScroll_)

export default InfiniteScroll
11 changes: 11 additions & 0 deletions packages/vantui/src/pull-to-refresh/index.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.van-pull-to-refresh {
&-status {
color: #969799;
font-size: 28px;
overflow: hidden;
transition: 0.6s all;
display: flex;
align-items: center;
justify-content: center;
}
}
5 changes: 5 additions & 0 deletions packages/vantui/src/pull-to-refresh/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import PullToRefresh from './pull-to-refresh'

export default PullToRefresh

export { PullToRefresh }
148 changes: 148 additions & 0 deletions packages/vantui/src/pull-to-refresh/pull-to-refresh.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { useState, useCallback, useEffect, useMemo } from 'react'
import { View } from '@tarojs/components'
import { Loading } from '../loading'
import { getRect } from '../common/utils'

const clsPrefix = 'van-pull-to-refresh'

type IPullToFreshProps = {
children?: React.ReactNode
pullText?: React.ReactNode
releaseText?: React.ReactNode
loadingText?: React.ReactNode
renderLoading?: React.ReactNode
successText?: React.ReactNode
onRefresh: () => Promise<undefined>
touchMaxStart: number
headHeight?: number
}

type IStatus = 'pull' | 'release' | 'loading' | 'success'
let initIndex = 0

export default function PullToRefresh(props: IPullToFreshProps) {
const {
children,
loadingText = '加载中...',
successText = '刷新成功',
pullText = '下拉刷新',
releaseText = '松开刷新',
headHeight = 40,
renderLoading,
onRefresh,
touchMaxStart = 300,
} = props
const [statusHeight, setStatusHeight] = useState(0)
const [status, setStatus] = useState<IStatus>('pull')
const [componentIndex] = useState(initIndex++)
const [touch] = useState({
start: 0,
time: 0,
maxStart: 0,
})

const reset = useCallback(() => {
touch.start = 0
touch.time = 0
setStatusHeight(0)
}, [touch])

const onTouchStart = useCallback(
function (event) {
if (status !== 'pull') setStatus('pull')
const start = event.touches[0].clientY
if (start < touch.maxStart) {
touch.start = event.touches[0].clientY
touch.time = Date.now()
} else {
touch.time = Date.now() + 9999 * 1000
}
},
[status, touch],
)

const onTouchMove = useCallback(
function (event) {
if (status === 'pull' && Date.now() - touch.time > 500) {
event.preventDefault()
event.stopPropagation()
const y = event.touches[0].clientY - touch.start
setStatusHeight(y)
}
},
[status, touch.start, touch.time],
)

const onTouchEnd = useCallback(async () => {
if (statusHeight > headHeight) {
setStatus('loading')
setStatusHeight(headHeight)
if (status === 'release') {
await onRefresh()
setStatus('success')
setTimeout(() => {
reset()
}, 1000)
}
} else {
setStatusHeight(0)
}
}, [headHeight, onRefresh, reset, status, statusHeight])

useEffect(() => {
if (status === 'pull' && statusHeight - headHeight >= 50) {
setStatus('release')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [statusHeight])

useEffect(() => {
setTimeout(() => {
getRect(null, `.${clsPrefix}-status${componentIndex}`).then(
(res: any) => {
touch.maxStart = res.top + touchMaxStart
},
)
}, 100)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

const renderHeight = useMemo(() => {
return statusHeight > headHeight ? headHeight : statusHeight
}, [headHeight, statusHeight])

const renderMarginBottom = useMemo(() => {
let marginBottom = 0
const ly = statusHeight - headHeight
if (ly > 20) {
marginBottom = 20
}

return marginBottom
}, [headHeight, statusHeight])

return (
<View
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
className={`${clsPrefix}`}
>
<View
className={`${clsPrefix}-status ${clsPrefix}-status${componentIndex}`}
style={{
height: renderHeight,
marginBottom: renderMarginBottom,
}}
>
{status === 'loading' && (
<>{renderLoading || <Loading size={24}>{loadingText}</Loading>}</>
)}
{status === 'release' && releaseText}
{status === 'pull' && pullText}
{status === 'success' && successText}
</View>
{children}
</View>
)
}
6 changes: 5 additions & 1 deletion packages/vantui/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,8 @@ export { Cascader } from './cascader'
export { Sku } from './sku'
export { WaterMark } from './water-mark'
export { Ellipsis } from './ellipsis'
export { InfiniteScroll, InfiniteScrollProps } from './infinite-scroll'
export {
InfiniteScroll,
InfiniteScrollProps,
InfiniteScrollInstance,
} from './infinite-scroll'
7 changes: 7 additions & 0 deletions packages/vantui/types/infinite-scroll.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ export interface InfiniteScrollProps extends ViewProps {
renderError?: React.ReactNode
}

export interface InfiniteScrollInstance {
/**
* @description 重置加载状态
*/
reset: () => void
}

declare const InfiniteScroll: FunctionComponent<InfiniteScrollProps>

export { InfiniteScroll }

0 comments on commit 8c18c7e

Please sign in to comment.