有个聊天对话框需要改造成虚拟列表,碰到了一些问题做下记录。
聊天对话框有以下特点:
用 react-virtualized 来实现上面的功能需要一些工作量。
react-virtualized
有提供一些高阶组件来解决典型问题,这里我们使用 CellMeasurer 。
基本上是照着官网的例子改的
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 datareturn (<CellMeasurercache={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}/>);}
react-virtualized
内部会缓存每一列的内容,所以如果你按照上面例子来实现,必然会碰到点击没效果、图片重叠等问题。
解决方法是在状态更新后调用 measure 方法。举个图片的例子,当图片加载完成后再调用 measure 来重新计算高度
function rowRenderer ({ index, isScrolling, key, parent, style }) {const source // This comes from your list datareturn (<CellMeasurercache={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}><imgonLoad={measure}src={source}/></div>)}</CellMeasurer>);}
官网的例子没找到有删除的功能。如果直接在 React 里面删除一项数据,你会发现 中间空了一行 或者 高度塌陷 ...
这是因为 react-virtualized
内部的机制导致的:
举个例子,如果第1到3项的高度为100、200、300,那么删掉第2项以后,第三项就占了200的高度,显然就不对了
react-virtualized
根本就不知道哪一行改变了,需要用户手动告诉它。
我们使用 List 提供的 recomputeRowHeights 来告诉它。
List 有 scrollToIndex
属性,但是这个属性有个问题,滚动到同一个属性没有效果。
还有个可选的方法是 scrollToPosition,相同的行数也可以.
因为高度不固定,所以滚动条在渲染后才会知道真正高度。这里可以试试 measureAll 方法
固定住 width,将 height 设为 auto,然后读 node.offsetHeight
。
_getCellMeasurements() {const {cache} = this.props;const node = this._child || findDOMNode(this);if (node &&node.ownerDocument &&node.ownerDocument.defaultView &&node instanceof node.ownerDocument.defaultView.HTMLElement) {const styleWidth = node.style.width;const styleHeight = node.style.height;if (!cache.hasFixedWidth()) {node.style.width = 'auto';}if (!cache.hasFixedHeight()) {node.style.height = 'auto';}const height = Math.ceil(node.offsetHeight);const width = Math.ceil(node.offsetWidth);// Reset after measuring to avoid breaking styles; see #660if (styleWidth) {node.style.width = styleWidth;}if (styleHeight) {node.style.height = styleHeight;}return {height, width};} else {return {height: 0, width: 0};}}