React项目实战之租房app项目(八)列表找房模块之获取并渲染房源数据&吸顶功能&整体优化&遮罩层动画效果

前言

目录

  • 前言
  • 一、列表找房模块-获取房屋列表数据
    • 1.1 处理筛选条件数据
    • 1.2 获取房源数据
    • 1.3 渲染房源数据
  • 二、列表找房模块-房源列表优化
    • 2.1 使用WindowScroller实现列表跟随页面滚动
      • 2.1.1 概述
      • 2.1.2 代码示例
    • 2.2 使用InfiniteLoader组件实现动态加载更多房屋数据
      • 2.2.1 使用步骤
      • 2.2.2 代码示例
  • 三、列表找房模块-吸顶功能
    • 3.1 实现思路
    • 3.2 实现步骤
    • 3.3 代码示例
  • 四、列表找房模块-整体优化
    • 4.1 加载提示
    • 4.2 条件筛选栏优化
    • 4.3 切换城市显示房源优化
  • 五、使用react-spring动画库实现动画效果
    • 5.1 概述
    • 5.2 基本使用
    • 5.3 实现遮罩层动画
  • 总结

一、列表找房模块-获取房屋列表数据

1.1 处理筛选条件数据

实现步骤:

1、在Filter组件的onSave方法中,根据最新selectedValues组装筛选的条件数据 filters,以下是数据格式
2、获取区域数据的参数名:area 或 subway(选中值,数组的第一个元素)
3、获取区域数据值(以最后一个value为准)
4、获取方式和租金的值(选中值得第一个元素)
5、获取筛选(more)的值(讲选中值数组转换为以逗号分隔的字符串)

代码示例:
在src/pages/Houselist/components/Filter/index.js中添加如下代码:

// 只更新当前 type 对应的选中值
let newSelectedValues = {
  ...selectedValues,
  [type]: value
};
const { area, mode, price, more } = newSelectedValues;


// 筛选条件数据
const filters = {};
// 区域
const areaKey = area[0];
let areaValue = "null";
if (area.length === 3) {
  areaValue = area[2] !== "null" ? area[2] : area[1];
}
filters[areaKey] = areaValue;
// 方式和租金
filters.mode = mode[0];
filters.price = price[0];
// more
filters.more = more.join(",");

1.2 获取房源数据

实现步骤:

1、将筛选条件数据filters传递给父组件HouseList
2、HouseList组件中,创建方法onFilter,通过参数接收filters数据,并存储到this3、创建方法searchHouseList(用来获取房屋列表数据)
4、根据接口,获取当前定位城市id参数
5、将筛选条件数据与分页数据合并后,作为借口的参数,发送请求,获取房屋数据
6、在componentDidMount钩子函数中,调用searchHouseList,来获取房屋列表数据

代码示例:
在src/pages/Houselist/components/Filter/index.js中添加如下代码:

// 保存,隐藏对话框
  onSave = (type, value) => {
    ...
    this.props.onFilter(filters)
    ...
  };
在src/pages/Houselist/index.js中添加如下代码:

  state = {
    // 列表数据
    list: [],
    // 总条数
    count: 0
  }

  // 初始化实例属性
  filters = {}

  componentDidMount() {
    this.searchHouseList()
  }

  // 提供给Filter组件调用的函数,接受参数 filters
  onFilter = filters => {
    this.filters = filters;
    this.searchHouseList();
  };

  // 用来获取房屋列表数据
  async searchHouseList() {
    // 获取当前定位城市id
    // const { value } = JSON.parse(localStorage.getItem('hkzf_city'))
    const res = await API.get('/houses', {
      params: {
        cityId: value,
        ...this.filters,
        start: 1,
        end: 20
      }
    })
    const { list, count } = res.data.body
    this.setState({
      list,
      count
    })
  }

1.3 渲染房源数据

实现步骤:

1、我们之前在地图找房模块以及实现了房源列表的渲染,先将其封装为HouseItem组件,提高代码复用性
2、使用HouseItem组件改造Map组件的房屋列表项
3、使用react-virtualized的List组件渲染房屋列表

代码示例:
在src/components/HouseItem/index.js中添加如下代码:

import React from 'react'
import PropTypes from 'prop-types'
import styles from './index.module.css'

function HouseItem({ src, title, desc, tags, price, onClick }) {
  return (
    <div className={styles.house} onClick={onClick}>
      <div className={styles.imgWrap}>
        <img className={styles.img} src={src} alt="" />
      </div>
      <div className={styles.content}>
        <h3 className={styles.title}>{title}</h3>
        <div className={styles.desc}>{desc}</div>
        <div>
          {/* ['近地铁', '随时看房'] */}
          {tags.map((tag, index) => {
            const tagClass = 'tag' + (index + 1)
            return (
              <span
                className={[styles.tag, styles[tagClass]].join(' ')}
                key={tag}
              >
                {tag}
              </span>
            )
          })}
        </div>
        <div className={styles.price}>
          <span className={styles.priceNum}>{price}</span>/</div>
      </div>
    </div>
  )
}

HouseItem.propTypes = {
  src: PropTypes.string,
  title: PropTypes.string,
  desc: PropTypes.string,
  tags: PropTypes.array.isRequired,
  price: PropTypes.number,
  onClick: PropTypes.func
}

export default HouseItem

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

  // 封装渲染房屋列表的方法
  renderHousesList() {
    return this.state.housesList.map(item => (
      <HouseItem
        key={item.houseCode}
        src={BASE_URL + item.houseImg}
        title={item.title}
        desc={item.desc}
        tags={item.tags}
        price={item.price}
      />
    ))

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

// 渲染每一行的内容
renderHouseList = ({
  key, // Unique key within array of rows
  index, // 索引号
  style // 重点属性:一定要给每一个行数添加该样式
}) => {
  // 当前这一行的
  const { list } = this.state;
  const house = list[index];
  return (
    <HouseItem
      key={key}
      style={style}
      src={BASE_URL + house.houseImg}
      title={house.title}
      desc={house.desc}
      tags={house.tags}
      price={house.price}
    />
  );
};
render(){
    return (
      <div>
         ...
          {/* 房屋列表 */}
        <div className={styles.houseItems}>
          <List
            // 组件的宽度
            width={300}
            // 组件的高度
            height={300}
            rowCount={this.state.count} // List列表项总条目数
            // 每行的高度
            rowHeight={120} // 每一行高度
            rowRenderer={this.renderHouseList}
          />
        </div>
      </div>
    )
}

二、列表找房模块-房源列表优化

2.1 使用WindowScroller实现列表跟随页面滚动

2.1.1 概述

默认:List组件只让组件自身出现滚动条,无法让整个页面滚动,也就无法实现标题吸顶功能
解决方式:使用WindowScroller高阶组件,让List组件跟随页面滚动(为List组件提供状态,同时还需要设置List组件的autoHeight属性)
注意:WindowScroller高阶组件只能提供height,无法提供width
解决方式:在WindowScroller组件中使用AutoSizer高阶组件来为List组件提供width

2.1.2 代码示例

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

{/* 房屋列表 */}
<div className={styles.houseItems}>
  <WindowScroller>
    {({ height, isScrolling, scrollTop }) => (
      <AutoSizer>
        {({ width }) => (
          <List
            autoHeight // 设置高度为 WindowScroller 最终渲染的列表高度
            width={width} // 视口的宽度
            height={height} // 视口的高度
            rowCount={this.state.count} // List列表项的行数
            rowHeight={120} // 每一行的高度
            rowRenderer={this.renderHouseList} // 渲染列表项中的每一行
            isScrolling={isScrolling}
            scrollTop={scrollTop}
          />
        )}
      </AutoSizer>
    )}
  </WindowScroller>
</div>

2.2 使用InfiniteLoader组件实现动态加载更多房屋数据

2.2.1 使用步骤

1、使用InfiniteLoader 组件,来实现无限滚动列表,从而加载更多房屋数据
2、在loadMoreRows方法中,根据起始索引和结束索引,发送请求,获取更多房屋数据
3、获取到最新的数据后,与当前list中的数据合并,再更新state,并调用Promise的resolve
4、在renderHouseList方法中,判断house是否存在
5、不存在的时候,就渲染一个loading元素
6、存在的时候,再渲染HouseItem组件

2.2.2 代码示例

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

// isRowLoaded 表示每一行数据是否加载完成 
// loadMoreRows 加载更多数据的方法,在需要加载更多数据时,会调用该方法 
// rowCount 列表数据总条数
<InfiniteLoader
  isRowLoaded={this.isRowLoaded}
  loadMoreRows={this.loadMoreRows}
  rowCount={count}
>
  {({ onRowsRendered, registerChild }) => (
    <WindowScroller>
      {({ height, isScrolling, scrollTop }) => (
        <AutoSizer>
          {({ width }) => (
            <List
              onRowsRendered={onRowsRendered}
              ref={registerChild}
              autoHeight // 设置高度为 WindowScroller 最终渲染的列表高度
              // 组件的宽度
              width={width} // 视口宽度
              // 组件的高度
              height={height} // 视口高度
              rowCount={count} // List列表项总条目数
              // 每行的高度
              rowHeight={120} // 每一行高度
              rowRenderer={this.renderHouseList}
              isScrolling={isScrolling}
              scrollTop={scrollTop}
            />
          )}
        </AutoSizer>
      )}
    </WindowScroller>
  )}
</InfiniteLoader>
isRowLoaded和loadMoreRows方法:

// 判断列表中的每一行是否加载完成
isRowLoaded = ({ index }) => {
  return !!this.state.list[index]
}

loadMoreRows = ({ startIndex, stopIndex }) => {
    return new Promise(resolve => {
      instance
        .get("/houses", {
          params: {
            cityId: value,
            ...this.filters,
            start: startIndex,
            end: stopIndex
          }
        })
        .then(res => {
          this.setState({
            list: [...this.state.list, ...res.data.body.list]
          });

          // 加载数据完成时,调用resolve即可
          resolve();
        });
    });
  };

renderHouseList方法:

// 渲染每一行的内容
  renderHouseList = ({
    key, // Unique key within array of rows
    index, // 索引号
    style // 重点属性:一定要给每一个行数添加该样式
  }) => {
    // 当前这一行的
    const { list } = this.state;
    const house = list[index];
    // 如果不存在,需要渲染loading元素占位
    if (!house) {
      return (
        <div key={key} style={style}>
          <p className={styles.loading}></p>
        </div>
      );
    }
    return (
      ...
    );
  };

三、列表找房模块-吸顶功能

3.1 实现思路

1、在页面滚动的时候,判断筛选栏上边是否还在可视区域内
2、如果在,不需要吸顶
3、如果不在,就吸顶
4、吸顶之后,我们用一个跟筛选栏相同的占位元素,在筛选栏脱标后,代替它撑起高度

3.2 实现步骤

1、封装Sticky组件
2、在HouseList页面中,导入Sticky组件
3、使用Sticky组件包裹要实现吸顶功能的Filter组件
4、在Sticky组件中,创建两个ref对象(placeholder,content),分别指向占位元素和内容元素
5、在Sticky组件中,监听浏览器的scroll事件
6、在scroll事件中,通过getBoundingClientRect()方法得到筛选栏占位元素当前位置
7、判断top是否小于0(是否在可视区内)
8、如果小于,就添加需要吸顶样式(fixed),同时设置占位元素高度
9、否则,就移除吸顶样式,同时让占位元素高度为0

3.3 代码示例

在src/components/Sticky/index.js中添加如下代码:

class Sticky extends Component {
  // 创建ref对象
  placeholder = createRef()
  content = createRef()

  // scroll 事件的事件处理程序
  handleScroll = () => {
    const { height } = this.props
    // 获取DOM对象
    const placeholderEl = this.placeholder.current
    const contentEl = this.content.current

    const { top } = placeholderEl.getBoundingClientRect()
    if (top < 0) {
      // 吸顶
      contentEl.classList.add(styles.fixed)
      placeholderEl.style.height = `${height}px`
    } else {
      // 取消吸顶
      contentEl.classList.remove(styles.fixed)
      placeholderEl.style.height = '0px'
    }
  }

  // 监听 scroll 事件
  componentDidMount() {
    window.addEventListener('scroll', this.handleScroll)
  }

  componentWillUnmount() {
    window.removeEventListener('scroll', this.handleScroll)
  }

  render() {
    return (
      <div>
        {/* 占位元素 */}
        <div ref={this.placeholder} />
        {/* 内容元素 */}
        <div ref={this.content}>{this.props.children}</div>
      </div>
    )
  }
}

Sticky.propTypes = {
  height: PropTypes.number.isRequired
}

export default Sticky
在src/pages/HouseList/index.js中添加如下代码:

        {/* 条件筛选栏 */}
        <Sticky height={40}>
          <Filter onFilter={this.onFilter} />
        </Sticky>

四、列表找房模块-整体优化

4.1 加载提示

需求1:实现加载房源数据时:加载中、加载完成的提示(需要解决:没有房源数据时,不弹提示框)
解决方案:判断一下count是否为0,如果为0,就不加载提示信息

需求2:找不到房源数据时候的提示(需要解决:进入页面就展示该提示的问题)
解决方案:判断一下是否是第一次进入,可以用一个变量来进行记录,然后只要进行了数据请求,就把这个标识更改

4.2 条件筛选栏优化

问题1:点击条件筛选栏,展示FilterPicker组件时,样式错乱问题(需要解决:样式问题)
解决方案:把FilterPicker组件修改成绝对定位,脱标了,就不会挤下面的结构了

问题2:使用条件筛选查询数据时,页面没有回到顶部(需要解决:每次重新回到页面顶部)
解决方案:在点击条件查询确定按钮的时候,利用window.scroll(0 ,0) 来回到页面顶部

问题3:点击条件筛选栏,展示对话框后,页面还会滚动(需要解决:展示对话框后页面不滚动)
解决方案:展示对话框的时候,给body添加 overflow: hidden 的样式,这样页面就不能进行滚动,等到对话框消失的时候,取消body的overflow: hidden 样式

4.3 切换城市显示房源优化

问题:切换城市时,该页面无法展示当前定位城市名称和当前城市房源数据,刷新页面后才会生效(需要解决:切换城市后立即生效)
解决方案:在组件的 componentDidMount构造函数中,进行获取当前定位城市名称

五、使用react-spring动画库实现动画效果

5.1 概述

使用场景:展示筛选对话框的时候,实现动画效果,增强用户体验
介绍:react-spring是基于spring-physics(弹簧物理)的react动画库,动画效果更加流畅、自然
优势:
    * 几乎可以实现任意UI动画效果
    * 组件式使用方式(render-props模式),简单易用,符合react的声明式特性,性能高

5.2 基本使用

使用步骤:

1、安装: yarn add react-spring
2、打开Spring组件文档
3、导入Spring组件,使用Spring组件包裹要实现动画效果的遮罩层div
4、通过render-props模式,将参数props设置为遮罩层div的style
5、给Spring组件添加from属性,指定:组件第一次渲染时的动画状态
6、给Spring组件添加to属性,指定:组件要更新的新动画状态
7、props就是透明度有0~1中变化的值

5.3 实现遮罩层动画

实现步骤:

1、创建方法 renderMask来渲染遮罩层 div
2、修改渲染遮罩层的逻辑,保证Spring组件一直都被渲染(Spring组件被销毁了,就无法实现动画效果)
3、修改to属性的值,在遮罩层隐藏时为0,在遮罩层展示为1
4、在render-props的函数内部,判断props.opacity是否等于0
5、如果等于0,就返回null,解决遮罩层遮挡页面导致顶部点击事件失效
6、如果不等于0,渲染遮罩层div

代码示例:
在src/pages/HouseList/index.js中添加如下代码:

renderMask() {
    const { openType } = this.state
    const isHide = openType === 'more' || openType === ''

    return (
      <Spring from={{ opacity: 0 }} to={{ opacity: isHide ? 0 : 1 }}>
        {props => {
          // 说明遮罩层已经完成动画效果,隐藏了
          if (props.opacity === 0) {
            return null
          }

          return (
            <div
              style={props}
              className={styles.mask}
              onClick={() => this.onCancel(openType)}
            />
          )
        }}
      </Spring>
    )

总结

你可能感兴趣的:(React项目,react.js,javascript,ecmascript,前端,前端框架)