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.