React 中的 TypeScript 简介
在本文中,我想与大家分享如何使用 TypeScript 改进我的 React 代码。
首先,我们将了解什么是 TypeScript 以及它的作用。
然后,我们将探讨如何在 React 中使用 TypeScript(组件、钩子、外部库)。
最后,我将总结在 React 应用中使用 TypeScript 的优缺点。
打字稿?
人们对 JavaScript 的一个常见批评是,JavaScript 是无类型的。这意味着你可以这样做:
let a = "Hello";
let b = 5;
// Here we substract a number to a string
// Javascript does not warn us even if we try to substract a number to a string
let c = a - b;
console.log(a) // Hello
console.log(b) // 5
console.log(c) // NaN
正如你所见,JavaScript 非常宽松,这可能会导致意外的行为和错误。
另一个经常被批评的问题是,我们不知道 JavaScript 中对象的字段。
有时我们得到一个对象,却不确定这个对象的结构是什么。
例如:
const user = {
firstName: "Eikichi",
lastName: "Onizuka"
}
console.log(user.name)
// user.name does not exist.
// Javascript is unable to tell us field name does not exist
这两个示例可能会在运行时产生错误。如果在运行上述代码之前能够提示潜在的错误,那就太好了。Typescript
尝试通过向 JavaScript 添加类型来解决这些问题。Typescript
是一种编程语言。通过添加类型,Typescript 能够在运行代码之前提供一些提示。Typescript
不会直接在浏览器中执行,而是先将其转换为 JavaScript 代码。
最终,使用 Typescript 时,浏览器中只会执行 JavaScript。
现在,让我们看看如何将 typescript 与 React 一起使用!
项目
我将使用 React 和 TypeScript 开发的一个基础待办事项列表应用的一些代码示例。
在这个应用中,我们可以添加待办事项并切换待办事项以完成它们。
待办事项列表包含 3 个字段:
- id:通用唯一标识符(uuid)
- label:todo的标签
- isDone:布尔值,如果待办事项已完成则为 true 以下是应用程序的一个示例:
该项目使用create react app创建。Create
react app 提供了一个使用 React 和 TypeScript 的模板,方便快速上手。
该项目的目标是在一个小项目中提供一些 React/TypeScript 示例。样式并不重要。您可以在此处
找到项目代码。 以下是 todos 应用的屏幕截图:
使用 Typescript 进行反应
在本部分中,我们将看到如何使用 TypeScript:
- 成分
- 钩子
- 外部库
共享类型
通常,应用程序的多个部分都需要某些类型。例如,Todo 类型可能在多个组件中使用。
我在项目根目录下的types.ts
文件中定义了这些类型。这样,我们就可以轻松地在整个应用程序中访问共享类型。 要定义类型,我们interface
在 TypeScript 中使用关键字 。让我们分析一下在 Todo 应用程序中是如何实现的!
待办事项应用
正如我在上一节中所说,待办事项具有以下字段:
- id:uuid。uuid是一个 128 位的数字。
- label:与待办事项对应的标签。在我们的应用中,它以字符串形式表示。
- isDone:一个布尔值。
让我们看看如何定义 Todo 类型,以便稍后在我们的 React 应用中使用它。
正如我之前所说,所有共享类型都在types.ts文件中。
以下是 types.ts 的一个示例:
interface Todo {
id: string
label: string
isDone: boolean
}
我们将这个新类型命名为Todo
。
最后,我们为字段分配相应的类型:
- id :字符串,uuid 将表示为字符串(例如:“123e4567-e89b-12d3-a456-426614174000”)
- 标签:字符串,标签将表示为字符串(例如:“Cook”)
- isDone:布尔值(例如:true)
太棒了!我们有了 Todo 接口。现在我们可以像这样在代码中使用它:
let todo: Todo = {
id: "123e4567-e89b-12d3-a456-426614174000",
label: "Cook",
isDone: false
}
:
如你所见,我们可以在 TypeScript 中指定变量的类型。
如果我们尝试访问或添加不存在的字段,TypeScript 将显示错误。
我们还需要一个 NewTodo 类型。此类型将用于在列表中添加新的待办事项。
它与上面的 Todo 类型相同,只是它还没有 id。
以下是代码types.ts
:
export interface NewTodo {
label: string
isDone: boolean
}
现在我们可以在组件内部使用 todos 类型了。
接下来看看如何组织我们的组件!
React 组件
在 React 组件中,我喜欢在声明组件之前定义一个 Props 接口。
这个 Props 接口包含组件的所有属性。
在我看来,编写 Props 接口有以下优点:
- 它迫使我们思考组件需要什么属性
- 如果你打开文件,你可以快速找出组件的参数(你不必查看组件代码就知道它可以采用哪些参数)
- 当我们在应用程序中使用该组件时,如果我们向组件传递了错误的参数,typescript 可以警告我们。
让我们看一个来自todo应用程序的具体示例!
待办事项应用
我们将分析 TodosList 组件。它的作用是显示待办事项列表。
它接受两个参数:
- todos:这是将显示的待办事项列表。
- onTodoClick:点击待办事项时调用的回调。此回调以待办事项作为参数。
让我们看看如何使用 typescript 定义这个 React 组件。
import { Todo } from './types'; // import the Todo type
import TodoItem from './TodoItem'; // TodoItem is the component used to display one todo on the screen
/*
* We define our Props type
* It is used to define the props our TodosList will take in parameter
*/
interface Props {
todos: Array<Todo>,
onTodoClick?: (todo: Todo) => void
}
/*
* The TodosList component.
* We are using our Props type to tell typescript "This component uses the Props type for its parameter".
* This way, when we use our component, typescript is able to tell you if we try to use a non existing property.
* Or if we try to give a bad type to a props.
*/
function TodosList({todos, onTodoClick}: Props) {
/*
* Now we can use todos and the onTodoClick
* if we try to write : `todos.foo`, typescript can tell us that an array of todos has no "foo" property
* Same things apply to onTodoClick. If we try to call onTodoClick like this : onTodoClick(10)
* Typescript is able to say "10 is not a todo, onTodoClick takes a todo as a parameter not a number"
*/
return (
<ul>
{ todos.map(todo => <TodoItem key={todo.id} onTodoClick={onTodoClick} todo={todo} />) }
</ul>
)
}
export default TodosList
注意:你可能注意到我们在 onTodoClick 中添加了一个“?”。这意味着 onTodoClick 是可选的。
让我们看看如果我们尝试在另一个文件中使用我们的组件会发生什么:
/* Typescript warns us, because hello does not exist as a parameter for our TodosList */
<TodosList hello={"world"} />
/* Typescript warns us, because badTodos are missing id and label. */
let badTodos = [{isDone: false}, {isDone: true}];
<TodosList todos={badTodos} />
可以看到,TypeScript 可以帮助我们在运行代码之前避免出现 bug。你可以在TodoItem.tsx
文件 中找到另一个组件的示例。
现在让我们看看如何使用带有钩子的 TypeScript!
钩子
有多个钩子。本文我将重点介绍 useState。useState
钩子使我们能够在组件中保存状态。
使用 TypeScript,我们可以定义要用 useState 存储的状态。TypeScript
会使用这些信息来防止我们设置错误类型的状态。
我们来看一个例子:
/*
* Typescript now knows that num is a number and setNum takes a number as a parameter.
* Typescript will warn us if we try to call setNum("a"), for example.
*/
const [num, setNum] = useState<number>();
让我们在todo App中看一个例子!
待办事项应用
在todo应用中,我们需要useState
钩子来管理todos。
让我们看看App.tsx代码:
import styles from './App.module.css';
import {v4 as uuidv4} from 'uuid';
import { Todo, NewTodo } from './types';
import { useState } from 'react';
import TodosList from './TodosList';
import AddTodo from './AddTodo';
function App() {
/*
* With useState<Todo[]>, typescript knows:
* - todos is an Array of todos
* - setTodos takes an array of todos as parameter
*/
const [todos, setTodos] = useState<Todo[]>([
{id: uuidv4(), label: "Cleaning", isDone: true},
{id: uuidv4(), label: "Cooking", isDone: false}
])
function toggleTodo(todo: Todo) {
setTodos(todos.map(
t => t.id === todo.id ? {...t, isDone: !t.isDone} : t
))
}
function addTodo(newTodo: NewTodo) {
/*
* If we try to pass a non todos array, typescript will tell us
*/
setTodos([...todos, {
...newTodo,
id: uuidv4()
}])
}
return (
<div className={styles['App']}>
{/* Since useState is typed, typescript knows that we are passing a todos array in TodosList */}
<TodosList onTodoClick={toggleTodo} todos={todos} />
<AddTodo onNewTodoSubmit={addTodo} />
</div>
);
}
export default App;
由于 useState 是类型化的,typescript 确保我们不会错误地使用 todos 和 setTodos。
请注意,我们使用外部库 ( uuid ) 来生成待办事项 ID。
默认情况下,TypeScript 无法识别 v4 函数返回的字符串。
让我们看看如何帮助 TypeScript 理解外部库!
外部库
对于外部库,通常有3种情况:
- 这个库是用 TypeScript 编写的。在这种情况下,大多数时候我们只需要
npm install
这个库和相应的类型。这是最好的情况。 - 该库不直接提供类型。默认情况下,TypeScript 无法识别该库的任何类型。但是,大多数情况下,项目会附带一些类型。通常,我们可以使用 来安装这些类型
npm install @types/[LIB_NAME]
。React 就是这样的。例如,有一个@types/react
包可以使用 React 添加类型。 - 这个库不是用 TypeScript 编写的,也没有类型。这是最糟糕的情况。你要么自己编写类型,要么使用
any
TypeScript 中的类型。
注意:随着 TypeScript 越来越流行,大多数情况下,在使用外部库时都会发现类型
待办事项应用
让我们回到uuid包。uuid 包不是用 TypeScript 编写的。
但是,有一个@types/uuid
对应的包。该包使用 进行安装npm install --save-dev @types/uuid
。
这样,当我们将 uuid 分配给待办事项的 id 时,TypeScript 就知道,我们分配给 id 的是一个字符串。
结论
在我看来,以下是使用 typescript 和 react 的优缺点。
优点:
- 通过在编写组件时编写类型,它迫使我们更多地思考我们的组件以及如何使用它
- 如果您有一个兼容的编辑器,TypeScript 可以在您编写代码时(即使在 JSX 中)为您提供错误和自动完成功能!
- 当你使用或打开组件文件时,你可以轻松查看其参数。你无需再问自己“这个属性的名称是什么,或者这个属性是字符串还是数字”。
缺点:
- 这会让代码变得有点冗长。因为我们需要指定类型。
- 这增加了构建项目的复杂性。现在我们需要在运行应用程序之前将 TypeScript 转换为 JavaScript。希望像Cra这样的工具能够提供现成的 React/TypeScript 模板。
正如我所说,你可以在这个 repo中找到待办事项应用的代码。
希望你喜欢这篇关于 TypeScript 与 React 的小介绍!:)