React Native Expo FlatList含输入框焦点跳转问题求助
React Native FlatList输入框焦点跳转问题优化方案
问题背景
使用React Native Expo开发的商品列表页面,通过FlatList展示商品,每个列表项包含数量输入框。因FlatList组件复用机制,在键盘关闭时点击底部项的输入框,焦点会随机跳转到顶部某输入框;禁用removeClippedSubviews虽能解决,但列表项增多后会大幅卡顿,需更优方案。
核心原因
FlatList的组件复用会导致旧组件的状态(包括输入框焦点、本地state)被新的列表项继承,当购物车状态更新或列表滚动时,组件状态未及时与当前商品绑定,引发焦点错乱。
优化方案
1. 给FlatList添加extraData属性
FlatList默认仅监听data数组的变化,添加extraData后,当购物车状态改变时,FlatList会重新渲染对应列表项,确保组件状态与当前商品匹配。
修改Catalogue.tsx中的FlatList配置:
import {useAppSelector} from "@src/hooks"; const renderProducts = () => { // 获取购物车列表作为extraData的依赖 const cartList = useAppSelector(state => state.cartSlice.list); return ( <FlatList ref={listRef} key={userHasRoleBusiness ? 'single' : 'double'} data={products} renderItem={renderItem} keyExtractor={(item) => item.id.toString()} numColumns={userHasRoleBusiness ? 1 : 2} contentContainerStyle={flatListStyle} ListHeaderComponent={renderHeader} ListEmptyComponent={renderEmptyList} onScroll={userHasRoleBusiness ? handleScroll : undefined} scrollEventThrottle={16} keyboardShouldPersistTaps="handled" removeClippedSubviews={true} // 恢复组件复用,保证性能 extraData={cartList} // 新增:让FlatList感知购物车状态变化 /> ); };
2. 监听product变化重置输入框状态
当FlatList复用组件时,product属性会切换为新商品,此时需重置输入框的数量值和焦点状态,避免旧数据残留。
修改AddQuantityInput组件:
const AddQuantityInput = forwardRef<AddQuantityInputRef, QuantityInputProps>(({product, removeDelayMs = 0}, ref) => { const removeTimerRef = useRef<NodeJS.Timeout | null>(null); const dispatch = useAppDispatch(); const inputRef = useRef<TextInput>(null); const [quantity, setQuantity] = useState(''); // 初始化或product变化时同步购物车数量 useEffect(() => { const currentQty = useAppSelector((state) => state.cartSlice.list.find(item => item.product.id === product.id) )?.quantity ?? 0; setQuantity(currentQty > 0 ? String(currentQty) : ''); // 如果输入框当前处于聚焦状态,强制失焦避免焦点残留 if (inputRef.current?.isFocused()) { inputRef.current.blur(); } }, [product]); // ... 原有handleQuantityChange等逻辑保持不变 return ( // ... 原有JSX结构保持不变 ); });
3. 集中管理输入框Ref(可选)
在父组件维护每个商品输入框的Ref映射,确保点击操作能精准定位到当前商品的输入框,彻底避免复用导致的Ref指向错误。
步骤1:修改Catalogue.tsx,添加Ref映射
const inputRefs = useRef<Map<string, AddQuantityInputRef>>(new Map()); const renderItem = ({item}: { item: Product }) => { return ( <View style={{width: userHasRoleBusiness ? '100%' : '50%', paddingHorizontal: 8}}> <ProductItem product={item} isAdmin={userHasRoleAdmin} isBusinessUser={userHasRoleBusiness} onChangePrice={onChangePrice} // 传递Ref回调,将当前商品的输入框Ref存入映射 inputRef={(ref) => { const productId = item.id.toString(); if (ref) { inputRefs.current.set(productId, ref); } else { inputRefs.current.delete(productId); } }} /> </View> ); };
步骤2:更新ProductItem组件的Props
type ProductItemProps = { product: Product; isAdmin: boolean; isBusinessUser: boolean; onChangePrice: (product: Product, price: number) => Promise<void>; inputRef: (ref: AddQuantityInputRef | null) => void; // 新增Ref回调属性 }; const ProductItem = ({ product, inputRef }: ProductItemProps) => { return ( <AddQuantityInput ref={inputRef} product={product} /> ); };
效果验证
恢复removeClippedSubviews={true}后,列表滚动性能正常,点击底部输入框时焦点不会跳转到顶部,输入操作正常。
内容的提问来源于stack exchange,提问作者user2342348
相关产品推荐
相关产品推荐

