如何在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
相关产品推荐
相关产品推荐

