iT邦幫忙

2022 iThome 鐵人賽

DAY 16
0
自我挑戰組

新手前端與真實世界的開發 feat.React 與他的夥伴系列 第 16

Day-16 掌控表格的行與列 react-table 基本安裝與使用

  • 分享至 

  • xImage
  •  

Day-16 掌控表格的行與列 react-table 基本安裝與使用

會開始學習使用 react-table 是有故事的,因為工作上有個需求是要讓表格需要排序、分頁功能,如果只是這樣的話,我內心想的是 MUI 的 DataGrid 也許能解決這需求,只要直接輸入資料就能擁有功能,結果後來發現不行,但...就沒有給表格格子操作的空間,另外我的樣式都是自己寫的。MUI 自帶的樣式我還得要一個要把它壓過去,感覺不太舒服...疼痛許久,轉向了 headless (不帶樣式的) 的套件,最後留下了 react-table。

緣起

其實我剛開始使用 react-table 時碰到各種障礙 :

  • 強型別的報錯不知道該如何解除(算是 typescript 的部分)
  • 弄不好 react-table 初始設定
  • 改成使用 react-table 來 render 表格時很陌生

花不少時間做出功能,利用這次鐵人賽的機會,跟大家分享學習 react-table 的同時,也幫自己複習知識。

react-table 是甚麼

TanStack Table v8 是一款支援多個框架的套件,react-table 是它支援 react 的版本。

react-table 利用對表格資料的掌控,提供篩選、排序、分頁...等更多的功能,是強大的套件,提供的每個功能都可以個別裝上去,解決需要高度客製化的問題。

而且它有詳盡的官方文件,這也是一大優點。

閱讀這一頁來了解 react-table 的 core。

提醒一下大家,要安裝的是 v8 版本,不要裝錯了喔。

表格的基本知識

使用 react-table 需要更加了解網頁中的表格元素。

閱讀 MDN 的 HTML 表格的基礎,能夠有效的補足這方面的知識。

基本用法就是在 <table> 標籤中,用 <tr><tb> 組成表格,附上官方的範例 :

<table>
  <tr>
    <td>&nbsp;</td>
    <td>Knocky</td>
    <td>Flor</td>
    <td>Ella</td>
    <td>Juan</td>
  </tr>
  <tr>
    <td>Breed</td>
    <td>Jack Russell</td>
    <td>Poodle</td>
    <td>Streetdog</td>
    <td>Cocker Spaniel</td>
  </tr>
  <tr>
    <td>Age</td>
    <td>16</td>
    <td>9</td>
    <td>10</td>
    <td>5</td>
  </tr>
  <tr>
    <td>Owner</td>
    <td>Mother-in-law</td>
    <td>Me</td>
    <td>Me</td>
    <td>Sister-in-law</td>
  </tr>
  <tr>
    <td>Eating Habits</td>
    <td>Eats everyone's leftovers</td>
    <td>Nibbles at food</td>
    <td>Hearty eater</td>
    <td>Will eat till he explodes</td>
  </tr>
</table>

除此之外,表格中另外有提供 <th> 來標記表頭 :

<table>
  <tr>
    <th>Data 1</th> // 它是表頭
    <th>Data 2</th> // 它是表頭
  </tr>
  <tr>
    <td>Calcutta</td>
    <td>Orange</td>
  </tr>
  <tr>
    <td>Robots</td>
    <td>Jazz</td>
  </tr>
</table>

我們還能夠用語意化標籤為表格分類出表頭、表身還有表尾,分別用 <thead><body><tfoot>來包裹元素。

<table>
    <thead>
        <tr>
            <th>Items</th>
            <th scope="col">Expenditure</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <th scope="row">Donuts</th>
            <td>3,000</td>
        </tr>
        <tr>
            <th scope="row">Stationery</th>
            <td>18,000</td>
        </tr>
    </tbody>
    <tfoot>
        <tr>
            <th scope="row">Totals</th>
            <td>21,000</td>
        </tr>
    </tfoot>
</table>

安裝 react-table 與使用方式

進入安裝頁的時候會發現很多選擇,這邊我們安裝 react 版本 :

$ npm install @tanstack/react-table

使用 react-table 的時候需要事先定義好 Columns (也就是表頭的部分),第二項需要定義的內容就是要做成表格的資料內容,最少是一定要放入 data 與 column,以下是官方的範例 :

import {
  createColumnHelper, // 幫忙製作表格列的工具
  flexRender, // 其實就是 flex box
  getCoreRowModel, // 取得行的資料來渲染新表格
  useReactTable, // 使用此 Hook 來掌握表格
} from "@tanstack/react-table";

const table = useReactTable({
  data, // 輸入表格的資料
  columns, // 輸入定義好的表頭
  getCoreRowModel: getCoreRowModel(),
});

資料需要用 type 先定義好,在設進去 columns :

type Person = {
  firstName: string
  lastName: string
  age: number
  visits: number
  status: string
  progress: number
}

columns :

const columnHelper = createColumnHelper<Person>()
const columns = [
  columnHelper.accessor('firstName', {
    cell: info => info.getValue(),
    footer: info => info.column.id,
  }),
  columnHelper.accessor(row => row.lastName, {
    id: 'lastName',
    cell: info => <i>{info.getValue()}</i>,
    header: () => <span>Last Name</span>,
    footer: info => info.column.id,
  }),
  columnHelper.accessor('age', {
    header: () => 'Age',
    cell: info => info.renderValue(),
    footer: info => info.column.id,
  }),
  columnHelper.accessor('visits', {
    header: () => <span>Visits</span>,
    footer: info => info.column.id,
  }),
  columnHelper.accessor('status', {
    header: 'Status',
    footer: info => info.column.id,
  }),
  columnHelper.accessor('progress', {
    header: 'Profile Progress',
    footer: info => info.column.id,
  }),
]

data :


const defaultData: Person[] = [
  {
    firstName: 'tanner',
    lastName: 'linsley',
    age: 24,
    visits: 100,
    status: 'In Relationship',
    progress: 50,
  },
  {
    firstName: 'tandy',
    lastName: 'miller',
    age: 40,
    visits: 40,
    status: 'Single',
    progress: 80,
  },
  {
    firstName: 'joe',
    lastName: 'dirte',
    age: 45,
    visits: 20,
    status: 'Complicated',
    progress: 10,
  },
]

同場加映 : <colgroup>

會認識這個元素,是因為我用 react-table 把表格蓋好之後,收到需求方想要在表格的中心加上一條分隔線,當下我的想法是 :等下,我要在動態生成的表格中寫 css 來加一條線,感覺怪怪的啊,資料是動態的,這意味著會變動的資料我並不能知道選擇器要寫在哪裡(這個狀態是沒辦法用 tailwind 的)...就在我想著要寫一百個選擇器來加一條線的時候,我找到了 <colgroup> ,簡直就是救星!

關於<colgroup>的事情在 MDN 中有講到,可以在表格存在但沒辦法個別對一個 <th> 去做樣式改變的時候(tailwind 跟傳統 css 選擇器很難做的調整),做到對表格一整行的樣式修改。

附上我實際撰寫的程式碼,如下 :

<div className="mb-8">
      <Table>
        <colgroup>
          <col span={8}></col>
          <col className="border-l-[1px] border-[#734A42]"></col> //加上這一行就能在第八行到第10行之間的 <th> 加上 className : border-l-[1px] border-[#734A42]
          <col span={10}></col>
        </colgroup>
        <Thead>
          {table.getHeaderGroups().map((headerGroup) => (
            <tr key={headerGroup.id}>
              {headerGroup.headers.map((header) => {
                return (
                  <th
                    key={header.id}
                    colSpan={header.colSpan}
                    {...{
                      onClick: header.column.getToggleSortingHandler(),
                    }}
                  >
                    {header.isPlaceholder ? null : (
                      <div>
                        {flexRender(
                          header.column.columnDef.header,
                          header.getContext()
                        )}
                        {{
                          asc: "?",
                          desc: "?",
                        }[header.column.getIsSorted() as string] ?? null}
                      </div>
                    )}
                  </th>
                );
              })}
            </tr>
          ))}
        </Thead>
        <tbody className="bg-white">
          {table.getRowModel().rows.map((row, i) => (
            <tr className="border-b border-gray-200" key={row.id}>
              {row.getVisibleCells().map((cell, i) => (
                <td
                  className={`${
                    isOverZero(cell.getContext().getValue())
                      ? "text-red cursor-pointer bg-gray-50"
                      : ""
                  }`}
                  data-rowIndex={i}
                  key={cell.id}
                  onClick={async (e) => {
                    const rowIndex = e.currentTarget.dataset.rowindex as string;
                    const singerCode = await getSignerCode(e, signers);
                    const unitCode = (await getUnitCode(e, units)) as string;
                    const value = await cell.getContext().getValue();
                    if (!value) {
                      return;
                    } else {
                      dispatch(doClick());
                      const singerName = await getSingerName(e);
                      const JBtype = getCurrentJBType(rowIndex) as string[];
                      dispatch(setSingerName(singerName));
                      dispatch(setSignType(JBtype[1]));
                      await dispatch(
                        fetchQuerySignerDetail({
                          DateType: dateType,
                          DateStart: dateStart,
                          DateEnd: dateEnd,
                          SignerCode: singerCode,
                          UnitGCode: unitCode,
                          JBType: JBtype[0],
                        })
                      );
                      dispatch(openModal());
                    }
                  }}
                >
                  {flexRender(cell.column.columnDef.cell, cell.getContext())}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
        <tfoot className="bg-[#734A42] text-[#FFE600]">
          <tr>
            <td colSpan={2}>總計</td>
            <td>{footerData.UMJB1}</td>
            <td>{footerData.UMJB100}</td>
            <td>{footerData.UMJB150}</td>
            <td>{footerData.UJB1}</td>
            <td>{footerData.UJB100}</td>
            <td>{footerData.UJB150}</td>
            <td>{footerData.MJB1}</td>
            <td>{footerData.MJB100}</td>
            <td>{footerData.MJB150}</td>
            <td>{footerData.JB1}</td>
            <td>{footerData.JB100}</td>
            <td>{footerData.JB150}</td>
          </tr>
        </tfoot>
      </Table>
    </div>

參考用 repo

跟這個專案演練無關,這是當初研究時做的練習,是用 js 而不是 ts,送給大家參考。

repo 傳送門

結語

學習套件的同時補強基本知識,也是一種學習跟複習的方法,對我而言是最不無聊的學習方式,滿好玩的。

例如 :學 react-hook-form 的同時穩固對網頁表單的知識,在學習 react-table 的同時更熟的掌握對網頁表格的理解,對我來說一邊實作一邊補足基本知識,當然還有 typescript 的熟練度也因此提升了...

參考資料


上一篇
Day-15 專案演練 - 打造表單功能 react-hook-form
下一篇
Day-17 打造表格列選擇功能 react-table
系列文
新手前端與真實世界的開發 feat.React 與他的夥伴30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言