繁体   English   中英

在列表渲染完成之前,不会触发 React Native Flatlist 元素 onPress

[英]React Native Flatlist element onPress not fired until List rendering is complete

我有一个FlatList接收最大的(不可变)数据。 50 个元素,它使用react-native-svg在每个列表项 Svg 中呈现。

部分图形包裹有用于选择元素的Pressable组件。

现在的问题是,在FlatList遍历所有 50 个项目之前,我不能 select 任何元素。

我不明白的是,屏幕外的项目甚至没有被渲染,它只是容器。 全部渲染完成后,我可以单击元素,显示涟漪效果并触发事件。

眼镜:

  • Expo@46.0.0
  • 反应本机@ 0.69.6
  • 反应@ 18.0.0
  • 通过 android 运行expo start --no-dev --minify然后在 Expo Go 中打开

再生产:

import React, { useEffect, useState } from 'react'
import { FlatList } from 'react-native'
import { Foo } from '/path/to/Foo'
import { Bar } from '/path/to/Bar'

export const Overview = props => {
  const [data, setData] = useState(null)
  
  // 1. fetching data

  useEffect(() => {
    // load data from api
    const loaded = [{ id: 0, type: 'foo' }, { id: 1, type: 'bar' }] // make a list of ~50 here
    setData(loaded)
  }, [])

  if (!data?.length) {
    return null
  }

  // 2. render list item
  const onPressed = () => console.debug('pressed')

  const renderListItem = ({ index, item }) => {
    if (item.type === 'foo') {
      return (<Foo key={`foo-${index}`} onPressed={onPressed} />)
    } 


    if (item.type === 'bar') {
      return (<Foo key={`bar-${index}`} onPressed={onPressed} />)
    }
  
    return null
  }

  // at this point data exists but will not be changed anymore
  // so theoretically there should be no re-render
  return (
    <FlatList
       data={data}
       renderItem={renderListItem}
       inverted={true}
       decelerationRate="fast"
       disableIntervalMomentum={true}
       removeClippedSubviews={true}
       persistentScrollbar={true}
       keyExtractor={flatListKeyExtractor}
       initialNumToRender={10}
       maxToRenderPerBatch={10}
       updateCellsBatchingPeriod={100}
       getItemLayout={flatListGetItemLayout}
     />
    )
  }
}


// optimized functions
const flatListKeyExtractor = (item) => item.id
const flatListGetItemLayout = (data, index) => {
  const entry = data[index]
  const length = entry && ['foo', 'bar'].includes(entry.type)
    ? 110
    : 59
  return { length, offset: length * index, index }
}

Svg 组件,仅显示Foo ,因为Bar在结构上相似并且问题影响两者:

import React from 'react'
import Svg, { G, Circle } from 'react-native-svg'

const radius = 25
const size = radius * 2

// this is a very simplified example, 
// rendering a pressable circle
const FooSvg = props => {
  return (
    <Pressable
      android_ripple={rippleConfig}
      pressRetentionOffset={0}
      hitSlop={0}
      onPress={props.onPress}
    >
      <Svg
        style={props.style}
        width={size}
        height={size}
        viewBox={`0 0 ${radius * 2} ${radius * 2}`}
      >
        <G>
          <Circle
            cx='50%'
            cy='50%'
            stroke='black'
            strokeWidth='2'
            r={radius}
            fill='red'
          />
        </G>
      </Svg>
    </Pressable>
  )
}

const rippleConfig = {
  radius: 50,
  borderless: true,
  color: '#00ff00'
}

// pure component
export const Foo = React.memo(FooSvg)

渲染性能本身非常好,但是我不明白,为什么我需要等待长达两秒钟,直到我可以按下圆圈,尽管它们已经被渲染了。

任何帮助是极大的赞赏。

编辑

快速滚动列表时,我得到:

 VirtualizedList: You have a large list that is slow to update - make sure your renderItem function renders components that follow React performance best practices like PureComponent, shouldComponentUpdate, etc. {"contentLength": 4740, "dt": 4156, "prevDt": 5142}

但是,组件已经被记忆(PureComponent)并且不是很复杂。 一定还有别的问题。

硬件

我用 iPad 进行了交叉测试,如果描述的问题没有。 它似乎只发生在 Android 上。

请忽略语法错误。

这是 FlatList 的问题。 平面列表不适合在联系人列表中呈现更大的列表。 Flatlist 仅适用于从教堂中的 API 获取数据,例如 Facebook。 从 API 中得到 10 个元素,然后。 然后在下一次通话中再获得 10 个。

渲染。 大量项目,如联系人列表(超过 1000 个)或类似的东西,请使用https://bolan9999.github.io/react-native-largelist/#/en/

import React, {useRef, useState} from 'react';
import {
  Image,
  StyleSheet,
  Text,
  TextInput,
  TouchableOpacity,
  View,
} from 'react-native';
import {LargeList} from 'react-native-largelist-v3';
import Modal from 'react-native-modal';
import {widthPercentageToDP as wp} from 'react-native-responsive-screen';
import FontAwesome from 'react-native-vector-icons/FontAwesome';
import fonts from '../constants/fonts';
import {moderateScale} from '../constants/scaling';
import colors from '../constants/theme';
import countries from '../Data/larger_countries.json';

const CountrySelectionModal = ({visible, setDefaultCountry, setVisible}) => {
  const pressable = useRef(true);
  const [country_data, setCountryData] = useState(countries);
  const [search_text, setSearchText] = useState('');

  const onScrollStart = () => {
    if (pressable.current) {
      pressable.current = false;
    }
  };

  const onScrollEnd = () => {
    if (!pressable.current) {
      setTimeout(() => {
        pressable.current = true;
      }, 100);
    }
  };

  const _renderHeader = () => {
    return (
      <View style={styles.headermainView}>
        <View style={styles.headerTextBg}>
          <Text style={styles.headerTitle}>Select your country</Text>
        </View>
        <View style={styles.headerInputBg}>
          <TouchableOpacity
            onPress={() => searchcountry(search_text)}
            style={styles.headericonBg}>
            <FontAwesome
              name="search"
              size={moderateScale(20)}
              color={colors.textColor}
            />
          </TouchableOpacity>
          <TextInput
            placeholder="Select country by name"
            value={search_text}
            placeholderTextColor={colors.textColor}
            style={styles.headerTextInput}
            onChangeText={text => searchcountry(text)}
          />
        </View>
      </View>
    );
  };

  const _renderEmpty = () => {
    return (
      <View
        style={{
          height: moderateScale(50),
          backgroundColor: colors.white,

          flex: 1,
          justifyContent: 'center',
        }}>
        <Text style={styles.notFoundText}>No Result Found</Text>
      </View>
    );
  };
  const _renderItem = ({section: section, row: row}) => {
    const country = country_data[section].items[row];
    return (
      <TouchableOpacity
        activeOpacity={0.95}
        onPress={() => {
          setDefaultCountry(country),
            setSearchText(''),
            setCountryData(countries),
            setVisible(false);
        }}
        style={styles.renderItemMainView}>
        <View style={styles.FlagNameView}>
          <Image
            source={{
              uri: `https://zoobiapps.com/country_flags/${country.code.toLowerCase()}.png`,
            }}
            style={styles.imgView}
          />
          <Text numberOfLines={1} ellipsizeMode="tail" style={styles.text}>
            {country.name}
          </Text>
        </View>
        <Text style={{...styles.text, marginRight: wp(5), textAlign: 'right'}}>
          (+{country.callingCode})
        </Text>
      </TouchableOpacity>
    );
  };

  const searchcountry = text => {
    setSearchText(text);
    const items = countries[0].items.filter(row => {
      const result = `${row.code}${row.name.toUpperCase()}`;
      const txt = text.toUpperCase();
      return result.indexOf(txt) > -1;
    });
    setCountryData([{header: 'countries', items: items}]);
  };

  return (
    <Modal
      style={styles.modalStyle}
      animationIn={'slideInUp'}
      animationOut={'slideOutDown'}
      animationInTiming={1000}
      backdropOpacity={0.3}
      animationOutTiming={700}
      hideModalContentWhileAnimating={true}
      backdropTransitionInTiming={500}
      backdropTransitionOutTiming={700}
      useNativeDriver={true}
      isVisible={visible}
      onBackdropPress={() => {
        setVisible(false);
      }}
      onBackButtonPress={() => {
        setVisible(false);
      }}>
      <LargeList
        showsHorizontalScrollIndicator={false}
        style={{flex: 1, padding: moderateScale(10)}}
        onMomentumScrollBegin={onScrollStart}
        onMomentumScrollEnd={onScrollEnd}
        contentStyle={{backgroundColor: '#fff'}}
        showsVerticalScrollIndicator={false}
        heightForIndexPath={() => moderateScale(49)}
        renderIndexPath={_renderItem}
        data={country_data}
        bounces={false}
        renderEmpty={_renderEmpty}
        renderHeader={_renderHeader}
        headerStickyEnabled={true}
        initialContentOffset={{x: 0, y: 600}}
      />
    </Modal>
  );
};
export default CountrySelectionModal;

const styles = StyleSheet.create({
  modalStyle: {
    margin: moderateScale(15),
    borderRadius: moderateScale(10),
    overflow: 'hidden',
    backgroundColor: '#fff',
    marginVertical: moderateScale(60),
    justifyContent: 'center',
  },
  headermainView: {
    height: moderateScale(105),
    backgroundColor: '#fff',
  },
  headerTextBg: {
    height: moderateScale(50),
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
  },
  headerTitle: {
    textAlign: 'center',
    fontFamily: fonts.Bold,
    fontSize: moderateScale(16),
    color: colors.textColor,
    textAlignVertical: 'center',
  },
  headerInputBg: {
    height: moderateScale(40),
    borderRadius: moderateScale(30),
    overflow: 'hidden',
    justifyContent: 'center',
    alignItems: 'center',
    paddingHorizontal: moderateScale(10),
    backgroundColor: colors.inputbgColor,
    flexDirection: 'row',
  },
  headericonBg: {
    backgroundColor: colors.inputbgColor,
    alignItems: 'center',
    justifyContent: 'center',
    width: moderateScale(40),
    height: moderateScale(40),
  },
  headerTextInput: {
    backgroundColor: colors.inputbgColor,
    height: moderateScale(30),
    flex: 1,
    paddingTop: 0,
    includeFontPadding: false,
    fontFamily: fonts.Medium,
    color: colors.textColor,
    paddingBottom: 0,
    paddingHorizontal: 0,
  },
  notFoundText: {
    fontFamily: fonts.Medium,
    textAlign: 'center',
    fontSize: moderateScale(14),
    textAlignVertical: 'center',
    color: colors.textColor,
  },
  renderItemMainView: {
    backgroundColor: colors.white,
    flexDirection: 'row',
    alignSelf: 'center',
    height: moderateScale(43),
    alignItems: 'center',
    justifyContent: 'space-between',
    width: wp(100) - moderateScale(30),
  },
  FlagNameView: {
    flexDirection: 'row',
    justifyContent: 'center',
    paddingLeft: moderateScale(12),
    alignItems: 'center',
  },
  imgView: {
    height: moderateScale(30),
    width: moderateScale(30),
    marginRight: moderateScale(10),
    borderRadius: moderateScale(30),
  },
  text: {
    fontSize: moderateScale(13),
    color: colors.textColor,
    marginLeft: 1,
    fontFamily: fonts.Medium,
  },
});

暂无
暂无

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

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