一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情。
本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧35:从api/约定中生成 type ,而不是数据
有的时候,不一定要自己写type。我们可以从api/约定中生成 type,也可以从数据中生成type。我们要尽可能的从前者生成,因为前者能让ts保证我们通过所有的测试用例。后者却无法做到。
例如在技巧31中,我们写了一个函数用于计算 GeoJson几何形状的边界框:
function calculateBoundingBox(f: GeoJSONFeature): BoundingBox | null {
let box: BoundingBox | null = null;
const helper = (coords: any[]) => {
// ...
};
const {geometry} = f;
if (geometry) {
helper(geometry.coordinates);
}
return box;
}
技巧31中GeoJSONFeature的type写的不是很完美。其实更好的做法是从正式的GeoJSON规范生成type。幸运的是,有人帮我们生成好了,我们可以这样获取:
$ npm install --save-dev @types/geojson
+ @types/geojson@7946.0.7
当你安装了上面的类型声明,ts立马就能发现代码中的错误:
import {Feature} from 'geojson';
function calculateBoundingBox(f: Feature): BoundingBox | null {
let box: BoundingBox | null = null;
const helper = (coords: any[]) => {
// ...
};
const {geometry} = f;
if (geometry) {
helper(geometry.coordinates);
// ~~~~~~~~~~~
// Property 'coordinates' does not exist on type 'Geometry'
// Property 'coordinates' does not exist on type
// 'GeometryCollection'
}
return box;
}
错误的原因:代码默认geometry拥有属性coordinates。但是GeoJSON中有个数据结构GeometryCollection是没有属性coordinates。如果你调用该函数同时将GeometryCollection作为参数传入。将会发生严重的错误。
我们可以这样解决这个报错:
onst {geometry} = f;
if (geometry) {
if (geometry.type === 'GeometryCollection') {
throw new Error('GeometryCollections are not supported.');
}
helper(geometry.coordinates); // OK
}
但是还有更好的解决办法,对数据结构GeometryCollection也提供支持:
const geometryHelper = (g: Geometry) => {
if (geometry.type === 'GeometryCollection') {
geometry.geometries.forEach(geometryHelper);
} else {
helper(geometry.coordinates); // OK
}
}
const {geometry} = f;
if (geometry) {
geometryHelper(geometry);
}
你可能在实际工作中没有碰到数据结构GeometryCollection,使用官方规范生成的type,能让我们的代码更健壮。同时让我们对自己的代码更自信。
还有一个类似的例子关于GraphQL。GraphQL拥有和ts类似的类型系统。例如,在GraphQL中写查询语句请求接口中的特定字段:
query {
repository(owner: "Microsoft", name: "TypeScript") {
createdAt
description
}
}
查询结果如下:
{
"data": {
"repository": {
"createdAt": "2014-06-17T15:28:39Z",
"description":
"TypeScript is a superset of JavaScript that compiles to JavaScript."
}
}
}
GraphQL中的类型系统能帮助你清晰的确定:某个变量是不是为空。
以下是获取GitHub存储库开源许可证的查询:
uery getLicense($owner:String!, $name:String!){
repository(owner:$owner, name:$name) {
description
licenseInfo {
spdxId
name
}
}
}
name是GraphQL变量,它们本身就是类型化的。这其实是和ts很相似。但是要也有不同:这些变量在GraphQL可以为空,在ts中不可以。 有很多工具帮你将GraphQL的查询转成ts类型,例如Apollo:
$ apollo client:codegen \
--endpoint https://api.github.com/graphql \
--includes license.graphql \
--target typescript
Loading Apollo Project
Generating query files with 'typescript' target - wrote 2 files
转化成ts类型后输出如下:
export interface getLicense_repository_licenseInfo {
__typename: "License";
/** Short identifier specified by <https://spdx.org/licenses> */
spdxId: string | null;
/** The license full name specified by <https://spdx.org/licenses> */
name: string;
}
export interface getLicense_repository {
__typename: "Repository";
/** The description of the repository. */
description: string | null;
/** The license associated with the repository */
licenseInfo: getLicense_repository_licenseInfo | null;
}
export interface getLicense {
/** Lookup a given repository by the owner and repository name. */
repository: getLicense_repository | null;
}
export interface getLicenseVariables {
owner: string;
name: string;
}
这样的type会随着GraphQL的规范自动更新。保证你的type永远是正确的。当你无法从api/规范中生成type,你也可以用quicktype之类的工具从数据中生成type。
但是从数据中生成type有风险:可能有你没有遇到过的数据!