React开发中如何阻止图片数组出现不必要的频繁重渲染
问题根因
fetchAssets函数未做引用缓存,每次组件渲染都会生成新的函数实例,导致依赖它的useEffect反复触发,不断拉取相册资源更新files状态,触发连锁重渲染。replaceFileState函数每次渲染都会生成新实例,且依赖外部fileArray状态,状态更新后函数引用也会更新,进一步触发子组件重渲染。- 内联箭头函数回调(如Modal的
onBackdropPress)每次渲染生成新引用,若子组件是纯组件也会触发不必要更新。 - 自定义组件
OnboardingPhoto未做渲染缓存,父组件更新时即使props未变也会跟随重渲染。
修复方案
按以下步骤修改代码即可解决持续重渲染问题:
import {memo, useCallback, useEffect, useState} from 'react'; // 给OnboardingPhoto包memo,props无变化时不会重渲染 const OnboardingPhoto = memo((props) => { // 原有OnboardingPhoto逻辑保持不变 }) function OnboardingUploadPhotos() { const [modalVisible, setModalVisible] = useState(false); const [permissionStatus, setPermissionStatus] = useState(''); const [files, setFiles] = useState<string[]>([]); const [fileArray, setFileArray] = useState<string[]>(['', '', '', '', '', '', '']); const setPermissions = useCallback(val => { setPermissionStatus(val); }, [setPermissionStatus]); const setIsModalVisible = useCallback(val => { setModalVisible(val); }, [setModalVisible]); // 缓存关闭弹窗的回调,保持引用稳定 const hideModal = useCallback(() => { setIsModalVisible(false); }, [setIsModalVisible]) // 修复1:用useCallback缓存fetchAssets,依赖为空,仅组件挂载时执行一次 const fetchAssets = useCallback(async () => { const recentCameraRoll = await MediaLibrary.getAssetsAsync({first: 11}); setFiles(recentCameraRoll.assets.slice(1).map(file => file.uri)); }, []); // 修复2:用useCallback缓存replaceFileState,使用函数式更新不依赖外部fileArray const replaceFileState = useCallback((file: string, index: number) => { setFileArray(prev => { const f = [...prev]; f[index] = file; return f; }); // 选完图片需要自动关弹窗可保留这行,不需要就删除 hideModal(); }, [hideModal]); useEffect(() => { fetchAssets(); }, [fetchAssets]); return ( <View> {permissionStatus ? <Modal style={styles.bottomModalView} isVisible={modalVisible} backdropOpacity={0} onBackdropPress={hideModal}> <View style={styles.modal}> <TouchableOpacity> <Text style={{ borderBottomWidth: 1, borderBottomColor: '#FFF', color: '#FFF', textDecorationLine: 'underline', alignSelf: 'flex-end', justifyContent: 'center', paddingTop: 40 }}>All photos</Text> </TouchableOpacity> <ScrollView horizontal={true} scrollEnabled={true} contentContainerStyle={{justifyContent: 'center', alignItems: 'center'}}> {files.map((file, index) => { return ( // 把key从index换成唯一的file,避免列表渲染异常 <TouchableWithoutFeedback key={file} onPress={() => replaceFileState(file, index)}> <Image key={file} style={{width: 100, height: 100, marginLeft: 10, marginRight: 10, borderRadius: 4}} source={{uri: file}} /> </TouchableWithoutFeedback> ); })} </ScrollView> <Text style={{fontSize: 22, color: '#FFF', marginLeft: 20, marginBottom: 20}}>Your photos</Text> </View> </Modal> : null } <Text>Upload your photos</Text> <View style={{flexDirection: "row", flexWrap: "wrap", justifyContent: 'space-evenly'}}> {fileArray.slice(0,6).map((image, index) => { // 移除调试用的console.log,减少性能损耗 return ( image != '' ? <Image key={image} style={{width: 100, height: 100, borderRadius: 100}} source={{uri: image}} /> : <OnboardingPhoto key={index} setStatus={setPermissions} setModal={setIsModalVisible}/> ) })} </View> </View> ); }
补充优化说明
如果后续需要实现刷新相册的功能,可以单独给刷新操作绑定fetchAssets调用,不需要修改挂载阶段的useEffect逻辑。
内容的提问来源于stack exchange,提问作者Oliver Darby
相关产品推荐
相关产品推荐

