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