You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 10:45:28