Ant Design 结合 React 参考 Vben 逻辑实现的描述列表组件封装实践

前言

在现代前端开发中,组件化是提高代码复用性和维护性的关键。本文将介绍如何基于 Ant Design 的 Descriptions 组件,结合 React 和 TypeScript,参考 Vben Admin 的设计思想,封装一个功能完善、灵活易用的描述列表组件。

组件设计思路

需求分析

描述列表(Description List)通常用于展示对象的详细信息,如用户资料、订单详情等。一个高质量的描述列表组件需要具备以下特性:

  • 支持动态配置描述项

  • 支持自定义样式

  • 支持条件渲染

  • 提供实例方法用于动态更新

  • 完善的类型定义

技术选型

  • UI 基础:Ant Design 的 Descriptions 组件

  • 框架:React 18

  • 类型系统:TypeScript

  • 设计参考:Vben Admin 的组件封装思想

核心实现

1. 类型定义(typing.ts)

首先定义组件所需的类型,确保类型安全:

import type { ReactNode } from 'react';
import type { DescriptionsProps } from 'antd';

// 描述项配置接口
export interface DescItem {
  labelMinWidth?: number;
  contentMinWidth?: number;
  labelStyle?: React.CSSProperties;
  field: string;
  label: ReactNode;
  span?: number;
  show?: (data: Record<string, any>) => boolean;
  render?: (value: any, data: Record<string, any>) => ReactNode;
}

// 组件属性接口
export interface DescriptionProps extends AntDescriptionsProps {
  schema?: DescItem[];
  data?: Record<string, any>;
  column?: number;
}

// 组件实例接口
export interface DescInstance {
  setDescProps: (props: Partial<DescriptionProps>) => void;
}

2. 组件封装(Description.tsx)

使用 forwardRef 转发 ref,结合 useState 管理动态 props,通过 useImperativeHandle 暴露实例方法:

 

import React, { useRef, useState, useImperativeHandle, forwardRef } from "react";
import { Descriptions } from "antd";
import type { DescriptionProps, DescInstance, DescItem } from "./typing";

const Description = forwardRef<DescInstance, DescriptionProps>((props, ref) => {
  const [innerProps, setInnerProps] = useState<DescriptionProps>(props);
  const mergedProps = { ...innerProps, ...props };
  const {
    schema,
    data,
    column = 2,
    bordered = true,
    contentStyle,
    labelStyle,
    size = "default",
    ...descriptionsProps
  } = mergedProps;

  useImperativeHandle(
    ref,
    () => ({
      setDescProps: (newProps) => {
        setInnerProps((prev) => ({ ...prev, ...newProps }));
      },
    }),
    []
  );

  const renderLabel = ({ label, labelMinWidth }: DescItem) => {
    if (!labelMinWidth || !labelStyle) return label;
    return <div style={{ minWidth: `${labelMinWidth}px`, ...labelStyle }}>{label}</div>;
  };

  const renderDescriptionItem = (item: DescItem) => {
    const { label, field, span, show, render, contentMinWidth } = item;
    const value = data ? data[field] : undefined;

    if (show && !show(data)) return null;

    const getContent = () => render ? render(value, data) : value;

    return (
      <Descriptions.Item
        key={field}
        label={renderLabel(item)}
        span={span}
        styles={{ label: { ...labelStyle }, content: { ...contentStyle } }}
      >
        {contentMinWidth ? <div style={{ minWidth: `${contentMinWidth}px` }}>{getContent()}</div> : getContent()}
      </Descriptions.Item>
    );
  };

  return (
    <Descriptions
      column={column}
      bordered={bordered}
      size={size}
      {...descriptionsProps}
    >
      {schema?.map((item) => renderDescriptionItem(item))}
    </Descriptions>
  );
});

Description.displayName = "Description";
export default Description;

3. 自定义 Hook(useDescription.ts)

封装自定义 Hook 用于管理组件实例和状态更新:

 

import { useRef, useEffect } from 'react';
import type { MutableRefObject } from 'react';
import type { DescriptionProps, DescInstance } from './typing';

export function useDescription(initialProps?: Partial<DescriptionProps>) {
  const descRef = useRef<DescInstance | null>(null);
  const isLoaded = useRef(false);
  const timerRef = useRef<NodeJS.Timeout | null>(null);

  const register = (instance: DescInstance | null) => {
    if (instance) {
      descRef.current = instance;
      isLoaded.current = true;
      if (initialProps) instance.setDescProps(initialProps);
    } else {
      descRef.current = null;
      isLoaded.current = false;
      if (timerRef.current) {
        clearTimeout(timerRef.current);
        timerRef.current = null;
      }
    }
  };

  const setDescProps = (props: Partial<DescriptionProps>) => {
    if (!isLoaded.current) return;

    if (descRef.current) {
      descRef.current.setDescProps(props);
    } else if (!timerRef.current) {
      timerRef.current = setTimeout(() => {
        setDescProps(props);
        timerRef.current = null;
      }, 100);
    }
  };

  useEffect(() => {
    return () => {
      if (timerRef.current) clearTimeout(timerRef.current);
      isLoaded.current = false;
      descRef.current = null;
    };
  }, []);

  return {
    register,
    setDescProps,
    descRef: descRef as MutableRefObject<DescInstance>
  };
}

4. 组件导出(index.ts)

规范组件导出:

import Description from './src/Description.tsx';
​export {  Description  }

功能特性

  1. 动态配置:通过 schema 定义描述项,支持自定义标签、内容、样式

  2. 数据驱动:通过 data 属性动态渲染内容

  3. 条件渲染:支持 show 函数控制描述项显示/隐藏

  4. 自定义渲染:支持 render 函数自定义内容展示

  5. 实例方法:通过 useDescription hook 获取实例,调用 setDescProps 动态更新

  6. 类型安全:完善的 TypeScript 类型定义

使用示例

import { Description } from './components/Descrption';
import { useDescription } from './components/Descrption/src/useDescription';

const App = () => {
   const { register } = useDescription({
    schema: detailSchema,
    data,
    title: "基本信息",
    column: 2,
    labelStyle: { minWidth: "250px" },
    contentStyle: { minWidth: "300px" },
  });

  const schema = [
    { label: '姓名', field: 'name', labelMinWidth: 100 },
    { label: '年龄', field: 'age' },
    { label: '邮箱', field: 'email', render: (value) => <a href={`mailto:${value}`}>{value}</a> },
    { label: '地址', field: 'address', span: 2, show: (data) => data.address },
  ];

  const data = {
    name: '张三',
    age: 28,
    email: 'zhangsan@example.com',
    address: '北京市海淀区'
  };

  return (
    <Description
      ref={register}
    />
  );
};

总结

本文介绍了如何基于 Ant Design 和 React,参考 Vben Admin 的设计思想,封装一个功能完善的描述列表组件。通过 TypeScript 类型定义确保类型安全,使用 React hooks 管理状态和实例,支持动态配置和自定义渲染,满足各种复杂场景的需求。这种组件封装方式不仅提高了代码复用性,也便于维护和扩展,是现代前端开发中的最佳实践之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值