冷漠的爱 發表於 2019-10-9 09:56:00

TypeScript 技巧

<p data-mpa-powered-by="yiban.io">前言</p>
<p>很早以前就尝试过使用 TypeScript 来进行日常编码,但自己对静态类型语言的了解并不深入,再加上 TypeScript 的类型系统有着一定的复杂度,因此感觉自己并没有发挥好这门语言的优势,使代码变得更具<strong>可读性</strong>与<strong>可维护性</strong>。于是这几天便想着好好研究下这门语言,希望能够总结出一些特别的语言特性与实用技巧。</p>
<h2>操作符</h2>
<p>typeof - 获取变量的类型</p>
<pre><code><span>const&nbsp;colors&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>red:&nbsp;<span>'red',<br>&nbsp;&nbsp;<span>blue:&nbsp;<span>'blue'<br>}<br><br><span>//&nbsp;type&nbsp;res&nbsp;=&nbsp;{&nbsp;red:&nbsp;string;&nbsp;blue:&nbsp;string&nbsp;}<br>type&nbsp;res&nbsp;=&nbsp;<span>typeof&nbsp;colors<br></span></span></span></span></span></span></span></code></pre>
<p>keyof - 获取类型的键</p>
<pre><code><span>const&nbsp;data&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>a:&nbsp;<span>3,<br>&nbsp;&nbsp;<span>hello:&nbsp;<span>'world'<br>}<br><br><span>//&nbsp;类型保护<br><span><span>function&nbsp;<span>get&lt;<span>T&nbsp;<span>extends&nbsp;<span>object,&nbsp;<span>K&nbsp;<span>extends&nbsp;<span>keyof&nbsp;<span>T&gt;(<span>o:&nbsp;T,&nbsp;name:&nbsp;K):&nbsp;<span>T[<span>K]&nbsp;{<br>&nbsp;&nbsp;<span>return&nbsp;o<br>}<br><br>get(data,&nbsp;<span>'a')&nbsp;<span>//&nbsp;3<br>get(data,&nbsp;<span>'b')&nbsp;<span>//&nbsp;Error<br></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>
<p>组合 typeof 与 keyof - 捕获键的名称</p>
<pre><code><span>const&nbsp;colors&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>red:&nbsp;<span>'red',<br>&nbsp;&nbsp;<span>blue:&nbsp;<span>'blue'<br>}<br><br>type&nbsp;Colors&nbsp;=&nbsp;keyof&nbsp;<span>typeof&nbsp;colors<br><br><span>let&nbsp;color:&nbsp;Colors&nbsp;<span>//&nbsp;'red'&nbsp;|&nbsp;'blue'<br>color&nbsp;=&nbsp;<span>'red'&nbsp;<span>//&nbsp;ok<br>color&nbsp;=&nbsp;<span>'blue'&nbsp;<span>//&nbsp;ok<br>color&nbsp;=&nbsp;<span>'anythingElse'&nbsp;<span>//&nbsp;Error<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>in - 遍历键名</p>
<pre><code>interface&nbsp;Square&nbsp;{<br>&nbsp;&nbsp;<span>kind:&nbsp;<span>'square'<br>&nbsp;&nbsp;size:&nbsp;number<br>}<br><br><span>//&nbsp;type&nbsp;res&nbsp;=&nbsp;(radius:&nbsp;number)&nbsp;=&gt;&nbsp;{&nbsp;kind:&nbsp;'square';&nbsp;size:&nbsp;number&nbsp;}<br>type&nbsp;res&nbsp;=&nbsp;<span>(<span>radius:&nbsp;number)&nbsp;=&gt;&nbsp;{&nbsp;:&nbsp;Square&nbsp;}<br></span></span></span></span></span></span></code></pre>
<h2>特殊类型</h2>
<p>嵌套接口类型</p>
<pre><code>interface&nbsp;Producer&nbsp;{<br>&nbsp;&nbsp;<span>name:&nbsp;string<br>&nbsp;&nbsp;cost:&nbsp;number<br>&nbsp;&nbsp;production:&nbsp;number<br>}<br><br>interface&nbsp;Province&nbsp;{<br>&nbsp;&nbsp;<span>name:&nbsp;string<br>&nbsp;&nbsp;demand:&nbsp;number<br>&nbsp;&nbsp;price:&nbsp;number<br>&nbsp;&nbsp;producers:&nbsp;Producer[]<br>}<br><br><span>let&nbsp;data:&nbsp;Province&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>name:&nbsp;<span>'Asia',<br>&nbsp;&nbsp;<span>demand:&nbsp;<span>30,<br>&nbsp;&nbsp;<span>price:&nbsp;<span>20,<br>&nbsp;&nbsp;<span>producers:&nbsp;[<br>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;<span>name:&nbsp;<span>'Byzantium',&nbsp;<span>cost:&nbsp;<span>10,&nbsp;<span>production:&nbsp;<span>9&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;<span>name:&nbsp;<span>'Attalia',&nbsp;<span>cost:&nbsp;<span>12,&nbsp;<span>production:&nbsp;<span>10&nbsp;},<br>&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;<span>name:&nbsp;<span>'Sinope',&nbsp;<span>cost:&nbsp;<span>10,&nbsp;<span>production:&nbsp;<span>6&nbsp;}<br>&nbsp;&nbsp;]<br>}<br></span></span></span></span></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>
<pre><code>interface&nbsp;Play&nbsp;{<br>&nbsp;&nbsp;<span>name:&nbsp;string<br>&nbsp;&nbsp;type:&nbsp;string<br>}<br><br>interface&nbsp;Plays&nbsp;{<br>&nbsp;&nbsp;:&nbsp;Play<br>}<br><br><span>let&nbsp;plays:&nbsp;Plays&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>'hamlet':&nbsp;{&nbsp;<span>name:&nbsp;<span>'Hamlet',&nbsp;<span>type:&nbsp;<span>'tragedy'&nbsp;},<br>&nbsp;&nbsp;<span>'as-like':&nbsp;{&nbsp;<span>name:&nbsp;<span>'As&nbsp;You&nbsp;Like&nbsp;It',&nbsp;<span>type:&nbsp;<span>'comedy'&nbsp;},<br>&nbsp;&nbsp;<span>'othello':&nbsp;{&nbsp;<span>name:&nbsp;<span>'Othello',&nbsp;<span>type:&nbsp;<span>'tragedy'&nbsp;}<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>条件类型</p>
<pre><code>type&nbsp;isBool&lt;T&gt;&nbsp;=&nbsp;T&nbsp;extends&nbsp;boolean&nbsp;?&nbsp;<span>true&nbsp;:&nbsp;<span>false<br><br><span>//&nbsp;type&nbsp;t1&nbsp;=&nbsp;false<br>type&nbsp;t1&nbsp;=&nbsp;isBool&lt;number&gt;<br><br><span>//&nbsp;type&nbsp;t2&nbsp;=&nbsp;true<br>type&nbsp;t2&nbsp;=&nbsp;isBool&lt;<span>false&gt;<br></span></span></span></span></span></code></pre>
<p>字典类型</p>
<pre><code>interface&nbsp;Dictionary&lt;T&gt;&nbsp;{<br>&nbsp;&nbsp;:&nbsp;T<br>}<br><br><span>const&nbsp;data:&nbsp;Dictionary&lt;number&gt;&nbsp;=&nbsp;{<br>&nbsp;&nbsp;<span>a:&nbsp;<span>3,<br>&nbsp;&nbsp;<span>b:&nbsp;<span>4,<br>}<br></span></span></span></span></span></code></pre>
<p>infer - 延迟推断类型</p>
<pre><code>type&nbsp;ParamType&lt;T&gt;&nbsp;=&nbsp;T&nbsp;extends&nbsp;(param:&nbsp;infer&nbsp;P)&nbsp;=&gt;&nbsp;any&nbsp;?&nbsp;P&nbsp;:&nbsp;T<br><br>interface&nbsp;User&nbsp;{<br>&nbsp;&nbsp;<span>name:&nbsp;string<br>&nbsp;&nbsp;age:&nbsp;number<br>}<br><br>type&nbsp;Func&nbsp;=&nbsp;<span>(<span>user:&nbsp;User)&nbsp;=&gt;&nbsp;<span>void<br><br>type&nbsp;Param&nbsp;=&nbsp;ParamType&lt;Func&gt;&nbsp;<span>//&nbsp;Param&nbsp;=&nbsp;User<br>type&nbsp;AA&nbsp;=&nbsp;ParamType&lt;string&gt;&nbsp;<span>//&nbsp;string<br></span></span></span></span></span></span></code></pre>
<pre><code>type&nbsp;ElementOf&lt;T&gt;&nbsp;=&nbsp;T&nbsp;extends&nbsp;<span>Array&lt;infer&nbsp;E&gt;&nbsp;?&nbsp;E&nbsp;:&nbsp;never<br><br>type&nbsp;TTuple&nbsp;=&nbsp;<br><br>type&nbsp;ToUnion&nbsp;=&nbsp;ElementOf&lt;TTuple&gt;&nbsp;<span>//&nbsp;string&nbsp;|&nbsp;number<br></span></span></code></pre>
<h2>常用技巧</h2>
<p>使用 const enum 维护常量列表</p>
<pre><code><span>const&nbsp;enum&nbsp;STATUS&nbsp;{<br>&nbsp;&nbsp;TODO&nbsp;=&nbsp;<span>'TODO',<br>&nbsp;&nbsp;DONE&nbsp;=&nbsp;<span>'DONE',<br>&nbsp;&nbsp;DOING&nbsp;=&nbsp;<span>'DOING'<br>}<br><br><span><span>function&nbsp;<span>todos(<span>status:&nbsp;STATUS):&nbsp;<span>Todo[]&nbsp;{<br>&nbsp;&nbsp;<span>//&nbsp;...<br>}<br><br>todos(STATUS.TODO)<br></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>Partial &amp; Pick</p>
<pre><code>type&nbsp;Partial&lt;T&gt;&nbsp;=&nbsp;{<br>&nbsp;&nbsp;?:&nbsp;T<br>}<br><br>type&nbsp;Pick&lt;T,&nbsp;K&nbsp;extends&nbsp;keyof&nbsp;T&gt;&nbsp;=&nbsp;{<br>&nbsp;&nbsp;:&nbsp;T<br>}<br><br>interface&nbsp;User&nbsp;{<br>&nbsp;&nbsp;<span>id:&nbsp;number<br>&nbsp;&nbsp;age:&nbsp;number<br>&nbsp;&nbsp;name:&nbsp;string<br>}<br><br><span>//&nbsp;type&nbsp;PartialUser&nbsp;=&nbsp;{&nbsp;id?:&nbsp;number;&nbsp;age?:&nbsp;number;&nbsp;name?:&nbsp;string&nbsp;}<br>type&nbsp;PartialUser&nbsp;=&nbsp;Partial&lt;User&gt;<br><br><span>//&nbsp;type&nbsp;PickUser&nbsp;=&nbsp;{&nbsp;id:&nbsp;number;&nbsp;age:&nbsp;number&nbsp;}<br>type&nbsp;PickUser&nbsp;=&nbsp;Pick&lt;User,&nbsp;<span>'id'|<span>'age'&gt;<br></span></span></span></span></span></span></span></code></pre>
<p>Exclude &amp; Omit</p>
<pre><code>type&nbsp;Exclude&lt;T,&nbsp;U&gt;&nbsp;=&nbsp;T&nbsp;extends&nbsp;U&nbsp;?&nbsp;never&nbsp;:&nbsp;T<br><br><span>//&nbsp;type&nbsp;A&nbsp;=&nbsp;'a'<br>type&nbsp;A&nbsp;=&nbsp;Exclude&lt;<span>'x'&nbsp;|&nbsp;<span>'a',&nbsp;<span>'x'&nbsp;|&nbsp;<span>'y'&nbsp;|&nbsp;<span>'z'&gt;<br></span></span></span></span></span></span></code></pre>
<pre><code>type&nbsp;Omit&lt;T,&nbsp;K&nbsp;extends&nbsp;keyof&nbsp;any&gt;&nbsp;=&nbsp;Pick&lt;T,&nbsp;Exclude&lt;keyof&nbsp;T,&nbsp;K&gt;&gt;<br><br>interface&nbsp;User&nbsp;{<br>&nbsp;&nbsp;<span>id:&nbsp;number<br>&nbsp;&nbsp;age:&nbsp;number<br>&nbsp;&nbsp;name:&nbsp;string<br>}<br><br><span>//&nbsp;type&nbsp;PickUser&nbsp;=&nbsp;{&nbsp;age:&nbsp;number;&nbsp;name:&nbsp;string&nbsp;}<br>type&nbsp;OmitUser&nbsp;=&nbsp;Omit&lt;User,&nbsp;<span>'id'&gt;<br></span></span></span></code></pre>
<p>巧用 never 类型</p>
<pre><code>type&nbsp;FunctionPropertyNames&lt;T&gt;&nbsp;=&nbsp;{&nbsp;:&nbsp;T&nbsp;extends&nbsp;<span>Function&nbsp;?&nbsp;K&nbsp;:&nbsp;never&nbsp;}<br><br>type&nbsp;NonFunctionPropertyNames&lt;T&gt;&nbsp;=&nbsp;{&nbsp;:&nbsp;T&nbsp;extends&nbsp;<span>Function&nbsp;?&nbsp;never&nbsp;:&nbsp;K&nbsp;}<br><br>interface&nbsp;Part&nbsp;{<br>&nbsp;&nbsp;<span>id:&nbsp;number<br>&nbsp;&nbsp;name:&nbsp;string<br>&nbsp;&nbsp;subparts:&nbsp;Part[]<br>&nbsp;&nbsp;updatePart(newName:&nbsp;string):&nbsp;<span>void<br>}<br><br>type&nbsp;T40&nbsp;=&nbsp;FunctionPropertyNames&lt;Part&gt;&nbsp;&nbsp;<span>//&nbsp;'updatePart'<br>type&nbsp;T41&nbsp;=&nbsp;NonFunctionPropertyNames&lt;Part&gt;&nbsp;&nbsp;<span>//&nbsp;'id'&nbsp;|&nbsp;'name'&nbsp;|&nbsp;'subparts'<br></span></span></span></span></span></span></span></span></code></pre>
<p>混合类 ( mixins )</p>
<pre><code><span>//&nbsp;所有&nbsp;mixins&nbsp;都需要<br>type&nbsp;Constructor&lt;T&nbsp;=&nbsp;{}&gt;&nbsp;=&nbsp;<span>new&nbsp;(...args:&nbsp;any[])&nbsp;=&gt;&nbsp;T<br><br><span>//&nbsp;添加属性的混合例子<br><span><span>function&nbsp;<span>TimesTamped&lt;<span>TBase&nbsp;<span>extends&nbsp;<span>Constructor&gt;(<span>Base:&nbsp;TBase)&nbsp;{<br>&nbsp;&nbsp;<span>return&nbsp;<span><span>class&nbsp;<span>extends&nbsp;<span>Base&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;timestamp&nbsp;=&nbsp;<span>Date.now()<br>&nbsp;&nbsp;}<br>}<br><br><span>//&nbsp;添加属性和方法的混合例子<br><span><span>function&nbsp;<span>Activatable&lt;<span>TBase&nbsp;<span>extends&nbsp;<span>Constructor&gt;(<span>Base:&nbsp;TBase)&nbsp;{<br>&nbsp;&nbsp;<span>return&nbsp;<span><span>class&nbsp;<span>extends&nbsp;<span>Base&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;isActivated&nbsp;=&nbsp;<span>false<br>&nbsp;&nbsp;&nbsp;&nbsp;activate()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>this.isActivated&nbsp;=&nbsp;<span>true<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;deactivate()&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>this.isActivated&nbsp;=&nbsp;<span>false<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br><br><span>//&nbsp;简单的类<br><span><span>class&nbsp;<span>User&nbsp;{<br>&nbsp;&nbsp;name&nbsp;=&nbsp;<span>''<br>}<br><br><span>//&nbsp;添加&nbsp;TimesTamped&nbsp;的&nbsp;User<br><span>const&nbsp;TimestampedUser&nbsp;=&nbsp;TimesTamped(User)<br><br><span>//&nbsp;添加&nbsp;TimesTamped&nbsp;和&nbsp;Activatable&nbsp;的类<br><span>const&nbsp;TimestampedActivatableUser&nbsp;=&nbsp;TimesTamped(Activatable(User))<br><br><span>//&nbsp;使用组合类<br><span>const&nbsp;timestampedUserExample&nbsp;=&nbsp;<span>new&nbsp;TimestampedUser()<br><span>console.log(timestampedUserExample.timestamp)<br><br><span>const&nbsp;timestampedActivatableUserExample&nbsp;=&nbsp;<span>new&nbsp;TimestampedActivatableUser()<br><span>console.log(timestampedActivatableUserExample.timestamp)<br><span>console.log(timestampedActivatableUserExample.isActivated)<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></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>
<h2>类型转换</h2>
<p>下面来求解一道 LeetCode 上关于 TypeScript 的面试题,题目的大致内容为有一个叫做 EffectModule 的类,它的实现如下:</p>
<pre><code>interface&nbsp;Action&lt;T&gt;&nbsp;{<br>&nbsp;&nbsp;payload?:&nbsp;T<br>&nbsp;&nbsp;type:&nbsp;string<br>}<br><br><span><span>class&nbsp;<span>EffectModule&nbsp;{<br>&nbsp;&nbsp;count&nbsp;=&nbsp;<span>1<br>&nbsp;&nbsp;message&nbsp;=&nbsp;<span>'hello!'<br>&nbsp;&nbsp;delay(input:&nbsp;<span>Promise&lt;number&gt;)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;input.then(<span><span>i&nbsp;=&gt;&nbsp;({<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>payload:&nbsp;<span>`hello&nbsp;<span>${i}!`,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>type:&nbsp;<span>'delay'<br>&nbsp;&nbsp;&nbsp;&nbsp;}))<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;setMessage(action:&nbsp;Action&lt;<span>Date&gt;)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>payload:&nbsp;action.payload.getMilliseconds(),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>type:&nbsp;<span>'set-message'<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>现在有一个 connect 函数接收 EffectModule 类的实例对象作为参数,且该函数会返回新的对象,相关的实现如下:</p>
<pre><code><span>const&nbsp;connect:&nbsp;Connect&nbsp;=&nbsp;<span><span>_m&nbsp;=&gt;&nbsp;({<br>&nbsp;&nbsp;<span>delay:&nbsp;<span>(<span>input:&nbsp;number)&nbsp;=&gt;&nbsp;({<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>type:&nbsp;<span>'delay',<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>payload:&nbsp;<span>`hello&nbsp;<span>${input}`<br>&nbsp;&nbsp;}),<br>&nbsp;&nbsp;<span>setMessage:&nbsp;<span>(<span>input:&nbsp;<span>Date)&nbsp;=&gt;&nbsp;({<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>type:&nbsp;<span>'set-message',<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>payload:&nbsp;input.getMilliseconds()<br>&nbsp;&nbsp;})<br>})<br><br>type&nbsp;Connected&nbsp;=&nbsp;{<br>&nbsp;&nbsp;delay(input:&nbsp;number):&nbsp;Action&lt;string&gt;<br>&nbsp;&nbsp;setMessage(action:&nbsp;<span>Date):&nbsp;Action&lt;number&gt;<br>}<br><br><span>const&nbsp;connected:&nbsp;Connected&nbsp;=&nbsp;connect(<span>new&nbsp;EffectModule())<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<p>可以看到在调用 connect 函数之后,返回的新对象只包含 EffectModule 的同名方法,并且方法的类型签名改变了:</p>
<pre><code>asyncMethod&lt;T,&nbsp;U&gt;(input:&nbsp;<span>Promise&lt;T&gt;):&nbsp;<span>Promise&lt;Action&lt;U&gt;&gt;&nbsp;&nbsp;变成了<br>asyncMethod&lt;T,&nbsp;U&gt;(input:&nbsp;T):&nbsp;Action&lt;U&gt;&nbsp;<br></span></span></code></pre>
<pre><code>syncMethod&lt;T,&nbsp;U&gt;(action:&nbsp;Action&lt;T&gt;):&nbsp;Action&lt;U&gt;&nbsp;&nbsp;变成了<br>syncMethod&lt;T,&nbsp;U&gt;(action:&nbsp;T):&nbsp;Action&lt;U&gt;<br></code></pre>
<p>现在就需要我们来编写<span class="Apple-converted-space">&nbsp;<code>type Connect = (module: EffectModule) =&gt; any</code><span class="Apple-converted-space">&nbsp;使得最终的编译能够顺利通过。不难看出,这个题目主要考察两点:</span></span></p>
<ul class="list-paddingleft-2">
<li>
<p>从类中挑选出函数</p>
</li>
<li>
<p>巧用 infer 进行类型转换</p>
</li>
</ul>
<p>下面是我解这道题的答案:</p>
<pre><code>type&nbsp;FuncName&lt;T&gt;&nbsp;=&nbsp;{&nbsp;:&nbsp;T&nbsp;extends&nbsp;<span>Function&nbsp;?&nbsp;P&nbsp;:&nbsp;never&nbsp;}<br><br>type&nbsp;Middle&nbsp;=&nbsp;{&nbsp;:&nbsp;EffectModule&nbsp;}<br><br>type&nbsp;Transfer&lt;T&gt;&nbsp;=&nbsp;{<br>&nbsp;&nbsp;:&nbsp;T&nbsp;extends&nbsp;(input:&nbsp;<span>Promise&lt;infer&nbsp;J&gt;)&nbsp;=&gt;&nbsp;<span>Promise&lt;infer&nbsp;K&gt;<br>&nbsp;&nbsp;?&nbsp;<span>(<span>input:&nbsp;J)&nbsp;=&gt;&nbsp;K<br>&nbsp;&nbsp;:&nbsp;T&nbsp;extends&nbsp;(action:&nbsp;Action&lt;infer&nbsp;J&gt;)&nbsp;=&gt;&nbsp;infer&nbsp;K<br>&nbsp;&nbsp;?&nbsp;<span>(<span>input:&nbsp;J)&nbsp;=&gt;&nbsp;K<br>&nbsp;&nbsp;:&nbsp;never<br>}<br><br>type&nbsp;Connect&nbsp;=&nbsp;<span>(<span><span>module:&nbsp;EffectModule)&nbsp;=&gt;&nbsp;{&nbsp;:&nbsp;Transfer&lt;Middle&gt;&nbsp;}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></code></pre>
<h2>控制反转与依赖注入</h2>
<p>控制反转 ( Inversion of Control ) 与依赖注入 ( Dependency Injection ) 是面向对象编程中十分重要的思想和法则。维基百科上给出的解释是 IoC 能够降低计算机代码之间的耦合度,DI 代表的则是在一个对象被创建时,注入该对象所依赖的所有对象的过程。前端框架 Angular 与基于 Node.js 的后端框架 Nest 都引用了这一思想。对于这两个概念的具体阐述在这里就不再展开,但读者可以看看这两篇文章 。下面我们基于 Angular 5 以前的 Dependency Injection 来实现简版的控制反转与依赖注入。</p>
<p>首先让我们来编写一段相关的测试代码:</p>
<pre><code><span>import&nbsp;{&nbsp;expect&nbsp;}&nbsp;<span>from&nbsp;<span>'chai'<br><span>import&nbsp;{&nbsp;Injectable,&nbsp;createInjector&nbsp;}&nbsp;<span>from&nbsp;<span>'./injection'<br><br><span><span>class&nbsp;<span>Engine&nbsp;{}<br><br><span><span>class&nbsp;<span>DashboardSoftware&nbsp;{}<br><br>@Injectable()<br><span><span>class&nbsp;<span>Dashboard&nbsp;{<br>&nbsp;&nbsp;<span>constructor(public&nbsp;software:&nbsp;DashboardSoftware)&nbsp;{}<br>}<br><br>@Injectable()<br><span><span>class&nbsp;<span>Car&nbsp;{<br>&nbsp;&nbsp;<span>constructor(public&nbsp;engine:&nbsp;Engine)&nbsp;{}<br>}<br><br>@Injectable()<br><span><span>class&nbsp;<span>CarWithDashboard&nbsp;{<br>&nbsp;&nbsp;<span>constructor(public&nbsp;engine:&nbsp;Engine,&nbsp;public&nbsp;dashboard:&nbsp;Dashboard)&nbsp;{}<br>}<br><br><span><span>class&nbsp;<span>NoAnnotations&nbsp;{<br>&nbsp;&nbsp;<span>constructor(_secretDependency:&nbsp;any)&nbsp;{}<br>}<br><br>describe(<span>'injector',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;it(<span>'should&nbsp;instantiate&nbsp;a&nbsp;class&nbsp;without&nbsp;dependencies',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;injector&nbsp;=&nbsp;createInjector()<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;engine&nbsp;=&nbsp;injector.get(Engine)<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(engine&nbsp;<span>instanceof&nbsp;Engine).to.be.true<br>&nbsp;&nbsp;})<br><br>&nbsp;&nbsp;it(<span>'should&nbsp;resolve&nbsp;dependencies&nbsp;based&nbsp;on&nbsp;type&nbsp;information',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;injector&nbsp;=&nbsp;createInjector()<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;car&nbsp;=&nbsp;injector.get(Car)<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(car&nbsp;<span>instanceof&nbsp;Car).to.be.true<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(car.engine&nbsp;<span>instanceof&nbsp;Engine).to.be.true<br>&nbsp;&nbsp;})<br><br>&nbsp;&nbsp;it(<span>'should&nbsp;resolve&nbsp;nested&nbsp;dependencies&nbsp;based&nbsp;on&nbsp;type&nbsp;information',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;injector&nbsp;=&nbsp;createInjector()<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;_CarWithDashboard&nbsp;=&nbsp;injector.get(CarWithDashboard)<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(_CarWithDashboard.dashboard.software&nbsp;<span>instanceof&nbsp;DashboardSoftware).to.be.true<br>&nbsp;&nbsp;})<br><br>&nbsp;&nbsp;it(<span>'should&nbsp;cache&nbsp;instances',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;injector&nbsp;=&nbsp;createInjector()<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;e1&nbsp;=&nbsp;injector.get(Engine)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;e2&nbsp;=&nbsp;injector.get(Engine)<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(e1).to.equal(e2)<br>&nbsp;&nbsp;})<br><br>&nbsp;&nbsp;it(<span>'should&nbsp;show&nbsp;the&nbsp;full&nbsp;path&nbsp;when&nbsp;no&nbsp;provider',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;injector&nbsp;=&nbsp;createInjector()<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(<span><span>()&nbsp;=&gt;&nbsp;injector.get(CarWithDashboard)).to.throw(<span>'No&nbsp;provider&nbsp;for&nbsp;DashboardSoftware!')<br>&nbsp;&nbsp;})<br><br>&nbsp;&nbsp;it(<span>'should&nbsp;throw&nbsp;when&nbsp;no&nbsp;type',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(<span><span>()&nbsp;=&gt;&nbsp;createInjector()).to.throw(<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>'Make&nbsp;sure&nbsp;that&nbsp;NoAnnotations&nbsp;is&nbsp;decorated&nbsp;with&nbsp;Injectable.'<br>&nbsp;&nbsp;&nbsp;&nbsp;)<br>&nbsp;&nbsp;})<br><br>&nbsp;&nbsp;it(<span>'should&nbsp;throw&nbsp;when&nbsp;no&nbsp;provider&nbsp;defined',&nbsp;()&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;injector&nbsp;=&nbsp;createInjector([])<br>&nbsp;&nbsp;&nbsp;&nbsp;expect(<span><span>()&nbsp;=&gt;&nbsp;injector.get(<span>'NonExisting')).to.throw(<span>'No&nbsp;provider&nbsp;for&nbsp;NonExisting!')<br>&nbsp;&nbsp;})<br>})<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></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>
<p>可以看到我们要实现的核心功能有三个:</p>
<ul class="list-paddingleft-2">
<li>
<p>根据提供的类创建 IoC 容器并且能够管理类之间的依赖关系</p>
</li>
<li>
<p>在通过 IoC 容器获取类的实例对象时注入相关的依赖对象</p>
</li>
<li>
<p>实现多级依赖与处理边缘情况</p>
</li>
</ul>
<p>首先来实现最简单的<span class="Apple-converted-space">&nbsp;<code>@Injectable</code><span class="Apple-converted-space">&nbsp;装饰器:</span></span></p>
<pre><code><span>export&nbsp;<span>const&nbsp;Injectable&nbsp;=&nbsp;():&nbsp;<span><span>ClassDecorator&nbsp;=&gt;&nbsp;target&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;<span>Reflect.defineMetadata(<span>'Injectable',&nbsp;<span>true,&nbsp;target)<br>}<br></span></span></span></span></span></span></span></code></pre>
<p>然后我们来实现根据提供的 provider 类创建能够管理类之间依赖关系的 IoC 容器:</p>
<pre><code>abstract&nbsp;<span><span>class&nbsp;<span>ReflectiveInjector&nbsp;<span>implements&nbsp;<span>Injector&nbsp;{<br>&nbsp;&nbsp;abstract&nbsp;get(token:&nbsp;any):&nbsp;any<br>&nbsp;&nbsp;<span>static&nbsp;resolve(providers:&nbsp;Provider[]):&nbsp;ResolvedReflectiveProvider[]&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;providers.map(resolveReflectiveProvider)<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;<span>static&nbsp;fromResolvedProviders(providers:&nbsp;ResolvedReflectiveProvider[]):&nbsp;ReflectiveInjector&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;<span>new&nbsp;ReflectiveInjector_(providers)<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;<span>static&nbsp;resolveAndCreate(providers:&nbsp;Provider[]):&nbsp;ReflectiveInjector&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;resolvedReflectiveProviders&nbsp;=&nbsp;ReflectiveInjector.resolve(providers)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;ReflectiveInjector.fromResolvedProviders(resolvedReflectiveProviders)<br>&nbsp;&nbsp;}<br>}<br><br><span><span>class&nbsp;<span>ReflectiveInjector_&nbsp;<span>implements&nbsp;<span>ReflectiveInjector&nbsp;{<br>&nbsp;&nbsp;_providers:&nbsp;ResolvedReflectiveProvider[]<br>&nbsp;&nbsp;keyIds:&nbsp;number[]<br>&nbsp;&nbsp;objs:&nbsp;any[]<br>&nbsp;&nbsp;<span>constructor(_providers:&nbsp;ResolvedReflectiveProvider[])&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>this._providers&nbsp;=&nbsp;_providers<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;len&nbsp;=&nbsp;_providers.length<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>this.keyIds&nbsp;=&nbsp;<span>new&nbsp;<span>Array(len)<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>this.objs&nbsp;=&nbsp;<span>new&nbsp;<span>Array(len)<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>for&nbsp;(<span>let&nbsp;i&nbsp;=&nbsp;<span>0;&nbsp;i&nbsp;&lt;&nbsp;len;&nbsp;i++)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>this.keyIds&nbsp;=&nbsp;_providers.key.id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>this.objs&nbsp;=&nbsp;<span>undefined<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;<span>//&nbsp;...<br>}<br><br><span><span>function&nbsp;<span>resolveReflectiveProvider(<span>provider:&nbsp;Provider):&nbsp;<span>ResolvedReflectiveProvider&nbsp;{<br>&nbsp;&nbsp;<span>return&nbsp;<span>new&nbsp;ResolvedReflectiveProvider_(<br>&nbsp;&nbsp;&nbsp;&nbsp;ReflectiveKey.get(provider),<br>&nbsp;&nbsp;&nbsp;&nbsp;resolveReflectiveFactory(provider)<br>&nbsp;&nbsp;)<br>}<br><br><span><span>function&nbsp;<span>resolveReflectiveFactory(<span>provider:&nbsp;Provider):&nbsp;<span>ResolvedReflectiveFactory&nbsp;{<br>&nbsp;&nbsp;<span>let&nbsp;factoryFn:&nbsp;<span>Function<br>&nbsp;&nbsp;<span>let&nbsp;resolvedDeps:&nbsp;ReflectiveDependency[]<br><br>&nbsp;&nbsp;factoryFn&nbsp;=&nbsp;factory(provider)<br>&nbsp;&nbsp;resolvedDeps&nbsp;=&nbsp;dependenciesFor(provider)<br><br>&nbsp;&nbsp;<span>return&nbsp;<span>new&nbsp;ResolvedReflectiveFactory(factoryFn,&nbsp;resolvedDeps)<br>}<br><br><span><span>function&nbsp;<span>factory&lt;<span>T&gt;(<span>t:&nbsp;Type&lt;T&gt;):&nbsp;(<span>args:&nbsp;any[])&nbsp;=&gt;&nbsp;<span>T&nbsp;{<br>&nbsp;&nbsp;<span>return&nbsp;<span>(<span>...args:&nbsp;any[])&nbsp;=&gt;&nbsp;<span>new&nbsp;t(...args)<br>}<br><br><span><span>function&nbsp;<span>dependenciesFor(<span>type:&nbsp;Type&lt;any&gt;):&nbsp;<span>ReflectiveDependency[]&nbsp;{<br>&nbsp;&nbsp;<span>const&nbsp;params&nbsp;=&nbsp;parameters(type)<br>&nbsp;&nbsp;<span>return&nbsp;params.map(extractToken)<br>}<br><br><span><span>function&nbsp;<span>parameters(<span>type:&nbsp;Type&lt;any&gt;)&nbsp;{<br>&nbsp;&nbsp;<span>if&nbsp;(noCtor(type))&nbsp;<span>return&nbsp;[]<br><br>&nbsp;&nbsp;<span>const&nbsp;isInjectable&nbsp;=&nbsp;<span>Reflect.getMetadata(<span>'Injectable',&nbsp;type)<br>&nbsp;&nbsp;<span>const&nbsp;res&nbsp;=&nbsp;<span>Reflect.getMetadata(<span>'design:paramtypes',&nbsp;type)<br><br>&nbsp;&nbsp;<span>if&nbsp;(!isInjectable)&nbsp;<span>throw&nbsp;noAnnotationError(type)<br><br>&nbsp;&nbsp;<span>return&nbsp;res&nbsp;?&nbsp;res&nbsp;:&nbsp;[]<br>}<br><br><span>export&nbsp;<span>const&nbsp;createInjector&nbsp;=&nbsp;(providers:&nbsp;Provider[]):&nbsp;<span><span>ReflectiveInjector_&nbsp;=&gt;&nbsp;{<br>&nbsp;&nbsp;<span>return&nbsp;ReflectiveInjector.resolveAndCreate(providers)&nbsp;<span>as&nbsp;ReflectiveInjector_<br>}<br></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></span></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>
<p>从上面的代码不难看出当 IoC 容器创建时会将提供的每个类以及该类所依赖的其他类作为<span class="Apple-converted-space">&nbsp;<code>ResolvedReflectiveProvider_</code><span class="Apple-converted-space">&nbsp;的实例对象存储在容器中,对外返回的则是容器对象<span class="Apple-converted-space">&nbsp;<code>ReflectiveInjector_</code><span class="Apple-converted-space">&nbsp;。</span></span></span></span></p>
<p>接下来让我们来实现通过 IoC 容器获取类的实例对象的逻辑:</p>
<pre><code><span><span>class&nbsp;<span>ReflectiveInjector_&nbsp;<span>implements&nbsp;<span>ReflectiveInjector&nbsp;{<br>&nbsp;&nbsp;<span>//&nbsp;...<br>&nbsp;&nbsp;get(token:&nbsp;any):&nbsp;any&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;<span>this._getByKey(ReflectiveKey.get(token))<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;private&nbsp;_getByKey(key:&nbsp;ReflectiveKey,&nbsp;isDeps?:&nbsp;boolean)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>for&nbsp;(<span>let&nbsp;i&nbsp;=&nbsp;<span>0;&nbsp;i&nbsp;&lt;&nbsp;<span>this.keyIds.length;&nbsp;i++)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>if&nbsp;(<span>this.keyIds&nbsp;===&nbsp;key.id)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>if&nbsp;(<span>this.objs&nbsp;===&nbsp;<span>undefined)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>this.objs&nbsp;=&nbsp;<span>this._new(<span>this._providers)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;<span>this.objs<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>let&nbsp;res&nbsp;=&nbsp;isDeps&nbsp;?&nbsp;(key.token&nbsp;<span>as&nbsp;Type).name&nbsp;:&nbsp;key.token<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>throw&nbsp;noProviderError(res)<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;_new(provider:&nbsp;ResolvedReflectiveProvider)&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;resolvedReflectiveFactory&nbsp;=&nbsp;provider.resolvedFactory<br>&nbsp;&nbsp;&nbsp;&nbsp;<span>const&nbsp;factory&nbsp;=&nbsp;resolvedReflectiveFactory.factory<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>let&nbsp;deps&nbsp;=&nbsp;resolvedReflectiveFactory.dependencies.map(<span><span>dep&nbsp;=&gt;&nbsp;<span>this._getByKey(dep.key,&nbsp;<span>true))<br><br>&nbsp;&nbsp;&nbsp;&nbsp;<span>return&nbsp;factory(...deps)<br>&nbsp;&nbsp;}<br>}<br></span></span></span></span></span></span></span></span></span></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>
<p>可以看到当我们调用<span class="Apple-converted-space">&nbsp;<code>injector.get()</code><span class="Apple-converted-space">&nbsp;方法时 IoC 容器会根据给定类查找对应的<span class="Apple-converted-space">&nbsp;<code>ReflectiveInjector_</code><span class="Apple-converted-space">&nbsp;对象,找到之后便会在实例化给定类之前注入该类依赖的所有类的实例对象,最后再返回给定类的实例化对象。</span></span></span></span></p>
<p>现在我们再回头看上文的代码,多级依赖的功能其实早已实现。虽然在初始化 loC 容器时我们只能找到某个类的相关依赖,无法再通过依赖类找到更深层级的依赖,但是我们对提供的每个类遍历执行了相同的操作,因此很自然的就实现了多个类之间的依赖。</p>
<p>对于边缘情况我们也做了相应的处理,比如提供的 provider 类为空数组,类并没有被<span class="Apple-converted-space">&nbsp;<code>@Injectable</code><span class="Apple-converted-space">&nbsp;装饰器修饰,提供的类并不完整等。对应上文的代码为:</span></span></p>
<pre><code><span>let&nbsp;res&nbsp;=&nbsp;isDeps&nbsp;?&nbsp;(key.token&nbsp;<span>as&nbsp;Type).name&nbsp;:&nbsp;key.token<br><br><span>throw&nbsp;noProviderError(res)<br></span></span></span></code></pre>
<pre><code><span>if&nbsp;(!isInjectable)&nbsp;<span>throw&nbsp;noAnnotationError(type)<br></span></span></code></pre>
<p>至此,控制反转与依赖注入的核心功能就实现的差不多了,剩下的就是一些接口定义代码,还有就是<span class="Apple-converted-space">&nbsp;<code>ReflectiveKey</code><span class="Apple-converted-space">&nbsp;类的实现,它的大致作用其实就是基于 ES6 中的 Map 存储 provider 类。</span></span></p><br><br>
来源:https://www.cnblogs.com/cczlovexw/p/11639856.html
頁: [1]
查看完整版本: TypeScript 技巧