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,您可以通過兩種方式定義新元件:作為函數或作為類。 這兩種元件的類型為:
- Component Classes ::
React.ComponentClass<
;P>
元件類::
; P>React.ComponentClass<
- Stateless Functional Components (SFC) ::
React.StatelessComponent<
;P>
無狀态功能元件(SFC)::
; P>React.StatelessComponent<
元件類别 (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
)是一個函數,它接受屬性對象,子元件的可選清單以及可選的上下文對象。 它傳回一個
ReactElement
或
null
。
Despite what the name may suggest, a
StatelessComponent
does not have a relationship to a
Component
type.
盡管名稱可能暗示,但
StatelessComponent
與
Component
類型沒有關系。
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 of
shouldComponentUpdate
function (which does a shallow compare of
this.state
and
this.props
).
PureComponent
是一個元件,它提供了
shouldComponentUpdate
函數的預設實作(該函數對
this.state
和
this.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指南 ,但進行了以下小的修改:
- The
setState
function uses a callback to update state based on the previous state as per the React documentation.
根據React文檔,
函數使用回調根據先前的狀态更新狀态。setState
- We derive from
React.PureComponent
because it does not override the lifecycle functions
我們從
派生,因為它沒有覆寫生命周期函數React.PureComponent
- The
type is defined as a class so that it can have an initializer.State
類型定義為一個類,以便可以具有初始化程式。State
-
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.
高階元件可能會根據其定義方式傳回
StatelessComponent
或
ComponentClass
。
出口違約的困惑 (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:
來自material-ui:withStyles
withStyles
- From redux-form:
從redux-form:reduxForm
reduxForm
- From react-intl:
從react-intl:injectIntl
injectIntl
- From react-redux:
從react-redux:connect
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:
是以,忽略可選的合并功能參數,我們可能會連接配接兩個參數:
-
mapStateToProps
mapStateToProps
-
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:
這給我們留下了兩個選擇:
- We can split up the interface into the inner properties of
and another for themapStateToProps
mapDispatchToProps
.
我們可以分裂的界面進入内性能
,另一個用于mapStateToProps
。mapDispatchToProps
-
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
ormapStateToProps
mapDispatchToProps
functions, we just let the compiler infer them.
我們沒有為
或mapStateToProps
函數提供類型,我們隻是讓編譯器來推斷它們。mapDispatchToProps
- We define both
andmapStateToProps
as arrow functions assigned tomapDispatchToProps
const
variables.
我們将
和mapStateToProps
都定義為配置設定給mapDispatchToProps
變量的箭頭函數。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.
不得将在
mapStateToProps
和
mapDispatchToProps
中連接配接到商店的屬性聲明為可選,否則您會在推斷的類型中得到類型錯誤。
最後的話 (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開始,那麼以下指南将非常有用:
-
Microsoft TypeScript React Starter
Microsoft TypeScript React Starter
-
Microsoft’s TypeScript React Conversion Guide
Microsoft的TypeScript React轉換指南
-
Lyft’s React JavaScript to TypeScript Converter
Lyft的React JavaScript到TypeScript轉換器
After that I recommend reading through the following articles:
之後,我建議您通讀以下文章:
-
React Higher-Order Component Patterns in TypeScript by James Ravenscroft
在TypeScript中React高階元件模式 James Ravenscroft
-
Piotr Witek’s React-Redux TypeScript Guide
Piotr Witek的React-Redux TypeScript指南
緻謝 (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/