React项目实战之租房app项目(四)长列表性能优化&城市选择模块渲染列表

前言

一、长列表性能优化

1.1 概述

在展示大型列表和表格数据的时候(城市列表、通讯录、微博等),会导致页面卡顿,滚动不流畅等性能问题
这样就会导致移动设备耗电加快,影响移动设备的电池寿命
产生性能问题的原因:大量DOM节点的重绘和重排
优化方案:

  • 懒渲染
  • 可视区域渲染

1.2 懒渲染

概述:懒加载,常见的长列表优化方案,常见于移动端
原理:每次只渲染一部分,等渲染的数据即将滚动完时,再渲染下面部分
优点:每次渲染一部分数据,速度快
缺点:数据量大时,页面中依然存在大量DOM节点,占用内存过多,降低浏览器渲染性能,导致页面卡顿
使用场景:数据量不大的情况下

1.3 可视区渲染(React-virtualized)

原理: 只渲染页面可视区域的列表项,非可视区域的数据完全不渲染(预加载前面几项和后面几项) ,在滚动列表时动态更新列表项
使用场景: 一次性展示大量数据的情况

二、react-virtualized组件

2.1 概述

react-virtualized是React组件,用来高效渲染大型列表和表格数据
我们使用它来渲染城市列表数据

2.2 基本使用

安装: yarn add react-virtualized
在项目入口文件 index.js 中导入样式文件
拷贝示例代码到我们项目中,示例代码如下:

import { List } from 'react-virtualized';

// 列表数据
const list = [
  'Brian Vaughn'
];
// 渲染每一行的内容
function rowRenderer ({
  key,         // Unique key within array of rows
  index,       // 索引号
  isScrolling, // 当前项是否正在滚动中
  isVisible,   // 当前项在List中是可见的
  style        // 重点属性:一定要给每一个行数添加该样式
}) {
  return (
    <div
      key={key}
      style={style}
    >
      {list[index]}
    </div>
  )
}

// 渲染list列表
ReactDOM.render(
  <List
    // 组件的宽度
    width={300}
    // 组件的高度
    height={300}
    rowCount={list.length}
    // 每行的高度
    rowHeight={20}
    rowRenderer={rowRenderer}
  />,
  document.getElementById('example')
);

三、城市选择模块-渲染城市列表

3.1 让List组件占满屏幕

1、导入 AutoSizer 组件。
2、通过 render-props 模式,获取到 AutoSizer 组件暴露的 width 和 height 属性。
3、设置 List 组件的 width 和 height 属性。
4、设置城市选择页面根元素高度 100% ,让 List 组件占满整个页面。
5、调整样式,让页面不要出现全局滚动条,避免顶部导航栏滚动。

在src/pages/Citylist/index.js中添加如下代码:

        {/* 城市列表 */}
        <AutoSizer>
          {({ width, height }) => (
            <List
              width={width}
              height={height}
              rowCount={list.length}
              rowHeight={50}
              rowRenderer={rowRenderer}
            />
          )}
        </AutoSizer>
在src/pages/Citylist/index.scss中添加样式代码:

.citylist {
    height: 100%;
    padding-top: 45px;
    .navbar {
        margin-top: -45px;
        color: #333;
        background-color: #f6f5f6;
    }
    // navbar 标题颜色
    .am-navbar-title {
        color: #333;
    }
}

3.2 渲染城市列表

在src/pages/Citylist/index.js中添加如下代码:

// 索引(A、B等)的高度
const TITLE_HEIGHT = 36
// 每个城市名称的高度
const NAME_HEIGHT = 50

// 封装处理字母索引的方法
const formatCityIndex = letter => {
  switch (letter) {
    case '#':
      return '当前定位'
    case 'hot':
      return '热门城市'
    default:
      return letter.toUpperCase()
  }
}
  
  6 修改 List 组件的 rowHeight 为函数,动态计算每一行的高度(因为每一行高度都不相同)。
export default class CityList extends React.Component {
  //  1 将获取到的 cityList 和 cityIndex  添加为组件的状态数据。
  state = {
    cityList: {},
    cityIndex: []
  }

  //5 修改 rowRenderer 方法中渲染的每行结构和样式。
  // 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}>
            {item.label}
          </div>
        ))}
      </div>
    )
  }

  // 创建动态计算每一行高度的方法
  getRowHeight = ({ index }) => {
    // 索引标题高度 + 城市数量 * 城市名称的高度
    const { cityList, cityIndex } = this.state
    return TITLE_HEIGHT + cityList[cityIndex[index]].length * NAME_HEIGHT
  }

  render() {
    return (
      <div className="citylist">
        {/* 顶部导航栏 */}
        <NavBar
          className="navbar"
          mode="light"
          icon={<i className="iconfont icon-back" />}
          onLeftClick={() => this.props.history.go(-1)}
        >
          城市选择
        </NavBar>


        {/* 城市列表 */}
        <AutoSizer>
          {({ width, height }) => (
            <List
              width={width}
              height={height}
              //2 修改 List 组件的 rowCount 为 cityIndex 的长度。
              rowCount={this.state.cityIndex.length}
              rowHeight={this.getRowHeight}
              rowRenderer={this.rowRenderer}
            />
          )}
        </AutoSizer>
      </div>
    )
  }
}

3.3 渲染右侧索引列表

在src/pages/Citylist/index.js中添加如下代码:
在state中添加状态activeIndex ,指定当前高亮的索引:

  state = {
    // 指定右侧字母索引列表高亮的索引号
    activeIndex: 0
  }
结构代码:

<ul className="city-index">{this.renderCityIndex()}</ul>
渲染右侧索引的函数:

renderCityIndex() {
    return this.state.cityIndex.map((item,index) => {
        return (
            <li className="city-index-item" key={item}>
                {/*判断一下,如果高亮状态的索引等于当前索引,那么就设置高亮样式*/}
                <span className={this.state.activeIndex == index? 'index-active' : ''}>{item == 'hot' ? '热' : item.toUpperCase()}</span>
            </li>
        )
    })
}

3.4 滚动城市列表让对应右侧索引高亮显示

1、给list组件添加onRowsRendered配置项,用于获取当前列表渲染的行信息,在里面就会有相应信息
2、通过参数 startIndex 获取到 起始行对应的索引号
3、判断 startIndex 和 activeIndex 不同时候,更新状态 activeIndex为 startIndex

在src/pages/Citylist/index.js中添加如下代码:

<List
    ...
    onRowsRendered={this.rowRendered}
/>

/**
* 获取滚动时候,相应的数据
* @param {*} param0
*/
rowRendered = ({ startIndex }) => {
    if (this.state.activeIndex !== startIndex) {
        this.setState({
            activeIndex: startIndex
        })
    }
}

3.5 点击右侧索引置顶该索引城市

1、给索引列表绑定点击事件
2、在点击事件中,通过index获取到当前项索引号
3、调用List组件的 scrollToRow方法,让List组件滚动到指定行
3.1、在constructor中,调用React.createRef() 创建ref对象
3.2、将创建好的ref对象,添加为List组件的ref属性
3.3、通过ref的current属性,获取到组件实例,再调用组件的scrollToRow方法
4、设置List组件的scrollToAlignment配置项值为start,保证点击行出现在页面顶部
5、对于点击索引无法正确定位的问题,调用List组件的 measureAllRows 方法,提前计算高度来解决

在src/pages/Citylist/index.js中添加核心代码:

constructor() {
    ...
    this.listComponent = React.createRef()
}
async componentDidMount() {
    await this.getCityList()
    // 计算List组件高度
    this.listComponent.current.measureAllRows()
}
renderCityIndex() {
    return this.state.cityIndex.map((item, index) => {
        return (
            <li className="city-index-item" key={item} onClick={() => {
                // 拿到List组件的实例
                this.listComponent.current.scrollToRow(index)
            }}>
              ...
            </li>
        )
    })
}
render() {
    return (
        <div className="citylist">
            ...
            {/* 城市列表 */}
            <AutoSizer>
                {
                    ({ width, height }) => {
                        return <List
                            ref={this.listComponent}
                             scrollToAlignment="start"
                            ...
                        />
                    }
                }
            </AutoSizer>
            ...
        </div>
    )
}

3.6 点击城市列表实现切换城市功能

1、给城市列表项绑定事件
2、判断当前城市是否有房源数据
3、如果有房源数据,则保存当前城市数据到本地缓存中,并返回上一页
4、如果没有房源数据,则提示用户:改城市暂无房源数据,不执行任何操作

在src/pages/Citylist/index.js中添加核心代码:

const HOST_CITY = ['北京', '上海', '广州', '深圳']
// 渲染每一行的内容
rowRenderer({
    key,         // Unique key within array of rows
    index,       // 索引号
    isScrolling, // 当前项是否正在滚动中
    isVisible,   // 当前项在List中是可见的
    style        // 重点属性:一定要给每一个行数添加该样式
}) {
    let letter = this.state.cityIndex[index]
    let citys = this.state.cityList[letter]
    return (
        <div
            key={key}
            style={style}
            className="city"
        >
            <div className="title">{this.formatCityIndex(letter)}</div>
            {citys.map(item => {
                return (
                    // 绑定点击事件,传递城市名称和value
                    <div className="name" key={item.value} onClick={() => this.changeCity(item.label, item.value)}>{item.label}</div>
                )
            })}</div>
    )
}
changeCity = (label, value) => {
    if (HOST_CITY.indexOf(label) > -1) {
        // 说明是有房源数据的城市
        localStorage.setItem('localCity', JSON.stringify({label,value}))
        this.props.history.go(-1)
    } else {
        // 没有房源城市,提示用户
        Toast.info('当前城市没有房源', 1);
    }
}

总结

到目前为止,我们完成了项目的4个部分的代码编写,分别是
1、项目准备:部署本地接口,脚手架初始化项目,antd-mobile,路由等
2、项目整体布局:分析两种页面布局,使用嵌套路由实现带TabBar页面布局等
3、首页模块:租房小组结构布局,数据获取,H5地理定位和百度地图地理定位等
4、城市选择模块:数据结构处理,长列表性能优化,react-virtualized,索引列表等

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小陈工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值