用TypeScript编写React的优秀实践

开发 前端
如今, React 和 TypeScript 是许多开发人员正在使用的两种很棒的技术。但是把他们结合起来使用就变得很棘手了,有时很难找到正确的答案。不要担心,本文我们来总结一下两者结合使用的优秀实践。

 如今, React 和 TypeScript 是许多开发人员正在使用的两种很棒的技术。但是把他们结合起来使用就变得很棘手了,有时很难找到正确的答案。不要担心,本文我们来总结一下两者结合使用的优秀实践。

[[328323]]

React 和 TypeScript 如何一起使用

在开始之前,让我们回顾一下 React 和 TypeScript 是如何一起工作的。React 是一个 “用于构建用户界面的 JavaScript 库” ,而 TypeScript 是一个 “可编译为普通 JavaScript 的 JavaScript类型化超集” 。通过同时使用它们,我们实际上是使用 JavaScript 的类型化版本来构建 UI。

将它们一起使用的原因是为了获得静态类型化语言( TypeScript )对 UI 的好处:减少 JS 带来的 bug,让前端开发更安全。

TypeScript 会编译我的 React 代码吗?

一个经常被提到的常见问题是 TypeScript 是否编译你的 React 代码。TypeScript 的工作原理类似于下面的方式:

  • TS:“嘿,这是你所有的UI代码吗?”
  • React:“是的!”
  • TS:“酷!我将对其进行编译,并确保你没有错过任何内容。”
  • React:“听起来对我很好!”

因此,答案是肯定的!但是稍后,当我们介绍 tsconfig.json 配置时,大多数时候你都想使用 "noEmit": true 。这是因为通常情况下,我们只是利用 TypeScript 进行类型检查。

概括地说, TypeScript 编译你的 React 代码以对你的代码进行类型检查。在大多数情况下,它不会发出任何 JavaScript 输出。输出仍然类似于非 TypeScript React 项目。

TypeScript 可以与 React 和 Webpack 一起使用吗?

是的, TypeScript 可以与 React 和 webpack 一起使用。幸运的是,官方 TypeScript 手册对此提供了配置指南。

希望这能使你轻而易举地了解两者的工作方式。现在,进入最佳实践!

最佳实践

我们研究了最常见的问题,并整理了 React with TypeScript 最常用的一些写法和配置。这样,通过使用本文作为参考,你可以在项目中遵循最佳实践。

配置

配置是开发中最无趣但是最重要的部分之一。我们怎样才能在最短的时间内完成这些配置,从而提供比较大的效率和生产力?我们一起来讨论下面的配置

  • tsconfig.json
    1. npx create-react-app my-app --template typescript 
  • ESLint / Prettier
  • VS Code 扩展和配置

项目初始化

初始化一个 React/TypeScript 应用程序的最快方法是 create-react-app 与 TypeScript 模板一起使用。你可以运行以下面的命令:

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

这可以让你开始使用 TypeScript 编写 React 。一些明显的区别是:

  • .tsx:TypeScript JSX 文件扩展
  • tsconfig.json:具有一些默认配置的 TypeScript 配置文件
  • react-app-env.d.ts:TypeScript 声明文件,可以进行允许引用 SVG 这样的配置

tsconfig.json

幸运的是,最新的 React/TypeScript 会自动生成 tsconfig.json ,并且默认带有一些最基本的配置。我们建议你修改成下面的内容:

 

  1.   "compilerOptions": { 
  2.     "target""es5", // 指定 ECMAScript 版本 
  3.     "lib": [ 
  4.       "dom"
  5.       "dom.iterable"
  6.       "esnext" 
  7.     ], // 要包含在编译中的依赖库文件列表 
  8.     "allowJs"true, // 允许编译 JavaScript 文件 
  9.     "skipLibCheck"true, // 跳过所有声明文件的类型检查 
  10.     "esModuleInterop"true, // 禁用命名空间引用 (import * as fs from "fs") 启用 CJS/AMD/UMD 风格引用 (import fs from "fs"
  11.     "allowSyntheticDefaultImports"true, // 允许从没有默认导出的模块进行默认导入 
  12.     "strict"true, // 启用所有严格类型检查选项 
  13.     "forceConsistentCasingInFileNames"true, // 不允许对同一个文件使用不一致格式的引用 
  14.     "module""esnext", // 指定模块代码生成 
  15.     "moduleResolution""node", // 使用 Node.js 风格解析模块 
  16.     "resolveJsonModule"true, // 允许使用 .json 扩展名导入的模块 
  17.     "noEmit"true, // 不输出(意思是不编译代码,只执行类型检查) 
  18.     "jsx""react", // 在.tsx文件中支持JSX 
  19.     "sourceMap"true, // 生成相应的.map文件 
  20.     "declaration"true, // 生成相应的.d.ts文件 
  21.     "noUnusedLocals"true, // 报告未使用的本地变量的错误 
  22.     "noUnusedParameters"true, // 报告未使用参数的错误 
  23.     "experimentalDecorators"true, // 启用对ES装饰器的实验性支持 
  24.     "incremental"true, // 通过从以前的编译中读取/写入信息到磁盘上的文件来启用增量编译 
  25.     "noFallthroughCasesInSwitch"true  
  26.   }, 
  27.   "include": [ 
  28.     "src/**/*" // *** TypeScript文件应该进行类型检查 *** 
  29.   ], 
  30.   "exclude": ["node_modules""build"] // *** 不进行类型检查的文件 *** 

其他建议来自 react-typescript-cheatsheet 社区

ESLint / Prettier

为了确保你的代码遵循项目或团队的规则,并且样式保持一致,建议你设置 ESLint 和 Prettier 。为了让它们配合的很好,请按照以下步骤进行设置。

1.安装依赖

 

  1. yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react --dev 

2.在根目录下创建一个eslintrc.js 文件并添加以下内容:

 

  1. module.exports =  { 
  2.   parser:  '@typescript-eslint/parser',  // 指定ESLint解析器 
  3.   extends:  [ 
  4.     'plugin:react/recommended',  // 使用来自 @eslint-plugin-react 的推荐规则 
  5.     'plugin:@typescript-eslint/recommended',  // 使用来自@typescript-eslint/eslint-plugin的推荐规则 
  6.   ], 
  7.   parserOptions:  { 
  8.   ecmaVersion:  2018,  // 允许解析最新的 ECMAScript 特性 
  9.   sourceType:  'module',  // 允许使用 import 
  10.   ecmaFeatures:  { 
  11.     jsx:  true,  // 允许对JSX进行解析 
  12.   }, 
  13.   }, 
  14.   rules:  { 
  15.     // 自定义规则 
  16.     // e.g. "@typescript-eslint/explicit-function-return-type""off"
  17.   }, 
  18.   settings:  { 
  19.     react:  { 
  20.       version:  'detect',  // 告诉 eslint-plugin-react 自动检测 React 的版本 
  21.     }, 
  22.   }, 
  23. }; 

3.添加 Prettier 依赖

 

  1. yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev 

4.在根目录下创建一个 .prettierrc.js 文件并添加以下内容:

 

  1. module.exports =  { 
  2.   semi:  true
  3.   trailingComma:  'all'
  4.   singleQuote:  true
  5.   printWidth:  120, 
  6.   tabWidth:  4, 
  7. }; 

更新 .eslintrc.js 文件:

 

  1. module.exports =  { 
  2.   parser:  '@typescript-eslint/parser',  // 指定ESLint解析器 
  3.   extends:  [ 
  4.     'plugin:react/recommended',  // 使用来自 @eslint-plugin-react 的推荐规则 
  5.     'plugin:@typescript-eslint/recommended',  // 使用来自@typescript-eslint/eslint-plugin的推荐规则 
  6.     'prettier/@typescript-eslint',  // 使用 ESLint -config-prettier 禁用来自@typescript-eslint/ ESLint 与 prettier 冲突的 ESLint 规则 
  7.     'plugin:prettier/recommended',   
  8.   ], 
  9.   parserOptions:  { 
  10.   ecmaVersion:  2018,  // 允许解析最新的 ECMAScript 特性 
  11.   sourceType:  'module',  // 允许使用 import 
  12.   ecmaFeatures:  { 
  13.     jsx:  true,  // 允许对JSX进行解析 
  14.   }, 
  15.   }, 
  16.   rules:  { 
  17.     // 自定义规则 
  18.     // e.g. "@typescript-eslint/explicit-function-return-type""off"
  19.   }, 
  20.   settings:  { 
  21.     react:  { 
  22.       version:  'detect',  // 告诉 eslint-plugin-react 自动检测 React 的版本 
  23.     }, 
  24.   }, 
  25. }; 

VSCode 扩展和设置

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

首先,安装 VSCode 的 ESLint extension 和 Prettier extension 。这将使 ESLint 与您的编辑器无缝集成。

接下来,通过将以下内容添加到您的中来更新工作区设置 .vscode/settings.json :

 

  1.     "editor.formatOnSave"true 

保存时, VS Code 会发挥它的魔力并修复您的代码。很棒!

组件

React 的核心概念之一是组件。在这里,我们将引用 React v16.8 以后的标准组件,这意味着使用 Hook 而不是类的组件。

通常,一个基本的组件有很多需要关注的地方。让我们看一个例子:

 

  1. import React from 'react' 
  2.  
  3. // 函数声明式写法 
  4. function Heading(): React.ReactNode { 
  5.   return <h1>My Website Heading</h1> 
  6.  
  7. // 函数扩展式写法 
  8. const OtherHeading: React.FC = () => <h1>My Website Heading</h1> 

注意这里的关键区别。在第一个例子中,我们使用函数声明式写法,我们注明了这个函数返回值是 React.ReactNode 类型。相反,第二个例子使用了一个函数表达式。因为第二个实例返回一个函数,而不是一个值或表达式,所以我们我们注明了这个函数返回值是 React.FC 类型。

记住这两种方式可能会让人混淆。这主要取决于设计选择。无论您选择在项目中使用哪个,都要始终如一地使用它。

Props

我们将介绍的下一个核心概念是 Props。你可以使用 interface 或 type 来定义 Props 。让我们看另一个例子:

 

  1. import React from 'react' 
  2.  
  3. interface Props { 
  4.   name: string; 
  5.   color: string; 
  6.  
  7. type OtherProps = { 
  8.   name: string; 
  9.   color: string; 
  10.  
  11. // Notice here we're using the function declaration with the interface Props 
  12. function Heading({ name, color }: Props): React.ReactNode { 
  13.   return <h1>My Website Heading</h1> 
  14.  
  15. // Notice here we're using the function expression with the type OtherProps 
  16. const OtherHeading: React.FC<OtherProps> = ({ name, color }) => 
  17.   <h1>My Website Heading</h1> 

 

关于 interface 或 type ,我们建议遵循 react-typescript-cheatsheet 社区提出的准则:

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

让我们再看一个示例:

 

  1. import React from 'react' 
  2.  
  3. type Props = { 
  4.    /** color to use for the background */ 
  5.   color?: string; 
  6.    /** standard children prop: accepts any valid React Node */ 
  7.   children: React.ReactNode; 
  8.    /** callback function passed to the onClick handler*/ 
  9.   onClick: ()  => void; 
  10.  
  11. const Button: React.FC<Props> = ({ children, color = 'tomato', onClick }) => { 
  12.    return <button style={{ backgroundColor: color }} onClick={onClick}>{children}</button> 

在此 <Button /> 组件中,我们为 Props 使用 type。每个 Props 上方都有简短的说明,以为其他开发人员提供更多背景信息。? 表示 Props 是可选的。children props 是一个 React.ReactNode 表示它还是一个 React 组件

通常,在 React 和 TypeScript 项目中编写 Props 时,请记住以下几点:

  • 始终使用 TSDoc 标记为你的 Props 添加描述性注释 /** comment */。
  • 无论你为组件 Props 使用 type 还是 interfaces ,都应始终使用它们。
  • 如果 props 是可选的,请适当处理或使用默认值。

Hooks

幸运的是,当使用 Hook 时, TypeScript 类型推断工作得很好。这意味着你没有什么好担心的。举个例子:

 

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

TypeScript 推断出 useState 钩子给出的值。这是一个 React 和 TypeScript 协同工作的成果。

在极少数情况下,你需要使用一个空值初始化 Hook ,可以使用泛型并传递联合以正确键入 Hook 。查看此实例:

 

  1. type User = { 
  2.   email: string; 
  3.   id: string; 
  4.  
  5. // the generic is the < > 
  6. // the union is the User | null 
  7. // together, TypeScript knows, "Ah, user can be User or null"
  8. const [user, setUser] = useState<User | null>(null); 

下面是一个使用 userReducer 的例子:

 

  1. type AppState = {}; 
  2. type Action = 
  3.   | { type: "SET_ONE"; payload: string } 
  4.   | { type: "SET_TWO"; payload: number }; 
  5.  
  6. export function reducer(state: AppState, actionAction): AppState { 
  7.   switch (action.type) { 
  8.     case "SET_ONE"
  9.       return { 
  10.         ...state, 
  11.         one: action.payload // `payload` is string 
  12.       }; 
  13.     case "SET_TWO"
  14.       return { 
  15.         ...state, 
  16.         two: action.payload // `payload` is number 
  17.       }; 
  18.     default
  19.       return state; 
  20.   } 

可见,Hooks 并没有为 React 和 TypeScript 项目增加太多复杂性。

常见用例

本节将介绍人们在将 TypeScript 与 React 结合使用时一些常见的坑。我们希望通过分享这些知识,您可以避免踩坑,甚至可以与他人分享这些知识。

处理表单事件

最常见的情况之一是 onChange 在表单的输入字段上正确键入使用的。这是一个例子:

  1. import React from 'react' 
  2.  
  3. const MyInput = () => { 
  4.   const [value, setValue] = React.useState(''
  5.  
  6.   // 事件类型是“ChangeEvent” 
  7.   // 我们将 “HTMLInputElement” 传递给 input 
  8.   function onChange(e: React.ChangeEvent<HTMLInputElement>) { 
  9.     setValue(e.target.value) 
  10.   } 
  11.  
  12.   return <input value={value} onChange={onChange} id="input-example"/> 

扩展组件的 Props

有时,您希望获取为一个组件声明的 Props,并对它们进行扩展,以便在另一个组件上使用它们。但是你可能想要修改一两个属性。还记得我们如何看待两种类型组件 Props、type 或 interfaces 的方法吗?取决于你使用的组件决定了你如何扩展组件 Props 。让我们先看看如何使用 type:

 

  1. import React from 'react' 
  2.  
  3. const MyInput = () => { 
  4.   const [value, setValue] = React.useState(''
  5.  
  6.   // 事件类型是“ChangeEvent” 
  7.   // 我们将 “HTMLInputElement” 传递给 input 
  8.   function onChange(e: React.ChangeEvent<HTMLInputElement>) { 
  9.     setValue(e.target.value) 
  10.   } 
  11.  
  12.   return <input value={value} onChange={onChange} id="input-example"/> 

 

如果你使用 interface 来声明 props,那么我们可以使用关键字 extends 从本质上“扩展”该接口,但要进行一些修改:

  1. import React from 'react'
  2.  
  3. interface ButtonProps { 
  4.     /** the background color of the button */ 
  5.     color: string; 
  6.     /** the text to show inside the button */ 
  7.     text: string; 
  8.  
  9. interface ContainerProps extends ButtonProps { 
  10.     /** the height of the container (value used with 'px') */ 
  11.     height: number; 
  12.  
  13. const Container: React.FC<ContainerProps> = ({ color, height, width, text }) => { 
  14.   return <div style={{ backgroundColor: color, height: `${height}px` }}>{text}</div> 

 

两种方法都可以解决问题。由您决定使用哪个。就个人而言,扩展 interface 更具可读性,但最终取决于你和你的团队。

第三方库

无论是用于诸如 Apollo 之类的 GraphQL 客户端还是用于诸如 React Testing Library 之类的测试,我们经常会在 React 和 TypeScript 项目中使用第三方库。发生这种情况时,你要做的第一件事就是查看这个库是否有一个带有 TypeScript 类型定义 @types 包。你可以通过运行:

 

  1. #yarn 
  2. yarn add @types/<package-name
  3.  
  4. #npm 
  5. npm install @types/<package-name

例如,如果您使用的是 Jest ,则可以通过运行以下命令来实现:

 

  1. #yarn 
  2. yarn add @types/jest 
  3.  
  4. #npm 
  5. npm install @types/jest 

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

该 @types 命名空间被保留用于包类型定义。它们位于一个名为 DefinitelyTyped 的存储库中,该存储库由 TypeScript 团队和社区共同维护。

总结

由于信息量大,以优秀方式一起使用 React 和 TypeScript 需要一些学习时间,但是从长远来看,其收益是巨大的。在本文中,我们介绍了配置,组件,Props,Hook,常见用例和第三方库。尽管我们可以更深入地研究各个领域,但这应涵盖帮助您遵循最佳实践所需的 80% 。

如果您希望看到它的实际效果,可以在GitHub上看到这个示例。

https://github.com/jsjoeio/react-ts-example

 

责任编辑:华轩 来源: code秘密花园
相关推荐

2022-08-19 09:01:59

ReactTS类型

2020-06-03 16:50:24

TypeScriptReact前端

2019-09-17 09:44:45

DockerHTMLPython

2023-07-04 15:56:08

DevOps开发测试

2022-02-25 08:00:00

编程ReactTypescript

2017-02-28 21:57:05

React组件

2017-05-17 15:50:34

开发前端react

2024-05-17 08:25:06

数据驱动React语言包

2024-01-15 08:00:00

开发API文档集成

2022-08-02 09:55:04

React前端

2023-01-27 14:53:03

2023-03-30 08:00:00

ReactJavaScript前端

2021-12-04 23:10:02

Java代码开发

2020-02-25 20:55:20

JavaScript开发 技巧

2023-04-14 08:10:59

asyncawait

2024-06-20 12:30:34

2024-04-15 12:54:00

ReactVue列表逻辑

2019-11-25 15:44:13

TS数据JavaScrip

2024-01-22 12:46:00

KubernetesAPI接口

2020-09-16 14:39:13

ReactJavaScript框架
点赞
收藏

51CTO技术栈公众号