You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在React中封装React Bootstrap Table为可复用的输入与展示表格

封装可复用的健身数据表格组件(React + React Bootstrap)

先理清楚核心逻辑:把两类表格的公共结构、基础样式抽离成通用部分,通过props传递差异化内容(比如录入用的表单控件、展示用的静态数据),再结合自定义CSS隔离样式,避免代码冗余和样式混乱。


1. 先写自定义样式文件(CustomTable.css)

单独维护样式,让组件样式独立,避免和项目其他样式冲突:

/* 通用表格容器样式 */
.custom-fitness-table {
  margin: 2rem 0;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

/* 录入表格的输入框样式 */
.fitness-input-cell input,
.fitness-input-cell select {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid #ced4da;
  border-radius: 4px;
}

/* 展示表格的hover高亮效果 */
.fitness-display-table tbody tr:hover {
  background-color: #f8f9fa;
}

2. 封装健身数据录入表格(FitnessInputTable.jsx)

这个组件只负责渲染录入表单和处理输入交互,所有数据、回调都通过props传入:

import React from 'react';
import { Table, Button } from 'react-bootstrap';
import './CustomTable.css';

const FitnessInputTable = ({ formData, onInputChange, onSubmit }) => {
  // 统一处理输入框变化
  const handleChange = (field, value) => {
    onInputChange({ ...formData, [field]: value });
  };

  return (
    <Table striped bordered hover className="custom-fitness-table fitness-input-table">
      <thead>
        <tr>
          <th>运动项目</th>
          <th>时长(分钟)</th>
          <th>消耗热量(大卡)</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td className="fitness-input-cell">
            <select
              value={formData.activity}
              onChange={(e) => handleChange('activity', e.target.value)}
            >
              <option value="">请选择</option>
              <option value="跑步">跑步</option>
              <option value="力量训练">力量训练</option>
              <option value="瑜伽">瑜伽</option>
            </select>
          </td>
          <td className="fitness-input-cell">
            <input
              type="number"
              value={formData.duration}
              onChange={(e) => handleChange('duration', e.target.value)}
              placeholder="输入时长"
            />
          </td>
          <td className="fitness-input-cell">
            <input
              type="number"
              value={formData.calories}
              onChange={(e) => handleChange('calories', e.target.value)}
              placeholder="输入热量"
            />
          </td>
          <td>
            <Button variant="primary" onClick={onSubmit}>提交记录</Button>
          </td>
        </tr>
      </tbody>
    </Table>
  );
};

export default FitnessInputTable;

3. 封装健身记录展示表格(FitnessDisplayTable.jsx)

这个组件负责渲染已有记录,支持编辑、删除操作,同样通过props接收数据和回调:

import React from 'react';
import { Table, Button } from 'react-bootstrap';
import './CustomTable.css';

const FitnessDisplayTable = ({ records, onDelete, onEdit }) => {
  // 空状态提示
  if (!records.length) {
    return <p className="text-center">暂无健身记录</p>;
  }

  return (
    <Table striped bordered hover className="custom-fitness-table fitness-display-table">
      <thead>
        <tr>
          <th>序号</th>
          <th>运动项目</th>
          <th>时长(分钟)</th>
          <th>消耗热量(大卡)</th>
          <th>记录时间</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        {records.map((record, index) => (
          <tr key={record.id}>
            <td>{index + 1}</td>
            <td>{record.activity}</td>
            <td>{record.duration}</td>
            <td>{record.calories}</td>
            <td>{new Date(record.createdAt).toLocaleString()}</td>
            <td>
              <Button variant="warning" size="sm" onClick={() => onEdit(record)} className="me-2">编辑</Button>
              <Button variant="danger" size="sm" onClick={() => onDelete(record.id)}>删除</Button>
            </td>
          </tr>
        ))}
      </tbody>
    </Table>
  );
};

export default FitnessDisplayTable;

4. 在主组件中调用两个表格(FitnessDashboard.jsx)

主组件负责管理全局状态(表单数据、记录列表),处理提交、编辑、删除的核心逻辑,然后把需要的参数传递给子组件:

import React, { useState } from 'react';
import { Container } from 'react-bootstrap';
import FitnessInputTable from './FitnessInputTable';
import FitnessDisplayTable from './FitnessDisplayTable';

const FitnessDashboard = () => {
  // 表单数据状态
  const [formData, setFormData] = useState({
    activity: '',
    duration: '',
    calories: ''
  });

  // 健身记录列表状态
  const [records, setRecords] = useState([
    // 示例初始数据
    { id: 1, activity: '跑步', duration: 30, calories: 300, createdAt: Date.now() }
  ]);

  // 处理表单输入变化
  const handleInputChange = (newData) => {
    setFormData(newData);
  };

  // 提交新记录
  const handleSubmit = () => {
    if (!formData.activity || !formData.duration || !formData.calories) {
      alert('请填写完整信息');
      return;
    }
    const newRecord = {
      id: Date.now(),
      ...formData,
      createdAt: Date.now()
    };
    setRecords([...records, newRecord]);
    // 重置表单
    setFormData({ activity: '', duration: '', calories: '' });
  };

  // 编辑记录(示例:把数据填充到录入表单)
  const handleEdit = (record) => {
    setFormData({
      activity: record.activity,
      duration: record.duration,
      calories: record.calories
    });
    // 移除原记录,编辑后重新提交
    setRecords(records.filter(r => r.id !== record.id));
  };

  // 删除记录
  const handleDelete = (id) => {
    setRecords(records.filter(record => record.id !== id));
  };

  return (
    <Container className="mt-4">
      <h2>健身数据录入</h2>
      <FitnessInputTable
        formData={formData}
        onInputChange={handleInputChange}
        onSubmit={handleSubmit}
      />

      <h2 className="mt-5">健身记录展示</h2>
      <FitnessDisplayTable
        records={records}
        onEdit={handleEdit}
        onDelete={handleDelete}
      />
    </Container>
  );
};

export default FitnessDashboard;

几点要注意的地方

  • props解耦:所有差异化内容(数据、操作逻辑)都通过props传递,让子组件只负责渲染,核心逻辑放在主组件或状态管理工具(比如Redux)中,方便后续维护。
  • 样式隔离:用专属类名包裹组件样式,避免和项目其他组件样式冲突;如果需要更严格的隔离,可以用CSS Modules或者Styled Components。
  • 可扩展性:后续要添加新字段(比如运动强度),只需要修改录入表格的表单控件、展示表格的表头,主组件的状态对应更新即可,不需要重构整个组件。

内容的提问来源于stack exchange,提问作者rahemanali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 19:31:20