首页 > 解决方案 > 以高性能的方式渲染 FlatList 的视频

问题描述

我正在使用带有 expo 的 react native。我有很多视频需要渲染(有点像 TikTok)。当我获取大约 30 个视频并将它们放入renderItem方法,它会卡住且缓慢。我正在考虑获取大量视频,但每次仅向 renderItem 方法发送 3 个视频,当用户向下滚动并到达索引 2 时,它将移动第一个索引并从获取的视频中附加第四​​个视频。这个想法是有一个大小为 3 的小数组,并在每次滚动时更改其中的项目,以防止一次渲染所有视频。这需要数组操作,并且每次更新视频数组时都会导致重新渲染(每次更改都会产生某种闪光 - 表示整个重新渲染)。我的问题是应该如何实现,以便从客户端的角度来看,视频之间的过渡尽可能快和干净?在平面列表中呈现视频的正确方法是什么?不会被卡住?我认为不应该那样做,必须有更好的方法。

这就是我尝试过的: // 挑战是一个来自 fetch 的数组,只是为了示例的目的而对其进行切片 // 假设它是一个包含 30 个项目的数组 const [currentVideos, setCurrentVideos] = useState([challenge.切片(0,3)]);

<FlatList
        data={currentVideos}
        renderItem={renderItem}
        keyExtractor={(challenge, i) => challenge._id}
        showsVerticalScrollIndicator={false}
        snapToInterval={Dimensions.get("window").height - UIConsts.bottomNavbarHeight}
        snapToAlignment={"start"}
        decelerationRate={"fast"}
        ref={(ref) => {
          flatListRef.current = ref;
        }}
        onScrollToIndexFailed={() => alert("no such index")}
        onViewableItemsChanged={onViewRef.current}
        onScrollEndDrag={() => (scrollEnded.current = true)}
        onScrollBeginDrag={beginDarg}
      ></FlatList>

  useEffect(() => {
// just wanted to check on 3 videos
         if (currentlyPlaying === 2) {
           let temp = currentVideos;
           temp.shift(); // pop the top item
           temp.push(challenges[4]) // append a new one
           setCurrentVideos(temp);
         }
      }, [currentlyPlaying]);


 const onViewRef = useRef(({ viewableItems }) => {
    // change playing video only after user stop dragging
    scrollEnded.current && setCurrentlyPlaying(viewableItems[0]?.index);
  });

标签: react-nativeexpovideo-streaming

解决方案


  1. 我会避免data在组件内部操作数组和执行业务逻辑。此外,您可以使用道具实现您想要的行为,而无需操作您的data数组。正如官方 RN 文档中提到的FlatList 优化技术maxToRenderPerBatch FlatList

  2. 您应该避免在组件内部使用匿名函数和对象properties,将它们移到 return 语句之外,并使用useMemoanduseCallback钩子来避免它们在每次重新渲染时不必要的重新创建。例如,不要像这样编写代码:

const App = () => {
  return (
    <FlatList
      keyExtractor={(challenge, i) => challenge._id}
      snapToInterval={Dimensions.get('window').height - UIConsts.bottomNavbarHeight}
    />
  );
};

更好的方法是将其重写为如下内容:

const App = () => {
  // Because of useCallback, the keyExtractor function will be memoized and won't recreate itself on every re-render
  const keyExtractor = useCallback((challenge, i) => challenge._id, []);

  // useMemo is almost the same as useCallback, but it is used to return non-function types
  // Defining your snapToInterval variable like this will cause it to memoize its value and it
  // won't recreate itself on every re-render
  const snapToInterval = useMemo(() => Dimensions.get('window').height - UIConsts.bottomNavbarHeight, []);

  return (
    <FlatList
      keyExtractor={keyExtractor}
      snapToInterval={snapToInterval}
    />
  );
};

如果您还没有,您应该考虑将从renderItem函数返回的组件提取到不同的文件并应用React.memo到它。

注意:尽量不要过度使用useCallbackuseMemo. 您可以在此处此处找到关于为什么不过度使用它们的详细说明。

  1. 如果可以的话,您应该在将视频上传到服务器之前对其进行优化。您可以根据需要优化应用程序的客户端部分,但如果内容没有得到适当优化,无论您如何努力,都无法获得流畅和高性能的体验。

这里还有一些描述如何优化FlatList组件的文章:

我希望其中的一些内容对您有所帮助。祝你好运。


推荐阅读