2025年07月23日秋瑶传媒一面

发布于:2025-07-31 ⋅ 阅读:(18) ⋅ 点赞:(0)

目录

  1. react 如何封装一个 table 组件
  2. react 父子组件传值
  3. 讲一下 umi
  4. umi 的状态管理的使用
  5. 介绍下你的项目难点
  6. 讲一下你们小程序是怎么做的
  7. tooltip 组件怎么自定义判断 2 行 3 行的时候展示
  8. umi 怎么动态加载 css js
  9. umi 怎么使用状态管理

1. react 如何封装一个 table 组件

我将为你提供一个封装 Ant Design Table 组件的方案,使其具有类似 Ant Design Pro 的高级功能。这种封装可以提高代码复用性,简化表格使用,并统一项目中的表格风格。

封装思路

我们将创建一个 ProTable 组件,支持以下核心功能:

  • 自动处理分页、排序、筛选
  • 支持搜索表单
  • 集成请求数据的 Hooks
  • 自动生成表格列配置
  • 支持操作栏自定义
  • 统一的样式风格

组件实现

下面是一个完整的 ProTable 组件实现:

import React, { useState, useMemo, useCallback, useEffect } from 'react';
import { Table, Form, Button, Space, Input, Select, Popconfirm } from 'antd';
import { SearchOutlined, ReloadOutlined } from '@ant-design/icons';
import { debounce } from 'lodash';

const { Item } = Form;
const { Option } = Select;

/**
 * ProTable 组件 - 封装 Ant Design Table 提供高级功能
 * @param {Object} props - 组件属性
 * @param {Array} props.columns - 表格列配置
 * @param {Function} props.request - 获取数据的请求函数
 * @param {Object} props.formItems - 搜索表单配置
 * @param {Object} props.initialValues - 初始表单值
 * @param {Boolean} props.loading - 加载状态
 * @param {Function} props.onRow - 行点击事件
 * @param {Function} props.rowKey - 行键
 * @param {Object} props.tableProps - 传递给 Table 的其他属性
 */
const ProTable = ({
  columns = [],
  request,
  formItems = [],
  initialValues = {},
  loading = false,
  onRow,
  rowKey = 'id',
  tableProps = {},
}) => {
  const [form] = Form.useForm();
  const [dataSource, setDataSource] = useState([]);
  const [pagination, setPagination] = useState({
    current: 1,
    pageSize: 10,
    total: 0,
  });
  const [sorter, setSorter] = useState({});
  const [filters, setFilters] = useState({});

  // 处理搜索表单值变化
  const handleFormValuesChange = useCallback((changedValues, allValues) => {
    // 重置分页
    setPagination(prev => ({
      ...prev,
      current: 1,
    }));
    // 更新筛选条件
    setFilters(allValues);
  }, []);

  // 处理分页变化
  const handleTableChange = useCallback((pagination, filters, sorter) => {
    setPagination(pagination);
    setFilters(filters);
    setSorter(sorter);
  }, []);

  // 构建搜索表单
  const renderFormItems = useMemo(() => {
    return formItems.map(item => {
      const { key, title, type = 'input', options = [], ...rest } = item;
      
      switch (type) {
        case 'select':
          return (
            <Item key={key} label={title} name={key} {...rest}>
              <Select placeholder={`请选择${title}`}>
                {options.map(option => (
                  <Option key={option.value} value={option.value}>{option.label}</Option>
                ))}
              </Select>
            </Item>
          );
        case 'input':
        default:
          return (
            <Item key={key} label={title} name={key} {...rest}>
              <Input placeholder={`请输入${title}`} />
            </Item>
          );
      }
    });
  }, [formItems]);

  // 处理搜索
  const handleSearch = useCallback(() => {
    // 重置分页
    setPagination(prev => ({
      ...prev,
      current: 1,
    }));
    // 触发数据加载
    fetchData();
  }, [fetchData]);

  // 处理重置
  const handleReset = useCallback(() => {
    form.resetFields();
    // 重置分页和筛选
    setPagination({
      current: 1,
      pageSize: 10,
      total: 0,
    });
    setFilters({});
    setSorter({});
    // 触发数据加载
    fetchData();
  }, [form, fetchData]);

  // 构建表格列配置
  const buildColumns = useMemo(() => {
    return columns.map(column => {
      // 如果有操作列,添加固定位置
      if (column.key === 'actions') {
        return {
          ...column,
          fixed: 'right',
          width: 180,
        };
      }
      return column;
    });
  }, [columns]);

  // 合并表格属性
  const mergedTableProps = useMemo(() => {
    return {
      rowKey,
      dataSource,
      pagination,
      loading,
      onChange: handleTableChange,
      onRow,
      columns: buildColumns,
      ...tableProps,
    };
  }, [dataSource, pagination, loading, handleTableChange, onRow, buildColumns, tableProps, rowKey]);

  // 数据加载
  const fetchData = useCallback(async () => {
    if (!request) return;
    
    try {
      const { current, pageSize } = pagination;
      const { field, order } = sorter;
      
      const response = await request({
        page: current,
        pageSize,
        sortField: field,
        sortOrder: order === 'ascend' ? 'asc' : 'desc',
        ...filters,
      });
      
      setDataSource(response.data || []);
      setPagination(prev => ({
        ...prev,
        total: response.total || 0,
      }));
    } catch (error) {
      console.error('Failed to fetch data:', error);
    }
  }, [request, pagination, sorter, filters]);

  // 初始化数据
  useEffect(() => {
    form.setFieldsValue(initialValues);
    fetchData();
  }, [form, initialValues, fetchData]);

  // 防抖搜索
  const debouncedSearch = useCallback(
    debounce(handleSearch, 500),
    [handleSearch]
  );

  return (
    <div className="pro-table-container">
      {formItems.length > 0 && (
        <Form
          form={form}
          layout="inline"
          onValuesChange={handleFormValuesChange}
          initialValues={initialValues}
        >
          <div className="pro-table-search-bar">
            <Space>
              {renderFormItems}
              <Item>
                <Button type="primary" onClick={handleSearch} icon={<SearchOutlined />}>
                  搜索
                </Button>
              </Item>
              <Item>
                <Button onClick={handleReset}>重置</Button>
              </Item>
            </Space>
          </div>
        </Form>
      )}
      
      <Table {...mergedTableProps} />
    </div>
  );
};

export default ProTable;

使用示例

下面是如何使用这个封装组件的示例:

import React from 'react';
import ProTable from './ProTable';

// 模拟API请求
const fetchTableData = async ({ page, pageSize, sortField, sortOrder, ...filters }) => {
  // 这里应该是实际的API调用
  console.log('Fetching data with params:', { page, pageSize, sortField, sortOrder, filters });
  
  // 模拟返回数据
  return new Promise(resolve => {
    setTimeout(() => {
      resolve({
        data: [
          { id: 1, name: '张三', age: 28, address: '北京市朝阳区', status: 'active' },
          { id: 2, name: '李四', age: 32, address: '上海市浦东新区', status: 'inactive' },
          { id: 3, name: '王五', age: 45, address: '广州市天河区', status: 'active' },
        ],
        total: 100,
      });
    }, 800);
  });
};

// 表格列配置
const columns = [
  {
    title: 'ID',
    dataIndex: 'id',
    key: 'id',
    sorter: true,
  },
  {
    title: '姓名',
    dataIndex: 'name',
    key: 'name',
    sorter: true,
  },
  {
    title: '年龄',
    dataIndex: 'age',
    key: 'age',
    sorter: true,
  },
  {
    title: '地址',
    dataIndex: 'address',
    key: 'address',
  },
  {
    title: '状态',
    dataIndex: 'status',
    key: 'status',
    filters: [
      { text: '活跃', value: 'active' },
      { text: '非活跃', value: 'inactive' },
    ],
    onFilter: (value, record) => record.status === value,
  },
  {
    title: '操作',
    key: 'actions',
    render: (_, record) => (
      <Space size="small">
        <a href="#">查看</a>
        <a href="#">编辑</a>
        <Popconfirm title="确定要删除吗?" onConfirm={() => console.log('Delete:', record.id)}>
          <a href="#">删除</a>
        </Popconfirm>
      </Space>
    ),
  },
];

// 搜索表单配置
const formItems = [
  {
    key: 'name',
    title: '姓名',
  },
  {
    key: 'status',
    title: '状态',
    type: 'select',
    options: [
      { label: '全部', value: '' },
      { label: '活跃', value: 'active' },
      { label: '非活跃', value: 'inactive' },
    ],
  },
];

// 页面组件
const MyTablePage = () => {
  return (
    <div className="page-container">
      <h1>用户列表</h1>
      <ProTable
        columns={columns}
        request={fetchTableData}
        formItems={formItems}
        initialValues={{ status: 'active' }}
      />
    </div>
  );
};

export default MyTablePage;

样式优化(可选)

添加一些全局样式可以使表格看起来更像 Ant Design Pro:

/* pro-table.css */
.pro-table-container {
  background-color: #fff;
  border-radius: 8px;
  padding: 20px;
  margin-bottom: 20px;
  box-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.16), 0 3px 6px 0 rgba(0, 0, 0, 0.12), 0 5px 12px 4px rgba(0, 0, 0, 0.09);
}

.pro-table-search-bar {
  margin-bottom: 16px;
  padding-bottom: 16px;
  border-bottom: 1px solid #f0f0f0;
}

/* 表格样式优化 */
.ant-table-thead > tr > th {
  background-color: #fafafa;
  font-weight: 600;
}

.ant-table-tbody > tr:hover > td {
  background-color: #f5f7fa !important;
}

.ant-pagination {
  margin-top: 16px !important;
}

封装优势

  1. 统一风格:所有表格使用相同的样式和交互逻辑
  2. 简化使用:只需提供列配置和数据请求函数
  3. 自动处理分页/排序/筛选:减少重复代码
  4. 可扩展:可以轻松添加新功能(如导出、批量操作)
  5. 易于维护:一处修改,所有表格生效

你可以根据项目需求进一步扩展这个组件,比如添加批量操作、导出功能、行选择等。


网站公告

今日签到

点亮在社区的每一天
去签到