React-Virtualized table 长到最大高度

IT技术 html css reactjs
2021-05-13 17:55:16

问题:我想要一个React Virtualized table,它可以占据最大的高度空间(比方说500px)。在表格的最后可见行的正下方,我想添加另一个元素。有点像这样:

If the content of the table is shorter than 500px

+-----------------------------------------+    ----+
| Header1 | Header2 | ...                 |        |
+---------+---------+---------------------+        |  less than 500px,
| row1    | row1    | row1                |        |  say 200px
+---------+---------+---------------------+        |
| row2    | row2    | row2                |        |
+---------+---------+---------------------+    ----+
Other content


If the content of the table is longer than 500px

+-----------------------------------------+^   ----+
| Header1 | Header2 | ...                 ||       |
+---------+---------+---------------------+|       |  500px,
| row1    | row1    | row1                ||       |  notice the
+---------+---------+---------------------+|       |  scroll bar
| row2    | row2    | row2                ||       |
+---------+---------+---------------------+|       |
| row3    | row3    | row3                ||       |
+---------+---------+---------------------+|       |
| row4    | row4    | row4                |V   ----+
Other content

换句话说:表格和“其他内容”之间不应有白色间隙,而且表格最多应占用 500px。

问题:我怎样才能做到这一点?

我的尝试:我尝试过AutoSizer,但问题是:

  • 如果AutoSizer包含在div只有固定高度的 a 中(例如:)height: 500px,那么如果表格太短,表格和“其他内容”之间将出现白色间隙;所以我得到的是:

    +-----------------------------------------+    ----+
    | Header1 | Header2 | ...                 |        |
    +---------+---------+---------------------+        |  less than 500px,
    | row1    | row1    | row1                |        |  say 200px
    +---------+---------+---------------------+        |
    | row2    | row2    | row2                |        |
    +---------+---------+---------------------+    ----+
                                                       |
                                                       | gap of        
                                                       | 300px
                                                       |        
                                                       |
    Other content                                  ----+
    
  • 如果AutoSizer被包含在div仅具有最大高度(例如:max-height: 500px),则仅将报头中示出,和“其他内容”将是头部上方(即,含有div不成长为表将增长,并且因此AutoSizer确实没有什么); 我得到的是这样的:

    Other content ----------------------------+
    | Header1 | Header2 | ...                 |
    +---------+---------+---------------------+
    
  • (如果我同时给出最大和固定高度,那么当然固定高度“获胜”,结果与第一种情况相同。)

备注:我完全不相信,这AutoSizer是这里的正确方法,所以我愿意接受任何其他建议(如果可能,没有进一步的第三方依赖,但也可以,如果没有其他方法) .

参考来源:

 <div>
   <div style={{height: "100px", maxHeight: "500px"}}>
      <AutoSizer>
        {({height, width}) => (
          <Table
            ref="Table"
            disableHeader={false}
            headerClassName=""
            headerHeight={40}
            height={height}
            rowClassName=""
            rowHeight={40}
            rowGetter={rowGetter}
            rowCount={6}
            width={width}>
            <Column
                label="Index"
                cellDataGetter={({rowData}) => rowData.index}
                dataKey="index"
                width={60}
            />
            <Column
              label="Text1"
              cellDataGetter={({rowData}) => rowData.text1}
              dataKey="text1"
              width={90}
            />
            <Column
              label="Text2"
              cellDataGetter={({rowData}) => rowData.text2}
              dataKey="text2"
              width={90}
            />
          </Table>
        )}
      </AutoSizer>             
  </div>
  <div>Some other text</div>
 </div>

更新

约束的澄清:解决方案必须使用 Virtualized-React 表。不能选择使用 HTML 表格。(上面我只是说这可能AutoSizer不是正确的解决方案,但我确实需要react表。)

答案中的建议方法:我尝试将@jered 的建议应用于我的案例,并再次得到一个延伸到最大最大高度的表格。我不确定我是否做错了什么,或者建议的方法在这种情况下是否根本不起作用

   <div style={{  display: 'flex',
                  border: '1px solid red',
                  flexDirection: 'column' }}>
    <div style={{
                  display: 'flex',
                  maxHeight: '500px',
                  border: '1px solid blue',
                  padding: '2px',
                  margin: '2px',
                  overflow: 'scroll'
          }}>
          <Table
            ref="Table"
            disableHeader={false}
            headerClassName=""
            headerHeight={40}
            height={900}
            rowClassName=""
            rowHeight={40}
            rowGetter={rowGetter}
            rowCount={6}
            width={400}>
            <Column
                label="Index"
                cellDataGetter={({rowData}) => rowData.index}
                dataKey="index"
                width={60}
            />
            <Column
              label="Text1"
              cellDataGetter={({rowData}) => rowData.text1}
              dataKey="text1"
              width={90}
            />
            <Column
              label="Text2"
              cellDataGetter={({rowData}) => rowData.text2}
              dataKey="text2"
              width={90}
            />
          </Table>
   </div>
  <div style={{   display: 'flex',
                  flexDirection: 'column',
                  border: '1px solid green',
                  padding: '2px',
                  margin: '2px' }}>Some other text</div>
 </div>

相关问题:基本上 React Virtualized 表创建了一个div具有0px x 0px大小和可见溢出的内容。因此,如果可以考虑溢出的大小,这将解决问题。这本身就是一个有趣的问题,所以我将打开一个后续程序,但保持这个打开状态,因为对于这种特定情况可能有更好的解决方案。

另一个更新:我注意到,如果我将表放在 flex 容器中,则不会创建零大小的 div。

3个回答

无需求助于 React 或任何复杂的东西。只需在简单的 CSS 中使用 FlexBox。

给你的“表格”和“其他内容”的容器,display: flexmax-height在包裹<table>. FlexBox 子项将尝试扩展以填充其内容所需的尽可能多的空间,仅此而已,但不会扩展超过它们的max-heightif 设置。

注意:将片段展开到全屏以查看完整效果。

function adjustrows() {
  let val = document.getElementById("slider").value;
  let table = document.getElementById("table");
  let inner = `
    <tr>
      <th>foo</th>
      <th>bar</th>
      <th>baz</th>
      <th>foo</th>
      <th>bar</th>
      <th>baz</th>
    </tr>
  `;
  for (let i = 0; i < val; i++) {
    inner += `
      <tr>
        <td>foo</td>
        <td>bar</td>
        <td>baz</td>
        <td>foo</td>
        <td>bar</td>
        <td>baz</td>
      </tr>
    `;
  }
  table.innerHTML = inner;
  document.getElementById("numrows").innerText = val;
}
adjustrows();
table, tr, th, td {
  box-sizing: border-box;
  border: 1px solid gray;
}

table {
  flex-grow: 1;
}

th {
  background: lightgray;
}

label {
  display: flex;
  justify-content: center;
  margin: 5px;
}

#numrows {
  margin: 0 10px;
}

#flex-container {
  display: flex;
  border: 1px solid red;
  flex-direction: column;
}

#table-container {
  display: flex;
  max-height: 500px;
  border: 1px solid blue;
  padding: 2px;
  margin: 2px;
  overflow: scroll;
}

#other-content-container {
  display: flex;
  flex-direction: column;
  border: 1px solid green;
  padding: 2px;
  margin: 2px;
}

.placeholder {
  background: darkgray;
  margin: 10px;
  height: 120px;
}
<div id="flex-container">
  <label>
    <strong>Number of table rows:<span id="numrows"></span> </strong>
    <input id="slider" onchange="adjustrows();" oninput="adjustrows();" type="range" min="0" max="100" value="1" />
  </label>
  <div id="table-container">
    <table id="table">
    </table>
  </div>
  <div id="other-content-container">
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
    <div class="placeholder"></div>
  </div>
</div>

请注意,如果您<tr>从表中删除某些元素使其短于500px#table-container则只会缩小以适应。

和往常一样,Mozilla Developer Network 有一些关于 FlexBox 的很好的信息

我能够使用 rowRenderer 和 refs为 React 虚拟化列表完成此操作,但我相信它也应该与 Table 一起使用。

  1. 创建状态以包含列表的高度,将默认值设为您预期的最大高度:

const [listHeight, setListHeight] = useState(500);

  1. 渲染列表时,使用此状态的高度:

<List height={listHeight} .../>

  1. 为列表中的每个项目创建一个 refs 列表(在这种情况下,元素是 div):

const rowRefs = useRef(dataInList.map(() => React.createRef<HTMLDivElement>()));

  1. 在 rowRenderer 上,将引用附加到代表表中行的 DOM 元素(我也在使用 cellMeasurer)

const rowRenderer = useCallback(
  ({ key, index, style, parent }: ListRowProps) => {
    const item = dataInList[index];
    return (
      <CellMeasurer
        key={key}
        cache={cache.current}
        columnIndex={0}
        parent={parent}
        rowIndex={index}>
          <div ref={rowRefs.current[index]}>
              Your row info goes here 
          </div>
      </CellMeasurer>
    );
  },
  [formatted]
);

  1. 创建一个在数据源更改时触发的效果(或者可能只在组件安装时触发一次)。该效果将使用 refs 对行的高度求和offsetHeight,并将状态 listHeight 设置为该高度或您想要的任何最大高度(以两者中的较小者为准)。
useEffect(() => {
    const domListHeight = rowRefs.current
        .filter((e: any) => e.current)
        .reduce((acc: any, e: any) => acc + e.current.offsetHeight, 0);
    
    const MAX_HEIGHT = 500;
    const usableHeight = domListHeight < MAX_HEIGHT ? domListHeight : MAX_HEIGHT;
    
    setListHeight(usableHeight);
}, [dataInList]);

摘要:组件将在最大高度渲染列表,然后计算所有行组合的实际高度,如果总数小于预期的最大高度,组件将从头开始重新渲染列表,并使用新的较低高度.

我能够通过在 Autosizer 中禁用高度并像这样手动计算它来解决这个问题:

const ROW_HEIGHT = 40
const MAX_HEIGHT = 500
<AutoSizer disableHeight>
  {({ width }) => (
    <List
      width={width}
      height={Math.min(MAX_HEIGHT, ROW_HEIGHT * data.length)}
      rowCount={data.length}
      rowHeight={ROW_HEIGHT}
      rowRenderer={rowRenderer}
      overscanRowCount={10}
    />
  )}
</AutoSizer>