iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
佛心分享-SideProject30

就決定是你了!打造寶可夢持有追蹤系統 (React x Express)系列 第 25

DAY 25:實作 React Window 虛擬滾動列表 (核心程式碼對比)

  • 分享至 

  • xImage
  •  

今日目標說明

今天我們要把昨天的 react-window 觀念,實際應用到系統的「寶可夢交換列表」中。在這個對話框裡,我們需要一口氣列出玩家擁有的所有寶可夢,結合虛擬滾動後,我們還要搭配一個效能殺手鐧:React.memo

【技術原理解析】為什麼長列表一定要搭配 React.memo

在虛擬滾動中,當使用者快速向下滑動,React 必須在毫秒級別內不斷地「摧毀上方看不到的 Row」並「建立下方新出現的 Row」。
如果我們沒對 Row 進行快取,當上層組件 (List) 的狀態改變時,清單裡所有的 Row 都會被迫重新渲染 (Re-render)。
透過 React.memo 把每一列 (Row) 包起來,可以告訴 React:「只要這列的 props (例如這隻寶可夢的資料) 沒有改變,就直接拿上次畫好的結果來用,不要重畫!」這正是長列表效能優化的最後一哩路。

核心程式碼片段

src/components/admin/TradePokemonDialog.tsx 中,我們完美結合了這兩項技術:

import React, { memo } from 'react';
import { FixedSizeList } from 'react-window';
import { Box, Stack, Typography } from '@mui/material';

// 1. 使用 React.memo 包裝 Row,避免不必要的重新渲染
const TradePokemonRow = memo(({ index, style, data }: any) => {
  const { filtered, selectedCounts } = data;
  const group = filtered[index];
  
  if (!group) return null;

  return (
    // 必須把 style 傳給最外層的容器,這是 react-window 絕對定位的關鍵!
    <Box style={style} sx={{ borderBottom: '1px solid #eee' }}>
      <Stack direction="row" sx={{ p: 2 }}>
        <Typography>{group.representative.pokemon_name}</Typography>
        <Typography>選擇數量:{selectedCounts[group.key] || 0}</Typography>
      </Stack>
    </Box>
  );
});

// 2. 在主件中使用 FixedSizeList
const TradePokemonList = ({ filtered, selectedCounts }) => {
  return (
    <FixedSizeList
      height={400}               // 列表可視高度
      width="100%"               // 列表可視寬度
      itemSize={60}              // 每一列的固定高度
      itemCount={filtered.length} // 資料總筆數
      itemData={{ filtered, selectedCounts }} // 傳遞給 Row 的動態資料
    >
      {TradePokemonRow}
    </FixedSizeList>
  );
};

實際畫面截圖

交換功能
交換寶可夢視窗

在交換寶可夢頁面使用交換功能,選擇要交換的帳號後開啟「從庫存選擇」,展開可以交換的寶可夢庫存。
透過 React Window,載入多筆資料依然順暢。

小結

我們成功排除了 DOM 節點過多與無謂渲染所造成的效能地雷!明天,我們要為這個超長列表加上「搜尋與過濾功能」,讓使用者能一秒找到他想找的寶可夢!


上一篇
DAY 24:當寶可夢變多時?React Window 虛擬滾動介紹
下一篇
DAY 26:實作前端簡易搜尋與過濾功能 (找出特定的寶可夢)
系列文
就決定是你了!打造寶可夢持有追蹤系統 (React x Express)30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言