当前位置: 首页 > 科技观察

用React和Typescript编写干净代码的十一个必备模式_1

时间:2023-03-19 00:03:40 科技观察

不知道大家知不知道,因为JavaScript是一种松散类型的语言,需要缓存运行时(runtime),所以会造成检测滞后的错误,然后导致严重的后果。React作为知名的JavaScript库,是当今最流行、行业领先的前端开发库。它也继承了这样的问题。对此,有人提出了纯代码的概念。它是一种一致的、以读者为中心的编程风格,旨在提高软件代码的质量和可维护性。我们常说任何人都可以编写计算机可以理解的代码,但只有优秀的开发人员才能通过清晰简洁的设计模式编写出易于人类阅读、理解、修改和维护的干净代码,以降低软件成本。降低开发成本并消除技术债务。下面,我们将介绍使用React和TypeScript编写干净代码时必要且实用的11种模式。1.使用默认方式导入React,请导入如下代码:import*asReactfrom"react";{详情请参考GitHub上的Gist链接--https://gist.github.com/lawrenceagles/c95fa549af8a958fd074324fcfb6f73f}以上代码简单粗暴。如果我们不想使用React的所有内容,也没有必要这样做,而是应该采用更好的默认导入方式,如下:importReact,{useContext,useState}from"react";{详情,请参考GitHub的Gist链接--https://gist.github.com/lawrenceagles/c6de59538119ee33d9c2e71c64620a56}使用这种方法,我们可以根据需要从模块中解构React内容,而无需导入所有组件。当然,值得注意的是,在使用这种导入方式之前,我们需要对tsconfig.json文件进行如下配置:GitHub--https://gist.github.com/lawrenceagles/d704140bbe003cd05be31b6ae7120468}在上面的配置片段中,我们通过将esModuleInterop设置为true来启用[allowSyntheticDefaultImports]。它将允许TypeScript支持我们的语法。2.请看下面代码在runtime实现之前声明类型:importReact,{Component}from"react";constinitialState={count:1}constdefaultProps={name:"JohnDoe"}typeState=typeofinitialState;typeProps={count?:number}&typeofdefaultPropsclassCounterextendsComponent{staticdefaultProps=defaultProps;状态=初始状态;//...}{有关详细信息,请参阅GitHub的Gist链接--https://gist.github.com/lawrenceagles/f7b5daad358c0e9f3623baa83474ab01}上面的代码片段看起来更清晰易读,因为我们将运行时与编译分开-时间声明。此类声明的类型称为编译类型的前置声明。我们再看下面的代码:importReact,{Component}from"react";typeState=typeofinitialState;typeProps={count?:number}&typeofdefaultPropsconstinitialState={count:1}constdefaultProps={name:"JohnDoe"}classCounterextendsComponent{staticdefaultProps=defaultProps;状态=初始状态;//...}{详情请参考GitHubGist链接--https://gist.github.com/lawrenceagles/317170a0b580b92738a94fce7197be17}我们从第一行代码可以明显看出开发者已经知道相应的API。接下来,我们区分编译时和运行时声明。3.始终为ChildrenProp(子属性)提供显式类型在React.d.ts中,TypeScript需要对函数组件和类组件的ChildrenProp进行注解,以展示React是如何处理ChildrenProp的。为此,需要明确的为ChildrenProp提供一个类型,以便在内容映射场景中可以使用“children”。当然,如果我们的组件不需要使用contentmapping,可以简单的注解为never类型。请参考以下代码片段:importReact,{Component}from"react";//Card.tsxtypeProps={children:import('react').ReactNode}classCardextendsComponent{render(){const{children}=this.props;返回

{children}
;}}{详情请参考GitHub的Gist链接--https://gist.github.com/lawrenceagles/45672ae71f63904fa7f6c88ec5a90e75反应儿童|ReactElement对于原语我们可以使用string|编号|boolean对象和数组也是有效类型空|undefined(注意:我们不建议使用null和undefined)4。使用类型推断定义组件状态或DefaultProps,请参考以下代码片段:importReact,{Component}from"react";typeState={count:number};typeProps={someProps:string&DefaultProps;}typeDefaultProps={name:string}classCounterextendsComponent{staticdefaultProps:DefaultProps={name:"JohnDoe"}state={count:0}//...}{详情请参考GitHubGist链接--https://gist.github.com/lawrenceagles/05c4c6b8ff5930776a0febab60ce53f8}虽然上面的代码可以顺利执行,但是我们还是有必要对其进行重构和改进,让TypeScript的类型系统能够正确推断出只读类型(比如DefaultProps和initialState),从而防止开发者不小心设置state:this.state={},错误请参考以下代码:importReact,{Component}from"react";constinitialState=Object.freeze({count:0})constdefaultProps=Object.freeze({name:"JohnDoe"})typeState=typeofinitialState;typeProps={someProps:string}&typeofdefaultProps;classCounterextendsComponent{staticreadonlydefaultProps=defaultProps;只读状态={count:0}//...}{SeetheGistlinkonGitHubfordetails--https://gist.github.com/lawrenceagles/6e4ea2442091e86b3903738d21f0b613}在上面的代码中,通过冻结DefaultProps和initialState,TypeScript类型系统可以将它们推断为Readonly类型。可以看出,通过在静态DefaultProps及其类中标记Readonly状态,我们已经排除了上述设置状态可能导致的运行时错误。5.使用类型别名代替接口来声明属性和状态虽然我们可以使用接口,但为了保证清晰的一致性和应对不能使用接口的场景,我们应该使用类型别名。例如,在前面的示例中,我们重构了代码,以便TypeScript的类型系统正确地从实现定义的状态类型中推断出只读类型。在下面的代码中,我们不能为其模式使用接口://workstypeState=typeofinitialState;typeProps={someProps:string}&typeofdefaultProps;//throwserrorinterfaceState=typeofinitialState;interfaceProps={someProps:string}&typeofdefaultProps;{具体可以参考GitHub的Gist链接——https://gist.github.com/lawrenceagles/74b89d92471cdadbba52065a87334549}另外,当union和intersection创建的类型不能用来扩展接口时,我们还An必须使用类型的别名。6.不要在接口/类型别名中使用方法声明通常,只有以相同的方式声明所有类型和推理元素,才能确保代码中模式的一致性。但是,--strictFunctionTypes参数只能有效地比较两个函数,而不是方法。对此,您可以点击链接-https://github.com/Microsoft/TypeScript/issues/25296#issuecomment-401517062了解更多关于Typescript相关问题的解释。当然你也可以参考下面的代码片段://Don'tdothisinterfaceCounter{start(count:number):stringreset():void}//DointerfaceCounter{start:(count:number)=>stringreset:()=>string}{详情请参考GitHub的Gist链接--https://gist.github.com/lawrenceagles/63cc5145646f5f02c4e28e36bd8af926}7.不要使用FunctionComponent请不要使用FunctionComponent(abbreviatedasFC)来定义一个功能组件。通常我们在React中使用TypeScript时,对应的功能组件可以写成以下两种方式:(1)常规功能代码:typeProps={message:string};constGreeting=({message}:Props)=>
{message}
;{详情请参考GitHub的Gist链接--https://gist.github.com/lawrenceagles/276112f9e5ed21c69ba02ffec755a7e1}(2)使用React.FC或React.FunctionComponent代码片段(如下所示):importReact,{FC}from"react";typeProps={message:string};constGreeting:FC=(props)=>
{props}
;{具体可以参考GitHub的Gist链接--https://gist.github.com/lawrenceagles/310dd40107547a3d3ed08ae782f767cf}可见使用FC的优势include:针对displayName、propTypes和DefaultProps等静态属性,类型检查和自动补全。但是,根据经验,它可能会导致propTypes、contextTypes和defaultProps出现问题。此外,FC为ChildrenProp提供的隐式类型也存在一些已知问题。当然,如前所述,组件API应该是显式的,所以我们不需要将ChildrenProp设置为隐式类型。8.类组件不要使用构造器请使用新的类字段提案(请参考--https://github.com/tc39/proposal-class-fields#consensus-in-tc39)而不是在JavaScript类中使用构造函数。毕竟,使用构造函数会涉及调用super()和传递props,这会引入不必要的车牌图案和复杂性。我们可以使用以下代码片段中的类字段来编写更简洁和可维护的React类组件://Don'tdotypeState={count:number}typeProps={}classCounterextendsComponent{构造函数(道具:道具){超级(道具);this.state={count:0}}}//DotypeState={count:number}typeProps={}classCounterextendsComponent{state={count:0}}{详情请参考到GitHub的Gist链接--https://gist.github.com/lawrenceagles/ddd9bb947f736794db1d85d8b560f1f0}从上面的代码可以看出,当我们使用class字段时,boilerplate(样板模式)this变量越少过程。9.不要在类中使用公共访问器(PublicAccessor)让我们看下面的代码:}}{具体可以参考GitHub的Gist链接--https://gist.github.com/lawrenceagles/96e8c072ad43426b7306e77f1a462e4d}在上面的类中,由于public所有元素的runtime都是默认的,我们不需要要通过显式使用public关键字添加额外的样板文件,只需要以下模式:import{Component}from"react"classFriendsextendsComponent{fetchFriends(){}render(){return//jsxblob}}{specific请参阅GitHub上的Gist链接--https://gist.github.com/lawrenceagles/87da70d3cef765b652e9532b98b52921}10.不要在组件类中使用私有访问器让我们再看看下面的代码:import{Component}from"react"classFriendsextendsComponent{privatefetchProfileByID(){}render(){return//jsxblob}}{详情请参考GitHub的Gist链接--https://gist.github.com/lawrenceagles/8e31307e752e5f6b93c901556bd1dfc1}在上面的代码中,私有访问器仅在编译时将fetchProfileByID方法设为私有。在运行时,fetchProfileByID方法保持公开。目前,我们有几种方法可以将JavaScript类中的属性和方法设为私有。下面的代码片段展示了其中之一——使用下划线(_)命名约定:GitHubGist链接了解详情——https://gist.github.com/lawrenceagles/d69d76d7c0c3c10c6e95af5354b0da2b}虽然上述方法并没有真正使fetchProfileByID私有化,但它很好地将我们的意图传达给其他开发人员,即:任何指定的方法应被视为私有方法。这对于weakmaps、symbols和作用域变量都是正确的。正如下面的代码片段所示,我们可以通过新的ECMAScript类字段“建议”使用各种私有字段轻松实现这一点:/jsxblob}}{详情请参考GitHub的Gist链接——https://gist.github.com/lawrenceagles/594c426e763afe26c12badf60432831e}当然,值得注意的是需要使用TypeScript3.8及以上版本才能支持私有字段的相关语法。11.不要使用枚举(enum)虽然enum是JavaScript中的保留字,但是使用enum并不是一个标准化的JavaScript习语。由于枚举在C#、Java等编程语言中已经被广泛使用,所以可以使用编译型的表达方式如下://Don'tdothisenumResponse{Successful,Failed,Pending}functionfetchData(status:Response):void=>{//一些代码。}//执行此类型Response=Sucessful|失败|PendingfunctionfetchData(status:Response):void=>{//somecode.}{SeeGitHubfordetailsGistlink--https://gist.github.com/lawrenceagles/2dd0ecbd8d54ceae715feedc81d445cb}总结毫无疑问,使用TypeScript将在您的代码中添加大量额外的样板文件,但总的来说利大于弊。希望上面介绍的React和TypeScript应用的11个最佳实践和JavaScript惯用模式能够让你的代码更清晰,更容易维护。原标题:10Must-KnowPatternsforWritingCleanCodeWithReactandTypescriptbyAlexOmeyer