React 19 的開發(fā)體驗實在是太好了!
自從徹底掌握了 React 19 之后,我感覺自己更愛寫 React 代碼了。比如,像分頁列表這種復(fù)雜交互,核心邏輯只需要簡單幾行代碼就可以搞定。
分頁列表是我們?nèi)粘i_發(fā)中,比較常見的需求。其中,通過點擊或者滾動來觸發(fā)加載更多是主流的交互方式之一。
這篇文章要帶大家實現(xiàn)的效果如下圖所示。
為了便于大家更容易理解和消化,我們先通過一個更簡單的案例來理解代碼思路,然后再實現(xiàn)最終目標(biāo)。
傳統(tǒng)方式實現(xiàn)請求結(jié)果新增到列表中
react19 中實現(xiàn)新增列表內(nèi)容
react19 中通過點擊按鈕實現(xiàn)分頁列表加載更多
0
傳統(tǒng)方案實現(xiàn)請求結(jié)果新增到列表
首先,先定義請求數(shù)據(jù)的 promise
// api.js export const getMessage = async () => { const res = await fetch('https://api.chucknorris.io/jokes/random' ) return res.json() }
然后需要定義一個狀態(tài)用于存儲列表。
const [list, updateList] = useState([])
由于每一項在請求時,都需要顯示一個 Loading 狀態(tài),此時我們可以使用一個巧妙的方式來解決這個問題。那就是暫時往 list 中新增一條 type: loading 的數(shù)據(jù)。在遍歷的時候判斷出該數(shù)據(jù)渲染成 Skeleton 組件。
因此,我們單獨聲明一個列表組件 List,該組件接收 list 作為參數(shù)
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} /> })} </> ) }
當(dāng)我們在發(fā)送請求時,先往 list 中新增一條 type: loading 的數(shù)據(jù)。此時我們利用 list 的特性與閉包的緩存特性,在接口請求成功之后再把請求過來的有效數(shù)據(jù)更新到 list 中即可。
代碼如下
useEffect(() => { updateList([...list, {type : 'loading' }]) getMessage().then(res => { updateList([...list, res]) }) }, []);
完整代碼如下:
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} > 新增數(shù)據(jù)</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
新的思路
舊的思路在實現(xiàn)上非常巧妙。但是簡潔度依然弱于新的實現(xiàn)方案。除此之外,舊的實現(xiàn)思路還有許多問題需要處理,例如初始化時請求了兩次,我們要考慮接口防重的問題。以及當(dāng)我們多次連續(xù)點擊按鈕時,會出現(xiàn)競態(tài)問題而導(dǎo)致渲染結(jié)果出現(xiàn)混亂。
我們基于 use + Suspense 的思路來考慮新的方案。
首先,我們應(yīng)該將數(shù)據(jù)存儲在 promise 中,因此很自然就能想到,多個數(shù)據(jù),那么我們應(yīng)該需要維護(hù)多個 promise,因此,我們需要定義一個由 promise 組成的數(shù)組。
const [promise, updatePromise] = useState(() => [getMessage()])
由于初始化時,我們需要自動請求一條數(shù)據(jù),因此我們給該數(shù)組的初始值為 [getMessage()]
點擊時,需要新增一個數(shù)據(jù),那么其實就是新增一個 promise,所以代碼也非常簡單,就是如下所示
function __handler ( ) { updatePromise([...promise, getMessage()]) }
處理好之后,我們只需要使用 map 遍歷該數(shù)組即可。在遍歷邏輯中,每一項都返回 Suspense 包裹的子組件。我們將 promise 傳遞給該子組件,并在子組件中使用 use 讀取 promise 中的值。
最終的代碼實現(xiàn)如下。
export default function Demo01 ( ) { const [promise, updatePromise] = useState(() => [getMessage()]) function __handler ( ) { updatePromise([...promise, getMessage()]) } return ( <> <div className ='text-right mb-4' > <Button onClick ={__handler} > 新增數(shù)據(jù)</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} /> ) }
此時通過案例演示結(jié)果可以觀察到,初始化時的接口重復(fù)問題被解決掉了,并且當(dāng)我們多次連續(xù)點擊新增時,也不會出現(xiàn)接口競態(tài)混亂的問題。
希望大家能夠通過這個案例,進(jìn)一步感受到新的開發(fā)思維的強(qiáng)大之處。
2
點擊按鈕實現(xiàn)分頁列表加載更多
我們可以在思維上將上一節(jié)的解決方案擴(kuò)展到分頁列表中,加載更多的場景。
這里唯一的一個小區(qū)別就是,上一章中,我們只在 promise 中存儲了一條數(shù)據(jù)。如果我們將一頁數(shù)據(jù)也存在 promise 中呢?
加載更多的分頁邏輯就會變得非常簡單。為了方便演示,我們這里以一頁數(shù)據(jù)只有三條為例。
首先簡單約定接口,該接口返回一頁數(shù)據(jù)。3條
// 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() }
然后定義一個可以遍歷顯示一頁數(shù)據(jù)的組件。該組件接收一個 promise,并使用 use 讀取請求結(jié)果。
// 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://qinglite-1253448069.cos.ap-shanghai.myqcloud.com/web/96b2b85f4c53744407dbed03982cf81c0e1dc322" /> </div > ))} </div > ) }
此時我們稍微梳理一下邏輯,首先我們有多個 promise,然后每個 promise 中有一頁數(shù)據(jù),因此,我們可以遍歷 promise,并在遍歷中渲染能顯示一頁數(shù)據(jù)的 List 組件。
因此,我們首先要定義一個狀態(tài)用于保存 promise 數(shù)組
const [promises, increasePromise] = useState(() => [fetchList()])
初始化時需要渲染一頁數(shù)據(jù),所以我們設(shè)置該數(shù)組的默認(rèn)值為 [fetchList()]
loadmore 事件觸發(fā)之后,我們只需要往該數(shù)組中新增一個 promise 即可
const onLoadMore = () => { increasePromise([...promises, fetchList()]) };
然后遍歷 promises,在遍歷中使用 Suspense 包裹內(nèi)部有 use 邏輯的 List 組件
{promises.map((promise, i ) => ( <Suspense fallback ={ <Skeleton /> } key={`hello ${i}`}> <List promise ={promise} /> </Suspense > ))}
注意看,完整的代碼
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,我們用極簡的代碼實現(xiàn)了復(fù)雜的交互邏輯。
i
分頁參數(shù)的維護(hù)、最后一頁的判斷,大家在實踐中要自行維護(hù),這里只做方案的演示,沒有考慮所有邊界情況
3
合集介紹
本文內(nèi)容與案例來自于我傾力打造的付費小冊 《React 19》 。這本小冊將會是市面上學(xué)習(xí)體驗最好質(zhì)量最高的小冊,沒有之一 。
在這本小冊的文章中,所有的案例,都不再是以截圖的形式展示,而是以可操作,可交互的真實組件渲染而成 。你可以輕松感受案例的最終形態(tài)。掃清學(xué)習(xí)過程中的認(rèn)知差異。
除此之外,最終的完整代碼,與最佳實踐的案例演示,都會呈現(xiàn)在右側(cè)區(qū)域。你還可以通過修改代碼實時查看不同邏輯下的運(yùn)行結(jié)果 ,學(xué)習(xí)效果直接翻倍。
并且每一個案例,我都精心設(shè)計了 UI 與 Loading 效果。確保案例也有最好的學(xué)習(xí)體驗。而不是簡單粗糙的案例。
小冊內(nèi)容會包含大量實戰(zhàn)案例,確保每一位學(xué)完《React 19》的小伙伴都能所學(xué)即所得,并且在必要的案例中,我還會詳細(xì)對比新舊方案的差異。目前該小冊內(nèi)容已經(jīng)完成了一大半。