List view is very common in app. At present, React Native has serious performance problems mainly in FlatList large list and other places. The following is to optimize the JS layer and even optimize the packaging of the Native layer to match the performance of the Native layer.

FlatList

React Native 0.43 releases FlatList instead of ListView. The FlatList implementation inherits from VirtualizedList. The underlying VirtualizedList provides more flexibility, but is not as easy to use as FlatList. Use FlatList if no special requirements cannot be met. The VirtualizedList implementation inherits from ScrollView, so FlatList inherits all props of VirtualizedList and ScrollView. If the corresponding prop or method is not found in the FlatList, the other two components can be used. React Native FlatList is similar to Android ListView and ios UITableView in that it reuses the off-screen view components to achieve high performance.

usage

The following example code uses typescript

The basic use

<FlatList<number> // Data ={[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]} // Key keyExtractor={(item, The index) = > index. The toString ()} / / item rendering renderItem = {({item: num}) = > (< Text > {num} < / Text >)} / >Copy the code

Commonly used props

extraData

Data other than data is used in the list, specified in this property, otherwise the interface probably won’t refresh

horizontal

Set to true to horizontal layout mode

inverted

Flip the scrolling direction, mostly used for reverse display of data such as chat lists

numColumns

Specifies how many items to display in a column

Commonly used method

scrollToEnd

Slide to the bottom of the view

scrollToIndex

Slide to the specified position

scrollToOffset

Slide to the specified pixel

Pull on loading

<FlatList // Pull back onEndReached={() => console.log()'Pull-up load'OnEndReachedThreshold ={0.1} /> onEndReachedThreshold={0.1}Copy the code

The drop-down refresh

<FlatList
  // trueRefreshing component refreshing={this.state.refreshing} // Drop back onRefresh=(async () => {this.setstate ({refreshing:true}); Await this.setState({refreshing:false
    });
  });
/>
Copy the code

Sliding event

onTouchStart

Finger down start slide, called once, to listen for the start of an interaction

onTouchMove

Finger swipe, call multiple times

onTouchEnd

The finger is released and called once to begin inertial scrolling, which is used to listen for the end of the interaction

onMomentumScrollBegin

Inertia scroll start, called once to listen for the start of the sliding inertia animation

onMomentumScrollEnd

Inertia scroll end, called once to listen for the end of the slide inertia animation

onScroll

Slide, called multiple times, used to listen to slide position

onScrollBeginDrag

Start slide, called once, to listen for the start of the slide

onScrollEndDrag

End of slide, called once to listen for end of slide

paging

For the development of a simple rotation view, paging slide to view the content

Private index = 0; / / must be with this binding. Otherwise, throw an exception private viewabilityConfig = {100} viewAreaCoveragePercentThreshold:; handleViewableItemsChanged = (info: { viewableItems: Array<ViewToken>; Changed: Array<ViewToken>}) => {// index = info.changed[0].index! ; } <FlatList // After each slide, one item stays in the entire view pagingEnabled={true} // Visible view Settings, 1-100, 50 indicates half visible callback, ViewabilityConfig ={this.viewabilityConfig} // Callback for visible view changes OnViewableItemsChanged = {this. HandleViewableItemsChanged} / / onViewableItemsChanged multiple callback, sliding judgment paging slide over the end of the listening inertia for real-time judgment view index shows, OnViewableItemsChanged onMomentumScrollEnd={() => console.log(' swipe to ', this.index)} />Copy the code

To optimize the

removeClippedSubviews

Remove off-screen components. Default is true and has the greatest impact on performance. Do not change to False

windowSize

The default value is 11. In high performance components, small values can be set appropriately. In fast sliding views, large values such as 300 can be set so that the current view does not render blank after fast sliding.

getItemLayout

Get height, if the view height is fixed, setting this property can greatly improve performance, eliminating the need to recalculate the view height every time during rendering.

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

key

Configure react keys properly to improve the reuse of react components, which greatly improves the performance. After components are removed from the screen, they are recycled and reused.

Native to optimize

In extremely demanding list views, where the data is in the thousands or even tens of thousands, flatLists can be inadequate in some cases, especially on Android devices. Here’s how to use the native Android RecyclerView directly to create a demanding list view.

Native view code

public class MyFlatListManager extends SimpleViewManager<MyFlatListManager.MyRecyclerView> {

  // Custom RecyclerView
  public static class MyRecyclerView extends RecyclerView {

    // Data list
    public List<Data> list = new ArrayList<>();
    / / adapter
    public MyAdapter myAdapter;
    // Layout manager
    public LinearLayoutManager mLayoutManager;

    public MyRecyclerView(Context context) {
      super(context);
      myAdapter = new MyAdapter(this, list);
      // Set it to vertical
      mLayoutManager = new LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false);
      setLayoutManager(mLayoutManager);
      // Fixed height to avoid re-measurement, improve performance
      setHasFixedSize(true);
      // Disable animation during data changes to avoid blinking
      setItemAnimator(null);
      setAdapter(myAdapter);
    }

    @Override
    public void requestLayout(a) {
      super.requestLayout();
      // React Native Android root view requestLayout is an empty function to avoid adding new views that cannot be displayed or the height and width are incorrect
      post(measureAndLayout);
    }

    public final Runnable measureAndLayout = new Runnable() {
      @Override
      public void run(a) {
        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;
    }

    // View creation
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
      View itemView = LayoutInflater.from(parent.getContext())
          .inflate(R.layout.movie_list_row, parent, false);
      // Manually reset the height, 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);
      // Bind the view data
    }

    @Override
    public int getItemCount(a) {
      returnlist.size(); }}private static final String TAG = "MyFlatListViewManager";

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

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

  @Nullable
  @Override
  public Map<String, Integer> getCommandsMap(a) {
    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 is a function that gets data from map
          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());
        // Notify the change
        myAdapter.notifyItemRangeInserted(position, args.size());
        break; }}}Copy the code

There are several caveats

  • SetHasFixedSize If the view height is fixed, setting the fixed height can improve performance
  • SetItemAnimator animations may cause flickering while loading images, etc
  • RequestLayout must manually trigger the measurement view again, which is blocked in Android by React Native
  • OnCreateViewHolder must manually set the Height and width of the itemView

The react anti-patterns

If the amount of data is too large, it is not appropriate to use props to transfer the data directly. The data may be several meters or larger. The React props mode is no longer suitable for this scenario. In the Web, too, a large amount of data is retransmitted every time a single data change is made, causing serious performance problems. In this case, using the component ref to call functions to add or remove large objects such as arrays one by one can improve performance. In android code, instead of using prop to pass FlatList data, add method is used to add, and then a layer of native component encapsulation in THE JS layer, so that the use of other components is consistent.