封裝組件是為了能在開發(fā)過程中高度復(fù)用功能和樣式相似的組件耳胎,以便我們只關(guān)注于業(yè)務(wù)邏輯層的處理送滞,提高開發(fā)效率谬墙,提高逼格夫嗓,降低代碼重復(fù)率迹炼,降低勞動(dòng)時(shí)間德迹,減少加班的可能知染。
本次組件的封裝采用了函數(shù)式組件即無狀態(tài)組件的方式來提高頁面渲染性能烈掠,由于無狀態(tài)組件在數(shù)據(jù)變更后不會(huì)主動(dòng)觸發(fā)頁面的重新渲染彤钟,所以本次的封裝也用到了React Hooks来候。下面簡要介紹一下函數(shù)式組件和React Hooks。
函數(shù)式組件是被精簡成一個(gè)render方法的函數(shù)來實(shí)現(xiàn)的逸雹,由于是無狀態(tài)組件营搅,所以無狀態(tài)組件就不會(huì)再有組件實(shí)例化的過程云挟,無實(shí)例化過程也就不需要分配多余的內(nèi)存,從而性能得到一定的提升转质。但函數(shù)式組件是沒有this和ref的园欣,如果強(qiáng)行給函數(shù)式組件添加ref會(huì)報(bào)一個(gè)'Function components cannot have string refs. We recommend using useRef() instead.'的錯(cuò)誤。函數(shù)式組件也沒有生命周期休蟹,沒有所謂的componentWillMount沸枯、componentDidMount、componentWillReceiveProps赂弓、shouldComponentUpdate等方法绑榴。
React Hooks是react16.8引入的特性,他允許你在不寫class的情況下操作state和react的其他特性盈魁。Hook就是JavaScript函數(shù)翔怎,但是使用它們會(huì)有兩個(gè)額外的規(guī)則:
只能在函數(shù)最外層調(diào)用Hook,不能在循環(huán)备埃、條件判斷或者子函數(shù)中調(diào)用姓惑;
只能在React的函數(shù)組件中調(diào)用Hook,不能在其他JavaScript函數(shù)中調(diào)用(自定義Hook除外)按脚。
React Hooks中用的比較頻繁的方法:
useState
useEffect
useContext
useReducer
useMemo
useRef
useImperativeHandle
由于以上方法的具體介紹及使用的篇幅過大于毙,故請(qǐng)自行查閱API或資料,這里不再展開描述辅搬。
另外唯沮,本次封裝一部分采用了JSX來創(chuàng)建虛擬dom節(jié)點(diǎn),一部分采用了createElement方法來創(chuàng)建虛擬dom節(jié)點(diǎn)堪遂,createElement方法接收三個(gè)參數(shù):
第一個(gè)參數(shù):必填介蛉,可以是一個(gè)html標(biāo)簽名稱字符串如span、div溶褪、p等币旧,也可以是一個(gè)react組件;
第二個(gè)參數(shù):選填猿妈,創(chuàng)建的標(biāo)簽或組件的屬性吹菱,用對(duì)象方式表示;
第三個(gè)參數(shù):選填彭则,創(chuàng)建的標(biāo)簽或組件的子節(jié)點(diǎn)鳍刷,可以是一個(gè)字符串或一個(gè)組件,也可以是一個(gè)包含了字符串或組件的數(shù)組俯抖,還可以是一個(gè)采用createElement創(chuàng)建的虛擬dom節(jié)點(diǎn)输瓜。
createElement方法可能用的人不會(huì)很多,因?yàn)楝F(xiàn)在有了類似于html的JavaScript語法糖JSX,使用和理解起來也較為直觀和方便尤揣,符合我們對(duì)html結(jié)構(gòu)的認(rèn)知搔啊。但其實(shí)JSX被babel編譯后的呈現(xiàn)方式就是使用createElement方法創(chuàng)建的虛擬dom,至于為何使用createElement方法芹缔,私心覺得可以提升編譯打包效率坯癣。另外本次封裝組件時(shí)有些地方也使用了JSX,是覺得在那些地方使用JSX更舒服最欠,而有些地方使用createElement方法私心也覺得更符合js的編寫習(xí)慣,如果你覺得在一個(gè)組件中既有JSX又有createElement會(huì)很亂的話惩猫,你可以統(tǒng)一使用一種即可芝硬。
本次封裝所使用到的方法的介紹基本完畢,以下是組件封裝的具體實(shí)現(xiàn)部分轧房。
先貼一張最后實(shí)現(xiàn)的效果圖:
1拌阴、所封裝的antd table組件table.js
import React, { createElement, useState, useImperativeHandle } from 'react'
import PropTypes from 'prop-types'
import { Link } from "react-router-dom"
import { Table } from 'antd';
import { timestampToTime, currency } from '@/utils'
const h = createElement;
const TableComp = ({columns, dataSource, hasCheck, cRef, getCheckboxProps}) => {
? const empty = '-',
? ? [selectedRowKeys, setSelectedRowKeys ] = useState([]),
? ? [selectedRows, setSelectedRows] = useState([]),
? ? render = {
? ? ? Default: v => v,
? ? ? Enum: (v, {Enum}) => {
? ? ? ? if(!Enum[v]) return empty;
? ? ? ? return Enum[v]
? ? ? },
? ? ? Format: (v, {format}, row) => format(v, row),
? ? ? Currency: v => currency(v),
? ? ? Date: v => timestampToTime(v, 'second'),
? ? ? Link: (v, {url}) => <Link to={url}>{v}</Link>,
? ? ? Action: (v, {value}, row) => {
? ? ? ? const result = value.filter(n => {
? ? ? ? ? const {filter = () => true} = n
? ? ? ? ? return filter(row)
? ? ? ? })
? ? ? ? return result.map(t => <span className="table-link" onClick={() => t.click(row)} key={t.label}>{t.label}</span>)
? ? ? },
? ? }
? columns = columns.map(n => {
? ? const { type = 'Default' } = n;
? ? return {...n, render: (v, row) => (!v || v.length < 1) && type != 'Action' ? empty : render[type](v, n, row)}
? })
? //父組件獲取selectedRowKeys的方法-cRef就是父組件傳過來的ref
? useImperativeHandle(cRef, () => ({
? ? //getSelectedRowKeys就是暴露給父組件的方法
? ? getSelectedRowKeys: () => selectedRowKeys,
? ? getSelectedRows: () => selectedRows
? }));
? const onSelectChange = (selectedRowKeys, selectedRows) => {
? ? setSelectedRowKeys(selectedRowKeys);
? ? setSelectedRows(selectedRows);
? }
? const rowSelection = {
? ? selectedRowKeys,
? ? onChange: onSelectChange,
? ? getCheckboxProps: record => getCheckboxProps(record)
? };
? return hasCheck ? h(Table, {columns, dataSource, rowSelection}) : h(Table, {columns, dataSource})
}
TableComp.propTypes = {
? columns: PropTypes.array.isRequired,? ? //表格頭部
? dataSource: PropTypes.array.isRequired, //表格數(shù)據(jù)
? hasCheck: PropTypes.bool,? ? ? ? ? ? ? //表格行是否可選擇
? cRef: PropTypes.object,? ? ? ? ? ? ? ? //父組件傳過來的獲取組件實(shí)例對(duì)象或者是DOM對(duì)象
? getCheckboxProps: PropTypes.func,? ? ? //選擇框的默認(rèn)屬性配置
}
export default TableComp
2、時(shí)間戳格式化timestampToTime.js
export const timestampToTime = (timestamp, type) => {
? let date = new Date(timestamp); //時(shí)間戳為10位需*1000奶镶,時(shí)間戳為13位的話不需乘1000
? let Y = date.getFullYear() + '-';
? let M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
? let D = date.getDate() < 10 ? '0' + date.getDate() + ' ' : date.getDate() + '';
? let h = date.getHours() < 10 ? '0' + date.getHours() + ':' : date.getHours() + ':';
? let m = date.getMinutes() < 10 ? '0' + date.getMinutes() + ':' : date.getMinutes() + ':';
? let s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds();
? if (type == 'second') {
? ? return Y + M + D + ' ' + h + m + s;
? } else {
? ? return Y + M + D
? }
}
3迟赃、金額千分位currency.js
export const currency = v => {
? let [n, d = []] = v.toString().split('.');
? return [n.replace(/(\d)(?=(?:\d{3})+$)/g, '$1,')].concat(d).join('.');
};
簡單介紹下封裝的table組件,本組件基本涵蓋了大多數(shù)情況下我們所用到的表格組件的情況厂镇,包括:操作按鈕的權(quán)限控制纤壁、某一列的跳轉(zhuǎn)、某一列的字段映射以及金額千分位捺信、時(shí)間戳格式化和某一列數(shù)組數(shù)據(jù)的循環(huán)展示酌媒、表格是否可選擇等等。如還有其他需求迄靠,可自行添加即可秒咨。
4、table組件的使用方法:
import React, { useRef } from 'react'
import { Button } from 'antd'
import Table from './table'
const TableDemo = () => {
? const permission = ["handle", "pass", "refuse", "reApply", 'export'], Enum = {
? ? CREATED: '代辦理',
? ? PASS: '待審批',
? ? REJECT: '駁回',
? ? REFUSE: '拒絕',
? };
? const statisticFormat = val => val.map((t, idx) => <span key={idx} style={{marginRight: '5px'}}>{t.total}</span>)
? const columns = [
? ? { title: '姓名', dataIndex: 'name', key: 'name', type: 'Link', url: 'https://www.baidu.com' },
? ? { title: '年齡', dataIndex: 'age', key: 'age' },
? ? { title: '狀態(tài)', dataIndex: 'status', key: 'status', type: 'Enum', Enum, },
? ? { title: '預(yù)警統(tǒng)計(jì)', dataIndex: 'statistic', key: 'statistic', type: 'Format', format: statisticFormat },
? ? { title: '存款', dataIndex: 'money', key: 'money', type: 'Currency' },
? ? { title: '日期', dataIndex: 'date', key: 'date', type: 'Date'},
? ? { title: '操作', dataIndex: 'action', key: 'action', type: 'Action', value: [
? ? ? {label: "查看", click: data => {console.log(data)}},
? ? ? {label: "辦理", click: data => {}, filter: ({status}) => status == 'CREATED' && permission.some(n => n == 'handle')},
? ? ? {label: "通過", click: data => {}, filter: ({status}) => status == 'PASS' && permission.some(n => n == 'pass')},
? ? ? {label: "駁回", click: data => {}, filter: ({status}) => status == 'REJECT' && permission.some(n => n == 'reject')},
? ? ? {label: "拒絕", click: data => {}, filter: ({status}) => status == 'CREATED' && permission.some(n => n == 'refuse')},
? ? ? {label: "重新付款", click: data => {}, filter: ({status}) => status == 'REAPPLY' && permission.some(n => n == 'reApply')},
? ? ]},
? ]
? const dataSource = [
? ? {key: 1, name: '小壞', age: 20, status: 'CREATED', date: 1596791666000, statistic: [{level: 3, total: 5}, {level: 2, total: 7}, {level: 1, total: 20}, {level: 0, total: 0}], money: 200000000000},
? ? {key: 2, name: 'tnnyang', age: 18, status: 'PASS', date: 1596791666000, statistic: [],? money: 2568912357893},
? ? {key: 3, name: 'xiaohuai', status: 'REJECT', statistic: [], money: 6235871},
? ? {key: 4, name: '陳公子', age: 21, status: 'REAPPLY', date: 1596791666000, statistic: []},
? ]
? const config = {
? ? columns,
? ? dataSource,
? ? hasCheck: true,? //是否顯示表格第一列的checkbox復(fù)選框
? ? getCheckboxProps: record => {return {disabled: record.status == 'REJECT'}}? //table表格rowSelection的禁用
? }
? //點(diǎn)擊獲取通過checkbox復(fù)選框選中的表格
? const childRef = useRef();
? const getTableChecked = () => {
? ? const selectedRowKeys = childRef.current.getSelectedRowKeys(), selectedRows = childRef.current.getSelectedRows();
? ? console.log(selectedRowKeys)
? ? console.log(selectedRows)
? }
? return <div>
? ? <Table {...config} cRef={childRef} />
? ? <Button type="primary" onClick={getTableChecked}>獲取選擇的列表項(xiàng)</Button>
? </div>
}
export default TableDemo
最后再貼一下本次封裝所用到的各個(gè)包的版本:
react: 16.8.6,
react-dom: 16.8.6,
react-router-dom: 5.0.0,
antd: 4.3.5,
@babel/core: 7.4.4,
babel-loader: 8.0.5
其實(shí)最主要的是react和antd的版本掌挚,其中antd4和antd3在table組件上的差異還是很大的雨席,在其他組件上的差異也是很大的。
------------------------------------ 分割線 -----------------------------------
有大佬在我另一篇博文封裝react antd的form表單組件的評(píng)論區(qū)提出關(guān)于合并表格的單元格功能等吠式,我一想陡厘,合并單元格也是我們?cè)谑褂帽砀窠M件時(shí)可能會(huì)經(jīng)常遇到的需求,那么就在之前封裝的基礎(chǔ)上加上合并單元格的實(shí)現(xiàn)吧奇徒。
在看了antd的表格組件的實(shí)例后覺得合并單元格其實(shí)也很簡單雏亚,是基于單元格的值和索引來實(shí)現(xiàn)的,那么在我們之前封裝的基礎(chǔ)上再套一層合并單元格的函數(shù)就可以了摩钙。
帶有合并單元格功能的table封裝組件的完整代碼如下:
import React, { createElement, useState, useImperativeHandle } from 'react'
import PropTypes from 'prop-types'
import { Link } from "react-router-dom"
import { Table } from 'antd';
import { timestampToTime, currency } from '@/utils'
const h = createElement;
const TableComp = ({columns, dataSource, hasCheck, cRef, getCheckboxProps, bordered}) => {
? const empty = '-',
? ? [selectedRowKeys, setSelectedRowKeys ] = useState([]),
? ? [selectedRows, setSelectedRows] = useState([]),
? ? render = {
? ? ? Default: v => v,
? ? ? Enum: (v, {Enum}) => {
? ? ? ? if(!Enum[v]) return empty
? ? ? ? return Enum[v]
? ? ? },
? ? ? Format: (v, {format}, row) => format(v, row),
? ? ? Currency: v => currency(v),
? ? ? Date: v => timestampToTime(v, 'second'),
? ? ? Link: (v, {url}) => <Link to={url}>{v}</Link>,
? ? ? Action: (v, {value}, row) => {
? ? ? ? const result = value.filter(n => {
? ? ? ? ? const {filter = () => true} = n
? ? ? ? ? return filter(row)
? ? ? ? })
? ? ? ? return result.map(t => <span className="table-link" onClick={() => t.click(row)} key={t.label}>{t.label}</span>)
? ? ? },
? ? }
? columns = columns.map(n => {
? ? const { type = 'Default', merge } = n;
? ? return {...n, render: (v, row, index) => (!v || v.length < 1) && type != 'Action' ? empty : merge && typeof merge == 'function' ? merge(render[type](v, n, row), index) : render[type](v, n, row)}
? })
? //父組件獲取selectedRowKeys的方法-cRef就是父組件傳過來的ref
? useImperativeHandle(cRef, () => ({
? ? //getSelectedRowKeys就是暴露給父組件的方法
? ? getSelectedRowKeys: () => selectedRowKeys,
? ? getSelectedRows: () => selectedRows
? }));
? const onSelectChange = (selectedRowKeys, selectedRows) => {
? ? setSelectedRowKeys(selectedRowKeys);
? ? setSelectedRows(selectedRows);
? }
? const rowSelection = {
? ? selectedRowKeys,
? ? onChange: onSelectChange,
? ? getCheckboxProps: record => getCheckboxProps(record)
? };
? return hasCheck ? h(Table, {columns, dataSource, rowSelection, bordered}) : h(Table, {columns, dataSource, bordered})
}
Components.propTypes = {
? columns: PropTypes.array.isRequired,? ? //表格頭部
? dataSource: PropTypes.array.isRequired,? //表格數(shù)據(jù)
? onChange: PropTypes.func,? ? ? ? ? ? ? ? //表格翻頁函數(shù)
? hasCheck: PropTypes.bool,? ? ? ? ? ? ? ? //表格行是否可選擇
? cRef: PropTypes.object,? ? ? ? ? ? ? ? ? //父組件傳過來的獲取組件實(shí)例對(duì)象或者是DOM對(duì)象
? getCheckboxProps: PropTypes.func,? ? ? ? //選擇框的默認(rèn)屬性配置
}
export default TableComp
使用方法完整代碼:
import React, { useRef } from 'react'
import { Button } from 'antd'
import Table from './mergeTable'
import './demo.css'
const TableDemo = () => {
? const permission = ["handle", "pass", "refuse", "reApply", 'export'], Enum = {
? ? CREATED: '代辦理',
? ? PASS: '待審批',
? ? REJECT: '駁回',
? ? REFUSE: '拒絕',
? };
? const statisticFormat = val => val.map((t, idx) => <span key={idx} style={{marginRight: '5px'}}>{t.total}</span>)
? const columns = [
? ? { title: '姓名', dataIndex: 'name', key: 'name', type: 'Link', url: 'https://www.baidu.com' },
? ? { title: '身份證號(hào)/年齡', dataIndex: 'ID', key: 'ID', colSpan: 2, },
? ? { title: '年齡', dataIndex: 'age', key: 'age', colSpan: 0, },
? ? { title: '狀態(tài)', dataIndex: 'status', key: 'status', type: 'Enum', Enum, merge: (v, index) => {
? ? ? const obj = {
? ? ? ? children: v,
? ? ? ? props: {},
? ? ? };
? ? ? if (index != 2) {
? ? ? ? return v;
? ? ? }
? ? ? //從第二行開始合并罢低,合并兩列 - index從0開始,colSpan代表合并行。
? ? ? if (index === 2) {
? ? ? ? obj.props.colSpan = 2;
? ? ? }
? ? ? return obj;
? ? }},
? ? { title: '規(guī)則', dataIndex: 'rule', key: 'rule', merge: (v, index) => {
? ? ? const obj = {
? ? ? ? children: v,
? ? ? ? props: {},
? ? ? };
? ? ? //從第二行開始合并网持,合并兩列 - index從0開始宜岛,colSpan代表合并行。
? ? ? if (index === 2) {
? ? ? ? obj.props.colSpan = 0;
? ? ? }
? ? ? return obj;
? ? }},
? ? { title: '預(yù)警統(tǒng)計(jì)', dataIndex: 'statistic', key: 'statistic', type: 'Format', format: statisticFormat },
? ? { title: '存款', dataIndex: 'money', key: 'money', type: 'Currency', merge: (v, index) => {
? ? ? const obj = {
? ? ? ? children: v,
? ? ? ? props: {},
? ? ? };
? ? ? //從第二行開始合并功舀,合并兩行 - index從0開始萍倡,rowSpan代表合并列,rowSpan = 2代表合并兩列辟汰,即將存款列的第二行和第三行合并
? ? ? if (index === 1) {
? ? ? ? obj.props.rowSpan = 2;
? ? ? }
? ? ? //colSpan或者rowSpan設(shè)值為0時(shí)列敲,設(shè)置的表格不會(huì)渲染,即表格數(shù)據(jù)不會(huì)展示出來帖汞。這段代碼的意思是第三行的數(shù)據(jù)不展示并將第三行合并到第二行
? ? ? if (index === 2) {
? ? ? ? obj.props.rowSpan = 0;
? ? ? }
? ? ? return obj;
? ? }},
? ? { title: '日期', dataIndex: 'date', key: 'date', type: 'Date'},
? ? { title: '操作', dataIndex: 'action', key: 'action', type: 'Action', value: [
? ? ? {label: "查看", click: data => {console.log(data)}},
? ? ? {label: "辦理", click: data => {}, filter: ({status}) => status == 'CREATED' && permission.some(n => n == 'handle')},
? ? ? {label: "通過", click: data => {}, filter: ({status}) => status == 'PASS' && permission.some(n => n == 'pass')},
? ? ? {label: "駁回", click: data => {}, filter: ({status}) => status == 'REJECT' && permission.some(n => n == 'reject')},
? ? ? {label: "拒絕", click: data => {}, filter: ({status}) => status == 'CREATED' && permission.some(n => n == 'refuse')},
? ? ? {label: "重新付款", click: data => {}, filter: ({status}) => status == 'REAPPLY' && permission.some(n => n == 'reApply')},
? ? ]},
? ]
? const dataSource = [
? ? {key: 1, name: '小壞', ID: 'abc', age: 20, status: 'CREATED', rule: '標(biāo)準(zhǔn)', date: 1596791666000, statistic: [{level: 3, total: 5}, {level: 2, total: 7}, {level: 1, total: 20}, {level: 0, total: 0}], money: 200000000000},
? ? {key: 2, name: 'tnnyang', ID: 'def', age: 18, status: 'PASS', rule: '個(gè)性化', date: 1596791666000, statistic: [],? money: 2568912357893},
? ? {key: 3, name: 'xiaohuai', ID: 'ghi', status: 'REJECT', rule: '標(biāo)準(zhǔn)', statistic: [], money: 6235871},
? ? {key: 4, name: '陳公子', ID: 'jkl', age: 21, status: 'REAPPLY', rule: '個(gè)性化', date: 1596791666000, statistic: []},
? ]
? const config = {
? ? columns,
? ? dataSource,
? ? getCheckboxProps: record => {return {disabled: record.status == 'REJECT'}},? //table表格rowSelection的禁用
? ? bordered: true,
? }
? //點(diǎn)擊獲取通過checkbox復(fù)選框選中的表格
? const childRef = useRef();
? const getTableChecked = () => {
? ? const selectedRowKeys = childRef.current.getSelectedRowKeys(), selectedRows = childRef.current.getSelectedRows();
? ? console.log(selectedRowKeys)
? ? console.log(selectedRows)
? }
? return <div style={{margin: '20px'}}>
? ? <Table {...config} cRef={childRef} />
? ? <Button type="primary" onClick={getTableChecked}>獲取選擇的列表項(xiàng)</Button>
? </div>
}
export default TableDemo
轉(zhuǎn)自:https://www.cnblogs.com/tnnyang/p/13491868.html