在React中使用TypeScript

2019 年 11 月 22 日

在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


2019 年 11 月 22 日 08:001616

评论

发布
暂无评论
发现更多内容

office 365激活,总是自动变成专业版2019

wood

Office Office 365

Flink 中的应用部署:当前状态与新应用模式

Apache Flink

flink

源码分析 | 咋嘞?你的IDEA过期了吧!加个Jar包就破解了,为什么?

小傅哥

Java 字节码插桩 asm bytebuddy

非科班学习编程一定得知道这几个网站!

我是程序员小贱

Python中的单下划线和双下划线使用场景

王坤祥

Python Python基础知识 Python基础

为什么修改hosts不立即生效?--浏览器DNS缓存机制分析

陈磊@Criss

手撕二分查找及其变种,就是干!

我是程序员小贱

一文了解对称加密与非对称加密

我是程序员小贱

安全

记一次腾讯云(西安)后台开发面试经历

z小赵

面试 分布式 高并发

数据治理第一步,摆脱“手工作坊”

KAMI

大数据 数据治理 数据开发 数据平台

Windows AD 保姆级配置NTP服务器教程

Young先生

时间 AD ntp Windows Server 2012 R2

Git设置分支保护实现CodeReview卡点

陈磊@Criss

想要成功,你需要的是目标与动机,目标是你的助攻,动机是你的爱人。

叶小鍵

成功学 心理学 海蒂·格兰特·霍尔沃森

2.3.1 理解动态代理 -《SSM深入解析与项目实战》

谙忆

Django的Models更新时,不触发Signals解决办法

Young先生

django singals 信号机制 update 更新

从北京降雨的复盘中,我发现了企业SD-WAN网络的秘密

脑极体

中本聪原始比特币论文解读:点对点的电子现金系统

韩超

比特币 区块链

2.3.2 JDK动态代理 -《SSM深入解析与项目实战》

谙忆

图解23种设计模式——前方高能,前端切图仔请务必抓好方向盘

执鸢者

typescript 前端 设计模式

MySQL 架构与历史

多选参数

MySQL 数据库 MySQL优化

为什么会是Docker?

flyer0126

Docker

微博基于 Flink 的机器学习实践

Apache Flink

flink

Go: Goroutine, 系统线程和CPU管理

陈思敏捷

go golang mpg

契约测试:解决微服务测试的问题

陈磊@Criss

手把手教你从零开始使用python编写大型冒险类游戏01之游戏介绍

Geek_8dbdc1

用故事去理解「文件 I/O」

小林coding

操作系统 异步 文件系统 同步 非阻塞网络I/O

微服务框架 - 模块功能设计篇

superman

[修复 Webpack 官方 Bug] 提取CSS时的依赖图修正

分一

前端 webpack 编译优化 源码刨析

芯片破壁者(十二.下):青瓦台魔咒与半导体“死亡谷”

脑极体

Serverless:为我们到底带来了什么

刘宇

Serverless 云原生

Dubbo2.7试用

心平气和

dubbo 灰度 hessian

在React中使用TypeScript-InfoQ