持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧59:使用@ts-check 和 JSDoc提前尝试ts
在你将你的js代码转换成ts之前,你可能想提前尝试ts,看自己的js代码会出哪些错误。@ts-check会对单个文件执行检查,并且报告所有会出现的错误:
// @ts-check
const person = {first: 'Grace', last: 'Hopper'};
2 * person.first
// ~~~~~~~~~~~~ The right-hand side of an arithmetic operation must be of type
// 'any', 'number', 'bigint', or an enum type
person.first类型推断为string,所以 2 * person.first会报类型错误。
除了一些明显的类型错误,在实践中,@ts-check还会报一些特定的错误:
未申明的全局变量
如果你想声明一个变量,可以使用let,const关键字。如果是其他地方定义的环境变量(例如<script>标签里面定义的),你可以创建一个类型申明文件去声明他们:
例如,你的js文件像这样:
/ @ts-check
console.log(user.firstName);
// ~~~~ Cannot find name 'user'
你可以新建一个types.d.ts:
interface UserData {
firstName: string;
lastName: string;
}
declare let user: UserData;
创建这个文件可以修复这个错误,如果不行。你再添加一个「triple slash」引用
// @ts-check
/// <reference path="./types.d.ts" />
console.log(user.firstName); // OK
types.d.ts文件会成为你项目的基础的类型声明文件。
未知的库
如果你是用第三方的库,ts需要了解这个库。例如你正在使用jQuery来设置HTML元素的size,使用@ts-check会报这样的错误:
/ @ts-check
$('#graph').style({'width': '100px', 'height': '100px'});
// ~ Cannot find name '$'
解决办法是按照jQuery的@types:
$ npm install --save-dev @types/jquery
现在jQuery的错误更加具体了:
/ @ts-check
$('#graph').style({'width': '100px', 'height': '100px'});
// ~~~~~ Property 'style' does not exist on type 'JQuery<HTMLElement>'
实际上,应该是.css而不是.style。
@ts-check能让你享受ts的好处,但又不用转成ts。你更应该试试它
DOM Issues
当你写的代码运行在web浏览器上,在你处理DOM元素的时候,ts很有可能报错。
/ @ts-check
const ageEl = document.getElementById('age');
ageEl.value = '12';
// ~~~~~ Property 'value' does not exist on type 'HTMLElement'
问题出在:只有HTMLInputElement有value属性,但是document.getElementById返回的是更通用的HTMLElement(具体见技巧55)。 如果你知道#age元素是HTMLInputElement,你可以使用类型断言,但这不是ts文件,在@ts-check中可以这么做:
// @ts-check
const ageEl = /** @type {HTMLInputElement} */(document.getElementById('age'));
ageEl.value = '12'; // OK
不准确的JSDoc
如果你的代码已经有JSDoc风格的注释,那么当有一些「野心勃勃」但是又不准确的JSDoc时,可能会出错:
// @ts-check
/**
* Gets the size (in pixels) of an element.
* @param {Node} el The element
* @return {{w: number, h: number}} The size
*/
function getSize(el) {
const bounds = el.getBoundingClientRect();
// ~~~~~~~~~~~~~~~~~~~~~ Property 'getBoundingClientRect'
// does not exist on type 'Node'
return {width: bounds.width, height: bounds.height};
// ~~~~~~~~~~~~~~~~~~~ Type '{ width: any; height: any; }' is not
// assignable to type '{ w: number; h: number; }'
}
第一个误会是getBoundingClientRect()在Element上,而不是Node上。所以@param tag
应该升级。第二个误会是@return tag的声明和实现不一致。
你可以使用JSDoc逐步给你的项目添加类型声明:
function double(val) {
return 2 * val;
}
你可以这样添加类型:
// @ts-check
/**
* @param {number} val
*/
function double(val) {
return 2 * val;
}
@ts-check鼓励你让type在你的代码中流动。但是并不是每次都有效:
function loadData(data) {
data.files.forEach(async file => {
// ...
});
}
当你想快速修复这个错误,你可能会这样做:
/**
* @param {{
* files: { forEach: (arg0: (file: any) => Promise<void>) => void; };
* }} data
*/
function loadData(data) {
// ...
}
虽然可以从技术上修复这个错误,但是代码的目的很可能是:{files: string[]}。
@ts-check和JSDoc能让你提前享受ts的好处,但是这也有成本的:很容易让你逻辑迷失在JSDoc的大海中。能尽量使用ts就使用ts。@ts-check和JSDoc只是你提前尝试ts的工具