React+Ts实现二次封装组件

前言

在react中相信大家用的最多的组件库就是Antd了,可是往往在公司的实际开发过程中,我们会发现ui给的设计图和组件有着不小的差别,不管是在样式上还是功能上。并且可能存在根据项目主题定制的风格,那我们最好是不要一个个的去使用antd里的组件,然后再去修改其内部样式和功能扩展或修改,这样不管是从效率还是维护性都不是理想的。那如何优雅的对现有的组件库进行二次封装就是我们今天探讨的主要内容了,以下做法有不足之处望各位大佬不吝赐教。

ps:项目演示采用 react18+umi4+antd5+ts

样式

若是确定了组件在项目中的整体样式,可以写在全局样式中,做一个覆盖效果。
以下的overrides.less 是Umi中专门为修改组件样式定制的,我们通过增加id选择器的方式加强权重来进行覆盖默认样式的效果。

-1

若是有某些样式不一致的地方,我们可以直接到组件里再进行一次覆盖,如下所示:
同理只是又借用了组件的id进行加强权重

-2

这样就避免了 !import的普遍存在了

类型扩展

我们可能会遇到组件依赖外部类型来决定内部类型的情况,就比如表格组件中列表的数据类型肯定是不一样的,那我们就需要通过泛型让外面传递进来。从而达到规范的效果。

  1. import type { PaginationProps } from ‘antd’
  2. import { Table } from ‘antd’
  3. import type { TableProps } from ‘antd/es/table’
  4. import {
  5.      FilterValue,
  6.      RowSelectMethod,
  7.      SorterResult,
  8.      TableCurrentDataSource,
  9.      TablePaginationConfig,
  10.      TableRowSelection,
  11. } from ‘antd/es/table/interface’
  12. import { ForwardedRef, forwardRef, Key, useCallback, useImperativeHandle, useMemo, useState } from ‘react’
  13. import styles from ‘./index.less’
  14.  
  15. // 处理forwardRef使其可以接受泛型
  16. declare module ‘react’ {
  17.      // eslint-disable-next-line @typescript-eslint/ban-types
  18.      function forwardRef<T, P = {}>(
  19.      render: (props: P, ref: React.Ref<T>) => React.ReactElement | null
  20.      ): (props: P & React.RefAttributes<T>) => React.ReactElement | null
  21. }
  22.  
  23. export type onChangeType<RecordType> = (
  24.      pagination: TablePaginationConfig,
  25.      filters: Record<string, FilterValue | null>,
  26.      sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
  27.      extra: TableCurrentDataSource<RecordType>
  28. ) => void
  29.  
  30. export type onSelectChangeType<T> = (
  31.      selectedRowKeys: Key[],
  32.      selectedRows: T[],
  33.      info: {
  34.      type: RowSelectMethod
  35.      }
  36. ) => void
  37.  
  38. interface TProps<T> extends React.PropsWithChildren<TableProps<T>> {
  39.      tableData?: pagingResProps<T> // 总数据
  40.      tableOnChange?: onChangeType<T> // 变化回调
  41.      tableRowSelection?: TableRowSelection<T> // 自定义行数据设置
  42.      tablePagination?: TablePaginationConfig //自定义分页配置
  43.      bottomTitleFlag?: boolean // 左下页角信息是否显示
  44. }
  45.  
  46. // table ref的参数
  47. export interface TableDefaultRefProps<T> {
  48.      selectedRowKeys: Key[]
  49.      selectedRowRows: T[]
  50. }
  51.  
  52. const _TableDefault = <extends object>(props: TProps<T>, ref: ForwardedRef<TableDefaultRefProps<T>>) => {
  53.      return <div>table</div>
  54. }
  55.  
  56. const TableDefault = forwardRef(_TableDefault)
  57. export default TableDefault

我们使用时就可以直接传入类型

-3

功能扩展 继承 修改 拦截

我们想要扩展组件内的结构,可以自定义字段来控制显示,如下的bottomTitleFlag,就是控制标题的展示。

我们可以通过…otherProps的方式接收剩余参数,从而实现了可以在我们的组件上传递antd规定的组件属性,若是相同则会进行覆盖采用新传入的。若是我们不传递则采用内置的,若是内置的还不满足需求,我们可以在内置里再加上剩余参数的写法进行补充如 …tablePagination。

事件需要拦截可以内置一个事件,然后通过调用内置事件时进行数据相关处理后再去调传入的事件,这样就实现拦截的效果了。

另外对于外界可能会用的的一些参数我们可以通过 useImperativeHandle 进行Ref抛出,使得更好去获取内部的属性。

  1. const _TableDefault = <extends object>(props: TProps<T>, ref: ForwardedRef<TableDefaultRefProps<T>>) => {
  2.      const { tableData, tableOnChange, tableRowSelection, tablePagination, bottomTitleFlag = false, otherProps } = props
  3.  
  4.      // 当前选择的key和行
  5.      const [selectedRowKeys, setSelectedRowKeys] = useState<Key[]>([])
  6.      const [selectedRowRows, setSelectedRowRows] = useState<T[]>([])
  7.  
  8.      // 左右分页样式处理
  9.      const itemRender: PaginationProps[‘itemRender’] = (_, type, originalElement) => {
  10.      if (type === ‘prev’) {
  11.          return <a style={{ color: ‘#CCF2FF’, fontSize: ’14px’ }}>上一页</a>
  12.      }
  13.      if (type === ‘next’) {
  14.          return <a style={{ color: ‘#CCF2FF’, fontSize: ’14px’ }}>下一页</a>
  15.      }
  16.      return originalElement
  17.      }
  18.  
  19.      const handelChange: onChangeType<T> = useCallback((pagination, filters, sorter, extra) => {
  20.      // 想要做的拦截操作
  21.      props.tableOnChange?.(pagination, filters, sorter, extra)
  22.      }, [])
  23.  
  24.      // 选择多选框回调
  25.      const onSelectChange: onSelectChangeType<T> = (selectedRowKeys, selectedRows, info) => {
  26.      setSelectedRowKeys(selectedRowKeys)
  27.      setSelectedRowRows(selectedRows)
  28.      }
  29.  
  30.      // ref抛出变量
  31.      useImperativeHandle(ref, () => ({
  32.      selectedRowKeys,
  33.      selectedRowRows,
  34.      }))
  35.      // 开始页码
  36.      const startCode = useMemo(
  37.      () => () => {
  38.          if (!tableData || !tableData?.current) return 1
  39.          return (tableData?.current  1) * tableData?.size + 1
  40.      },
  41.      [tableData]
  42.      )
  43.      // 结束页码
  44.      const endCode = useMemo(
  45.      () => () => {
  46.          if (!tableData) return 99
  47.          return Math.min(tableData?.total, tableData?.current * tableData?.size)
  48.      },
  49.      [tableData]
  50.      )
  51.      return (
  52.      <div
  53.          className={styles.tableDefault}
  54.          id=“tableDefault”
  55.      >
  56.          <Table
  57.          rowKey=“id”
  58.          dataSource={tableData?.records}
  59.          pagination={{
  60.              itemRender,
  61.              total: tableData?.total,
  62.              showSizeChanger: false,
  63.              pageSize: tableData?.size,
  64.              current: tableData?.current,
  65.              tablePagination,
  66.          }}
  67.          onChange={handelChange}
  68.          rowSelection={{
  69.              type: ‘checkbox’,
  70.              fixed: false,
  71.              columnWidth: ‘120px’,
  72.              selectedRowKeys,
  73.              onChange: onSelectChange,
  74.              tableRowSelection,
  75.          }}
  76.          {…otherProps}
  77.          />
  78.          <div className={styles.leftIcon}></div>
  79.          {bottomTitleFlag &amp;& (
  80.          <div className={styles.bottomTitle}>{`显示第${startCode()}到第${endCode()}条记录,总共${
  81.              tableData?.total
  82.          }条记录`}</div>
  83.          )}
  84.      </div>
  85.      )
  86. }
  87.  
  88. const TableDefault = forwardRef(_TableDefault)
  89. export default TableDefault

我们想要扩展组件内的功能,增加内置功能可以直接在组件内部增加,使用内部数据来完成,最后进行一个抛出。
这里使用 node[fieldNames.key] 计算属性名的原因是树组件的字段可能会发生变化,所以我们需要根据传入的fieldNames来进行字段更新。

树组件

  1. // 当前节点展开
  2.      function nowNodeExpand(node: DataNode) {
  3.      const newExpandedKeys: any[] = []
  4.      const fn = (node: any) => {
  5.          node[fieldNames.key] && newExpandedKeys.push(node[fieldNames.key])
  6.          node[fieldNames.children] && node[fieldNames.children].forEach((item: DataNode) => fn(item))
  7.      }
  8.      fn(node)
  9.      setExpandedKeys(uniq([…expandedKeys, newExpandedKeys]))
  10.      }
  11.      // ref抛出变量
  12.      useImperativeHandle(ref, () => ({
  13.      nowNodeExpand,
  14.      }))

到此这篇关于React+Ts实现二次封装组件的文章就介绍到这了,更多相关React Ts封装组件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

标签

发表评论