选择城市列表
选择城市列表渲染后的界面:
1,获取并处理城市列表数据
- 接口返回的数据结构:
[{"label": "北京", "value": "", "pinyin": "beijing", "short": "bj"}]
- 渲染城市列表的数据格式应该为:
{a: [{}, {}], b: [{}, ...]}
- 渲染右侧索引的数据格式为:
['a', 'b']
示例代码:
// 获取城市列表
async getCityList() {
const res = await axios.get('http://localhost:8080/area/city', {
params: { level: 1 }
})
const {cityList, cityIndex} = formatCityData(res.data.body)
// 将当前定位城市数据添加到cityList中,当前定位城市的索引添加到cityIndex中
const curCity = await getCurrentCity()
cityList['#'] = curCity
cityIndex.unshift('#')
// 获取热门城市数据,并将数据添加到 cityList 中,将索引添加到 cityIndex 中
const hotRes = await axios.get('http://localhost:8080/area/hot')
cityList['hot'] = hotRes.data.body
cityIndex.unshift('hot')
}
// 数据格式化的方法
const formatCityData = list => {
const cityList = {}
// 1,遍历list数组
list.map(item => {
// 2,获取每一个城市的首字母
const firstStr = item.short.substr(0, 1)
// 3,判断 cityList 中是否有该分类
if(cityList[firstStr]) {
// 4,如果有,直接往该分类中 push 数据
cityList[firstStr].push(item)
} else {
// 5,如果没有,就先创建一个数组,然后把当前城市信息添加到数组中
cityList[firstStr] = [item]
}
})
// 获取索引数组
const cityIndex = Object.keys(cityList).sort()
return {
cityList,
cityIndex
}
}
2,当前定位城市数据问题
- 多个模块需要获取定位城市,这时候我们需要把获取定位城市的方法封装成函数,哪个模块需要直接调用该方法即可
- 封装思路
- 1,在utils目录中,创建index.js,在该文件中封装
- 2,创建并导出获取定位城市的函数,并将定位到的城市存储在本地(localStorage)
- 3,判断localStorage中是否有定位城市,没有,就直接获取定位城市的方法来获取,并保存在本地,并返回该城市数据。如果有,直接返回本地存储中的城市数据。
3,长列表性能优化
- 场景:展示大型列表和表格数据(比如:城市列表、通讯录、微博等),会导致页面卡顿、滚动不流畅等性能问题
- 产生性能问题的原因:大量DOM节点的重绘和重排
- 其他原因:老旧设备
- 其他问题:移动设备耗电加快、影响移动设备电池寿命
- 优化方案:1,
懒渲染
2,可视区域渲染
1,懒渲染
- 常见的长列表优化方案,常用于移动端
- 原理:每次只渲染一部分(比如10条数据),等渲染的数据即将滚动完再渲染下面数据
- 优点:每次渲染一部分数据,速度快
- 缺点:数据量大,页面中依然存在大量DOM节点,占用内存多、降低浏览器渲染性能,导致页面可顿
- 使用场景:数据量不大的情况(比如1000条,具体还要看每条数据的复杂程度)
2,可视区域渲染(react-virtualized)
- 原理:只渲染页面可视区域的列表项,非可视区域的数据不渲染,在滚动列表的时候动态更新列表项
- 使用场景:一次性展示大量数据的情况(比如:大表格、微博、聊天应用等)
react-virtualized
- 安装:yarn add react-virtualized
- 文档
- 在需要使用的组件里引入
import {List, AutoSizer} from 'react-virtualized'
城市列表示例代码
imageimport {getCurrentCity} from '../../utils'
import {List, AutoSizer} from 'react-virtualized';
const TITLE_HEIGHT = 36
const NAME_HEIGHT = 50
// 数据格式化的方法
const formatCityData = list => {
const cityList = {}
// 获取cityList
list.map(item => {
// 获取每一个城市的首字母
const firstStr = item.short.substr(0, 1)
// 判断 cityList 中是否有该分类
if(cityList[firstStr]) {
cityList[firstStr].push(item)
} else {
cityList[firstStr] = [item]
}
})
// 获取索引数组
const cityIndex = Object.keys(cityList).sort()
return {
cityList,
cityIndex
}
}
// 封装处理字母索引的方法
const formatCityIndex = letter => {
switch (letter) {
case '#':
return '当前定位'
case 'hot':
return '热门城市'
default:
return letter.toUpperCase()
break
}
}
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 的精确跳转
// 注意:调用这个方法的时候,需要保证 List 组件中已经有数据了!如果 List 组件中的数据为空,就会导致调用方法报错!
// 解决:只要保证这个方法是在 获取到数据之后 调用的即可。
this.cityListComponent.current.measureAllRows()
}
// 获取城市列表
async getCityList() {
const res = await axios.get('http://localhost:8080/area/city', {
params: { level: 1 }
})
const {cityList, cityIndex} = formatCityData(res.data.body)
const hotRes = await axios.get('http://localhost:8080/area/hot')
cityList['hot'] = hotRes.data.body
cityIndex.unshift('hot')
const curCity = await getCurrentCity()
cityList['#'] = [curCity]
cityIndex.unshift('#')
this.setState({cityList, cityIndex})
}
rowRender = ({
key, // Unique key within array of rows
index, // Index of row within collection
isScrolling, // The List is currently being scrolled
isVisible, // This row is visible within the List (eg it is not an overscanned row)
style, // Style object to be applied to row (to position it)
}) => {
// 获取每一行的字母索引
const {cityIndex, cityList} = this.state
const letter = cityIndex[index]
return (
<div key={key} style={style} className="city">
<div className="title">{formatCityIndex(letter)}</div>
{
Array.from(cityList[letter]).map(item => <div className="name" key={item.value}>{item.label}</div>)
}
</div>
);
}
// 封装渲染右侧索引列表的方法
renderCityIndex() {
// 获取到 cityIndex,并遍历其,实现渲染
const {cityIndex, activeIndex} = this.state
return cityIndex.map((item, index) => <li className="city-index-item" key={item} onClick={() => {
// index 当前索引
this.cityListComponent.current.scrollToRow(index)
}}>
<span className={activeIndex === index ? "index-active" : ""}>{item === 'hot' ? '热' : item.toUpperCase()}</span>
</li>)
}
// 创建动态计算每一行高度的方法
getRowHeight = ({index}) => {
// 索引标题高度 + 城市数量 * 城市名称的高度
// TITLE_HEIGHT + cityList[cityIndex[index]].length * NAME_HEIGHT
const {cityList, cityIndex} = this.state
return TITLE_HEIGHT + cityList[cityIndex[index]].length * NAME_HEIGHT
}
onRowsRendered = ({startIndex}) => {
console.log(startIndex)
if (this.state.activeIndex !== startIndex) {
this.setState({
activeIndex: startIndex
})
}
}
render() {
return(
<div className="cityList">
<NavBar
mode="light"
icon={<i className="iconfont icon-back"/>}
onLeftClick={() => this.props.history.go(-1)}
>城市选择</NavBar>
<AutoSizer>
{({width, height}) => (
<List
ref={this.cityListComponent}
width={width}
height={height}
rowCount={this.state.cityIndex.length}
rowRenderer={this.rowRender}
rowHeight={this.getRowHeight}
scrollToAlignment="start"
onRowsRendered={this.onRowsRendered}
/>
)}
</AutoSizer>
{/* 右侧 */}
<ul className="city-index">
{this.renderCityIndex()}
</ul>
</div>
)
}
}
切换城市
1, 给城市列表项绑定点击事件
{
Array.from(cityList[letter]).map(item => <div className="name" key={item.value} onClick={() => this.changeCity(item)}>{item.label}</div>)
}
2, 判断当前城市是否有房源数据(只有北、上、广、深有数据)
3, 有房源,保存当前城市到本地缓存中,并返回上一页
4,没有则提示用户该城市暂无房源信息
// 有房源的城市
const HOUSE_CITY = ['北京', '上海', '广州', '深圳']
changeCity = ({label, value}) => {
if (HOUSE_CITY.indexOf(label) > -1) {
// 有
localStorage.setItem('current_city', JSON.stringify({label, value}))
this.props.history.go(-1)
} else {
Toast.info('该城市暂无房源数据', 1, null ,false)
}
}
网友评论