子组件不会在道具更改时重新渲染

Child component not rerendered on prop change

在以下代码中,我希望 OfferList 在我向商店添加商品时重新呈现。 OfferList 本身不是 observable,但 offer 数组作为 prop 传递。

export const MerchantScreen: FC = observer(() => {
  const { merchantStore } = useStores()

  return (
    <View>
      <OfferList data={merchantStore.offers} />
      <View>
        <Button title={"New Offer"} onPress={() => merchantStore.addOffer()}/>
      </View>
    </View>
  )
})

export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
  const renderItem = (offer: ListRenderItemInfo<any>) => {
    return (
      <Text>{offer.name}</Text>
    )
  }

  return (
    <FlatList
      data={data}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
    />
  )
}

我使用 Mobx 状态树。 merchantStore.addOffer() 目前所做的只是将另一个报价项目推入数组。

我的尝试/发现:

当我从 MerchantScreen 中的商店阅读时,例如通过添加

<Text>{ merchantStore.offers.toString() }</Text>

OfferList也会更新。我怀疑直接在父组件中从商店读取也会强制重新渲染子组件。

我在这里偶然发现了一些答案,这些答案表明 FlatList renderItems 中缺少 key 属性可能是问题所在。尝试使用 key={item.id} 无济于事。另外,如您所见,我使用了 FlatList 的 keyExtractor 道具。

另一个答案建议像这样将本地状态引入组件:

export const OfferList: FC<OfferListProps> = ({ data }: OfferListProps) => {
  const [offers, setOfferss] = useState()

  useEffect(() => {
    setOffers(data)
  }, [data])
  
  const renderItem = (offer: ListRenderItemInfo<any>) => {
    return (
      <Text>{offer.name}</Text>
    )
  }

  return (
    <FlatList
      data={offers}
      renderItem={renderItem}
      keyExtractor={(item) => item.id}
    />
  )
}

这行不通,我的直觉是这不是它的完成方式。

如您所见,我的 MerchantScreen 父组件是可观察的,而我的子组件 OfferList 不是。按照我的预期,我的父组件上的 observer 应该足够了。父组件应该已经检测到商店中的变化并重新呈现。子组件本身甚至不使用商店。

总的来说,手头的问题似乎很微不足道,所以我想我只是错过了一个重要的细节。

MobX 只跟踪观察者组件访问的数据,如果它们被渲染直接访问的话,所以如果你想对每个 offers 做出反应,你需要在某个地方访问它们。当你尝试 merchantStore.offers.toString() 时,你有点做了,这就是它起作用的原因。

所以首先你需要让 OfferList 成为 observer.

但是你有 FlatList,它是本机组件,你不能将它变成 observer。您可以做的是访问 OfferList 中的每个 offers 项目(基本上只是为了订阅更新),就像 data={offers.slice()} 甚至更好地使用 MobX 辅助方法 toJS data={toJS(offers)}

根据您的用例,您可能还想在 renderItem 回调中使用 <Observer>

  const renderItem = (offer: ListRenderItemInfo<any>) => {
    return (
      <Observer>{() => <Text>{offer.name}</Text>}</Observer>
    )
  }