前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >帅!新思路极简代码实现数据加载更多

帅!新思路极简代码实现数据加载更多

作者头像
用户6901603
发布2024-06-19 12:27:30
1010
发布2024-06-19 12:27:30
举报
文章被收录于专栏:不知非攻不知非攻

React 19 的开发体验实在是太好了!

自从彻底掌握了 React 19 之后,我感觉自己更爱写 React 代码了。比如,像分页列表这种复杂交互,核心逻辑只需要简单几行代码就可以搞定。

分页列表是我们日常开发中,比较常见的需求。其中,通过点击或者滚动来触发加载更多是主流的交互方式之一。

这篇文章要带大家实现的效果如下图所示。

为了便于大家更容易理解和消化,我们先通过一个更简单的案例来理解代码思路,然后再实现最终目标。

  • 传统方式实现请求结果新增到列表中
  • react19 中实现新增列表内容
  • react19 中通过点击按钮实现分页列表加载更多

0、传统方案实现请求结果新增到列表

首先,先定义请求数据的 promise

代码语言:javascript
复制
// api.js
export const getMessage = async () => {
  const res = await fetch('https://api.chucknorris.io/jokes/random')
  return res.json()
}

然后需要定义一个状态用于存储列表。

代码语言:javascript
复制
const [list, updateList] = useState([])

由于每一项在请求时,都需要显示一个 Loading 状态,此时我们可以使用一个巧妙的方式来解决这个问题。那就是暂时往 list 中新增一条 type: loading 的数据。在遍历的时候判断出该数据渲染成 Skeleton 组件。

因此,我们单独声明一个列表组件 List,该组件接收 list 作为参数

代码语言:javascript
复制
function List(props) {
  const list = props.list
  return (
    <>
      {list.map((item, index) => {
        if (item.type === 'loading') {
          return <Skeleton />
        }
        return <Userinfo index={index} username={item.id} message={item.value} />
      })}
    </>
  )
}

当我们在发送请求时,先往 list 中新增一条 type: loading 的数据。此时我们利用 list 的特性与闭包的缓存特性,在接口请求成功之后再把请求过来的有效数据更新到 list 中即可。

代码如下

代码语言:javascript
复制
useEffect(() => {
  updateList([...list, {type: 'loading'}])
  getMessage().then(res => {
    updateList([...list, res])
  })
}, []);

完整代码如下:

代码语言:javascript
复制
import {use, useState, Suspense, useEffect} from 'react'
import Userinfo from './Userinfo'
import Skeleton from './Skeleton'
import Button from './Button'
import {getMessage} from './api'

export default function Demo01() {
  const [list, updateList] = useState([])

  useEffect(() => {
    updateList([...list, {type: 'loading'}])
    getMessage().then(res => {
      updateList([...list, res])
    })
  }, []);

  function __handler() {
    updateList([...list, {type: 'loading'}])
    getMessage().then(res => {
      updateList([...list, res])
    })
  }

  return (
    <>
      <div className='text-right mb-4'>
        <Button onClick={__handler}>新增数据</Button>
      </div>
      <List list={list} />
    </>
  )
}

function List(props) {
  const list = props.list
  return (
    <>
      {list.map((item, index) => {
        if (item.type === 'loading') {
          return <Skeleton />
        }
        return <Userinfo index={index} username={item.id} message={item.value} />
      })}
    </>
  )
}

1、新的思路

旧的思路在实现上非常巧妙。但是简洁度依然弱于新的实现方案。除此之外,旧的实现思路还有许多问题需要处理,例如初始化时请求了两次,我们要考虑接口防重的问题。以及当我们多次连续点击按钮时,会出现竞态问题而导致渲染结果出现混乱。

我们基于 use + Suspense 的思路来考虑新的方案。

首先,我们应该将数据存储在 promise 中,因此很自然就能想到,多个数据,那么我们应该需要维护多个 promise,因此,我们需要定义一个由 promise 组成的数组。

代码语言:javascript
复制
const [promise, updatePromise] = useState(() => [getMessage()])

由于初始化时,我们需要自动请求一条数据,因此我们给该数组的初始值为 [getMessage()]

点击时,需要新增一个数据,那么其实就是新增一个 promise,所以代码也非常简单,就是如下所示

代码语言:javascript
复制
function __handler() {
  updatePromise([...promise, getMessage()])
}

处理好之后,我们只需要使用 map 遍历该数组即可。在遍历逻辑中,每一项都返回 Suspense 包裹的子组件。我们将 promise 传递给该子组件,并在子组件中使用 use 读取 promise 中的值。

最终的代码实现如下。

代码语言:javascript
复制
export default function Demo01() {
  const [promise, updatePromise] = useState(() => [getMessage()])

  function __handler() {
    updatePromise([...promise, getMessage()])
  }

  return (
    <>
      <div className='text-right mb-4'>
        <Button onClick={__handler}>新增数据</Button>
      </div>
      {promise.map((item, index) => (
        <Suspense fallback={<Skeleton />} key={`hello ${index}`}>
          <User promise={item} index={index} />
        </Suspense>
      ))}
    </>
  )
}

function User(props) {
  const result = use(props.promise)
  return (
    <Userinfo index={props.index} username={result.id} message={result.value} />
  )
}

此时通过案例演示结果可以观察到,初始化时的接口重复问题被解决掉了,并且当我们多次连续点击新增时,也不会出现接口竞态混乱的问题。

希望大家能够通过这个案例,进一步感受到新的开发思维的强大之处。

2、点击按钮实现分页列表加载更多

我们可以在思维上将上一节的解决方案扩展到分页列表中,加载更多的场景。

这里唯一的一个小区别就是,上一章中,我们只在 promise 中存储了一条数据。如果我们将一页数据也存在 promise 中呢?

加载更多的分页逻辑就会变得非常简单。为了方便演示,我们这里以一页数据只有三条为例。

首先简单约定接口,该接口返回一页数据。3条

代码语言:javascript
复制
// api.js
const count = 3;
const fakeDataUrl = `https://randomuser.me/api/?results=${count}&inc=name,gender,email,nat,picture&noinfo`;

export const fetchList = async () => {
  const res = await fetch(fakeDataUrl)
  return res.json()
}

然后定义一个可以遍历显示一页数据的组件。该组件接收一个 promise,并使用 use 读取请求结果。

代码语言:javascript
复制
// List.jsx
import { use } from 'react';
export default function CurrentList({promise}) {
  const {results} = use(promise)
  return (
    <div>
      {results.map((item, i) => (
        <div key={item.name.last} className='flex border-b py-4 mx-4 items-center'>
          <div className='flex-1'>
            <div className='flex'>
              <img className='w-14 h-14 rounded-full' src={item.picture.large} alt='' />
              <div className='flex-1 ml-4'>
                <div className='font-bold'>{item.name.last}</div>
                <div className='text-gray-400 mt-3 text-sm line-clamp-1'>react 19 re, a design language for background applications</div>
              </div>
            </div>

            <div className='mt-4 line-clamp-2 text-sm'>We supply a series of design principles, practical patterns and high quality design resources (Sketch and Axure), to help people create their product prototypes beautifully and efficiently.</div>
          </div>

          <img
            className='w-52 ml-2'
            alt="logo"
            src="https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png"
          />
        </div>
      ))}
    </div>
  )
}

此时我们稍微梳理一下逻辑,首先我们有多个 promise,然后每个 promise 中有一页数据,因此,我们可以遍历 promise,并在遍历中渲染能显示一页数据的 List 组件。

因此,我们首先要定义一个状态用于保存 promise 数组

代码语言:javascript
复制
const [promises, increasePromise] = useState(() => [fetchList()])

初始化时需要渲染一页数据,所以我们设置该数组的默认值为 [fetchList()]

loadmore 事件触发之后,我们只需要往该数组中新增一个 promise 即可

代码语言:javascript
复制
const onLoadMore = () => {
  increasePromise([...promises, fetchList()])
};

然后遍历 promises,在遍历中使用 Suspense 包裹内部有 use 逻辑的 List 组件

代码语言:javascript
复制
{promises.map((promise, i) => (
  <Suspense fallback={<Skeleton />} key={`hello ${i}`}>
    <List promise={promise} />
  </Suspense>
))}

注意看,完整的代码

代码语言:javascript
复制
const Index = () => {
  const [promises, increasePromise] = useState(() => [fetchList()])

  const onLoadMore = () => {
    increasePromise([...promises, fetchList()])
  };

  return (
    <>
      {promises.map((promise, i) => (
        <Suspense fallback={<Skeleton />} key={`hello ${i}`}>
          <List promise={promise} />
        </Suspense>
      ))}
      <div className='text-center my-4'>
        <Button onClick={onLoadMore}>loading more</Button>
      </div>
    </>
  );
};
export default Index;

非常 nice,我们用极简的代码实现了复杂的交互逻辑。

i分页参数的维护、最后一页的判断,大家在实践中要自行维护,这里只做方案的演示,没有考虑所有边界情况

3、合集介绍

本文内容与案例来自于我倾力打造的付费小册 《React 19》。这本小册将会是市面上学习体验最好质量最高的小册,没有之一

在这本小册的文章中,所有的案例,都不再是以截图的形式展示,而是以可操作,可交互的真实组件渲染而成。你可以轻松感受案例的最终形态。扫清学习过程中的认知差异。

除此之外,最终的完整代码,与最佳实践的案例演示,都会呈现在右侧区域。你还可以通过修改代码实时查看不同逻辑下的运行结果,学习效果直接翻倍。

并且每一个案例,我都精心设计了 UI 与 Loading 效果。确保案例也有最好的学习体验。而不是简单粗糙的案例。

小册内容会包含大量实战案例,确保每一位学完《React 19》的小伙伴都能所学即所得,并且在必要的案例中,我还会详细对比新旧方案的差异。目前该小册内容已经完成了一大半。预计最迟在未来两周以内会完结上线。

该小册的上线价格预计会在 30 元到 100 之间,如果你对该小册的内容质量和学习体验比较看好,可以在该小册上线之前提前投资,你只需要点击下方红色按钮,赞赏本文任意金额元以上,即可提前购买

赞赏之后,请务必添加我的微信好友 icanmeetu 并告知来意。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-06-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 这波能反杀 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0、传统方案实现请求结果新增到列表
  • 1、新的思路
  • 2、点击按钮实现分页列表加载更多
  • 3、合集介绍
相关产品与服务
数据保险箱
数据保险箱(Cloud Data Coffer Service,CDCS)为您提供更高安全系数的企业核心数据存储服务。您可以通过自定义过期天数的方法删除数据,避免误删带来的损害,还可以将数据跨地域存储,防止一些不可抗因素导致的数据丢失。数据保险箱支持通过控制台、API 等多样化方式快速简单接入,实现海量数据的存储管理。您可以使用数据保险箱对文件数据进行上传、下载,最终实现数据的安全存储和提取。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档