当前位置: 首页 > 工具软件 > React Flow > 使用案例 >

react flow_如何将Flow增量添加到现有React应用

漆雕亮
2023-12-01

react flow

Flow is a static type checker for Javascript. This post is intended for those who have heard of Flow, but have not yet tried to use it within a React app. If this is the first time you have heard of Flow then I can recommend these four posts by Preethi Kasireddy as a great introduction.

Flow是Javascript的静态类型检查器。 这篇文章供那些听说过 Flow但尚未尝试在React应用程序中使用它的人使用。 如果这是您第一次听说Flow,那么我可以推荐Preethi Kasireddy撰写的这四篇文章作为出色的介绍。

One great thing about Flow is that it is possible use it incrementally. You do not have to completely refactor an existing project to start using it. It can be added only to new files, or slowly tried in existing files to see if it provides benefits to your specific project before committing fully.

Flow的一大优点是可以逐步使用它。 您不必完全重构现有项目即可开始使用它。 只能将其添加到新文件中,也可以在现有文件中慢慢尝试它,以便在完全提交之前查看它是否对您的特定项目有所帮助。

As the setup of a new tool can often be the most challenging, in this post we are going to take an existing project and walk through the setup of adding Flow. A general introduction to syntax is covered in the second of Preethi’s posts, and the Flow docs are also very readable.

由于新工具的设置通常是最具挑战性的,因此在本文中,我们将采用现有项目并逐步完成添加Flow的设置。 Preethi的第二篇文章介绍了语法的一般介绍,并且Flow文档也非常易读。

We will be using this example repo, with two directories for pre- and post- Flow. It uses Skyscanner’s custom Create React App script backpack-react-scripts, paired with their custom Backpack components. This is aimed at creating examples more complex than single snippets, yet still readable even if you are unfamiliar with them.

我们将使用这个示例repo ,其中有两个目录用于Flow前后。 它使用Skyscanner的自定义Create React App脚本backpack-react-scripts以及自定义的Backpack组件 。 这样做的目的是创建比单个片段更复杂的示例,即使您不熟悉它们也可以阅读。

The exact nature of the app is unimportant compared to seeing the difference between it’s implementation without and with Flow. Very few files change here, but they are often the most frustrating to get right!

与查看应用程序在 使用 Flow和使用 Flow的实现之间的差异相比,该应用程序的确切性质并不重要。 这里很少更改文件,但通常最令人沮丧的是正确!

Let’s walk through each step, and then take a look at converting the example components.

让我们完成每个步骤,然后看一下如何转换示例组件。

安装主要依赖项 (Install the main dependencies)

Alongside Flow itself install babel-cli and babel-preset-flow so that babel can remove the type annotations on compile.

在Flow本身旁边安装babel-cli和babel-preset-flow,以便babel可以删除编译时的类型注释。

npm install flow-bin babel-cli babel-preset-flow --save-dev

设置通天塔 (Setup Babel)

In order for these to take effect create a .babelrc file, or add to your existing .babelrc the following config:

为了使它们生效,请创建一个.babelrc文件,或将以下配置添加到您现有的.babelrc中:

{
  "presets": ["flow"]
}

安装脚本 (Setup scripts)

If you use any hooks, such as a pretest script, you may want to update these as well as adding the basic Flow script to your package.json:

如果您使用任何钩子(例如预测试脚本),则可能需要更新这些钩子,并将基本的Flow 脚本添加到package.json

"scripts": {
  "flow": "flow",
  "pretest": "npm run flow && npm run lint"
}

生成流程配置 (Generate a flowconfig)

If you are running flow for the first time you can generate a template .flowconfig by running npm run flow init. In our example we can see we extend it to add the following:

如果是第一次运行flow,则可以通过运行npm run flow init生成模板.flowconfig 。 在我们的示例中,我们可以看到我们对其进行了扩展以添加以下内容:

忽略模式 (Ignore patterns)

To avoid Flow parsing your node modules and build output these can easily be ignored.

为了避免Flow解析节点模块并生成输出,可以很容易地忽略它们。

[ignore].*/node_modules/*.*/build/*

添加CSS模块支持 (Add CSS Modules support)

If you are using CSS Modules their type needs to be specified in order for Flow to understand them, or else you will receive this error:

如果您正在使用CSS模块,则需要指定它们的类型,以使Flow能够理解它们,否则您将收到此错误:

This is done in two steps. First the below is added to your .flowconfig:

这分两个步骤完成。 首先,将以下内容添加到您的.flowconfig

[libs]
./src/types/global.js  // this can be any path and filename you wish
[options]
module.name_mapper='^\(.*\)\.scss$' -> 'CSSModule'
module.system=haste

And secondly a CSS Module type is created in the file referenced in [libs].

其次,在[libs] 引用的文件中创建CSS模块类型。

// @flow
declare module CSSModule {
  declare var exports: { [key: string]: string };
  declare export default typeof exports;
}

与其他正在使用的短绒同步 (Sync with other linters being used)

In the example project ESLint is already used to provide standard linting. There are some initial configuration steps needed to get ESLint to play nicely with Flow, and some later ones due to the specific types used in this project.

在示例项目中,ESLint已用于提供标准棉绒。 要使ESLint与Flow完美配合,需要一些初始配置步骤,而由于该项目中使用的特定类型,需要一些后续配置步骤。

For general setup the following is added to our .eslintrc:

对于常规设置,以下内容已添加到我们的.eslintrc

"extends": [
  "plugin:flowtype/recommended"
],
"plugins": [
  "flowtype"
]

Extensions specific to this example, and the errors they avoid, will be covered towards the end of this post.

本示例的特定扩展及其避免的错误将在本文结尾处介绍。

流类型的libdefs (Flow typed libdefs)

The final piece of setup is to get ready for using libdefs created using the flow-typed NPM package. This is used to create definitions for installed node modules, and by default creates these files in a flow-typed/ directory.

最后的设置是准备使用使用flow-typed NPM软件包创建的libdefs 。 这用于为已安装的节点模块创建定义,并且默认情况下在flow-typed/目录中创建这些文件。

We do want to commit this file, but do not want ESLint to lint it. This creates a problem, as previously our linting script in our package.json is set to use our .gitignore to know while files ESLint should also ignore:

我们确实要提交该文件,但不希望ESLint将其文件化。 这就产生了一个问题,因为以前我们在package.json linting脚本被设置为使用.gitignore来知道文件ESLint也应该忽略:

"lint:js": "eslint . --ignore-path .gitignore --ext .js,.jsx",

We now want to change this, as we want ESLint to also ignore the to-be-created flow-typed/ directory. We can alter our script to:

现在,我们要更改此设置,因为我们希望ESLint也忽略要创建的flow-typed/目录。 我们可以将脚本更改为:

"lint:js": "eslint . --ext .js,.jsx",

This means it will now fall back to using a .eslintignore file, so we have to create this, duplicate what is in our .gitignore, and add the extra directory to ignore to it.

这意味着它现在将退回到使用.eslintignore文件,因此我们必须创建此文件,复制.gitignore ,然后添加要忽略的额外目录

Finally, we need to install flow-types. We do this globally.

最后,我们需要安装flow-types 。 我们在全球范围内这样做。

npm install flow-typed -g

libdefs can either be full definitions or stubs that accept any types. A list of full definitions is maintained. To see if there is one available for a package you are using use

libdefs可以是完整定义,也可以是接受任何类型的存根。 保留完整定义的列表。 查看您正在使用的软件包是否有可用的软件包

flow-typed install my-dependency@<version.being.used>

and this will either add it to your flow-typed directory, or prompt you to create a stub using

然后将其添加到您的flow-typed目录中,或提示您使用创建一个存根

flow-typed create-stub my-dependency@<version.being.used>

If you want to create a full definition you can do so, and also contribute it back to the repository so it is available to other developers.

如果您想创建一个完整的定义,则可以这样做,并将其贡献回存储库中,以便其他开发人员可以使用。

A simple process to follow is only to create libdefs as they are specifically required. For each component you are converting to use Flow add its imports using flow-typed at that time, it is not necessary to add types for all dependencies if they are not being used in files where Flow is also being used.

遵循的简单过程仅是创建libdefs因为它们是特别需要的。 对于要转换为使用Flow的每个组件,当时使用flow-typed添加其导入,如果未在同时使用Flow的文件中使用所有依赖项,则不必为所有依赖项添加类型。

转换现有组件 (Converting existing components)

That is all the general setup done, now we can look at converting our example components!

这就是所有常规设置,现在我们可以看看如何转换示例组件!

We have two, a stateful component and a function component. Overall these create a banner than has some text and a button. The text on the banner can be clicked to open a popover, containing a bullet pointed list.

我们有两个,一个有状态组件和一个功能组件。 总体而言,这些横幅创建时会带有一些文本和按钮。 可以单击横幅上的文本以打开一个包含项目符号指向列表的弹出框。

添加流类型的定义 (Add flow-typed definitions)

For any component, the first step is to create flow-typed definitions for any imports in the component we are working in.

对于任何组件,第一步是为正在使用的组件中的任何导入创建flow-typed定义。

For example, if we only had imports of

例如,如果我们只有进口

import React from 'react';
import BpkButton from 'bpk-component-button';

then we would try:

那么我们将尝试:

flow-typed install bpk-component-button@<its.installed.version>

flow-typed install bpk-component-button@<its.installed.versi on>

if it was not available, and it currently is not, then we would stub its definition:

如果它不可用,并且当前不可用,则将其定义存根:

flow-typed create-stub bpk-component-button@latest

flow-typed create-stub bpk-component-button@latest

In the example repo we can see the list of all created definitions for the components we moved to using Flow. These were added one at a time as each component had Flow integrated with them.

在示例存储库中,我们可以看到为使用Flow移动到的组件创建的所有定义列表 。 由于每个组件都集成了Flow,因此一次只能添加一个。

功能组成 (Function Components)

In our example without Flow we use PropTypes for some limited type checking and their ability to define defaultProps for use in development.

没有Flow的示例中我们使用PropTypes进行一些有限的类型检查,并可以使用它们定义defaultProps以便在开发中使用。

It may look a little complex on first glance, but there is relatively little that we need to change in order to add Flow.

乍一看可能看起来有些复杂,但是为了添加Flow,我们几乎不需要更改。

To transform this to use Flow we can first remove the PropTypes import and definitions. The // @flow annotation can then be added to line one.

为了将其转换为使用Flow,我们首先可以删除PropTypes导入和定义。 // @flow注释可以随后添加到第一行。

For this component we are only going to type check the props passed in. To do so we will first create a Props type, much cleaner than defining each prop individually inline.

对于此组件,我们将仅对传入的道具进行类型检查。为此,我们将首先创建一个Props类型,这比内联定义每个道具要干净得多。

type Props = {
  strings: { [string_key: string]: string },
  onClose: Function,
  isOpen: boolean,
  target: Function,
};

Here the latter three types are self-explanatory. As strings is an object of strings an object as a map has been used, checking each key and value in the object received to check that their types match, without having to specify their exact string keys.

在这里,后三种类型是不言自明的。 由于strings是字符串的对象,因此已使用一个对象作为映射 ,检查接收到的对象中的每个键和值以检查其类型是否匹配,而不必指定其确切的字符串键。

The prop-types definitions can then be removed along with its import. As defaultProps are not tied to this import they can, and should, remain. *See the closing ESLint comments for any errors reported at this point.

然后可以将prop-types定义及其导入删除。 由于defaultProps与该导入无关,因此它们可以而且应该保留。 *有关此时报告的任何错误,请参见ESLint的结尾注释。

The component should now look like this:

该组件现在应如下所示:

状态组件 (Stateful Components)

Stateful components follow some slightly different declarations. As this component is more complex we will also look at declaring types for some additional aspects.

有状态组件遵循一些稍微不同的声明。 由于此组件更加复杂,我们还将查看一些其他方面的声明类型。

As before, first take a look at the component before adding Flow.

和以前一样, 在添加Flow之前先查看一下组件

Props and State

道具与状态

As in the function component we first remove the propTypes definition and import, and add the // @flow annotation.

与在函数组件中一样,我们首先删除propTypes定义并导入,然后添加// @flow批注。

First we will take a look at adding types for Props and State. Again we will create types for these:

首先,我们将看看为Props和State添加类型。 同样,我们将为这些创建类型:

type Props = {
  strings: { [string_key: string]: string },
  hideBannerClick: Function,
}; 
type State = {
  popoverIsOpen: boolean,
};

and specify that the component will use them:

并指定组件将使用它们:

class Banner extends Component<Props, State> {
  constructor(props: Props) {
    super(props);    
    this.state = {
      popoverIsOpen: false,
    };
  ...
  };
...
};

Next we hit our first difference between Function and Stateful components, defaultProps. In a Function component these were declared as we are used to, in Stateful components the external Banner.defaultProps syntax is removed, and instead the defaults are declared within the class:

接下来,我们实现函数和有状态组件defaultProps之间的第一个区别。 在功能组件中,这些是我们Banner.defaultProps声明,在有状态组件中,外部Banner.defaultProps语法已删除,而默认值在类内声明:

class Banner extends Component<Props, State> {
  static defaultProps = {
    strings: defaultStrings,
  };
constructor(props: Props) {
...
// the below is removed
// Banner.defaultProps = {
//  strings: defaultStrings,
// };

Constructor declarations

构造函数声明

stringWithPlaceholder is declared within the constructor. Here we are not looking at why it is declared there (we will assume there is good reason), but rather to see whether flow can be added without any changes to the existing code.

在构造函数中声明stringWithPlaceholder 。 在这里,我们不是在研究为什么在这里声明它(我们假设有充分的理由),而是在不更改现有代码的情况下查看是否可以添加流程。

If run in its existing state we would encounter the error Cannot get this.stringWithPlaceholder because property stringWithPlaceholder is missing in Banner [1].

如果以现有状态运行,则会遇到错误Cannot get this.stringWithPlaceholder because property stringWithPlaceholder is missing in Banner [1]

To fix this we must add a single line inside the Banner class block, just beneath and outside of the constructor:

为了解决这个问题,我们必须在Banner类块内的构造函数的下面和外面添加一行:

class Banner extends Component<Props, State> {
  constructor(props: Props) {
    super(props);    
    this.state = {
      popoverIsOpen: false,
    };
    this.stringWithPlaceholder = ...
  };
  stringWithPlaceholder: string;
...
};

This variable is created in the constructor but not passed in as props. As we are using Flow for type checking the props passed into the constructor, it requires everything within the constructor be type checked. It is known that Flow requires this, and this can be done by specifying their type in the class block.

此变量在构造函数中创建,但未作为prop传递。 当我们使用Flow进行类型检查传递给构造函数的props时,它要求对构造函数中的所有内容进行类型检查。 众所周知 ,Flow需要这样做,这可以通过在类块中指定它们的类型来完成。

At this point Props and State are complete. Let’s look at some quick additional examples of type checking within this component. *See the closing ESLint comments for any errors reported at this point.

至此,道具和状态已完成。 让我们看一下该组件中的一些其他类型检查的快速示例。 *有关此时报告的任何错误,请参见ESLint的结尾注释。

Return, Event, and Node types

返回,事件和节点类型

togglePopover takes no arguments, so a simple example of specifying no return value can be seen:

togglePopover带任何参数,因此可以看到一个简单的示例,该示例不指定返回值:

togglePopover = (): void => {
  ...
};

keyboardOnlyTogglePopover returns nothing, but has a single parameter. This is an event, specifically a keypress event. SyntheticKeyboardEvent is used as

keyboardOnlyTogglePopover返回任何内容,但只有一个参数。 这是事件,特别是按键事件。 SyntheticKeyboardEvent用来作为

React uses its own event system so it is important to use the SyntheticEvent types instead of the DOM types such as Event, KeyboardEvent, and MouseEvent.

React使用自己的事件系统,因此使用SyntheticEvent类型而不是诸如Event,KeyboardEvent和MouseEvent之类的DOM类型非常重要。

keyboardOnlyTogglePopover = (e: SyntheticKeyboardEvent<>): void => {
  ...
};

Popover is defined in render() and returns an instance of the ListPopover Function component we looked a previously. We can specify its return type as a React Node. However, to be able to do so, we must first import it, as it is not accessible by default. There is more than one way to import it, one of which shown below:

Popoverrender()定义,并返回我们之前看过的ListPopover Function组件的实例。 我们可以将其返回类型指定为React Node 。 但是,为了做到这一点,我们必须首先将其导入,因为默认情况下无法访问它。 有多种导入方法,如下所示:

import React, { Component } from 'react';
import type { Node } from 'react';
...
const Popover: Node = (
  <ListPopover
    onClose={this.togglePopover}
    isOpen={this.state.popoverIsOpen}
    strings={this.props.strings}
    target={() => document.getElementById('ListPopoverLink')}
  />
);

类型检查导入的React组件 (Type checking imported React components)

When Prop types have been declared in a component, they can be used when using that component within another. However, if you are using an index.js to export the first component then the flow, // @flow will need to be added to the index.

在组件中声明了Prop类型后,可以在另一个组件中使用该组件时使用它们。 但是,如果使用index.js导出第一个组件,则需要将流// @flow添加到索引中。

For example:

例如

// @flow
import ListPopover from './ListPopover';
export default ListPopover;

标记道具为可选 (Marking props as optional)

A prop can be marked as optional using the prop?: type syntax, for example:

可以使用prop?: type语法将prop?: type标记为可选,例如:

type Props = {  
  strings: { [string_key: string]: string },  
  hideBannerClick?: Function,
};

This is supported, but no longer recommended by Flow. Instead all props should be left as required, with no ? , even if optional, as Flow automatically detects defaultProps and marks props with a default as optional internally.

支持此功能,但Flow不再建议这样做。 而是应根据需要保留所有道具,没有? ,即使是可选的,因为Flow 在内部自动检测 defaultProps并将带有默认值的道具标记为可选。

In the section below we can see how manually marking props as optional can cause conflicts with other tools in some cases.

在下面的部分中,我们可以看到在某些情况下手动将props标记为可选项会导致与其他工具的冲突。

ESLint扩展,默认道具和道具验证错误解决方案 (ESLint extensions, default props, and props validation error solutions)

Two additions are made to our .eslintrc. For this project specifically you can simply accept their use, or read the detail below if you see any of the three errors:

我们的.eslintrc增加了.eslintrc 。 特别是对于该项目,您可以简单地接受它们的使用,或者如果看到三个错误中的任何一个,请阅读下面的详细信息:

  • x missing in props validation

    x missing in props validation

  • error defaultProp "x" defined for isRequired propType

    error defaultProp "x" defined for isRequired propType

  • Cannot get strings.xxx because property xxx is missing in undefined

    Cannot get strings.xxx because property xxx is missing in undefined

The rules added, with reasoning, are:

通过推理添加的规则是:

"react/default-props-match-prop-types": [
  "error", { "allowRequiredDefaults": true }
]

When using objects as maps (in this case for the 'strings' prop) a missing in props validation error occurs. This is a bug and so is explicitly ignored here.

当使用对象作为地图(在这种情况下为“字符串”道具)时,会发生missing in props validation错误。 这是一个错误 ,因此在此明确忽略

"react/default-props-match-prop-types": [  "error", { "allowRequiredDefaults": true }]

When using objects as maps complexities between ESLint, flow, and prop-types come into play.

当使用对象作为地图时,ESLint,flow和prop-type之间的复杂性起作用。

strings is a required prop, passed as an object of strings. The flow type checks that for each entry in the object the string key is a string, and the value is a string. This is far more maintainable than having to list out the prop type of each specific key.

strings是必需的道具,作为字符串对象传递。 流类型检查对象中每个条目的字符串键是一个字符串,值是一个字符串。 这比必须列出每个特定键的prop类型要容易得多。

If the prop is marked as required in Flow then ESLint would error stating: error defaultProp "strings" defined for isRequired propType.

如果在Flow中将prop标记为必需,则ESLint将错误说明: error defaultProp "strings" defined for isRequired propType

If the prop is manually marked as optional then Flow will error with Cannot get strings.xxx because property xxx is missing in undefined [1].

如果将prop手动标记为可选,则Flow将Cannot get strings.xxx because property xxx is missing in undefined [1]出错, Cannot get strings.xxx because property xxx is missing in undefined [1]

This is known and is due to refinement invalidation as JSX can transform method calls so Flow cannot be sure that xxx has not been redefined.

这是已知的,并且是由于细化无效导致的,因为JSX可以转换方法调用,因此Flow不能确保尚未重新定义xxx。

This leaves us with fixing the ESLint error. The rules above allows defaultProps to be defined while the Flow type is not marked as optional. Flow will understand this and convert it to optional. ESLint is marked to "allowRequiredDefaults": true, meaning that although ESLint sees the prop as required it will not error.

这样就可以修复ESLint错误。 上面的规则允许在Flow类型标记为可选时定义defaultProps。 Flow将理解这一点并将其转换为可选的。 ESLint标记为"allowRequiredDefaults": true ,这意味着尽管ESLint认为prop是必需的,但不会出错。

最后的想法 (Final thoughts)

Once over the initial hurdle of installation, Flow is fairly straightforward to use. The ability to add it incrementally definitely helps, rather than having to refactor an entire project in one go.

一旦超过了最初的安装障碍,Flow就相当容易使用。 逐步添加它的功能肯定会有所帮助,而不必一次重构整个项目。

Hopefully the setup instructions and examples here prove useful if you are looking to try Flow out yourself.

如果希望自己尝试使用Flow Out,希望此处的设置说明和示例对您有所帮助。

Thanks for reading ?

谢谢阅读 ?

You may also enjoy:

您可能还会喜欢:

翻译自: https://www.freecodecamp.org/news/incrementally-add-flow-type-checking-react-261fee015f80/

react flow

 类似资料: