TypeScript 高级类型
<p>⒈交叉类型(Intersection Types)</p><p> 交叉类型是将<strong>多个类型合并为一个类型</strong>。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如, <code>Person & Serializable & Loggable</code>同时是 <code>Person</code> 和 <code>Serializable</code> 和 <code>Loggable</code>。 就是说这个类型的对象同时拥有了这三种类型的成员。</p>
<p> 每当我们正确的使用交叉类型的时候,TypeScript可以帮我们合理地将两个不同类型叠加为新的类型,并包含了所需的所有类型。</p>
<p> 我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在JavaScript里发生这种情况的场合很多!)</p>
<div class="cnblogs_code">
<pre>type newType = number &<span style="color: rgba(0, 0, 0, 1)"> string;
let a : newType;
interface A{
a:number,
b:string,
}
interface B{
c:string,
d:string,
}
type newType2 </span>= A &<span style="color: rgba(0, 0, 0, 1)"> B;
let b : newType2;</span></pre>
</div>
<p> 这里的Type关键字是用来声明类型变量的。在运行时,与类型相关的代码都会被移除掉,并不会影响到JavaScript的执行。</p>
<p> *当交叉类型中有属性冲突时,则无论如何赋值都不可能通过类型检查。如下面的代码所示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface A{
a:number,
b:string,
}
interface B{
c:string,
d:string,
}
type newType </span>= A &<span style="color: rgba(0, 0, 0, 1)"> B;
let a : newType </span>= {a:1,b:'',c:'',d:''<span style="color: rgba(0, 0, 0, 1)">};
a.a </span>= 1<span style="color: rgba(0, 0, 0, 1)">;
a.b </span>= ''<span style="color: rgba(0, 0, 0, 1)">;
a.c </span>= ''<span style="color: rgba(0, 0, 0, 1)">;
a.d </span>= 5;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Error,无法通过类型检查</span></pre>
</div>
<p> </p>
<p>⒉联合类型(Union Types)</p>
<p> 联合类型与交叉类型类似,但使用上却完全不同。</p>
<p> 例如我们需要一个变量可能是number,也有可能是string,这是一个很常见的场景,联合类型便是用于解决这样的问题。</p>
<p> 比如下面这段经典的函数:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> padLeft(value: string, padding: any) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> padding === "number"<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Array(padding + 1).join(" ") +<span style="color: rgba(0, 0, 0, 1)"> value;
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> padding === "string"<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> padding +<span style="color: rgba(0, 0, 0, 1)"> value;
}
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error(`Expected string or number, got '${padding}'<span style="color: rgba(0, 0, 0, 1)">.`);
}
padLeft(</span>"Hello world", 4); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> returns " Hello world"</span></pre>
</div>
<p> <code>padLeft函数</code>存在一个问题, <code>padding</code>参数的类型指定为 <code>any</code>。 也就是说,我们可以传入一个既不是 <code>number</code>也不是 <code>string</code>类型的参数,但是TypeScript却不报错。</p>
<div class="cnblogs_code">
<pre>let indentedString = padLeft("Hello world", <span style="color: rgba(0, 0, 255, 1)">true</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 编译阶段通过,运行时报错</span></pre>
</div>
<p> 在传统的面向对象语言里,我们可以使用重载或将这两种类型抽象成有层级的类型(父类与子类)。 这么做显然是非常清晰的,但同时也存在了过度设计。</p>
<p> 因为在JavaScript中并没有重载可以使用(可以使用特殊的方式创建出类似重载的函数),因此在JavaScript的函数中手动去判断参数的类型这种操作更为常见,这在一定程度上避免了过度设计。</p>
<p> <code>padLeft</code>原始版本的好处之一是允许我们传入原始类型。 这样做的话使用起来既简单又方便。 如果我们就是想使用已经存在的函数的话,这种新的方式就不适用了。</p>
<p> 如果我们希望更准确的描述padding的类型,就可以使用<strong>联合类型</strong>将padding的类型限定为既可以是number又可以是string。</p>
<p> 代替 <code>any</code>, 我们可以使用 <strong>联合类型</strong>作为 <code>padding</code>的参数:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> padLeft(value: string, padding: string |<span style="color: rgba(0, 0, 0, 1)"> number) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
let indentedString </span>= padLeft("Hello world", <span style="color: rgba(0, 0, 255, 1)">true</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 编译器报错,类型true的参数不能赋值给类型string|number的参数</span></pre>
</div>
<p> 联合类型表示一个变量可以是几种类型之一。 我们用竖线( <code>|</code>)分隔每个类型,所以 <code>number | string | boolean</code>表示一个值可以是 <code>number</code>、<code>string</code>或 <code>boolean</code>。</p>
<p> 注意,如果一个值是联合类型,我们只能访问它们共有的属性或方法。</p>
<p> 我们来看一下下面的例子:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span> getSmallPet(): Fish |<span style="color: rgba(0, 0, 0, 1)"> Bird {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
let pet </span>=<span style="color: rgba(0, 0, 0, 1)"> getSmallPet();
pet.layEggs(); </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> okay</span>
pet.swim(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> errors</span></pre>
</div>
<p> 如果一个值的类型是 <code>A | B</code>,我们能够 <em>确定</em>的是它包含了 <code>A</code> <em>和</em> <code>B</code>中共有的成员。 这个例子里, <code>Bird</code>具有一个 <code>fly</code>成员。 我们不能确定一个 <code>Bird | Fish</code>类型的变量是否有 <code>fly</code>方法。 如果变量在运行时是 <code>Fish</code>类型,那么调用 <code>pet.fly()</code>就出错了。</p>
<p> 联合类型取的是交集,交叉类型取的是并集,这听起来和它们的名字有些冲突。</p>
<p> **谨记,TypeScript只会帮你在编译时做类型检查,并不确保你的代码在运行过程中的安全。</p>
<p>⒊类型保护【区分值的类型】</p>
<p> 联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解某个值的类型时该怎么办? JavaScript里常用来区分2个可能值的方法是检查成员是否存在。 如之前提及的,我们只能访问联合类型中共同拥有的成员。</p>
<div class="cnblogs_code">
<pre>let pet =<span style="color: rgba(0, 0, 0, 1)"> getSmallPet();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 每一个成员访问都会报错</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (pet.swim) {
pet.swim();
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span> <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (pet.fly) {
pet.fly();
}</span></pre>
</div>
<p> 而在TypeScript中,我们可以使用类型断言。</p>
<div class="cnblogs_code">
<pre>let pet =<span style="color: rgba(0, 0, 0, 1)"> getSmallPet();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> ((<Fish><span style="color: rgba(0, 0, 0, 1)">pet).swim) {
(</span><Fish><span style="color: rgba(0, 0, 0, 1)">pet).swim();
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
(</span><Bird><span style="color: rgba(0, 0, 0, 1)">pet).fly();
}</span></pre>
</div>
<p> 为了准确判断值的类型,我们在方法体中多次使用了类型断言(即使通过了类型断言,我们知道了值的类型,在接下来的代码中,我们仍然要对其添加类型断言),这是一件非常麻烦的事情。如果我们一旦检查并确定了值的类型,在之后的代码中无需类型断言就能清楚地知道值的类型的话就好了。</p>
<p> 1.自定义类型保护(用户自定义的类型保护)</p>
<p> TypeScript里的 <strong>类型保护</strong>机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 既使可读性得到提升,又减少了使用烦琐的类型断言。要定义一个类型保护,我们只需要简单地定义一个函数就可以,但返回值是一个主谓宾语句( 类型谓词),如下所示:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> isFish(pet: Fish |<span style="color: rgba(0, 0, 0, 1)"> Bird): pet is Fish {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> (<Fish>pet).swim !==<span style="color: rgba(0, 0, 0, 1)"> undefined;
}</span></pre>
</div>
<p> 在这个例子里, <code>pet is Fish</code>就是类型谓词。 谓词为 <code>parameterName is Type</code>这种形式, <code>parameterName</code>必须是来自于当前函数签名里的一个参数名。</p>
<p> 每当使用一些变量调用 <code>isFish</code>时,TypeScript会将变量指定为类型保护中的类型,只要这个类型与变量的原始类型是兼容的。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 'swim' 和 'fly' 调用都没有问题了</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isFish(pet)) {
pet.swim();
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
pet.fly();
}</span></pre>
</div>
<p> 注意,TypeScript不仅知道在 <code>if</code>分支里 <code>pet</code>是 <code>Fish</code>类型; 它还清楚在 <code>else</code>分支里pet一定是 <code>Bird</code>类型,这得益于类型保护的实现。</p>
<p> </p>
<p> 2.<code>typeof</code>类型保护</p>
<p> 现在我们可以使用类型保护来重构一开始的padLeft代码了,可以考虑用联合类型书写 <code>padLeft</code>代码。 可以像下面这样利用类型断言来写:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> isNumber(x: any): x is number {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">typeof</span> x === "number"<span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> isString(x: any): x is string {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">typeof</span> x === "string"<span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span> padLeft(value: string, padding: string |<span style="color: rgba(0, 0, 0, 1)"> number) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isNumber(padding)) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Array(padding + 1).join(" ") +<span style="color: rgba(0, 0, 0, 1)"> value;
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isString(padding)) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> padding +<span style="color: rgba(0, 0, 0, 1)"> value;
}
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error(`Expected string or number, got '${padding}'<span style="color: rgba(0, 0, 0, 1)">.`);
}</span></pre>
</div>
<p> 然而,每次typeof进行类型判断都必须要定义一个函数,这太痛苦了。 幸运的是,现在我们不必将 <code>typeof x === "number"</code>抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> padLeft(value: string, padding: string |<span style="color: rgba(0, 0, 0, 1)"> number) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> padding === "number"<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Array(padding + 1).join(" ") +<span style="color: rgba(0, 0, 0, 1)"> value;
}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> padding === "string"<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> padding +<span style="color: rgba(0, 0, 0, 1)"> value;
}
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error(`Expected string or number, got '${padding}'<span style="color: rgba(0, 0, 0, 1)">.`);
}</span></pre>
</div>
<p> <code>typeof</code>类型保护只有两种形式能被识别: <code>typeof v === "typename"</code>和 <code>typeof v !== "typename"</code>,且typeof在TypeScript中使用时,只有匹配基本类型时(即<code>"typename"</code>必须是 <code>"number"</code>, <code>"string"</code>, <code>"boolean"</code>或 <code>"symbol"</code>),才会启动类型保护 。 但是TypeScript并不会阻止你与其它字符串比较(例如typeof v === 'hello TypeScript'),typeof并不会把它识别为一个有效的类型,因此也不会把这些字符串识别为类型保护。</p>
<p> </p>
<p> 3.<code>instanceof</code>类型保护</p>
<p> 除了typeof以外,instanceof也可以起来类型保护的作用。Instanceof相较于typeof,其类型保护更为精细,是通过构造函数来区分类型的一种方式。</p>
<p> 如果你已经阅读了 <code>typeof</code>类型保护并且对JavaScript里的 <code>instanceof</code>操作符熟悉的话,你可能已经很轻而易举的在TypeScript中使用<code>instanceof</code>类型保护了。</p>
<p><em><code> instanceof</code>类型保护</em>是通过构造函数来细化类型的一种方式。 比如,我们借鉴一下之前字符串填充的例子:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface Padder {
getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) { }
getPaddingString() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Array(<span style="color: rgba(0, 0, 255, 1)">this</span>.numSpaces + 1).join(" "<span style="color: rgba(0, 0, 0, 1)">);
}
}
class StringPadder implements Padder {
constructor(private value: string) { }
getPaddingString() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.value;
}
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getRandomPadder() {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Math.random() < 0.5 ?
<span style="color: rgba(0, 0, 255, 1)">new</span> SpaceRepeatingPadder(4<span style="color: rgba(0, 0, 0, 1)">) :
</span><span style="color: rgba(0, 0, 255, 1)">new</span> StringPadder(""<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 类型为SpaceRepeatingPadder | StringPadder</span>
let padder: Padder =<span style="color: rgba(0, 0, 0, 1)"> getRandomPadder();
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (padder <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> SpaceRepeatingPadder) {
padder; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 类型细化为'SpaceRepeatingPadder'</span>
<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (padder <span style="color: rgba(0, 0, 255, 1)">instanceof</span><span style="color: rgba(0, 0, 0, 1)"> StringPadder) {
padder; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 类型细化为'StringPadder'</span>
}</pre>
</div>
<p> 可以看出instanceof在类型的使用上,与typeof相比,可以将类作为比较对象,从而实现类型保护。</p>
<p><code> instanceof</code>的右侧要求是一个构造函数,TypeScript将细化为:</p>
<ol>
<li>此构造函数的 <code>prototype</code>属性的类型,如果它的类型不为 <code>any</code>的话</li>
<li>构造签名所返回的类型的联合</li>
</ol>
<p> 以此顺序。</p>
<p>⒋可以为null的类型</p>
<p> TypeScript具有两种特殊的类型, <code>null</code>和 <code>undefined</code>,它们分别具有值null和undefined. 我们在[基础类型](./Basic Types.md)一节里已经做过简要说明。 默认情况下,类型检查器认为 <code>null</code>与 <code>undefined</code>可以赋值给任何类型。 <code>null</code>与 <code>undefined</code>是所有其它类型的一个有效值。 这也意味着,你阻止不了将它们赋值给其它类型,就算是你想要阻止这种情况也不行。 <code>null</code>的发明者,Tony Hoare,称它为 价值亿万美金的错误。</p>
<p><code> <span style="font-family: "courier new", courier">--strictNullChecks</span></code>标记可以解决此错误:当你声明一个变量时,它不会自动地包含 <code>null</code>或 <code>undefined</code>。 你可以使用联合类型明确的包含它们</p>
<div class="cnblogs_code">
<pre>let s = "foo"<span style="color: rgba(0, 0, 0, 1)">;
s </span>= <span style="color: rgba(0, 0, 255, 1)">null</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 错误, 'null'不能赋值给'string'</span>
let sn: string | <span style="color: rgba(0, 0, 255, 1)">null</span> = "bar"<span style="color: rgba(0, 0, 0, 1)">;
sn </span>= <span style="color: rgba(0, 0, 255, 1)">null</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 可以</span>
<span style="color: rgba(0, 0, 0, 1)">
sn </span>= undefined; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error, 'undefined'不能赋值给'string | null'</span></pre>
</div>
<p> 注意,按照JavaScript的语义,TypeScript会把 <code>null</code>和 <code>undefined</code>区别对待。 <code>string | null</code>, <code>string | undefined</code>和 <code>string | undefined | null</code>是不同的类型。</p>
<p> </p>
<p>⒌可选参数和可选属性</p>
<p> 使用了<span style="font-family: "courier new", courier"> --<code>strictNullChecks</code></span>,可选参数会被自动地加上 <code>| undefined</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> f(x: number, y?<span style="color: rgba(0, 0, 0, 1)">: number) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> x + (y || 0<span style="color: rgba(0, 0, 0, 1)">);
}
f(</span>1, 2<span style="color: rgba(0, 0, 0, 1)">);
f(</span>1<span style="color: rgba(0, 0, 0, 1)">);
f(</span>1<span style="color: rgba(0, 0, 0, 1)">, undefined);
f(</span>1, <span style="color: rgba(0, 0, 255, 1)">null</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error, 'null' is not assignable to 'number | undefined'</span></pre>
</div>
<p> 可选属性也会有同样的处理:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class C {
a: number;
b</span>?<span style="color: rgba(0, 0, 0, 1)">: number;
}
let c </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> C();
c.a </span>= 12<span style="color: rgba(0, 0, 0, 1)">;
c.a </span>= undefined; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error, 'undefined' is not assignable to 'number'</span>
c.b = 13<span style="color: rgba(0, 0, 0, 1)">;
c.b </span>= undefined; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ok</span>
c.b = <span style="color: rgba(0, 0, 255, 1)">null</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error, 'null' is not assignable to 'number | undefined'</span></pre>
</div>
<p> </p>
<p>⒍类型保护和类型断言</p>
<p> 由于可以为null的类型是通过联合类型实现,那么你需要使用类型保护来去除 <code>null</code>。 幸运地是这与在JavaScript里写的代码一致:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> f(sn: string | <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">): string {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (sn == <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> "default"<span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> sn;
}
}</span></pre>
</div>
<p> 这里很明显地去除了 <code>null</code>,你也可以使用短路运算符:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> f(sn: string | <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">): string {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> sn || "default"<span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p> 如果编译器不能够去除 <code>null</code>或 <code>undefined</code>,你可以使用类型断言手动去除。 语法是添加 <code>!</code>后缀: <code>identifier!</code>从 <code>identifier</code>的类型里去除了 <code>null</code>和 <code>undefined</code>:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> broken(name: string | <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">): string {
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> postfix(epithet: string) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> name.charAt(0) + '.the ' + epithet; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error, 'name' is possibly null</span>
<span style="color: rgba(0, 0, 0, 1)">}
name </span>= name || "Bob"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> postfix("great"<span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span> fixed(name: string | <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">): string {
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> postfix(epithet: string) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> name!.charAt(0) + '.the ' + epithet; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ok</span>
<span style="color: rgba(0, 0, 0, 1)">}
name </span>= name || "Bob"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> postfix("great"<span style="color: rgba(0, 0, 0, 1)">);
}</span></pre>
</div>
<p> 本例使用了嵌套函数,因为编译器无法去除嵌套函数的null(除非是立即调用的函数表达式)。 因为它无法跟踪所有对嵌套函数的调用,尤其是你将内层函数做为外层函数的返回值。 如果无法知道函数在哪里被调用,就无法知道调用时 <code>name</code>的类型。</p>
<p> </p>
<p>⒎类型别名</p>
<p> 类型别名就是可以给一个类型起个新名字。类型别名有时和接口很像, 类型别名可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。</p>
<p> 如果你学过C语言,可能还记得alias关键字,不过在TypeScript中,我们使用type关键字来描述类型变量。 </p>
<div class="cnblogs_code">
<pre>type Name =<span style="color: rgba(0, 0, 0, 1)"> string;
type NameResolver </span>= () =><span style="color: rgba(0, 0, 0, 1)"> string;
type NameOrResolver </span>= Name |<span style="color: rgba(0, 0, 0, 1)"> NameResolver;
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getName(n: NameOrResolver): Name {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">typeof</span> n === 'string'<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> n;
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> n();
}
}</span></pre>
</div>
<p> 使用别名并不会在类型系统中新建一个类型 - 它创建了一个新的<em>名字</em>来<strong>引用</strong>那个类型。 给基本类型起别名通常没什么用,一般是用来减少文档的编写量。</p>
<p> 类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入:</p>
<div class="cnblogs_code">
<pre>type Person<T> = {age : T};</pre>
</div>
<p> 也可以使用类型别名在属性里引用自己,这看起来很像是递归。 </p>
<div class="cnblogs_code">
<pre>type Person<T> =<span style="color: rgba(0, 0, 0, 1)"> {
name : T;
mother : Person</span><T><span style="color: rgba(0, 0, 0, 1)">;
father : Person</span><T><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p> 这使得类型编排非常复杂。当然,这种复杂性是为了描述的准确性,正如上面的例子,mother和father肯定也是person。这样在代码中看上去有点不可思议的操作,在现实世界中却是非常真实合理的。</p>
<p> 与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。</p>
<div class="cnblogs_code">
<pre>type LinkedList<T> = T & { next: LinkedList<T><span style="color: rgba(0, 0, 0, 1)"> };
interface Person {
name: string;
}
</span><span style="color: rgba(0, 0, 255, 1)">var</span> people: LinkedList<Person><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> s =<span style="color: rgba(0, 0, 0, 1)"> people.name;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> s =<span style="color: rgba(0, 0, 0, 1)"> people.next.name;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> s =<span style="color: rgba(0, 0, 0, 1)"> people.next.next.name;
</span><span style="color: rgba(0, 0, 255, 1)">var</span> s = people.next.next.next.name;</pre>
</div>
<p> 然而,类型别名不能出现在声明右侧的任何地方。</p>
<div class="cnblogs_code">
<pre>type Yikes = Array<Yikes>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error</span></pre>
</div>
<h2> 接口 vs. 类型别名</h2>
<p> 像我们提到的,类型别名可以像接口一样;然而,仍有一些细微差别。</p>
<p> 其一,接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。 在下面的示例代码里,在编译器中将鼠标悬停在 <code>interfaced</code>上,显示它返回的是 <code>Interface</code>,但悬停在 <code>aliased</code>上时,显示的却是对象字面量类型。</p>
<div class="cnblogs_code">
<pre>type Alias =<span style="color: rgba(0, 0, 0, 1)"> { num: number }
interface Interface {
num: number;
}
declare </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> aliased(arg: Alias): Alias;
declare </span><span style="color: rgba(0, 0, 255, 1)">function</span> interfaced(arg: Interface): Interface;</pre>
</div>
<p> 另一个重要区别是类型别名不能被 <code>extends</code>和 <code>implements</code>(自己也不能 <code>extends</code>和 <code>implements</code>其它类型)。 因为 软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。</p>
<p> 另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。</p>
<p> </p>
<p>⒏字符串字面量类型</p>
<p> 我们先看一个简单的字面量类型,比如下面这个字符串常量。</p>
<div class="cnblogs_code">
<pre>type Profession = "teacher";</pre>
</div>
<p> 字符串字面量类型允许你指定字符串必须的固定值。</p>
<p> 在实际应用中,通常字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 而通过结合使用这些特性,达到类似枚举类型的效果。</p>
<div class="cnblogs_code">
<pre>type Profession = "teacher" | "doctor" | "accountant"<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> personCreator(Profession : Profession){
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">省略函数内部的具体实现,这并不影响案例的运行</span>
<span style="color: rgba(0, 0, 0, 1)">}
personCreator(</span>"teacher"<span style="color: rgba(0, 0, 0, 1)">);
personCreator(</span>"doctor"<span style="color: rgba(0, 0, 0, 1)">);
personCreator(</span>"accountant"<span style="color: rgba(0, 0, 0, 1)">);
personCreator(</span>"programmer");<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">Error</span></pre>
</div>
<p> 你只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误。【参见上面的联合类型】</p>
<p> 字符串字面量类型还可以用于区分函数重载:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> createElement(tagName: "img"<span style="color: rgba(0, 0, 0, 1)">): HTMLImageElement;
</span><span style="color: rgba(0, 0, 255, 1)">function</span> createElement(tagName: "input"<span style="color: rgba(0, 0, 0, 1)">): HTMLInputElement;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ... more overloads ...</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> createElement(tagName: string): Element {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ... code goes here ...</span>
}</pre>
</div>
<p> </p>
<p>⒐数字字面量类型</p>
<p> TypeScript还具有数字字面量类型,其用法和字符串字面量一致。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> rollDie(): 1 | 2 | 3 | 4 | 5 | 6<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ...</span>
}</pre>
</div>
<p> 我们很少直接这样使用,但它们可以用在缩小范围调试bug的时候:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> foo(x: number) {
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (x !== 1 || x !== 2<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ~~~~~~~</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Operator '!==' cannot be applied to types '1' and '2'.</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
<p> 换句话说,当 <code>x</code>与 <code>2</code>进行比较的时候,它的值必须为 <code>1</code>,这就意味着上面的比较检查是非法的。</p>
<p> </p>
<p>⒑枚举成员类型</p>
<p> 如我们在 枚举一节里提到的,当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的。</p>
<p> 在我们谈及“单例类型”的时候,多数是指枚举成员类型和数字/字符串字面量类型,尽管大多数用户会互换使用“单例类型”和“字面量类型”。</p>
<h1> 可辨识联合(Discriminated Unions)</h1>
<p> 你可以合并单例类型,联合类型,类型保护和类型别名来创建一个叫做 <em>可辨识联合</em>的高级模式,它也称做 <em>标签联合</em>或 <em>代数数据类型</em>。 可辨识联合在函数式编程很有用处。 一些语言会自动地为你辨识联合;而TypeScript则基于已有的JavaScript模式。 它具有3个要素:</p>
<ol>
<li>具有普通的单例类型属性— <em>可辨识的特征</em>。</li>
<li>一个类型别名包含了那些类型的联合— <em>联合</em>。</li>
<li>此属性上的类型保护。</li>
</ol>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface Square {
kind: </span>"square"<span style="color: rgba(0, 0, 0, 1)">;
size: number;
}
interface Rectangle {
kind: </span>"rectangle"<span style="color: rgba(0, 0, 0, 1)">;
width: number;
height: number;
}
interface Circle {
kind: </span>"circle"<span style="color: rgba(0, 0, 0, 1)">;
radius: number;
}</span></pre>
</div>
<p> 首先我们声明了将要联合的接口。 每个接口都有 <code>kind</code>属性但有不同的字符串字面量类型。 <code>kind</code>属性称做 <em>可辨识的特征</em>或 <em>标签</em>。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。 下面我们把它们联合到一起:</p>
<div class="cnblogs_code">
<pre>type Shape = Square | Rectangle | Circle;</pre>
</div>
<p> 现在我们使用可辨识联合:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> area(s: Shape) {
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (s.kind) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "square": <span style="color: rgba(0, 0, 255, 1)">return</span> s.size *<span style="color: rgba(0, 0, 0, 1)"> s.size;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "rectangle": <span style="color: rgba(0, 0, 255, 1)">return</span> s.height *<span style="color: rgba(0, 0, 0, 1)"> s.width;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "circle": <span style="color: rgba(0, 0, 255, 1)">return</span> Math.PI * s.radius ** 2<span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<h2> 完整性检查</h2>
<p> 当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们添加了 <code>Triangle</code>到 <code>Shape</code>,我们同时还需要更新 <code>area</code>:</p>
<div class="cnblogs_code">
<pre>type Shape = Square | Rectangle | Circle |<span style="color: rgba(0, 0, 0, 1)"> Triangle;
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> area(s: Shape) {
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (s.kind) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "square": <span style="color: rgba(0, 0, 255, 1)">return</span> s.size *<span style="color: rgba(0, 0, 0, 1)"> s.size;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "rectangle": <span style="color: rgba(0, 0, 255, 1)">return</span> s.height *<span style="color: rgba(0, 0, 0, 1)"> s.width;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "circle": <span style="color: rgba(0, 0, 255, 1)">return</span> Math.PI * s.radius ** 2<span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> should error here - we didn't handle case "triangle"</span>
}</pre>
</div>
<p> 有两种方式可以实现。 首先是启用 <code>--strictNullChecks</code>并且指定一个返回值类型:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> area(s: Shape): number { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error: returns number | undefined</span>
<span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (s.kind) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "square": <span style="color: rgba(0, 0, 255, 1)">return</span> s.size *<span style="color: rgba(0, 0, 0, 1)"> s.size;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "rectangle": <span style="color: rgba(0, 0, 255, 1)">return</span> s.height *<span style="color: rgba(0, 0, 0, 1)"> s.width;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "circle": <span style="color: rgba(0, 0, 255, 1)">return</span> Math.PI * s.radius ** 2<span style="color: rgba(0, 0, 0, 1)">;
}
}</span></pre>
</div>
<p> 因为 <code>switch</code>没有包涵所有情况,所以TypeScript认为这个函数有时候会返回 <code>undefined</code>。 如果你明确地指定了返回值类型为 <code>number</code>,那么你会看到一个错误,因为实际上返回值的类型为 <code>number | undefined</code>。 然而,这种方法存在些微妙之处且 <code>--strictNullChecks</code>对旧代码支持不好。</p>
<p> 第二种方法使用 <code>never</code>类型,编译器用它来进行完整性检查:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> assertNever(x: never): never {
</span><span style="color: rgba(0, 0, 255, 1)">throw</span> <span style="color: rgba(0, 0, 255, 1)">new</span> Error("Unexpected object: " +<span style="color: rgba(0, 0, 0, 1)"> x);
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> area(s: Shape) {
</span><span style="color: rgba(0, 0, 255, 1)">switch</span><span style="color: rgba(0, 0, 0, 1)"> (s.kind) {
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "square": <span style="color: rgba(0, 0, 255, 1)">return</span> s.size *<span style="color: rgba(0, 0, 0, 1)"> s.size;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "rectangle": <span style="color: rgba(0, 0, 255, 1)">return</span> s.height *<span style="color: rgba(0, 0, 0, 1)"> s.width;
</span><span style="color: rgba(0, 0, 255, 1)">case</span> "circle": <span style="color: rgba(0, 0, 255, 1)">return</span> Math.PI * s.radius ** 2<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">default</span>: <span style="color: rgba(0, 0, 255, 1)">return</span> assertNever(s); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error here if there are missing cases</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}</span></pre>
</div>
<p> 这里, <code>assertNever</code>检查 <code>s</code>是否为 <code>never</code>类型—即为除去所有可能情况后剩下的类型。 如果你忘记了某个case,那么 <code>s</code>将具有一个真实的类型并且你会得到一个错误。 这种方式需要你定义一个额外的函数,但是在你忘记某个case的时候也更加明显。</p>
<h1> 多态的 <code>this</code>类型</h1>
<p> 多态的 <code>this</code>类型表示的是某个包含类或接口的 <em>子类型</em>。 这被称做 <em>F</em>-bounded多态性。 它能很容易的表现连贯接口间的继承,比如。 在计算器的例子里,在每个操作之后都返回 <code>this</code>类型:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class BasicCalculator {
public constructor(protected value: number </span>= 0<span style="color: rgba(0, 0, 0, 1)">) { }
public currentValue(): number {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.value;
}
public add(operand: number): </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.value +=<span style="color: rgba(0, 0, 0, 1)"> operand;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;
}
public multiply(operand: number): </span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.value *=<span style="color: rgba(0, 0, 0, 1)"> operand;
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ... other operations go here ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
let v </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> BasicCalculator(2<span style="color: rgba(0, 0, 0, 1)">)
.multiply(</span>5<span style="color: rgba(0, 0, 0, 1)">)
.add(</span>1<span style="color: rgba(0, 0, 0, 1)">)
.currentValue();</span></pre>
</div>
<p> 由于这个类使用了 <code>this</code>类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">class ScientificCalculator extends BasicCalculator {
public constructor(value </span>= 0<span style="color: rgba(0, 0, 0, 1)">) {
super(value);
}
public sin() {
</span><span style="color: rgba(0, 0, 255, 1)">this</span>.value = Math.sin(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.value);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ... other operations go here ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
let v </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> ScientificCalculator(2<span style="color: rgba(0, 0, 0, 1)">)
.multiply(</span>5<span style="color: rgba(0, 0, 0, 1)">)
.sin()
.add(</span>1<span style="color: rgba(0, 0, 0, 1)">)
.currentValue();</span></pre>
</div>
<p> 如果没有 <code>this</code>类型, <code>ScientificCalculator</code>就不能够在继承 <code>BasicCalculator</code>的同时还保持接口的连贯性。 <code>multiply</code>将会返回 <code>BasicCalculator</code>,它并没有 <code>sin</code>方法。 然而,使用 <code>this</code>类型, <code>multiply</code>会返回 <code>this</code>,在这里就是 <code>ScientificCalculator</code>。</p>
<h1> 索引类型(Index types)</h1>
<p> 使用索引类型,编译器就能够检查使用了动态属性名的代码。例如,一个常见的JavaScript模式是从对象中选取属性的子集。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> pluck(o, names) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> names.map(n =><span style="color: rgba(0, 0, 0, 1)"> o);
}</span></pre>
</div>
<p> 在TypeScript里通过 <strong>索引类型查询</strong>和 <strong>索引访问</strong>操作符使用此函数:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> pluck<T, K extends keyof T><span style="color: rgba(0, 0, 0, 1)">(o: T, names: K[]): T[] {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> names.map(n =><span style="color: rgba(0, 0, 0, 1)"> o);
}
interface Person {
name: string;
age: number;
}
let person: Person </span>=<span style="color: rgba(0, 0, 0, 1)"> {
name: </span>'Jarid'<span style="color: rgba(0, 0, 0, 1)">,
age: </span>35<span style="color: rgba(0, 0, 0, 1)">
};
let strings: string[] </span>= pluck(person, ['name']); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ok, string[]</span></pre>
</div>
<p> 编译器会检查传入的值是否是 <code>Person</code>的一个属性。 本例还引入了几个新的类型操作符。 首先是 <code>keyof T</code>, <strong>索引类型查询操作符</strong>。 对于任何类型 <code>T</code>, <code>keyof T</code>的结果为 <code>T</code>上已知的公共属性名的联合。 例如:</p>
<div class="cnblogs_code">
<pre>let personProps: keyof Person; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 'name' | 'age'</span></pre>
</div>
<p><code> keyof Person</code>是完全可以与 <code>'name' | 'age'</code>互相替换的。 不同的是如果你为Person添加了新的属性 ,例如 <code>address: string</code>,那么 <code>keyof Person</code>会自动变为 <code>'name' | 'age' | 'address'</code>。 你可以在像 <code>pluck</code>函数这类上下文里使用 <code>keyof</code>,因为在使用之前你并不清楚可能出现的属性名。 但编译器会检查你是否传入了正确的属性名给 <code>pluck</code>:</p>
<div class="cnblogs_code">
<pre>pluck(person, ['age', 'unknown']); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error, 'unknown' is not in 'name' | 'age'</span></pre>
</div>
<p> 第二个操作符是 <code>T</code>, <strong>索引访问操作符</strong>。 在这里,类型语法反映了表达式语法。 这意味着 <code>person['name']</code>具有类型 <code>Person['name']</code> — 在我们的例子里则为 <code>string</code>类型。 然而,就像索引类型查询一样,你可以在普通的上下文里使用 <code>T</code>,这正是它的强大所在。 你只要确保类型变量 <code>K extends keyof T</code>就可以了。 例如下面 <code>getProperty</code>函数的例子:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> getProperty<T, K extends keyof T><span style="color: rgba(0, 0, 0, 1)">(o: T, name: K): T {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> o; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> o is of type T</span>
}</pre>
</div>
<p><code> getProperty</code>里的 <code>o: T</code>和 <code>name: K</code>,意味着 <code>o: T</code>。 当你返回 <code>T</code>的结果,编译器会实例化键的真实类型,因此 <code>getProperty</code>的返回值类型会随着你需要的属性改变。</p>
<div class="cnblogs_code">
<pre>let name: string = getProperty(person, 'name'<span style="color: rgba(0, 0, 0, 1)">);
let age: number </span>= getProperty(person, 'age'<span style="color: rgba(0, 0, 0, 1)">);
let unknown </span>= getProperty(person, 'unknown'); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> error, 'unknown' is not in 'name' | 'age'</span></pre>
</div>
<h2> 索引类型和字符串索引签名</h2>
<p><code> keyof</code>和 <code>T</code>与字符串索引签名进行交互。 如果你有一个带有字符串索引签名的类型,那么 <code>keyof T</code>会是 <code>string</code>。 并且 <code>T</code>为索引签名的类型:</p>
<div class="cnblogs_code">
<pre>interface Map<T><span style="color: rgba(0, 0, 0, 1)"> {
: T;
}
let keys: keyof Map</span><number>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> string</span>
let value: Map<number>['foo']; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> number</span></pre>
</div>
<p> 让我们解释下上面一开始代码的意义。首先看泛型,这里有T和K两种类型。根据类型推断,第一个参数o就是person,类型会被推断为Person,而第二个数组参数的类型推断,我们可以从右往左进行阅读,keyof关键字可以获取T(此处为Person)的所有属性名,即['name','age'],泛型K通过extends关键字继承了T(此处为Person)的所有属性名,即['name','age']。</p>
<p> 依托于keyof关键字完成了类型索引。</p>
<p> 我们再来看返回值,返回值的类型是T[],阅读起来有些困难,它实际上表述的意思是,变量T取属性K的值的数组,其中T就是索引访问操作符。</p>
<p> 这样强大的功能保证了代码的动态性和准确性,也让代码提示变得更加丰富了。</p>
<h1> 映射类型</h1>
<p> 一种常见的场景是将一个已知类型的每个属性都变为可选的,这样在实例化该类型时就不必为每个类型都赋值了。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface Person {
name</span>?<span style="color: rgba(0, 0, 0, 1)">: string;
age</span>?<span style="color: rgba(0, 0, 0, 1)">: number;
}</span></pre>
</div>
<p> 或者是我们想要一个该类型的只读版本【即该类型的属性值都是只读不可修改的】</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">interface Person {
readonly name: string;
readonly age: number;
}</span></pre>
</div>
<p> 这在JavaScript里会经常用到,而TypeScript提供了从旧类型中创建新类型的一种方式 — <strong>映射类型</strong>。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。 例如,你可以令每个属性成为只读类型或可选类型。 下面是一些例子:</p>
<div class="cnblogs_code">
<pre>type Readonly<T> =<span style="color: rgba(0, 0, 0, 1)"> {
readonly : T;
}
type Partial</span><T> =<span style="color: rgba(0, 0, 0, 1)"> {
?<span style="color: rgba(0, 0, 0, 1)">: T;
}</span></pre>
</div>
<p> 像下面这样使用:</p>
<div class="cnblogs_code">
<pre>type PersonPartial = Partial<Person><span style="color: rgba(0, 0, 0, 1)">;
type ReadonlyPerson </span>= Readonly<Person>;</pre>
</div>
<p> 下面来看看最简单的映射类型和它的组成部分:</p>
<div class="cnblogs_code">
<pre>type Keys = 'option1' | 'option2'<span style="color: rgba(0, 0, 0, 1)">;
type Flags </span>= { : <span style="color: rgba(0, 0, 255, 1)">boolean</span> };</pre>
</div>
<p> 它的语法与索引签名的语法类型,内部使用了 <code>for .. in</code>。 具有三个部分:</p>
<ol>
<li>类型变量 <code>K</code>,它会依次绑定到每个属性。</li>
<li>字符串字面量联合的 <code>Keys</code>,它包含了要迭代的属性名的集合。</li>
<li>属性的结果类型。</li>
</ol>
<p> 在个简单的例子里, <code>Keys</code>是硬编码的的属性名列表并且属性类型永远是 <code>boolean</code>,因此这个映射类型等同于:</p>
<div class="cnblogs_code">
<pre>type Flags =<span style="color: rgba(0, 0, 0, 1)"> {
option1: </span><span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">;
option2: </span><span style="color: rgba(0, 0, 255, 1)">boolean</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<p> 在真正的应用里,可能不同于上面的 <code>Readonly</code>或 <code>Partial</code>。 它们会基于一些已存在的类型,且按照一定的方式转换字段。 这就是 <code>keyof</code>和索引访问类型要做的事情:</p>
<div class="cnblogs_code">
<pre>type NullablePerson = { : Person | <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)"> }
type PartialPerson </span>= { ?: Person }</pre>
</div>
<p> 但它更有用的地方是可以有一些通用版本。</p>
<div class="cnblogs_code">
<pre>type Nullable<T> = { : T | <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)"> }
type Partial</span><T> = { ?: T }</pre>
</div>
<p> 在这些例子里,属性列表是 <code>keyof T</code>且结果类型是 <code>T</code>的变体。 这是使用通用映射类型的一个好模版。 因为这类转换是 同态的,映射只作用于 <code>T</code>的属性而没有其它的。 编译器知道在添加任何新属性之前可以拷贝所有存在的属性修饰符。 例如,假设 <code>Person.name</code>是只读的,那么 <code>Partial<Person>.name</code>也将是只读的且为可选的。</p>
<p> 下面是另一个例子, <code>T</code>被包装在 <code>Proxy<T></code>类里:</p>
<div class="cnblogs_code">
<pre>type Proxy<T> =<span style="color: rgba(0, 0, 0, 1)"> {
get(): T;
set(value: T): </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)">;
}
type Proxify</span><T> =<span style="color: rgba(0, 0, 0, 1)"> {
: Proxy<T><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 0, 255, 1)">function</span> proxify<T>(o: T): Proxify<T><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ... wrap proxies ...</span>
<span style="color: rgba(0, 0, 0, 1)">}
let proxyProps </span>= proxify(props);</pre>
</div>
<p> 注意 <code>Readonly<T></code>和 <code>Partial<T></code>用处不小,因此它们与 <code>Pick</code>和 <code>Record</code>一同被包含进了TypeScript的标准库里:</p>
<div class="cnblogs_code">
<pre>type Pick<T, K extends keyof T> =<span style="color: rgba(0, 0, 0, 1)"> {
: T;
}
type Record</span><K extends string, T> =<span style="color: rgba(0, 0, 0, 1)"> {
: T;
}</span></pre>
</div>
<p><code> Readonly</code>, <code>Partial</code>和 <code>Pick</code>是同态的,但 <code>Record</code>不是。 因为 <code>Record</code>并不需要输入类型来拷贝属性,所以它不属于同态:</p>
<div class="cnblogs_code">
<pre>type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string></pre>
</div>
<p> 非同态类型本质上会创建新的属性,因此它们不会从它处拷贝属性修饰符。</p>
<p> TypeScript中内置了Readonly和Partial,所以不需要手动声明实现。</p>
<p> 内置的类型还有Required、Pick、Record、Exclude、Extract、NonNullable;它们的实现都在typescript/lib/lib.es5.d.ts中。</p>
<h2> 由映射类型进行推断</h2>
<p> 现在你了解了如何包装一个类型的属性,那么接下来就是如何拆包。 其实这也非常容易:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">function</span> unproxify<T>(t: Proxify<T><span style="color: rgba(0, 0, 0, 1)">): T {
let result </span>=<span style="color: rgba(0, 0, 0, 1)"> {} as T;
</span><span style="color: rgba(0, 0, 255, 1)">for</span> (const k <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> t) {
result </span>=<span style="color: rgba(0, 0, 0, 1)"> t.get();
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> result;
}
let originalProps </span>= unproxify(proxyProps);</pre>
</div>
<p> 注意这个拆包推断只适用于同态的映射类型。 如果映射类型不是同态的,那么需要给拆包函数一个明确的类型参数。</p>
<h3> 预定义的有条件类型</h3>
<p> TypeScript 2.8在<code>lib.d.ts</code>里增加了一些预定义的有条件类型:</p>
<ul>
<li><code>Exclude<T, U></code> -- 从<code>T</code>中剔除可以赋值给<code>U</code>的类型。</li>
<li><code>Extract<T, U></code> -- 提取<code>T</code>中可以赋值给<code>U</code>的类型。</li>
<li><code>NonNullable<T></code> -- 从<code>T</code>中剔除<code>null</code>和<code>undefined</code>。</li>
<li><code>ReturnType<T></code> -- 获取函数返回值类型。</li>
<li><code>InstanceType<T></code> -- 获取构造函数类型的实例类型。</li>
</ul>
<h4> 示例</h4>
<div class="cnblogs_code">
<pre>type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "b" | "d"</span>
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> "a" | "c"</span>
<span style="color: rgba(0, 0, 0, 1)">
type T02 </span>= Exclude<string | number | (() => <span style="color: rgba(0, 0, 255, 1)">void</span>), Function>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> string | number</span>
type T03 = Extract<string | number | (() => <span style="color: rgba(0, 0, 255, 1)">void</span>), Function>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> () => void</span>
<span style="color: rgba(0, 0, 0, 1)">
type T04 </span>= NonNullable<string | number | undefined>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> string | number</span>
type T05 = NonNullable<(() => string) | string[] | <span style="color: rgba(0, 0, 255, 1)">null</span> | undefined>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> (() => string) | string[]</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> f1(s: string) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> { a: 1<span style="color: rgba(0, 0, 0, 1)">, b: s };
}
class C {
x </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
y </span>= 0<span style="color: rgba(0, 0, 0, 1)">;
}
type T10 </span>= ReturnType<() => string>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> string</span>
type T11 = ReturnType<(s: string) => <span style="color: rgba(0, 0, 255, 1)">void</span>>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> void</span>
type T12 = ReturnType<(<T>() => T)>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> {}</span>
type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> number[]</span>
type T14 = ReturnType<<span style="color: rgba(0, 0, 255, 1)">typeof</span> f1>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { a: number, b: string }</span>
type T15 = ReturnType<any>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> any</span>
type T16 = ReturnType<never>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> any</span>
type T17 = ReturnType<string>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Error</span>
type T18 = ReturnType<Function>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Error</span>
<span style="color: rgba(0, 0, 0, 1)">
type T20 </span>= InstanceType<<span style="color: rgba(0, 0, 255, 1)">typeof</span> C>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> C</span>
type T21 = InstanceType<any>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> any</span>
type T22 = InstanceType<never>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> any</span>
type T23 = InstanceType<string>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Error</span>
type T24 = InstanceType<Function>;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Error</span></pre>
</div>
<p> 注意:<code>Exclude</code>类型是建议的<code>Diff</code>类型的一种实现。我们使用<code>Exclude</code>这个名字是为了避免破坏已经定义了<code>Diff</code>的代码,并且我们感觉这个名字能更好地表达类型的语义。我们没有增加<code>Omit<T, K></code>类型,因为它可以很容易的用<code>Pick<T, Exclude<keyof T, K>></code>来表示。</p>
<p> </p><br><br>
来源:https://www.cnblogs.com/fanqisoft/p/11939900.html
頁:
[1]