React Native中useSelector引发组件重渲染与模态框打开延迟的问题排查及解决方案咨询
解决React Native模态框延迟、重渲染及实例丢失问题
咱们先逐个拆解你遇到的问题,一步步理顺修复思路:
一、核心问题根源
你碰到的三个问题其实是连锁反应:
- modals数组实例丢失:组件每次重渲染都会重新创建modals数组,之前存储的模态框ref被直接清空,导致第二次点击时找不到open方法。
- 不必要的重渲染:useSelector触发组件频繁重渲染,不仅拖慢了模态框打开速度,还让React.memo对Item组件的优化完全失效。
- Item组件未绑定模态框触发函数:原代码里renderItem没给Item传递modal props,长按事件根本没触发(这应该是笔误吧😅)
二、分步修复
1. 修复modals数组实例丢失问题
把modals数组用useRef保存,这样它的引用在组件生命周期内不会改变,不会随渲染重置:
// 替换原来的modals定义 const modals = React.useRef(Array.from({ length: 8 }).map(_ => null)).current;
这样哪怕组件重渲染十次,modals还是原来的那个数组,存储的模态框ref也不会丢失。
2. 避免不必要的重渲染
(1)优化useSelector的使用
给useSelector加上浅比较函数,避免因为state的无关变化触发组件重渲染:
import { shallowEqual } from 'react-redux'; // 修改useSelector的调用 const userID = useSelector(currentUserID, shallowEqual);
如果currentUserID是字符串/数字这类简单类型,默认比较也没问题,但加上shallowEqual更稳妥,后续state结构变化时也不容易出问题。
(2)确保Item组件的props稳定
- 用
useCallback包裹renderItem,避免每次渲染生成新的函数:
const renderItem = React.useCallback(({ item }) => ( <Item name={item.name} date={item.date} image={item.image} id={item.id} text={item.text} modal={handleOpenModalSetting} // 补上之前缺失的modal prop! /> ), [handleOpenModalSetting]);
- 确保
handleOpenModalSetting的依赖项正确,dispatch是redux提供的稳定函数,modals用useRef保存后也是稳定的,所以依赖项可以写成:
const handleOpenModalSetting = React.useCallback((e) => { modals[1]?.open(); dispatch(setCurrentUserID(e)); }, [dispatch, modals]);
3. 修复模态框触发逻辑
原代码里的renderItem没给Item传递modal属性,导致长按事件完全没绑定,这是关键笔误!一定要补上,不然长按毫无反应。
三、完整修复后的代码
import * as React from 'react'; import { StyleSheet, Text, View, Pressable, Image, Dimensions, Animated } from 'react-native'; import BottomSheet, { BottomSheetFlatList } from '@gorhom/bottom-sheet'; import { chatMockFlat } from '../utils/mockChat'; import { useSelector, useDispatch, shallowEqual } from 'react-redux'; import { ChatPeopleSetting } from '../components/modal/chat/chatPeopleSetting'; import { Portal } from 'react-native-portalize'; import { setCurrentUserID } from '../redux/slice/chat/chatSlice'; const { width } = Dimensions.get('window'); const chatSearchValue = state => state.chatSlice.headerSearchChats; const currentUserID = state => state.chatSlice.currentUserID; const Item = React.memo(({ name, date, image, id, text, modal }) => { console.log('rerender') return ( <Pressable style={s.chatItem}> <View style={s.viewLeftImage}> <Pressable delayLongPress={100} onLongPress={() => modal(id)} style={s.imagePress}> <Image source={{uri: image}} style={s.profile_image} resizeMode='contain' /> <View style={s.onlineStatus}></View> </Pressable> </View> <View style={s.viewMidText}> <View style={s.viewHeaderText}> <Text style={s.name}>{name}</Text> <Text style={s.date}>{date}</Text> </View> <Text style={s.text}>{text}</Text> </View> </Pressable> ) }); const Messages = (props) => { // 用useRef保存modals数组,避免渲染重置 const modals = React.useRef(Array.from({ length: 8 }).map(_ => null)).current; const userID = useSelector(currentUserID, shallowEqual); const dispatch = useDispatch(); const bottomSheetRef = React.useRef(null); const handleOpenModalSetting = React.useCallback((e) => { modals[1]?.open(); dispatch(setCurrentUserID(e)); }, [dispatch, modals]); const handleCloseModal = React.useCallback(() => modals[1]?.close(), [modals]) // variables const snapPoints = React.useMemo(() => ['25%', '100%'], []); // 用useCallback包裹renderItem,保证props稳定 const renderItem = React.useCallback(({ item }) => ( <Item name={item.name} date={item.date} image={item.image} id={item.id} text={item.text} modal={handleOpenModalSetting} /> ), [handleOpenModalSetting]); return ( <View style={s.container}> <BottomSheet ref={bottomSheetRef} index={1} style={s.sheetContainer} snapPoints={snapPoints} handleStyle={{borderTopLeftRadius: 16, borderTopRightRadius: 16,}} > <BottomSheetFlatList data={chatMockFlat} keyExtractor={i => i.id} renderItem={renderItem} > </BottomSheetFlatList> </BottomSheet> <Portal> <ChatPeopleSetting ref={el => (modals[1] = el)} onPressExit={handleCloseModal} /> </Portal> </View> ) }; // 补充样式定义(如果之前没写全的话) const s = StyleSheet.create({ container: { flex: 1 }, chatItem: { flexDirection: 'row', padding: 12, borderBottomWidth: 1, borderBottomColor: '#f0f0f0' }, viewLeftImage: { marginRight: 12 }, imagePress: { position: 'relative' }, profile_image: { width: 50, height: 50, borderRadius: 25 }, onlineStatus: { width: 12, height: 12, backgroundColor: '#2ecc71', borderRadius: 6, position: 'absolute', bottom: 2, right: 2 }, viewMidText: { flex: 1, justifyContent: 'center' }, viewHeaderText: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 4 }, name: { fontSize: 16, fontWeight: '600' }, date: { fontSize: 12, color: '#888' }, text: { fontSize: 14, color: '#666' }, sheetContainer: { backgroundColor: 'white' }, });
四、额外优化建议
- 如果
chatMockFlat是静态数据,可以用useMemo包裹,避免每次渲染重新生成:const data = React.useMemo(() => chatMockFlat, []);,然后传给BottomSheetFlatList的data属性。 - 检查ChatPeopleSetting组件是否也有不必要的重渲染,可以给它也加上React.memo,确保只有props变化时才重渲染。
内容的提问来源于stack exchange,提问作者locklock123
相关产品推荐
相关产品推荐

