TypeScript高级类型
<h3>交叉类型(Intersection Types)</h3><p>交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如,<code class="highlighter-rouge">Person & Serializable & Loggable</code>同时是<code class="highlighter-rouge">Person</code><em>和</em><code class="highlighter-rouge">Serializable</code><em>和</em><code class="highlighter-rouge">Loggable</code>。 就是说这个类型的对象同时拥有了这三种类型的成员。</p>
<p>我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在JavaScript里发生这种情况的场合很多!) 下面是如何创建混入的一个简单例子:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">extend<span class="o"><<span class="nx">T<span class="p">, <span class="nx">U<span class="o">><span class="p">(<span class="nx">first<span class="err">: <span class="nx">T<span class="p">, <span class="nx">second<span class="err">: <span class="nx">U<span class="p">)<span class="err">: <span class="nx">T <span class="o">& <span class="nx">U <span class="p">{
<span class="kd">let <span class="nx">result <span class="o">= <span class="o"><<span class="nx">T <span class="o">& <span class="nx">U<span class="o">><span class="p">{};
<span class="k">for <span class="p">(<span class="kd">let <span class="nx">id <span class="k">in <span class="nx">first<span class="p">) <span class="p">{
<span class="p">(<span class="o"><<span class="kr">any<span class="o">><span class="nx">result<span class="p">)[<span class="nx">id<span class="p">] <span class="o">= <span class="p">(<span class="o"><<span class="kr">any<span class="o">><span class="nx">first<span class="p">)[<span class="nx">id<span class="p">];
<span class="p">}
<span class="k">for <span class="p">(<span class="kd">let <span class="nx">id <span class="k">in <span class="nx">second<span class="p">) <span class="p">{
<span class="k">if <span class="p">(<span class="o">!<span class="nx">result<span class="p">.<span class="nx">hasOwnProperty<span class="p">(<span class="nx">id<span class="p">)) <span class="p">{
<span class="p">(<span class="o"><<span class="kr">any<span class="o">><span class="nx">result<span class="p">)[<span class="nx">id<span class="p">] <span class="o">= <span class="p">(<span class="o"><<span class="kr">any<span class="o">><span class="nx">second<span class="p">)[<span class="nx">id<span class="p">];
<span class="p">}
<span class="p">}
<span class="k">return <span class="nx">result<span class="p">;
<span class="p">}
<span class="kr">class <span class="nx">Person <span class="p">{
<span class="kd">constructor<span class="p">(<span class="k">public <span class="nx">name<span class="err">: <span class="kr">string<span class="p">) <span class="p">{ <span class="p">}
<span class="p">}
<span class="kr">interface <span class="nx">Loggable <span class="p">{
<span class="nx">log<span class="p">()<span class="err">: <span class="k">void<span class="p">;
<span class="p">}
<span class="kr">class <span class="nx">ConsoleLogger <span class="k">implements <span class="nx">Loggable <span class="p">{
<span class="nx">log<span class="p">() <span class="p">{
<span class="c1">// ...
<span class="p">}
<span class="p">}
<span class="kd">var <span class="nx">jim <span class="o">= <span class="nx">extend<span class="p">(<span class="k">new <span class="nx">Person<span class="p">(<span class="s2">"Jim"<span class="p">), <span class="k">new <span class="nx">ConsoleLogger<span class="p">());
<span class="kd">var <span class="nx">n <span class="o">= <span class="nx">jim<span class="p">.<span class="nx">name<span class="p">;
<span class="nx">jim<span class="p">.<span class="nx">log<span class="p">();
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h1 id="联合类型union-types">联合类型(Union Types)</h1>
<p>联合类型与交叉类型很有关联,但是使用上却完全不同。 偶尔你会遇到这种情况,一个代码库希望传入<code class="highlighter-rouge">number</code>或<code class="highlighter-rouge">string</code>类型的参数。 例如下面的函数:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="cm">/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
<span class="kd">function <span class="nx">padLeft<span class="p">(<span class="nx">value<span class="err">: <span class="kr">string<span class="p">, <span class="nx">padding<span class="err">: <span class="kr">any<span class="p">) <span class="p">{
<span class="k">if <span class="p">(<span class="k">typeof <span class="nx">padding <span class="o">=== <span class="s2">"number"<span class="p">) <span class="p">{
<span class="k">return <span class="nb">Array<span class="p">(<span class="nx">padding <span class="o">+ <span class="mi">1<span class="p">).<span class="nx">join<span class="p">(<span class="s2">" "<span class="p">) <span class="o">+ <span class="nx">value<span class="p">;
<span class="p">}
<span class="k">if <span class="p">(<span class="k">typeof <span class="nx">padding <span class="o">=== <span class="s2">"string"<span class="p">) <span class="p">{
<span class="k">return <span class="nx">padding <span class="o">+ <span class="nx">value<span class="p">;
<span class="p">}
<span class="k">throw <span class="k">new <span class="nb">Error<span class="p">(<span class="err">`<span class="nx">Expected <span class="kr">string <span class="nx">or <span class="kr">number<span class="p">, <span class="nx">got <span class="s1">'${padding}'<span class="p">.<span class="err">`<span class="p">);
<span class="p">}
<span class="nx">padLeft<span class="p">(<span class="s2">"Hello world"<span class="p">, <span class="mi">4<span class="p">); <span class="c1">// returns " Hello world"
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p><code class="highlighter-rouge">padLeft</code>存在一个问题,<code class="highlighter-rouge">padding</code>参数的类型指定成了<code class="highlighter-rouge">any</code>。 这就是说我们可以传入一个既不是<code class="highlighter-rouge">number</code>也不是<code class="highlighter-rouge">string</code>类型的参数,但是TypeScript却不报错。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">let <span class="nx">indentedString <span class="o">= <span class="nx">padLeft<span class="p">(<span class="s2">"Hello world"<span class="p">, <span class="kc">true<span class="p">); <span class="c1">// 编译阶段通过,运行时报错
</span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>在传统的面向对象语言里,我们可能会将这两种类型抽象成有层级的类型。 这么做显然是非常清晰的,但同时也存在了过度设计。 <code class="highlighter-rouge">padLeft</code>原始版本的好处之一是允许我们传入原始类型。 这样做的话使用起来既简单又方便。 如果我们就是想使用已经存在的函数的话,这种新的方式就不适用了。</p>
<p>代替<code class="highlighter-rouge">any</code>, 我们可以使用<em>联合类型</em>做为<code class="highlighter-rouge">padding</code>的参数:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="cm">/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
<span class="kd">function <span class="nx">padLeft<span class="p">(<span class="nx">value<span class="err">: <span class="kr">string<span class="p">, <span class="nx">padding<span class="err">: <span class="kr">string <span class="o">| <span class="kr">number<span class="p">) <span class="p">{
<span class="c1">// ...
<span class="p">}
<span class="kd">let <span class="nx">indentedString <span class="o">= <span class="nx">padLeft<span class="p">(<span class="s2">"Hello world"<span class="p">, <span class="kc">true<span class="p">); <span class="c1">// errors during compilation
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>联合类型表示一个值可以是几种类型之一。 我们用竖线(<code class="highlighter-rouge">|</code>)分隔每个类型,所以<code class="highlighter-rouge">number | string | boolean</code>表示一个值可以是<code class="highlighter-rouge">number</code>,<code class="highlighter-rouge">string</code>,或<code class="highlighter-rouge">boolean</code>。</p>
<p>如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Bird <span class="p">{
<span class="nx">fly<span class="p">();
<span class="nx">layEggs<span class="p">();
<span class="p">}
<span class="kr">interface <span class="nx">Fish <span class="p">{
<span class="nx">swim<span class="p">();
<span class="nx">layEggs<span class="p">();
<span class="p">}
<span class="kd">function <span class="nx">getSmallPet<span class="p">()<span class="err">: <span class="nx">Fish <span class="o">| <span class="nx">Bird <span class="p">{
<span class="c1">// ...
<span class="p">}
<span class="kd">let <span class="nx">pet <span class="o">= <span class="nx">getSmallPet<span class="p">();
<span class="nx">pet<span class="p">.<span class="nx">layEggs<span class="p">(); <span class="c1">// okay
<span class="nx">pet<span class="p">.<span class="nx">swim<span class="p">(); <span class="c1">// errors
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>这里的联合类型可能有点复杂,但是你很容易就习惯了。 如果一个值的类型是<code class="highlighter-rouge">A | B</code>,我们能够<em>确定</em>的是它包含了<code class="highlighter-rouge">A</code><em>和</em><code class="highlighter-rouge">B</code>中共有的成员。 这个例子里,<code class="highlighter-rouge">Bird</code>具有一个<code class="highlighter-rouge">fly</code>成员。 我们不能确定一个<code class="highlighter-rouge">Bird | Fish</code>类型的变量是否有<code class="highlighter-rouge">fly</code>方法。 如果变量在运行时是<code class="highlighter-rouge">Fish</code>类型,那么调用<code class="highlighter-rouge">pet.fly()</code>就出错了。</p>
<h1 id="类型保护与区分类型type-guards-and-differentiating-types">类型保护与区分类型(Type Guards and Differentiating Types)</h1>
<p>联合类型适合于那些值可以为不同类型的情况。 但当我们想确切地了解是否为<code class="highlighter-rouge">Fish</code>时怎么办? JavaScript里常用来区分2个可能值的方法是检查成员是否存在。 如之前提及的,我们只能访问联合类型中共同拥有的成员。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">let <span class="nx">pet <span class="o">= <span class="nx">getSmallPet<span class="p">();
<span class="c1">// 每一个成员访问都会报错
<span class="k">if <span class="p">(<span class="nx">pet<span class="p">.<span class="nx">swim<span class="p">) <span class="p">{
<span class="nx">pet<span class="p">.<span class="nx">swim<span class="p">();
<span class="p">}
<span class="k">else <span class="k">if <span class="p">(<span class="nx">pet<span class="p">.<span class="nx">fly<span class="p">) <span class="p">{
<span class="nx">pet<span class="p">.<span class="nx">fly<span class="p">();
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>为了让这段代码工作,我们要使用类型断言:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">let <span class="nx">pet <span class="o">= <span class="nx">getSmallPet<span class="p">();
<span class="k">if <span class="p">((<span class="o"><<span class="nx">Fish<span class="o">><span class="nx">pet<span class="p">).<span class="nx">swim<span class="p">) <span class="p">{
<span class="p">(<span class="o"><<span class="nx">Fish<span class="o">><span class="nx">pet<span class="p">).<span class="nx">swim<span class="p">();
<span class="p">}
<span class="k">else <span class="p">{
<span class="p">(<span class="o"><<span class="nx">Bird<span class="o">><span class="nx">pet<span class="p">).<span class="nx">fly<span class="p">();
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h2 id="用户自定义的类型保护">用户自定义的类型保护</h2>
<p>这里可以注意到我们不得不多次使用类型断言。 假若我们一旦检查过类型,就能在之后的每个分支里清楚地知道<code class="highlighter-rouge">pet</code>的类型的话就好了。</p>
<p>TypeScript里的<em>类型保护</em>机制让它成为了现实。 类型保护就是一些表达式,它们会在运行时检查以确保在某个作用域里的类型。 要定义一个类型保护,我们只要简单地定义一个函数,它的返回值是一个<em>类型谓词</em>:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">isFish<span class="p">(<span class="nx">pet<span class="err">: <span class="nx">Fish <span class="o">| <span class="nx">Bird<span class="p">)<span class="err">: <span class="nx">pet <span class="k">is <span class="nx">Fish <span class="p">{
<span class="k">return <span class="p">(<span class="o"><<span class="nx">Fish<span class="o">><span class="nx">pet<span class="p">).<span class="nx">swim <span class="o">!== <span class="kc">undefined<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>在这个例子里,<code class="highlighter-rouge">pet is Fish</code>就是类型谓词。 谓词为<code class="highlighter-rouge">parameterName is Type</code>这种形式,<code class="highlighter-rouge">parameterName</code>必须是来自于当前函数签名里的一个参数名。</p>
<p>每当使用一些变量调用<code class="highlighter-rouge">isFish</code>时,TypeScript会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="c1">// 'swim' 和 'fly' 调用都没有问题了
<span class="k">if <span class="p">(<span class="nx">isFish<span class="p">(<span class="nx">pet<span class="p">)) <span class="p">{
<span class="nx">pet<span class="p">.<span class="nx">swim<span class="p">();
<span class="p">}
<span class="k">else <span class="p">{
<span class="nx">pet<span class="p">.<span class="nx">fly<span class="p">();
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>注意TypeScript不仅知道在<code class="highlighter-rouge">if</code>分支里<code class="highlighter-rouge">pet</code>是<code class="highlighter-rouge">Fish</code>类型; 它还清楚在<code class="highlighter-rouge">else</code>分支里,一定<em>不是</em><code class="highlighter-rouge">Fish</code>类型,一定是<code class="highlighter-rouge">Bird</code>类型。</p>
<h2 id="typeof类型保护"><code class="highlighter-rouge">typeof</code>类型保护</h2>
<p>现在我们回过头来看看怎么使用联合类型书写<code class="highlighter-rouge">padLeft</code>代码。 我们可以像下面这样利用类型断言来写:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">isNumber<span class="p">(<span class="nx">x<span class="err">: <span class="kr">any<span class="p">)<span class="err">: <span class="nx">x <span class="k">is <span class="kr">number <span class="p">{
<span class="k">return <span class="k">typeof <span class="nx">x <span class="o">=== <span class="s2">"number"<span class="p">;
<span class="p">}
<span class="kd">function <span class="nx">isString<span class="p">(<span class="nx">x<span class="err">: <span class="kr">any<span class="p">)<span class="err">: <span class="nx">x <span class="k">is <span class="kr">string <span class="p">{
<span class="k">return <span class="k">typeof <span class="nx">x <span class="o">=== <span class="s2">"string"<span class="p">;
<span class="p">}
<span class="kd">function <span class="nx">padLeft<span class="p">(<span class="nx">value<span class="err">: <span class="kr">string<span class="p">, <span class="nx">padding<span class="err">: <span class="kr">string <span class="o">| <span class="kr">number<span class="p">) <span class="p">{
<span class="k">if <span class="p">(<span class="nx">isNumber<span class="p">(<span class="nx">padding<span class="p">)) <span class="p">{
<span class="k">return <span class="nb">Array<span class="p">(<span class="nx">padding <span class="o">+ <span class="mi">1<span class="p">).<span class="nx">join<span class="p">(<span class="s2">" "<span class="p">) <span class="o">+ <span class="nx">value<span class="p">;
<span class="p">}
<span class="k">if <span class="p">(<span class="nx">isString<span class="p">(<span class="nx">padding<span class="p">)) <span class="p">{
<span class="k">return <span class="nx">padding <span class="o">+ <span class="nx">value<span class="p">;
<span class="p">}
<span class="k">throw <span class="k">new <span class="nb">Error<span class="p">(<span class="err">`<span class="nx">Expected <span class="kr">string <span class="nx">or <span class="kr">number<span class="p">, <span class="nx">got <span class="s1">'${padding}'<span class="p">.<span class="err">`<span class="p">);
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>然而,必须要定义一个函数来判断类型是否是原始类型,这太痛苦了。 幸运的是,现在我们不必将<code class="highlighter-rouge">typeof x === "number"</code>抽象成一个函数,因为TypeScript可以将它识别为一个类型保护。 也就是说我们可以直接在代码里检查类型了。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">padLeft<span class="p">(<span class="nx">value<span class="err">: <span class="kr">string<span class="p">, <span class="nx">padding<span class="err">: <span class="kr">string <span class="o">| <span class="kr">number<span class="p">) <span class="p">{
<span class="k">if <span class="p">(<span class="k">typeof <span class="nx">padding <span class="o">=== <span class="s2">"number"<span class="p">) <span class="p">{
<span class="k">return <span class="nb">Array<span class="p">(<span class="nx">padding <span class="o">+ <span class="mi">1<span class="p">).<span class="nx">join<span class="p">(<span class="s2">" "<span class="p">) <span class="o">+ <span class="nx">value<span class="p">;
<span class="p">}
<span class="k">if <span class="p">(<span class="k">typeof <span class="nx">padding <span class="o">=== <span class="s2">"string"<span class="p">) <span class="p">{
<span class="k">return <span class="nx">padding <span class="o">+ <span class="nx">value<span class="p">;
<span class="p">}
<span class="k">throw <span class="k">new <span class="nb">Error<span class="p">(<span class="err">`<span class="nx">Expected <span class="kr">string <span class="nx">or <span class="kr">number<span class="p">, <span class="nx">got <span class="s1">'${padding}'<span class="p">.<span class="err">`<span class="p">);
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>这些<em><code class="highlighter-rouge">typeof</code>类型保护</em>只有两种形式能被识别:<code class="highlighter-rouge">typeof v === "typename"</code>和<code class="highlighter-rouge">typeof v !== "typename"</code>,<code class="highlighter-rouge">"typename"</code>必须是<code class="highlighter-rouge">"number"</code>,<code class="highlighter-rouge">"string"</code>,<code class="highlighter-rouge">"boolean"</code>或<code class="highlighter-rouge">"symbol"</code>。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。</p>
<h2 id="instanceof类型保护"><code class="highlighter-rouge">instanceof</code>类型保护</h2>
<p>如果你已经阅读了<code class="highlighter-rouge">typeof</code>类型保护并且对JavaScript里的<code class="highlighter-rouge">instanceof</code>操作符熟悉的话,你可能已经猜到了这节要讲的内容。</p>
<p><em><code class="highlighter-rouge">instanceof</code>类型保护</em>是通过构造函数来细化类型的一种方式。 比如,我们借鉴一下之前字符串填充的例子:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Padder <span class="p">{
<span class="nx">getPaddingString<span class="p">()<span class="err">: <span class="kr">string
<span class="p">}
<span class="kr">class <span class="nx">SpaceRepeatingPadder <span class="k">implements <span class="nx">Padder <span class="p">{
<span class="kd">constructor<span class="p">(<span class="k">private <span class="nx">numSpaces<span class="err">: <span class="kr">number<span class="p">) <span class="p">{ <span class="p">}
<span class="nx">getPaddingString<span class="p">() <span class="p">{
<span class="k">return <span class="nb">Array<span class="p">(<span class="k">this<span class="p">.<span class="nx">numSpaces <span class="o">+ <span class="mi">1<span class="p">).<span class="nx">join<span class="p">(<span class="s2">" "<span class="p">);
<span class="p">}
<span class="p">}
<span class="kr">class <span class="nx">StringPadder <span class="k">implements <span class="nx">Padder <span class="p">{
<span class="kd">constructor<span class="p">(<span class="k">private <span class="nx">value<span class="err">: <span class="kr">string<span class="p">) <span class="p">{ <span class="p">}
<span class="nx">getPaddingString<span class="p">() <span class="p">{
<span class="k">return <span class="k">this<span class="p">.<span class="nx">value<span class="p">;
<span class="p">}
<span class="p">}
<span class="kd">function <span class="nx">getRandomPadder<span class="p">() <span class="p">{
<span class="k">return <span class="nb">Math<span class="p">.<span class="nx">random<span class="p">() <span class="o">< <span class="mf">0.5 <span class="p">?
<span class="k">new <span class="nx">SpaceRepeatingPadder<span class="p">(<span class="mi">4<span class="p">) <span class="p">:
<span class="k">new <span class="nx">StringPadder<span class="p">(<span class="s2">""<span class="p">);
<span class="p">}
<span class="c1">// 类型为SpaceRepeatingPadder | StringPadder
<span class="kd">let <span class="nx">padder<span class="err">: <span class="nx">Padder <span class="o">= <span class="nx">getRandomPadder<span class="p">();
<span class="k">if <span class="p">(<span class="nx">padder <span class="k">instanceof <span class="nx">SpaceRepeatingPadder<span class="p">) <span class="p">{
<span class="nx">padder<span class="p">; <span class="c1">// 类型细化为'SpaceRepeatingPadder'
<span class="p">}
<span class="k">if <span class="p">(<span class="nx">padder <span class="k">instanceof <span class="nx">StringPadder<span class="p">) <span class="p">{
<span class="nx">padder<span class="p">; <span class="c1">// 类型细化为'StringPadder'
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p><code class="highlighter-rouge">instanceof</code>的右侧要求是一个构造函数,TypeScript将细化为:</p>
<ol>
<li>此构造函数的<code class="highlighter-rouge">prototype</code>属性的类型,如果它的类型不为<code class="highlighter-rouge">any</code>的话</li>
<li>构造签名所返回的类型的联合</li>
</ol>
<p>以此顺序。</p>
<h1 id="可以为null的类型">可以为null的类型</h1>
<p>TypeScript具有两种特殊的类型,<code class="highlighter-rouge">null</code>和<code class="highlighter-rouge">undefined</code>,它们分别具有值null和undefined. 我们在基础类型一节里已经做过简要说明。 默认情况下,类型检查器认为<code class="highlighter-rouge">null</code>与<code class="highlighter-rouge">undefined</code>可以赋值给任何类型。 <code class="highlighter-rouge">null</code>与<code class="highlighter-rouge">undefined</code>是所有其它类型的一个有效值。 这也意味着,你阻止不了将它们赋值给其它类型,就算是你想要阻止这种情况也不行。 <code class="highlighter-rouge">null</code>的发明者,Tony Hoare,称它为价值亿万美金的错误。</p>
<p><code class="highlighter-rouge">--strictNullChecks</code>标记可以解决此错误:当你声明一个变量时,它不会自动地包含<code class="highlighter-rouge">null</code>或<code class="highlighter-rouge">undefined</code>。 你可以使用联合类型明确的包含它们:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">let <span class="nx">s <span class="o">= <span class="s2">"foo"<span class="p">;
<span class="nx">s <span class="o">= <span class="kc">null<span class="p">; <span class="c1">// 错误, 'null'不能赋值给'string'
<span class="kd">let <span class="nx">sn<span class="err">: <span class="kr">string <span class="o">| <span class="kc">null <span class="o">= <span class="s2">"bar"<span class="p">;
<span class="nx">sn <span class="o">= <span class="kc">null<span class="p">; <span class="c1">// 可以
<span class="nx">sn <span class="o">= <span class="kc">undefined<span class="p">; <span class="c1">// error, 'undefined'不能赋值给'string | null'
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>注意,按照JavaScript的语义,TypeScript会把<code class="highlighter-rouge">null</code>和<code class="highlighter-rouge">undefined</code>区别对待。<code class="highlighter-rouge">string | null</code>,<code class="highlighter-rouge">string | undefined</code>和<code class="highlighter-rouge">string | undefined | null</code>是不同的类型。</p>
<h2 id="可选参数和可选属性">可选参数和可选属性</h2>
<p>使用了<code class="highlighter-rouge">--strictNullChecks</code>,可选参数会被自动地加上<code class="highlighter-rouge">| undefined</code>:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">f<span class="p">(<span class="nx">x<span class="err">: <span class="kr">number<span class="p">, <span class="nx">y<span class="p">?: <span class="kr">number<span class="p">) <span class="p">{
<span class="k">return <span class="nx">x <span class="o">+ <span class="p">(<span class="nx">y <span class="o">|| <span class="mi">0<span class="p">);
<span class="p">}
<span class="nx">f<span class="p">(<span class="mi">1<span class="p">, <span class="mi">2<span class="p">);
<span class="nx">f<span class="p">(<span class="mi">1<span class="p">);
<span class="nx">f<span class="p">(<span class="mi">1<span class="p">, <span class="kc">undefined<span class="p">);
<span class="nx">f<span class="p">(<span class="mi">1<span class="p">, <span class="kc">null<span class="p">); <span class="c1">// error, 'null' is not assignable to 'number | undefined'
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>可选属性也会有同样的处理:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">class <span class="nx">C <span class="p">{
<span class="nl">a<span class="p">: <span class="kr">number<span class="p">;
<span class="nx">b<span class="p">?: <span class="kr">number<span class="p">;
<span class="p">}
<span class="kd">let <span class="nx">c <span class="o">= <span class="k">new <span class="nx">C<span class="p">();
<span class="nx">c<span class="p">.<span class="nx">a <span class="o">= <span class="mi">12<span class="p">;
<span class="nx">c<span class="p">.<span class="nx">a <span class="o">= <span class="kc">undefined<span class="p">; <span class="c1">// error, 'undefined' is not assignable to 'number'
<span class="nx">c<span class="p">.<span class="nx">b <span class="o">= <span class="mi">13<span class="p">;
<span class="nx">c<span class="p">.<span class="nx">b <span class="o">= <span class="kc">undefined<span class="p">; <span class="c1">// ok
<span class="nx">c<span class="p">.<span class="nx">b <span class="o">= <span class="kc">null<span class="p">; <span class="c1">// error, 'null' is not assignable to 'number | undefined'
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h2 id="类型保护和类型断言">类型保护和类型断言</h2>
<p>由于可以为null的类型是通过联合类型实现,那么你需要使用类型保护来去除<code class="highlighter-rouge">null</code>。 幸运地是这与在JavaScript里写的代码一致:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">f<span class="p">(<span class="nx">sn<span class="err">: <span class="kr">string <span class="o">| <span class="kc">null<span class="p">)<span class="err">: <span class="kr">string <span class="p">{
<span class="k">if <span class="p">(<span class="nx">sn <span class="o">== <span class="kc">null<span class="p">) <span class="p">{
<span class="k">return <span class="s2">"default"<span class="p">;
<span class="p">}
<span class="k">else <span class="p">{
<span class="k">return <span class="nx">sn<span class="p">;
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>这里很明显地去除了<code class="highlighter-rouge">null</code>,你也可以使用短路运算符:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">f<span class="p">(<span class="nx">sn<span class="err">: <span class="kr">string <span class="o">| <span class="kc">null<span class="p">)<span class="err">: <span class="kr">string <span class="p">{
<span class="k">return <span class="nx">sn <span class="o">|| <span class="s2">"default"<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>如果编译器不能够去除<code class="highlighter-rouge">null</code>或<code class="highlighter-rouge">undefined</code>,你可以使用类型断言手动去除。 语法是添加<code class="highlighter-rouge">!</code>后缀:<code class="highlighter-rouge">identifier!</code>从<code class="highlighter-rouge">identifier</code>的类型里去除了<code class="highlighter-rouge">null</code>和<code class="highlighter-rouge">undefined</code>:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">broken<span class="p">(<span class="nx">name<span class="err">: <span class="kr">string <span class="o">| <span class="kc">null<span class="p">)<span class="err">: <span class="kr">string <span class="p">{
<span class="kd">function <span class="nx">postfix<span class="p">(<span class="nx">epithet<span class="err">: <span class="kr">string<span class="p">) <span class="p">{
<span class="k">return <span class="nx">name<span class="p">.<span class="nx">charAt<span class="p">(<span class="mi">0<span class="p">) <span class="o">+ <span class="s1">'.the ' <span class="o">+ <span class="nx">epithet<span class="p">; <span class="c1">// error, 'name' is possibly null
<span class="p">}
<span class="nx">name <span class="o">= <span class="nx">name <span class="o">|| <span class="s2">"Bob"<span class="p">;
<span class="k">return <span class="nx">postfix<span class="p">(<span class="s2">"great"<span class="p">);
<span class="p">}
<span class="kd">function <span class="nx">fixed<span class="p">(<span class="nx">name<span class="err">: <span class="kr">string <span class="o">| <span class="kc">null<span class="p">)<span class="err">: <span class="kr">string <span class="p">{
<span class="kd">function <span class="nx">postfix<span class="p">(<span class="nx">epithet<span class="err">: <span class="kr">string<span class="p">) <span class="p">{
<span class="k">return <span class="nx">name<span class="o">!<span class="p">.<span class="nx">charAt<span class="p">(<span class="mi">0<span class="p">) <span class="o">+ <span class="s1">'.the ' <span class="o">+ <span class="nx">epithet<span class="p">; <span class="c1">// ok
<span class="p">}
<span class="nx">name <span class="o">= <span class="nx">name <span class="o">|| <span class="s2">"Bob"<span class="p">;
<span class="k">return <span class="nx">postfix<span class="p">(<span class="s2">"great"<span class="p">);
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>本例使用了嵌套函数,因为编译器无法去除嵌套函数的null(除非是立即调用的函数表达式)。 因为它无法跟踪所有对嵌套函数的调用,尤其是你将内层函数做为外层函数的返回值。 如果无法知道函数在哪里被调用,就无法知道调用时<code class="highlighter-rouge">name</code>的类型。</p>
<h1 id="类型别名">类型别名</h1>
<p>类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Name <span class="o">= <span class="kr">string<span class="p">;
<span class="kd">type <span class="nx">NameResolver <span class="o">= <span class="p">() <span class="o">=> <span class="kr">string<span class="p">;
<span class="kd">type <span class="nx">NameOrResolver <span class="o">= <span class="nx">Name <span class="o">| <span class="nx">NameResolver<span class="p">;
<span class="kd">function <span class="nx">getName<span class="p">(<span class="nx">n<span class="err">: <span class="nx">NameOrResolver<span class="p">)<span class="err">: <span class="nx">Name <span class="p">{
<span class="k">if <span class="p">(<span class="k">typeof <span class="nx">n <span class="o">=== <span class="s1">'string'<span class="p">) <span class="p">{
<span class="k">return <span class="nx">n<span class="p">;
<span class="p">}
<span class="k">else <span class="p">{
<span class="k">return <span class="nx">n<span class="p">();
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>起别名不会新建一个类型 - 它创建了一个新<em>名字</em>来引用那个类型。 给原始类型起别名通常没什么用,尽管可以做为文档的一种形式使用。</p>
<p>同接口一样,类型别名也可以是泛型 - 我们可以添加类型参数并且在别名声明的右侧传入:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Container<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{ <span class="na">value<span class="p">: <span class="nx">T <span class="p">};
</span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>我们也可以使用类型别名来在属性里引用自己:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Tree<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{
<span class="na">value<span class="p">: <span class="nx">T<span class="p">;
<span class="nl">left<span class="p">: <span class="nx">Tree<span class="o"><<span class="nx">T<span class="o">><span class="p">;
<span class="nl">right<span class="p">: <span class="nx">Tree<span class="o"><<span class="nx">T<span class="o">><span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">LinkedList<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="nx">T <span class="o">& <span class="p">{ <span class="na">next<span class="p">: <span class="nx">LinkedList<span class="o"><<span class="nx">T<span class="o">> <span class="p">};
<span class="kr">interface <span class="nx">Person <span class="p">{
<span class="nl">name<span class="p">: <span class="kr">string<span class="p">;
<span class="p">}
<span class="kd">var <span class="nx">people<span class="err">: <span class="nx">LinkedList<span class="o"><<span class="nx">Person<span class="o">><span class="p">;
<span class="kd">var <span class="nx">s <span class="o">= <span class="nx">people<span class="p">.<span class="nx">name<span class="p">;
<span class="kd">var <span class="nx">s <span class="o">= <span class="nx">people<span class="p">.<span class="nx">next<span class="p">.<span class="nx">name<span class="p">;
<span class="kd">var <span class="nx">s <span class="o">= <span class="nx">people<span class="p">.<span class="nx">next<span class="p">.<span class="nx">next<span class="p">.<span class="nx">name<span class="p">;
<span class="kd">var <span class="nx">s <span class="o">= <span class="nx">people<span class="p">.<span class="nx">next<span class="p">.<span class="nx">next<span class="p">.<span class="nx">next<span class="p">.<span class="nx">name<span class="p">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>然而,类型别名不能出现在声明右侧的任何地方。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Yikes <span class="o">= <span class="nb">Array<span class="o"><<span class="nx">Yikes<span class="o">><span class="p">; <span class="c1">// error
</span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h2 id="接口-vs-类型别名">接口 vs. 类型别名</h2>
<p>像我们提到的,类型别名可以像接口一样;然而,仍有一些细微差别。</p>
<p>其一,接口创建了一个新的名字,可以在其它任何地方使用。 类型别名并不创建新名字—比如,错误信息就不会使用别名。 在下面的示例代码里,在编译器中将鼠标悬停在<code class="highlighter-rouge">interfaced</code>上,显示它返回的是<code class="highlighter-rouge">Interface</code>,但悬停在<code class="highlighter-rouge">aliased</code>上时,显示的却是对象字面量类型。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Alias <span class="o">= <span class="p">{ <span class="na">num<span class="p">: <span class="kr">number <span class="p">}
<span class="kr">interface <span class="nx">Interface <span class="p">{
<span class="nl">num<span class="p">: <span class="kr">number<span class="p">;
<span class="p">}
<span class="kr">declare <span class="kd">function <span class="nx">aliased<span class="p">(<span class="nx">arg<span class="err">: <span class="nx">Alias<span class="p">)<span class="err">: <span class="nx">Alias<span class="p">;
<span class="kr">declare <span class="kd">function <span class="nx">interfaced<span class="p">(<span class="nx">arg<span class="err">: <span class="nx">Interface<span class="p">)<span class="err">: <span class="nx">Interface<span class="p">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>另一个重要区别是类型别名不能被<code class="highlighter-rouge">extends</code>和<code class="highlighter-rouge">implements</code>(自己也不能<code class="highlighter-rouge">extends</code>和<code class="highlighter-rouge">implements</code>其它类型)。 因为软件中的对象应该对于扩展是开放的,但是对于修改是封闭的,你应该尽量去使用接口代替类型别名。</p>
<p>另一方面,如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,这时通常会使用类型别名。</p>
<h1 id="字符串字面量类型">字符串字面量类型</h1>
<p>字符串字面量类型允许你指定字符串必须的固定值。 在实际应用中,字符串字面量类型可以与联合类型,类型保护和类型别名很好的配合。 通过结合使用这些特性,你可以实现类似枚举类型的字符串。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Easing <span class="o">= <span class="s2">"ease-in" <span class="o">| <span class="s2">"ease-out" <span class="o">| <span class="s2">"ease-in-out"<span class="p">;
<span class="kr">class <span class="nx">UIElement <span class="p">{
<span class="nx">animate<span class="p">(<span class="nx">dx<span class="err">: <span class="kr">number<span class="p">, <span class="nx">dy<span class="err">: <span class="kr">number<span class="p">, <span class="nx">easing<span class="err">: <span class="nx">Easing<span class="p">) <span class="p">{
<span class="k">if <span class="p">(<span class="nx">easing <span class="o">=== <span class="s2">"ease-in"<span class="p">) <span class="p">{
<span class="c1">// ...
<span class="p">}
<span class="k">else <span class="k">if <span class="p">(<span class="nx">easing <span class="o">=== <span class="s2">"ease-out"<span class="p">) <span class="p">{
<span class="p">}
<span class="k">else <span class="k">if <span class="p">(<span class="nx">easing <span class="o">=== <span class="s2">"ease-in-out"<span class="p">) <span class="p">{
<span class="p">}
<span class="k">else <span class="p">{
<span class="c1">// error! should not pass null or undefined.
<span class="p">}
<span class="p">}
<span class="p">}
<span class="kd">let <span class="nx">button <span class="o">= <span class="k">new <span class="nx">UIElement<span class="p">();
<span class="nx">button<span class="p">.<span class="nx">animate<span class="p">(<span class="mi">0<span class="p">, <span class="mi">0<span class="p">, <span class="s2">"ease-in"<span class="p">);
<span class="nx">button<span class="p">.<span class="nx">animate<span class="p">(<span class="mi">0<span class="p">, <span class="mi">0<span class="p">, <span class="s2">"uneasy"<span class="p">); <span class="c1">// error: "uneasy" is not allowed here
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>你只能从三种允许的字符中选择其一来做为参数传递,传入其它值则会产生错误。</p>
<div class="language-text highlighter-rouge">
<pre class="highlight"><code>Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" | "ease-out" | "ease-in-out"'
</code></pre>
</div>
<p>字符串字面量类型还可以用于区分函数重载:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"img"<span class="p">)<span class="err">: <span class="nx">HTMLImageElement<span class="p">;
<span class="kd">function <span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="s2">"input"<span class="p">)<span class="err">: <span class="nx">HTMLInputElement<span class="p">;
<span class="c1">// ... more overloads ...
<span class="kd">function <span class="nx">createElement<span class="p">(<span class="nx">tagName<span class="err">: <span class="kr">string<span class="p">)<span class="err">: <span class="nx">Element <span class="p">{
<span class="c1">// ... code goes here ...
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h1 id="数字字面量类型">数字字面量类型</h1>
<p>TypeScript还具有数字字面量类型。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">rollDie<span class="p">()<span class="err">: <span class="mi">1 <span class="o">| <span class="mi">2 <span class="o">| <span class="mi">3 <span class="o">| <span class="mi">4 <span class="o">| <span class="mi">5 <span class="o">| <span class="mi">6 <span class="p">{
<span class="c1">// ...
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>我们很少直接这样使用,但它们可以用在缩小范围调试bug的时候:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">foo<span class="p">(<span class="nx">x<span class="err">: <span class="kr">number<span class="p">) <span class="p">{
<span class="k">if <span class="p">(<span class="nx">x <span class="o">!== <span class="mi">1 <span class="o">|| <span class="nx">x <span class="o">!== <span class="mi">2<span class="p">) <span class="p">{
<span class="c1">// ~~~~~~~
<span class="c1">// Operator '!==' cannot be applied to types '1' and '2'.
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>换句话说,当<code class="highlighter-rouge">x</code>与<code class="highlighter-rouge">2</code>进行比较的时候,它的值必须为<code class="highlighter-rouge">1</code>,这就意味着上面的比较检查是非法的。</p>
<h1 id="枚举成员类型">枚举成员类型</h1>
<p>如我们在枚举一节里提到的,当每个枚举成员都是用字面量初始化的时候枚举成员是具有类型的。</p>
<p>在我们谈及“单例类型”的时候,多数是指枚举成员类型和数字/字符串字面量类型,尽管大多数用户会互换使用“单例类型”和“字面量类型”。</p>
<h1 id="可辨识联合discriminated-unions">可辨识联合(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="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">Square <span class="p">{
<span class="nl">kind<span class="p">: <span class="s2">"square"<span class="p">;
<span class="nl">size<span class="p">: <span class="kr">number<span class="p">;
<span class="p">}
<span class="kr">interface <span class="nx">Rectangle <span class="p">{
<span class="nl">kind<span class="p">: <span class="s2">"rectangle"<span class="p">;
<span class="nl">width<span class="p">: <span class="kr">number<span class="p">;
<span class="nl">height<span class="p">: <span class="kr">number<span class="p">;
<span class="p">}
<span class="kr">interface <span class="nx">Circle <span class="p">{
<span class="nl">kind<span class="p">: <span class="s2">"circle"<span class="p">;
<span class="nl">radius<span class="p">: <span class="kr">number<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>首先我们声明了将要联合的接口。 每个接口都有<code class="highlighter-rouge">kind</code>属性但有不同的字符串字面量类型。 <code class="highlighter-rouge">kind</code>属性称做<em>可辨识的特征</em>或<em>标签</em>。 其它的属性则特定于各个接口。 注意,目前各个接口间是没有联系的。 下面我们把它们联合到一起:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Shape <span class="o">= <span class="nx">Square <span class="o">| <span class="nx">Rectangle <span class="o">| <span class="nx">Circle<span class="p">;
</span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>现在我们使用可辨识联合:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">area<span class="p">(<span class="nx">s<span class="err">: <span class="nx">Shape<span class="p">) <span class="p">{
<span class="k">switch <span class="p">(<span class="nx">s<span class="p">.<span class="nx">kind<span class="p">) <span class="p">{
<span class="k">case <span class="s2">"square"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">size <span class="o">* <span class="nx">s<span class="p">.<span class="nx">size<span class="p">;
<span class="k">case <span class="s2">"rectangle"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">height <span class="o">* <span class="nx">s<span class="p">.<span class="nx">width<span class="p">;
<span class="k">case <span class="s2">"circle"<span class="err">: <span class="k">return <span class="nb">Math<span class="p">.<span class="nx">PI <span class="o">* <span class="nx">s<span class="p">.<span class="nx">radius <span class="o">** <span class="mi">2<span class="p">;
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h2 id="完整性检查">完整性检查</h2>
<p>当没有涵盖所有可辨识联合的变化时,我们想让编译器可以通知我们。 比如,如果我们添加了<code class="highlighter-rouge">Triangle</code>到<code class="highlighter-rouge">Shape</code>,我们同时还需要更新<code class="highlighter-rouge">area</code>:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Shape <span class="o">= <span class="nx">Square <span class="o">| <span class="nx">Rectangle <span class="o">| <span class="nx">Circle <span class="o">| <span class="nx">Triangle<span class="p">;
<span class="kd">function <span class="nx">area<span class="p">(<span class="nx">s<span class="err">: <span class="nx">Shape<span class="p">) <span class="p">{
<span class="k">switch <span class="p">(<span class="nx">s<span class="p">.<span class="nx">kind<span class="p">) <span class="p">{
<span class="k">case <span class="s2">"square"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">size <span class="o">* <span class="nx">s<span class="p">.<span class="nx">size<span class="p">;
<span class="k">case <span class="s2">"rectangle"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">height <span class="o">* <span class="nx">s<span class="p">.<span class="nx">width<span class="p">;
<span class="k">case <span class="s2">"circle"<span class="err">: <span class="k">return <span class="nb">Math<span class="p">.<span class="nx">PI <span class="o">* <span class="nx">s<span class="p">.<span class="nx">radius <span class="o">** <span class="mi">2<span class="p">;
<span class="p">}
<span class="c1">// should error here - we didn't handle case "triangle"
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>有两种方式可以实现。 首先是启用<code class="highlighter-rouge">--strictNullChecks</code>并且指定一个返回值类型:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">area<span class="p">(<span class="nx">s<span class="err">: <span class="nx">Shape<span class="p">)<span class="err">: <span class="kr">number <span class="p">{ <span class="c1">// error: returns number | undefined
<span class="k">switch <span class="p">(<span class="nx">s<span class="p">.<span class="nx">kind<span class="p">) <span class="p">{
<span class="k">case <span class="s2">"square"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">size <span class="o">* <span class="nx">s<span class="p">.<span class="nx">size<span class="p">;
<span class="k">case <span class="s2">"rectangle"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">height <span class="o">* <span class="nx">s<span class="p">.<span class="nx">width<span class="p">;
<span class="k">case <span class="s2">"circle"<span class="err">: <span class="k">return <span class="nb">Math<span class="p">.<span class="nx">PI <span class="o">* <span class="nx">s<span class="p">.<span class="nx">radius <span class="o">** <span class="mi">2<span class="p">;
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>因为<code class="highlighter-rouge">switch</code>没有包涵所有情况,所以TypeScript认为这个函数有时候会返回<code class="highlighter-rouge">undefined</code>。 如果你明确地指定了返回值类型为<code class="highlighter-rouge">number</code>,那么你会看到一个错误,因为实际上返回值的类型为<code class="highlighter-rouge">number | undefined</code>。 然而,这种方法存在些微妙之处且<code class="highlighter-rouge">--strictNullChecks</code>对旧代码支持不好。</p>
<p>第二种方法使用<code class="highlighter-rouge">never</code>类型,编译器用它来进行完整性检查:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">assertNever<span class="p">(<span class="nx">x<span class="err">: <span class="nx">never<span class="p">)<span class="err">: <span class="nx">never <span class="p">{
<span class="k">throw <span class="k">new <span class="nb">Error<span class="p">(<span class="s2">"Unexpected object: " <span class="o">+ <span class="nx">x<span class="p">);
<span class="p">}
<span class="kd">function <span class="nx">area<span class="p">(<span class="nx">s<span class="err">: <span class="nx">Shape<span class="p">) <span class="p">{
<span class="k">switch <span class="p">(<span class="nx">s<span class="p">.<span class="nx">kind<span class="p">) <span class="p">{
<span class="k">case <span class="s2">"square"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">size <span class="o">* <span class="nx">s<span class="p">.<span class="nx">size<span class="p">;
<span class="k">case <span class="s2">"rectangle"<span class="err">: <span class="k">return <span class="nx">s<span class="p">.<span class="nx">height <span class="o">* <span class="nx">s<span class="p">.<span class="nx">width<span class="p">;
<span class="k">case <span class="s2">"circle"<span class="err">: <span class="k">return <span class="nb">Math<span class="p">.<span class="nx">PI <span class="o">* <span class="nx">s<span class="p">.<span class="nx">radius <span class="o">** <span class="mi">2<span class="p">;
<span class="nl">default<span class="p">: <span class="k">return <span class="nx">assertNever<span class="p">(<span class="nx">s<span class="p">); <span class="c1">// error here if there are missing cases
<span class="p">}
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>这里,<code class="highlighter-rouge">assertNever</code>检查<code class="highlighter-rouge">s</code>是否为<code class="highlighter-rouge">never</code>类型—即为除去所有可能情况后剩下的类型。 如果你忘记了某个case,那么<code class="highlighter-rouge">s</code>将具有一个真实的类型并且你会得到一个错误。 这种方式需要你定义一个额外的函数,但是在你忘记某个case的时候也更加明显。</p>
<h1 id="多态的this类型">多态的<code class="highlighter-rouge">this</code>类型</h1>
<p>多态的<code class="highlighter-rouge">this</code>类型表示的是某个包含类或接口的<em>子类型</em>。 这被称做<em>F</em>-bounded多态性。 它能很容易的表现连贯接口间的继承,比如。 在计算器的例子里,在每个操作之后都返回<code class="highlighter-rouge">this</code>类型:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">class <span class="nx">BasicCalculator <span class="p">{
<span class="k">public <span class="kd">constructor<span class="p">(<span class="k">protected <span class="nx">value<span class="err">: <span class="kr">number <span class="o">= <span class="mi">0<span class="p">) <span class="p">{ <span class="p">}
<span class="k">public <span class="nx">currentValue<span class="p">()<span class="err">: <span class="kr">number <span class="p">{
<span class="k">return <span class="k">this<span class="p">.<span class="nx">value<span class="p">;
<span class="p">}
<span class="k">public <span class="nx">add<span class="p">(<span class="nx">operand<span class="err">: <span class="kr">number<span class="p">)<span class="err">: <span class="k">this <span class="p">{
<span class="k">this<span class="p">.<span class="nx">value <span class="o">+= <span class="nx">operand<span class="p">;
<span class="k">return <span class="k">this<span class="p">;
<span class="p">}
<span class="k">public <span class="nx">multiply<span class="p">(<span class="na">operand<span class="p">: <span class="kr">number<span class="p">): <span class="k">this <span class="p">{
<span class="k">this<span class="p">.<span class="nx">value <span class="o">*= <span class="nx">operand<span class="p">;
<span class="k">return <span class="k">this<span class="p">;
<span class="p">}
<span class="c1">// ... other operations go here ...
<span class="p">}
<span class="kd">let <span class="nx">v <span class="o">= <span class="k">new <span class="nx">BasicCalculator<span class="p">(<span class="mi">2<span class="p">)
<span class="p">.<span class="nx">multiply<span class="p">(<span class="mi">5<span class="p">)
<span class="p">.<span class="nx">add<span class="p">(<span class="mi">1<span class="p">)
<span class="p">.<span class="nx">currentValue<span class="p">();
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>由于这个类使用了<code class="highlighter-rouge">this</code>类型,你可以继承它,新的类可以直接使用之前的方法,不需要做任何的改变。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">class <span class="nx">ScientificCalculator <span class="k">extends <span class="nx">BasicCalculator <span class="p">{
<span class="k">public <span class="kd">constructor<span class="p">(<span class="nx">value <span class="o">= <span class="mi">0<span class="p">) <span class="p">{
<span class="k">super<span class="p">(<span class="nx">value<span class="p">);
<span class="p">}
<span class="k">public <span class="nx">sin<span class="p">() <span class="p">{
<span class="k">this<span class="p">.<span class="nx">value <span class="o">= <span class="nb">Math<span class="p">.<span class="nx">sin<span class="p">(<span class="k">this<span class="p">.<span class="nx">value<span class="p">);
<span class="k">return <span class="k">this<span class="p">;
<span class="p">}
<span class="c1">// ... other operations go here ...
<span class="p">}
<span class="kd">let <span class="nx">v <span class="o">= <span class="k">new <span class="nx">ScientificCalculator<span class="p">(<span class="mi">2<span class="p">)
<span class="p">.<span class="nx">multiply<span class="p">(<span class="mi">5<span class="p">)
<span class="p">.<span class="nx">sin<span class="p">()
<span class="p">.<span class="nx">add<span class="p">(<span class="mi">1<span class="p">)
<span class="p">.<span class="nx">currentValue<span class="p">();
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>如果没有<code class="highlighter-rouge">this</code>类型,<code class="highlighter-rouge">ScientificCalculator</code>就不能够在继承<code class="highlighter-rouge">BasicCalculator</code>的同时还保持接口的连贯性。 <code class="highlighter-rouge">multiply</code>将会返回<code class="highlighter-rouge">BasicCalculator</code>,它并没有<code class="highlighter-rouge">sin</code>方法。 然而,使用<code class="highlighter-rouge">this</code>类型,<code class="highlighter-rouge">multiply</code>会返回<code class="highlighter-rouge">this</code>,在这里就是<code class="highlighter-rouge">ScientificCalculator</code>。</p>
<h1 id="索引类型index-types">索引类型(Index types)</h1>
<p>使用索引类型,编译器就能够检查使用了动态属性名的代码。 例如,一个常见的JavaScript模式是从对象中选取属性的子集。</p>
<div class="language-js highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">pluck<span class="p">(<span class="nx">o<span class="p">, <span class="nx">names<span class="p">) <span class="p">{
<span class="k">return <span class="nx">names<span class="p">.<span class="nx">map<span class="p">(<span class="nx">n <span class="o">=> <span class="nx">o<span class="p">[<span class="nx">n<span class="p">]);
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>下面是如何在TypeScript里使用此函数,通过<strong>索引类型查询</strong>和<strong>索引访问</strong>操作符:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">pluck<span class="o"><<span class="nx">T<span class="p">, <span class="nx">K <span class="k">extends <span class="nx">keyof <span class="nx">T<span class="o">><span class="p">(<span class="nx">o<span class="err">: <span class="nx">T<span class="p">, <span class="nx">names<span class="err">: <span class="nx">K<span class="p">[])<span class="err">: <span class="nx">T<span class="p">[<span class="nx">K<span class="p">][] <span class="p">{
<span class="k">return <span class="nx">names<span class="p">.<span class="nx">map<span class="p">(<span class="nx">n <span class="o">=> <span class="nx">o<span class="p">[<span class="nx">n<span class="p">]);
<span class="p">}
<span class="kr">interface <span class="nx">Person <span class="p">{
<span class="nl">name<span class="p">: <span class="kr">string<span class="p">;
<span class="nl">age<span class="p">: <span class="kr">number<span class="p">;
<span class="p">}
<span class="kd">let <span class="nx">person<span class="err">: <span class="nx">Person <span class="o">= <span class="p">{
<span class="na">name<span class="p">: <span class="s1">'Jarid'<span class="p">,
<span class="na">age<span class="p">: <span class="mi">35
<span class="p">};
<span class="kd">let <span class="nx">strings<span class="err">: <span class="kr">string<span class="p">[] <span class="o">= <span class="nx">pluck<span class="p">(<span class="nx">person<span class="p">, <span class="p">[<span class="s1">'name'<span class="p">]); <span class="c1">// ok, string[]
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>编译器会检查<code class="highlighter-rouge">name</code>是否真的是<code class="highlighter-rouge">Person</code>的一个属性。 本例还引入了几个新的类型操作符。 首先是<code class="highlighter-rouge">keyof T</code>,<strong>索引类型查询操作符</strong>。 对于任何类型<code class="highlighter-rouge">T</code>,<code class="highlighter-rouge">keyof T</code>的结果为<code class="highlighter-rouge">T</code>上已知的公共属性名的联合。 例如:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">let <span class="nx">personProps<span class="err">: <span class="nx">keyof <span class="nx">Person<span class="p">; <span class="c1">// 'name' | 'age'
</span></span></span></span></span></span></span></code></pre>
</div>
<p><code class="highlighter-rouge">keyof Person</code>是完全可以与<code class="highlighter-rouge">'name' | 'age'</code>互相替换的。 不同的是如果你添加了其它的属性到<code class="highlighter-rouge">Person</code>,例如<code class="highlighter-rouge">address: string</code>,那么<code class="highlighter-rouge">keyof Person</code>会自动变为<code class="highlighter-rouge">'name' | 'age' | 'address'</code>。 你可以在像<code class="highlighter-rouge">pluck</code>函数这类上下文里使用<code class="highlighter-rouge">keyof</code>,因为在使用之前你并不清楚可能出现的属性名。 但编译器会检查你是否传入了正确的属性名给<code class="highlighter-rouge">pluck</code>:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="nx">pluck<span class="p">(<span class="nx">person<span class="p">, <span class="p">[<span class="s1">'age'<span class="p">, <span class="s1">'unknown'<span class="p">]); <span class="c1">// error, 'unknown' is not in 'name' | 'age'
</span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>第二个操作符是<code class="highlighter-rouge">T</code>,<strong>索引访问操作符</strong>。 在这里,类型语法反映了表达式语法。 这意味着<code class="highlighter-rouge">person['name']</code>具有类型<code class="highlighter-rouge">Person['name']</code> — 在我们的例子里则为<code class="highlighter-rouge">string</code>类型。 然而,就像索引类型查询一样,你可以在普通的上下文里使用<code class="highlighter-rouge">T</code>,这正是它的强大所在。 你只要确保类型变量<code class="highlighter-rouge">K extends keyof T</code>就可以了。 例如下面<code class="highlighter-rouge">getProperty</code>函数的例子:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">getProperty<span class="o"><<span class="nx">T<span class="p">, <span class="nx">K <span class="k">extends <span class="nx">keyof <span class="nx">T<span class="o">><span class="p">(<span class="nx">o<span class="err">: <span class="nx">T<span class="p">, <span class="nx">name<span class="err">: <span class="nx">K<span class="p">)<span class="err">: <span class="nx">T<span class="p">[<span class="nx">K<span class="p">] <span class="p">{
<span class="k">return <span class="nx">o<span class="p">[<span class="nx">name<span class="p">]; <span class="c1">// o is of type T
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p><code class="highlighter-rouge">getProperty</code>里的<code class="highlighter-rouge">o: T</code>和<code class="highlighter-rouge">name: K</code>,意味着<code class="highlighter-rouge">o: T</code>。 当你返回<code class="highlighter-rouge">T</code>的结果,编译器会实例化键的真实类型,因此<code class="highlighter-rouge">getProperty</code>的返回值类型会随着你需要的属性改变。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">let <span class="nx">name<span class="err">: <span class="kr">string <span class="o">= <span class="nx">getProperty<span class="p">(<span class="nx">person<span class="p">, <span class="s1">'name'<span class="p">);
<span class="kd">let <span class="nx">age<span class="err">: <span class="kr">number <span class="o">= <span class="nx">getProperty<span class="p">(<span class="nx">person<span class="p">, <span class="s1">'age'<span class="p">);
<span class="kd">let <span class="nx">unknown <span class="o">= <span class="nx">getProperty<span class="p">(<span class="nx">person<span class="p">, <span class="s1">'unknown'<span class="p">); <span class="c1">// error, 'unknown' is not in 'name' | 'age'
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h2 id="索引类型和字符串索引签名">索引类型和字符串索引签名</h2>
<p><code class="highlighter-rouge">keyof</code>和<code class="highlighter-rouge">T</code>与字符串索引签名进行交互。 如果你有一个带有字符串索引签名的类型,那么<code class="highlighter-rouge">keyof T</code>会是<code class="highlighter-rouge">string</code>。 并且<code class="highlighter-rouge">T</code>为索引签名的类型:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nb">Map<span class="o"><<span class="nx">T<span class="o">> <span class="p">{
<span class="p">[<span class="na">key<span class="p">: <span class="kr">string<span class="p">]: <span class="nx">T<span class="p">;
<span class="p">}
<span class="kd">let <span class="na">keys<span class="p">: <span class="nx">keyof <span class="nb">Map<span class="o"><<span class="kr">number<span class="o">><span class="p">; <span class="c1">// string
<span class="kd">let <span class="na">value<span class="p">: <span class="nb">Map<span class="o"><<span class="kr">number<span class="o">><span class="p">[<span class="s1">'foo'<span class="p">]; <span class="c1">// number
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<h1 id="映射类型">映射类型</h1>
<p>一个常见的任务是将一个已知的类型每个属性都变为可选的:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">PersonPartial <span class="p">{
<span class="nx">name<span class="p">?: <span class="kr">string<span class="p">;
<span class="nx">age<span class="p">?: <span class="kr">number<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>或者我们想要一个只读版本:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kr">interface <span class="nx">PersonReadonly <span class="p">{
<span class="k">readonly <span class="nx">name<span class="err">: <span class="kr">string<span class="p">;
<span class="k">readonly <span class="nx">age<span class="err">: <span class="kr">number<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>这在JavaScript里经常出现,TypeScript提供了从旧类型中创建新类型的一种方式 — <strong>映射类型</strong>。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。 例如,你可以令每个属性成为<code class="highlighter-rouge">readonly</code>类型或可选的。 下面是一些例子:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Readonly<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{
<span class="k">readonly <span class="p">[<span class="nx">P <span class="k">in <span class="nx">keyof <span class="nx">T<span class="p">]: <span class="nx">T<span class="p">[<span class="nx">P<span class="p">];
<span class="p">}
<span class="kd">type <span class="nx">Partial<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{
<span class="p">[<span class="nx">P <span class="k">in <span class="nx">keyof <span class="nx">T<span class="p">]?: <span class="nx">T<span class="p">[<span class="nx">P<span class="p">];
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>像下面这样使用:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">PersonPartial <span class="o">= <span class="nx">Partial<span class="o"><<span class="nx">Person<span class="o">><span class="p">;
<span class="kd">type <span class="nx">ReadonlyPerson <span class="o">= <span class="nx">Readonly<span class="o"><<span class="nx">Person<span class="o">><span class="p">;
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>下面来看看最简单的映射类型和它的组成部分:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Keys <span class="o">= <span class="s1">'option1' <span class="o">| <span class="s1">'option2'<span class="p">;
<span class="kd">type <span class="nx">Flags <span class="o">= <span class="p">{ <span class="p">[<span class="nx">K <span class="k">in <span class="nx">Keys<span class="p">]: <span class="kr">boolean <span class="p">};
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>它的语法与索引签名的语法类型,内部使用了<code class="highlighter-rouge">for .. in</code>。 具有三个部分:</p>
<ol>
<li>类型变量<code class="highlighter-rouge">K</code>,它会依次绑定到每个属性。</li>
<li>字符串字面量联合的<code class="highlighter-rouge">Keys</code>,它包含了要迭代的属性名的集合。</li>
<li>属性的结果类型。</li>
</ol>
<p>在个简单的例子里,<code class="highlighter-rouge">Keys</code>是硬编码的的属性名列表并且属性类型永远是<code class="highlighter-rouge">boolean</code>,因此这个映射类型等同于:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Flags <span class="o">= <span class="p">{
<span class="na">option1<span class="p">: <span class="kr">boolean<span class="p">;
<span class="nl">option2<span class="p">: <span class="kr">boolean<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>在真正的应用里,可能不同于上面的<code class="highlighter-rouge">Readonly</code>或<code class="highlighter-rouge">Partial</code>。 它们会基于一些已存在的类型,且按照一定的方式转换字段。 这就是<code class="highlighter-rouge">keyof</code>和索引访问类型要做的事情:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">NullablePerson <span class="o">= <span class="p">{ <span class="p">[<span class="nx">P <span class="k">in <span class="nx">keyof <span class="nx">Person<span class="p">]: <span class="nx">Person<span class="p">[<span class="nx">P<span class="p">] <span class="o">| <span class="kc">null <span class="p">}
<span class="kd">type <span class="nx">PartialPerson <span class="o">= <span class="p">{ <span class="p">[<span class="nx">P <span class="k">in <span class="nx">keyof <span class="nx">Person<span class="p">]?: <span class="nx">Person<span class="p">[<span class="nx">P<span class="p">] <span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>但它更有用的地方是可以有一些通用版本。</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Nullable<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{ <span class="p">[<span class="nx">P <span class="k">in <span class="nx">keyof <span class="nx">T<span class="p">]: <span class="nx">T<span class="p">[<span class="nx">P<span class="p">] <span class="o">| <span class="kc">null <span class="p">}
<span class="kd">type <span class="nx">Partial<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{ <span class="p">[<span class="nx">P <span class="k">in <span class="nx">keyof <span class="nx">T<span class="p">]?: <span class="nx">T<span class="p">[<span class="nx">P<span class="p">] <span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>在这些例子里,属性列表是<code class="highlighter-rouge">keyof T</code>且结果类型是<code class="highlighter-rouge">T</code>的变体。 这是使用通用映射类型的一个好模版。 因为这类转换是同态的,映射只作用于<code class="highlighter-rouge">T</code>的属性而没有其它的。 编译器知道在添加任何新属性之前可以拷贝所有存在的属性修饰符。 例如,假设<code class="highlighter-rouge">Person.name</code>是只读的,那么<code class="highlighter-rouge">Partial<Person>.name</code>也将是只读的且为可选的。</p>
<p>下面是另一个例子,<code class="highlighter-rouge">T</code>被包装在<code class="highlighter-rouge">Proxy<T></code>类里:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Proxy<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{
<span class="nx">get<span class="p">(): <span class="nx">T<span class="p">;
<span class="nx">set<span class="p">(<span class="na">value<span class="p">: <span class="nx">T<span class="p">): <span class="k">void<span class="p">;
<span class="p">}
<span class="kd">type <span class="nx">Proxify<span class="o"><<span class="nx">T<span class="o">> <span class="o">= <span class="p">{
<span class="p">[<span class="nx">P <span class="k">in <span class="nx">keyof <span class="nx">T<span class="p">]: <span class="nx">Proxy<span class="o"><<span class="nx">T<span class="p">[<span class="nx">P<span class="p">]<span class="o">><span class="p">;
<span class="p">}
<span class="kd">function <span class="nx">proxify<span class="o"><<span class="nx">T<span class="o">><span class="p">(<span class="na">o<span class="p">: <span class="nx">T<span class="p">): <span class="nx">Proxify<span class="o"><<span class="nx">T<span class="o">> <span class="p">{
<span class="c1">// ... wrap proxies ...
<span class="p">}
<span class="kd">let <span class="nx">proxyProps <span class="o">= <span class="nx">proxify<span class="p">(<span class="nx">props<span class="p">);
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>注意<code class="highlighter-rouge">Readonly<T></code>和<code class="highlighter-rouge">Partial<T></code>用处不小,因此它们与<code class="highlighter-rouge">Pick</code>和<code class="highlighter-rouge">Record</code>一同被包含进了TypeScript的标准库里:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">Pick<span class="o"><<span class="nx">T<span class="p">, <span class="nx">K <span class="k">extends <span class="nx">keyof <span class="nx">T<span class="o">> <span class="o">= <span class="p">{
<span class="p">[<span class="nx">P <span class="k">in <span class="nx">K<span class="p">]: <span class="nx">T<span class="p">[<span class="nx">P<span class="p">];
<span class="p">}
<span class="kd">type <span class="nx">Record<span class="o"><<span class="nx">K <span class="k">extends <span class="kr">string<span class="p">, <span class="nx">T<span class="o">> <span class="o">= <span class="p">{
<span class="p">[<span class="nx">P <span class="k">in <span class="nx">K<span class="p">]: <span class="nx">T<span class="p">;
<span class="p">}
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p><code class="highlighter-rouge">Readonly</code>,<code class="highlighter-rouge">Partial</code>和<code class="highlighter-rouge">Pick</code>是同态的,但<code class="highlighter-rouge">Record</code>不是。 因为<code class="highlighter-rouge">Record</code>并不需要输入类型来拷贝属性,所以它不属于同态:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">type <span class="nx">ThreeStringProps <span class="o">= <span class="nx">Record<span class="o"><<span class="s1">'prop1' <span class="o">| <span class="s1">'prop2' <span class="o">| <span class="s1">'prop3'<span class="p">, <span class="kr">string<span class="o">>
</span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>非同态类型本质上会创建新的属性,因此它们不会从它处拷贝属性修饰符。</p>
<h2 id="由映射类型进行推断">由映射类型进行推断</h2>
<p>现在你了解了如何包装一个类型的属性,那么接下来就是如何拆包。 其实这也非常容易:</p>
<div class="language-ts highlighter-rouge">
<pre class="highlight"><code><span class="kd">function <span class="nx">unproxify<span class="o"><<span class="nx">T<span class="o">><span class="p">(<span class="nx">t<span class="err">: <span class="nx">Proxify<span class="o"><<span class="nx">T<span class="o">><span class="p">)<span class="err">: <span class="nx">T <span class="p">{
<span class="kd">let <span class="nx">result <span class="o">= <span class="p">{} <span class="k">as <span class="nx">T<span class="p">;
<span class="k">for <span class="p">(<span class="kd">const <span class="nx">k <span class="k">in <span class="nx">t<span class="p">) <span class="p">{
<span class="nx">result<span class="p">[<span class="nx">k<span class="p">] <span class="o">= <span class="nx">t<span class="p">[<span class="nx">k<span class="p">].<span class="nx">get<span class="p">();
<span class="p">}
<span class="k">return <span class="nx">result<span class="p">;
<span class="p">}
<span class="kd">let <span class="nx">originalProps <span class="o">= <span class="nx">unproxify<span class="p">(<span class="nx">proxyProps<span class="p">);
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
</div>
<p>注意这个拆包推断只适用于同态的映射类型。 如果映射类型不是同态的,那么需要给拆包函数一个明确的类型参数。</p><br><br>
来源:https://www.cnblogs.com/xiewangfei123/p/12235592.html
頁:
[1]