PullToRefresh 拉动刷新

通过触发,立刻重新加载内容。

规则

  • 可以和 ListView 结合使用,也可以单独使用。

  • 可考虑定期自动刷新, e.g. 登录 APP 后,自动刷新首页 List。

代码演示

拉动刷新

  1. import { PullToRefresh, Button } from 'antd-mobile';
  2. function genData() {
  3. const dataArr = [];
  4. for (let i = 0; i < 20; i++) {
  5. dataArr.push(i);
  6. }
  7. return dataArr;
  8. }
  9. class Demo extends React.Component {
  10. constructor(props) {
  11. super(props);
  12. this.state = {
  13. refreshing: false,
  14. down: true,
  15. height: document.documentElement.clientHeight,
  16. data: [],
  17. };
  18. }
  19. componentDidMount() {
  20. const hei = this.state.height - ReactDOM.findDOMNode(this.ptr).offsetTop;
  21. setTimeout(() => this.setState({
  22. height: hei,
  23. data: genData(),
  24. }), 0);
  25. }
  26. render() {
  27. return (<div>
  28. <Button
  29. style={{ marginBottom: 15 }}
  30. onClick={() => this.setState({ down: !this.state.down })}
  31. >
  32. direction: {this.state.down ? 'down' : 'up'}
  33. </Button>
  34. <PullToRefresh
  35. damping={60}
  36. ref={el => this.ptr = el}
  37. style={{
  38. height: this.state.height,
  39. overflow: 'auto',
  40. }}
  41. indicator={this.state.down ? {} : { deactivate: '上拉可以刷新' }}
  42. direction={this.state.down ? 'down' : 'up'}
  43. refreshing={this.state.refreshing}
  44. onRefresh={() => {
  45. this.setState({ refreshing: true });
  46. setTimeout(() => {
  47. this.setState({ refreshing: false });
  48. }, 1000);
  49. }}
  50. >
  51. {this.state.data.map(i => (
  52. <div key={i} style={{ textAlign: 'center', padding: 20 }}>
  53. {this.state.down ? 'pull down' : 'pull up'} {i}
  54. </div>
  55. ))}
  56. </PullToRefresh>
  57. </div>);
  58. }
  59. }
  60. ReactDOM.render(<Demo />, mountNode);

ListView 下拉刷新

  1. /* eslint no-dupe-keys: 0, no-mixed-operators: 0 */
  2. import { PullToRefresh, ListView, Button } from 'antd-mobile';
  3. const data = [
  4. {
  5. img: 'https://zos.alipayobjects.com/rmsportal/dKbkpPXKfvZzWCM.png',
  6. title: 'Meet hotel',
  7. des: '不是所有的兼职汪都需要风吹日晒',
  8. },
  9. {
  10. img: 'https://zos.alipayobjects.com/rmsportal/XmwCzSeJiqpkuMB.png',
  11. title: 'McDonald\'s invites you',
  12. des: '不是所有的兼职汪都需要风吹日晒',
  13. },
  14. {
  15. img: 'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
  16. title: 'Eat the week',
  17. des: '不是所有的兼职汪都需要风吹日晒',
  18. },
  19. ];
  20. const NUM_ROWS = 20;
  21. let pageIndex = 0;
  22. function genData(pIndex = 0) {
  23. const dataArr = [];
  24. for (let i = 0; i < NUM_ROWS; i++) {
  25. dataArr.push(`row - ${(pIndex * NUM_ROWS) + i}`);
  26. }
  27. return dataArr;
  28. }
  29. class App extends React.Component {
  30. constructor(props) {
  31. super(props);
  32. const dataSource = new ListView.DataSource({
  33. rowHasChanged: (row1, row2) => row1 !== row2,
  34. });
  35. this.state = {
  36. dataSource,
  37. refreshing: true,
  38. isLoading: true,
  39. height: document.documentElement.clientHeight,
  40. useBodyScroll: false,
  41. };
  42. }
  43. // If you use redux, the data maybe at props, you need use `componentWillReceiveProps`
  44. // componentWillReceiveProps(nextProps) {
  45. // if (nextProps.dataSource !== this.props.dataSource) {
  46. // this.setState({
  47. // dataSource: this.state.dataSource.cloneWithRows(nextProps.dataSource),
  48. // });
  49. // }
  50. // }
  51. componentDidUpdate() {
  52. if (this.state.useBodyScroll) {
  53. document.body.style.overflow = 'auto';
  54. } else {
  55. document.body.style.overflow = 'hidden';
  56. }
  57. }
  58. componentDidMount() {
  59. const hei = this.state.height - ReactDOM.findDOMNode(this.lv).offsetTop;
  60. setTimeout(() => {
  61. this.rData = genData();
  62. this.setState({
  63. dataSource: this.state.dataSource.cloneWithRows(genData()),
  64. height: hei,
  65. refreshing: false,
  66. isLoading: false,
  67. });
  68. }, 1500);
  69. }
  70. onRefresh = () => {
  71. this.setState({ refreshing: true, isLoading: true });
  72. // simulate initial Ajax
  73. setTimeout(() => {
  74. this.rData = genData();
  75. this.setState({
  76. dataSource: this.state.dataSource.cloneWithRows(this.rData),
  77. refreshing: false,
  78. isLoading: false,
  79. });
  80. }, 600);
  81. };
  82. onEndReached = (event) => {
  83. // load new data
  84. // hasMore: from backend data, indicates whether it is the last page, here is false
  85. if (this.state.isLoading && !this.state.hasMore) {
  86. return;
  87. }
  88. console.log('reach end', event);
  89. this.setState({ isLoading: true });
  90. setTimeout(() => {
  91. this.rData = [...this.rData, ...genData(++pageIndex)];
  92. this.setState({
  93. dataSource: this.state.dataSource.cloneWithRows(this.rData),
  94. isLoading: false,
  95. });
  96. }, 1000);
  97. };
  98. render() {
  99. const separator = (sectionID, rowID) => (
  100. <div
  101. key={`${sectionID}-${rowID}`}
  102. style={{
  103. backgroundColor: '#F5F5F9',
  104. height: 8,
  105. borderTop: '1px solid #ECECED',
  106. borderBottom: '1px solid #ECECED',
  107. }}
  108. />
  109. );
  110. let index = data.length - 1;
  111. const row = (rowData, sectionID, rowID) => {
  112. if (index < 0) {
  113. index = data.length - 1;
  114. }
  115. const obj = data[index--];
  116. return (
  117. <div key={rowID}
  118. style={{
  119. padding: '0 15px',
  120. backgroundColor: 'white',
  121. }}
  122. >
  123. <div style={{ height: '50px', lineHeight: '50px', color: '#888', fontSize: '18px', borderBottom: '1px solid #ddd' }}>
  124. {obj.title}
  125. </div>
  126. <div style={{ display: '-webkit-box', display: 'flex', padding: '15px' }}>
  127. <img style={{ height: '63px', width: '63px', marginRight: '15px' }} src={obj.img} alt="" />
  128. <div style={{ display: 'inline-block' }}>
  129. <div style={{ marginBottom: '8px', color: '#000', fontSize: '16px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '250px' }}>{obj.des}-{rowData}</div>
  130. <div style={{ fontSize: '16px' }}><span style={{ fontSize: '30px', color: '#FF6E27' }}>{rowID}</span> 元/任务</div>
  131. </div>
  132. </div>
  133. </div>
  134. );
  135. };
  136. return (<div>
  137. <Button
  138. style={{ margin: '30px 15px' }}
  139. inline
  140. onClick={() => this.setState({ useBodyScroll: !this.state.useBodyScroll })}
  141. >
  142. {this.state.useBodyScroll ? 'useBodyScroll' : 'partial scroll'}
  143. </Button>
  144. <ListView
  145. key={this.state.useBodyScroll ? '0' : '1'}
  146. ref={el => this.lv = el}
  147. dataSource={this.state.dataSource}
  148. renderHeader={() => <span>Pull to refresh</span>}
  149. renderFooter={() => (<div style={{ padding: 30, textAlign: 'center' }}>
  150. {this.state.isLoading ? 'Loading...' : 'Loaded'}
  151. </div>)}
  152. renderRow={row}
  153. renderSeparator={separator}
  154. useBodyScroll={this.state.useBodyScroll}
  155. style={this.state.useBodyScroll ? {} : {
  156. height: this.state.height,
  157. border: '1px solid #ddd',
  158. margin: '5px 0',
  159. }}
  160. pullToRefresh={<PullToRefresh
  161. refreshing={this.state.refreshing}
  162. onRefresh={this.onRefresh}
  163. />}
  164. onEndReached={this.onEndReached}
  165. pageSize={5}
  166. />
  167. </div>);
  168. }
  169. }
  170. ReactDOM.render(<App />, mountNode);

PullToRefresh拉动刷新 - 图1

API

属性说明类型默认值
direction拉动方向,可以是 updownStringdown
distanceToRefresh刷新距离number25
refreshing是否显示刷新状态boolfalse
onRefresh必选, 刷新回调函数() => void-
indicator指示器配置 { activate: ReactNode, deactivate: ReactNode, release: ReactNode, finish: ReactNode }Object-
damping拉动距离限制, 建议小于 200number100
如果页面使用了对 viewport 进行缩放的高清适配方案,请自行对 distanceToRefresh 进行调整, 例如对 antd-mobile@1.x 高清方案,可设置 distanceToRefresh = window.devicePixelRatio * 25