前言
最近在开发一款react项目,其中有一个小功能是地理位置关键词输入提示。多番考虑下,选择接入百度地图API。在使用的过程中,遇到了一些问题,在此记录并分享一下。
一、接入API
首先,我们需要获得百度地图API的使用权,然后才能在项目中引用。
1、登录百度地图
http://lbsyun.baidu.com/
2、创建应用,获取密钥
登录后,点击控制台,选择我的应用,点击创建应用,然后保存密钥。
3、引入API
将百度API的script引入到项目的index.html文件。
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=你的密钥"></script>
4、当作模块导入BMap
在webpack.config.js的module.exports里添加:
externals:{
'BMap':'BMap',
}
二、使用
1、引入
在jsx文件中引入BMap:
import BMap from 'BMap'
2、展示地图
由于项目需要实现让用户选择某一商品的地理位置,所以需要根据用户输入的关键词给出位置提示,或者通过用户点击地图选择位置。
这一功能作为 antd 的Form中的一项,限制比较大,但能实现。
(1)设置两个内容块放置输入框和地图
(map组件需要设置长宽)
<div id="l-map"></div>
<input type="text" id="suggestId" />
如果直接在Form.Item中这样写,则只能显示输入框,map框会被隐藏。所以,使用help属性让<div id="l-map"></div>
显示:
<Form.Item label="地址" name="address"
help={<div id='l-map' className='map'>点我</div>}
>
<input type="text" id="suggestId" />
</Form.Item>
(2)在组件第一次渲染后初始化地图
componentDidMount(){
map = new BMap.Map("l-map");
map.centerAndZoom("北京",12); // 初始化地图,设置城市和地图级别。
map.enableScrollWheelZoom(true);//让地图能随鼠标滚轮缩放
}
这时,界面上已经能显示输入框和地图了。
(3)添加自动完成对象
var ac = new BMap.Autocomplete( //建立一个自动完成的对象
{"input" : "suggestId"
,"location" : map
});
将map与input绑定。这样,当在输入框输入时,输入框下方会显示此输入的位置提示信息列表。
按照官方教程,当点击列表中的一项,输入框会呈现你的选择。然而,事与愿违,百度地图API的赋值与React不兼容。React中的Input是受控组件,不能直接id赋值。所以这里,就需要想替代办法了。
我的想法是拦截输出并获取提示列表,然后展示列表并添加事件响应。
(4)设计输出
首先,将其原来的输出列表隐藏:
.tangram-suggestion{
display: none;
}
采用一种野蛮的方法:检查元素后发现包裹列表的组件类名为“tangram-suggestion”,直接在样式中设置隐藏。
其次,获取位置提示列表,并显示在输入框下。
这里可以使用antd的AutoComplete组件,并且将options属性设置为组件state。只要更新state.addressOptions,就能更新选项了。(onSelect方法后文实现)
<AutoComplete id="suggestId"
options={this.state.addressOptions}
onSelect={this.onSelect}
/>
拦截位置提示列表,并赋值给state.addressOptions。修改自动完成的对象如下:
new BMap.Autocomplete( //建立一个自动完成的对象
{
"input" : "suggestId",
"location" : map,
onSearchComplete: function(e) {
let searchResult=[];
for(let i=0;i<e.Hr.length;i++){
let _value=e.Hr[i];
let a=_value.province + _value.city + _value.district + _value.street + _value.business;
searchResult.push({value:a});
}
this.setState({addressOptions:searchResult});
}
},
);
然后,实现AutoComplete的onSelect方法。onSelect方法传递的参数为列表中所选择的那一项的内容。清除地图上所有覆盖物,然后让地图中心移动到选择的位置。
(map为全局变量,后文解释)
onSelect = (data) => {
map.clearOverlays();
map.centerAndZoom(data,18);
};
(5)添加地图点击响应
当点击地图上某一位置,输入框会显示该位置,并且地图中心移动到选择的位置。
由于点击地图只能获取经纬度信息,所以需要通过BMap.Geocoder转换为地理位置。
map.addEventListener('click', function(e){
var pt = e.point;
var geoc = new BMap.Geocoder();
var addComp;
geoc.getLocation(pt, (rs)=>{
addComp = rs.address;
this.uploadRef.current.setFieldsValue({address: addComp});
map.clearOverlays();
map.centerAndZoom(pt,18);
map.addOverlay(new BMap.Marker(pt));
});
});
对于输入框赋值,本人尝试了无数的方法,最终找到一种:setFieldsValue()赋值。this.uploadRef为Form的ref属性,地址输入框的name为address。转换后的处理函数需绑定到组件。
(6)移除事件监听
将触发函数提取为函数:
map.addEventListener('click', this.mapClick);
mapClick=(e)=> {
var pt = e.point;
var geoc = new BMap.Geocoder();
var addComp;
geoc.getLocation(pt, (rs)=>{
addComp = rs.address;
this.uploadRef.current.setFieldsValue({address: addComp});
map.clearOverlays();
map.centerAndZoom(pt,18);
map.addOverlay(new BMap.Marker(pt));
});
}
此时,为了能在其他函数中使用map,需要将map设置为全局变量(考虑变量作用域)。在组件外声明:
let map;
在componentWillUnmount移除事件。
componentWillUnmount(){
// 注销监听事件
map.removeEventListener('click', this.mapClick);
}
(7)优化——函数防抖
输入框输入后触发自动完成对象,是一个高频函数。为了减轻资源加载的负担,将函数延迟处理,使用debounce()。
import debounce from 'lodash/debounce';
new BMap.Autocomplete( //建立一个自动完成的对象
{
"input" : "suggestId",
"location" : map,
onSearchComplete: this.searchComplete
},
);
searchComplete=(e)=> {
// console.log(e);
let searchResult=[];
for(let i=0;i<e.Hr.length;i++){
let _value=e.Hr[i];
let a=_value.province + _value.city + _value.district + _value.street + _value.business;
searchResult.push({value:a});
}
this.setState({addressOptions:searchResult});
}
this.searchComplete = debounce(this.searchComplete, 500);
三、效果展示
React项目使用百度地图API效果展示.mp4
总结
接入百度地图API这一过程完全是参考前辈们的经验,并没有遇到问题。感谢愿意分享的好心人!
在使用过程中,遇到了形形色色的bug:变量作用域的问题、组件自带函数传递参数的问题、赋值的问题。我通常的处理方式是控制台输出,哪里不清楚就输出哪些。
经过不断的探索,最终找到了一种方式实现了地理位置关键词输入提示这一功能。