在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://shimo.im/docs/DphtJpQdqQydKtx8

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

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/G9HJfXzzWqsPdrqcwt4A
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券