当前位置:首页 > 应用开发

用React和Typescript编写纯净代码的十一种必备模式

不知您是编备模否知晓,由于JavaScript是写纯一种松散类型的语言,而且需要缓存运行时(runtime),净代因此会造成错误滞后被发现,种必进而导致严重的编备模后果。而作为一个知名的写纯JavaScript库,React虽然已是净代当今最流行、且行业领先的种必前端开发库了。它也继承了此类问题。编备模

对此,写纯有人提出了纯净代码的净代概念。它是种必一种旨在提高软件代码的质量和可维护性,以读者为中心的编备模一致性编程风格。我们常说,写纯任何人都可以编写出能让计算机理解的净代代码,但只有优秀的开发人员才能够通过清晰简洁的设计模式,编写出易于人类阅读、理解、修改和维护的纯净代码,亿华云计算以降低软件的开发成本,并消除技术债务。

下面,我们将介绍十一种在使用React和TypeScript编写纯净代码时,必备且实用的模式。

1. 使用默认方式导入React

请导入如下代码:

import * as React from "react";

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/c95fa549af8a958fd074324fcfb6f73f }

上述代码既简单又粗暴。如果我们不想使用React的所有内容的话,就没有必要如此,而应当采用如下更好的默认导入模式:

import React, { useContext, useState} from "react";

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/c6de59538119ee33d9c2e71c64620a56 }

使用这种方法,我们可以从模块中按需解构出React内容,而不必导入所有的组件。当然,值得注意的是:在使用该导入方式之前,我们需要按照如下方式配置tsconfig.json文件:

{

"compilerOptions": {

"esModuleInterop": true

}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/d704140bbe003cd05be31b6ae7120468 }

在上述配置代码段中,我们通过将esModuleInterop设置true,以启用​​[allowSyntheticDefaultImports]​​。它会让TypeScript能够支持我们的语法。

2. 在运行时的实现之前声明类型

请看如下的代码:

import React, { Component} from "react";

const initialState = { count: 1 }

const defaultProps = { name: "John Doe" }

type State = typeof initialState;

type Props = { count?: number } & typeof defaultProps

class Counter extends Component {

static defaultProps = defaultProps;

state = initialState;

// ...

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/f7b5daad358c0e9f3623baa83474ab01 }

由于我们将运行时与编译时(compile-time)的高防服务器声明区分开来,因此上面的代码段看上去更加清晰、易读。此类声明类型被称为——编译类型的优先声明。

让我们再来看如下的代码:

import React, { Component} from "react";

type State = typeof initialState;

type Props = { count?: number } & typeof defaultProps

const initialState = { count: 1 }

const defaultProps = { name: "John Doe" }

class Counter extends Component {

static defaultProps = defaultProps;

state = initialState;

// ...

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/317170a0b580b92738a94fce7197be17 }

我们从代码的第一行就能够清晰地看到,开发人员已经知晓了对应的API。接着,我们就将编译时与运行时的声明区分开来了。

3. 始终为Children Prop(子属性)提供显式类型

在React.d.ts中,TypeScript需要将函数组件和类组件的Children Prop予以注解,以展示React是如何处理Children Prop的。对此,我们有必要为Children Prop显式地提供一个类型,以便将“children”用于内容映射的场景中。当然,如果我们的组件无需使用内容映射的话,则可以简单地用never类型予以注释。请参考如下代码段:

import React, { Component} from "react";

// Card.tsx

type Props = {

children: import(react).ReactNode

}

class Card extends Component{

render() {

const { children} = this.props;

return

{ children};

}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/45672ae71f63904fa7f6c88ec5a90e75 }

下面是一些用于注释Children Prop的云服务器其他有效类型:

ReactNode | ReactChild | ReactElement对于原语,我们可以使用string | number | boolean对象和数组也是有效的类型never | null | undefined (注意:我们并不推荐使用null和undefined)4. 使用类型推断来定义组件状态或DefaultProps

请参考如下代码段:

import React, { Component} from "react";

type State = { count: number };

type Props = {

someProps: string & DefaultProps;

}

type DefaultProps = {

name: string

}

class Counter extends Component{

static defaultProps: DefaultProps = { name: "John Doe"}

state = { count: 0}

// ...

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/05c4c6b8ff5930776a0febab60ce53f8 }

虽然上述代码可以被顺利执行,但我们有必要对其进行重构和改进,以便TypeScript的类型系统能够正确地推断出readonly类型(如DefaultProps和initialState),进而防止开发人员意外地设置状态:this.state = { },而引起错误。请参见如下代码:

import React, { Component} from "react";

const initialState = Object.freeze({ count: 0 })

const defaultProps = Object.freeze({ name: "John Doe"})

type State = typeof initialState;

type Props = { someProps: string } & typeof defaultProps;

class Counter extends Component{

static readonly defaultProps = defaultProps;

readonly state = { count: 0}

// ...

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/6e4ea2442091e86b3903738d21f0b613 }

在上述代码中,通过冻结DefaultProps和initialState,TypeScript类型系统可以将它们推断为Readonly类型。可见,通过对静态DefaultProps和其类中的Readonly状态予以标记,我们消除了上文提到的,由设置状态可能引起的运行时错误。

5. 使用类型别名而不是接口(interface)来声明属性和状态

虽然我们可以使用interface,但是为了确保清晰的一致性,并应对无法使用interface的场景,我们应当使用类型别名。例如,在前面的示例中,我们通过重构代码,使得TypeScript的类型系统,能够从实现中定义状态类型,进而正确地推断出只读类型。而在如下代码中,我们就无法针对其模式使用interface:

// works

type State = typeof initialState;

type Props = { someProps: string } & typeof defaultProps;

// throws error

interface State = typeof initialState;

interface Props = { someProps: string } & typeof defaultProps;

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/74b89d92471cdadbba52065a87334549 }

此外,在不能使用由unions和intersection创建的types,去扩展interface时,我们也必须使用type的别名。

6. 不要在接口/类型别名中使用方法声明

通常,只有所有的类型和推理要素都以相同的方式被声明时,我们才能确保代码中的模式一致性。然而,--strictFunctionTypes参数只能有效地比较两个函数,而非方法。对此,你可以通过链接--https://github.com/Microsoft/TypeScript/issues/25296#issuecomment-401517062,来进一步了解有关Typescript相关问题的解释。当然,您也可以参考如下代码段:

// Dont do this

interface Counter {

start(count:number) : string

reset(): void

}

// Do

interface Counter {

start: (count:number) => string

reset: () => string

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/63cc5145646f5f02c4e28e36bd8af926 }

7. 不要使用FunctionComponent

请不要使用FunctionComponent (简写 FC ),来定义某个函数组件。通常,我们在将TypeScript与React一起使用时,对应的函数式组件可以被写成如下两种方式:

(1)常规性功能代码:

type Props = { message: string };

const Greeting = ({ message }: Props) => <div>{ message}</div>;

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/276112f9e5ed21c69ba02ffec755a7e1 }

(2)使用React.FC或React.FunctionComponent的代码段(如下所示):

import React, { FC} from "react";

type Props = { message: string };

const Greeting: FC<Props> = (props) => <div>{ props}</div>;

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/310dd40107547a3d3ed08ae782f767cf }

可见,使用FC的优点包括:针对displayName、propTypes和DefaultProps等静态属性,提供了类型检查和自动完成。不过,根据经验,它对propTypes、contextTypes、以及displayName的defaultProps,可能会造成问题。此外,FC为Children Prop提供的隐式类型,也存在着一些已知的问题。当然,如前所述,组件API本来就应该是显式的,因此我们没有必要将Children Prop设置为隐式类型。

8. 不要将构造函数用于类组件

请使用新的类字段建议(请参考--https://github.com/tc39/proposal-class-fields#consensus-in-tc39),而不必在JavaScript类中使用构造函数。毕竟,使用构造函数会涉及调用super()和传递props,这些都会引入不必要的板模式(plate)和复杂性。

我们可以使用如下代码段中的类字段,来编写更简洁、更易于维护的React类组件:

// Dont do

type State = { count: number}

type Props = { }

class Counter extends Component{

constructor(props:Props){

super(props);

this.state = { count: 0}

}

}

// Do

type State = { count: number}

type Props = { }

class Counter extends Component{

state = { count: 0}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/ddd9bb947f736794db1d85d8b560f1f0 }

由上述代码可知,我们在使用类字段时,涉及到的boilerplate(锅炉板模式)越少,待处理的this变量也就越少。

9. 不要在类中使用公共访问器(Public Accessor)

让我们来看如下代码:

import { Component } from "react"

class Friends extends Component {

public fetchFriends () { }

public render () {

return // jsx blob

}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/96e8c072ad43426b7306e77f1a462e4d }

在上述类中,由于public的所有元素的运行时都是默认的,因此我们无需通过显式使用public关键字,来添加额外的boilerplate文件,只需如下模式即可:

import { Component } from "react"

class Friends extends Component {

fetchFriends () { }

render () {

return // jsx blob

}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/87da70d3cef765b652e9532b98b52921 }

10. 不要在组件类中使用私有访问器

让我们再来看如下代码:

import { Component} from "react"

class Friends extends Component {

private fetchProfileByID () { }

render () {

return // jsx blob

}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/8e31307e752e5f6b93c901556bd1dfc1 }

在上面的代码中,私有访问器仅在编译时,将fetchProfileByID方法私有化(private)。而在运行时,fetchProfileByID方法仍然是公共的。

目前,我们有多种方法可以将JavaScript类中的属性和方法设定为私有。下面的代码段展示了其中的一种--使用下划线 (_) 的命名规则:

import { Component} from "react"

class Friends extends Component {

_fetchProfileByID () { }

render () {

return // jsx blob

}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/d69d76d7c0c3c10c6e95af5354b0da2b }

虽然上述方法并没有真正使得fetchProfileByID成为私有方法,但它很好地向其他开发人员传达了我们的意图,即:任何指定的方法应该被视为私有方法。弱映射(weakmap)、符号和作用域变量(scoped variable)都是如此。正如下面的代码段所示,我们可以通过新的​​ECMAScript类字段​​​的“建议”,使用各种​​私有字段​​,来轻松地达到此目的:

import { Component} from "react"

class Friends extends Component {

#fetchProfileByID () { }

render () {

return // jsx blob

}

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/594c426e763afe26c12badf60432831e }

当然,值得注意的是,您需要使用TypeScript3.8及更高版本,才能支持私有字段的相关语法。

11. 不要使用枚举(enum)

虽然enum是JavaScript中的保留字,但是使用enum并非标准化的JavaScript惯用模式。鉴于枚举已在C#和Java之类的编程语言中被广泛使用,您可以在此按照如下代码方式,使用编译类型的表述方式:

// Dont do this

enum Response {

Successful,

Failed,

Pending

}

function fetchData (status: Response): void => {

// some code.

}

// Do this

type Response = Sucessful | Failed | Pending

function fetchData (status: Response): void => {

// some code.

}

{ 具体请参见GitHub的Gist链接--https://gist.github.com/lawrenceagles/2dd0ecbd8d54ceae715feedc81d445cb }

小结

毫无疑问,使用TypeScript会为您的代码添加许多额外的boilerplate,不过总体说来仍然是利大于弊的。希望上述介绍的十一种有关React和TypeScript应用的最佳实践和JavaScript惯用模式,能够让您的代码更加清晰、更加易被维护。

原文标题:10 Must-Know Patterns for Writing Clean Code With React and Typescript,作者: Alex Omeyer

分享到:

滇ICP备2023006006号-16