likes
comments
collection
share

【译】React+TypeScript 最佳实践

作者站长头像
站长
· 阅读数 12

原文链接:www.sitepoint.com/react-with-…

React和TypeScript是现在很多开发者都在使用的两种很棒的技术。寻求做事的方法会变得很棘手,有时很难找到正确的答案。不要担心。我们将最佳实践与示例结合在一起,以澄清您可能存在的任何疑问。

接下来让我们一探究竟!

React和TypeScript是如何一起工作的

在我们开始之前,让我们先回顾一下React和TypeScript是如何一起工作的。React是一个用于构建用户界面的JavaScript库,而TypeScript是一个JavaScript的类型化超集,可以编译成纯JavaScript。通过将它们一起使用,我们大致可以使用JavaScript的类型化版本来构建用户界面。

将它们一起使用的原因是为了在UI中使用静态类型语言(TypeScript)。这意味着更高的安全性和传送到前端更少的bug。

TypeScript会编译我的React代码吗

一个经常需要检查的问题是TypeScript是否编译了你的React代码。TypeScript的工作方式类似于下面的对话: TS:嘿,这是你所有的UI代码吗? React:是的! TS:酷!我要编译它,确保你没有遗漏任何东西。 React:听起来不错!

所以答案是肯定的。但是稍后,当我们讨论tsconfig.json配置时,大多数时候你会想要使用"noEmit": true。这意味着TypeScript在编译后不会输出JavaScript。这是因为通常情况下,我们只是使用TypeScript来做类型检查。

在CRA设置中,由react-scripts处理输出。我们运行yarn build之后,react-scripts就将输出代码打包到产品中了。

总结一下,TypeScript编译你的React代码来检查你的代码。它不会输出任何JavaScript(在大多数情况下)。输出代码仍然类似于非typescript React项目。

TypeScript可以与React和webpack协同工作吗?

是的,TypeScript可以和React和webpack一起工作。幸运的是,webpack文档有这方面的指南

希望这能让你对这两者如何协同工作有一个简单的复习。现在,进入最佳实践!

最佳实践

我们研究了最常见的问题,并将React和TypeScript的最常见用例汇总在一起。这样,您就可以将本文作为参考应用到你的项目中了。

配置

开发中最无趣却最重要的部分当属配置了。怎样才能迅速搭建好能提供最大的效率和生产力的项目呢?我们将从以下几点开始讨论:

  • tsconfig.json
  • ESLint
  • Prettier
  • VS Code extensions and settings

项目搭建

启动一个React/TypeScript应用的最快方法是在create-react-app中使用TypeScript模板。你可以运行如下命令:

npx create-react-app my-app --template typescript

这将让你有最少的时间开始用TypeScript编写React。有几个明显的区别是:

  • .tsx文件扩展名
  • tsconfig.json
  • react-app-env.d.ts

tsx是TypeScript的JSX。tsconfig.json是TypeScript的配置文件,它有一些默认设置。react-app-env.d.ts引用react-scripts的类型,并帮助实现SVG等类型导入。

tsconfig.json

幸运的是,最新的React/TypeScript模板会生成tsconfig.json。然而,他们添加的是最小化的初始配置。我们建议您根据下面这个修改自己的。下面有添加注释来解释每个选项的作用。

{
  "compilerOptions": {
    "target": "es5", // 指定ECMAScript目标版本
    "lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ], //要包含在编译中的库文件列表
    "allowJs": true, // 允许编译JavaScript文件
    "skipLibCheck": true, // 跳过所有声明文件的类型检查
    "esModuleInterop": true, // 禁用命名空间导入(import * as fs from "fs") ,并启用CJS/AMD/UMD风格导入(import fs from "fs")
    "allowSyntheticDefaultImports": true, // 允许从没有默认导出的模块中进行默认导入
    "strict": true, // 启用所有严格类型检查选项
    "forceConsistentCasingInFileNames": true, // 禁止对同一文件使用大小写不一致的引用。
    "module": "esnext", //  指定模块代码生成
    "moduleResolution": "node", // 使用Node.js风格解析模块
    "isolatedModules": true, // 无条件地发出未解析文件的导入
    "resolveJsonModule": true, // 包括以.json扩展名导入的模块
    "noEmit": true, // 不输出结果(意思是不编译代码,只执行类型检查)
    "jsx": "react", // Support JSX in .tsx files
    "sourceMap": true, // 生成相应的.map文件
    "declaration": true, // 生成相应的.d.ts文件
    "noUnusedLocals": true, // 报告未使用的局部变量的错误
    "noUnusedParameters": true, // 报告未使用参数的错误
    "incremental": true, // 通过从先前的编译中读取/写入磁盘上的文件来启用增量编译
    "noFallthroughCasesInSwitch": true // 报告switch语句中不可命中情况的错误
  },
  "include": [
    "src/**/*" // *** 需要做类型检查的文件 ***
  ],
  "exclude": ["node_modules", "build"] // *** 避免检查类型的文件 ***
}

额外的建议来自react-typescript-cheatsheet社区,解释来自TypeScript官方手册中的编译器选项文档。如果你想了解其他选项和它们的功能,这是一个很好的资源。

ESLint/Prettier

为了确保你的代码遵循项目或团队的规则,并且风格一致,建议你设置ESLint和Prettier。为了让它们更好地发挥作用,请按照以下步骤进行设置。

  1. Install the required dev dependencies:
    yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react --dev
    
  2. 在根目录下创建一个.eslintrc.js文件,并添加以下内容:
    module.exports =  {
      parser:  '@typescript-eslint/parser',  // 指定ESLint解析器
      extends:  [
        'plugin:react/recommended',  //  从@eslint-plugin-react推荐规则
        'plugin:@typescript-eslint/recommended',  // 使用@typescript-eslint/eslint-plugin推荐的规则
      ],
      parserOptions:  {
        ecmaVersion:  2018,  // 允许解析现代ECMAScript特性
        sourceType:  'module',  // 允许使用 imports 导入
        ecmaFeatures:  {
          jsx:  true,  // 语序解析JSX
        },
      },
      rules:  {
        // 指定ESLint规则的位置。可以用来覆盖从扩展的配置中指定的规则。
        // e.g. "@typescript-eslint/explicit-function-return-type": "off",
      },
      settings:  {
        react:  {
          version:  'detect',  // 告诉eslint-plugin-react自动检测要使用的React版本
        },
      },
    };
    
  3. 添加Prettier依赖:
     yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev
    
  4. 在根目录下创建一个.pretierrc .js文件,并添加以下内容:
    module.exports =  {
      semi:  true,
      trailingComma:  'all',
      singleQuote:  true,
      printWidth:  120,
      tabWidth:  4,
    };
    
  5. 更新.eslintrc.js文件
    module.exports =  {
      parser:  '@typescript-eslint/parser',  // 指定ESLint解析器
      extends:  [
        'plugin:react/recommended',  //  从@eslint-plugin-react推荐规则
        'plugin:@typescript-eslint/recommended',  // 使用@typescript-eslint/eslint-plugin推荐的规则
    +	'prettier/@typescript-eslint',  // 使用eslint-config-prettier禁用@typescript-eslint/eslint-plugin中的ESLint规则,这些规则会与prettier冲突
    +	'plugin:prettier/recommended',  // 启用eslint-plugin-prettier,并将prettier错误显示为ESLint错误。确保这一项始终在数组的最后一项。
      ],
      parserOptions:  {
        ecmaVersion:  2018,  // 允许解析现代ECMAScript特性
        sourceType:  'module',  // 允许使用 imports 导入
        ecmaFeatures:  {
          jsx:  true,  // 语序解析JSX
        },
      },
      rules:  {
        // 指定ESLint规则的位置。可以用来覆盖从扩展的配置中指定的规则。
        // e.g. "@typescript-eslint/explicit-function-return-type": "off",
      },
      settings:  {
        react:  {
          version:  'detect',  // 告诉eslint-plugin-react自动检测要使用的React版本
        },
      },
    };
    

这些建议来自一个名为“Using ESLint and Prettier in a TypeScript Project”的社区资源,作者是Robert Cooper。如果访问该资源,您可以阅读更多关于这些规则和配置背后的原因的信息。

VS Code Extensions and Settings

我们已经添加了ESLint和Prettier,下一步改进DX是在保存时自动修复/美化我们的代码。

首先,为VS Code的安装ESLint 和Prettier扩展插件。这将使得ESLint与你的编辑器无缝集成。

接下来,通过将以下内容添加到您的.vscode/settings.json

{
    "editor.formatOnSave": true
}

这将允许VS Code发挥它的魔力,并在你保存代码时修复你的代码。真漂亮!

这些建议也来自之前链接的文章,Using ESLint and Prettier in a TypeScript Project,作者是Robert Cooper。

注意: 想了解更多关于React.FC的内容,请看这里;想了解更多关于React.ReactNode的内容,请看这里

组件

React的核心概念之一是组件。这里,我们将引用React v16.8中的标准组件,即:使用hooks而不是类组件。

一般来说,对于基本组件有很多需要关注的地方。让我们来看一个例子

import React from 'react'

// Written as a function declaration
function Heading(): React.ReactNode {
  return <h1>My Website Heading</h1>
}

// Written as a function expression
const OtherHeading: React.FC = () => <h1>My Website Heading</h1>

注意这里的关键区别。在第一个示例中,我们将函数写成函数声明。我们用React.Node来标注返回类型。因为这就是它所返回的。相比之下,第二个示例使用了函数表达式。因为第二个实例返回的是一个函数,而不是值或表达式,所以我们用React.FC来标注函数类型的React函数组件。

记住这两个可能会让人感到困惑。这主要是设计选择的问题。无论您选择在项目中使用哪一种,都要始终如一地使用它。

Props

我们要介绍的下一个核心概念是props。你可以使用接口(interface)或者类型(type)来定义你的道具。让我们看另一个例子:

import React from 'react'

interface Props {
  name: string;
  color: string;
}

type OtherProps = {
  name: string;
  color: string;
}

// Notice here we're using the function declaration with the interface Props
function Heading({ name, color }: Props): React.ReactNode {
  return <h1>My Website Heading</h1>
}

// Notice here we're using the function expression with the type OtherProps
const OtherHeading: React.FC<OtherProps> = ({ name, color }) =>
  <h1>My Website Heading</h1>

当涉及到类型或接口时,我们建议遵循react-typescript-cheatsheet社区提出的指导原则:

  • 在编写库或第三方环境类型定义时,始终使用接口(interface)作为公共API定义。
  • 考虑为React组件的Props和State使用类型(type),因为它更受约束。

你可以阅读更多关于讨论的内容,并在这里看到一个比较类型和接口的表格。

让我们再看一个例子,这样我们就能看到一些更实用的东西

import React from 'react'

type Props = {
  /** color to use for the background */
  color?: string;
  /** standard children prop: accepts any valid React Node */
  children: React.ReactNode;
  /** callback function passed to the onClick handler*/
  onClick: ()  => void;
}

const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => {
   return <button style={{ backgroundColor: color }} onClick={onClick}>{children}</button>
}

在这个<Button />组件中,我们为props使用类型(type)。每个props上面都列出了一个简短的描述,以便为其他开发人员提供更多的上下文。在props的属性名color的后面有一个‘?’表示它是可选的。children标注为React.Node,因为它接受组件的所有有效返回值(详情请看这里)。为了说明prop中的color,我们在解构它时使用一个默认值。这个例子应该涵盖了基本知识,并示意你必须为props编写类型,并使用可选值和默认值。

一般来说,在React和TypeScript项目中编写props时,要记住这些内容

  • 总是使用TSDoc符号/** comment */向props添加描述性注释。
  • 无论你为组件props使用类型(type)还是接口(interface),都要始终如一地使用它们。
  • 当道具是可选的,适当处理或使用默认值。

Hooks

幸运的是,TypeScript类型推断在使用hook时工作得很好。这意味着你没什么好担心的。例如下面这个例子:

// `value` is inferred as a string
// `setValue` is inferred as (newValue: string) => void
const [value, setValue] = useState('')

TypeScript根据useState的参数推断类型。这方面React和TypeScript能很好的协同工作,非常漂亮。

在很少的情况下,你需要初始化一个带有空值的钩子,你可以使用一个泛型并传递一个联合类型来正确地为你的hook标注类型。看到这个实例:

type User = {
  email: string;
  id: string;
}

// the generic is the < >
// the union is the User | null
// together, TypeScript knows, "Ah, user can be User or null".
const [user, setUser] = useState<User | null>(null);

TypeScript和Hooks一起发光的另一个地方是userReducer,在那里你可以利用可区分的联合类型。这里有一个有用的例子:

type AppState = {};
type Action =
  | { type: "SET_ONE"; payload: string }
  | { type: "SET_TWO"; payload: number };

export function reducer(state: AppState, action: Action): AppState {
  switch (action.type) {
    case "SET_ONE":
      return {
        ...state,
        one: action.payload // `payload` is string
      };
    case "SET_TWO":
      return {
        ...state,
        two: action.payload // `payload` is number
      };
    default:
      return state;
  }
}

来源:react-typescript-cheatsheet Hooks章节

这里的妙处就在于有区别的结合的用处。请注意Action如何拥有两个外观相似的对象的并集。type属性是一串文字。它与类型type的区别在于,该值必须与类型中定义的字符串字面值相匹配。这意味着你的程序是非常安全的,因为开发人员只能调用type属性设置为"SET_ONE" 或 "SET_TWO"的Action。

如你所见,Hook不会给React和TypeScript项目增加太多复杂性。如果说有什么不同的话,那就是他们的二重奏更和谐。

通用场景

本节将介绍人们在使用TypeScript和React时遇到的最常见的问题。我们希望通过分享这些,您可以避免陷阱,甚至与他人分享这些知识。

处理表单事件

最常见的一种情况是正确输入表单中输入字段使用的onChange。这里有个例子

import React from 'react'

const MyInput = () => {
  const [value, setValue] = React.useState('')

  // The event type is a "ChangeEvent"
  // We pass in "HTMLInputElement" to the input
  function onChange(e: React.ChangeEvent<HTMLInputElement>) {
    setValue(e.target.value)
  }

  return <input value={value} onChange={onChange} id="input-example"/>
}

扩展组件的Props

有时,您需要将为一个组件声明的组件道具扩展到另一个组件上使用它们。但你可能需要修改一两个。嗯,还记得我们如何看待组件、类型或接口吗?如何扩展组件道具取决于您使用的是什么。让我们先看看如何使用类型

import React from 'react';

type ButtonProps = {
    /** the background color of the button */
    color: string;
    /** the text to show inside the button */
    text: string;
}

type ContainerProps = ButtonProps & {
    /** the height of the container (value used with 'px') */
    height: number;
}

const Container: React.FC<ContainerProps> = ({ color, height, width, text }) => {
  return <div style={{ backgroundColor: color, height: `${height}px` }}>{text}</div>
}

如果你使用一个接口声明了你的道具,那么我们可以使用关键字extends来扩展这个接口,但需要做一两处修改

import React from 'react';

interface ButtonProps {
    /** the background color of the button */
    color: string;
    /** the text to show inside the button */
    text: string;
}

interface ContainerProps extends ButtonProps {
    /** the height of the container (value used with 'px') */
    height: number;
}

const Container: React.FC<ContainerProps> = ({ color, height, width, text }) => {
  return <div style={{ backgroundColor: color, height: `${height}px` }}>{text}</div>
}

这两种方法都能解决问题。使用哪一种由您决定。就个人而言,扩展一个界面让人感觉更容易理解,但最终,这取决于你和你的团队。

你可以在TypeScript手册中阅读更多关于这两个概念的内容

第三方库

无论是像Apollo这样的GraphQL客户端,还是像React Testing Library这样的测试库,我们经常在React和TypeScript项目中使用第三方库。当这种情况发生时,你要做的第一件事就是看看是否有一个@types包,里面有TypeScript的类型定义。你可以运行下面这条命令:

#yarn
yarn add @types/<package-name>

#npm
npm install @types/<package-name>

例如,如果您正在使用Jest,则可以通过运行以下命令来实现这一点:

#yarn
yarn add @types/jest

#npm
npm install @types/jest

这样,当您在项目中使用Jest时,就可以增加类型安全。

@types命名空间是为包类型定义保留的。它们存在于一个名为DefinitelyTyped的存储库中,部分由TypeScript团队和部分社区维护。

在我的package.json中,这些应该保存为dependencies还是devDependencies呢?

简而言之,这要视情况而定。大多数时候,如果你正在构建一个web应用程序,它们可以被归类为devDependencies。然而,如果你在TypeScript中编写React库,你可能想要把它们作为依赖项包含进去。

Stack Overflow有一些回复,您可以查看进一步的信息。

如果没有@types包会发生什么?

如果你在npm上没有找到@types包,那么你基本上有两个选择:

  • 添加一个基本声明文件
  • 添加一个完整的声明文件

第一个选项意味着基于包名创建一个文件,并将其放在根目录下。例如,如果我们需要包banana-js的类型,那么我们可以创建一个基本的声明文件banana-js.d.ts放在根目录:

declare module 'banana-js';

这不会为你提供类型安全,但它会为你解除阻塞。

更完整的声明文件是为库/包添加类型的地方

declare namespace bananaJs {
    function getBanana(): string;
    function addBanana(n: number) void;
    function removeBanana(n: number) void;
}

如果你从来没有写过声明文件,那么我们建议你看看官方TypeScript手册

总结

以最好的方式一起使用React和TypeScript需要一点时间来学习,因为信息量很大。但从长远来看,好处是巨大的。在本文中,我们讨论了配置、组件、Props、Hooks、常用用例和第三方库。尽管我们可以深入到许多单独的领域,但这应该涵盖了帮助您遵循最佳实践所需的80%。

如果你想要看到它的实际应用,你可以在GitHub上看到这个例子

如果您想与我取得联系、分享对本文的反馈或讨论一起使用这两种技术,您可以通过Twitter@jsjoeio与我联系。

延伸阅读

如果您想深入了解,以下是我们建议的一些资源

react-typescript-cheatsheet

很多建议都直接来自react-typescript-cheatsheet。如果你想在React-TypeScript中寻找特定的例子或细节,这里就是你的好地方。我们也欢迎捐款。

Official TypeScript Handbook

另一个奇妙的资源是ypeScript手册这是由TypeScript团队保持的最新版本,并提供了一些例子,以及对语言内部工作原理的深入解释。

TypeScript Playground

你知道你可以在浏览器里用TypeScript代码测试React吗?你要做的就是导入React。这里有一个开始链接

Practical Ways to Advance Your TypeScript Skills

阅读我们的Practical Ways to Advance Your TypeScript Skills,让你在前进的过程中不断学习。

转载自:https://juejin.cn/post/6927159742359928845
评论
请登录