实现步骤:
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、将筛选条件数据filters传递给父组件HouseList
2、HouseList组件中,创建方法onFilter,通过参数接收filters数据,并存储到this中
3、创建方法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、我们之前在地图找房模块以及实现了房源列表的渲染,先将其封装为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>
)
}
默认:List组件只让组件自身出现滚动条,无法让整个页面滚动,也就无法实现标题吸顶功能
解决方式:使用WindowScroller高阶组件,让List组件跟随页面滚动(为List组件提供状态,同时还需要设置List组件的autoHeight属性)
注意:WindowScroller高阶组件只能提供height,无法提供width
解决方式:在WindowScroller组件中使用AutoSizer高阶组件来为List组件提供width
在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>
1、使用InfiniteLoader 组件,来实现无限滚动列表,从而加载更多房屋数据
2、在loadMoreRows方法中,根据起始索引和结束索引,发送请求,获取更多房屋数据
3、获取到最新的数据后,与当前list中的数据合并,再更新state,并调用Promise的resolve
4、在renderHouseList方法中,判断house是否存在
5、不存在的时候,就渲染一个loading元素
6、存在的时候,再渲染HouseItem组件
在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 (
...
);
};
1、在页面滚动的时候,判断筛选栏上边是否还在可视区域内
2、如果在,不需要吸顶
3、如果不在,就吸顶
4、吸顶之后,我们用一个跟筛选栏相同的占位元素,在筛选栏脱标后,代替它撑起高度
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
在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>
需求1:实现加载房源数据时:加载中、加载完成的提示(需要解决:没有房源数据时,不弹提示框)
解决方案:判断一下count是否为0,如果为0,就不加载提示信息
需求2:找不到房源数据时候的提示(需要解决:进入页面就展示该提示的问题)
解决方案:判断一下是否是第一次进入,可以用一个变量来进行记录,然后只要进行了数据请求,就把这个标识更改
问题1:点击条件筛选栏,展示FilterPicker组件时,样式错乱问题(需要解决:样式问题)
解决方案:把FilterPicker组件修改成绝对定位,脱标了,就不会挤下面的结构了
问题2:使用条件筛选查询数据时,页面没有回到顶部(需要解决:每次重新回到页面顶部)
解决方案:在点击条件查询确定按钮的时候,利用window.scroll(0 ,0) 来回到页面顶部
问题3:点击条件筛选栏,展示对话框后,页面还会滚动(需要解决:展示对话框后页面不滚动)
解决方案:展示对话框的时候,给body添加 overflow: hidden 的样式,这样页面就不能进行滚动,等到对话框消失的时候,取消body的overflow: hidden 样式
问题:切换城市时,该页面无法展示当前定位城市名称和当前城市房源数据,刷新页面后才会生效(需要解决:切换城市后立即生效)
解决方案:在组件的 componentDidMount构造函数中,进行获取当前定位城市名称
使用场景:展示筛选对话框的时候,实现动画效果,增强用户体验
介绍:react-spring是基于spring-physics(弹簧物理)的react动画库,动画效果更加流畅、自然
优势:
* 几乎可以实现任意UI动画效果
* 组件式使用方式(render-props模式),简单易用,符合react的声明式特性,性能高
使用步骤:
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中变化的值
实现步骤:
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>
)