Framework/React

[React] react-virtuoso๋ฅผ ์‚ฌ์šฉํ•œ mui react Table ๋ Œ๋”๋ง ์ตœ์ ํ™” ์„ฑ๋Šฅ๊ฐœ์„  ๋ฐฉ๋ฒ•

yuri lee 2024. 9. 1. 17:50
๋ฐ˜์‘ํ˜•

Intro

์•ˆ๋…•ํ•˜์„ธ์š”. ์ด๋ฒˆ ์‹œ๊ฐ„์—๋Š” react-virtuoso ๊ฐ€ ๋ฌด์—‡์ด๋ฉฐ, react virtuoso ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ react table ๋ Œ๋”๋ง ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
 

Issue

mui react table์„ ์‚ฌ์šฉํ•˜๋˜ ๋„์ค‘ ๋ฐ์ดํ„ฐ ์–‘์ด ๋งŽ์•„์ง€๋ฉด์„œ ํ…Œ์ด๋ธ”์ด ๊ทธ๋ ค์ง€๋Š” ์†๋„๊ฐ€ ๋Š๋ ค์ ธ ์„œ๋น„์Šค ์ด์šฉ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.  ๋”ฐ๋ผ์„œ react table์˜ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•ด ๊ฐœ์„  ๋ฐฉ์•ˆ์„ ์ฐพ์•„์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. 
 
๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„œ์น˜ํ•˜๋˜ ๋„์ค‘ TanStack ์™€, mui ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์— Virtualized table ๋ถ€๋ถ„์— ์†Œ๊ฐœ๋˜์–ด ์žˆ๋Š” react-virtuoso ๋‘๊ฐœ์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ฐœ๊ฒฌํ•˜์˜€์Šต๋‹ˆ๋‹ค. star์˜ ์ˆ˜ ์ž์ฒด๋Š” TanStack์ด ๋” ๋†’์•˜์ง€๋งŒ ์‚ฌ์šฉ๋ฐฉ๋ฒ•์ด ๋” ์‰ฝ๊ณ , ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์— ์šฉ์ดํ•˜๊ฒŒ ๋ถ™์ผ ์ˆ˜ ์žˆ๋Š” react-virtuoso ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€์Šต๋‹ˆ๋‹ค. 

 

react-virtuoso

react virtuoso๋Š” react ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ๋Œ€์šฉ๋Ÿ‰ list, table, grid ๋“ฑ์„ ํšจ์œจ์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ์ฃผ๋กœ ๊ฐ€์ƒํ™” (virtualization) ๋˜๋Š” ์œˆ๋„์ž‰(windowing) ๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ™”๋ฉด์— ๋ณด์ด๋Š” ํ•ญ๋ชฉ๋งŒ ๋ Œ๋”๋งํ•˜์—ฌ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•ฉ๋‹ˆ๋‹ค. 

 

react-virtuoso example

react virtuoso ์˜ˆ์‹œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค. 

import * as React from 'react';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';
import { TableVirtuoso, TableComponents } from 'react-virtuoso';

interface Data {
  calories: number;
  carbs: number;
  dessert: string;
  fat: number;
  id: number;
  protein: number;
}

interface ColumnData {
  dataKey: keyof Data;
  label: string;
  numeric?: boolean;
  width: number;
}

type Sample = [string, number, number, number, number];

const sample: readonly Sample[] = [
  ['Frozen yoghurt', 159, 6.0, 24, 4.0],
  ['Ice cream sandwich', 237, 9.0, 37, 4.3],
  ['Eclair', 262, 16.0, 24, 6.0],
  ['Cupcake', 305, 3.7, 67, 4.3],
  ['Gingerbread', 356, 16.0, 49, 3.9],
];

function createData(
  id: number,
  dessert: string,
  calories: number,
  fat: number,
  carbs: number,
  protein: number,
): Data {
  return { id, dessert, calories, fat, carbs, protein };
}

const columns: ColumnData[] = [
  {
    width: 200,
    label: 'Dessert',
    dataKey: 'dessert',
  },
  {
    width: 120,
    label: 'Calories\u00A0(g)',
    dataKey: 'calories',
    numeric: true,
  },
  {
    width: 120,
    label: 'Fat\u00A0(g)',
    dataKey: 'fat',
    numeric: true,
  },
  {
    width: 120,
    label: 'Carbs\u00A0(g)',
    dataKey: 'carbs',
    numeric: true,
  },
  {
    width: 120,
    label: 'Protein\u00A0(g)',
    dataKey: 'protein',
    numeric: true,
  },
];

const rows: Data[] = Array.from({ length: 200 }, (_, index) => {
  const randomSelection = sample[Math.floor(Math.random() * sample.length)];
  return createData(index, ...randomSelection);
});

const VirtuosoTableComponents: TableComponents<Data> = {
  Scroller: React.forwardRef<HTMLDivElement>((props, ref) => (
    <TableContainer component={Paper} {...props} ref={ref} />
  )),
  Table: (props) => (
    <Table {...props} sx={{ borderCollapse: 'separate', tableLayout: 'fixed' }} />
  ),
  TableHead: React.forwardRef<HTMLTableSectionElement>((props, ref) => (
    <TableHead {...props} ref={ref} />
  )),
  TableRow,
  TableBody: React.forwardRef<HTMLTableSectionElement>((props, ref) => (
    <TableBody {...props} ref={ref} />
  )),
};

function fixedHeaderContent() {
  return (
    <TableRow>
      {columns.map((column) => (
        <TableCell
          key={column.dataKey}
          variant="head"
          align={column.numeric || false ? 'right' : 'left'}
          style={{ width: column.width }}
          sx={{
            backgroundColor: 'background.paper',
          }}
        >
          {column.label}
        </TableCell>
      ))}
    </TableRow>
  );
}

function rowContent(_index: number, row: Data) {
  return (
    <React.Fragment>
      {columns.map((column) => (
        <TableCell
          key={column.dataKey}
          align={column.numeric || false ? 'right' : 'left'}
        >
          {row[column.dataKey]}
        </TableCell>
      ))}
    </React.Fragment>
  );
}

export default function ReactVirtualizedTable() {
  return (
    <Paper style={{ height: 400, width: '100%' }}>
      <TableVirtuoso
        data={rows}
        components={VirtuosoTableComponents}
        fixedHeaderContent={fixedHeaderContent}
        itemContent={rowContent}
      />
    </Paper>
  );
}

 
TableVirtuoso ์ปดํฌ๋„ŒํŠธ ์ƒ์œ„๋กœ Paper ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐ์‹ผ ํ›„, ๊ธฐ๋ณธ์ ์œผ๋กœ height๋ฅผ ์ง€์ •ํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค. height๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ํ™”๋ฉด์— ๋‚˜ํƒ€๋‚˜์ง€ ์•Š๋”๋ผ๊ณ ์š”. ๊ผญ Paper ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์•„๋‹ˆ๋”๋ผ๋„ Box ์ปดํฌ๋„ŒํŠธ๋‚˜ div ํƒœ๊ทธ๋„ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์‹ธ์ค„ ํƒœ๊ทธ๋ฅผ ์ง€์ •ํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค. 
 
์ดํ›„ data prop์—๋Š” rows ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ฃผ๊ณ , components props์—๋Š” ์ •์˜๋œ VirtuosoTablecomponents๋ฅผ ๋„ฃ์–ด์ค๋‹ˆ๋‹ค. ์ดํ›„ fixedHeaderContent, itemContent props๋ฅผ ํ™œ์šฉํ•˜์—ฌ headers์™€ rowContent ๋ฅผ ์ง€์ •ํ•ด์ค๋‹ˆ๋‹ค.
 
๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•์€ ๋‹ค์Œ๊ณผ ๊ฐ™๊ณ , ๋” ๋””ํ…Œ์ผํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์‚ฟ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€๋ฅผ ์ฐธ๊ณ ํ•˜์‹œ๊ธธ ๋ฐ”๋ž˜์š”! 
 


https://virtuoso.dev/

 

Getting Started with React Virtuoso | React Virtuoso

React Virtuoso is a set of React components that display large data sets using virtualized rendering. The Virtuoso components automatically handle items with variable sizes and changes in items' sizes.

virtuoso.dev

 
https://tanstack.com/
https://mui.com/material-ui/react-table/#virtualized-table

๋ฐ˜์‘ํ˜•