React JS Material可折叠表格实现单次仅展开一行需求咨询
实现单行互斥展开的修改方案
核心逻辑:当前每个Row组件独立维护自身的展开状态,组件间没有状态联动,因此无法实现互斥收起。我们只需将展开状态提升到父组件统一管理,仅记录当前处于展开状态的行唯一标识,即可快速实现需求。
具体修改步骤
1. 改造Row组件
移除Row内部的本地open状态,改为从父组件接收展开状态和切换回调函数,修改后的Row代码如下:
import React from 'react'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import Box from '@material-ui/core/Box'; import Collapse from '@material-ui/core/Collapse'; import IconButton from '@material-ui/core/IconButton'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableCell from '@material-ui/core/TableCell'; import TableHead from '@material-ui/core/TableHead'; import TableRow from '@material-ui/core/TableRow'; import Typography from '@material-ui/core/Typography'; import Paper from '@material-ui/core/Paper'; import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; const useRowStyles = makeStyles({ root: { '& > *': { borderBottom: 'unset', }, }, }); function Row(props) { // 从props接收展开状态和切换方法,移除本地state const { row, open, onToggle } = props; const classes = useRowStyles(); return ( <React.Fragment> <TableRow className={classes.root}> <TableCell> <IconButton aria-label="expand row" size="small" onClick={() => onToggle(row.name)} // 点击时把当前行名传给父组件 > {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />} </IconButton> </TableCell> <TableCell component="th" scope="row"> {row.name} </TableCell> <TableCell align="right">{row.calories}</TableCell> <TableCell align="right">{row.fat}</TableCell> <TableCell align="right">{row.carbs}</TableCell> <TableCell align="right">{row.protein}</TableCell> </TableRow> <TableRow> <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={6}> <Collapse in={open} timeout="auto" unmountOnExit> <Box margin={1}> <Typography variant="h6" gutterBottom component="div"> History </Typography> <Table size="small" aria-label="purchases"> <TableHead> <TableRow> <TableCell>Date</TableCell> <TableCell>Customer</TableCell> <TableCell align="right">Amount</TableCell> <TableCell align="right">Total price ($)</TableCell> </TableRow> </TableHead> <TableBody> {row.history.map((historyRow) => ( <TableRow key={historyRow.date}> <TableCell component="th" scope="row"> {historyRow.date} </TableCell> <TableCell>{historyRow.customerId}</TableCell> <TableCell align="right">{historyRow.amount}</TableCell> <TableCell align="right"> {Math.round(historyRow.amount * row.price * 100) / 100} </TableCell> </TableRow> ))} </TableBody> </Table> </Box> </Collapse> </TableCell> </TableRow> </React.Fragment> ); } Row.propTypes = { row: PropTypes.shape({ calories: PropTypes.number.isRequired, carbs: PropTypes.number.isRequired, fat: PropTypes.number.isRequired, history: PropTypes.arrayOf( PropTypes.shape({ amount: PropTypes.number.isRequired, customerId: PropTypes.string.isRequired, date: PropTypes.string.isRequired, }), ).isRequired, name: PropTypes.string.isRequired, price: PropTypes.number.isRequired, protein: PropTypes.number.isRequired, }).isRequired, // 新增prop校验 open: PropTypes.bool.isRequired, onToggle: PropTypes.func.isRequired, }; // createData方法和rows常量可以移到父组件文件,避免冗余 export default Row;
2. 改造CollapsibleTable父组件
新增状态管理当前展开的行标识,统一处理展开/收起逻辑,修改后的代码如下:
import React from 'react'; import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import Paper from '@material-ui/core/Paper'; import Row from './Row'; // 引入改造后的Row组件 // 把原来的createData和rows移到这里 function createData(name, calories, fat, carbs, protein, price) { return { name, calories, fat, carbs, protein, price, history: [ { date: '2020-01-05', customerId: '11091700', amount: 3 }, { date: '2020-01-02', customerId: 'Anonymous', amount: 1 }, ], }; } const rows = [ createData('Frozen yoghurt', 159, 6.0, 24, 4.0, 3.99), createData('Ice cream sandwich', 237, 9.0, 37, 4.3, 4.99), createData('Eclair', 262, 16.0, 24, 6.0, 3.79), createData('Cupcake', 305, 3.7, 67, 4.3, 2.5), createData('Gingerbread', 356, 16.0, 49, 3.9, 1.5), ]; export default function CollapsibleTable() { // 仅存储当前展开的行的name作为唯一标识,null代表没有行展开 const [expandedRowName, setExpandedRowName] = React.useState(null); const handleToggle = (rowName) => { // 如果点击的是当前已展开的行,就收起,否则展开当前行 setExpandedRowName(expandedRowName === rowName ? null : rowName); }; return ( <TableContainer component={Paper}> <Table aria-label="collapsible table"> <TableBody> {rows.map((row) => ( <Row key={row.name} row={row} open={expandedRowName === row.name} // 判断当前行是否是展开状态 onToggle={handleToggle} /> ))} </TableBody> </Table> </TableContainer> ); }
修改完成后即可实现预期效果:点击任意行展开时,其他已展开的行都会自动收起,同一时间最多只有一行处于展开状态,再次点击当前展开的行也可以手动收起。
内容的提问来源于stack exchange,提问作者Debanjan Sinha
相关产品推荐
相关产品推荐

