美文网首页
React项目实战四

React项目实战四

作者: 小王子__ | 来源:发表于2021-12-06 15:12 被阅读0次

选择城市列表

选择城市列表渲染后的界面:

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中是否有定位城市,没有,就直接获取定位城市的方法来获取,并保存在本地,并返回该城市数据。如果有,直接返回本地存储中的城市数据。
image
3,长列表性能优化
  • 场景:展示大型列表和表格数据(比如:城市列表、通讯录、微博等),会导致页面卡顿、滚动不流畅等性能问题
  • 产生性能问题的原因:大量DOM节点的重绘和重排
  • 其他原因:老旧设备
  • 其他问题:移动设备耗电加快、影响移动设备电池寿命
  • 优化方案:1,懒渲染 2,可视区域渲染

1,懒渲染

  • 常见的长列表优化方案,常用于移动端
  • 原理:每次只渲染一部分(比如10条数据),等渲染的数据即将滚动完再渲染下面数据
  • 优点:每次渲染一部分数据,速度快
  • 缺点:数据量大,页面中依然存在大量DOM节点,占用内存多、降低浏览器渲染性能,导致页面可顿
  • 使用场景:数据量不大的情况(比如1000条,具体还要看每条数据的复杂程度)

2,可视区域渲染(react-virtualized)

  • 原理:只渲染页面可视区域的列表项,非可视区域的数据不渲染,在滚动列表的时候动态更新列表项
  • 使用场景:一次性展示大量数据的情况(比如:大表格、微博、聊天应用等)
react-virtualized
  • 安装:yarn add react-virtualized
  • 文档
  • 在需要使用的组件里引入
import {List, AutoSizer} from 'react-virtualized'
城市列表示例代码
image
import {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)
  }
}

相关文章

网友评论

      本文标题:React项目实战四

      本文链接:https://www.haomeiwen.com/subject/tsagxrtx.html