摘要:加這兩個屬性的原因很容易想到,因為我們在寫表格相關業務時,樣式里面寫的最多的就是單元格的寬度和對齊方式。然而,寫的表格后粘貼在中,整行的內容都在一個單元格里面,用寫的表格則能夠幾乎保持原本的格式,所以我們這次用了原生的來寫表格。
Table 是最常用展示數據的方式之一,可是一個產品中往往很多非常類似的 Table,但是我們碰到的情況往往是 Table A 要排序,Table B 不需要排序,等等這種看起來非常類似,但是又不完全相同的表格。這種情況下,到底要不要抽取一個公共的 Table 組件呢?對于這個問題,我們團隊也糾結了很久,先后開發了多個版本的 Table 組件,在最近的一個項目中,產出了第三版 Table 組件,能夠較好的解決靈活性和公共邏輯抽取的問題。本文將會詳細的講述這種 Table 組件解決方案產出的過程和一些思考。
Table 的常見實現首先我們看到的是不使用任何組件實現一個業務表格的代碼:
import React, { Component } from "react"; const columnOpts = [ { key: "a", name: "col-a" }, { key: "b", name: "col-b" }, ]; function SomeTable(props) { const { data } = props; return (); }{ columnOpts.map((opt, colIndex) => (
- {opt.name}
)) }{ data.map((entry, rowIndex) => (
- { columnOpts.map((opt, colIndex) => ( {entry[opt.key]} )) }
)) }
這種實現方法帶來的問題是:
每次寫表格需要寫很多布局類的樣式
重復代碼很多,而且項目成員之間很難達到統一,A 可能喜歡用表格來布局,B 可能喜歡用 ul 來布局
相似但是不完全相同的表格很難復用
抽象過程組件是對數據和方法的一種封裝,在封裝之前,我們總結了一下表格型的展示的特點:
輸入數據源較統一,一般為對象數組
thead 中的單元格大部分只是展示一些名稱,也有一些個性化的內容,如帶有排序 icon 的單元格
tbody 中的部分單元格只是簡單的讀取一些值,很多單元格的都有自己的邏輯,但是在一個產品中通常很多類似的單元格
列是有順序的,更適合以列為單位來添加布局樣式
基于以上特點,我們希望 Table 組件能夠滿足以下條件:
接收一個 對象數組 和 所有列的配置 為參數,自動創建基礎的表格內容
thead 和 tbody 中的單元格都能夠定制化,以滿足不同的需求
至此,我們首先想到 Table 組件應該長成這樣的:
const columnOpts = [ { key: "a", name: "col-a", onRenderTd: () => {} }, { key: "b", name: "col-b", onRenderTh: () => {}, onRenderTd: () => {} }, ];
其中 onRenderTd 和 onRenderTh 分別是渲染 td 和 th 時的回調函數。
到這里我們發現對于稍微復雜一點的 table,columnOpts 將會是一個非常大的配置數組,我們有沒有辦法不使用數組來維護這些配置呢?這里我們想到的一個辦法是創建一個 Column 的組件,讓大家可以這么來寫這個 table:
這樣大家就可以像寫HTML一樣把一個簡單的表格給搭建出來了。
優化有了 Table 的雛形,再聯系下寫表格的常見需求,我們給 Column 添加了 width 和 align 屬性。加這兩個屬性的原因很容易想到,因為我們在寫表格相關業務時,樣式里面寫的最多的就是單元格的寬度和對齊方式。我們來看一下 Column 的實現:
import React, { PropTypes, Component } from "react"; const propTypes = { name: PropTypes.string, dataKey: PropTypes.string.isRequired, align: PropTypes.oneOf(["left", "center", "right"]), width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), th: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), td: PropTypes.oneOfType([ PropTypes.element, PropTypes.func, PropTypes.oneOf([ "int", "float", "percent", "changeRate" ]) ]), }; const defaultProps = { align: "left", }; function Column() { return null; } Column.propTypes = propTypes; Column.defaultProps = defaultProps; export default Column;
代碼中可以發現 th 可以接收兩種格式,一種是 function,一種是 ReactElement。這里提供 ReactElement 類型的 th 主要讓大家能夠設置一些額外的 props,后面我們會給出一個例子。
td 的類型就更復雜了,不僅能夠接收 function 和 ReactElement 這兩種類型,還有 int, float, percent, changeRate 這三種類型是最常用的數據類型,這樣方便我們可以在 Table 里面根據類型對數據做格式化,省去了項目成員中很多重復的代碼。
下面我們看一下 Table 的實現:
const getDisplayName = (el) => { return el && el.type && (el.type.displayName || el.type.name); }; const renderChangeRate = (changeRate) => { ... }; const renderThs = (columns) => { return columns.map((col, index) => { const { name, dataKey, th } = col.props; const props = { name, dataKey, colIndex: index }; let content; let className; if (React.isValidElement(th)) { content = React.cloneElement(th, props); className = getDisplayName(th); } else if (_.isFunction(th)) { content = th(props); } else { content = name || ""; } return ({content} ); }); }; const renderTds = (data, entry, columns, rowIndex) => { return columns.map((col, index) => { const { dataKey, td } = col.props; const value = getValueOfTd(entry, dataKey); const props = { data, rowData: entry, tdValue: value, dataKey, rowIndex, colIndex: index }; let content; let className; if (React.isValidElement(td)) { content = React.cloneElement(td, props); className = getDisplayName(td); } else if (td === "changeRate") { content = renderChangeRate(value || ""); } else if (_.isFunction(td)) { content = td(props); } else { content = formatIndex(parseValueOfTd(value), dataKey, td); } return ({content} ); }); }; const renderRows = (data, columns) => { if (!data || !data.length) {return null;} return data.map((entry, index) => { return ({renderTds(data, entry, columns, index)} ); }); }; function Table(props) { const { children, data, className } = props; const columns = findChildrenByType(children, Column); return (); }{hasNames(columns) && (
{renderThs(columns)} )} {renderRows(data, columns)}
代碼說明了一切,就不再詳細說了。當然,在業務組件里,還可以加上公共的錯誤處理邏輯。
單元格示例前面提到我們的 td 和 th 還可以接收 ReactElement 格式的 props,大家可能還有會有點疑惑,下面我們看一個 SortableTh 的例子:
class SortableTh extends Component { static displayName = "SortableTh"; static propTypes = { ..., initialOrder: PropTypes.oneOf(["asc", "desc"]), order: PropTypes.oneOf(["asc", "desc", "none"]).isRequired, onChange: PropTypes.func.isRequired, }; static defaultProps = { order: "none", initialOrder: "desc", }; onClick = () => { const { onChange, initialOrder, order, dataKey } = this.props; if (dataKey) { let nextOrder = "none"; if (order === "none") { nextOrder = initialOrder; } else if (order === "desc") { nextOrder = "asc"; } else if (order === "asc") { nextOrder = "desc"; } onChange({ orderBy: dataKey, order: nextOrder }); } }; render() { const { name, order, hasRate, rateType } = this.props; return ({name}); } }
通過這個例子可以看到,th 和 td 接收 ReactElement 類型的 props 能夠讓外部很好的控制單元格的內容,每個單元格不只是接收 data 數據的封閉單元。
總結總結一些自己的感想:
前端工程師也需要往前走一步,了解用戶習慣。在寫這個組件之前,我一直是用 ul 來寫表格的,用 ul 寫的表格調整樣式比較便利,后來發現用戶很多時候喜歡把整個表格里面的內容 copy 下來用于存檔。然而,ul 寫的表格 copy 后粘貼在 excel 中,整行的內容都在一個單元格里面,用 table 寫的表格則能夠幾乎保持原本的格式,所以我們這次用了原生的 table 來寫表格。
業務代碼中組件抽取的粒度一直是一個比較糾結的問題。粒度太粗,項目成員之間需要寫很多重復的代碼。粒度太細,后續可擴展性又很低,所以只能是大家根據業務特點來評估了。像 Table 這樣的組件非常通用,而且后續肯定有新的類型冒出來,所以粒度不宜太細。當然,我們這樣寫 Table 組件后,大家可以抽取常用的一些 XXXTh 和 XXXTd。
最終,我把這次 Table 組件的經驗抽離出來,開源到 https://github.com/recharts/react-smart-table,希望開發者們可以參考。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/79386.html
摘要:場景為了更清晰的安排年前年后的工作和值班,現在要對過年期間人員請假的情況進行統計,并且進行一個簡單的管理。我們現在來訂閱一個名為的事件,用來表示表格中需要展示每條數據。 前言 React 導讀(一)React 導讀(二) 在之前 2 篇文章中中學習到了寫第一個 Web 組件以及常用的生命周期函數的使用,這篇文章將繼續之前的目錄,開始新的知識點補充: [x] React 如何編寫 He...
摘要:本次需求其實就兩個邏輯輸入篩選項。當發生改變時,重新渲染頁面首次進入頁面時,無任何篩選項。關于的一些,官方也有很棒的文檔寫在后面本文通過工作中的一個小需求,完成了一次的實踐,不過上述代碼依然有很多需要優化的地方。 寫在前面 showImg(https://segmentfault.com/img/bVbpBgw?w=1000&h=563); 本文首發于公眾號:符合預期的CoyPan R...
摘要:是今年一定要學的東西這兩年頁面上用的三方組件多了,寫的少了,的一些屬性不太記得了,針對的學習計劃有兩個參照的樣式進行學習參照的組件樣式,學習如何處理樣式與組件之間的關系,規范自己的寫法。 磕磕絆絆工作有幾年了,前端界幾乎每天都有新名詞,令人眼花繚亂,目瞪狗呆。這兩年一直在外包工作,業務寫的多些,對js的基礎掌握的還不是很到位。最近深感技術嗅覺遲鈍,雖然平時也有看書學習,更多的時候都是斷...
摘要:每個框架類庫被大量用戶大規模使用都說明其戳中了開發者的剛需。但是未執行完的情況下發生人機交互雖然不會報腳本錯誤,但是嚴重影響用戶體驗開發者們被各種爽到之后,這個問題已經被拋到了九霄云外。 寫在前面 因為zepto、jQuery2.x.x和Nuclear都是為現代瀏覽器而出現,不兼容IE8,適合現代瀏覽器的web開發或者移動web/hybrid開發。每個框架類庫被大量用戶大規模使用都說明...
摘要:謹記,請勿犯這樣的錯誤。由于在之前的教程中,積累了堅實的基礎。其實,這是有緣由的其復雜度在早期的學習過程中,將會帶來災難性的影響。該如何應對對于來說,雖然有大量的學習計劃需要采取,且有大量的東西需要學習。 前言倘若你正在建造一間房子,那么為了能快點完成,你是否會跳過建造過程中的部分步驟?如在具體建設前先鋪設好部分石頭?或直接在一塊裸露的土地上先建立起墻面? 又假如你是在堆砌一個結婚蛋糕...
閱讀 923·2021-11-22 13:54
閱讀 2851·2021-09-28 09:36
閱讀 2989·2019-08-30 15:55
閱讀 1957·2019-08-30 15:44
閱讀 551·2019-08-29 12:31
閱讀 2568·2019-08-28 18:18
閱讀 1207·2019-08-26 13:58
閱讀 1393·2019-08-26 13:44