React Native中FlatList项TouchableOpacity onPress响应缓慢求助
优化方案
1. 用Set替代数组存储选中ID,提升查询和更新性能
数组的includes和filter都是O(n)操作,换成Set后,has、add、delete都是O(1),大幅提升状态更新效率:
// 初始化状态为Set const [selectedPlayers, setSelectedPlayers] = React.useState<Set<string>>(new Set()); // 修改handlePress逻辑 const handlePress = (id: string) => { setSelectedPlayers(prev => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); };
2. 缓存过滤后的players列表,避免重复计算
每次组件渲染时,profiles.filter都会重新执行,用useMemo缓存结果,只在profiles变化时重新计算:
const players = React.useMemo(() => { return profiles.filter(profile => profile.role.includes("ATHLETE")); }, [profiles]); // 修正原代码中Coaches的过滤条件笔误 const coaches = React.useMemo(() => { return profiles.filter(profile => profile.role.includes("COACH")); }, [profiles]);
3. 修复React.memo失效问题,避免列表项不必要重渲染
原代码中RenderPlayerItem接收的handlePress是每次renderItem时创建的新箭头函数,导致React.memo的浅比较失效,所有列表项都会重渲染。同时把isChecked作为props传入,让组件内部不依赖外部状态:
// 用useCallback缓存handlePress,确保每次渲染函数引用不变 const handlePress = React.useCallback((id: string) => { setSelectedPlayers(prev => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); }, []); // 修改RenderPlayerItem的props定义 interface RenderItemProps { item: IProfile; isChecked: boolean; onItemPress: (id: string) => void; } const RenderPlayerItem = React.memo( ({ item, isChecked, onItemPress }: RenderItemProps) => { return ( <TouchableOpacity style={styles.rowContainer} onPress={() => onItemPress(item.id)}> <View style={styles.itemRow}> <Text> {item.lastName.length > 1 ? `${item.firstName.charAt(0)}.${item.lastName}` : item.firstName} </Text> <CheckboxComponent isChecked={isChecked} /> </View> </TouchableOpacity> ); } ); // 渲染列表时传入正确的props <FlatList data={players} renderItem={({ item }) => ( <RenderPlayerItem item={item} isChecked={selectedPlayers.has(item.id)} onItemPress={handlePress} /> )} keyExtractor={(item) => item.id} extraData={selectedPlayers} // 开启视图裁剪,优化不可见区域性能 removeClippedSubviews={true} // 控制每次渲染的数量,减少单次渲染压力 maxToRenderPerBatch={10} // 调整窗口大小,预渲染可见区域外的少量项 windowSize={5} />
4. 优化CheckboxComponent(如果自定义组件未优化)
如果CheckboxComponent没有用React.memo包裹,每次状态变化时也会触发不必要的重渲染,建议给CheckboxComponent加上React.memo:
// CheckboxComponent.tsx export const CheckboxComponent = React.memo(({ isChecked }) => { // 组件逻辑 });
优化后的完整代码示例
import React from "react"; import { View, Text, TextInput, StyleSheet, FlatList, TouchableOpacity, } from "react-native"; import { heightPercentageToDP, widthPercentageToDP, } from "react-native-responsive-screen"; import { IProfile } from "../types/cores/profiles"; import { CheckboxComponent } from "../components/cores/checkbox"; interface RenderItemProps { item: IProfile; isChecked: boolean; onItemPress: (id: string) => void; } export const CreateTeamPage = ({ route }) => { const { profiles } = route.params; const [teamName, setTeamName] = React.useState<string>(); const [selectedPlayers, setSelectedPlayers] = React.useState<Set<string>>(new Set()); // 缓存过滤后的玩家和教练列表 const players = React.useMemo(() => { return profiles.filter(profile => profile.role.includes("ATHLETE")); }, [profiles]); const coaches = React.useMemo(() => { return profiles.filter(profile => profile.role.includes("COACH")); }, [profiles]); // 缓存点击事件函数 const handlePress = React.useCallback((id: string) => { setSelectedPlayers(prev => { const newSet = new Set(prev); if (newSet.has(id)) { newSet.delete(id); } else { newSet.add(id); } return newSet; }); }, []); const RenderPlayerItem = React.memo( ({ item, isChecked, onItemPress }: RenderItemProps) => { return ( <TouchableOpacity style={styles.rowContainer} onPress={() => onItemPress(item.id)}> <View style={styles.itemRow}> <Text> {item.lastName.length > 1 ? `${item.firstName.charAt(0)}.${item.lastName}` : item.firstName} </Text> <CheckboxComponent isChecked={isChecked} /> </View> </TouchableOpacity> ); } ); return ( <View style={styles.container}> <View style={styles.header}> <Text style={styles.headerText}>Create Team</Text> </View> <Text style={styles.fieldText}>Team name:</Text> <TextInput onChangeText={(text) => setTeamName(text)} value={teamName} style={styles.textInput} /> <View style={{ flexDirection: "row" }}> <View style={{ width: "50%", height: widthPercentageToDP("50%") }}> <Text style={styles.fieldText}>Players:</Text> <FlatList data={players} renderItem={({ item }) => ( <RenderPlayerItem item={item} isChecked={selectedPlayers.has(item.id)} onItemPress={handlePress} /> )} keyExtractor={(item) => item.id} extraData={selectedPlayers} removeClippedSubviews={true} maxToRenderPerBatch={10} windowSize={5} /> </View> </View> </View> ); };
内容的提问来源于stack exchange,提问作者tromborg
相关产品推荐
相关产品推荐

