React+Ts實現(xiàn)二次封裝組件
前言
在react中相信大家用的最多的組件庫就是Antd了,可是往往在公司的實際開發(fā)過程中,我們會發(fā)現(xiàn)ui給的設計圖和組件有著不小的差別,不管是在樣式上還是功能上。并且可能存在根據(jù)項目主題定制的風格,那我們最好是不要一個個的去使用antd里的組件,然后再去修改其內部樣式和功能擴展或修改,這樣不管是從效率還是維護性都不是理想的。那如何優(yōu)雅的對現(xiàn)有的組件庫進行二次封裝就是我們今天探討的主要內容了,以下做法有不足之處望各位大佬不吝賜教。
ps:項目演示采用 react18+umi4+antd5+ts
樣式
若是確定了組件在項目中的整體樣式,可以寫在全局樣式中,做一個覆蓋效果。
以下的overrides.less 是Umi中專門為修改組件樣式定制的,我們通過增加id選擇器的方式加強權重來進行覆蓋默認樣式的效果。

若是有某些樣式不一致的地方,我們可以直接到組件里再進行一次覆蓋,如下所示:
同理只是又借用了組件的id進行加強權重

這樣就避免了 !import的普遍存在了
類型擴展
我們可能會遇到組件依賴外部類型來決定內部類型的情況,就比如表格組件中列表的數(shù)據(jù)類型肯定是不一樣的,那我們就需要通過泛型讓外面?zhèn)鬟f進來。從而達到規(guī)范的效果。
import type { PaginationProps } from 'antd'
import { Table } from 'antd'
import type { TableProps } from 'antd/es/table'
import {
FilterValue,
RowSelectMethod,
SorterResult,
TableCurrentDataSource,
TablePaginationConfig,
TableRowSelection,
} from 'antd/es/table/interface'
import { ForwardedRef, forwardRef, Key, useCallback, useImperativeHandle, useMemo, useState } from 'react'
import styles from './index.less'
// 處理forwardRef使其可以接受泛型
declare module 'react' {
// eslint-disable-next-line @typescript-eslint/ban-types
function forwardRef<T, P = {}>(
render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
): (props: P & React.RefAttributes<T>) => React.ReactElement | null
}
export type onChangeType<RecordType> = (
pagination: TablePaginationConfig,
filters: Record<string, FilterValue | null>,
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
extra: TableCurrentDataSource<RecordType>
) => void
export type onSelectChangeType<T> = (
selectedRowKeys: Key[],
selectedRows: T[],
info: {
type: RowSelectMethod
}
) => void
interface TProps<T> extends React.PropsWithChildren<TableProps<T>> {
tableData?: pagingResProps<T> // 總數(shù)據(jù)
tableOnChange?: onChangeType<T> // 變化回調
tableRowSelection?: TableRowSelection<T> // 自定義行數(shù)據(jù)設置
tablePagination?: TablePaginationConfig //自定義分頁配置
bottomTitleFlag?: boolean // 左下頁角信息是否顯示
}
// table ref的參數(shù)
export interface TableDefaultRefProps<T> {
selectedRowKeys: Key[]
selectedRowRows: T[]
}
const _TableDefault = <T extends object>(props: TProps<T>, ref: ForwardedRef<TableDefaultRefProps<T>>) => {
return <div>table</div>
}
const TableDefault = forwardRef(_TableDefault)
export default TableDefault
我們使用時就可以直接傳入類型

功能擴展 繼承 修改 攔截
我們想要擴展組件內的結構,可以自定義字段來控制顯示,如下的bottomTitleFlag,就是控制標題的展示。
我們可以通過...otherProps的方式接收剩余參數(shù),從而實現(xiàn)了可以在我們的組件上傳遞antd規(guī)定的組件屬性,若是相同則會進行覆蓋采用新傳入的。若是我們不傳遞則采用內置的,若是內置的還不滿足需求,我們可以在內置里再加上剩余參數(shù)的寫法進行補充如 ...tablePagination。
事件需要攔截可以內置一個事件,然后通過調用內置事件時進行數(shù)據(jù)相關處理后再去調傳入的事件,這樣就實現(xiàn)攔截的效果了。
另外對于外界可能會用的的一些參數(shù)我們可以通過 useImperativeHandle 進行Ref拋出,使得更好去獲取內部的屬性。
const _TableDefault = <T extends object>(props: TProps<T>, ref: ForwardedRef<TableDefaultRefProps<T>>) => {
const { tableData, tableOnChange, tableRowSelection, tablePagination, bottomTitleFlag = false, ...otherProps } = props
// 當前選擇的key和行
const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([])
const [selectedRowRows, setSelectedRowRows] = useState<T[]>([])
// 左右分頁樣式處理
const itemRender: PaginationProps['itemRender'] = (_, type, originalElement) => {
if (type === 'prev') {
return <a style={{ color: '#CCF2FF', fontSize: '14px' }}>上一頁</a>
}
if (type === 'next') {
return <a style={{ color: '#CCF2FF', fontSize: '14px' }}>下一頁</a>
}
return originalElement
}
const handelChange: onChangeType<T> = useCallback((pagination, filters, sorter, extra) => {
// 想要做的攔截操作
props.tableOnChange?.(pagination, filters, sorter, extra)
}, [])
// 選擇多選框回調
const onSelectChange: onSelectChangeType<T> = (selectedRowKeys, selectedRows, info) => {
setSelectedRowKeys(selectedRowKeys)
setSelectedRowRows(selectedRows)
}
// ref拋出變量
useImperativeHandle(ref, () => ({
selectedRowKeys,
selectedRowRows,
}))
// 開始頁碼
const startCode = useMemo(
() => () => {
if (!tableData || !tableData?.current) return 1
return (tableData?.current - 1) * tableData?.size + 1
},
[tableData]
)
// 結束頁碼
const endCode = useMemo(
() => () => {
if (!tableData) return 99
return Math.min(tableData?.total, tableData?.current * tableData?.size)
},
[tableData]
)
return (
<div
className={styles.tableDefault}
id="tableDefault"
>
<Table
rowKey="id"
dataSource={tableData?.records}
pagination={{
itemRender,
total: tableData?.total,
showSizeChanger: false,
pageSize: tableData?.size,
current: tableData?.current,
...tablePagination,
}}
onChange={handelChange}
rowSelection={{
type: 'checkbox',
fixed: false,
columnWidth: '120px',
selectedRowKeys,
onChange: onSelectChange,
...tableRowSelection,
}}
{...otherProps}
/>
<div className={styles.leftIcon}></div>
{bottomTitleFlag && (
<div className={styles.bottomTitle}>{`顯示第${startCode()}到第${endCode()}條記錄,總共${
tableData?.total
}條記錄`}</div>
)}
</div>
)
}
const TableDefault = forwardRef(_TableDefault)
export default TableDefault我們想要擴展組件內的功能,增加內置功能可以直接在組件內部增加,使用內部數(shù)據(jù)來完成,最后進行一個拋出。
這里使用 node[fieldNames.key] 計算屬性名的原因是樹組件的字段可能會發(fā)生變化,所以我們需要根據(jù)傳入的fieldNames來進行字段更新。
樹組件
// 當前節(jié)點展開
function nowNodeExpand(node: DataNode) {
const newExpandedKeys: any[] = []
const fn = (node: any) => {
node[fieldNames.key] && newExpandedKeys.push(node[fieldNames.key])
node[fieldNames.children] && node[fieldNames.children].forEach((item: DataNode) => fn(item))
}
fn(node)
setExpandedKeys(uniq([...expandedKeys, ...newExpandedKeys]))
}
// ref拋出變量
useImperativeHandle(ref, () => ({
nowNodeExpand,
}))到此這篇關于React+Ts實現(xiàn)二次封裝組件的文章就介紹到這了,更多相關React Ts封裝組件內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
react antd-design Select全選功能實例
這篇文章主要介紹了react antd-design Select全選功能實例,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03

