一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第21天,点击查看活动详情。
本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧29:函数类型设计:宽进严出
稳健性原则,也称作波斯特尔定律。波斯特尔在TCP的背景中写下这个原则:
TCP实现应该遵循健壮性的一般原则:在你做的事情上要保守,在你接受他人的事情上要自由。
这一原则应用于函数的设计上就是:宽进严出。函数入参类型越广越好,函数输出越具体越好。
这有个例子:例如,3D mapping API可以提供一种方法来定位相机并计算边界框的视口:
declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): CameraOptions;
viewportForBounds 的输出可直接传入:setCamera。另外还有其他的一些 types:
interface CameraOptions {
center?: LngLat;
zoom?: number;
bearing?: number;
pitch?: number;
}
type LngLat =
{ lng: number; lat: number; } |
{ lon: number; lat: number; } |
[number, number];
type LngLatBounds =
{northeast: LngLat, southwest: LngLat} |
[LngLat, LngLat] |
[number, number, number, number];
CameraOptions 的字段都是可选的。LngLatBounds是联合类型。这让viewportForBounds() 和 setCamera() 调用起来非常方便。
现在,让我们编写一个函数,调整viewport以适应GeoJSON功能,并将新viewport存储在URL中(有关CalculateBondingBox的定义,请参见技巧 31 ):
function focusOnFeature(f: Feature) {
const bounds = calculateBoundingBox(f);
const camera = viewportForBounds(bounds);
setCamera(camera);
const {center: {lat, lng}, zoom} = camera;
// ~~~ Property 'lat' does not exist on type ...
// ~~~ Property 'lng' does not exist on type ...
zoom; // Type is number | undefined
window.location.search = `?v=@${lat},${lng}z${zoom}`;
}
这报错的原因:viewportForBounds() 函数输出的变量类型CameraOptions都是可选字段, 所以center,zoom都有可能为undefine。简而言之:viewportForBounds()参数设计上宽入宽出,不符合宽入严出的原则!
我们的解决办法:定义非可选类型 Camera,同时对应生成可选类型:CameraLike。
interface LngLat { lng: number; lat: number; };
type LngLatLike = LngLat | { lon: number; lat: number; } | [number, number];
interface Camera {
center: LngLat;
zoom: number;
bearing: number;
pitch: number;
}
interface CameraOptions extends Omit<Partial<Camera>, 'center'> {
center?: LngLatLike;
}
type LngLatBounds =
{northeast: LngLatLike, southwest: LngLatLike} |
[LngLatLike, LngLatLike] |
[number, number, number, number];
declare function setCamera(camera: CameraOptions): void;
declare function viewportForBounds(bounds: LngLatBounds): Camera;
上面的 CameraOptions 等价于:
interface CameraOptions {
center?: LngLatLike;
zoom?: number;
bearing?: number;
pitch?: number;
}
重要的改变在于:
- 将宽松的类型 CameraLike 用于函数 viewportForBounds 的入参
- 将严格的类型 Camera 用于函数 viewportForBounds 出参
然后就发现能解决报错的问题:
function focusOnFeature(f: Feature) {
const bounds = calculateBoundingBox(f);
const camera = viewportForBounds(bounds);
setCamera(camera);
const {center: {lat, lng}, zoom} = camera; // OK
zoom; // Type is number
window.location.search = `?v=@${lat},${lng}z${zoom}`;
}