编写高性能组件
虽然 FlashList
尽力实现高性能,但如果你的项目组件渲染速度缓慢,它的性能仍然会很差。在这篇文章中,让我们深入探讨如何解决这个问题。
回收
要理解的一个重要事情是 FlashList
在底层是如何工作的。当一个项目超出视口时,该组件不会被销毁,而是使用不同的 item
属性重新渲染。在优化你的项目组件时,尽量确保在回收时需要重新渲染和重新计算的东西尽可能少。
优化
有很多适用于任何 React Native 组件的优化方法,这些方法也可能有助于提高项目组件的渲染时间。建议使用 useCallback
、useMemo
和 useRef
- 但不要盲目使用这些,始终在进行更改前后测量性能。
注意
始终在发布模式下分析性能。FlashList
在 JS 开发模式和发布模式之间的性能差异很大。
estimatedItemSize
确保 estimatedItemSize
尽可能接近实际平均值 - 请参阅此处,了解如何正确计算此属性的值。
移除 key
属性
警告
在你的项目和项目嵌套组件中使用 key
属性会严重降低性能。
请确保你的项目组件及其嵌套组件没有 key
属性。使用此属性会导致 FlashList
无法回收视图,从而失去使用它而不是 FlatList
的所有好处。
例如,如果我们有以下项目组件
const MyNestedComponent = ({ item }) => {
return <Text key={item.id}>I am nested!</Text>;
};
const MyItem = ({ item }) => {
return (
<View key={item.id}>
<MyNestedComponent item={item} />
<Text>{item.title}</Text>
</View>
);
};
那么 key
属性应该从 MyItem
和 MyNestedComponent
中移除
const MyNestedComponent = ({ item }) => {
return <Text>I am nested!</Text>;
};
const MyItem = ({ item }) => {
return (
<View>
<MyNestedComponent item={item} />
<Text>{item.title}</Text>
</View>
);
};
在某些情况下,React 会强制你使用 key
属性,例如在使用 map
时。在这种情况下,请确保 key
不以任何方式与 item
属性绑定,这样键在回收时就不会更改。
假设我们要显示用户的姓名
const MyItem = ({ item }: { item: any }) => {
return (
<>
{item.users.map((user: any) => {
<Text key={user.id}>{user.name}</Text>;
})}
</>
);
};
如果我们像这样编写我们的项目组件,则需要重新创建 Text
组件。相反,我们可以这样做
const MyItem = ({ item }) => {
return (
<>
{item.users.map((user, index) => {
/* eslint-disable-next-line react/no-array-index-key */
<Text key={index}>{user.name}</Text>;
})}
</>
);
};
虽然 React 不建议在 map
中使用索引作为 key
,但在这种情况下,由于数据来自列表的数据,因此项目将正确更新。
复杂的计算
如果你进行任何可能占用大量资源的计算,请考虑将其记忆化,使其更快或完全删除。项目的渲染方法应尽可能高效
getItemType
如果你有不同类型的单元格组件,并且这些组件差异很大,请考虑利用 getItemType
属性。例如,如果我们正在构建一个消息列表,我们可以这样编写
// A message can be either a text or an image
enum MessageType {
Text,
Image,
}
interface TextMessage {
text: string;
type: MessageType.Text;
}
interface ImageMessage {
image: ImageSourcePropType;
type: MessageType.Image;
}
type Message = ImageMessage | TextMessage;
const MessageItem = ({ item }: { item: Message }) => {
switch (item.type) {
case MessageType.Text:
return <Text>{item.text}</Text>;
case MessageType.Image:
return <Image source={item.image} />;
}
};
// Rendering the actual messages list
const MessageList = () => {
return <FlashList renderItem={MessageItem} estimatedItemSize={200} />;
};
但是,此实现有一个性能缺点。当列表回收项目并且 MessageType
从 Text
更改为 Image
或反之时,React 将无法优化重新渲染,因为项目组件的整个渲染树都会发生变化。我们可以通过将 MessageList
更改为以下内容来解决此问题
const MessageList = () => {
return (
<FlashList
renderItem={MessageItem}
estimatedItemSize={200}
getItemType={(item) => {
return item.type;
}}
/>
);
};
FlashList
现在将根据 item.type
使用单独的回收池。这意味着我们永远不会回收不同类型的项目,从而使重新渲染更快。
叶子组件
让我们考虑以下示例
const MyHeavyComponent = () => {
return ...;
};
const MyItem = ({ item }) => {
return (
<>
<MyHeavyComponent />
<Text>{item.title}</Text>
</>
);
};
由于 MyHeavyComponent
不直接依赖于 item
属性,因此可以使用 memo
来跳过在项目回收并因此重新渲染时重新渲染 MyHeavyComponent
const MyHeavyComponent = () => {
return ...;
};
const MyItem = ({ item }: { item: any }) => {
const MemoizedMyHeavyComponent = memo(MyHeavyComponent);
return (
<>
<MemoizedMyHeavyComponent />
<Text>{item.title}</Text>
</>
);
};