React Native:Redux Toolkit状态变更时重渲染Memo组件方案
问题
使用FlatList渲染商品列表,在ProductView组件中请求商品图片并存储到Redux Store,但图片更新后组件未触发重渲染。组件代码如下:
const ProductView: FC<Props> = ({ price }: Props): ReactElement => { const basketItem = useReduxSelector((state) => state.basket.items.find((item) => item.item.id === price.id) ); const productImage = useReduxSelector(state => state.product.productImage.find((item) => item.id === price.id)); const [isFocused, setIsFocused] = useState<boolean>(false); const [image, setImage] = useState<string>(productDefaultImage); const useDispatch = useReduxDispatch(); useEffect(() => { const getProductImage = async () => { const token: string | null = await localStorage.getValue("token"); getProductImageById(token!, price.id.toString()) .then((res) => { if (res.data.fileContent !== "") { useDispatch(updateProductImage({ id: price.id, fileName: res.data.fileName, fileContent: `data:image/jpeg;base64,${res.data.fileContent}` })) } else if (res.data.fileContent === "") { setImage(productDefaultImage); } }) .catch((error) => { console.log(error); }); }; if (!productImage) getProductImage(); else setImage(productImage.fileContent) }); console.log('Rendering product item with id:', price.priceId); return ( <Layout style={styles.container}> <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}> <Image source={{ uri: image }} resizeMode="contain" style={{ width: 100, height: 70 }} /> <View style={styles.priceContainer}> <Text style={styles.priceText}>{price.price}</Text> <Text style={styles.currencyText}>دم</Text> </View> <PriceType quantityType={ price.sellUnit === SellUnitEnum.ATOMIC ? price.atomicRtlName : price.packingRtlName } /> </View> <View style={{ flex: 3 }}> <View style={{ flex: 1, flexDirection: "row-reverse", justifyContent: "space-between", alignItems: "center", }} > <Text style={{ width: "75%", textAlign: "right", fontFamily: "almarai-bold", marginRight: 3, }} > {price.rtlName} </Text> {price.withPoints ? <AwalPoint points={price.points} backgroundColor={Colors.gray200} textColor={Colors.gray800} /> : <View></View>} </View> <View style={{ flex: 1, flexDirection: "row-reverse" }}> <PackageQuantity title={price.atomicRtlName.toString() + " في"} packageName={price.packingRtlName} quantity={price.packageQuantity.toString()} image={require("../assets/surprise.png")} backgroundColor={Colors.primary400} containerStyle={{ flex: 1 }} /> <PackageQuantity title="تمن" packageName={ price.sellUnit === "ATOMIC" ? price.packingRtlName : price.atomicRtlName } quantity={ (price.sellUnit === "ATOMIC" ? (price.price * price.packageQuantity).toFixed(2) : (price.price / price.packageQuantity).toFixed(2) ).toString() + " دم" } image={require("../assets/dollar.png")} backgroundColor={Colors.secondary400} containerStyle={{ flex: 1.5 }} /> </View> <View style={{ flex: 1, flexDirection: "row", paddingBottom: 7 }}> {basketItem === undefined && !isFocused ? ( <Button style={{ height: 45, borderWidth: 0, borderRadius: 100, backgroundColor: Colors.secondary500, }} onPress={() => { useDispatch(updateItems({ item: price, quantity: 1, error: undefined, productImage: image })); }} accessoryRight={IconComponent(evaIcons.cart, Colors.white500)} > {(props) => ( <Text {...props} style={{ fontFamily: "almarai-bold", fontSize: 15, color: Colors.white500, }} > أضف إلى السلة </Text> )} </Button> ) : ( <CounterInput style={{ flex: 1 }} priceId={price.id} isFocused={isFocused} setIsFocused={setIsFocused} maxValue={price.storeQuantity} buttonStyle={{ backgroundColor: Colors.primary500, borderWidth: 0 }} /> )} <View style={{ flex: 1, justifyContent: 'center', alignItems: 'flex-end' }}> {(price.isDiscountable === true) ? <View style={{ flexDirection: 'row-reverse', justifyContent: 'center', alignItems: 'center' }}> <Image source={require("../assets/promo.png")} resizeMode="contain" style={{ width: 30, height: 30 }} /> <Text style={{ textAlign: 'center', fontFamily: 'almarai-regular' }}>فيه روميز</Text> </View> : null} </View> </View> </View> </Layout> ); }; const isChanged = (prev: Props, next: Props) => { return prev.price === next.price; } export default memo(ProductView, isChanged)
解决方法
问题根源在于组件的依赖管理和memo优化逻辑,按以下步骤修复:
1. 给useEffect添加正确的依赖项
当前useEffect没有设置依赖数组,只会在组件挂载时执行一次。当Redux中的productImage更新后,useEffect不会重新触发来同步状态。修改如下:
useEffect(() => { const getProductImage = async () => { const token: string | null = await localStorage.getValue("token"); try { const res = await getProductImageById(token!, price.id.toString()); if (res.data.fileContent !== "") { useDispatch(updateProductImage({ id: price.id, fileName: res.data.fileName, fileContent: `data:image/jpeg;base64,${res.data.fileContent}` })); } else { setImage(productDefaultImage); } } catch (error) { console.log(error); } }; if (!productImage) { getProductImage(); } else { setImage(productImage.fileContent); } // 添加依赖数组:当price.id或productImage变化时重新执行 }, [price.id, productImage, useDispatch]);
2. 调整memo的比较逻辑
当前isChanged函数只比较了price属性,忽略了Redux中productImage的变化。因为组件用memo包裹,只有当props变化时才会重渲染,但productImage是从Redux获取的,不在props里,所以需要修改比较逻辑:
方案一:移除自定义比较函数
直接使用memo默认的浅比较,无需自定义isChanged:
// 移除isChanged函数 export default memo(ProductView);
方案二:将productImage作为props传入并修改比较逻辑
如果需要保留自定义比较,把productImage从父组件传入props,再调整比较逻辑:
// 父组件传入props <ProductView price={price} productImage={productImage} /> // 组件Props类型更新 type Props = { price: PriceType; productImage: ProductImage | undefined; }; // 修改isChanged函数 const isChanged = (prev: Props, next: Props) => { return prev.price.id === next.price.id && prev.productImage?.fileContent === next.productImage?.fileContent; }; export default memo(ProductView, isChanged);
3. 移除冗余本地state,直接使用Redux数据
本地image state和Redux中的productImage同步容易出错,直接在渲染时使用Redux数据,默认值设为productDefaultImage:
// 移除本地image state // const [image, setImage] = useState<string>(productDefaultImage); // 渲染Image组件时直接使用 <Image source={{ uri: productImage?.fileContent || productDefaultImage }} resizeMode="contain" style={{ width: 100, height: 70 }} />
这样不需要在useEffect中同步state,减少出错概率。
4. 确保Redux reducer的不可变性
如果Redux状态更新后组件仍不重渲染,检查reducer是否返回了新的数组(Redux依赖不可变更新检测状态变化):
// 错误示例:直接修改原数组 case UPDATE_PRODUCT_IMAGE: const index = state.productImage.findIndex(item => item.id === action.payload.id); state.productImage[index] = action.payload; return state; // 正确示例:返回新数组 case UPDATE_PRODUCT_IMAGE: return { ...state, productImage: state.productImage.map(item => item.id === action.payload.id ? action.payload : item ) };
内容的提问来源于stack exchange,提问作者DarkSide77
相关产品推荐
相关产品推荐

