React 实现一个漂亮的 Table
2019獨角獸企業重金招聘Python工程師標準>>>
概述
對于企業級后臺產品來說,Table 應該是使用最頻繁的組件了,它通常比 Form 和 Chart 的使用還頻繁。對于這么一個常用的組件,我們決定要把它從 RSuite 中單獨出來開發,并且要具有一定的通用性,適應很多場景。 首先看一下,Table 完成的效果。
- 預覽地址: https://rsuitejs.com/rsuite-table
- Github: https://github.com/rsuite/rsuite-table
最開始促使我們去實現這個 Table 組件是因為產品經理希望表格可以像 Excel一樣固定表頭和列,我們都知道 HTML table 是不支持這個功能,但是在實際應用中,對于數據行多列多的情況下,固定表頭和列非常有用,方便數據關聯瀏覽。我們的組件庫都是 React 的, 開源環境中也沒有找到一個適合的我們的 Table 組件。 Ant Design 中的 Table 估計有些人用過,UI 比較漂亮,但是在固定表頭和列的這個功能上我還是有些不滿意,特別是要同時固定表頭和列的時候,在 Retina 屏幕上,Ant Table 通過觸摸板滾動表格,固定的區域和非固定的區域會對不整齊,看上去會抖動,這個體驗不是特別好。我知道 facebook 的 FixedDataTable 針對這塊的處理做的還不錯,是一個好的參考,特別是大數據量渲染也不卡頓,但是有些功能也不能滿足我們的業務場景,比如在要 Table 中呈現一個樹形結構就沒有這個功能。所以還是決定自己造這個輪子。
設計
在 UI 的設計上符合 RSuite 的整體風格。 我們具體看一下組件的設計,整個 Table 提供了 5 個組件,分別是:
- <Table> 定義表格,可以設置數據源,表格類型等等
- <Column> 定義列,設置列與數據源關聯的 key, 設置列寬度,設置是否可以排序,是否需要固定列等等。
- <Cell> 定義單元格,用于渲染數據的組件,可以自定義顯示的方式。
- <HeaderCell> 定義列頭的單元格。
- <TablePagination> 定義分頁,是一個可選組件。
看一個簡單的示例:
npm i rsuite rsuite-table --save有些地方依賴了 RSuite 中的基礎組件,所有需要安裝 rsuite。
import { Table, Column, HeaderCell, Cell } from 'rsuite-table'; <Table data={data} ><Column width={100} sort fixed resizable><HeaderCell>ID</HeaderCell><Cell dataKey="id" /></Column><Column width={100} sort resizable><HeaderCell>Name</HeaderCell><Cell dataKey="name" /></Column><Column width={100} sort resizable><HeaderCell>Email</HeaderCell><Cell dataKey="email" /></Column> </Table>這是一個簡單 3 列的表格,接下來我們來看一下具體的功能點。
功能介紹
鎖定列
表頭是默認固定的不需要額外的配置,要固定列,需要在固定的列 <Column> 添加 fixed 屬性。
<Column width={100} fixed><HeaderCell>ID</HeaderCell><Cell dataKey="id" /> </Column>這個功能是所有功能里面最麻煩的,特別是表頭和列同時固定的時候,前面我也提到過 Ant Design 的 Table 就存在一個問題,滾動的時候固定列和非固定列未對齊,以下是一個 Ant Design 的 Table 的一個截圖和訪問鏈接。
訪問地址: https://ant.design/components/table-cn/#components-table-demo-fixed-columns-header
造成這個問題的主要原因是 onScroll 觸發的頻率和渲染的速度跟不上造成的, 如果要列和表頭都固定,那必然會在一個方向上需要手動修改元素的位置,這里肯定不能用 React state 存儲位置,然后等待渲染,那太慢了。所有需要操作 DOM, 去改變元素的位置,這里有這幾個需要注意的技術點:
- 用 transform: translate3D 代替 top 與 left ,因為 top/left 會導致回流,而 translate 只產生重繪,性能會更好,另外 translate3D 走的是 3D, 在手機瀏覽器器上會 GPU 加速。
- onScroll 觸發的頻率和渲染的速度會存在跟不上的情況,所有這里最好是自己實現一個滾動條,在 Table Body 上監聽 onWheel 事件,在滾動條上監聽 onMouse* 事件。 在自己實現滾動條的時候需要注意的是,在 Mac 的 chrome 上,左右滑動的時候會觸發瀏覽器的上一頁和下一頁功能,所以這里的事件冒泡要處理好(本來想找一個開源的滾動條輪子,發現有好多組件這個問題沒有處理好,所以就自己寫了)。
對 DOM 操作用到了 dom-lib
我們的 Table 在處理上面兩點以后,就解決了 Ant Design 的 Table 滾動存在的問題,當然如果大家有更好的方案,感謝你分享一下。 另外,Ant Table 有很多方面做得是比我們好的,比如它支持固定右側的列,支持嵌套表格等等功能。
完整示例代碼
可調整列寬
在表格中有些列的數據有長有短,不太好預測,但還是希望在一個單元格內顯示,如果給列固定好一個寬度以后,那超出單元格的內容就會被截斷隱藏,導致信息顯示不完整。Excel 的列是可以調整寬度的,所以我們也希望列可以調整寬度,只需要在 <Column> 設置一個 resizable 屬性。
<Column width={130} sortable><HeaderCell>First Name</HeaderCell><Cell dataKey="firstName" /> </Column>完整示例代碼
自動設置列寬
有一種情況,Table 在頁面中的寬度比如是 1000px+ (可能更寬,根據顯示器屏幕的寬度決定), 但是這個 Table 只有 3 列,如果每列都固定一個 200px, 肯定 撐不滿整個 Table,導致不美觀, 我們都知道 HTML table, 當給 table 設置 width:100% 以后,列會根據內容自動撐滿,如果給其中一個 td 設置了 width, 那 Table 剩下的 width, 會被剩下的幾列撐滿。那在 rsuite-table 怎么解決問題呢? 看以下示例:
<Table width={1000}><Column width={100}><HeaderCell>First Name</HeaderCell><Cell dataKey="firstName" /></Column><Column flexGrow={1}><HeaderCell>City</HeaderCell><Cell dataKey="city" /></Column><Column flexGrow={2}><HeaderCell>Company Name</HeaderCell><Cell dataKey="companyName" /></Column> </Table>在 <Column> 組件上提供了一個 flexGrow 屬性,有點類似 CSS3 中的 flex-grow 屬性。上面示例中,Table 的 width 為 1000 , 第一列的 width:100, 第二列設置為 flexGrow:1, 第三列設置為 flexGrow:2。 渲染后計算的結果是:
- 第一列: 100px
- 第二列: flexGrow:1, (1000 - 100)/(2 + 1) * 1 = 300px
- 第三列: flexGrow:2, (1000 - 100)/(2 + 1) * 2 = 600px
完整示例代碼
排序
排序是一個基礎的功能,在需要排序的列 <Column> 設置一個 sortable 屬性。 同時在 <Table> 定義一個 onSortColumn:Function 回調函數,點擊列頭排序圖標的時候,會觸發該方法,并返回 sortColumn:String 和 sortType:String('asc'|desc)。 看一下示例:
<TableonSortColumn={(sortColumn, sortType)=>{console.log(sortColumn, sortType);}}><Column width={50} sortable><HeaderCell>Id</HeaderCell><Cell dataKey="id" /></Column><Column width={130} sortable ><HeaderCell>First Name</HeaderCell><Cell dataKey="firstName" /></Column><!--... --> </Table>完整示例代碼
分頁
提供了一個 <TablePagination> 組件,用于顯示分頁欄,這里的分頁需要開發人員自己去處理數據,看一下示例代碼:
function formatLengthMenu(lengthMenu) {return (<div className="table-length"><span> 每頁 </span>{lengthMenu}<span> 條 </span></div>); }function formatInfo(total, activePage) {return (<span>共 <i>{total}</i> 條數據</span>); } <TablePaginationformatLengthMenu={formatLengthMenu}formatInfo={formatInfo}displayLength={100}total={500}onChangePage={this.handleChangePage}onChangeLength={this.handleChangeLength} />- formatLengthMenu 格式化顯示行數;
- formatInfo 格式化顯示總條目信息;
- displayLength 默認顯示多少行數據,可以通過 state 管理;
- total 它不是當前返回數據的行數,他是所有數據的總條目數,這個需要后端 API 的返回,通過這個值與displayLength,才能計算出表格分多少頁。可以通過 state 管理;
- onChangePage 切換分頁的回調函數;
- onChangeLength 切換顯示條目數的回調函數。
看一下,效果:
完整示例代碼
樹形表格
先看一下樹形表格的樣子
渲染成樹形的表格需要設置兩個地方,首先 <Table> 組件上設置一個 isTree 屬性,同時 data 中的數據需要通過 children 來定義關系結構。
<Table data={data} isTree expand height={400}>data 中的數據結構
[{labelName: '汽車',status: 'ENABLED',children: [{labelName: '梅賽德斯-奔馳',status: 'ENABLED',count: 460}...]... }]完整示例代碼
自定義單元格
單元格中的內容往往需要能交互的,比如設置為一個連接,或者 hover 的時候能顯示一段信息等等。 在 rsuite-table 中,可以對 Cell 進行自定義。 先看一下以下是一個自定義后的表格圖例:
比如,顯示一個圖片,定義一個 ImageCell 組件:
const ImageCell = ({ rowData, dataKey, ...props }) => (<Cell {...props}><img src={rowData[dataKey]} width="50" /></Cell> );用的時候:
<Column width={200} ><HeaderCell>Avartar</HeaderCell><ImageCell dataKey="avartar" /> </Column>比如,要格式化日期,就定義一個 DateCell 組件:
const DateCell = ({ rowData, dataKey, ...props }) => (<Cell {...props}>{rowData[dataKey].toLocaleString()}</Cell> );用的時候:
<Column width={200} ><HeaderCell>Action</HeaderCell><DateCell dataKey="date" /> </Column>自定義行高
如果在實際應用中需要根據數據內容來定義行高,可以使用以下方式
<TableonRerenderRowHeight={(rowData) => {if (rowData.firstName === 'Janis') {return 30;}}}> ... </Table>完整示例代碼
可編輯的表格
可編輯的表格,只需要自定義一個 <Cell>, 然后通過 state 管理狀態。
export const EditCell = ({ rowData, dataKey, onChange, ...props }) => {return (<Cell {...props}>{rowData.status === 'EDIT' ? (<inputclassName="input"defaultValue={rowData[dataKey]}onChange={(event) => {onChange && onChange(rowData.id, dataKey, event.target.value);}}/>) : rowData[dataKey]}</Cell>); };完整示例代碼
遺留的問題
- 內容自動換行,并且自動設置行高,在 HTML table 中很容易實現這個功能,如果整個 <Table> 是的通過 CSS 布局控制也許能實現這個功能,但是在實現的時候,很多地方都是通過 JS 控制高度,比如: 行高、單元格的 left,top 相對位置等等,所以要根據內容來自動行高是比較麻煩的事情,暫時沒想到好的解決辦法,但是我們提供了一個 onRerenderRowHeight 函數,可以讓用戶自己根據內容來控制行高。
- 根據內容自動設置列寬, 這個問題暫時也沒有想到好的解決方案, 現在只能通過 flexGrow 來填充剩余寬度。
- 固定列在右側,這個功能后續會考慮加進去。
- 表頭分組,合并單元格,這個功能麻煩點在于,我們所有的列都是可以調整列寬的,如果同時考慮合并單元格邏輯上處理有些麻煩,不過后續會考慮加入該功能。
如果,你對這些問題有好的想法歡迎你 提交 pull request。 如果,你在使用中存在任何問題,可以提交 issues。
轉載于:https://my.oschina.net/simonguo/blog/1528330
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的React 实现一个漂亮的 Table的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux中各目录及详细介绍
- 下一篇: 【机器学习实战】第4章 朴素贝叶斯(Na