React Native — настраиваемый раскрывающийся список обновлений

внешний интерфейс JavaScript React.js React Native

Страницы списков очень распространены в мобильной разработке.В React Native мы обычно используем компоненты FlatList или SectionList для реализации этих представлений списков. Обычно требуется загрузить и отобразить на странице списка большой объем данных. В настоящее время используется постраничная загрузка. Поэтому для компонентов списка во многих случаях необходимо реализовать обновление по запросу и загрузку по запросу.

В этой статье инкапсулируется RefreshListView, который поддерживает обновление по запросу и загрузку по запросу на основе FlatList.После инкапсуляции исходного FlatList очень удобно вызывать обновление по запросу и по запросу.

Реализация выпадающего обновления очень проста, здесь мы следуем свойствам самого FlatList для достижения

onRefresh设置此选项后,则会在列表头部添加一个标准的RefreshControl控件,以便实现“下拉刷新”的功能。同时你需要正确设置refreshing属性。

refreshing——bool值,用来控制刷新控件的显示与隐藏。刷新完成后设为false。

Установив эти два свойства, мы можем добиться операции обновления заголовка FlatList.Элементы управления используют стиль по умолчанию, а Android и iOS используют компоненты своих соответствующих систем для отображения.

Смысл в том, чтобы подтянуть и загрузить больше, в компоненте списка React Native такой функции нет, и нам нужно реализовать ее самим. Для подтягивающей загрузки у нас обычно есть несколько состояний.Здесь я создаю файл RefreshState.js для хранения подтягивающего состояния загрузки:

export default {
  Idle: 'Idle',               // 初始状态,无刷新的情况
  CanLoadMore: 'CanLoadMore', // 可以加载更多,表示列表还有数据可以继续加载
  Refreshing: 'Refreshing',   // 正在刷新中
  NoMoreData: 'NoMoreData',   // 没有更多数据了
  Failure: 'Failure'          // 刷新失败
}

Затем инкапсулируйте компонент RefreshFooter в соответствии с этими состояниями, чтобы он отображал разное содержимое в соответствии с разными состояниями, не так уж много глупостей, чтобы говорить о коде:

import React, {Component} from 'react';
import {View, Text, ActivityIndicator, StyleSheet, TouchableOpacity} from 'react-native';
import RefreshState from './RefreshState';
import PropTypes from 'prop-types';

export default class RefreshFooter extends Component {

  static propTypes = {
    onLoadMore: PropTypes.func,     // 加载更多数据的方法
    onRetryLoading: PropTypes.func, // 重新加载的方法
  };
  
  static defaultProps = {
    footerRefreshingText: "努力加载中",
    footerLoadMoreText: "上拉加载更多",
    footerFailureText: "点击重新加载",
    footerNoMoreDataText: "已全部加载完毕"
  };
  
  render() {
    let {state} = this.props;
    let footer = null;
    switch (state) {
      case RefreshState.Idle:
        // Idle情况下为null,不显示尾部组件
        break;
      case RefreshState.Refreshing:
        // 显示一个loading视图
        footer =
          <View style={styles.loadingView}>
            <ActivityIndicator size="small"/>
            <Text style={styles.refreshingText}>{this.props.footerRefreshingText}</Text>
          </View>;
        break;
      case RefreshState.CanLoadMore:
        // 显示上拉加载更多的文字
        footer =
          <View style={styles.loadingView}>
            <Text style={styles.footerText}>{this.props.footerLoadMoreText}</Text>
          </View>;
        break;
      case RefreshState.NoMoreData:
        // 显示没有更多数据的文字,内容可以自己修改
        footer =
          <View style={styles.loadingView}>
            <Text style={styles.footerText}>{this.props.footerNoMoreDataText}</Text>
          </View>;
        break;
      case RefreshState.Failure:
        // 加载失败的情况使用TouchableOpacity做一个可点击的组件,外部调用onRetryLoading重新加载数据
        footer =
          <TouchableOpacity style={styles.loadingView} onPress={()=>{
            this.props.onRetryLoading && this.props.onRetryLoading();
          }}>
            <Text style={styles.footerText}>{this.props.footerFailureText}</Text>
          </TouchableOpacity>;
        break;
    }
    return footer;
  }
}

const styles = StyleSheet.create({
  loadingView: {
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
    padding: 15,
  },
  refreshingText: {
    fontSize: 12,
    color: "#666666",
    paddingLeft: 10,
  },
  footerText: {
    fontSize: 12,
    color: "#666666"
  }
});

Обратите внимание, что propTypes — это метод, который мы определяем для внешнего вызова компонента RefreshFooter.Тип метода должен быть указан в PropTypes, и должна быть установлена ​​библиотека зависимостей prop-types facebook.Лучше всего использоватьyarn add prop-typesПри установке легко ошибиться. Используется здесь как проверка типа во время выполнения, вы можетекликните сюдаУзнать больше.

В defaultProps мы определяем текстовое содержимое по умолчанию в нескольких разных состояниях, которые можно изменить, передав значения извне.

Следующим шагом является реализация этого RefreshListView. Прежде всего, должно быть понятно, что этот RefreshListView должен иметь методы вызова для обновления заголовка и обновления хвоста, а конкретный метод вызова данных должен быть реализован извне. Сначала определите два метода, например RefreshFooter:

static propTypes = {
  onHeaderRefresh: PropTypes.func, // 下拉刷新的方法,供外部调用
  onFooterRefresh: PropTypes.func, // 上拉加载的方法,供外部调用
};

Как упоминалось выше, выпадающее обновление заголовка реализовано с использованием функций FlatList.Нам нужно определить логическое значение isHeaderRefreshing как значение атрибута обновления, чтобы контролировать, отображается ли заголовок или нет. В то же время определите isFooterRefreshing, чтобы судить о статусе обновления хвостового компонента. Определите footerState, чтобы установить состояние текущего хвостового компонента в качестве значения RefreshFooter.

constructor(props) {
    super(props);
    this.state = {
      isHeaderRefreshing: false,  // 头部是否正在刷新
      isFooterRefreshing: false,  // 尾部是否正在刷新
      footerState: RefreshState.Idle, // 尾部当前的状态,默认为Idle,不显示控件
    }
  }

Функция рендеринга выглядит следующим образом:

render() {
    return (
      <FlatList
        {...this.props}
        onRefresh={()=>{ this.beginHeaderRefresh() }}
        refreshing={this.state.isHeaderRefreshing}
        onEndReached={() => { this.beginFooterRefresh() }}
        onEndReachedThreshold={0.1}  // 这里取值0.1(0~1之间不包括0和1),可以根据实际情况调整,取值尽量小
        ListFooterComponent={this._renderFooter}
      />
    )
  }
  
  _renderFooter = () => {
    return (
      <RefreshFooter
        state={this.state.footerState}
        onRetryLoading={()=>{
          this.beginFooterRefresh()
        }}
      />
    )
  };

Вы можете видеть, что в приведенном выше коде есть два метода, beginHeaderRefresh и beginFooterRefresh.Эти два метода используются для вызова обновления, но все еще есть некоторые логические ситуации, которые необходимо оценить перед обновлением. Например, голова и хвост не могут быть обновлены одновременно, иначе это может повлиять на результаты обработки данных, и необходимо предотвратить повторные операции обновления при обновлении.Все это следует учитывать. Здесь я подробно прокомментировал код:

/// 开始下拉刷新
beginHeaderRefresh() {
  if (this.shouldStartHeaderRefreshing()) {
    this.startHeaderRefreshing();
  }
}

/// 开始上拉加载更多
beginFooterRefresh() {
  if (this.shouldStartFooterRefreshing()) {
    this.startFooterRefreshing();
  }
}

/***
 * 当前是否可以进行下拉刷新
 * @returns {boolean}
 *
 * 如果列表尾部正在执行上拉加载,就返回false
 * 如果列表头部已经在刷新中了,就返回false
 */
shouldStartHeaderRefreshing() {
  if (this.state.footerState === RefreshState.refreshing ||
    this.state.isHeaderRefreshing ||
    this.state.isFooterRefreshing) {
    return false;
  }
  return true;
}

/***
 * 当前是否可以进行上拉加载更多
 * @returns {boolean}
 *
 * 如果底部已经在刷新,返回false
 * 如果底部状态是没有更多数据了,返回false
 * 如果头部在刷新,则返回false
 * 如果列表数据为空,则返回false(初始状态下列表是空的,这时候肯定不需要上拉加载更多,而应该执行下拉刷新)
 */
shouldStartFooterRefreshing() {
  if (this.state.footerState === RefreshState.refreshing ||
    this.state.footerState === RefreshState.NoMoreData ||
    this.props.data.length === 0 ||
    this.state.isHeaderRefreshing ||
    this.state.isFooterRefreshing) {
    return false;
  }
  return true;
}

Логика startHeaderRefreshing и startFooterRefreshing следующая:

/// 下拉刷新,设置完刷新状态后再调用刷新方法,使页面上可以显示出加载中的UI,注意这里setState写法
startHeaderRefreshing() {
  this.setState(
    {
      isHeaderRefreshing: true
    },
    () => {
      this.props.onHeaderRefresh && this.props.onHeaderRefresh();
    }
  );
}

/// 上拉加载更多,将底部刷新状态改为正在刷新,然后调用刷新方法,页面上可以显示出加载中的UI,注意这里setState写法
startFooterRefreshing() {
  this.setState(
    {
      footerState: RefreshState.Refreshing,
      isFooterRefreshing: true
    },
    () => {
      this.props.onFooterRefresh && this.props.onFooterRefresh();
    }
  );
}

Перед обновлением нам нужно отобразить компоненты головы или хвоста, а затем вызвать метод внешнего интерфейса данных. Преимущество написания setState здесь в том, что метод в стрелочной функции не будет вызываться до тех пор, пока значение в состоянии не будет обновлено, что происходит в строгом порядке.this.props.onFooterRefresh && this.props.onFooterRefresh()Написанный вне setState, мы можем не увидеть загрузку головы или жесткую загрузку хвоста в пользовательском интерфейсе, и был вызван метод интерфейса.

Наконец, после завершения обновления нам также нужно вызвать метод, чтобы остановить обновление, чтобы компоненты головы или хвоста больше не отображались, иначе это может считаться ошибкой, пока он еще загружается. Вот как остановить обновление:

/**
 * 根据尾部组件状态来停止刷新
 * @param footerState
 *
 * 如果刷新完成,当前列表数据源是空的,就不显示尾部组件了。
 * 这里这样做是因为通常列表无数据时,我们会显示一个空白页,如果再显示尾部组件如"没有更多数据了"就显得很多余
 */
endRefreshing(footerState: RefreshState) {
  let footerRefreshState = footerState;
  if (this.props.data.length === 0) {
    footerRefreshState = RefreshState.Idle;
  }
  this.setState({
    footerState: footerRefreshState,
    isHeaderRefreshing: false,
    isFooterRefreshing: false
  })
}

Здесь параметр состояния хвостового компонента передается для обновления стиля хвостового компонента. В то же время выносится суждение о данных источника данных.Если он пуст, это означает, что данных в настоящее время нет, и можно отобразить пустую страницу, поэтому хвостовой компонент не нужно отображать.

Ниже приведен рендеринг загрузки страницы фильма Douban, которую я реализовал с помощью RefreshListView:

Полный демонстрационный адрес:GitHub.com/Nobody Arron at/Hot…, Спасибо за прочтение!