zarm
Version:
基于 React 的移动端UI库
275 lines (235 loc) • 9.49 kB
Markdown
# Pull 上拉加载下拉刷新
## 基本用法
```jsx
import { useState, useEffect, useRef } from 'react';
import { Pull, List, Message, Button, Loading, BackTop } from 'zarm';
import { WarningCircle, SuccessCircle, CloseCircle } from '@zarm-design/icons';
const REFRESH_STATE = {
normal: 0, // 普通
pull: 1, // 下拉刷新(未满足刷新条件)
drop: 2, // 释放立即刷新(满足刷新条件)
loading: 3, // 加载中
success: 4, // 加载成功
failure: 5, // 加载失败
};
const LOAD_STATE = {
normal: 0, // 普通
abort: 1, // 中止
loading: 2, // 加载中
success: 3, // 加载成功
failure: 4, // 加载失败
complete: 5, // 加载完成(无新数据)
};
const getRandomNum = (min, max) => {
const Range = max - min;
const Rand = Math.random();
return min + Math.round(Rand * Range);
};
const fetchData = (length, dataSource = []) => {
let newData = [].concat(dataSource);
const startIndex = newData.length;
for (let i = startIndex; i < startIndex + length; i++) {
newData.push(<List.Item key={+i} title={`第 ${i + 1} 行`} />);
}
return newData;
};
let mounted = true;
const Demo = () => {
const pullRef = useRef();
const [bodyScroll, setBodyScroll] = useState(false);
const [dataSource, setDataSource] = useState([]);
const [refreshing, setRefreshing] = useState(REFRESH_STATE.normal);
const [loading, setLoading] = useState(LOAD_STATE.normal);
const toggleScrollContainer = () => {
const newBodyScroll = !bodyScroll;
setBodyScroll(newBodyScroll);
if (newBodyScroll) {
document.body.style.overflow = 'auto';
} else {
document.body.style.overflow = 'hidden';
}
};
// 模拟请求数据
const refreshData = () => {
setRefreshing(REFRESH_STATE.loading);
setTimeout(() => {
if (!mounted) return;
setDataSource(fetchData(20));
setRefreshing(REFRESH_STATE.success);
}, 2000);
};
// 模拟加载更多数据
const loadData = () => {
setLoading(LOAD_STATE.loading);
setTimeout(() => {
if (!mounted) return;
const randomNum = getRandomNum(0, 5);
console.log(`状态: ${randomNum === 0 ? '失败' : randomNum === 1 ? '完成' : '成功'}`);
let loadingState = LOAD_STATE.success;
if (randomNum === 0) {
loadingState = LOAD_STATE.failure;
} else if (randomNum === 1) {
loadingState = LOAD_STATE.complete;
} else {
setDataSource(fetchData(20, dataSource));
}
setLoading(loadingState);
}, 2000);
};
useEffect(() => {
setDataSource(fetchData(20));
return () => {
mounted = false;
document.body.style.overflow = 'auto';
};
}, []);
const style = bodyScroll ? {} : { overflowY: 'auto', maxHeight: 400 };
const mountContainer = () => {
return bodyScroll ? document.body : pullRef.current;
};
const scrollContainer = () => {
return bodyScroll ? window : pullRef.current;
};
return (
<>
<Message theme="warning" icon={<WarningCircle />}>
当前使用的是 `{bodyScroll ? 'window' : 'div'}` 作为滚动容器。
<Button theme="primary" size="xs" onClick={toggleScrollContainer}>
点击切换
</Button>
</Message>
<Pull
ref={pullRef}
style={style}
refresh={{
state: refreshing,
handler: refreshData,
// render: (refreshState, percent) => {
// const cls = 'custom-control';
// switch (refreshState) {
// case REFRESH_STATE.pull:
// return (
// <div className={cls}>
// <Loading loading={false} percent={percent} />
// <span>下拉刷新</span>
// </div>
// );
// case REFRESH_STATE.drop:
// return (
// <div className={cls}>
// <Loading loading={false} percent={100} />
// <span>释放立即刷新</span>
// </div>
// );
// case REFRESH_STATE.loading:
// return (
// <div className={cls}>
// <Loading type="spinner" />
// <span>加载中</span>
// </div>
// );
// case REFRESH_STATE.success:
// return (
// <div className={cls}>
// <SuccessCircle theme="success" />
// <span>加载成功</span>
// </div>
// );
// case REFRESH_STATE.failure:
// return (
// <div className={cls}>
// <CloseCircle theme="danger" />
// <span>加载失败</span>
// </div>
// );
// default:
// }
// },
}}
load={{
state: loading,
distance: 200,
handler: loadData,
// render: (loadState) => {
// const cls = 'custom-control';
// switch (loadState) {
// case LOAD_STATE.loading:
// return <div className={cls}><Loading type="spinner" /></div>;
// case LOAD_STATE.failure:
// return <div className={cls}>加载失败</div>;
// case LOAD_STATE.complete:
// return <div className={cls}>我是有底线的</div>;
// }
// },
}}
>
<List>{dataSource}</List>
</Pull>
<BackTop
mountContainer={mountContainer}
scrollContainer={scrollContainer}
onClick={() => console.log('click back to top')}
>
<div
style={{
width: 60,
height: 60,
lineHeight: '60px',
textAlign: 'center',
backgroundColor: '#fff',
color: '#999',
fontSize: 20,
borderRadius: 30,
boxShadow: '0 2px 10px 0 rgba(0, 0, 0, 0.2)',
cursor: 'pointer',
}}
>
Up
</div>
</BackTop>
</>
);
};
ReactDOM.render(<Demo />, mountNode);
```
## API
| 属性 | 类型 | 默认值 | 说明 |
| :---------------- | :----- | :----- | :----------------------- |
| refresh | Action | - | 下拉刷新的参数配置 |
| load | Action | - | 上拉加载的参数配置 |
| animationDuration | number | 400 | 动画执行时间,单位:毫秒 |
| stayTime | number | 1000 | 加载成功停留时间 |
### Action 类型定义
| 属性 | 类型 | 默认值 | 说明 |
| :------------ | :---------------------------------------------------------------------------- | :----- | :-------------------------------------------------------------------- |
| state | REFRESH_STATE | LOAD_STATE | 0 | 状态枚举 |
| startDistance | number | 30 | 下拉时的助跑距离,单位:px |
| distance | number | 30 | 触发距离阀值,单位:px;下拉刷新阀值默认为 30px,上拉加载阀值默认为 0 |
| render | (refreshState: REFRESH_STATE | LOAD_STATE, percent: number) => ReactNode | - | 各状态渲染的回调函数 |
| handler | () => void | - | 达到阀值后释放触发的回调函数 |
### REFRESH_STATE 枚举定义
| 枚举值 | 说明 |
| :------ | :--------------------------- |
| normal | 普通状态 |
| pull | 下拉状态(未满足刷新条件) |
| drop | 释放立即刷新(满足刷新条件) |
| loading | 加载中 |
| success | 加载成功 |
| failure | 加载失败 |
### LOAD_STATE 枚举定义
| 枚举值 | 说明 |
| :------- | :------- |
| normal | 普通状态 |
| abort | 终止状态 |
| loading | 加载中 |
| success | 加载成功 |
| failure | 加载失败 |
| complete | 加载完成 |
## CSS 变量
| 属性 | 默认值 | 说明 |
| :------------------------- | :----------------------------- | :------------------------- |
| --control-height | '50px' | 刷新节点、加载节点高度 |
| --control-font-size | '14px' | 刷新节点、加载节点字体大小 |
| --control-padding-vertical | '20px' | 垂直方向内边距大小 |
| --control-text-color | 'var(--za-color-text-caption)' | 字体颜色 |
| --control-icon-size | '22px' | 图标大小 |