踩坑细数这些年被困扰过的TS问题

TypeScript是一种由微软开发的自由和开源的编程语言。它是JavaScript的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript提供最新的和不断发展的JavaScript特性,包括那些来自年的ECMAScript和未来的提案中的特性,比如异步功能和Decorators,以帮助建立健壮的组件。

阿宝哥第一次使用TypeScript是在Angular2.x项目中,那时候TypeScript还没有进入大众的视野。然而现在学习TypeScript的小伙伴越来越多了,本文阿宝哥将分享这些年在学习TypeScript过程中,曾被困扰过的一些TS问题,希望本文对学习TypeScript的小伙伴能有一些帮助。

好的,下面我们来开始介绍第一个问题——如何在window对象上显式设置属性。

一、如何在window对象上显式设置属性

对于使用过JavaScript的开发者来说,对于window.MyNamespace=window.MyNamespace

{};这行代码并不会陌生。为了避免开发过程中出现冲突,我们一般会为某些功能设置独立的命名空间。

然而,在TS中对于window.MyNamespace=window.MyNamespace

{};这行代码,TS编译器会提示以下异常信息:

PropertyMyNamespacedoesnotexistontypeWindowtypeofglobalThis.()

以上异常信息是说在WindowtypeofglobalThis交叉类型上不存在MyNamespace属性。那么如何解决这个问题呢?最简单的方式就是使用类型断言:

(windowasany).MyNamespace={};

虽然使用any大法可以解决上述问题,但更好的方式是扩展lib.dom.d.ts文件中的Window接口来解决上述问题,具体方式如下:

declareinterfaceWindow{MyNamespace:any;}window.MyNamespace=window.MyNamespace

{};

下面我们再来看一下lib.dom.d.ts文件中声明的Window接口:

/***AwindowcontainingaDOMdocument;thedocumentproperty*pointstotheDOMdocumentloadedinthatwindow.*/interfaceWindowextendsEventTarget,AnimationFrameProvider,GlobalEventHandlers,WindowEventHandlers,WindowLocalStorage,WindowOrWorkerGlobalScope,WindowSessionStorage{//已省略大部分内容readonlydevicePixelRatio:number;readonlydocument:Document;readonlytop:Window;readonlywindow:WindowtypeofglobalThis;addEventListener(type:string,listener:EventListenerOrEventListenerObject,options?:boolean

AddEventListenerOptions):void;removeEventListenerKextendskeyofWindowEventMap(type:K,listener:(this:Window,ev:WindowEventMap[K])=any,options?:boolean

EventListenerOptions):void;[index:number]:Window;}

在上面我们声明了两个相同名称的Window接口,这时并不会造成冲突。TypeScript会自动进行接口合并,即把双方的成员放到一个同名的接口中。

二、如何为对象动态分配属性

在JavaScript中,我们可以很容易地为对象动态分配属性,比如:

letdeveloper={};developer.name="semlinker";

以上代码在JavaScript中可以正常运行,但在TypeScript中,编译器会提示以下异常信息:

Propertynamedoesnotexistontype{}.()

{}类型表示一个没有包含成员的对象,所以该类型没有包含name属性。为了解决这个问题,我们可以声明一个LooseObject类型:

interfaceLooseObject{[key:string]:any}

该类型使用索引签名的形式描述LooseObject类型可以接受key类型是字符串,值的类型是any类型的字段。有了LooseObject类型之后,我们就可以通过以下方式来解决上述问题:

interfaceLooseObject{[key:string]:any}letdeveloper:LooseObject={};developer.name="semlinker";

对于LooseObject类型来说,它的约束是很宽松的。在一些应用场景中,我们除了希望能支持动态的属性之外,也希望能够声明一些必选和可选的属性。

比如对于一个表示开发者的Developer接口来说,我们希望它的name属性是必填,而age属性是可选的,此外还支持动态地设置字符串类型的属性。针对这个需求我们可以这样做:

interfaceDeveloper{name:string;age?:number;[key:string]:any}letdeveloper:Developer={name:"semlinker"};developer.age=30;developer.city="XiaMen";

其实除了使用索引签名之外,我们也可以使用TypeScript内置的工具类型Record来定义Developer接口:

//typeRecordKextendsstring

number

symbol,T={[PinK]:T;}interfaceDeveloperextendsRecordstring,any{name:string;age?:number;}letdeveloper:Developer={name:"semlinker"};developer.age=30;developer.city="XiaMen";三、如何理解泛型中的T

对于刚接触TypeScript泛型的读者来说,首次看到T语法会感到陌生。其实它没有什么特别,就像传递参数一样,我们传递了我们想要用于特定函数调用的类型。

参考上面的图片,当我们调用identityNumber(1),Number类型就像参数1一样,它将在出现T的任何位置填充该类型。图中T内部的T被称为类型变量,它是我们希望传递给identity函数的类型占位符,同时它被分配给value参数用来代替它的类型:此时T充当的是类型,而不是特定的Number类型。

其中T代表Type,在定义泛型时通常用作第一个类型变量名称。但实际上T可以用任何有效名称代替。除了T之外,以下是常见泛型变量代表的意思:

K(Key):表示对象中的键类型;V(Value):表示对象中的值类型;E(Element):表示元素类型。

其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量U,用于扩展我们定义的identity函数:

functionidentityT,U(value:T,message:U):T{console.log(message);returnvalue;}console.log(identityNumber,string(68,"Semlinker"));

除了为类型变量显式设定值之外,一种更常见的做法是使编译器自动选择这些类型,从而使代码更简洁。我们可以完全省略尖括号,比如:

functionidentityT,U(value:T,message:U):T{console.log(message);returnvalue;}console.log(identity(68,"Semlinker"));

对于上述代码,编译器足够聪明,能够知道我们的参数类型,并将它们赋值给T和U,而不需要开发人员显式指定它们。

??继续阅读:一文读懂TypeScript泛型及应用

四、如何理解装饰器的作用

在TypeScript中装饰器分为类装饰器、属性装饰器、方法装饰器和参数装饰器四大类。装饰器的本质是一个函数,通过装饰器我们可以方便地定义与对象相关的元数据。

比如在ionic-native项目中,它使用Plugin装饰器来定义IonicNative中Device插件的相关信息:

Plugin({pluginName:Device,plugin:cordova-plugin-device,pluginRef:device,repo:

转载请注明:http://www.sonphie.com/jbzd/14467.html

  • 上一篇文章:
  • 下一篇文章: 没有了
  • 网站简介| 发布优势| 服务条款| 隐私保护| 广告合作| 网站地图| 版权申明

    当前时间: