FlatList多次加载数据

发布于 2025-01-10 09:44:19 字数 4000 浏览 1 评论 0原文

我有下一个简单的页面:

type Props = {
  getPostsUseCase: GetPostsUseCase
}

const renderItem = ({item}: ListRenderItemInfo<Post>) => (
  <NewsPost title={item.msg} />
)

const HomeScreen = ({getPostsUseCase}: Props) => {
  const [isLoading, setLoading] = useState(true)
  const [shouldFetch, setShouldFetch] = useState(true)
  const [since, setSince] = useState<number | null>(null)
  const [posts, setPosts] = useState<Post[]>([])

  const fetchMore = useCallback(() => setShouldFetch(since != null), [since])

  useEffect(() => {
    // Prevent fetching for other state changes
    if (!shouldFetch) return

    getPostsUseCase
      .execute({
        limit: 15,
        since: since,
      })
      .then((page: Page<Post>) => {
        setPosts(oldPosts => [...oldPosts, ...page.data])
        setSince(page.next)
      })
      .catch((error: string) => console.error(error))
      .finally(() => {
        setLoading(false)
        setShouldFetch(false)
      })
  }, [getPostsUseCase, shouldFetch, since])

  const onEndReachedCalledDuringMomentum = useRef(true)

  const onEndReachedHandler = () => {
    if (!onEndReachedCalledDuringMomentum.current) {
      fetchMore()
      onEndReachedCalledDuringMomentum.current = true
    }
  }

  return (
    <SafeAreaView style={styles.frame}>
      {isLoading ? (
        <Text>Loading...</Text>
      ) : (
        <FlatList
          style={styles.list}
          data={posts}
          renderItem={renderItem}
          keyExtractor={item => item.id.toString()}
          onEndReachedThreshold={0.1}
          initialNumToRender={15}
          onEndReached={onEndReachedHandler}
          onMomentumScrollBegin={() => {
            onEndReachedCalledDuringMomentum.current = false
          }}
        />
      )}
    </SafeAreaView>
  )
}

export default HomeScreen

但是,它有两个问题:

  1. 它在开始时加载两个页面(30 个项目,而不是 15,对后端的两个请求)
  2. 它在滚动时多次加载整个集合(总共 30 个项目,它渲染了 90 个项目) )

问题出在哪里?我发现很多主题都有类似的问题,我尝试了他们的解决方案,但没有帮助(例如,这个

我是 React Native 的菜鸟。老实说,我什至不明白我的问题和他们的解决方案,以及为什么它们不起作用。

UDP

最后,我想出了一个可行的解决方案,但仍然有一个烦人的时刻:

type Props = {
  getPostsUseCase: GetPostsUseCase
}

type Data = {
  posts: Post[]
  next: number | null
}

const renderItem = ({item}: ListRenderItemInfo<Post>) => (
  <NewsPost title={item.msg} />
)

const HomeScreen = ({getPostsUseCase}: Props) => {
  const [isLoading, setLoading] = useState(true)
  const [data, setData] = useState<Data>({
    posts: [],
    next: null,
  })

  const loadData = () => {
    if (!isLoading && data.next == null) return

    getPostsUseCase
      .execute({
        limit: 15,
        since: data.next,
      })
      .then((page: Page<Post>) => {
        setData(oldPage => ({
          posts: [...oldPage.posts, ...page.data],
          next: page.next,
        }))
      })
      .catch((error: string) => console.error(error))
      .finally(() => setLoading(false))
  }

  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(loadData, [])

  return (
    <SafeAreaView style={styles.frame}>
      {isLoading ? (
        <Text>Loading...</Text>
      ) : (
        <FlatList
          style={styles.list}
          data={data?.posts}
          renderItem={renderItem}
          keyExtractor={item => item.id.toString()}
          onEndReachedThreshold={0.1}
          onEndReached={loadData}
        />
      )}
    </SafeAreaView>
  )
}

export default HomeScreen

ESlint 需要在 useEffect< 中指定 loadData /代码> 依赖项。我通过 // eslint-disable-next-line react-hooks/exhaustive-deps 禁用了该警告,但这是一个肮脏的伎俩......

如何修复解决方案,我不需要禁用ESLint 规则?

I have the next simple page:

type Props = {
  getPostsUseCase: GetPostsUseCase
}

const renderItem = ({item}: ListRenderItemInfo<Post>) => (
  <NewsPost title={item.msg} />
)

const HomeScreen = ({getPostsUseCase}: Props) => {
  const [isLoading, setLoading] = useState(true)
  const [shouldFetch, setShouldFetch] = useState(true)
  const [since, setSince] = useState<number | null>(null)
  const [posts, setPosts] = useState<Post[]>([])

  const fetchMore = useCallback(() => setShouldFetch(since != null), [since])

  useEffect(() => {
    // Prevent fetching for other state changes
    if (!shouldFetch) return

    getPostsUseCase
      .execute({
        limit: 15,
        since: since,
      })
      .then((page: Page<Post>) => {
        setPosts(oldPosts => [...oldPosts, ...page.data])
        setSince(page.next)
      })
      .catch((error: string) => console.error(error))
      .finally(() => {
        setLoading(false)
        setShouldFetch(false)
      })
  }, [getPostsUseCase, shouldFetch, since])

  const onEndReachedCalledDuringMomentum = useRef(true)

  const onEndReachedHandler = () => {
    if (!onEndReachedCalledDuringMomentum.current) {
      fetchMore()
      onEndReachedCalledDuringMomentum.current = true
    }
  }

  return (
    <SafeAreaView style={styles.frame}>
      {isLoading ? (
        <Text>Loading...</Text>
      ) : (
        <FlatList
          style={styles.list}
          data={posts}
          renderItem={renderItem}
          keyExtractor={item => item.id.toString()}
          onEndReachedThreshold={0.1}
          initialNumToRender={15}
          onEndReached={onEndReachedHandler}
          onMomentumScrollBegin={() => {
            onEndReachedCalledDuringMomentum.current = false
          }}
        />
      )}
    </SafeAreaView>
  )
}

export default HomeScreen

However, it has two problems:

  1. It loads two pages on start (30 items, not 15, two requests to the backend)
  2. It loads entire collection multiple times on scrolling (there 30 items totally, it rendered 90)

Where is the problem? I found a lot of topics with similar problem, I tried their solutions but it didn't help (for example, this)

I am a noobie in React Native. To be honest, I don't even understand my problem and their solutions, and why they don't work.

UDP

Finally, I came up with a working solution but there is still an annoying moment:

type Props = {
  getPostsUseCase: GetPostsUseCase
}

type Data = {
  posts: Post[]
  next: number | null
}

const renderItem = ({item}: ListRenderItemInfo<Post>) => (
  <NewsPost title={item.msg} />
)

const HomeScreen = ({getPostsUseCase}: Props) => {
  const [isLoading, setLoading] = useState(true)
  const [data, setData] = useState<Data>({
    posts: [],
    next: null,
  })

  const loadData = () => {
    if (!isLoading && data.next == null) return

    getPostsUseCase
      .execute({
        limit: 15,
        since: data.next,
      })
      .then((page: Page<Post>) => {
        setData(oldPage => ({
          posts: [...oldPage.posts, ...page.data],
          next: page.next,
        }))
      })
      .catch((error: string) => console.error(error))
      .finally(() => setLoading(false))
  }

  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(loadData, [])

  return (
    <SafeAreaView style={styles.frame}>
      {isLoading ? (
        <Text>Loading...</Text>
      ) : (
        <FlatList
          style={styles.list}
          data={data?.posts}
          renderItem={renderItem}
          keyExtractor={item => item.id.toString()}
          onEndReachedThreshold={0.1}
          onEndReached={loadData}
        />
      )}
    </SafeAreaView>
  )
}

export default HomeScreen

ESlint requires to specify loadData in useEffect dependencies. I disabled that warning by // eslint-disable-next-line react-hooks/exhaustive-deps but it's a dirty trick...

How to fix the solution to I was not needed disabling an ESLint rule?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文