FlashList滚动时出现图片加载错乱问题求助
FlashList滚动时图片错乱问题解决
问题描述
使用FlashList展示包含头像、标题的列表时,滚动出现图片错乱(加载错误图片)的情况。原因是FlashList的Recycling机制:组件移出视口时不会被销毁,而是被复用并传入新的item属性。当前renderItem返回的组件层级使用了useState,且尝试通过对比id重置状态的方法无效。
相关版本:
- react-native: 0.70
- @shopify/flash-list: 1.4.3
问题代码
renderItem代码
const renderItem = useCallback( ({ item }: ListRenderItemInfo<Avatar>) => ( <ListItemAvatar logoUri: getLogos(item), id: item.name + index /> ), [] );
Avatar组件代码
type Avatar = { id?: string; logoUri?: ImageURISource | Array<ImageURISource>; }; export const Avatar = ({ id, logoUri }: AvatarProps) => { const indexValue = useRef<number>(0); const lastItemId = useRef(id); const [sourceImage, setSourceImage] = useState( logoUri === undefined ? undefined : Array.isArray(logoUri) ? logoUri[0] : logoUri ); if (id !== lastItemId.current) { console.log(`${lastItemId.current} has been recycled to ${id}`); lastItemId.current = id; setSourceImage( logoUri === undefined ? undefined : Array.isArray(logoUri) ? logoUri[indexValue.current] : logoUri ); } const onError = () => { if (Array.isArray(logoUri) && indexValue.current + 1 < logoUri.length) { indexValue.current = indexValue.current + 1; setImageSource(logoUri[indexValue.current]); return; } setSourceImage(undefined); }; return ( <View> {sourceImage === undefined ? ( <Icon /> ) : ( <Image source={sourceImage} onError={onError} /> )} </View> ); };
解决方案
1. 用useEffect正确重置状态(修复原代码逻辑)
原代码在渲染阶段直接修改状态,违反React渲染规则,导致状态重置无效。改用useEffect监听id变化,在副作用中统一重置所有状态:
export const Avatar = ({ id, logoUri }: AvatarProps) => { const indexValue = useRef<number>(0); const [sourceImage, setSourceImage] = useState<ImageURISource | undefined>( logoUri === undefined ? undefined : Array.isArray(logoUri) ? logoUri[0] : logoUri ); // 监听id变化,重置重试索引和图片源 useEffect(() => { indexValue.current = 0; const initialSource = logoUri === undefined ? undefined : Array.isArray(logoUri) ? logoUri[0] : logoUri; setSourceImage(initialSource); }, [id, logoUri]); const onError = () => { if (Array.isArray(logoUri) && indexValue.current + 1 < logoUri.length) { indexValue.current += 1; setSourceImage(logoUri[indexValue.current]); return; } setSourceImage(undefined); }; return ( <View> {sourceImage === undefined ? ( <Icon /> ) : ( <Image source={sourceImage} onError={onError} /> )} </View> ); };
2. 移除本地状态,基于props推导(推荐)
遵循官方建议,尽量不在renderItem返回的组件中使用本地状态,改用useMemo结合useState(仅保留重试索引),完全基于props计算当前图片源:
export const Avatar = ({ id, logoUri }: AvatarProps) => { const [retryIndex, setRetryIndex] = useState(0); // 基于logoUri和retryIndex计算当前要显示的图片源 const currentSource = useMemo(() => { if (!logoUri) return undefined; if (Array.isArray(logoUri)) { // 确保索引不越界 return logoUri[Math.min(retryIndex, logoUri.length - 1)]; } return logoUri; }, [logoUri, retryIndex]); // 组件复用(id变化)时重置重试索引 useEffect(() => { setRetryIndex(0); }, [id]); const onError = () => { if (Array.isArray(logoUri) && retryIndex + 1 < logoUri.length) { setRetryIndex(prev => prev + 1); } }; return ( <View> {currentSource === undefined ? ( <Icon /> ) : ( <Image source={currentSource} onError={onError} /> )} </View> ); };
3. 强制组件重新挂载(性能妥协方案)
给Avatar组件添加唯一key,让React在item变化时销毁旧组件、创建新组件,彻底避免复用带来的状态问题。但会损失FlashList的复用性能,仅在其他方案无效时使用:
const renderItem = useCallback( ({ item, index }: ListRenderItemInfo<Avatar>) => { const itemId = `${item.name}-${index}`; return ( <ListItemAvatar key={itemId} logoUri={getLogos(item)} id={itemId} /> ); }, [] );
内容的提问来源于stack exchange,提问作者Alessandro
相关产品推荐
相关产品推荐

