1. TypeScript 类型声明文件
概述:
几乎所有的 JavaScript 应用都会引入许多第三方库来完成任务需求。
这些第三方库不管是否是用 TS 编写的,最终都要编译成 JS 代码,才能发布给开发者使用。
我们知道是 TS 提供了类型,才有了代码提示和类型保护等机制。
类型声明文件:
但在项目开发中使用第三方库时,你会发现它们几乎都有相应的 TS 类型,这些类型是怎么来的呢?类型声明文件 类型声明文件:用来为已存在的 JS 库提供类型信息
。
这样在 TS 项目中使用这些库时,就像用 TS 一样,都会有代码提示、类型保护等机制了。
1.1 TS 中的两种文件类型
TS 中有两种文件类型:1: .ts 文件
2: .d.ts 文件
-
.ts 文件:
-
既包含类型信息又可执行代码
。
-
- 可以被编译为 .js 文件,然后,执行代码。
-
- 用途:编写程序代码的地方。
-
-
.d.ts 文件
-
只包含类型信息的类型声明文件
。
-
不会生成 .js 文件,仅用于提供类型信息
。
-
- 用途:为 JS 提供类型信息。
-
总结:.ts 是 implementation(代码实现文件);.d.ts 是 declaration(类型声明文件)。
如果要为 JS 库提供类型信息,要使用 .d.ts 文件。
1.2 类型声明文件的使用说明
在使用 TS 开发项目时,类型声明文件的使用
包括以下两种方式:
- 使用已有的类型声明文件
- 创建自己的类型声明文件
学习顺序:先会用
(别人的)再会写
(自己的)。
1.2.1 使用已有的类型声明文件
使用已有的类型声明文件
:1 内置类型声明文件 2 第三方库的类型声明文件。
(1)内置类型声明文件
TS 为 JS 运行时可用的所有标准化内置 API 都提供了声明文件。
比如,在使用数组时,数组所有方法都会有相应的代码提示以及类型信息:
实际上这都是 TS 提供的内置类型声明文件。
可以通过 Ctrl + 鼠标左键(Mac:option + 鼠标左键)来查看内置类型声明文件内容。
比如,查看 forEach 方法的类型声明,在 VSCode 中会自动跳转到 lib.es5.d.ts
类型声明文件中。
当然,像 window、document 等 BOM、DOM API 也都有相应的类型声明(lib.dom.d.ts
)。
(2)第三方库的类型声明文件
目前,几乎所有常用的第三方库都有相应的类型声明文件。
第三方库的类型声明文件有两种存在形式:1 库自带类型声明文件
2 由 DefinitelyTyped 提供
。
(2.1) 库自带类型声明文件:比如,axios。
解释:这种情况下,正常导入该库,TS 就会自动加载库自己的类型声明文件
,以提供该库的类型声明。
(2.2) 由 DefinitelyTyped 提供
。
DefinitelyTyped 是一个 github 仓库,用来提供高质量 TypeScript 类型声明
。
可以通过 npm/yarn 来下载该仓库提供的 TS 类型声明包,这些包的名称格式为:@types/*。
比如,@types/react、@types/lodash 等。
说明:在实际项目开发时,如果你使用的第三方库没有自带的声明文件,VSCode 会给出明确的提示。
解释:当安装 @types/* 类型声明包后,TS 也会自动加载该类声明包
,以提供该库的类型声明。
补充:TS 官方文档提供了一个页面,可以来查询 @types/* 库。
1.2.2 创建自己的类型声明文件
- 创建自己的类型声明文件:1
项目内共享类型
2为已有 JS 文件提供类型声明
。
- 项目内共享类型:如果
多个 .ts 文件
中都用到同一个类型,此时可以创建.d.ts
文件提供该类型,实现类型共享
。
操作步骤:
(1) 创建 index.d.ts
类型声明文件。
(2) 创建需要共享的类型,并使用 export 导出
(TS 中的类型也可以使用 import/export 实现模块化功能)。
(3) 在需要使用共享类型的 .ts 文件中,通过 import 导入即可(.d.ts 后缀导入时,直接省略)。 为已有 JS 文件提供类型声明
:
(1) 在将 JS 项目
迁移到 TS 项目
时,为了让已有的 .js 文件有类型声明。
(2) 成为库作者,创建库给其他人使用。
注意:类型声明文件的编写与模块化方式相关
,不同的模块化方式有不同的写法。但由于历史原因,JS 模块化的发展
经历过多种变化(AMD、CommonJS、UMD、ESModule 等),而 TS 支持各种模块化形式的类型声明。这就导致
,类型声明文件相关内容又多又杂。
演示:基于最新的 ESModule
(import/export)来为已有 .js 文件,创建类型声明文件。
开发环境准备:使用 webpack 搭建,通过 ts-loader
处理 .ts 文件。
说明:TS 项目中也可以使用 .js 文件。
说明:在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件
,以提供类型声明。
declare
关键字:用于类型声明,为其他地方
(比如,.js 文件)已存在的变量声明类型,而不是创建一个新的变量
。
- 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字。
- 对于 let、function 等具有双重含义(在 JS、TS 中都能用),应该使用 declare 关键字,明确指定此处用于类型声明。
2. 在 React 中使用 TypeScript
概述:
现在,我们已经掌握了 TS 中基础类型、高级类型的使用了。但是,如果要在前端项目开发中使用 TS,还需要掌握
React、Vue、Angular 等这些库或框架中提供的 API 的类型,以及在 TS 中是如何使用的。
接下来,我们以 React 为例,来学习如何在 React 项目中使用 TS。包括以下内容:
- 使用 CRA 创建支持 TS 的项目
- TS 配置文件 tsconfig.json
- React 中的常用类型
2.1 使用 CRA 创建支持 TS 的项目
React 脚手架工具 create-react-app(简称:CRA)文档地址,默认支持 TypeScript。
创建支持 TS 的项目命令:npx create-react-app 项目名称 --template typescript
。
当看到以下提示时,表示支持 TS 的项目创建成功:
相对于非 TS 项目,目录结构主要由以下三个变化:
- 项目根目录中增加了
tsconfig.json
配置文件:指定 TS 的编译选项
(比如,编译时是否移除注释)。 - React 组件的文件扩展名变为:
*.tsx
。 - src 目录中增加了 react-app-env
.d.ts:React 项目默认的类型声明文件
。
react-app-env.d.ts:React 项目默认的类型声明文件
。
三斜线指令
:指定依赖的其他类型声明文件,types 表示依赖的类型声明文件包的名称。
/// <reference types="react-scripts" />
解释:告诉 TS 帮我加载 react-scripts 这个包提供的类型声明。
react-scripts 的类型声明文件包含了两部分类型:
- react、react-dom、node 的类型
- 图片、样式等模块的类型,以允许在代码中导入图片、SVG 等文件。
TS 会自动加载该 .d.ts 文件,以提供类型声明(通过修改 tsconfig.json 中的 include 配置来验证)。
2.2 TS 配置文件 tsconfig.json
tsconfig.json 指定:项目文件和项目编译所需的配置项
。
注意:TS 的配置项非常多(100+),配置项用到时查文档即可。
- tsconfig.json 文件所在目录为项目根目录(与 package.json 同级)。
- tsconfig.json 可以自动生成,命令:
tsc --init
。
tsconfig.json文件解析
{
// 编译选项
"compilerOptions": {
// 生成代码的语言版本
"target": "es5",
// 指定要包含在编译中 library
"lib": ["dom", "dom.iterable", "esnext"],
// 允许ts编辑器编译js文件
"allowJs": true,
// 跳过声明的类型检查
"skipLibCheck": true,
// es 模块 互操作作,屏蔽ESModule 和Common]s 之间的差异
"esModuleInterop": true,
// 允许通过 import xfrom'y’即使模块没有显式指定 default 导出
"allowSyntheticDefaultImports": true,
// 开启严格模式
"strict": true,
// 对文件名称强制区分文小写
"forceConsistentCasingInFileNames": true,
// 为 switch 语句启用错误报告
"noFallthroughCasesInSwitch": true,
// 生成代码的模块化标准
"module": "esnext",
// 模块解析(查找)策略
"moduleResolution": "node",
// 允许导入扩展名为.json的模块
"resolveJsonModule": true,
// 是否将没有 import/export 的文件视为旧(全局而非模块化)脚本文件。
"isolatedModules": true,
// 编译时不生成任何文件(只进行类型检查)
"noEmit": true,
//指定将 JSX 编译成什么形式
"jsx": "react-jsx"
},
// 指定允许ts处理的目录
"include": [ "src"]
}
除了在 tsconfig.json 文件中使用编译配置外,还可以通过命令行来使用
。
使用演示:tsc hello.ts --target es6。
注意:
4. tsc 后带有输入文件
时(比如,tsc hello.ts),将忽略 tsconfig.json 文件。
5. tsc 后不带输入文件
时(比如,tsc),才会启用 tsconfig.json。
推荐使用:tsconfig.json 配置文件。
2.3 React 中的常用类型
前提说明:现在,基于 class 组件来讲解 React+TS 的使用(最新的 React Hooks,在后面讲解)。
在不使用 TS 时,可以使用 prop-types 库,为 React 组件提供类型检查。
说明:TS 项目中,推荐使用 TypeScript 实现组件类型校验(代替 PropTypes)
。
不管是 React 还是 Vue,只要是支持 TS 的库,都提供了很多类型,来满足该库对类型的需求。
注意:
- React 项目是通过 @types/react、@types/react-dom 类型声明包,来提供类型的。
- 这些包 CRA 已帮我们安装好(react-app-env.d.ts),直接用即可。
React 是组件化开发模式,React 开发主要任务就是写组件,两种组件:1 函数组件 2 class 组件。
函数组件
- 组件的类型
- 组件的属性(props)
- 组件属性的默认值(defaultProps)
- 事件绑定和事件对象
- 函数组件的类型以及组件的属性
- 组件类型和属性
type Props = { name: string; age?: number }
const Hello: FC<Props> = ({ name, age }) => (
<div>你好,我叫:{name},我 {age} 岁了</div>
)
<Hello name="jack" />
实际上,还可以直接简化为(完全按照函数在 TS 中的写法):
const Hello = ({ name, age }: Props) => (
<div>你好,我叫:{name},我 {age} 岁了</div>
)
- 组件属性的默认值(defaultProps)
const Hello: FC<Props> = ({ name, age }) => (
<div>你好,我叫:{name},我 {age} 岁了</div>
)
Hello.defaultProps = {
age: 18
}
实际上,还可以直接简化为(完全按照函数在 TS 中的写法):
const Hello = ({ name, age = 18 }: Props) => (
<div>你好,我叫:{name},我 {age} 岁了</div>
)
- 事件绑定和事件对象的类型
<button onClick={onClick}>点赞</button>
const onClick = () => {}
const onClick1 = (e: React.MouseEvent<HTMLButtonElement>) => {}
再比如,文本框:
<input onChange={onChange} />
const onChange= (e: React.ChangeEvent<HTMLInputElement>) => {}
技巧:在 JSX 中写事件处理程序(e => {}),然后,把鼠标放在 e 上,利用 TS 的类型推论来查看事件对象类型。
class组件
- 组件的类型、属性、事件
- 组件状态(state)
- class组件的类型
type State = { count: number }
type Props = { message?: string }
class C1 extends React.Component {} // 无 props、state
class C1 extends React.Component<Props> {} // 有 props、无 state
class C1 extends React.Component<{}, State> {} // 无 props、有 state
class C1 extends React.Component<Props, State> {} // 有 props、state
- class组件的属性和属性默认值
type Props = { name: string;age?: number }
class Hello extends React.Component<Props> {
//提供属性默认值
static defaultProps: Partial<Props> = {
age: 18
}
render() { //class使用render渲染组件
const { name, age } = this.props
// const { name, age = 18 } = this.props //默认值简写
return <div>你好,我叫:{name},我 {age} 岁了</div>
}
}
<Hello name="rose" />
- class 组件状态(state)和事件
type State = { count: number }
class Counter extends React.Component<{}, State> {
state: State = {
count: 0
}
handleClick= () => {
this.setState({
count: this.state.count + 1
})
}
render(){
return(
<div>
计数器:{this.state.count}
<button onClick={this.handleClick}>+1</button>
</div>
)
}
}