简体   繁体   English

如何在 react-native 中渲染 200 多个视图而不会出现性能问题

[英]How to render 200+ view without performance issue in react-native

I am trying to make a game in react-native.我正在尝试在 react-native 中制作游戏。 I want to render 200+ views on the Game screen.我想在游戏屏幕上呈现 200 多个视图。 Each View has a pressable functionality.每个视图都有一个可按下的功能。 Whenever I press the View I need to run a function that will change the View background color and update score on the game context.每当我按下视图时,我都需要运行 function 来更改视图背景颜色并更新游戏上下文中的分数。 But Whenever I try to press any View it took some time to change the background and update the context.但是每当我尝试按下任何视图时,都需要一些时间来更改背景和更新上下文。

Note笔记

I am using the expo as a development environment and I am using a real device too.我正在使用 expo 作为开发环境,并且我也在使用真实设备。

My View Component我的视图组件

import { useEffect, useState, memo } from "react";
import { useContext } from "react";
import { gameContext } from "./gameContext";
import { Pressable, View } from "react-native";
function CheckBoxCom() {
  const [active, setActive] = useState(false);
  const { score, setScore } = useContext(gameContext);
  useEffect(() => {
    let time = setTimeout(() => {
      setActive(false);
    }, Math.floor(Math.random() * 35000));

    return () => clearTimeout(time);
  }, [active]);
  const handlePress = () => {
    if (active) return;
    setActive(true);
    setScore(score + 1);
  };

  return (
    <View>
      <Pressable onPress={handlePress}>
        <View
          style={{
            width: 20,
            height: 20,
            borderWidth: 2,
            borderColor: active ? "green" : "gray",
            margin: 3,
            borderRadius: 3,
            backgroundColor: active ? "green" : null,
          }}
        ></View>
      </Pressable>
    </View>
  );
}

export default memo(CheckBoxCom);

Game Screen Component游戏画面组件

import { useContext, useEffect, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View, FlatList } from "react-native";
import CheckBox from "./CheckBox";
import { gameContext } from "./gameContext";

export default function Game({ navigation }) {
  const { score, time, setTime, boxList } = useContext(gameContext);
  const [intervalId, setIntervalId] = useState("");

  useEffect(() => {
    const int = setInterval(() => {
      setTime((prvTime) => prvTime - 1);
    }, 1000);
    setIntervalId(int);
    return () => clearInterval(int);
  }, []);

  if (time === 0) {
    clearInterval(intervalId);
    navigation.navigate("Score", { score });
  }

  return (
    <View style={{ flex: 1 }}>
      <StatusBar style="auto" />
      <View style={styles.textHeader}>
        <Text>Score : {score}</Text>
        <Text>Time Left: {time}s</Text>
      </View>
      <View style={styles.checkBoxContainer}>
        <FlatList
          style={{ alignSelf: "center" }}
          data={boxList}
          initialNumToRender={50}
          numColumns={12}
          renderItem={(i) => <CheckBox />}
          keyExtractor={(i) => i.toString()}
          scrollEnabled={false}
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  textHeader: {
    display: "flex",
    flexDirection: "row",
    justifyContent: "space-between",
    width: "100%",
    marginTop: 40,
    paddingHorizontal: 30,
  },
  checkBoxContainer: {
    margin: 20,
    display: "flex",
    flexWrap: "wrap",
    height: "80%",
    overflow: "hidden",
    flexDirection: "row",
  },
});

How can I run view function immediately whenever I press it?每当我按下它时,如何立即运行视图 function?

Use pagination in flatlist在平面列表中使用分页

for ref: Pagination in flatlist参考:平面列表中的分页

 import React, { Component } from 'react'; import { View, Text, TouchableOpacity, StyleSheet, FlatList, Platform, ActivityIndicator, } from 'react-native'; export default class App extends Component { constructor() { super(); this.state = { loading: true, //Loading state used while loading the data for the first time serverData: [], //Data Source for the FlatList fetching_from_server: false, //Loading state used while loading more data }; this.offset = 0; //Index of the offset to load from web API } componentDidMount() { //fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset) fetch('https://www.doviz.com/api/v1/currencies/all/latest').then(response => response.json()).then(responseJson => { responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1) console.log("offset: "+this.offset); console.log(responseJson.slice((this.offset*12),((this.offset+1)*12)-1)); //Successful response from the API Call this.offset = this.offset + 1; //After the response increasing the offset for the next API call. this.setState({ // serverData: [...this.state.serverData, ...responseJson.results], serverData: [...this.state.serverData, ...responseJson], //adding the new data with old one available in Data Source of the List loading: false, //updating the loading state to false }); }).catch(error => { console.error(error); }); } loadMoreData = () => { //On click of Load More button We will call the web API again this.setState({ fetching_from_server: true }, () => { //fetch('http://aboutreact.com/demo/getpost.php?offset=' + this.offset) fetch('https://www.doviz.com/api/v1/currencies/all/latest').then(response => response.json()).then(responseJson => { responseJson = responseJson.slice((this.offset*12),((this.offset+1)*12)-1) console.log("offset Load: "+this.offset); console.log(responseJson); //Successful response from the API Call this.offset = this.offset + 1; //After the response increasing the offset for the next API call. this.setState({ //serverData: [...this.state.serverData, ...responseJson.results], serverData: [...this.state.serverData, ...responseJson], fetching_from_server: false, //updating the loading state to false }); }).catch(error => { console.error(error); }); }); }; renderFooter() { return ( //Footer View with Load More button <View style={styles.footer}> <TouchableOpacity activeOpacity={0.9} onPress={this.loadMoreData} //On Click of button calling loadMoreData function to load more data style={styles.loadMoreBtn}> <Text style={styles.btnText}>Loading</Text> {this.state.fetching_from_server? ( <ActivityIndicator color="white" style={{ marginLeft: 8 }} /> ): null} </TouchableOpacity> </View> ); } render() { return ( <View style={styles.container}> {this.state.loading? ( <ActivityIndicator size="large" /> ): ( <FlatList style={{ width: '100%' }} keyExtractor={(item, index) => index} data={this.state.serverData} renderItem={({ item, index }) => ( <View style={styles.item}> <Text style={styles.text}> {item.currency} {'.'} {item.code} </Text> </View> )} onEndReached={this.loadMoreData} onEndReachedThreshold ={0.1} ItemSeparatorComponent={() => <View style={styles.separator} />} ListFooterComponent={this.renderFooter.bind(this)} //Adding Load More button as footer component /> )} </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: 30, }, item: { padding: 10,height:80 }, separator: { height: 0.5, backgroundColor: 'rgba(0,0,0,0.4)', }, text: { fontSize: 15, color: 'black', }, footer: { padding: 10, justifyContent: 'center', alignItems: 'center', flexDirection: 'row', }, loadMoreBtn: { padding: 10, backgroundColor: '#800000', borderRadius: 4, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', }, btnText: { color: 'white', fontSize: 15, textAlign: 'center', }, });

The reason it is slow is that when you press on a view, all 200+ CheckBoxCom components rerender.缓慢的原因是当您按下视图时,所有 200 多个CheckBoxCom组件都会重新呈现。 If they don't need to, we can improve performance by trying to prevent those unnecessary rerenders.如果他们不需要,我们可以通过尝试防止那些不必要的重新渲染来提高性能。

I believe the major bottleneck here is the gameContext .我相信这里的主要瓶颈是gameContext It groups together a lot of states and if any of these were to change, all components will rerender.它将许多状态组合在一起,如果其中任何一个状态发生变化,所有组件都将重新呈现。 It provides score state that you are reading within each CheckBoxCom .它提供您在每个CheckBoxCom中阅读的score state 。 Whenever the score changes all CheckBoxCom components will re-render.每当分数发生变化时,所有CheckBoxCom组件都会重新渲染。 If you change handlePress() to:如果您将handlePress()更改为:

const handlePress = () => {
  if (active) return;
  setActive(true);
  setScore(score => score + 1);
};

Please note the use of callback to update the score in the above handler.请注意使用回调来更新上述处理程序中的分数。 In this case, we don't need to read score from context, so we can remove it from the game context provider, only pass setScore .在这种情况下,我们不需要从上下文中读取score ,因此我们可以将其从游戏上下文提供程序中移除,只需通过setScore Removing score from the context provider is important because not doing so will rerender all components using the context even if you don't specifically destructure score .从上下文提供程序中删除score很重要,因为不这样做会使用上下文重新渲染所有组件,即使您没有专门解构score

Also, make sure you don't have a lot of state variables within a single context.此外,请确保在单个上下文中没有很多 state 变量。 Split it into multiple contexts if you have different states in there.如果您在那里有不同的状态,请将其拆分为多个上下文。 In this way, you will be able to reduce unnecessary rerenders of the CheckBoxCom components.这样,您将能够减少CheckBoxCom组件的不必要重新呈现。

Since your CheckBoxCom components have an internal state, using React.memo() will not help to prevent rerenders because it only works for rerenders resulting from changed props.由于您的CheckBoxCom组件具有内部 state,因此使用React.memo()将无助于防止重新渲染,因为它仅适用于由更改的道具导致的重新渲染。

But if you are able to refactor them to lift the active state up to the parent ie something like activeViews or something (which could be a map of indexes which are true ie active), then you can pass the active state as a boolean prop to each CheckBoxCom component. But if you are able to refactor them to lift the active state up to the parent ie something like activeViews or something (which could be a map of indexes which are true ie active), then you can pass the active state as a boolean prop to每个CheckBoxCom组件。 And if we also pass setScore via a prop instead of via context, we can benefit from React.memo() .如果我们也通过 prop 而不是 context 传递setScore ,我们可以从React.memo()中受益。 BTW it is not necessary to wrap setState methods with useCallback() .顺便说一句,没有必要用useCallback()包装setState方法。

The end result will be: CheckBoxCom components with zero internal states and no reliance on context, in other words, pure components ie components which work nicely with React.memo() .最终结果将是:内部状态为零且不依赖上下文的CheckBoxCom组件,换句话说,纯组件,即与React.memo()配合得很好的组件。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM