在 React 中使用 TypeScript

阅读数:846 2019 年 11 月 22 日 08:00

在React中使用TypeScript

此文基于我在 React JS 与 React Native 波恩线下交流会发表的演讲。演讲旨在回答以下问题:

  • TypeScript 是什么?
  • 如何在 React 使用它?
  • 为何或为何不使用它?

演讲幻灯片

TypeScript 是什么?

TypeScript 官网是这样描述 TypeScript 的:

TypeScript 是一个可以被编译为纯 JavaScript 的类型超集
~ typescriptlang.org

这句话是什么意思?首先,从这个陈述中我们可以明显看出 TypeScript 是一种与 JavaScript 有某种关系的 类型 语言,这是它跟 JavaScript 这种动态类型语言最主要的不同。其次,由于 TypeScript 可以被编译为纯 JavaScript,就意味着它可以运行在浏览器或者 Node.js 等目标环境中。最后,作为 JavaScript 的超集,TypeScript 只会在保证与 JS 标准兼容性的基础上的情况下,在 JS 之上增加特性。

让我们来看下面这个例子。由于 TS 是 JS 的超集,所以下面的代码段对于两种语言来说都是合法的:

复制代码
function add(a, b) {
return a + b;
}
const favNumber = add(31, 11);

TypeScript 特别之处在于它提供的类型。为以上代码增加类型后,我们就得到了以下合法的 TypeScript 代码,但它不是合法的 JavaScript。

复制代码
function add(a: number, b: number): number {
return a + b;
}
const favNumber: number = add(31, 11);

增加类型注解后,TypeScript 编译器就可以检查你的代码,找出其中的错误。比如,它可以找出任何不匹配的参数类型,或是在当前作用域之外使用变量。

可以通过 tsc 命令行工具编译 TypeScript。它会做类型检查,并在所有检查成功后输出纯 JavaScript 代码。生成的代码跟你的源码很像,只是移除了类型注释。

TypeScript 同时支持基本的类型推导。它允许你省略函数的返回类型,或是在大多数情况下省略变量赋值的类型,达到简化代码的效果:

复制代码
function add(a: number, b: number) {
return a + b;
}
const favNumber = add(31, 11);

(可能有点难看出区别:第一行没有了函数返回类型;第五行没有了变量的类型。)

我们来看看 TS 提供的类型吧。有 boolean、string、number、Symbol、null、undefined 和 BigInt 这些原始类型;有 void 类型用以标记一个不返回任何值的函数;有 Function 函数类型和通常写为 string[] 或是 numbe[] 的 Array<T> 类型;同时,还有根据你的部署环境、依赖关系,会有如 ReactElement、HTMLInputElement 或 Express.App 之类的环境相关的类型。

自定义类型是 TS 最有意思的特性。接下来我们来看看如何使用它来为你的域建模。你可以使用 interface(接口)关键字定义对象的形态:

复制代码
interface User {
firstName: string;
lastName: string;
age: number;
state: UserState;
}

也可以定义 enum(枚举)类型:

复制代码
enum UserState {
ACTIVE,
INACTIVE,
INVITED,
}

TypeScript 支持类型的继承:

复制代码
interface FunkyUser extends User {
isDancing: boolean;
}

同时还有些高级类型,如联合类型。我们可以用它以接近原生 JavaScript 的形式替代枚举:

复制代码
type UserState =
"active" |
"inactive" |
"invited";
const correctState: UserState = "active"; // ✅
const incorrectState: UserState = "loggedin"; // ❌ 类型错误

当进行类型检查时,TypeScript 不会检查对象的原型链或者其他任意的继承形式,它只会检查属属性和函数的类型签名。

复制代码
const bugs: User = {
firstName: "Bugs",
lastName: "Bunny",
age: "too old", // TypeError: expected `number` but got `string`
state: UserState.ACTIVE,
}

显然,你可以在 TypeScript 中使用类,但请注意:稍后我会向你说明为何我觉得你不要使用它。无论如何,这里有个例子:

复制代码
class JavaLover implements User {
private firstName: string;
private lastName: string;
private age: number;
private state: UserState;
getOpinion() {
return [ "!!JAVA!1!" ];
}
}

现在我们已了解了一些基本的 TypeScript 语法和基本概念,接下来我们来看看如何将它与 React 一起使用。

如何在 React 使用 TypeScript?

由于 Babel 7 内置了 TypeScript 支持,因此我们可以很容易地将它集成到构建过程。不过我依然建议你查阅构建工具的文档。大多数构建工具的文档都会包含一份精心编写的 TypeScript 安装指南。

当配置好了构建工具后,你就可以在组件中使用 TypeScript 了。以下是个简单的 React Native 组件:它接收一个 TodoItem 和回调函数作为 props,展示一个待办事项功能。

复制代码
import * as React from "react";
import { View, Text, CheckBox } from "react-native";
interface TodoItem {
id: string;
name: string;
isCompleted: boolean;
}
interface TodoListItemProps {
item: TodoItem;
onComplete: (id: string) => void;
}
function TodoListItem(props: TodoListItemProps) {
const { item, onComplete } = props;
return (
<View>
<Text>
{item.name}
</Text>
<CheckBox
isChecked={item.isCompleted}
onClick={state => {
onComplete(item.id);
}}
/>
</View>
);
}

因为 React 本质上是纯 JavaScript,于是你可以跟 JavaScript 一样为它增加类型。通过 interface 类型声明组件的 props 结构,并用刚刚定义的 TodoListItemProps 注释组件的 props 参数的类型。

尽管 JSX 没有包含在 JavaScript 的标准内,TypeScript 仍然可以为 JSX 做类型检查。这意味着它可以为传入组件的 props 做验证。

你也可以将 TypeScript 与 React 的 class API 结合使用:

复制代码
import * as React from "react";
interface TimerState {
count: number;
}
class Timer extends React.Component<{}, TimerState> {
state: TimerState = {
count: 0
}
timerId: number | undefined = undefined;
componentDidMount() {
this.timerId = setInterval(
() => {
this.setState(
state => ({
count: state.count + 1
})
);
},
1000
);
}
componentWillUnmount() {
if (this.timerId) {
clearInterval(this.timerId);
}
}
render() {
return (
<p>Count: {this.state.count}</p>
)
}
}

当为一个类增加类型时,你需要将类型参数传入正在“扩展”的 React.Component。组件的第一个泛型参数代表组件的 props,其类型为空({} 对象);第二个泛型参数是组件的 state,其被定义为 TimerState。你可以看到组件有一个名为 timerId 的成员变量,其被初始化为 undefined。这就是为什么它的类型是 number | undefined,代表数字或未定义。

如你所见,在 React 里使用 TypeScript 很容易。 它是 prop-types 更强大的替代品,因为它支持更高级的类型,并且也可以为普通的 JS 代码增加类型。 此外,TypeScript 在编译阶段验证,而 prop-types 在开发阶段进行验证。

为何或为何不使用它?

到目前为止,我假设你已经对对 TypeScript 是什么、可以做什么有了一个大致的了解。 我来继续详细说明关于 TypeScript 可能存在的错误假设。

TypeScript 不是什么?

TypeScript 不是写 JavaScript 的新方法,它所做的就是为 JS 扩展了类型注解的能力。首先,TypeScript 是种类型检查器。现在社区里似乎有一种忽视这一事实的倾向,因为我最近读到过这样的陈述:

TypeScript 很棒。我们 Java 开发者们终于可以也可以在前端领域工作了。

这句话似乎有点道理,但这种态度是有害的。由于存在类型、类和接口以及其他的特性,TypeScript 似乎非常吸引 Java 开发人员。但将 Java(或任何其他深受 OOP 影响的语言)开发人员转移到编写 TypeScript,而不了解 Java 和 JavaScript 之间根深蒂固的差异,可能会导致很大的问题。请记住,JavaScript 代码是否是从 TypeScript 编译而来的事实并不会改变其运行时行为。一旦它被执行,类型信息就没有了 - 它就是纯粹的 JavaScript,包含着它所有的爽点和痛点。 JavaScript 使用基于原型的继承、动态化类型、并具有类型强制转换、受到了函数式语言影响的设计,以及许多其他 Java 程序员没那么熟悉的性质。将 Java 开发人员转移到 TypeScript 时,应该牢记这一点。

另一个误解是“TypeScript 会消灭所有类型错误”。 虽然这对于自有代码来说是正确的,但是你的(非 TypeScript 书写的)依赖项的类型声明可能有缺陷或根本没有类型声明。 这是 TypeScript 为了让开发人员轻松地与 JavaScript 代码交互做出的妥协,不过这一点牺牲了真正的类型安全性。

那么 TypeScript 还能帮到我吗?

向项目添加类型是为其结构书写文档好方法。 它提供了一种方法来记录数据在传递过程中的形态。 同时它也是机器可读的,由机器强制执行。 这意味着 TypeScript 可以得到编辑器很好的支持,让开发人员更容易做代码重构和增强安全检查。

从另一角度,TypeScript 强制你为代码结构写文档。 有时你可能需要暂时停下来,专门去书写类型,让你得到更清晰的代码结构,提高代码的质量。

对我来说,在代码中使用类型可以让代码更容易理解,因为我不需要为了找出数据的结构而在项目里来回挖掘,消除了不少认知复杂性。

总结

在项目中采用 TypeScript 将带来更高质量的代码。 通过添加类型信息,它可以强化项目结构并带来了高级静态类型分析。 同时可以让你更轻松地阅读代码,并增强工作流程。

原文链接

https://simonknott.de/articles/Using-TypeScript-with-React.html

评论

发布