一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第14天,点击查看活动详情。
本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧22: 理解类型缩小
类型扩展的反面是类型缩小.
-
null 检查最常见到类型缩小:
const el = document.getElementById('foo'); // Type is HTMLElement | null if (el) { el // Type is HTMLElement el.innerHTML = 'Party Time'.blink(); } else { el // Type is null alert('No element #foo'); }ts 非常擅长在if分支判断缩小类型.尽管有时候可能因为别名受阻(见 技巧 24)
-
通过if判断抛出错误或者return, 也能缩小类型范围:
const el = document.getElementById('foo'); // Type is HTMLElement | null if (!el) throw new Error('Unable to find #foo'); el; // Now type is HTMLElement el.innerHTML = 'Party Time'.blink(); -
instanceof:
function contains(text: string, search: string|RegExp) { if (search instanceof RegExp) { search // Type is RegExp return !!search.exec(text); } search // Type is string return text.includes(search); } -
属性检查:
interface A { a: number } interface B { b: number } function pickAB(ab: A | B) { if ('a' in ab) { ab // Type is A } else { ab // Type is B } ab // Type is A | B } -
内置函数, 比如 Array.isArray:
function contains(text: string, terms: string|string[]) { const termList = Array.isArray(terms) ? terms : [terms]; termList // Type is string[] // ... } -
tag标签:
interface UploadEvent { type: 'upload'; filename: string; contents: string } interface DownloadEvent { type: 'download'; filename: string; } type AppEvent = UploadEvent | DownloadEvent; function handleEvent(e: AppEvent) { switch (e.type) { case 'download': e // Type is DownloadEvent break; case 'upload': e; // Type is UploadEvent break; } }这种模式被称为标签联合体
-
用户自定义的类型守护:
function isInputElement(el: HTMLElement): el is HTMLInputElement { return 'value' in el; } function getElementContent(el: HTMLElement) { if (isInputElement(el)) { el; // Type is HTMLInputElement return el.value; } el; // Type is HTMLElement return el.textContent; }对于array, 类型守护依旧有用. 当我们对array进行搜索,可能会得到 undefine[]:
const jackson5 = ['Jackie', 'Tito', 'Jermaine', 'Marlon', 'Michael']; const members = ['Janet', 'Michael'].map( who => jackson5.find(n => n === who) ); // Type is (string | undefined)[]ts缺无法执行用filter函数:
Const members = ['Janet', 'Michael'].map( who => jackson5.find(n => n === who) ).filter(who => who !== undefined); // Type is (string | undefined)[]那么可以用类型守护:
function isDefined<T>(x: T | undefined): x is T { return x !== undefined; } const members = ['Janet', 'Michael'].map( who => jackson5.find(n => n === who) ).filter(isDefined); // Type is string[]
还有一些容易让人误解的类型缩小:
- typeof null
因为在js中, typeof null 是 'objec'const el = document.getElementById('foo'); // type is HTMLElement | null if (typeof el === 'object') { el; // Type is HTMLElement | null } - !number 和 !string :
空string 和0 都是falsy, 所以function foo(x?: number|string|null) { if (!x) { x; // Type is string | number | null | undefined } }!x无法完全过滤string , number.