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

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) => boolean; render?: (value: any, data: Record) => ReactNode;}// 组件属性接口export interface DescriptionProps extends AntDescriptionsProps { schema?: DescItem[]; data?: Record; column?: number;}// 组件实例接口export interface DescInstance { setDescProps: (props: Partial) => 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((props, ref) => { const [innerProps, setInnerProps] = useState(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}
; }; 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()}
: getContent()} ); }; return ( {schema?.map((item) => renderDescriptionItem(item))} );});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) { const descRef = useRef(null); const isLoaded = useRef(false); const timerRef = useRef(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) => { 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 };}

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) => {value} }, { label: \'地址\', field: \'address\', span: 2, show: (data) => data.address }, ]; const data = { name: \'张三\', age: 28, email: \'zhangsan@example.com\', address: \'北京市海淀区\' }; return (  );};

总结

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