React Native list view Руководство по оптимизации использования FlatList

React Native

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

FlatList

React Native версии 0.43 представил FlatList вместо ListView. Реализация FlatList наследует от VirtualizedList. Базовый VirtualizedList обеспечивает более высокую гибкость, но не так удобен в использовании, как FlatList. Если нет особых требований, FlatList нельзя использовать напрямую. Реализация VirtualizedList наследуется от ScrollView, поэтому FlatList наследует все реквизиты VirtualizedList и ScrollView.При просмотре связанных документов, если соответствующий реквизит или метод не может быть найден во FlatList, можно использовать два других компонента. FlatList в React Native похож на listview для android и uitableview для ios, он перерабатывает компоненты внеэкранного представления для достижения высокой производительности.

Применение

В следующем примере кода используется машинописный текст

основное использование

<FlatList<number>
  // 数据数组
  data={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
  // key
  keyExtractor={(item, index) => index.toString()}
  // item渲染
  renderItem={({item: num}) => (
    <Text>{num}</Text>
  )}
/>

Общий реквизит

extraData

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

horizontal

Установите значение true, чтобы перейти в режим горизонтальной компоновки.

inverted

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

numColumns

Укажите, сколько элементов отображать в столбце

Общий метод

scrollToEnd

Проведите вниз экрана

scrollToIndex

Проведите в указанную позицию

scrollToOffset

проведите пальцем до указанного пикселя

подтягивающая загрузка

<FlatList
  // 上拉回调
  onEndReached={() => console.log('上拉加载')}
  // 滑动到最后视图内容比例,设置为0-1,例如0.5则表示滑到最后一个视图一半开始回调
  onEndReachedThreshold={0.1}
/>

Потяните вниз, чтобы обновить

<FlatList
  // true显示刷新组件
  refreshing={this.state.refreshing}
  // 下拉回调
  onRefresh=(async () => {
    this.setState({
      refreshing: true
    });
    await 耗时操作
    this.setState({
      refreshing: false
    });
  });
/>

событие смахивания

onTouchStart

Нажмите пальцем, чтобы начать скольжение, вызывается один раз, чтобы отслеживать начало взаимодействия.

onTouchMove

Проведите пальцем, чтобы позвонить несколько раз

onTouchEnd

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

onMomentumScrollBegin

Начинается инерционная прокрутка, вызываемая один раз для контроля начала скользящей инерционной анимации.

onMomentumScrollEnd

Инерционная прокрутка заканчивается, вызывается один раз для контроля окончания скользящей инерционной анимации.

onScroll

Во время скольжения вызывается несколько раз для контроля положения скольжения

onScrollBeginDrag

Начать скольжение, вызывается один раз для отслеживания начала скольжения

onScrollEndDrag

Конец слайда, вызываемый один раз, используется для отслеживания конца слайда.

нумерация страниц

Используется для разработки простого представления карусели, постраничного просмотра и прокрутки для просмотра контента и т. д.

// 当前视图索引
private index = 0;
// 必须与this绑定,否则抛出异常
private viewabilityConfig = {viewAreaCoveragePercentThreshold: 100};

handleViewableItemsChanged = (info: { viewableItems: Array<ViewToken>; changed: Array<ViewToken>}) => {
  // index为当前可见视图在view的索引
  this.index = info.changed[0].index!;
}

<FlatList
  // 每次滑动后一个item停留在整个视图
  pagingEnabled={true}
  // 可见视图设置,1-100,50表示一半可见时回调,100表示全部可见时回调
  viewabilityConfig={this.viewabilityConfig}
  // 可见视图变更回调
  onViewableItemsChanged={this.handleViewableItemsChanged}
  // onViewableItemsChanged会多次回调,监听惯性滑动结束判断分页滑动结束,如需要实时判断视图索引显示,则直接使用onViewableItemsChanged
  onMomentumScrollEnd={() => console.log('滑动至', this.index)}
/>

оптимизация

removeClippedSubviews

Удалите компоненты за пределами экрана, значение по умолчанию — true, что оказывает наибольшее влияние на производительность, не меняйте на false.

windowSize

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

getItemLayout

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

getItemLayout={(data, index) => ({length: height, offset: height * index, index})}

key

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

Встроенная оптимизация

В чрезвычайно требовательном представлении списка данные могут достигать тысяч или даже десятков тысяч.В некоторых случаях FlatList больше не может удовлетворить, особенно для устройств Android. Ниже описано, как напрямую использовать собственное представление Android RecyclerView для завершения представления списка с высоким спросом.

нативный код представления

public class MyFlatListManager extends SimpleViewManager<MyFlatListManager.MyRecyclerView> {

  // 自定义RecyclerView
  public static class MyRecyclerView extends RecyclerView {

    // 数据列表
    public List<Data> list = new ArrayList<>();
    // 适配器
    public MyAdapter myAdapter;
    // 布局管理器
    public LinearLayoutManager mLayoutManager;

    public MyRecyclerView(Context context) {
      super(context);
      myAdapter = new MyAdapter(this, list);
      // 设置为垂直方向
      mLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
      setLayoutManager(mLayoutManager);
      // 固定高度避免重新测量,提高性能
      setHasFixedSize(true);
      // 禁止数据变更时动画,避免闪烁
      setItemAnimator(null);
      setAdapter(myAdapter);
    }

    @Override
    public void requestLayout() {
      super.requestLayout();
      // react native android根视图requestLayout为空函数,避免加入新视图无法显示或者高度宽度不正确,手动执行测量
      post(measureAndLayout);
    }

    public final Runnable measureAndLayout = new Runnable() {
      @Override
      public void run() {
        measure(
            MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
            MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.EXACTLY));
        Log.d(TAG, "measureAndLayout");
        layout(getLeft(), getTop(), getRight(), getBottom());
      }
    };
  }

  private static class MyViewHolder extends RecyclerView.ViewHolder {

    public MyViewHolder(View itemView) {
      super(itemView);
    }
  }

  private static class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {

    private List<MyViewHolder> holders;

    private List<Data> list;

    private MyRecyclerView recyclerView;

    public MyAdapter(MyRecyclerView recyclerView, List<VideoInfo> list) {
      this.list = list;
      this.holders = new ArrayList<>();
      this.recyclerView = recyclerView;
    }

    // 视图创建
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      View itemView = LayoutInflater.from(parent.getContext())
          .inflate(R.layout.movie_list_row, parent, false);
      // 手动重新设置高度,match parent      
      itemView.getLayoutParams().height = parent.getHeight();
      itemView.getLayoutParams().width = parent.getWidth();
      return new MyViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(final MyViewHolder holder, int position) {
      Data data = list.get(position);
//      Log.i(TAG, "setTag " + position);
      holder.itemView.setTag(position);
      // 绑定视图数据
    }

    @Override
    public int getItemCount() {
      return list.size();
    }
  }

  private static final String TAG = "MyFlatListViewManager";

  @Override
  public String getName() {
    return "MyFlatListViewManager";
  }

  @Override
  protected MyRecyclerView createViewInstance(final ThemedReactContext reactContext) {
    return new MyRecyclerView(reactContext);
  }

  @Nullable
  @Override
  public Map<String, Integer> getCommandsMap() {
    Map<String, Integer> commandsMap = new HashMap<>();
    commandsMap.put("addData", 1);
    return commandsMap;
  }

  @Override
  public void receiveCommand(MyRecyclerView root, int commandId, @Nullable ReadableArray args) {
    MyAdapter myAdapter = (MyAdapter) root.getAdapter();
    switch (commandId) {
      case 1:
        if (args == null) return;
        Log.i(TAG, "addData size: " + args.size());
        Integer position = root.list.size();
        for (int i = 0; i < args.size(); i++) {
          // 初始化值,getData为从map中获取data的函数,自行根据结构实现
          Data data = getData(args.getMap(i));
          Log.i(TAG, "add data " + data);
          root.list.add(data);
        }
        Log.i(TAG, "addDatas old position " + position + " size " + args.size());
        // 通知变更
        myAdapter.notifyItemRangeInserted(position, args.size());
        break;
    }
  }
}

Есть несколько мест, на которые стоит обратить внимание

  • setHasFixedSize Если высота представления фиксирована, установка фиксированной высоты может улучшить производительность.
  • Анимация setItemAnimator может вызывать мерцание при загрузке изображений и т. д.
  • requestLayout должен повторно запускать представление измерений вручную.В Android эта часть механизма заблокирована React Native.
  • onCreateViewHolder должен вручную установить высоту и ширину itemView

реагировать на анти-паттерн

Реквизиты передаются между нативными компонентами и слоями js.Если объем данных слишком велик, использовать реквизиты для прямой передачи нецелесообразно, и данные могут достигать нескольких метров или даже больше. Режим реквизита в React уже не подходит для такого сценария, в вебе тоже большой объем данных пересылается каждый раз при изменении единичных данных, что вызовет серьезные проблемы с производительностью. В этом случае использование компонента ref для вызова функции для добавления или удаления связанных массивов один за другим, этих больших объектов, значительно улучшит производительность. В коде Android вместо использования prop для передачи данных FlatList используйте метод add для его добавления, а затем выполните еще один уровень инкапсуляции собственного компонента в слое js, чтобы использование соответствовало другим компонентам.