导语
有个聊天对话框需要改造成虚拟列表,碰到了一些问题做下记录。
聊天对话框有以下特点:
- 内容高度非固定(长度姑且可以认为固定)
- 内容有多种类型,如文本、图片、投票等
- 内容可以删除
用 react-virtualized 来实现上面的功能需要一些工作量。
细节
高度非固定
react-virtualized
有提供一些高阶组件来解决典型问题,这里我们使用 CellMeasurer 。
基本上是照着官网的例子改的
内容状态会变化
react-virtualized
内部会缓存每一列的内容,所以如果你按照上面例子来实现,必然会碰到点击没效果、图片重叠等问题。
解决方法是在状态更新后调用 measure 方法。举个图片的例子,当图片加载完成后再调用 measure 来重新计算高度
内容可以删除
官网的例子没找到有删除的功能。如果直接在 React 里面删除一项数据,你会发现 中间空了一行 或者 高度塌陷 ...
这是因为 react-virtualized
内部的机制导致的:
- 内部会缓存所有项的内容、高度
- 缓存的 key 是 column+row
举个例子,如果第1到3项的高度为100、200、300,那么删掉第2项以后,第三项就占了200的高度,显然就不对了
react-virtualized
根本就不知道哪一行改变了,需要用户手动告诉它。
我们使用 List 提供的 recomputeRowHeights 来告诉它。
滚动到底部
List 有 scrollToIndex
属性,但是这个属性有个问题,滚动到同一个属性没有效果。
还有个可选的方法是 scrollToPosition,相同的行数也可以.
其他
滚动条
因为高度不固定,所以滚动条在渲染后才会知道真正高度。这里可以试试 measureAll 方法
高度计算
固定住 width,将 height 设为 auto,然后读 node.offsetHeight
。
import React from 'react';
import { CellMeasurer, CellMeasurerCache, List } from 'react-virtualized';
// In this example, average cell height is assumed to be about 50px.
// This value will be used for the initial `Grid` layout.
// Width is not dynamic.
const cache = new CellMeasurerCache({
defaultHeight: 50,
fixedWidth: true
});
function rowRenderer ({ index, isScrolling, key, parent, style }) {
const source // This comes from your list data
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{({ measure, registerChild }) => (
// 'style' attribute required to position cell (within parent List)
<div ref={registerChild} style={style}>
blabla
</div>
)}
</CellMeasurer>
);
}
function renderList (props) {
return (
<List
{...props}
deferredMeasurementCache={cache}
rowHeight={cache.rowHeight}
rowRenderer={rowRenderer}
/>
);
}
function rowRenderer ({ index, isScrolling, key, parent, style }) {
const source // This comes from your list data
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
{({ measure, registerChild }) => (
// 'style' attribute required to position cell (within parent List)
<div ref={registerChild} style={style}>
<img
onLoad={measure}
src={source}
/>
</div>
)}
</CellMeasurer>
);
}