• React项目实战之租房app项目(五)顶部导航组件封装&CSS Modules解决样式覆盖&地图找房模块封装


    前言

    一、顶部导航组件封装

    1.1 基础布局

    实现步骤:

    1、封装NavHeader组件实现城市选择,地图找房页面的复用
    2、在components目录中创建组件 NavHeader/index.js
    3、在该组件中封装 antd-mobile 组件库中的 NavBar组件
    
    • 1
    • 2
    • 3

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

    import React from 'react';
    import {NavBar} from 'antd-mobile'
    
    export default class extends React.Component {
        render() {
            return (
                <NavBar className="navbar"
                        // 模式 默认值是 dark
                        mode="light"
                        // 左侧小图片
                        icon={<i className='iconfont icon-back' />}
                        // 左侧按钮的点击事件
                        onLeftClick={() => this.props.history.go(-1)}
                    // 标题内容不定的,所以我们通过外界来传入
                    >{this.props.children}</NavBar>
            )
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    1.2 样式调整

    在src/components下的NavHeader文件夹中创建 index.scss 文件
    把之前城市列表写过的样式,复制到这个文件下

    1.3 功能处理

    注意:默认情况下,只有路由 Route 直接渲染的组件才能够获取到路由信息,如果需要在其他组件中获取到路由信息,可以通过 withRouter 高阶组件来获取

    实现步骤:

    1、从 react-router-dom 中导入 withRouter 高阶组件
    2、使用 withRouter 高阶组件包装 NavHeader 组件
    3、从 props 中就能获取history对象,调用history对象的 go() 方法就能实现返回上一页功能了
    4、由于头部的左侧按钮不一定是返回上一个页面的功能,所以我们需要把左侧点击逻辑处理一下,改成需要通过父组件传递进来,如果说外界传递了,那么我们就直接使用外界的行为,如果没有传递,那么就用默认的行为
    
    • 1
    • 2
    • 3
    • 4

    代码示例:
    在src/components/NavHeader/index.js中修改如下代码:

    import React from 'react';
    import { NavBar } from 'antd-mobile'
    import './index.scss'
    import { withRouter } from 'react-router-dom'
    
    class NavHeader extends React.Component {
        render() {
            let defaultHandler = () => {this.props.history.go(-1)}
            return (
                <NavBar
                    className="navbar"
                    // 模式 默认值是 dark
                    mode="light"
                    // 左侧小图片
                    icon={<i className='iconfont icon-back' />}
                    // 左侧按钮的点击事件
                    onLeftClick={this.props.onLeftClick || defaultHandler}
                >{this.props.children}</NavBar>
            )
        }
    }
    // 通过withRouter 包装一层后,返回的还是一个组件
    export default withRouter(NavHeader)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    1.4 添加props校验

    我们可以通过添加props校验,来提示使用者,应该怎样正确的传递props
    使用步骤:

    1、安装 yarn add prop-types
    2、导入 PropTypes
    3、给NavHeader组件的 children 和 onLeftClick添加props校验
    
    • 1
    • 2
    • 3

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

    import PropTypes from 'prop-types'
    
    NavHeader.propTypes = {
        children: PropTypes.string.isRequired,
        onLeftClick:PropTypes.func
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.5 在城市找房页面使用NavHeader组件

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

    // 导入 NavHeader 组件
    import NavHeader from '../../components/NavHeader'
            
    {/* 顶部导航栏 */}
    <NavHeader>城市选择</NavHeader>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    二、使用CSS Modules解决组件之间样式覆盖的问题

    2.1 组件间样式覆盖问题概述

    1、问题:CityList组件的样式,会影响Map组件的样式
    2、原因:在配置路由的时候,CityList组件与Map组件都会被导入到路由中,组件被导入,相关的样式也会被导入进来,如果两个组件的样式名称相同,那么就会影响另外一个组件的样式
    3、小结:默认情况下,只要导入了组件,不管组件有没有显示在页面中,组件的样式就会生效
    4、解决方式:1、写不同的类名   2CSS IN JS
    5CSS IN JS
    CSS IN JS 是使用JavaScript 编写 CSS 的统称,用来解决CSS样式冲突,覆盖等问题;
    CSS IN JS 的具体实现有50多种,比如:CSS Modules、styled-components等
    推荐使用CSS Modules,因为React脚手架已经集成进来了,可以直接使用
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.2 CSS Modules

    概念:

    1CSS Modules 通过对CSS类名重命名,保证每一个类名的唯一性,从而避免样式冲突问题
    2、实现方式:webpack的css-loader 插件
    3、命名采用:BEM(Block块、Element元素、Modifier三部分组成)命名规范。比如: .list_item_active
    4、在React脚手架中演化成:文件名、类名、hash(随机)三部分,只需要指定类名即可
    
    • 1
    • 2
    • 3
    • 4

    使用方式:

    1、创建名为[name].module.css 的样式文件(React脚手架中的约定,与普通CSS区分开)
          示例:
               在 CityList 组件中创建的样式文件名称:index.module.css
    
    2、组件中导入样式文件(注意语法)
          示例:
               在 CityList 组件中导入样式文件: import styles from './index.module.css'
    
    3、通过styles对象访问对象中的样式名来设置样式
          示例:
               className={styles.navBar}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3 使用CSS Modules修改 NavHeader 样式

    使用步骤:

    1、在NavHeader目录中创建 index.module.css 的样式文件
    2、在样式文件中修改当前组件的样式
    3、对于组件库中已经有的全局样式,需要使用:global() 来指定
    
    • 1
    • 2
    • 3

    代码示例:
    在src/components/NavHeader/index.module.css中添加如下代码:

    .navbar {
        color: #333;
        background-color: #f6f5f6;
    }
    
    .navbar :global(.am-navbar-title) {
        color: #333;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三、地图找房模块-根据定位展示当前城市

    实现步骤:

    1、获取当前定位城市
    2、使用 地址解析器 解析当前城市坐标
    3、调用 centerAndZoom() 方法在地图中展示当前城市,并设置缩放级别为11
    4、在地图中添加比例尺和平移缩放控件
    
    • 1
    • 2
    • 3
    • 4

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

    // 解决脚手架中全局变量访问的问题
    const BMap = window.BMap
    
    export default class Map extends React.Component {
      componentDidMount() {
        this.initMap()
      }
    
      // 初始化地图
      initMap() {
        // 获取当前定位城市
        const { label, value } = JSON.parse(localStorage.getItem('hkzf_city'))
    
        // 初始化地图实例
        // 注意:在 react 脚手架中全局对象需要使用 window 来访问,否则,会造成 ESLint 校验错误
        const map = new BMap.Map('container')
    
        // 创建地址解析器实例
        const myGeo = new BMap.Geocoder()
        // 将地址解析结果显示在地图上,并调整地图视野
        myGeo.getPoint(
          label,
          point => {
            if (point) {
              //  初始化地图
              map.centerAndZoom(point, 11)
    
              // 添加常用控件
              map.addControl(new BMap.NavigationControl())
              map.addControl(new BMap.ScaleControl())
            }
          },
          label
        )
      }
    
      render() {
        return (
          <div className={styles.map}>
            {/* 顶部导航栏组件 */}
            <NavHeader>地图找房</NavHeader>
            {/* 地图容器元素 */}
            <div id="container" className={styles.container} />
          </div>
        )
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    四、地图找房模块-房源信息在地图中展示

    4.1 房源信息在地图中展示效果图

    在这里插入图片描述

    这些房源信息其实就是用文本覆盖物来实现的,所以我们先查看百度地图开发文档,学习如何创建文本覆盖物

    4.2 创建文本覆盖物

    创建步骤:

    1、打开百度地图添加文字标签DEMO 
    2、创建 Label 实例对象
    3、调用 setStyle() 方法设置样式 
    4、在 map 对象上调用 addOverlay() 方法,将文本覆盖物添加到地图中。
    
    • 1
    • 2
    • 3
    • 4

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

      const opts = {
        position: point
      }
    
      const label = new BMap.Label('文本覆盖物', opts)
    
      // 设置样式
      label.setStyle({
        color: 'green'
      })
    
      // 添加覆盖物到地图中
      map.addOverlay(label)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4.3 绘制房源信息覆盖物

    绘制步骤:

    1、由于默认提供的本文覆盖物与我们效果不符合,所以我们需要进行重新的绘制
    2、调用Label的setContent方法,传入html结构,修改HTML的内容样式
    3、调用setStyle方法修改覆盖物样式
    4、给覆盖物添加点击事件
    
    • 1
    • 2
    • 3
    • 4

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

    // 覆盖物样式
    const labelStyle = {
      cursor: 'pointer',
      border: '0px solid rgb(255, 0, 0)',
      padding: '0px',
      whiteSpace: 'nowrap',
      fontSize: '12px',
      color: 'rgb(255, 255, 255)',
      textAlign: 'center'
    }
    
     const opts = {
        position: point,
        offset: new BMap.Size(-35, -35)
      }
    
      // 说明:设置 setContent 后,第一个参数中设置的文本内容就失效了,因此,直接清空即可
      const label = new BMap.Label('', opts)
    
      // 设置房源覆盖物内容
      label.setContent(`
        

    浦东

    99套

    `) // 设置覆盖物样式 label.setStyle(labelStyle) // 添加单击事件 label.addEventListener('click', () => { console.log('房源覆盖物被点击了') }) // 添加覆盖物到地图中 map.addOverlay(label)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    五、地图找房模块-业务逻辑实现

    5.1 业务逻辑分析

    地图找房模块需要实现以下功能:

    1、获取房源数据,渲染覆盖物
    2、点击覆盖物后放大地图,并获取数据,渲染下一级覆盖物
    3、区、镇覆盖物点击后,清除现有的覆盖物,获取下一级数据,创建新的覆盖物
    4、小区覆盖物点击后,不清除现有覆盖物,移动地图,展示该小区下的房源信息
    
    • 1
    • 2
    • 3
    • 4

    5.2 获取所有区的信息并显示在地图中
    实现步骤:

    1、发送请求获取房源数据
    2、遍历数据,创建覆盖物,给每一个覆盖物添加唯一标识
    3、给覆盖物添加点击事件
    4、在单击事件中,获取到当前单击项的唯一标识
    5、放大地图(级别为13),调用clearOverlays()方法清除当前覆盖物
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

    // 请求接口,获取房源数据
    let res = await axios.get(`http://localhost:8080/area/map?id=${value}`)
    // 遍历房源信息,创建对应的覆盖物
    res.data.body.map(item => {
        // 给每一条数据添加覆盖物
        // 得到返回的经纬度信息
        let { coord: { longitude, latitude }, label: areaName, count, value } = item
        // 创建覆盖物
        let label = new window.BMap.Label('', {
            position: new window.BMap.Point(longitude, latitude),
            offset: new window.BMap.Size(-35, -35)
        })
        // 设置覆盖物内容
        label.setContent(`
    ${styles.bubble}">

    ${styles.name}">${areaName}

    ${count}

    `
    ) // 设置样式 label.setStyle(labelStyle) // 添加点击事件 label.addEventListener('click', function () { // 当点击了覆盖物,要以当前点击的覆盖物为中心来放大地图 map.centerAndZoom(this.K.position, 13); // 解决清除覆盖物的时候,百度地图js报错问题 setTimeout(function () { map.clearOverlays() }, 0) }) // 给label添加唯一标识 label.id = value // 添加到地图上 map.addOverlay(label) })
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    5.3 业务逻辑封装

    经过分析业务逻辑我们发现,区,镇,小区要实现的业务逻辑是相似的,为了提高代码的复用性,可以对此进行一层封装
    封装流程图如下图所示:
    在这里插入图片描述

    1、renderOverlays() 作为入口
    * 接收区域id参数,获取该区域下的房源数据
    * 获覆盖物类型以及下级地图缩放级别

    2、createOverlays() 方法
    * 根据传入的类型,调用对应方法,创建覆盖物,到底是创建区镇的覆盖物还是小区覆盖物

    3、createCircle() 方法
    * 根据传入的数据创建覆盖物,绑定事件(放大地图,清除覆盖物,渲染下一级房源数据)

    4、createReact() 方法
    * 根据传入的数据创建覆盖物,绑定事件(移动地图,渲染房源列表)

    5.4 业务逻辑封装-renderOverlays 方法的封装

    实现步骤:

    1、接收区域 id 参数,获取该区域下的房源数据
    2、获取房源类型以及下级地图缩放级别
    
    • 1
    • 2

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

    /**
    * 根据id获取对应的房源信息
    */
    async renderOverlays(id) {
        // 请求,拿到对应房源数据
        let res = await axios.get(`http://localhost:8080/area/map?id=${id}`)
        let data = res.data.body
    
        let {type,nextZoom} = this.getTypeAndZoom()
    
        // 遍历,调用createOverlays创建覆盖物
        data.map(item => {
            this.createOverlays(item,type,nextZoom)
        })
    }
    
    /**
    * 获取对应要绘制的类型和缩放的比例
    */
      getTypeAndZoom() {
        // 调用地图的 getZoom() 方法,来获取当前缩放级别
        const zoom = this.map.getZoom()
        let nextZoom, type
    
        if (zoom >= 10 && zoom < 12) {
          // 区
          // 下一个缩放级别
          nextZoom = 13
          // circle 表示绘制圆形覆盖物(区、镇)
          type = 'circle'
        } else if (zoom >= 12 && zoom < 14) {
          // 镇
          nextZoom = 15
          type = 'circle'
        } else if (zoom >= 14 && zoom < 16) {
          // 小区
          type = 'rect'
        }
    
        return {
          nextZoom,
          type
        }
      }
    最后,在初始化地图方法initMap中调用:
    
      // 初始化地图
      initMap() {
        // 获取当前定位城市
        const { label, value } = JSON.parse(localStorage.getItem('hkzf_city'))
        // 初始化地图实例
        const map = new BMap.Map('container')
        // 作用:能够在其他方法中通过 this 来获取到地图对象
        this.map = map
        // 创建地址解析器实例
        const myGeo = new BMap.Geocoder()
        // 将地址解析结果显示在地图上,并调整地图视野
        myGeo.getPoint(
          label,
          async point => {
            if (point) {
              //  初始化地图
              map.centerAndZoom(point, 11)
              // 添加常用控件
              map.addControl(new BMap.NavigationControl())
              map.addControl(new BMap.ScaleControl())
    
              // 调用 renderOverlays 方法
              this.renderOverlays(value)
            }
          },
          label
        )
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    5.5 业务逻辑封装-createOverlays 方法的封装

    实现步骤:
    1、这个方法主要是逻辑判断,然后根据不同条件调用不同渲染的方法

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

      // 创建覆盖物
      createOverlays(data, zoom, type) {
        const {
          coord: { longitude, latitude },
          label: areaName,
          count,
          value
        } = data
    
        // 创建坐标对象
        const areaPoint = new BMap.Point(longitude, latitude)
    
        if (type === 'circle') {
          // 区或镇
          this.createCircle(areaPoint, areaName, count, value, zoom)
        } else {
          // 小区
          this.createRect(areaPoint, areaName, count, value)
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    5.6 业务逻辑封装-createCircle 方法的封装

    实现步骤:
    1、复用之前的创建覆盖物的代码逻辑
    2、在覆盖物的单击事件中,调用 renderOverlays(id)方法,重新渲染该区域的房屋数据

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

    // 创建区、镇覆盖物
      createCircle(point, name, count, id, zoom) {
        // 创建覆盖物
        const label = new BMap.Label('', {
          position: point,
          offset: new BMap.Size(-35, -35)
        })
    
        // 给 label 对象添加一个唯一标识
        label.id = id
    
        // 设置房源覆盖物内容
        label.setContent(`
          
    ${styles.bubble}">

    ${styles.name}">${name}

    ${count}

    `
    ) // 设置样式 label.setStyle(labelStyle) // 添加单击事件 label.addEventListener('click', () => { // 调用 renderOverlays 方法,获取该区域下的房源数据 this.renderOverlays(id) // 放大地图,以当前点击的覆盖物为中心放大地图 this.map.centerAndZoom(point, zoom) // 解决清除覆盖物时,百度地图API的JS文件自身报错的问题 setTimeout(() => { // 清除当前覆盖物信息 this.map.clearOverlays() }, 0) }) // 添加覆盖物到地图中 this.map.addOverlay(label) }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    5.7 业务逻辑封装-createRect 方法的封装

    实现步骤:
    1、创建Label、设置样式、设置html内容,绑定事件

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

      // 创建小区覆盖物
      createRect(point, name, count, id) {
        // 创建覆盖物
        const label = new BMap.Label('', {
          position: point,
          offset: new BMap.Size(-50, -28)
        })
    
        // 给 label 对象添加一个唯一标识
        label.id = id
    
        // 设置房源覆盖物内容
        label.setContent(`
          
    ${styles.rect}"> ${styles.housename}">${name} ${styles.housenum}">${count} ${styles.arrow}">
    `
    ) // 设置样式 label.setStyle(labelStyle) // 添加单击事件 label.addEventListener('click', () => { console.log('小区被点击了') }) // 添加覆盖物到地图中 this.map.addOverlay(label) }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    现在只是封装了创建小区覆盖物的方法,点击获取小区下的所有房源数据和渲染房源列表还未实现

    总结

  • 相关阅读:
    【004】Shell脚本以怎样的方式执行?
    【中秋征文】使用Python创意中秋节画月饼《花好月圆》
    谈谈HMI 的自动化生成技术
    R语言ggplot2可视化:gganimate包创建条件筛选的动画图(gif)、transition_filter函数在一系列筛选条件之间转换可视化动画图
    婴儿摇椅出口亚马逊欧盟CE认证检测标准
    计算机毕业设计家校通微信小程序源码
    将docker打包成镜像并保存到本地
    [Springboot]安全框架Spring Security使用
    使用ffmpeg将一个目录下的mkv格式的视频文件转换成mp4格式
    [机缘参悟-47]:鬼谷子-第十一决篇-决策者,中庸也,利益合理化分配也
  • 原文地址:https://blog.csdn.net/qq_40652101/article/details/127958359