const cityList = {}
//遍历数据,拿到数据的首字符创建对象进行数据分类
res.body.forEach((item) => {
const first = item.short.substr(0, 1)
if (cityList[first]) {
cityList[first].push(item)
} else {
cityList[first] = [item]
}
})
//Object.keys(cityList).sort()拿到对象的key并排序
const cityIndex = Object.keys(cityList).sort()
实现效果
将当前定位城市和热门城市追加到数组最前面
// 将热门的城市加到数组的最前面
let result = await getHotCity()
cityList['hot'] = result.body
// 将当前定位的城市加到数组的最前面
const city = await getCurrentCity()
cityList['#'] = [city]
cityIndex.unshift('hot')
cityIndex.unshift('#')
长列表渲染性能优化 react-virtualized
1.安装使用
yarn add react-virtualized
2.index.js导入
// 导入 react-virtualized 组件的样式
import 'react-virtualized/styles.css'
3.AutoSizer进行长列表性能优化
// 导入 List 组件
import { List, AutoSizer } from 'react-virtualized'
{/* 城市列表 */}
<AutoSizer>
{({ width, height }) => (
<List
ref={this.cityListComponent}
width={width}
height={height}
rowCount={this.state.cityIndex.length}
rowHeight={this.getRowHeight}
rowRenderer={this.rowRenderer}
onRowsRendered={this.onRowsRendered}
scrollToAlignment="start"
/>
)}
</AutoSizer>
4.设置样式
// 创建动态计算每一行高度的方法
getRowHeight = ({ index }) => {
// 索引标题高度 + 城市数量 * 城市名称的高度
// TITLE_HEIGHT + cityList[cityIndex[index]].length * NAME_HEIGHT
const { cityList, cityIndex } = this.state
return TITLE_HEIGHT + cityList[cityIndex[index]].length * NAME_HEIGHT
}
// List组件渲染每一行的方法:
rowRenderer = ({
key, // Unique key within array of rows
index, // 索引号
isScrolling, // 当前项是否正在滚动中
isVisible, // 当前项在 List 中是可见的
style, // 注意:重点属性,一定要给每一个行数据添加该样式!作用:指定每一行的位置
}) => {
// 获取每一行的字母索引
const { cityIndex, cityList } = this.state
const letter = cityIndex[index]
return (
<div key={key} style={style} className="city">
<div className="title">{formatCityIndex(letter)}</div>
{cityList[letter].map((item) => (
<div
className="name"
key={item.value}
onClick={() => this.changeCity(item)}
>
{item.label}
</div>
))}
</div>
)
}
// 用于获取List组件中渲染行的信息
onRowsRendered = ({ startIndex }) => {
// console.log('startIndex:', startIndex)
if (this.state.activeIndex !== startIndex) {
this.setState({
activeIndex: startIndex,
})
}
}
react拿到组件的实例,关键代码和注意点
constructor(props) {
super(props)
// 创建ref对象
this.cityListComponent = React.createRef()
}
<List ref={this.cityListComponent}
.../>
// 如果没有全部加载数据,点击字母跳转会出现定位不准
async componentDidMount() {
await this.getCityList()
// 调用 measureAllRows,提前计算 List 中每一行的高度,实现 scrollToRow 的精确跳转
this.cityListComponent.current.measureAllRows()
}
完整代码
import React from 'react'
import { getCityList, getHotCity } from '../../api/home'
import { Toast } from 'antd-mobile'
// 导入 List 组件
import { List, AutoSizer } from 'react-virtualized'
// 导入 utils 中获取当前定位城市的方法
import { getCurrentCity } from '../../utils'
// 导入 NavHeader 组件
import NavHeader from '../../components/NavHeader'
import './index.scss'
// 数据格式化的方法
const formatCityData = (list) => {
const cityList = {}
// 1 遍历list数组
list.forEach((item) => {
// 2 获取每一个城市的首字母
const first = item.short.substr(0, 1)
// 3 判断 cityList 中是否有该分类
if (cityList[first]) {
// 4 如果有,直接往该分类中push数据
cityList[first].push(item)
} else {
// 5 如果没有,就先创建一个数组,然后,把当前城市信息添加到数组中
cityList[first] = [item]
}
})
// 获取索引数据
const cityIndex = Object.keys(cityList).sort()
return {
cityList,
cityIndex,
}
}
// 索引(A、B等)的高度
const TITLE_HEIGHT = 36
// 每个城市名称的高度
const NAME_HEIGHT = 50
// 封装处理字母索引的方法
const formatCityIndex = (letter) => {
switch (letter) {
case '#':
return '当前定位'
case 'hot':
return '热门城市'
default:
return letter.toUpperCase()
}
}
// 有房源的城市
const HOUSE_CITY = ['北京', '上海', '广州', '深圳']
export default class CityList extends React.Component {
constructor(props) {
super(props)
this.state = {
cityList: {},
cityIndex: [],
// 指定右侧字母索引列表高亮的索引号
activeIndex: 0,
}
// 创建ref对象
this.cityListComponent = React.createRef()
}
async componentDidMount() {
await this.getCityList()
// 调用 measureAllRows,提前计算 List 中每一行的高度,实现 scrollToRow 的精确跳转
this.cityListComponent.current.measureAllRows()
}
// 获取城市列表数据的方法
async getCityList() {
const res = await getCityList({ level: 1 })
const { cityList, cityIndex } = formatCityData(res.body)
// 获取热门城市数据
const hotRes = await getHotCity()
cityList['hot'] = hotRes.body
cityIndex.unshift('hot')
// 获取当前定位城市
const curCity = await getCurrentCity()
cityList['#'] = [curCity]
cityIndex.unshift('#')
this.setState({
cityList,
cityIndex,
})
}
changeCity({ label, value }) {
if (HOUSE_CITY.indexOf(label) > -1) {
// 有
localStorage.setItem('hkzf_city', JSON.stringify({ label, value }))
this.props.history.go(-1)
} else {
Toast.info('该城市暂无房源数据', 1, null, false)
}
}
// List组件渲染每一行的方法:
rowRenderer = ({
key, // Unique key within array of rows
index, // 索引号
isScrolling, // 当前项是否正在滚动中
isVisible, // 当前项在 List 中是可见的
style, // 注意:重点属性,一定要给每一个行数据添加该样式!作用:指定每一行的位置
}) => {
// 获取每一行的字母索引
const { cityIndex, cityList } = this.state
const letter = cityIndex[index]
return (
<div key={key} style={style} className="city">
<div className="title">{formatCityIndex(letter)}</div>
{cityList[letter].map((item) => (
<div
className="name"
key={item.value}
onClick={() => this.changeCity(item)}
>
{item.label}
</div>
))}
</div>
)
}
// 创建动态计算每一行高度的方法
getRowHeight = ({ index }) => {
// 索引标题高度 + 城市数量 * 城市名称的高度
// TITLE_HEIGHT + cityList[cityIndex[index]].length * NAME_HEIGHT
const { cityList, cityIndex } = this.state
return TITLE_HEIGHT + cityList[cityIndex[index]].length * NAME_HEIGHT
}
// 封装渲染右侧索引列表的方法
renderCityIndex() {
// 获取到 cityIndex,并遍历其,实现渲染
const { cityIndex, activeIndex } = this.state
return cityIndex.map((item, index) => (
<li
className="city-index-item"
key={item}
onClick={() => {
// console.log('当前索引号:', index)
this.cityListComponent.current.scrollToRow(index)
}}
>
<span className={activeIndex === index ? 'index-active' : ''}>
{item === 'hot' ? '热' : item.toUpperCase()}
</span>
</li>
))
}
// 用于获取List组件中渲染行的信息
onRowsRendered = ({ startIndex }) => {
// console.log('startIndex:', startIndex)
if (this.state.activeIndex !== startIndex) {
this.setState({
activeIndex: startIndex,
})
}
}
render() {
return (
<div className="citylist">
{/* 顶部导航栏 */}
<NavHeader>城市选择</NavHeader>
{/* 城市列表 */}
<AutoSizer>
{({ width, height }) => (
<List
ref={this.cityListComponent}
width={width}
height={height}
rowCount={this.state.cityIndex.length}
rowHeight={this.getRowHeight}
rowRenderer={this.rowRenderer}
onRowsRendered={this.onRowsRendered}
scrollToAlignment="start"
/>
)}
</AutoSizer>
{/* 右侧索引列表 */}
<ul className="city-index">{this.renderCityIndex()}</ul>
</div>
)
}
}