

用TypeScript编写React的最佳实践
source link: http://mp.weixin.qq.com/s?__biz=Mzg2NDAzMjE5NQ%3D%3D&%3Bmid=2247485771&%3Bidx=1&%3Bsn=a4bc77f47a35293d7c98ddbc1cc948f8
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本文译自 https://www.sitepoint.com/react-with-typescript-best-practices/
如今, React
和 TypeScript
是许多开发人员正在使用的两种很棒的技术。 但是把他们结合起来使用就变得很棘手了,有时很难找到正确的答案。 不要担心,本文我们来总 结一下两者结合使用的最佳实践。
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
-
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
,并且默认带有一些最基本的配置。我们建议你修改成下面的内容:
{ "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 风格解析模块 "resolveJsonModule": true, // 允许使用 .json 扩展名导入的模块 "noEmit": true, // 不输出(意思是不编译代码,只执行类型检查) "jsx": "react", // 在.tsx文件中支持JSX "sourceMap": true, // 生成相应的.map文件 "declaration": true, // 生成相应的.d.ts文件 "noUnusedLocals": true, // 报告未使用的本地变量的错误 "noUnusedParameters": true, // 报告未使用参数的错误 "experimentalDecorators": true, // 启用对ES装饰器的实验性支持 "incremental": true, // 通过从以前的编译中读取/写入信息到磁盘上的文件来启用增量编译 "noFallthroughCasesInSwitch": true }, "include": [ "src/**/*" // *** TypeScript文件应该进行类型检查 *** ], "exclude": ["node_modules", "build"] // *** 不进行类型检查的文件 *** }
其他建议来自 react-typescript-cheatsheet 社区
ESLint / Prettier
为了确保你的代码遵循项目或团队的规则,并且样式保持一致,建议你设置 ESLint
和 Prettier
。为了让它们配合的很好,请按照以下步骤进行设置。
1.安装依赖
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', // 允许使用 import ecmaFeatures: { jsx: true, // 允许对JSX进行解析 }, }, rules: { // 自定义规则 // 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.在根目录下创建一个 .prettierrc.js
文件并添加以下内容:
module.exports = { semi: true, trailingComma: 'all', singleQuote: true, printWidth: 120, tabWidth: 4, };
-
更新
.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 与 prettier 冲突的 ESLint 规则 'plugin:prettier/recommended', ], parserOptions: { ecmaVersion: 2018, // 允许解析最新的 ECMAScript 特性 sourceType: 'module', // 允许使用 import ecmaFeatures: { jsx: true, // 允许对JSX进行解析 }, }, rules: { // 自定义规则 // e.g. "@typescript-eslint/explicit-function-return-type": "off", }, settings: { react: { version: 'detect', // 告诉 eslint-plugin-react 自动检测 React 的版本 }, }, };
VSCode 扩展和设置
我们添加了 ESLint
和 Prettier
,下一步就是在保存时自动修复/美化我们的代码。
首先,安装 VSCode 的 ESLint extension
和 Prettier extension
。这将使 ESLint
与您的编辑器无缝集成。
接下来,通过将以下内容添加到您的中来更新工作区设置 .vscode/settings.json
:
{ "editor.formatOnSave": true }
保存时, VS Code
会发挥它的魔力并修复您的代码。很棒!
组件
React
的核心概念之一是组件。在这里,我们将引用 React v16.8
以后的标准组件,这意味着使用 Hook
而不是类的组件。
通常,一个基本的组件有很多需要关注的地方。让我们看一个例子:
import React from 'react' // 函数声明式写法 function Heading(): React.ReactNode { return <h1>My Website Heading</h1> } // 函数扩展式写法 const OtherHeading: React.FC = () => <h1>My Website Heading</h1>
注意这里的关键区别。在第一个例子中,我们使用函数声明式写法,我们注明了这个函数返回值是 React.ReactNode
类型。相反,第二个例子使用了一个函数表达式。因为第二个实例返回一个函数,而不是一个值或表达式,所以我们我们注明了这个函数返回值是 React.FC
类型。
记住这两种方式可能会让人混淆。这主要取决于设计选择。无论您选择在项目中使用哪个,都要始终如一地使用它。
Props
我们将介绍的下一个核心概念是 Props
。你可以使用 interface
或 type
来定义 Props
。让我们看另一个例子:
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>
关于 interface
或 type
,我们建议遵循 react-typescript-cheatsheet
社区提出的准则:
-
在编写库或第三方环境类型定义时,始终将
interface
用于公共API
的定义。 -
State Props 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
是可选的。 children props
是一个 React.ReactNode
表示它还是一个 React
组件。
通常,在 React
和 TypeScript
项目中编写 Props
时,请记住以下几点:
-
TSDoc Props /** comment */
-
Props type interfaces
-
如果
props
是可选的,请适当处理或使用默认值。
Hooks
幸运的是,当使用 Hook
时, TypeScript
类型推断工作得很好。这意味着你没有什么好担心的。举个例子:
// `value` is inferred as a string // `setValue` is inferred as (newValue: string) => void const [value, setValue] = useState('')
TypeScript
推断出 useState
钩子给出的值。这是一个 React
和 TypeScript
协同工作的成果。
在极少数情况下,你需要使用一个空值初始化 Hook
,可以使用泛型并传递联合以正确键入 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);
下面是一个使用 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; } }
可见,Hooks 并没有为 React
和 TypeScript
项目增加太多复杂性。
常见用例
本节将介绍人们在将 TypeScript
与 React
结合使用时一些常见的坑。我们希望通过分享这些知识,您可以避免踩坑,甚至可以与他人分享这些知识。
处理表单事件
最常见的情况之一是 onChange
在表单的输入字段上正确键入使用的。这是一个例子:
import React from 'react' const MyInput = () => { const [value, setValue] = React.useState('') // 事件类型是“ChangeEvent” // 我们将 “HTMLInputElement” 传递给 input function onChange(e: React.ChangeEvent<HTMLInputElement>) { setValue(e.target.value) } return <input value={value} onChange={onChange} id="input-example"/> }
扩展组件的 Props
有时,您希望获取为一个组件声明的 Props
,并对它们进行扩展,以便在另一个组件上使用它们。但是你可能想要修改一两个属性。还记得我们如何看待两种类型组件 Props
、 type
或 interfaces
的方法吗?取决于你使用的组件决定了你如何扩展组件 Props
。让我们先看看如何使用 type
:
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> }
如果你使用 interface
来声明 props
,那么我们可以使用关键字 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> }
两种方法都可以解决问题。由您决定使用哪个。就个人而言,扩展 interface
更具可读性,但最终取决于你和你的团队。
第三方库
无论是用于诸如 Apollo
之类的 GraphQL
客户端还是用于诸如 React Testing Library
之类的测试,我们经常会在 React
和 TypeScript
项目中使用第三方库。发生这种情况时,你要做的第一件事就是查看这个库是否有一个带有 TypeScript
类型定义 @types
包。你可以通过运行:
#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
团队和社区共同维护。
总结
由于信息量大,以最佳方式一起使用 React
和 TypeScript
需要一些学习时间,但是从长远来看,其收益是巨大的。在本文中,我们介绍了配置,组件,Props,Hook,常见用例和第三方库。尽管我们可以更深入地研究各个领域,但这应涵盖帮助您遵循最佳实践所需的 80%
。
如果您希望看到它的实际效果,可以在GitHub上看到这个示例。
https://github.com/jsjoeio/react-ts-example
轻点在看,支持作者:heart:
Recommend
-
48
docker 官方文档推荐使用 Dockerfile 构建镜像的最佳实践。 创建短生命周期容器 基于 Dockerfile 生成镜像,使用这个镜像生成的容器,我们要尽可能的缩短容器的生命周期。这里我...
-
82
前言 其实Vue官方从2.6.X版本开始就部分使用Ts重写了。 我个人对更严格类型限制没有积极的看法,毕竟各类转类型的骚写法写习惯了。 然鹅最近的一个项目中,是TypeScript+ Vue,毛计喇,学之...…真香! 注意此篇标题的“前”,本文旨在讲Ts混入
-
56
一年前刚接触 typescript 的时候, 觉得它加大了代码工作量. 写一大堆东西.为了找某个类型东奔西跑, 引入第三库还经常报错. 然而现在的我想说: 真香. 我们经常吐槽别人代码可维护性特别低, 总是希望别人能够主动的写注释, 可是写注释却没有任何方式可
-
17
[译]编写优雅的JavaScript代码 - 最佳实践[译]编写优雅的JavaScript代码 - 最佳实践[原文]: https://devinduc...
-
7
TypeScript 诞生已久,优缺点大家都知晓,它可以说是JavaScript静态类型校验和语法增强的利器,为了更好的代码可读性和可维护性,我们一个个老工程都坦然接受了用TypeScript 重构的命运。...
-
4
V2EX › 程序员 2021 了, typescript 自动格式化的最佳实践是啥 wangyong1027...
-
8
Library是dart用来组织代码的一种非常有用的方式,通过定义不同的Library,可以将非常有用的dart代码进行封装,从而提供给其他的项目使用。虽然我们可以自由使用import或者export来对library进行导入和导入。但是什么样的用法才是最合适的用法...
-
7
编写 Android Library 的最佳实践 – Android开发中文站 你的位置:Android开发中文站 > Android开发 >
-
5
2017-03-28 / blog 编写 Dockerfile 的最佳实践 我之前写过
-
4
JavaScript 功能强大且灵活,它允许您以任何您喜欢的方式编写代码,并尝试一些非常不寻常的事情,这些事情可能会导致代码中的错误。以下是我学到的关于 JavaScript 编码的 50 件事,你应该知道...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK