foreword
Table of contents
- foreword
- 1. Top Navigation Component Encapsulation
- 2. Use CSS Modules to solve the problem of style coverage between components
- 3. Map housing module - display the current city according to the location
- 4. Map housing search module - housing information is displayed on the map
- 5. Map housing module - business logic implementation
-
- 5.1 Business logic analysis
- 5.3 Business logic encapsulation
- 5.4 Business logic encapsulation - encapsulation of renderOverlays method
- 5.5 Business logic encapsulation - encapsulation of createOverlays method
- 5.6 Business logic encapsulation - encapsulation of createCircle method
- 5.7 Business logic encapsulation - encapsulation of createRect method
- Summarize
1. Top Navigation Component Encapsulation
1.1 Basic layout
Implementation steps:
1、封装NavHeader组件实现城市选择,地图找房页面的复用
2、在components目录中创建组件 NavHeader/index.js
3、在该组件中封装 antd-mobile 组件库中的 NavBar组件
Code example:
Add the following code in 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 Style adjustments
Create an index.scss file in the NavHeader folder under src/components
and copy the style written in the previous city list to this file
1.3 Function processing
Note: By default, only the components directly rendered by the route Route can get the routing information. If you need to get the routing information in other components, you can get it through the withRouter high-level component
Implementation steps:
1、从 react-router-dom 中导入 withRouter 高阶组件
2、使用 withRouter 高阶组件包装 NavHeader 组件
3、从 props 中就能获取history对象,调用history对象的 go() 方法就能实现返回上一页功能了
4、由于头部的左侧按钮不一定是返回上一个页面的功能,所以我们需要把左侧点击逻辑处理一下,改成需要通过父组件传递进来,如果说外界传递了,那么我们就直接使用外界的行为,如果没有传递,那么就用默认的行为
Code example:
Modify the following code in 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.4 Add props check
We can add props verification to remind users how to correctly pass props
using steps:
1、安装 yarn add prop-types
2、导入 PropTypes
3、给NavHeader组件的 children 和 onLeftClick添加props校验
Code example:
Add the following code in src/components/NavHeader/index.js:
import PropTypes from 'prop-types'
NavHeader.propTypes = {
children: PropTypes.string.isRequired,
onLeftClick:PropTypes.func
}
1.5 Use the NavHeader component on the city search page
Add the following code in src/pages/Citylist/index.js:
// 导入 NavHeader 组件
import NavHeader from '../../components/NavHeader'
{
/* 顶部导航栏 */}
<NavHeader>城市选择</NavHeader>
2. Use CSS Modules to solve the problem of style coverage between components
2.1 Overview of Style Override Issues Between Components
1、问题:CityList组件的样式,会影响Map组件的样式
2、原因:在配置路由的时候,CityList组件与Map组件都会被导入到路由中,组件被导入,相关的样式也会被导入进来,如果两个组件的样式名称相同,那么就会影响另外一个组件的样式
3、小结:默认情况下,只要导入了组件,不管组件有没有显示在页面中,组件的样式就会生效
4、解决方式:1、写不同的类名 2、CSS IN JS
5、CSS IN JS
CSS IN JS 是使用JavaScript 编写 CSS 的统称,用来解决CSS样式冲突,覆盖等问题;
CSS IN JS 的具体实现有50多种,比如:CSS Modules、styled-components等
推荐使用CSS Modules,因为React脚手架已经集成进来了,可以直接使用
2.2 CSS Modules
concept:
1、CSS Modules 通过对CSS类名重命名,保证每一个类名的唯一性,从而避免样式冲突问题
2、实现方式:webpack的css-loader 插件
3、命名采用:BEM(Block块、Element元素、Modifier三部分组成)命名规范。比如: .list_item_active
4、在React脚手架中演化成:文件名、类名、hash(随机)三部分,只需要指定类名即可
How to use:
1、创建名为[name].module.css 的样式文件(React脚手架中的约定,与普通CSS区分开)
示例:
在 CityList 组件中创建的样式文件名称:index.module.css
2、组件中导入样式文件(注意语法)
示例:
在 CityList 组件中导入样式文件: import styles from './index.module.css'
3、通过styles对象访问对象中的样式名来设置样式
示例:
className={
styles.navBar}
2.3 Use CSS Modules to modify NavHeader style
Steps for usage:
1、在NavHeader目录中创建 index.module.css 的样式文件
2、在样式文件中修改当前组件的样式
3、对于组件库中已经有的全局样式,需要使用:global() 来指定
Code example:
Add the following code in src/components/NavHeader/index.module.css:
.navbar {
color: #333;
background-color: #f6f5f6;
}
.navbar :global(.am-navbar-title) {
color: #333;
}
3. Map housing module - display the current city according to the location
Implementation steps:
1、获取当前定位城市
2、使用 地址解析器 解析当前城市坐标
3、调用 centerAndZoom() 方法在地图中展示当前城市,并设置缩放级别为11
4、在地图中添加比例尺和平移缩放控件
Code example:
Add the following code in 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>
)
}
}
4. Map housing search module - housing information is displayed on the map
4.1 Real estate information displayed on the map
These listings are actually implemented with text overlays, so let’s first check the Baidu Maps development documentation to learn how to create text overlays
4.2 Creating text overlays
Create steps:
1、打开百度地图添加文字标签DEMO
2、创建 Label 实例对象
3、调用 setStyle() 方法设置样式
4、在 map 对象上调用 addOverlay() 方法,将文本覆盖物添加到地图中。
Code example:
Add the following code in src/pages/Map/index.js:
const opts = {
position: point
}
const label = new BMap.Label('文本覆盖物', opts)
// 设置样式
label.setStyle({
color: 'green'
})
// 添加覆盖物到地图中
map.addOverlay(label)
4.3 Drawing listing information overlays
Drawing steps:
1、由于默认提供的本文覆盖物与我们效果不符合,所以我们需要进行重新的绘制
2、调用Label的setContent方法,传入html结构,修改HTML的内容样式
3、调用setStyle方法修改覆盖物样式
4、给覆盖物添加点击事件
Code example:
Add the following code in 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(`
<div class="${styles.bubble}">
<p class="${styles.name}">浦东</p>
<p>99套</p>
</div>
`)
// 设置覆盖物样式
label.setStyle(labelStyle)
// 添加单击事件
label.addEventListener('click', () => {
console.log('房源覆盖物被点击了')
})
// 添加覆盖物到地图中
map.addOverlay(label)
5. Map housing module - business logic implementation
5.1 Business logic analysis
The map housing module needs to implement the following functions:
1、获取房源数据,渲染覆盖物
2、点击覆盖物后放大地图,并获取数据,渲染下一级覆盖物
3、区、镇覆盖物点击后,清除现有的覆盖物,获取下一级数据,创建新的覆盖物
4、小区覆盖物点击后,不清除现有覆盖物,移动地图,展示该小区下的房源信息
5.2 Obtain the information of all districts and display it on the map
Implementation steps:
1、发送请求获取房源数据
2、遍历数据,创建覆盖物,给每一个覆盖物添加唯一标识
3、给覆盖物添加点击事件
4、在单击事件中,获取到当前单击项的唯一标识
5、放大地图(级别为13),调用clearOverlays()方法清除当前覆盖物
Code example:
Add the following code in 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(`<div class="${
styles.bubble}">
<p class="${
styles.name}">${
areaName}</p>
<p>${
count}套</p>
</div>`)
// 设置样式
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)
})
5.3 Business logic encapsulation
After analyzing the business logic, we found that the business logic to be implemented by districts, towns, and communities is similar. In order to improve the reusability of the code, a layer of encapsulation can be performed. The encapsulation flow chart is shown in the following figure
:
1. renderOverlays() is used as an entry
* to receive the area id parameter, and obtain the housing data under this area
* to obtain the type of overlay and the zoom level of the lower-level map
2. createOverlays() method
* According to the type passed in, call the corresponding method to create overlays, whether to create overlays for districts and towns or community overlays
3. createCircle() method
* Create an overlay based on the incoming data, and bind events (enlarge the map, clear the overlay, and render the next level of housing data)
4. createReact() method
* Create an overlay based on the incoming data, bind events (move the map, render the listing list)
5.4 Business logic encapsulation - encapsulation of renderOverlays method
Implementation steps:
1、接收区域 id 参数,获取该区域下的房源数据
2、获取房源类型以及下级地图缩放级别
Code example:
Add the following code in 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
)
}
5.5 Business logic encapsulation - encapsulation of createOverlays method
Implementation steps:
1. This method is mainly a logical judgment, and then calls different rendering methods according to different conditions
Code example:
Add the following code in 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)
}
}
5.6 Business logic encapsulation - encapsulation of createCircle method
Implementation steps:
1. Reuse the previous code logic for creating overlays
2. In the click event of the overlay, call the renderOverlays(id) method to re-render the house data in this area
Code example:
Add the following code in 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(`
<div class="${
styles.bubble}">
<p class="${
styles.name}">${
name}</p>
<p>${
count}套</p>
</div>
`)
// 设置样式
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)
}
5.7 Business logic encapsulation - encapsulation of createRect method
Implementation steps:
1. Create a Label, set the style, set the html content, and bind the event
Code example:
Add the following code in 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(`
<div class="${
styles.rect}">
<span class="${
styles.housename}">${
name}</span>
<span class="${
styles.housenum}">${
count}套</span>
<i class="${
styles.arrow}"></i>
</div>
`)
// 设置样式
label.setStyle(labelStyle)
// 添加单击事件
label.addEventListener('click', () => {
console.log('小区被点击了')
})
// 添加覆盖物到地图中
this.map.addOverlay(label)
}
Now it only encapsulates the method of creating community coverage. Clicking to get all the listing data and rendering listings under the community has not yet been implemented.