TypeScript 地图案例
效果图
需求:
随机创建用户和公司 并把它们的位置标注到地图中
- 案例准备
- 安装依赖
- 创建应用 - 创建用户
- 创建公司
- 创建地图
- 封装地图类
- 地图标记
- 消除重复的代码
- 增加程序扩展性
- 创建标记弹框
- 使用接口约束类
案例准备
安装依赖
parcel 是前端构建工具 类似于 webpack parcel 的最大的特点是打包速度非常快 而且零配置开箱即用
为什么 需要模块打包工具?
ts -> js -> 代码不能直接运行,浏览器不识别模块。
ts -> js -> 模块打包 -> 在浏览器中直接运行。
在 cmd 终端 输入以下命令 安装
npm install parcel@2.4.1 -g
安装成功后 在cmd 输入 npm list -y 查看
使用 @faker-js/faker 可以随机创建模拟数据,当前案例中用于随机生成用户和公司信息
在项目根目录
@types/google.maps 是谷歌地图的 TS 类型声明文件。
创建应用
# 在桌面或者任意位置 创建文件夹 case
# 创建项目工程文件
npm init -y
# 在项目创建 src 文件夹
# 创建 index.html 页面入口
# 在src 里 创建 index.ts 应用逻辑入口
具体长这样
<!-- 在应用页面入口文件中导入应用逻辑入口文件 -->
<!-- 当 parcel 检测到引入的是 TS 文件时, parcel 会先将其转换为 JS, 再将 TS 文件替换为转换后的 JS -->
<script src="./src/index.ts" type="module"></script>
# 启动服务器
parcel index.html
在项目的 根目录 打开终端 输入命令 启动服务
创建用户
在 src 文件夹里 创建 用户的文件 User.ts
代码:
// 导入 faker
import faker from "@faker-js/faker";
// 创建 用户 类
export class User {
// 姓名 string类型
name: string;
// 位置
location: {
// 纬度 number 类型
lat: number;
// 经度 number 类型
lng: number;
};
//
constructor() {
// 随机创建用户姓名
this.name = faker.name.firstName();
// 随机创建用户位置
this.location = {
lat: parseFloat(faker.address.latitude()),
lng: parseFloat(faker.address.longitude()),
}
}
}
有图有真相
// 在 src 下的 index.ts 里
// 导入 User 类
import {User} from "./User";
// 创建用户
const user = new User();
// 测试 在控制台输出用户信息
console.log(user);
在浏览器 查看
创建公司
在src 文件夹下 创建 Company.ts 公司
// src 下 的 Company.ts
// 导入 faker
import faker from "@faker-js/faker";
// 创建公司类
export class Company {
// 公司名称 string 类型
companyName: string;
// 公司口号 string 类型
catchPhrase: string;
// 公司位置 number 类型
location: {
// 纬度
lat: number;
// 经度
lng: number;
};
// 随机创建
constructor() {
// 随机创建公司名称
this.companyName = faker.company.companyName();
// 随机创建公司口号
this.catchPhrase = faker.company.catchPhrase();
// 随机创建公司位置
this.location = {
lat: parseFloat(faker.address.latitude());
lng: parseFloat(faker.address.longitude());
};
}
}
有图有真相
// 在 src 下的 index.ts
// 导入 Company 类
import { Company } from "./Company";
// 创建公司
const company = new Company();
// 测试: 在控制台中输出公司信息
console.log(company);
在浏览器查看
创建地图
目标:
在浏览器页面 显示 如下图
先在 应用页面 index.html 入口文件 添加 Google Map API
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBNLrJhOMz6idD05pzfn5lhA-TAw-mAZCU"></script>
然后 在 index.html 文件中创建用户放地图的容器
<style>
html, body, #map {
height: 100%;
margin: 0;
padding: 0;
}
</style>
<div id="map"></div>
有图有真相
在 Usrer.ts 下 创建地图
new google.maps.Map(document.getElementById("map"), {
zoom: 1,
center: {
lat: 0,
lng: 0,
}
})
有图有真相
封装地图类
目标:
创建 Map 类 在类中封装和地图的相关的业务逻辑 以后在创建地图时只需要对该类进行 实例化即可
( 现在只是地图的代码 以后有了别的代码 不能都写在一个文件里 不容易管理 )
在 src 下 创建 Map.ts 文件
代码:
// src/ Map.ts
// 创建地图 类
export class Map{
// 用于存储地图实例
private googleMap: google.maps.Map;
// 创建地图
constructor(divId: string) {
this.googleMaps = new google.maps.Map(document.getElementById(divId)!, {
zoom: 1,
center: {
lat: 0,
lng: 0,
},
});
}
}
然后在 index.ts 文件 引入 地图类
// src/index.ts
import { Map } from "./Map";
// 创建地图
new Map("map");
有图有真相
把 之前 没封装的 地图 代码 删掉!!!!!!!
地图标记
目标:
随机 显示地图上的 标记 浏览器刷新 位置发生变化
效果图:
在 src 下的 Map.ts 下
代码:
// src/Map.ts
// 导入 用户 和 公司
import {Company} from "./Company";
import {User} from "./User";
// 地图 类
export class Map {
/*......
用于存储地图实例
......
*/
// 在地图中标记用户位置
addUserMarker(user: User) {
new goole.maps.Marker({
map: this.googleMap,
position: {
lat: user.location.lat,
lng: user.location.lng,
}
})
}
// 在地图标记公司位置
addCompanyMarker(company: Company) {
new goole.maps.Marker({
map: this.googleMap,
position: {
lat: company.location.lat,
lng: company.location.lng,
}
})
}
}
然后在 index.ts 调用
// src/index.ts
const map = new Map("map");
map.addUserMarker(user);
map.addCompanyMarker(company);
消除重复的代码
在目前的代码中 addUserMarker 和 addCompanyMarker 两个方法中的代码是一模一样的 为消除重复代码 决定把两个方法进行合并 并把参数更国改为 User 和 Company 的联合类型
在 src 下的 Map.ts 下
代码:
//
export class Map {
// 在地图中标记位置
addMarker(mappable: User | Company) {
new google.maps.Marker({
map: this.google,
position: {
lat: mappable.location.lat,
lng: mappable.location.lng,
}
})
}
}
由于 location 属性是 User Company 类型中的公共属性 所以在 addMarker 方法中可以直接调用
在 index.ts 修改
// 显示标记
map.addMarker(user)
map.addMarker(company)
// map.addUserMarker(user)
// map.addCompanyMarker(company)
增加程序扩展性
虽然重复代码被消除了 但是这不利于程序的扩展性 如果要在地图中标记其他物体的位置信息 那么就只能在参数后面不断的罗例其他物体的类型信息了
addMarker(mappable: User | Company | Park | School) {}
如上面 不能 以后多了个 停车位置 。。。就加一个 这样比较麻烦
解决方法:
解决问题的方式是定义通过的类型接口 不论是什么数据类型只要满足接口中定义的规范即可 从而解决程序扩展性问题
在 Map.ts 定义接口
interface Mappable {
location: {
lat: number;
lng: number;
};
}
在目前的代码中 不论是 User 类的实例还是 Company 类的实例 都是满足 Mappable 接口规范的
创建标记弹框
目标:
在点击地图中的标记时 通过弹框显示标记信息
在 Map.ts 下
// src/Map.ts
export class Map{
// 在地图中标记位置
addMarker(mappable: Mappable) {
// 创建标记
const marker = new google.maps.Marker();
// 为标记绑定点击事件
marker.addListener("click", () => {
// 创建弹窗
const infoWindow = new google.maps.InfoWindow({
content: "Hello I am marker content"
});
// 打开弹框并指定弹框在那个地图的那个的标记的位置上弹出
infoWindow.open(this.googleMap, marker);
});
}
}
使用接口约束类
目标 :
使用接口约束
在 src 下 的 Map.ts
export interface Mappable {
markerContent(): string;
}
const infoWindow = new google.maps.InfoWindow({
content: mappable.markerContent(),
})
在 User.ts 下
import {Mappable} from "./Map";
export class User implements Mappable {
markerContent(): string {
return `用户名: ${this.name}`;
}
}
在 Company.ts 下
import {Mappable} from "./Map";
export class Company implements Mappable {
markerContent(): string {
return `
<h3>公司名称: ${this.companyName}<h3/>
<h5>公司口号: ${this.catchPhrase}<h5/>
`
}
}