1.思考TypeScript提供运行时检查关于TypeScript有一个普遍的误解:只要一个变量被标记了类型,它就会一直检查它自己的数据类型是否符合我们的预期。与这种误解相呼应的是,对从后端返回的对象进行类型注释可以在运行时执行检查以确保对象的类型正确。然而,这个想法是错误的!因为TypeScript最终会被编译成JavaScript代码,而JavaScript也在浏览器中运行。此时(译者注:Runtime)所有的类型信息都丢失了,所以TypeScript无法自动验证类型。理解这一点的一个好方法是查看编译后的代码:interfacePerson{name:string;age:number;}functionfetchFromBackend():Promise{returnfetch('http://example.com').then((res)=>res.json())}//编译好的functionfetchFromBackend(){returnfetch('http://example.com').then(function(res){returnres.json();})}可以查看编译后接口??定义已经完全消失,这里不会有任何验证码。但是你最好能够自己进行运行时验证,有很多库(译者注:io-ts)可以帮助你做到这一点。但是,请记住,必然会有性能开销。*考虑对所有外部提供的对象(如从后端获取的对象、JSON反序列化的对象等)进行运行时检查2.不要将类型定义为any在使用TypeScript时,可以声明变量或函数参数的类型是任何,但这样做也意味着该变量超出类型安全。但是,将其声明为任何类型也可能是有益的,这在某些场景下是有帮助的(例如逐渐将类型添加到现有的JavaScript代码库中,译者注:一般在将代码库从js升级到ts时)。但它也像一个逃生舱口,可以大大降低代码的类型安全性。类型安全在覆盖尽可能多的代码时效果最好。否则,安全网就会出现漏洞,漏洞可以通过这些漏洞传播。例如:如果一个函数返回any,那么所有使用它的返回值的表达式类型也将变成any。所以你应该尽量避免使用任何类型。幸运的是,TypeScript3.0引入了一种类型安全的替代方案——未知。你可以给一个未知类型的变量赋任何值,但是你不能把一个未知类型的变量的值赋给任何变量(这点和any不同)。如果您的函数返回未知类型的值,则调用者需要执行检查(使用类型保护),或者至少将该值显式转换为某种特定类型。(译者注:如果这段话看不懂,可以参考后面的文章,未知类型中的例子部分)letfoo:any;//anythingcanbeassignedtofoofoo='abc';//foocanbeassignedtoanythingconstx:number=foo;letbar:未知;//anythingcanbeassignedtobarbar='abc';//COMPILERROR!Type'unknown'isnotassignabletotype'number'.consty:number=bar;使用未知类型有时会有点麻烦,但它也让代码更容易理解,让你在开发时更加用心。此外,您需要启用noImplicitAny,这将在编译器推断值是任何类型时抛出错误。换句话说,它允许您明确标记所有发生任何情况的场景。尽管最终目标是消除any的使用,但显式声明any仍然是有益的:例如,在代码审查期间可以更容易地捕获它们。*不要使用任何类型并启用noImplicitAny3。启用strictNullChecks您看到此错误消息多少次了?TypeError:undefineddisnotanobject我多次打赌,JavaScript(甚至软件编程)中最常见的错误来源之一是忘记处理空值。在JavaScript中使用null或undefined来表示空值。开发人员通常乐观地认为给定变量不会为null,而忘记处理null情况。functionprintName(person:Person){console.log(person.name.toUpperCase());}//RUNTIMEERROR!TypeError:undefineddisnotanobject//(evaluating'person.name')printName(undefined);通过启用strictNullChecks,编译器会强制你做相关的检查,对防止这个常见问题起到了重要作用。默认情况下,每种类型的打字稿都包含两个值null和undefined。也就是说,可以将null和undefined分配给任何类型的任何变量。打开strictNullChecks将改变这种行为。由于undefined不能作为Person类型的参数传递,下面的代码在编译的时候会报错。//COMPILEERROR!//Argumentoftype'undefined'isnotassignabletoparameteroftype'Person'.printName(undefined);那么如果你真的想将undefined传递给printName怎么办?然后你可以调整类型签名,但你仍然需要处理未定义的情况。functionprintName(person:Person|undefined){//COMPILEERROR!//Objectispossibly'undefined'.console.log(person.name.toUpperCase());}您可以通过确保定义了person来修复此错误:functionprintName(person:Person|undefined){if(person){console.log(person.name.toUpperCase());}}遗憾的是,strictNullChecks默认是不开启的,我们需要在tsconfig.json中进行配置。或者,strictNullChecks是更通用的严格模式的一部分,可以通过strict标志启用。你绝对应该!因为编译器设置越严格,越早发现错误。*始终打开strictNullChecks4。打开strictPropertyInitializationStrictPropertyInitialization是属于严格模式标志集的另一个标志。使用Class时开启strictPropertyInitialization尤为重要。它实际上有点像对strictNullChecks的扩展。如果未启用strictPropertyInitialization,TS将允许以下代码:classPerson{name:string;sayHello(){//RUNTIMEERROR!console.log(`Hellofrom${this.name.toUpperCase()}`);}}hereAn明显的问题:this.name没有初始化,所以在运行时调用sayHello会报错。此错误的根本原因是该属性未在构造函数中分配或使用属性初始值设定项,因此它(至少最初)未定义,因此其类型变为string|不明确的。开启strictPropertyInitialization会提示如下错误:Property'name'hasnoinitializerandisnotassignedintheconstructor。当然,如果你在构造函数中赋值或者使用属性初始化器,这个错误就会消失。*始终启用strictPropertyInitialization5。记得指定函数的返回类型TypeScript允许你严重依赖类型推断,这意味着只要TS能推断出类型,你就不需要注解类型。不过,这就像一把双刃剑,一方面非常方便,也减少了使用TypeScript的麻烦。另一方面,有时推断的类型可能与您的预期不匹配,从而降低了使用静态类型提供的保证。在下面的示例中,我们没有指定返回类型,而是让TypeScript推断函数的返回值。interfacePerson{name:string;age:number;}functiongetName(person:Person|undefined){if(person&&person.name){returnperson.name;}elseif(!person){return"noname";}}乍一看,我们可能认为我们的方法是安全的,总是返回字符串类型,然而,当我们显式声明函数的(预期的)返回类型时,我们会发现已经报错了。//COMPILEERROR!//Functionlacksendingreturnstatementandreturntypedoesnotinclude'undefined'.functiongetName(person:Person|undefined):string{//...}顺便说一下,这个错误只有当你启用strictNullChecks。上面的错误说明getName函数的返回值没有涵盖一种情况:当person不为空,而person.name为空。在这种情况下,所有的if条件都不等于true,所以会返回undefined。所以TypeScript推断这个函数的返回类型是string|undefined,而我们改为声明string。(译者注:所以主动声明函数的返回值类型有助于我们提前捕捉到一些不易察觉的bug)*始终标记函数的返回值类型6.不要将隐式类型变量存储到对象中TypeScript的类型检查有时很微妙.通常,当类型A至少具有与类型B相同的属性时,TypeScript允许将类型A的对象分配给类型B的变量。这意味着它可以包含其他属性。//翻译器示例:typeA={name:string;age:number;};typeB={name:string;};leta:A={name:'John',age:12,};letb:B;//编译成功b=a;但是,如果直接传递对象文字,则行为会有所不同。如果目标类型包含相同的属性,TypeScript将只允许它(传递)。目前不允许使用其他属性。interfacePerson{name:string;}functiongetName(person:Person):string|undefined{//...}//okgetName({name:'John'});//COMPILEERROR//Argumentoftype'{name:string;age:number;}'isnotassignabletoparameteroftype'Person'.getName({name:'John',age:30});如果我们不直接传递对象字面量,而是将对象存储在一个常量中(然后再传递),看起来并没有什么区别。然而,这改变了类型检查的行为:constperson={name:'John',age:30};//OKgetName(person);传递额外的属性可能会导致错误(例如,当您想要合并两个对象时)。请注意此行为,并在可能的情况下直接传递对象文字。*注意对象是如何传递给函数的,并始终考虑传递额外的属性细节是否安全。此时您可以使用类型断言告诉编译器,“相信我,我知道我在做什么”。例如,断言从服务器请求的对象,或断言子类型的对象作为父类型。需要谨慎使用类型断言。例如,当函数参数类型不匹配时,一定不能使用它。有一种使用类型断言的更安全的方法:类型保护。类型保护是一个函数,当它返回true时断言其参数的类型。它可以提供代码的运行时检查,让我们更有信心传入的变量符合预期。在下面的代码中,我们需要使用类型断言,因为TypeScript不知道从后端返回的对象的类型。interfacePerson{name:string;age:number;}declareconstfetchFromBackend:(url:string)=>Promise