React八案例上

代码下载

技术栈:

  • React 核心库:react、react-dom、react-router-dom
  • 脚手架:create-react-app
  • 数据请求:axios
  • UI组件库: antd-mobile
  • 其他组件库: react-virtualized、formik+yup、react-spring 等
  • 百度地图API

项目搭建

部署本地接口

后台项目这里不做详细记录,只为辅助前端开发,这是后台项目下载地址。

  1. 将后台项目下载到本地,使用MySQLWorkbench新建名为 hkzf 的数据库,字符集选择 utf8,其他默认。
  2. 在MySQLWorkbench中,点击 File -> Open SQL Script…,然后选中db文件夹中的 hkzf_db.sql 文件打开,双击选中mydb数据库并点击 open,点击文档编辑区域上方闪电图标执行。
  3. 使用VSCode打开项目,执行 npm i 安装依赖库,进入 config/mysql.js 文件修改 module.exports 中的 password 为自己的数据库密码。
  4. 执行 npm start 运行后台项目,此时可以使用 Postman 测试相应接口,也可以在浏览器中直接打开接口文档地址 http://localhost:8080

初始化项目

1、初始化项目:npx create-react-app react_hkzf_mobile

2、启动项目,在项目根目录执行命令 npm start,主要结构说明:

结构说明
public公共资源
public/index.html首页(必须有)
public/manifest.jsonPWA应用的元数据
src项目源码,写项目功能代码
src/index.js项目入口文件(必须有)
src/App.js项目的根组件
src/setupTests.jsApp组件的测试文件
src/reportWebVitals.js用来实现PWA(可选)

3、项目中 src 目录增加结构如下:

结构说明
assets/资源(图片、字体图标等)
components/公共组件
pages/页面
utils/工具

antd-mobile 组件库 介绍

antd-mobile 中文文档

安装:

    npm i antd-mobile

导入要使用的组件,渲染组件:

    import { Button } from 'antd-mobile';function App() {return (<div className="App"><Button>按钮</Button></div>);}

配置基础路由

1、安装,在项目根目录执行 npm i react-router-domyarn add react-router-dom

2、在 src/index.html 中导入路由组件:

    import { BrowserRouter as Router, Routes, Route, Link, Navigate } from "react-router-dom";

3、在 pages 文件夹中创建 Home.jsHouse.jsNews.jsProfile.jsCityList.js 五个组件

项目整体布局有两种:

  • 有TabBar的页面: 首页、找房、资讯、我的
  • 无TabBar的页面:城市选择等

请添加图片描述

TabBar 的菜单也可以实现路由切换,也就是路由内部切换路由(嵌套路由)。用 App 组件表示父路由的内容,用 Home、House、News 和 Profile 组件表示子路由的内容。

4、在 App 组件中,添加一个Route作为子路由的出口:

    function App() {return (<>App<Outlet></Outlet></>)}

5、在入口文件 index.js 中设置路由,子路由以父路由path开头(父组件展示了,子组件才会展示),修改 pathname 为 /home,Home 组件的内容就会展示在 APP 组件中了:

        <Router><Routes>{/* 父路由 */}<Route path='/' element={<App></App>}>{/* 子路由 */}<Route path='/home' element={<Home></Home>}></Route><Route path='/house' element={<House></House>}></Route><Route path='/news' element={<News></News>}></Route><Route path='/profile' element={<Profile></Profile>}></Route></Route><Route path='/cityList' element={<CityList></CityList>}></Route></Routes></Router>

外观和样式调整

1、修改页面标题,在index.html里面修改:

        <title>React 好客租房案例</title>

2、基础样式调整,在 index.css 中调整 css 样式布局:

    html, body {height: 100%;font-family: 'Microsoft YaHei';color: #333;background-color: #fff;margin: 0;padding: 0;}* {box-sizing: border-box;}

实现TabBar

打开 antd-mobile 组件库中TabBar的组件文档,选中配合路由使用拷贝核心代码到 App 组件中(App是父路由组件)并调整代码:

    function App() {const tabs = [{key: '/home',title: '首页',icon: 'icon-ind',},{key: '/house',title: '找房',icon: 'icon-findHouse',},{key: '/news',title: '资讯',icon: 'icon-infom',},{key: '/profile',title: '我的',icon: 'icon-my',},]const location = useLocation()const navigate = useNavigate()console.log('-----', location.pathname);return (<><Outlet></Outlet><TabBar activeKey={location.pathname} defaultActiveKey='home' safeArea onChange={(key) => navigate(key)}>{tabs.map(item => (<TabBar.Item key={item.key} icon={(active) => active ? <i className={`active iconfont ${item.icon}`} /> : <i className={`iconfont ${item.icon}`} />} title={(active) => active ? <span className={`active`}>{item.title}</span> : <span>{item.title}</span>} />))}</TabBar></>)}

修改 TabBar 组件 css 样式布局:

    .active {color: #21b97a;}.iconfont {font-size: 20px;}.adm-tab-bar {position: fixed;bottom: 0;width: 100%;}

设置路由重定向:

        <Router><Routes>{/* 路由重定向 */}<Route path='/' element={<Navigate to='/home' replace></Navigate>}></Route>{/* 父路由 */}<Route path='/' element={<App></App>}>{/* 子路由 */}<Route path='/home' element={<Home></Home>}></Route><Route path='/house' element={<House></House>}></Route><Route path='/news' element={<News></News>}></Route><Route path='/profile' element={<Profile></Profile>}></Route></Route><Route path='/cityList' element={<CityList></CityList>}></Route></Routes></Router>

首页模块

轮播图

打开 antd-mobile 组件库的 Swiper 走马灯 组件文档,选择循环拷贝核心代码到 Home 组件中。并优化相应的结构,删除不必要的代码:

    mport {Swiper, Toast } from 'antd-mobile'import './Home.css'const colors = ['#ace0ff', '#bcffbd', '#e4fabd', '#ffcfac']const items = colors.map((color, index) => (<Swiper.Item key={index}><divclassName='content'onClick={() => {Toast.show(`你点击了卡片 ${index + 1}`)}}>{index + 1}</div></Swiper.Item>))export default function Home() {return (<><SwiperloopautoplayonIndexChange={i => {console.log(i, 'onIndexChange1')}}>{items}</Swiper></>)}

修改轮播图相关 css 样式布局:

    /* 轮播图 */.content {height: 212px;color: #ffffff;display: flex;justify-content: center;align-items: center;font-size: 48px;user-select: none;img {height: 212px;}}.adm-page-indicator {--active-dot-size: 3px;}
获取轮播图的数据

1、安装 axios: npm i axios

2、新建 utils > useData.js 文件,在这里配置 axios,并自定义获取网络数据的 Hook:

    import { useState, useEffect } from "react";import axios from "axios";// 全局配置axios.defaults.timeout = 10000 // 超时时间axios.defaults.baseURL = 'http://127.0.0.1:8080' // 域名// 响应拦截器axios.interceptors.response.use((res) => {console.log('data: ', res);return res.data}, (error) => {console.log('error: ', error);})function useNetwork(url, stringParams, type) {    const [data, setData] = useState(null)const [error, setError] = useState(null)const [loading, setLoading] = useState(true)useEffect(() => {console.log('stringParams:', stringParams);let ignore = falseconst request = async () => {try {let result = nullswitch (type) {case 'GET': result = await axios.get(url, stringParams && JSON.parse(stringParams))break;case 'POST': result = await axios.post(url, stringParams && JSON.parse(stringParams))breakdefault:break;}if (!ignore && result) {setData(result)}setLoading(false)} catch (error) {if (!ignore) {setError(error)setLoading(false)}}}request()return () => { ignore = true setLoading(false)}}, [url, stringParams, type])return { data, error, loading }}function useGet(url, params) {return useNetwork(url, JSON.stringify(params), 'GET')}function usePost(url, params) {return useNetwork(url, JSON.stringify(params), 'POST')}const useData = { get: useGet, post: usePost }export default useData

说明:React Effect 使用 Object.is 比较依赖项的值,如果依赖项为 对象,则比较的是是否在内存中为同一对象。自定义 Hook useNetwork 中的请求参数需要比较的是参数内容(值)是否相同,所以将参数转化为字符串作为Effect依赖性。

3、在 Home 组件中,导入 useData 获取数据并优化调整代码结构:

    import useData from '../utils/useData'// 渲染轮播图function renderSwiper(data, indexChange, indexClick) {return data && <SwiperloopautoplayonIndexChange={ indexChange }>{data.body.map((item, index) => (<Swiper.Item key={index}><divclassName='content'style={{ background: 'red' }}onClick={ (e) => indexClick(index, e) }><img src={'http://127.0.0.1:8080' + item.imgSrc} style={{width: '100%'}} alt=''></img></div></Swiper.Item>))}</Swiper>}export default function Home() {const { data: swiperData } = useData.get('/home/swiper')console.log('swiperData: ', swiperData);return (<>{/* 轮播图 */}{ renderSwiper(swiperData, i => console.log('indexChange: ', i), i => Toast.show(`你点击了卡片 ${i + 1}`)) }</>)}

导航菜单

1、导入图片:

    // 导入所需图片import nav1 from "../assets/images/nav-1.png";import nav2 from "../assets/images/nav-2.png";import nav3 from "../assets/images/nav-3.png";import nav4 from "../assets/images/nav-4.png";

2、构建导航菜单数据:

    // 构建导航菜单数据const navData = [{id: 1,img: nav1,title: '整租',path: '/house'},{id: 2,img: nav2,title: '合租',path: '/house'},{id: 3,img: nav3,title: '地图找房',path: '/map'},{id: 4,img: nav4,title: '去出租',path: '/rent/add'}]

3、编写页面内容:

    // 渲染导航菜单function renderNav(data, onClick) {return (<div className='home-nav'>{data.map((item) => {return <div className='home-nav-item' key={item.id} onClick={() => onClick(item)}><img src={item.img} alt=''></img><p>{item.title}</p></div>})}</div>)}

4、调整 css 样式布局:

    /* 导航菜单 */.home-nav {display: flex;margin: 16px 0;}.home-nav-item {flex-grow: 1;text-align: center;}.home-nav-item img {width: 48px;}.home-nav-item p {font-size: 14px;margin: 0;}

5、调用渲染方法:

            {/* 导航菜单 */}{ renderNavs(navData, item => navigate(item.path)) }
轮播图的问题

由于动态加载数据,有了数据才去渲染轮播图,轮播图从无到有导致轮播图下方的内容会产生一个从上被挤到下面的现象。

解决办法就是在轮播图的外层加一个div,给这个div设置高度:

    // 渲染轮播图function renderSwiper(data, indexChange, indexClick) {return <div className='swiper'>{data && <SwiperloopautoplayonIndexChange={ indexChange }>{data.body.map((item, index) => (<Swiper.Item key={index}><divclassName='content'onClick={ (e) => indexClick(index, e) }><img src={'http://127.0.0.1:8080' + item.imgSrc} style={{width: '100%'}} alt=''></img></div></Swiper.Item>))}</Swiper>}</div>}/* 轮播图 css */.swiper {height: 212px;}

Sass的使用

  • 打开脚手架文档,找到添加Sass样式
  • 安装Sass: npm i sassyarn add sass
  • 创建后缀名为.scss 或者 .sass 的样式文件
  • 在组件中导入Sass样式

修改 Home.cssHome.scss 文件,一并修改 Home 组件的导入 import './Home.scss',并修改 导航菜单 为如下样式:

    /* 导航菜单 */.home-nav {display: flex;margin: 16px 0;.home-nav-item {flex-grow: 1;text-align: center;img {width: 48px;}p {font-size: 14px;margin: 0;}}}

租房小组

根据当前地理位置展示不同小组信息,需要后台接口根据用户找房数据,推荐用户最感兴趣的内容(正常的逻辑是我们先获取到用户当前定位的信息,把信息发送给后台,后台根据定位信息获取对应的内容),前端只需要展示数据。

数据获取

在 Home 组件中调用接口获取数据:

        // 获取租房小组数据const { data: groupData } = useData.get('/home/groups', {params: {area: 'AREA%7C88cff55c-aaa4-e2e0'}})console.log('groupData: ', groupData);
页面结构

1、打开 antd-mobile 组件库的 Grid 组件文档,复杂核心代码并调整,封装为渲染函数:

    // 渲染租房小组function renderGroup(data) {return <div className='group'><div className='top'><div className='name'>租房小组</div><div className='more'>更多</div></div><Grid columns={2} gap={[32, 16]}>{data && data.body.map((item) => {return <Grid.Item key={item.id}><div className='item'><div className='left'><div className='title'>{item.title}</div><div className='desc'>{item.desc}</div></div><div className='right'><img className='picture' src={`http://127.0.0.1:8080${item.imgSrc}`} alt=''></img></div></div></Grid.Item>})}</Grid></div>}

2、css 样式布局:

    // 租房小组.group {margin: 8px 0;.top {display: flex;justify-content: space-between;margin: 16px;.name {font-size: 15px;font-weight: bold;}.more {font-size: 14px;color: #999999;}}.adm-grid {margin: 8px 16px;.adm-grid-item {height: 75px;.item {text-align: center;height: 100%;display: flex;justify-content: space-between;text-align: center;padding: 0 8px;.left {display: flex;flex-direction: column;justify-content: center;.title {font-size: 13px;font-weight: bold;margin-bottom: 4px;}.desc {font-size: 12px;color: #999999;}}.right {display: flex;flex-direction: column;justify-content: center;.picture {width: 55px;height: 53px;}}}}}}

3、在 Home 组件中调用渲染函数:

            {/* 租房小组 */}{ renderGroup(groupData)}

最新资讯

略……(同租房小组)

解决内容被 TabBar 压住的问题

1、在 App 组件中将 Outlet 路由占位包裹在一个 div 中:

        <div className='app-content'><Outlet></Outlet></div>

2、调整相应样式,设置 app 底部 49 像素的内边距,让 app-content 超出父元素的内容滚动显示:

    #root {height: 100%;}.app {height: 100%;padding-bottom: 49px;}.app-content {height: 100%;overflow: scroll;}

顶部搜索功能

1、相关结构:

    // 渲染顶部搜索function renderHeaderSearch(cityName, onClickLoction, onClickSearch, onClickMap) {return <div className='headerSearch'><div className='search'><div className='location' onClick={onClickLoction}><span className="name">{cityName}</span><i className="iconfont icon-arrow" /></div><div className='form' onClick={onClickSearch}><i className="iconfont icon-seach" /><span className="text">请输入小区或地址</span></div></div><div className="iconfont icon-map" onClick={onClickMap}></div></div>}

2、css 样式布局:

    // 顶部搜索.headerSearch {position: absolute;padding: 0 16px;width: 100%;height: 44px;top: 34px;display: flex;justify-content: space-between;align-items: center;.search {height: 100%;padding: 0 8px;font-size: 14px;border-radius: 3px;background-color: white;margin-right: 8px;flex-grow: 1;display: flex;align-items: center;.location {i {color: #7f7f80;font-size: 12px;margin-left: 2px;}}.form {margin-left: 24px;color: #9c9fa1;flex-grow: 1;display: flex;span {margin-left: 8px;}}}.icon-map {font-size: 25px;color: white;}}

3、在 Home 组件中调用渲染函数:

            {/* 顶部搜索 */}{ renderHeaderSearch('天津', () => navigate('/cityList'), () => navigate('/search'), () => navigate('/map')) }

定位相关

H5中地理定理定位API

在 Web 应用程序中获取地理位置(文档)

地理位置API 允许用户向 Web应用程序提供他们的位置,出于隐私考虑,报告地理位置前先会请求用户许可,地理位置的API是通过 navigator.geolocation 对象提供,通过getCurrentPosition方法获取。

获取到的地理位置跟 GPS、IP地址、WIFI和蓝牙的MAC地址、GSM/CDMS的ID有关,比如:手机优先使用GPS定位,笔记本等最准确的是定位是WIFI。

    navigator.geolocation.getCurrentPosition(position => {// position对象表示当前位置信息// 常用: latitude 纬度 / longitude 经度// 知道: accuracy 经纬度的精度 / altitude 海拔高度 / altitudeAccuracy 海拔高度的精度 / heading 设备行进方向 / speed 速度})
百度地图API

H5的地理位置API只能获取到对应经纬度信息,在实际开发中,会使用百度地图/高德地图来完成地理位置的相关功能。

1、参照百度地图文档,注册百度开发者账号,申请对应的AK

2、在 index.html 中引入百度地图 API 文件,替换自己申请好的密钥:

    <script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=你的密钥"></script>

3、新建 Map 组件文件,创建地图容器等基本结构,并参照文档创建地图:

    import { useEffect } from "react";import './Map.scss'export default function Map() {// 创建地图useEffect(() => {var map = new window.BMapGL.Map("container");          // 创建地图实例 var point = new window.BMapGL.Point(116.404, 39.915);  // 创建点坐标 map.centerAndZoom(point, 15);                 // 初始化地图,设置中心点坐标和地图级别map.enableScrollWheelZoom(true);     //开启鼠标滚轮缩放return () => map = null}, [])return <div className="map"><div id="container"></div></div>}

4、css 样式布局:

    .map {height: 100%;#container {height: 100%;}}

5、在 index.js 中配置地图页面的路由:

            <Route path='/map' element={<Map></Map>}></Route>
获取顶部搜索城市信息

1、通过IP定位获取到当前城市名称,调用服务器的接口,换取项目中的城市信息:

        // 百度地图 IP 定位,转换城市数据const [ city, setCity ] = useState(null);useEffect(() => {var ignore = falsevar localCity = new window.BMapGL.LocalCity();localCity.get((result) => {if (!ignore) {console.log('LocalCity:', result);axios.get('area/info?name=' + result.name).then((cityData) => {if (!ignore) {console.log('cityData:', cityData);setCity(cityData.body.label)}})}}); return () => ignore = true }, [])

2、修改顶部搜索栏渲染方法的调用,在城市信息获取之前使用 -- 占位符:

            {/* 顶部搜索 */}{ renderHeaderSearch(city ? city : '--', () => navigate('/cityList'), () => navigate('/search'), () => navigate('/map')) }

城市选择模块

顶部导航栏

  • 打开antd-mobile 组件库的NavBar 导航栏组件 文档
  • 从文档中拷贝组件示例代码到项目中,让其正确运行
  • 修改导航栏样式和结构

1、引入 组件库:

    import { NavBar } from 'antd-mobile'import { useNavigate } from "react-router-dom";

2、拷贝并修改代码结构:

    function CityList() {const navigate = useNavigate()return (<div className='city-list'>{/* 导航栏 */}<NavBar style={{'--height': '44px','--border-bottom': '1px #eee solid','color': '#333','backgroundColor': '#f6f5f6'}} backIcon={<i className='iconfont icon-back'></i>} onBack={() => { navigate(-1)}}>城市列表</NavBar></div>)}

3、新建并导入样式文件:

    import './CityList.scss'

4、设置相应的样式:

    .city-list {padding-top: 44px;width: 100%;height: 100%;// 导航栏样式.adm-nav-bar {margin-top: -44px;}.adm-nav-bar-title {color: #333;}}

城市列表

获取处理数据

1、导入自定义获取数据的 HOOK import useData from '../utils/useData',根据接口文档提供的url进行网络请求,获取到相应的数据信息:

        // 获取城市列表数据const { data: cityData } = useData.get('/area/city', {params:{'level': '1'}})console.log('cityData: ', cityData);

2、需要把服务器返回的数据进行格式化处理,可以通过首字母来进行城市的定位,所以需要把格式转换成以下格式:

    // 接口返回的数据格式:[{ "label": "北京", "value": "", "pinyin": "beijing", "short": "bj" }]// 渲染城市列表的数据格式为:{ a: [{}, {}], b: [{}, ...] }// 渲染右侧索引的数据格式为:['a', 'b']

3、封装一个函数,来处理这个数据:

    // 城市列表数据处理function cityDataHandle(data) {if (data && data.body && Array.isArray(data.body) && data.body.length > 0) {// 有数据// 键是首字母,值是一个数组:对应首字母的城市信息const cityList = {}data.body.forEach(element => {const firstL = element.short[0]if (cityList[firstL]) {cityList[firstL].push(element)} else {cityList[firstL] = [element]}});const result = { cityList, cityKeys: Object.keys(cityList).sort() }return result} else {return {}}}

4、调用函数,来格式化数据:

        // 城市列表数据处理const { cityList, cityKeys } = cityDataHandle(cityData)
获取热门数据

导入所需 HOOK import { useEffect, useState } from "react";,获取数据并添加到 cityListcityKeys中,注意,对象里面的属性是无序的,可以直接插入,但是数组是有序的需要添加到前面:

        // 获取热门城市数据const { data: hotData } = useData.get('/area/hot')console.log('hotData: ', hotData);if (cityList && cityKeys && hotData && hotData.body && Array.isArray(hotData.body) && hotData.body.length > 0) {cityList['hot'] = hotData.bodycityKeys.unshift('hot')}
获取当前城市信息

将获取定位城市的代码封装到一个函数中,哪个页面需要获取定位城市,直接调用该方法即可:

  • 在utils目录中,创建一个文件,在这个文件中进行封装
  • 创建并且导出获取定位城市的函数 getCurrentCity
  • 判断localStorage中是否有定位信息
  • 如果没有,我们通过获取定位信息来获取当前定位城市,获取完了需要存到本地存储中
  • 如果有,直接使用就好
    import axios from "axios";export default function requestCurrentCity() {// 获取本地存储中是否有const localCity = localStorage.getItem('localCity')console.log('localCity', localCity);if (localCity) {// 如果有,返回城市信息就好,返回一个成功的promis对象即可return Promise.resolve(JSON.parse(localCity))} else {return new Promise((resolve, reject) => {var localCity = new window.BMapGL.LocalCity();localCity.get(async (result) => {console.log('LocalCity:', result);try {const city = await axios.get('area/info?name=' + result.name)console.log('city: ', city);if (city.status === 200) {localStorage.setItem('localCity', JSON.stringify(city.body))resolve(city.body)} else {console.error(city.description);throw new Error(city.description);}} catch (error) {reject(error)}}); })}}

将定位的城市信息添加到 cityListcityIndex中:

        // 获取当前城市const [currentCity, setCurrentCity] = useState(null)useEffect(() => {let ignore = falserequestCurrentCity().then((data) => {if (!ignore) {setCurrentCity(data)}})return () => ignore = true}, [])if (currentCity && cityList) {cityList['#'] = [currentCity]cityKeys.unshift('#')}console.log('cityList: ', cityList);console.log('cityKeys: ', cityKeys);
长列表性能优化

在展示大型列表和表格数据的时候(城市列表、通讯录、微博等),会导致页面卡顿,滚动不流畅等性能问题,这样就会导致移动设备耗电加快,影响移动设备的电池寿命

产生性能问题的原因:大量DOM节点的重绘和重排

优化方案:懒渲染、可视区域渲染

懒加载,常见的长列表优化方案,常见于移动端:

  • 原理:每次只渲染一部分,等渲染的数据即将滚动完时,再渲染下面部分
  • 优点:每次渲染一部分数据,速度快
  • 缺点:数据量大时,页面中依然存在大量DOM节点,占用内存过多,降低浏览器渲染性能,导致页面卡顿
  • 使用场景:数据量不大的情况下

可视区渲染(React-virtualized):

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

react-virtualized 是React组件,用来高效渲染大型列表和表格数据,GitHub地址: react-virtualized

1、安装: import i react-virtualized

2、在项目入口文件 index.js 中导入样式文件 import "react-virtualized/styles.css";

3、打开 文档, 点击List组件,进入List的文档中,拷贝示例中渲染每一行的代码到项目中并按需求修改:

        // 渲染每一行function rowRenderer({index, // 索引号isScrolling, // 当前项是否正在滚动中isVisible, // 当前项在List中是可见的key, // 渲染数组中行的唯一键parent, // 对父List(实例)的引用style, // 重点属性:一定要给每一个行数添加该样式}){let title = cityKeys[index]const citys = cityList[title]switch (title) {case '#':title = '当前定位'break;case 'hot':title = '热门城市'breakdefault:break;}return (<div key={key} style={style} className='city'><div className='title'>{title}</div>{citys.map((item, i) => {return <div className='name' key={item.value}>{item.label}</div>})}</div>);}

4、渲染城市列表,利用 AutoSizer 组件来调整子元素的宽高

  • 导入 AutoSizerList 组件 import { List, AutoSizer } from "react-virtualized";
  • 通过 render-props 模式,获取到AutoSizer 组件暴露的 width 和 height 属性
  • 设置List组件的 width 和 height 属性
            {/* 城市列表 */}{ cityKeys && <AutoSizer>{ ({width, height}) => {return <Listwidth={width}height={height}rowCount={cityKeys.length}rowHeight={({index}) => {console.log('index: ', index);const key = cityKeys[index]const section = cityList[key]console.log('section: ', section);return 20 + 44*section.length}}rowRenderer={rowRenderer}/>} }</AutoSizer> }

5、为城市列表设置样式:

    // 城市列表样式.city {.title {height: 20px;background-color: #e5e5e5;padding: 0 12px;font-size: 12px;line-height: 20px;}.name {height: 44px;padding: 0 16px;font-size: 14px;line-height: 44px;}}
渲染右侧索引列表

1、添加状态 activeIndex,用来指定当前高亮的索引:

        // 高亮索引const [activeIndex, setActiveIndex] = useState(0)

2、遍历cityIndex,渲染索引列表,将索引 hot 替换成

            {/* 右侧索引 */}{ cityKeys && <ul className='city-index'>{cityKeys.map((item, index) => <li className={activeIndex === index ? 'active' : ''} key={item}>{item === 'hot' ? '热' : item}</li>)}</ul> }

3、为索引列表添加样式:

    // 城市索引列表样式.city-index {position: absolute;right: 8px;height: 90%;list-style: none;margin: 0;padding: 5px;text-align: center;display: flex;flex-direction: column;justify-content: space-around;.active {color: #fff;background-color: #21b97a;width: 15px;height: 15px;font-size: 12px;text-align: center;line-height: 15px;border-radius: 100%;}}

4、城市索引列表高亮

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

                        onRowsRendered={({startIndex}) => {console.log('startIndex: ', startIndex);if (startIndex !== activeIndex) {setActiveIndex(startIndex)}}}

5、点击索引置顶该索引城市

引入 useRef import { useRef } from "react";,并在组件顶部初始化 ref:

        // 列表 refconst listRef = useRef(null)

将创建好的ref对象,添加为List组件的ref属性,设置List组件的scrollToAlignment配置项值为start,保证点击行出现在页面顶部:

                    <List……scrollToAlignment='start'ref={listRef}/>

给索引列表绑定点击事件,在点击事件中。通过 index 获取到当前项索引号通过 ref 的 current 属性,获取到组件实例,再调用组件的scrollToRow方法让List组件滚动到指定行:

            {/* 右侧索引 */}{ cityKeys && <ul className='city-index'>{cityKeys.map((item, index) => <li className={activeIndex === index ? 'active' : ''} key={item} onClick={() => {listRef.current.measureAllRows()listRef.current.scrollToRow(index)}}>{item === 'hot' ? '热' : item}</li>)}</ul> }

对于点击索引无法正确定位的问题,在List组件调用 measureAllRows 方法之前调用 measureAllRows,提前计算高度来解决。

切换城市
  • 给城市列表项绑定事件
  • 判断当前城市是否有房源数据,只有热门城市有房源数据
  • 如果有房源数据,则保持当前城市数据到本地缓存中,并返回上一页
  • 如果没有房源数据,则使用antd-mobile中的 Toast 组件提示用户:改城市暂无房源数据,不执行任何操作
        // 渲染每一行function rowRenderer({index, // 索引号isScrolling, // 当前项是否正在滚动中isVisible, // 当前项在List中是可见的key, // 渲染数组中行的唯一键parent, // 对父List(实例)的引用style, // 重点属性:一定要给每一个行数添加该样式}){let title = cityKeys[index]const citys = cityList[title]switch (title) {case '#':title = '当前定位'break;case 'hot':title = '热门城市'breakdefault:break;}return (<div key={key} style={style} className='city'><div className='title'>{title}</div>{citys.map((item, i) => {return <div className='name' key={item.value} onClick={() => {const hotList = cityList['hot']let contain = falsefor (const i in hotList) {const v = hotList[i]if (v.label === item.label) {contain = truebreak}}if (contain) {// 热门城市才有房源数据localStorage.setItem('localCity', JSON.stringify({'label': item.label, 'value': item.value}))navigate(-1)} else {Toast.show({ content: '该城市暂无房源数据', duration: 2000 })}}}>{item.label}</div>})}</div>);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/78886.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

线代[13]|线性代数题37道以及数学分析题3道(多图预警)

博主首次发布于CSDN&#xff0c;禁止转载&#xff01;&#xff08;CSDN&#xff1a;汉密士2025&#xff09; 文章目录 一、缘起&#xff5c;《俗说矩阵》课程目录照片存档&#xff5c;线性代数学习脉络&#xff5c;线代习题集封面存档&#xff5c;未来——我与线性代数的纠缠 二…

OpenCV 图形API(24)图像滤波-----双边滤波函数bilateralFilter()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 应用双边滤波到图像。 该函数对输入图像应用双边滤波&#xff0c;如 http://www.dai.ed.ac.uk/CVonline/LOCAL_COPIES/MANDUCHI1/Bilateral_Fil…

AI与5G的融合:如何实现更快速、更智能的物联网应用?

引言 AI和5G的结合&#xff0c;正在加速物联网&#xff08;IoT&#xff09;应用的发展&#xff0c;让万物互联变得更加智能、高效。5G提供超高速率、低时延和海量连接的网络能力&#xff0c;而AI则赋予物联网设备更强的数据分析、预测和自动决策能力。当AI与5G融合&#xff0c;…

在ArcGIS Pro中将栅格NoData值修改为特定值

目录 问题如下&#xff1a;栅格文件中NoData值为65535&#xff0c;要将该NoData值修改为-9999 步骤一&#xff1a;使用栅格计算器&#xff08;Raster Calculator&#xff09;输出具有新NoData值的栅格文件 步骤二&#xff1a;输出修改值后的栅格文件&#xff08;Export Rast…

蓝牙连接hci 命令和事件的交互

参考&#xff1a;在HCI层看蓝牙的连接过程_hci 获取蓝牙pin码-CSDN博客 我这边查看的是core 5.2 一、数据交互流程 1、ACL连接建立后的可选流程 参考蓝牙core5.2: vol2 --> PartF --> 4 1.1 AUTHENTICATION REQUESTED Authentication can be explicitly executed at …

【计算机网络实践】(十二)大学校园网综合项目设计

本系列包含&#xff1a; &#xff08;一&#xff09;以太网帧分析与网际互联协议报文结构分析 &#xff08;二&#xff09;地址解析协议分析与传输控制协议特性分析 &#xff08;三&#xff09;交换机的基本操作、配置、 虚拟局域网配置和应用 &#xff08;四&#xff09;交…

制造企业数据治理体系搭建与业务赋能实践

当下制造企业正面临着前所未有的机遇与挑战&#xff0c;从多环节业务协同的复杂性&#xff0c;到海量数据资源的沉睡与孤岛化&#xff1b;从个性化定制需求的爆发&#xff0c;到供应链效率优化的迫切性——如何通过数据治理将“数据包袱”转化为“数据资产”&#xff0c;已成为…

python高级编程一(生成器与高级编程)

@TOC 生成器 生成器使用 通过列表⽣成式,我们可以直接创建⼀个列表。但是,受到内存限制,列表容量肯定是有限的。⽽且,创建⼀个包含100万个元素的列表,不仅占⽤很⼤的存储空间,如果我们仅仅需要访问前⾯⼏个元素,那后⾯绝⼤多数元素占 ⽤的空间都⽩⽩浪费了。所以,如果…

智能指针之设计模式2

前面介绍了工厂模式控制了智能指针和资源对象的创建过程&#xff0c;现在介绍一下智能指针是如何利用代理模式来实现“类指针&#xff08;like-pointer&#xff09;”的功能&#xff0c;并控制资源对象的销毁过程的。 2、代理模式 代理模式是为其它对象提供一种代理以控制对这…

探索R语言:在线学习资源汇总

一、收集关于特定R主题的问题和答案&#xff08;Q&A&#xff09; 1. Stack overflow Empowering the world to develop technology through collective knowledge – Stack Overflowhttps://stackoverflow.co/ 二、Rstudio工具栏help Rstudio中有个Cheat sheet&#xf…

《C语言中以数组作为参数的探讨》

&#x1f680;个人主页&#xff1a;BabyZZの秘密日记 &#x1f4d6;收入专栏&#xff1a;C语言 &#x1f30d;文章目入 一、数组作为参数的传递机制二、数组参数的声明方式&#xff08;一&#xff09;省略数组大小&#xff08;二&#xff09;指定数组大小&#xff08;三&#x…

深入解析区块链技术:原理、应用与未来展望

1 区块链技术原理 1.1 基本概念 区块链本质上是一个分布式账本&#xff0c;它由一系列按照时间顺序排列的数据块组成&#xff0c;每个数据块包含了一定时间内的交易信息。这些数据块通过密码学技术相互链接&#xff0c;形成一个不可篡改的链条。其核心特点包括去中心化、不可篡…

selenium快速入门

一、操作浏览器 from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By# 设置选项 q1 Options() q1.add_argument("--no-sandbo…

面试如何应用大模型

在面试中,如果被问及如何应用大模型,尤其是面向政务、国有企业或大型传统企业的数字化转型场景,你可以从以下几个角度进行思考和回答: 1. 确定应用大模型的目标与痛点 首先,明确应用大模型的业务目标,并结合企业的实际需求分析可能面临的痛点。这些企业通常会关注如何提…

嵌入式常见概念的介绍

目录 一、MCU、MPU、ARM &#xff08;一&#xff09;MCU&#xff08;微控制器&#xff09; &#xff08;二&#xff09;MPU&#xff08;微处理器&#xff09; &#xff08;三&#xff09;ARM&#xff08;架构&#xff09; 二、DSP &#xff08;一&#xff09;数字信号处理…

深度强化学习(DRL)框架与多目标调度优化详解

深度强化学习&#xff08;DRL&#xff09;框架与多目标调度优化详解 &#xff08;截至2025年4月&#xff0c;结合最新研究进展&#xff09; 一、DRL主流框架及核心算法 通用DRL框架 Ray RLlib&#xff1a;支持分布式训练&#xff0c;集成PPO、A3C、DQN等算法&#xff0c;适用于…

centos 安装python3.9.9

这里写自定义目录标题 安装编译依赖 sudo yum -y groupinstall "Development Tools" sudo yum -y install openssl-devel bzip2-devel libffi-devel wget zlib-devel yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel …

【动态规划】深入动态规划:背包问题

文章目录 前言01背包例题一、01背包二、分割等和子集三、目标和四、最后一块石头的重量|| 完全背包例题一、完全背包二、 零钱兑换三、零钱兑换||四、完全平方数 前言 什么是背包问题&#xff0c;怎么解决算法中的背包问题呢&#xff1f; 背包问题 (Knapsack problem) 是⼀种组…

Vue 接口请求 Nginx配置实时压缩 速度起飞

生效之前 nginx配置如下 gzip on; gzip_min_length 1k; gzip_buffers 16 256k; gzip_http_version 1.1; gzip_comp_level 6; gzip_types application/json application/javascript text/javascript text/css text/plain; gzip_vary on; 生效之后 #user…

Mitosis:跨框架的UI组件解决方案

Mitosis 是一个开源工具&#xff0c;可以将 JSX 组件转换为 Angular、React、Qwik、Vue、Svelte、Solid 和 React Native 等框架的功能齐全的组件。 Stars 数13019Forks 数593 主要特点 跨框架兼容性&#xff1a;Mitosis 允许开发者编写一次组件&#xff0c;然后编译成多个主流…