微信搜索【伟大的走向世界】,第一时间与大家分享前端行业动态、学习路径等。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。学习Typescript通常是一个重新发现的过程。最初的印象可能具有欺骗性:这不就是注释Javascript以便编译器可以帮助我找到潜在错误的一种方式吗?虽然这句话通常是正确的,但随着您的进步,您会发现语言最不可思议的力量在于组合、推断和操纵类型。本文总结了一些技巧,可帮助您充分发挥该语言的潜力。将类型视为集合类型是程序员的日常概念,但很难对其进行简洁定义。我发现将集合用作概念模型很有帮助。例如,新学习者发现Typescript组合类型的方式违反直觉。举一个非常简单的例子:类型Measure={radius:number};类型样式={颜色:字符串};//输入{半径:数字;颜色:字符串}类型Circle=Measure&Style;如果您使用&运算符解释为逻辑AND,您可能会认为Circle是一个愚蠢的类型,因为它是两个没有任何重叠字段的类型的联合。这不是TypeScript的工作方式。相反,将其视为一个集合更容易推断出正确的行为:每个类型都是值的集合。有些集合是无限的,比如string、object;有些是有限的,比如boolean、undefined、...unknownYes一个通用集合(包含所有值),而never是一个空集合(不包含任何值)TypeMeasure是一个包含所有对象的集合,这些对象包含一个名为radius的数字字段.样式也是如此。&运算符创建一个交集:Measure&Style表示具有半径和颜色字段的对象集合,这实际上是一个较小的集合,但具有更常用的字段。此外,|运算符创建一个联合:一个更大的集合,但可能具有更少的公共字段(如果组合了两个对象类型)集合还有助于理解可分配性:仅当值的类型是目标时才允许分配只有当它是该类型的子集时才允许:类型ShapeKind='rect'|'circle';letfoo:string=getSomeString();letshape:ShapeKind='rect';//不允许,因为字符串不是ShapeKind集的子项。形状=富;//允许,因为ShapeKind是字符串的子集。foo=形状;理解类型声明和类型缩小TypeScript的一个非常强大的特性是基于控制流的自动类型缩小。这意味着在代码位置的任何特定点,变量有两种类型:声明类型和缩小类型。functionfoo(x:string|number){if(typeofx==='string'){//x的类型缩小为字符串,所以.length是有效的console.log(x.length);//赋值遵循声明类型,而不是缩小类型x=1;控制台日志(x.length);//不允许,因为x现在是数字}else{...}}在定义一组多态类型(例如Shape)时,使用具有区别的联合类型而不是可选字段,可以轻松地从以下内容开始:typeShape={kind:'圆'|'直';半径?:数字;宽度?:数字;height?:number;}functiongetArea(shape:Shape){returnshape.kind==='circle'?Math.PI*shape.radius!**2:形状.宽度!*shape.height!;}需要使用非空断言(访问半径、宽度和高度字段时),因为种类与其他字段之间没有建立关系。相反,有区别的联合是更好的解决方案:typeCircle={kind:'circle';半径:数字};类型Rect={kind:'rect';宽度:数字;高度:数字};类型Shape=Circle|Rect;functiongetArea(shape:Shape){returnshape.kind==='circle'?Math.PI*shape.radius**2:shape.width*shape.height;}类型缩小消除了强制转换的需要。使用类型谓词来避免类型断言如果你正确使用TypeScript,你应该很少会发现自己使用显式类型断言(例如valueasSomeType);但是,有时您仍然有这样做的冲动,例如:typeCircle={kind:'circle';radius:number};typeRect={kind:'rect';宽度:数字;高度:数字};类型Shape=Circle|Rect;functionisCircle(shape:Shape){returnshape.kind==='circle';}functionisRect(shape:Shape){returnshape.kind==='rect';}constmyShapes:Shape[]=getShapes复制代码();//错误,因为typescript不知道如何过滤常量圆:Circle[]=myShapes.filter(isCircle);//你可能想添加一个断言//constcircles=myShapes.filter(isCircle)asCircle[];更优雅的解决方案是将isCircle和isRect更改为返回类型谓词,以便它们可以帮助Typescript在调用过滤器后进一步缩小类型。functionisCircle(shape:Shape):shapeisCircle{返回形状。kind==='circle';}functionisRect(shape:Shape):shapeisRect{返回形状。kind==='rect';}...//现在你得到正确推断的Circle[]类型constcircles=myShapes.filter(isCircle);控制联合类型的分布方式类型推断是Typescript的本能;大多数时候,它默默地工作。但是,在模棱两可的情况下,我们可能需要进行干预。分配条件类型就是其中之一。假设我们有一个ToArray辅助类型,如果输入类型不是数组,它会返回一个数组类型。输入ToArray=TextendsArray?T:T[];对于以下类型,您认为应该如何推断?输入Foo=ToArray;答案是字符串[]|数字[]。但这是模棱两可的。为什么不是(string|number)[]?默认情况下,当typescript遇到联合类型(此处为string|number)的泛型参数(此处为T)时,它会分配给每个组成元素,这就是为什么您在这里得到string[]|number[]的原因。可以通过使用特殊语法并用一对[]包装T来更改此行为,例如。输入ToArray=[T]extends[Array]?T:T[];输入Foo=ToArray;Foo现在被推断为(string|number)[]类型,使用详尽的方式检查在编译时捕获未处理的情况当切换大小写枚举时,积极地错误处理不需要的情况比像在其他编程中那样默默地忽略它们更好语言:functiongetArea(shape:Shape){switch(shape.kind){case'circle':returnMath.PI*shape.radius**2;case'rect':returnshape.width*shape.height;默认值:thrownewError('Unknownshapekind');}}使用Typescript,您可以通过使用never类型让静态类型检查提前为您发现错误:半径**2;case'rect':returnshape.width*shape.height;default://如果上面没有处理任何shape.kind//你会得到一个类型检查错误。const_exhaustiveCheck:never=shape;thrownewError('Unknownshapekind');}}有了这个,就不可能忘记在添加新的形状种类时更新getArea函数。这种技术背后的基本原理是never类型不能分配给任何东西,但never。如果所有shape.kind候选者都被case语句耗尽,则唯一可能达到默认的类型是never;但是,如果有任何候选人未被覆盖,它就会泄漏到默认分支中,从而导致分配无效。类型优先于接口在TypeScript中,类型和接口结构在用于类型化对象时是相似的。尽管可能存在争议,但我的建议是在大多数情况下始终如一地使用类型,并且仅在以下情况之一为真时才使用接口:您想利用接口的“合并”功能。您的代码遵循面向对象的风格,具有类/接口层次结构。否则,始终使用更通用的类型结构会使代码更加一致。在适当的时候优先使用元组而不是数组对象类型是输入结构化数据的常见方式,但有时您可能希望有更多的表示形式并改用简单的数组。比如我们的Circle可以这样定义:typeCircle=(string|number)[];constcircle:Circle=['circle',1.0];//[kind,radius]但是这种类型检查太松散了,我们创建类似['circle','1.0']这样的东西很容易出错。我们可以使用元组使其更严格:typeCircle=[string,number];//你会在这里得到一个错误constcircle:Circle=['circle','1.0'];元组用法的一个很好的例子是ReactuseState:const[name,setName]=useState('');它既紧凑又类型安全。控制推断类型的通用性或特异性Typescript在进行类型推断时使用合理的默认行为,目的是使为常见情况编写代码变得容易(因此不需要显式注释类型)。有几种方法可以调整它的行为。使用const缩小到最具体的类型letfoo={name:'foo'};//typed:{name:string}letBar={name:'bar'}asconst;//输入:{name:'bar'}leta=[1,2];//输入:number[]letb=[1,2]asconst;//typed:[1,2]//typed{kind:'circle;radius:number}letcircle={kind:'circle'asconst,radius:1.0};//如果circle没有用const关键字初始化,下面的代码将不起作用letshape:{kind:'circle'|'rect'}=圆圈;使用满足来检查类型而不影响推断类型考虑以下示例:typeNamedCircle={radius:number;名称?:字符串;};constcircle:NamedCircle={radius:1.0,name:'yeah'};//错误,因为circle.name可以是未定义的console.log(circle.name.length);我们遇到了一个错误,因为根据circle的声明类型NamedCircle,name字段可能确实是未定义的,即使变量初始值提供了字符串值。当然,我们可以删除:NamedCircle类型注释,但是我们将失去对circle对象有效性的类型检查。相当的困境。幸运的是,Typescript4.9引入了一个新的satisfies关键字,它允许您在不更改推断类型的情况下检查类型。输入NamedCircle={半径:数字;name?:string;};//错误,因为半径违反了NamedCircleconstwrongCircle={radius:'1.0',name:'ha'}满足NamedCircle;constcircle={radius:1.0,name:'yeah'}满足NamedCircle;//circle.name不能未定义nowconsole.log(circle.name.length);修改后的版本享有这两个好处:保证对象文字符合NamedCircle类型,并且推断的Type具有不可为null的名称字段。使用infer创建额外的泛型类型参数在设计实用函数和类型时,我们经常觉得需要使用从给定类型参数中提取的类型。在这种情况下,infer关键字非常方便。它帮助我们实时推断新的类型参数。这里有两个简单的例子://从Promise中获取展开的类型//如果T不是PromisetypeResolvedPromise=TextendsPromise是幂等的?U:T;typet=ResolvedPromise>;//t:string//获取数组T的展平类型;//如果T不是数组类型,则幂等Flatten=TextendsArray?Flatten:T;typee=Flatten;//e:numberTextendsPromiseinfer关键字的工作方式可以理解为:假设T兼容一些实例化的通用Promise类型,即时创建类型参数U使其工作。因此,如果T被实例化为Promise,则U的分辨率将为字符串。通过对类型操作进行创造性处理来保持DRY(无重复)Typescript提供了强大的类型操作语法和一组非常有用的工具,可帮助您最大程度地减少代码重复。不是重复声明:typeUser={age:number;性别:字符串;国家:字符串;城市:字符串};typeDemographic={年龄:数字:性别:字符串;};类型Geo={国家:字符串;城市:字符串;};相反,使用Pick工具提取新类型:typeUser={age:number;性别:字符串;国家:字符串;city:string};typeDemographic=Pick;typeGeo=Pick;不是重复函数的返回类型functioncreateCircle(){return{kind:'circle'asconst,radius:1.0}}functiontransformCircle(circle:{kind:'circle';radius:number}){...}transformCircle(createCircle());而是使用ReturnType来提取它:));没有并行同步两种类型的形状(这里是typeofconfig和Factory)。类型ContentTypes='新闻'|'博客'|'video';//用于指示启用哪些内容类型的配置constconfig={news:true,blog:true,video:false}满足Record;//用于创建内容类型的工厂Factory={createNews:()=>内容;createBlog:()=>内容;};相反,使用映射类型和模板文字类型根据配置的形状自动推断出适当的工厂类型。类型ContentTypes='新闻'|'博客'|'video';//具有推断方法列表的通用工厂类型//基于给定Configtype的形状ContentFactory>={[kinstring&keyofConfigasConfig[k]延伸真?`create${Capitalize}`:never]:()=>Content;};//用于指示启用哪些内容类型的配置constconfig={news:true,blog:true,video:false}满足Record;typeFactory=ContentFactory;//工厂:{//createNews:()=>Content;//createBlog:()=>Content;//}摘要本文涵盖了Typescript语言中一组相对高级的主题。在实践中,您可能会发现直接使用它们并不常见;然而,这些技术被专门为Typescript设计的库大量使用:例如Prisma和tRPC。了解这些技巧可以帮助您更好地理解这些工具的工作原理。编辑过程中可能存在的BUG无法实时获知。之后为了解决这些bug,花费了大量的时间在日志调试上。顺便推荐一个好用的BUG监控工具Fundebug。原文:https://dev.to/zenstack/11-ti...交流有梦想,有干货,微信搜索【大招走向世界】关注这位早期还在洗碗的洗碗智慧早晨。本文已收录到GitHubhttps://github.com/qq449245884/xiaozhi,里面有完整的测试站点、资料和我的一线厂商访谈系列文章。