使用Typescript和React的最佳实践

by Christopher Diggins

克里斯托弗·迪金斯(Christopher Diggins)

使用Typescript和React的最佳实践 (Best practices for using Typescript with React)

There are numerous tools and tutorials to help developers start writing simple React applications with TypeScript. The best practices for using TypeScript in a larger React application are less clear, however.

有许多工具和教程可帮助开发人员开始使用TypeScript编写简单的React应用程序。 但是,在较大的React应用程序中使用TypeScript的最佳实践尚不清楚。

This is especially the case when intergrating with an ecosystem of third party libraries used to address concerns such as: theming, styling, internationalization, logging, asynchronous communication, state-management, and form management.

与第三方库生态系统集成时尤其如此,该库用于解决诸如主题,样式,国际化,日志记录,异步通信,状态管理和表单管理等问题。

At Clemex, we develop computational microscopy applications. We recently migrated a React front-end for one of our applications from JavaScript to TypeScript. Overall, we are very pleased with the end result. The consensus is that our codebase is now easier to understand and maintain.

Clemex ,我们开发计算显微镜应用程序。 最近,我们将其中一个应用程序的React前端从JavaScript迁移到TypeScript。 总体而言,我们对最终结果感到非常满意。 共识是我们的代码库现在更易于理解和维护。

That said, our transition was not without some challenges. This article dives into some of the challenges we faced and how we overcame them.

也就是说,我们的过渡并非没有挑战。 本文探讨了我们面临的一些挑战以及如何克服这些挑战。

The challenges are primarily related to understanding the type signatures of the React API, and particularly those of higher order components. How can we resolve type errors correctly, while retaining the advantages of TypeScript?

这些挑战主要与理解React API的类型签名有关,尤其是那些高阶组件的类型签名。 如何在保留TypeScript优点的同时正确解决类型错误?

This article attempts to address how to most effectively use TypeScript with React and the ecosystem of supporting libraries. We’ll also address some common areas of confusion.

本文试图解决如何在React和支持库的生态系统中最有效地使用TypeScript。 我们还将解决一些常见的混淆领域。

查找库的类型定义 (Finding type definitions for a library)

A TypeScript program can easily import any JavaScript library. But without type declarations for the imported values and functions, we don’t get the full benefit of using TypeScript.

TypeScript程序可以轻松导入任何JavaScript库。 但是如果没有用于导入的值和函数的类型声明,我们将无法获得使用TypeScript的全部好处。

Luckily, TypeScript makes it easy to define type annotations for JavaScript libraries, in the form of type declaration files.

幸运的是,TypeScript可以轻松地以类型声明文件的形式为JavaScript库定义类型注释。

Only a few projects today offer TypeScript type definitions directly with the project. However, for many libraries you can usually find an up to date type-definition file in the @types organization namespace.

如今,只有少数几个项目直接在项目中提供TypeScript类型定义。 但是,对于许多库,通常可以在@types组织名称空间中找到最新的类型定义文件。

For example, if you look in the TypeScript React Template package.json, you can see that we use the following type-definition files:

例如,如果您查看TypeScript React Template package.json ,则可以看到我们使用了以下类型定义文件:

"@types/jest": "^22.0.1","@types/node": "^9.3.0","@types/react": "^16.0.34","@types/react-dom": "^16.0.3","@types/redux-logger": "^3.0.5"

The only downside of using external type declarations is that it can be a bit annoying to track down bugs which are due to a versioning mismatch, or subtle bugs in type declaration files themselves. The type declaration files aren’t always supported by the original library authors.

使用外部类型声明的唯一缺点是,跟踪由于版本不匹配或类型声明文件本身中的细微错误而导致的bug可能会有些烦人。 原库作者并不总是支持类型声明文件。

属性和状态字段的编译时验证 (Compile-time validation of properties and state fields)

One of the main advantages of using TypeScript in a React application is that the compiler (and the IDE, if configured correctly) can validate all of the necessary properties provided to a component.

在React应用程序中使用TypeScript的主要优点之一是编译器(和IDE,如果配置正确)可以验证提供给组件的所有必要属性。

It can also check that they have the correct type. This replaces the need for a run-time validation as provided by the prop-types library.

它还可以检查它们是否具有正确的类型。 这取代了prop-types库提供的对运行时验证的需求。

Here is a simple example of a component with two required properties:

这是具有两个必需属性的组件的简单示例:

import * as React from ‘react’;
export interface CounterDisplayProps {  value: number;  label: string;}
export class CounterDisplay extends React.PureComponent<CounterDisplayProps> {   render(): React.ReactNode {   return (     <div>       The value of {this.props.label} is {this.props.value}      </div>    );}

组件作为类或函数 (Components as classes or functions)

With React you can define a new component in two ways: as a function or as a class. The types of these two kinds of components are:

使用React,您可以通过两种方式定义新组件:作为函数或作为类。 这两种组件的类型为:

  1. Component Classes :: React.ComponentClass<;P>

    组件类:: React.ComponentClass< ; P>

  2. Stateless Functional Components (SFC) ::React.StatelessComponent<;P>

    无状态功能组件(SFC):: React.StatelessComponent< ; P>

组件类别 (Component Classes)

A class type is a new concept for developers from a C++/C#/Java background. A class has a special type, which is separate from the type of instance of a class. It is defined in terms of a constructor function. Understanding this is key to understanding type signatures and some of the type errors that may arise.

对于C ++ / C#/ Java背景的开发人员来说,类类型是一个新概念。 类具有特殊的类型,该类型与类的实例类型不同。 它是根据构造函数定义的。 理解这一点是理解类型签名和可能出现的某些类型错误的关键。

A ComponentClass is the type of a constructor function that returns an object which is an instance of a Component. With some details elided, the essence of the ComponentClass type definition is:

ComponentClass是构造函数的类型,该函数返回作为Component实例的对象。 省略了一些细节, ComponentClass类型定义的实质是:

interface ComponentClass<P = {}> {  new (props: P, context?: any): Component<P, ComponentState>;}
无状态组件(SFC) (Stateless Components (SFC))

A StatelessComponent (also known as SFC) is a function that takes a properties object, optional list of children components, and optional context object. It returns either a ReactElement or null.

StatelessComponent (也称为SFC )是一个函数,它接受属性对象,子组件的可选列表以及可选的上下文对象。 它返回一个ReactElementnull

Despite what the name may suggest, a StatelessComponent does not have a relationship to a Component type.

尽管名称可能暗示,但StatelessComponentComponent类型没有关系。

A simplified version of the definition of the type of a StatelessComponent and the SFC alias is:

StatelessComponent类型和SFC别名的定义的简化版本是:

interface StatelessComponent<P = {}> {  (props: P & { children?: ReactNode }, context?: any):   ReactElement<any> | null;}
type SFC<P = {}> = StatelessComponent<P>;

Prior React 16, SFCs were quite slow. Apparently this has improved with React 16. However, due to the desire for consistent coding style in our code base, we continue to define components as classes.

在React 16之前的版本中,SFC非常慢。 显然,这已经通过React 16得到了改善 。 但是,由于在我们的代码库中需要一致的编码风格,我们继续将组件定义为类。

纯和非纯成分 (Pure and Non-Pure Components)

There are two different types of Component: pure and non-pure.

有两种不同类型的Component :纯Component和非纯Component

The term ‘pure’ has a very specific meaning in the React framework, unrelated to the term in computer science.

“纯”一词在React框架中具有非常具体的含义,与计算机科学中的术语无关。

A PureComponent is a component that provides a default implementation ofshouldComponentUpdate function (which does a shallow compare of this.stateand this.props).

PureComponent是一个组件,它提供了shouldComponentUpdate函数的默认实现(该函数对this.statethis.props进行了浅层比较)。

Contrary to a common misconception, a StatelessComponent is not pure, and a PureComponent may have a state.

与常见的误解相反, StatelessComponent不是纯的,而PureComponent可能具有状态。

有状态组件可以(并且应该)从React.PureComponent派生 (Stateful Components can (and should) derive from React.PureComponent)

As stated above, a React component with state can still be considered a Pure component according to the vernacular of React. In fact, it is a good idea to derive components, which have an internal state, from React.PureComponent.

如上所述,根据React的说法,状态为React的组件仍可以视为Pure component 。 实际上,从React.PureComponent.派生具有内部状态的组件是一个好主意React.PureComponent.

The following is based on Piotr Witek’s popular TypeScript guide, but with the following small modifications:

以下内容基于Piotr Witek流行的TypeScript指南 ,但进行了以下小的修改:

  1. The setState function uses a callback to update state based on the previous state as per the React documentation.

    根据React文档, setState函数使用回调根据先前的状态更新状态。

  2. We derive from React.PureComponent because it does not override the lifecycle functions

    我们从React.PureComponent派生,因为它没有覆盖生命周期函数

  3. The State type is defined as a class so that it can have an initializer.

    State类型定义为一个类,以便可以具有初始化程序。

  4. We don’t assign properties to local variables in the render function as it violates the DRY principle, and adds unnecessary lines of code.

    我们不会在render函数中为局部变量分配属性,因为它违反了DRY原理,并增加了不必要的代码行。
import * as React from ‘react’;export interface StatefulCounterProps {  label: string;}
// By making state a class we can define default values.class StatefulCounterState {  readonly count: number = 0;};
// A stateful counter can be a React.PureComponentexport class StatefulCounter  extends React.PureComponent<StatefulCounterProps, StatefulCounterState>{  // Define  readonly state = new State();
// Callbacks should be defined as readonly fields initialized with arrow functions, so you don’t have to bind them  // Note that setting the state based on previous state is done using a callback.  readonly handleIncrement = () => {    this.setState((prevState) => {       count: prevState.count + 1 } as StatefulCounterState);  }
// We explicitly include the return type  render(): React.ReactNode {    return (      <div>        <span>{this.props.label}: {this.props.count} </span>        <button type=”button” onClick={this.handleIncrement}>           {`Increment`}        </button>      </div>     );  }}
React无状态功能组件不是纯组件 (React Stateless Functional Components are not Pure Components)

Despite a common misconception, stateless functional components (SFC) are not pure components, which means that they are rendered every time, regardless of whether the properties have changed or not.

尽管有一个普遍的误解,但无状态功能组件(SFC)并不是纯组件,这意味着它们每次都会被渲染,而不管属性是否已更改。

键入高阶组件 (Typing higher-order components)

Many libraries used with React applications provide functions that take a component definition and return a new component definition. These are called Higher-Order Components (or HOCs for short).

React应用程序使用的许多库都提供了接受组件定义并返回新组件定义的函数。 这些被称为高阶组件 (简称HOC )。

A higher-order component might return a StatelessComponent or a ComponentClass depending on how it is defined.

高阶组件可能会根据其定义方式返回StatelessComponentComponentClass

出口违约的困惑 (The confusion of export default)

A common pattern in JavaScript React applications is to define a component, with a particular name (say MyComponent) and keep it local to a module. Then, export by default the result of wrapping it with one or more HOC.

JavaScript React应用程序中的一种常见模式是定义一个具有特定名称的组件(例如MyComponent ),并将其保持在模块本地。 然后,默认情况下导出用一个或多个HOC包装它的结果。

The anonymous component is imported throughout the application as MyComponent. This is misleading because the programmer is reusing the same name for two very different things!

匿名组件作为MyComponent导入整个应用程序。 这具有误导性,因为程序员将相同的名称用于两个截然不同的事物!

To provide proper types, we need to realize that the component returned from a higher-order component is usually not the same type as the component defined in the file.

为了提供适当的类型,我们需要认识到从高阶组件返回的组件通常与文件中定义的组件类型不同。

In our team we found it useful to provide names for both the defined component that is kept local to the file (e.g. MyComponentBase) and to explicitly name a constant with the exported component (e.g. export const MyComponent = injectIntl(MyComponentBase);).

在我们的团队中,我们发现为既保留在文件本地的已定义组件(例如MyComponentBase )提供名称,并使用导出的组件显式命名常量(例如export const MyComponent = injectIntl(MyComponentBase); ) export const MyComponent = injectIntl(MyComponentBase);

In addition to being more explicit, this avoids the problem of aliasing the definition, which makes understanding and refactoring the code easier.

除了更明确之外,这还避免了别名别名的问题,这使理解和重构代码更加容易。

注入属性的HOC (HOCs that Inject Properties)

The majority of HOCs inject properties into your component that do not need to be provided by the consumer of your component. Some examples that we use in our application include:

大多数HOC将不需要用户使用的属性注入到您的组件中。 我们在应用程序中使用的一些示例包括:

  • From material-ui: withStyles

    来自material-ui: withStyles

  • From redux-form: reduxForm

    从redux-form: reduxForm

  • From react-intl: injectIntl

    从react-intl: injectIntl

  • From react-redux: connect

    从react-redux: connect

内部,外部和注入的属性 (Inner, Outer, and Injected Properties)

To better understand the relationship between the component returned from the HOC function and the component as it is defined, try this useful mental model:

为了更好地理解从HOC函数返回的组件与定义的组件之间的关系,请尝试以下有用的思维模型:

Think of the properties expected to be provided by a client of the component as outer properties, and the entirety of the properties visible to the component definition (e.g. the properties used in the render function) as the inner properties. The difference between these two sets of properties are the injected properties.

将期望由组件的客户端提供的属性视为外部属性 ,并将对组件定义可见的整个属性(例如,渲染函数中使用的属性)视为内部属性 。 这两组属性之间的差异是注入的属性

类型交集运算符 (The type intersection operator)

In TypeScript, we can combine types in the way we want to for properties using a type-level operator called the intersection operator (&). The intersection operator will combine the fields from one type with the fields from another type.

在TypeScript中,我们可以使用称为交集运算符 ( & )的类型级运算符,以我们想要的属性组合类型。 相交运算符将合并一种类型的字段和另一种类型的字段。

interface LabelProp {  label: string;}
interface ValueProp {  value: number;}
// Has both a label field and a value fieldtype LabeledValueProp = LabelProp & ValueProp;

For those of you familiar with set theory, you might be wondering why this isn’t considered a union operator. It is because it is an intersection of the sets of all possible values that satisfy the two type constraints.

对于那些熟悉集合论的人,您可能想知道为什么不将其视为联合运算符。 这是因为它是满足两个类型约束的所有可能值的集合的交集。

定义包装组件的属性 (Defining properties for a wrapped component)

When defining a component that will be wrapped with a higher-order component, we have to provide the inner properties to the base type (e.g. React.PureComponent<;P>).

当定义一个将被高阶组件包装的组件时,我们必须为基本类型提供内部属性(例如React.PureComponent< ; P>)。

However, we don’t want to define this all in a single exported interface, because these properties do not concern the client of the component: they only want the outer properties.

但是,我们不想在单个导出的接口中定义所有这些内容,因为这些属性与组件的客户端无关:它们仅需要外部属性。

To minimize boilerplate and repetition, we opted to use the intersection operator, at the single point which we need to refer to inner properties type, which is when we pass it as a generic parameter to the base class.

为了最小化样板和重复,我们选择在需要引用内部属性类型的单点使用交集运算符,这是将其作为通用参数传递给基类的时间。

interface MyProperties {  value: number;}
class MyComponentBase extends React.PureComponent<MyProperties & InjectedIntlProps> {  // Now has intl as a property  // ...}
export const MyComponent = injectIntl(MyComponentBase); // Has the type React.Component<MyProperties>;

React-Redux连接功能 (The React-Redux connect function)

The connect function of the React-Redux library is used to retrieve properties required by a component from the Redux store, and to map some of the callbacks to the dispatcher (which triggers actions which trigger updates to the store).

React-Redux库的connect函数用于从Redux存储中检索组件所需的属性,并将某些回调映射到分派器(这将触发触发更新存储的操作)。

So, ignoring the optional merge function argument, we have potentially two arguments to connect:

因此,忽略可选的合并功能参数,我们可能会连接两个参数:

  1. mapStateToProps

    mapStateToProps

  2. mapDispatchToProps

    mapDispatchToProps

Both of these functions provide their own subset of the inner properties to the component definition.

这两个函数都为组件定义提供了自己的内部属性子集。

However, the type signature of connect is a special case because of the way the type was written. It can infer a type for the properties that are injected and also infer a type for the properties that are remaining.

但是,由于类型的写入方式, connect的类型签名是一种特殊情况。 它可以为注入的属性推断类型,也可以为剩余的属性推断类型。

This leaves us with two options:

这给我们留下了两个选择:

  1. We can split up the interface into the inner properties of mapStateToProps and another for the mapDispatchToProps.

    我们可以分裂的界面进入内性能mapStateToProps ,另一个用于mapDispatchToProps

  2. We can let the type system infer the type for us.

    我们可以让类型系统为我们推断类型。

In our case, we had to convert roughly 50 connected components from JavaScript to TypeScript.

在我们的案例中,我们不得不将大约50个连接的组件从JavaScript转换为TypeScript。

They already had formal interfaces generated from the original PropTypes definition (thanks to an open-source tool we used from Lyft).

他们已经有了从原始PropTypes定义生成的正式接口(这要归功于我们从Lyft使用的开源工具 )。

The value of separating each of these interfaces into outer properties, mapped state properties, and mapped dispatch properties did not seem to outweigh the cost.

将这些接口中的每一个分为外部属性,映射状态属性和映射调度属性的价值似乎并没有超过成本。

In the end, using connect correctly allowed the clients to infer the types correctly. We are satisfied for now, but may revisit the choice.

最后,正确使用connect允许客户端正确推断类型。 我们暂时感到满意,但可以重新考虑选择。

帮助React-Redux连接函数推断类型 (Helping the React-Redux connect function infer types)

The TypeScript type inference engine seems to need at times a delicate touch. The connect function seems to be one of those cases. Now hopefully this isn’t a case of cargo cult programming, but here are the steps we take to assure the compiler can work out the type.

有时似乎需要TypeScript类型推断引擎。 connect功能似乎是其中一种情况。 现在希望这不是对货物崇拜编程的情况,但是这是我们为确保编译器可以计算出类型而采取的步骤。

  • We don’t provide a type to the mapStateToProps or mapDispatchToProps functions, we just let the compiler infer them.

    我们没有为mapStateToPropsmapDispatchToProps函数提供类型,我们只是让编译器来推断它们。

  • We define both mapStateToProps and mapDispatchToProps as arrow functions assigned to const variables.

    我们将mapStateToPropsmapDispatchToProps都定义为分配给const变量的箭头函数。

  • We use the connect as the outermost higher-order component.

    我们将connect用作最外面的高阶组件。

  • We don’t combine multiple higher-order components using a compose function.

    我们不使用compose函数组合多个高阶分量。

The properties that are connected to the store in mapStateToProps and mapDispatchToProps must not be declared as optional, otherwise you can get type errors in the inferred type.

不得将在mapStateToPropsmapDispatchToProps中连接到商店的属性声明为可选,否则您会在推断的类型中得到类型错误。

最后的话 (Final Words)

In the end, we found that using TypeScript made our applications easier to understand. It helped deepen our understanding of React and the architecture of our own application.

最后,我们发现使用TypeScript使我们的应用程序更易于理解。 它有助于加深我们对React和我们自己的应用程序架构的理解。

Using TypeScript correctly within the context of additional libraries designed to extend React required additional effort, but is definitely worth it.

在旨在扩展React的其他库的上下文中正确使用TypeScript需要付出额外的努力,但这绝对是值得的。

If you are just starting off with TypeScript in React, the following guides will be useful:

如果您只是从React中的TypeScript开始,那么以下指南将非常有用:

After that I recommend reading through the following articles:

之后,我建议您通读以下文章:

致谢 (Acknowledgements)

Many thanks to the members of Clemex team for their collaboration on this article, working together to figure out how to use TypeScript to its best potential in React applications, and developing the open-source TypeScript React Template project on GitHub.

非常感谢Clemex团队的成员在本文上的协作,共同努力找出如何在React应用程序中发挥TypeScript的最大潜力,并在GitHub上开发开源TypeScript React Template项目

翻译自: https://www.freecodecamp.org/news/effective-use-of-typescript-with-react-3a1389b6072a/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值