TypeScript 基础语法
TS 基础使用工具
TS 依赖工具下载
安装依赖包
npm i typescript -g
可以使用以下命令查看ts版本
tsc -v
运行 js 文件
node hello.js
简化TS编译步骤
npm i ts-node -g
使用方式:
ts-node hello.ts
这样当 hello.ts 文件发生改变时,会自动对 ts 文件进行编译并运行,无需直接编译成js
基础
TS 的常用类型
JS 已有的类型有:
- number
- string
- boolean
- null
- undefined
- symbol
- object(数组,对象,函数)
TS 新增的类型有:
- 联合类型、自定义类型(类型别名)
- 接口
- 元组
- 字面量类型
- 枚举
- void
- any
原始类型
TS 中使用 JS 原有的原始类型,可以在变量后增加类型名:
let age: number = 18
let name: string = "abc"
let isLoading: boolean = true
let a: null = null
let b: undefined = undefined
let s: Symbol = Symbol()
类组类型
数组类型为对象类型中的一种
在TS 中,为了区分 object 中的数组,对象等类型,TS 给这些类型也同时新增了对应类型
- 数组类型:number[] 或 Array<number>
联合类型
TS 中允许一个变量支持多种类型,我们可以通过使用联合类型符来定义该变量可被使用为特定类型
- | (竖线)在TS中叫做联合类型(由两个或多个其它类型组成的类型,表示可以是这些类型中的任意一种)
-
let arr: Array<number | string> = [1,'a',2,'b'] let arr: (number | string)[] = [1,'a',2,'b']
类型别名 type
类型别名,即为任意类型起别名
使用场影:当同一类型(复杂的)被多次使用时,可以通过类型别名,简化该类型的使用
举例:
type CustomArray = (string | number)[]
let arr1: CustomArray = ["a","b","c",1,2,3]
let arr2: CustomArray = ["1","2","3",4,5,6]
与 类型别名的区别
- 相同点:都可以给对象指定类型
- 不同点:
- 接口:只能为对象指定类型
- 类型别名:不仅可以为对象指定类型,实际上可以为任意类型指定别名
interface IPerson {
name: string
age: number
sayHi() : void
}
type IPerson = {
name: string
age: number
sayHi() : void
}
type NumStr = number | string
函数类型
函数类型实际上指的是:函数参数和返回值的类型
为函数指定类型的两种方式:
- 单独指定参数、返回值的类型
- 同时指定参数、返回值的类型
单独指定:
function add(num1: number, num2: number): number
{
return num1 + num2
}
const add = (num1: number, num2: number): number => {
return num1 + num2
}
同时指定:
在单独指的时,我们并没有对方法名“add”分配类型,所以同时指定时,则是指把方法名增加所有类型,如下类子
const add: (num1: number, num2: number) => number = (num1, num2) => {}
解析:在声明方法时,把所有类型都预先给方法名分配好了,后面的函数体就不需再定义类型了,这种方法只能对箭头函数有效
void 类型
void 是一个特殊的类型,代表【没有返回值】类型,如果我们需要声明一个方法是没有返回值的,那么我们可以指定为 void
function greet(name: string): void {}
可选参数类型
TS 中支持可选参数,当使用函数实现某个功能时,参数可以传也可以不传。这种情况下,在给函数参数指定类型时,就可以用到可选参数了。
可选参数可以用【?:】来表示
示例如下:
function slice(start ?: number, end ?: number): void {}
这样,在调用方法传参时,既可以 slice() 也可以 slice(1) 也可以 slice(1,2) 三种传参方式
注意:可选参数只能出现在参数列表的最后,也就是说可选参数后面不能再出现必选参数了。
对象类型
Js 中的对象是由属性和方法构成的,而TS中对象的类型就是在描述对象的结构(有什么类型的属性和方法)
示例:
let person: {name: string; sex: number; sayHi(str: string):void} = {
name:"jack",
sex: 1,
sayHi(str) {}
}
let person: {
name: string
sex: number
sayHi():void 或 sayHi()=>void
} = {
name:"jack",
sex: 1,
sayHi() {}
}
注意:声明对象类型中的分隔符使用分号【;】分隔,而不是逗号
- 如果一行代码只指定一个属性类型,或者把类型定义时使用多行书写方法,可以去掉【;】
- 方法定义中类型也可以使用箭头方式写法 {sayHi() => void}
对象类型中使用可选类型
在对象类型中,如果出现可传可不传的类型时,我们同样可以使用可选类型
function axios(config:{ url: string; method?: string}) : void {
}
接口类型 interface
当我们对一个对象类型被多次使用时,每声明一次该对象类型的变量时,就得重新定义一个对象内部的类型,这样会显得非常麻烦,而接口类型,则是针对对象类型的复用目的。
interface IPerson {
name: string
age: number
sayHi(): void
}
// 使用该对象类型
let person: IPerson = {
name: "",
age: 1,
sayHi() {}
}
与 类型别名的区别
- 相同点:都可以给对象指定类型
- 不同点:
- 接口:只能为对象指定类型
- 类型别名:不仅可以为对象指定类型,实际上可以为任意类型指定别名
interface IPerson {
name: string
age: number
sayHi() : void
}
type IPerson = {
name: string
age: number
sayHi() : void
}
type NumStr = number | string
接口继承
如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
比如下面两个接口:
interface Point2D {x: number; y: number}
interface Point3D {x: number; y: number; z: number}
其中 x 和 y 在两个接口中是相同的,那么我们就可以使用接口的继承来复用这些相同的类型规范
interface Point2D { x: number; y: number }
interface Point3D extends Point2D { z: number }
元组
元组类型是另一种类型的数组,它是这个数组是特殊的,它可以指定数组内每一个成员的类型,且受元组指定成员数限制。
let position: [number, number] = [11, 22]
元组可以确切的知道多少个元素,和这些元素对应的类型
与数组的区别:
- 数组只能定义一个类型,其成员都是该类型,而元组可以对每一个成员指定类型
- 数组不能限制成员数,而元组限制成员数
类型推论
在TS中,某些没有明确指出类型的地方,TS的类型推论机制会帮助提供类型。
也就是说,在某些地方,类型注解可以省略不写
2种常见场景下
- 声明变量判初始化时,如 let age = 18, 我们可以不指定类型,TS会帮我识别出来是 number,同时依然保护类型检查生效
- 决定函数返回值时
类型断言 as
有时候我们会比TS更加明确的知道一个值的类型时,我们可以使用类型断言来指定更具体的类型。使用 as 来强行转换为我们想要的类型
举例:
<a href="www.tzming.com" id="link">
const aLink = document.getElementById("link")
当我们使用 getElementById 获取到的DOM对象时,返回的是 HTMLElement 类型,而 HTMLElement 是一个父类对象,它只包含所有标签的共公属性,但并没有 a 标签特有的 href 属性
若此时我访问 aLink.href 时,因为类型检查的原因,必定会报错,因为 HTMLElement 类型的对象找不到 href 属性
此时我们可以通过强转类型的方 式对 aLink 的类型强转为 HTMLAnchorElement 类型
方法一:
const aLink = document.getElementById("link") as HTMLAnchorElement
方法二:
const aLink = <HTMLAnchorElement >document.getElementById("link")
技巧:如何知道每个标签的属于自己的类型?
可以通过浏览器中,先选中 想要知道 js 类型的标签(浏览器会显示 $0)
然后在控制台上,通过 console.dir($0) 打印该标签以 js 对象的方式显示,它的 prototype 原型对象类型就是该标签的 js 自己的类型
字面量类型
在JS中,存在一种常量,const 就是用于定义一个常量,从理论上来说,常量被定义值后,就永远只能是这个值,所以我们可以把一个常量值,当作这个常量的类型:
// 当被设定为字面量类型时, 那么值有且仅有这个值
const str: "hello" = "hello"
let str2: "a" | "b" | "c" = "a" // 值就被限制在 "a", "b" , "c" 之间了
字面量类型一般和联合类型一起使用,用于表示一组明确的可选值的列表。
枚举类型
枚举类似于字面量类型+联合类型组合的功能,也可以表示一组明确的可选值
定义方式:
enum Direction {
Up = "Up",
Down = "Down",
Left = "Left",
Right = "Right"
}
使用枚举
let dir: Direction = Direction.Up
枚举定义的成员,可以作为一个常量值使用,但是会默认为整数自增 0,1,2,...
除非明确的声明了枚举成员的值。
如果部分成员设定了值,而其它成员没有设定值,则其它值会以设定值的成员的值为原点进行自增,如下:
enum Direction {
Up = 10,
Down, // 11
Left, // 12
Right // 13
}
如果设定的值是字符串,那么枚举必须每个成员都要声明值。
枚举特性
枚兴是TS为数不多的非JS类型级扩展的特性之一,因为其他类型仅仅被当做类型,而枚举不仅用作类型,还提供值
也就是说,其它的类型会在编译为JS代码时会自动移除,但是枚举类型会被编译为JS代码
enum Direction {
Up = 10,
Down, // 11
Left, // 12
Right // 13
}
JS 会被编译为:
var Direction;
(function (Direction) {
Direction["Up"] = 10
Direction["Down"] = 11
Direction["Left"] = 12
Direction["Right"] = 13
})(Direction || (Direction = {}))
一般情况下,推荐使用字面量类型+联合类型组合的方式,因为相比枚举,这种方式更加直观、简洁、高效
any 类型
any 是 TS 为了解决兜底类型的问题,不推荐使用 any,因为使用 any 类型后,就失去了 TS 类型保护的优势了
以下是会出现隐式any类型的情况:
- 当声明变量时不提供类型也不提供默认值时,其类型为any
- 函数参数不加类型时,其参数值也为any
typeof 类型查询
JS 中提供一种方法可以检测变量值属于什么类型
typeof "abc" // string
TS 中的类型复用
在TS也提供了 typeof 操作符,但和 JS 有一定的区别,可以在类型上下文中引用变量或属性的类型
在TS中可以通过【对象】的结构来判定类型,并把这个类型应用在其它变量值中:
let p = {x:1, y:2} // 在这里定义了变量 p 并把p声明了一定的结构类型
let p1: typeof p // 声明 p1 时,可以使用 typeof p 来获取 p 的类型,并把这个类型应用在 p1 中
类似的使用方法还可以应用在方法上:
let p = {x:1, y:2} // 在这里定义了变量 p 并把p声明了一定的结构类型
function handler (point: typeof p) {} // 这样方法中的 point 类型就会随着 p 的结构变化,而变化它的类型
TS中的 typeof 类型复用只能用在变量的类型查询上,不能使用在方法的类型查询上,也就是说不能获取方法的类型:
function add(): number {}
let p: typeof add() // 不可用
TS 高级类型
Class 类
TS 中支持 ES2015 中引入的 class 关键字,并为其添皮了类型注解和其他语法
基本使用:
class Person {}
const p = new Person() // ts 会类型推断为 Person 类型
class不仅提供了class的语法功能,也作为一种类型存在
在class类中我们可以声明变量,和方法:
class Person {
age: number // 如果不设定值时,可以先设定类型
gender: = "男" // 如果有设定值时, TS 会自动类型推断
// 类的构造函数实例化
constructor(age: number, gender: string) {
this.age = age
this.gender = gender
}
// 还可以设定自定义实例方法
xxx(n: number): void {}
}
// 调用
const p = new Person(18,"男")
p.xxx(2)
class 的继承 extends
JS中只有 extends ,extends 可以继承父类的属性和方法
class Anumal {
move() { console.log("走两步") }
}
class Dog extends Animal {
back() { console.log("汪!") }
}
const dog = new Dog()
dog.move()
dog.back()
class 的继承 implements
在TS中我们可以使用 interface 接口对某一些变量做类型约束,同时TS也可以在 class 类中实现接口的方法;
// 声明定义接口,和一个抽象方法
interface Singable {
sing() : void
}
// 类中实现接口中的方法
class Person implements Singable {
sing(): void {}
}
class 类的权限
TS 中的 class 类也包含访问权限,它的权限包含
- public 公共 (默认):所有人都能访问
- private 私有: 只有自己可以访问
- protected 受保护的:只有自己及子类可以访问
class Person {
public sing() : void {} // 外面可以被访问调用
private sing() : void {} // 只允许自身访问调用
protected sing() : void {} // 只允许自身及子类访问调用
}
除了可孕性修饰符之外,还有一个常见修饰符,readonly 只读修饰符
readonly用来防止在构造函数之外对属性进行赋值,只能用于修饰属性不能修饰方法
class Person {
readonly age: number = 18 // 允许设置默认值,如果不指定类型,那么该变量将是字面量类型
constructor(age: number) {
this.age = age // 允许在构造函数中设置新值
}
}
除了这两个地方可以设置值以外,所有地方都不能再修改值了。
P.S readonly 除了可以使用在 class 上,还可以使用在对象和接口上
interface IPerson {
readonly name: string
}
let obj: IPerson = {
name: "jack"
}
let obj: {readonly name: string}= {
name: "jack"
}
obj.name = "xx" // 这里就报错了,不可以修改
类型兼容性
类型兼容性有两种类型系统:
- Structural Type System (结构化类型系统)
- 结构化类型系统指的是,判断一个类型是否符合要求,不是看这个类型定义整体,而是看这个类型里面的结构,TS 使用的是本类型系统
- 举例子:
-
// 定义了两个不同的接口,但是接口内的结构是一样的 interface Point { x: number y: number } interface Postation { x: number y: number } // 此时我定义了一个变量 pos1 ,它的类型是 Point let pos: Point = { x: 1, y: 2 } // 再定义一个变量 pos2 ,它的类型是 Postation let pos: Postation = { x: 3, y: 4 } // 基于结构化类型系统的规范,虽然 pos1 和 pos2 是两个不同的类型,但是因为它们的类型结构是一样的,所以我们可以让 pos1 与 pos2 随意交换数据 let p: Point = new Postation() // 结构相同不会报错 pos1 = pos2 // 在TS 中不会报错
- Norminal Type System (标明类型系统)
- 标明类型系统指的是,判断一个类型是否符合要求,不是看这个类型定义的结构,而是看这个类型定义的整体,Java、C#类型的语言使用的是该类型系统
- 举例子:
-
// 定义了两个不同的接口,但是接口内的结构是一样的 interface Point { x: number y: number } interface Postation { x: number y: number } // 此时我定义了一个变量 pos1 ,它的类型是 Point let pos: Point = { x: 1, y: 2 } // 再定义一个变量 pos2 ,它的类型是 Postation let pos: Postation = { x: 3, y: 4 } // 基于标明类型系统规范,本身 pos1 的类型是 Point,pos2的类型是Postation,即使两种类型的结构是一样的,但是互相不兼容 let p: Point = new Postation() // 类型不同会报错 pos1 = pos2 // 标明类型系统这样会报错,因为不能把Postation 类型数据存入到Point类型的变量中
结构化类型系统兼容性
因为结构化类型系统的特殊性,它看待一个类型不是以对象作为参考点,而是以对象内的结构作为参考,此时它就有更高的弹性,在结构化类型系统中只要要求两个类型有相似之处就可以互相兼容,比如下面的两种类结构依然兼容:
class Point2D {
x: number
y: number
}
class Point3D {
x: number
y: number
z: number
}
// 在此处中,Point3D 满足了 Point2D 类型所要求的结构,那么 Point3D 值也可以传给 Point2D 类型的变量
let pos: Point2D = new Point3D() // 此处不会报错
但是如果反过来就不可以了
class Point2D {
x: number
y: number
}
class Point3D {
x: number
y: number
z: number
}
// 在此处中,因为Point2D不能完全满足Point3D类型的所有要求,所以不能使用Point2D对象来填充Point3D类型的数据,否则Point3D多出来的属性就无法被满足
let pos: Point3D = new Point2D() // 此处会报错
接口也同样支持这样的兼容性
interface Point2D {
x: number
y: number
}
interface Point3D {
x: number
y: number
z: number
}
let p1: Point2D
let p2: Point3D
p1 = p2
函数间的兼容性
函数之间的兼容性比较复杂,需要考虑
- 参数个数 => 参数多的兼容参数少的(或者说参数少的可以赋值给多的)
- 参数类型 => 相同位置的参数类型要相同(原始类型)或兼容(对象类型)
- 返回值类型 => 相同的方法返回值类型或兼容类型
参数个数:
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2 = f1 // 参数少的类型可以赋给参数多的
通俗的讲:当我们声明一个方法类型时,里面有多个参数,当我创建一个类型的方法时,我可以不传任何参数,也可以传所有参数,但肯定不能传比方法类型还要多的参数。
所以当forEach(()=>{})的方法中,就是传了一个没有参数的方法给带有3个参数的方法类型,是允许的,但是 forEach((a,b,c,d)=>{}) 就不允许了,因为传了4个参数
返回值类型:
成员多的能赋给成员少的,这是因为在兼容面前,需要满足前者的所有属性要求
type F7 = () => {name: string}
type F8 = () => {name: string; age: number}
let f7: F7;
let f8: F8
f7 = f8
交叉类型
交叉类型(&)功能类似干接口继承extends,用于组合多个类型为一个类型(常用于对象类型)
// 定义两个独立的类型
interface Person { name: string }
interface Contact { phone: string }
// 使用交叉类型来合并两个类型为一个类型
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name: "jack",
pthone: "123..."
}
交叉类型与继承的区别
- 相同点:都可以实现对象类型的组合
- 不同点:两种方式实现类型组合时,对于同名属性之间,处理类型型冲突的方式不同
- 在继承中如果发生冲突时,会报错
- 在交叉类型时如果发生冲突时,会把两个冲突的类型合并为联合类型
以下代码使用继承合并类型,继承时会发生报错
interface A {
fn: (value: number) => string
}
// 类型不兼容
interface B extands A {
fn: (value: string) => string
}
以下代码使用交叉类型合并类型,不会发生错误,合并后该类型将成为两个冲突类型的联合类型
interface A {
fn: (value: number) => string
}
interface B {
fn: (value: string) => string
}
type C = A & B
// C 的类型变成如下,类似于方法重载
C = fn(value: string | number) => string
泛型 和 keyof
泛型是可以在保证类型安全前提下,让函数等与多种类型一起工作,从而实现复用,常用于函数、接口、class中
function id<T>(value: T): T {return value}
const num = id<number>(10)
- 语法:在函数名称的后面添加 <>(尖括号),尖括号中指定具体的类型 number
- 当传入number类型后,这个类型就会被函数声明时指定的类型变量T捕获到
- 此时,T的类型就是number,所以,函数id参数和返回值的类型也都是number
通过泛型就做到了多种不同的类型一起工作,实现了复用的同时保证了类型安全
同时通过TS的类型推断,可以通过传入的参数类型动态推断出需要填充的泛型类型
function id<T>(value: T): T {return value}
let num = id(10) // 会自动识别为 number 类型 => id<10>(10) 与手动配置不同,它会用字面量类型作为泛型,使用不受影响
泛型约束
默认情况下,泛型函数的类型变量 <T> 可以代表多个类型,这导致无法访问任何属性比如:
function id<T,R>(value: T, key: R): T {return value.length} // value.length 无法访问
这时我们可以把泛型类型的范围进行收缩
- 指定更加具体的类型,即可以定义为一种带有共通方法或属性的模式,比如数组类型必定有 length 属性
- 添加约束,使用 extends 修饰符来限定该泛型类型只允许声明在某个父类型之下
-
// 声明一个接口,约束了这个类型必须有 length 属性才可以 interface ILength { length: number } function id3<T extends ILength>(value: T): number { return value.length } 这样的话,当调用方法时声明确切类型时,就会检查该传入类型是否带有length
Keyof
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
比如,创建一个函数来获取对象中的属性的值:
// 通过获取 T 类型中获取其键限制成为K的类型
function getProp<T, K extends keyof T>(value: T, key: K): void {
console.log(value[key])
}
let person = {name:"jack",age:18}
// 这时第二个参数传入的只允许是传入的第一个参数对象的键名
getProp(person,"name")
泛型接口
接口也可以配合泛型来使用,以增加其灵活性,增强复用性
// 创建一个带有泛型的接口
interface IdFunc<T> {
id(value:T):T
}
// 使用泛型接口
let obj: IdFunc<number> = {
id(value){
return 1
}
}
泛型类
class也可以配合泛型来使用
class Person<T> {
name: T
eat():void {
console.log(this.name + "在吃饭")
}
}
let p = new Person<string>()
p.name = "TZMing"
p.eat()
泛型工具
TS 内置了一些常用的工具类型,来简化TS中的一些常见操作
它们都是基于泛型实现的,(泛型适用于多种类型,更加通用),并且是内置,可以直接在代码中使用
Partial<Type>
用来构造(创建)一个类型,将Type的所有属性设置为可选类型
interface Props {
id: string
children: number[]
}
type props = Partial<Props>
prop 等价于以下的接口:
interface Props {
id?: string
children?: number[]
}
Readonly<Type>
用来构造一个类型,将Type的所有属性都设置为 readonly(只读)
interface Props {
id: string
children: number[]
}
type props = Readonly<Props>
等价于下面的接口
interface Props {
readonly id: string
readonly children: number[]
}
Pick<Type, Keys>
拿Type中选取一组属性来构造新的类型
interface Props {
id: string
title: string
children: number[]
}
type props = Pick<Props, "id" | "title">
等价于拿了 Props 中的id 和 title 来组合成一个新的类型
interface Props {
id: string
title: string
}
Record<Keys, Type>
构造一个对象类型,属性键为 Keys, 属性类型为 Type
// 生成一个接口类型,接口中有 name 和 age 两个属性,它们的值类型要求为 string
type propsRecord = Record<"name" | "age", string>
相当于下面的类型
interface propsRecord {
name: string
age: string
}
- Record 工具类型有两个类型变量
- 1表示对象有哪些属性
- 2表示对象属性的类型
- 构建的新对象类型 RecordObj 表示:这个对象有三个属性,分别为“a”,"b" ,"c",属性值的类型是 sting[]
索引签名类型
绝大多数情况下,我们都可以在使用对象前就确定对象的结构,并为对象添加准确的类型。
但是我们有些时候,我们在当前无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),这时我们就可以使用到索引签名类型
interface AnyObject {
[key: string]: number // JS 中定义了对象的键为字符串类型的
}
let obj:AnyObject = {
sex:1,
age:2
}
- 使用 [key:string]来约束该接口中允许出现的属性名称。表示只要是 string 类型的属性名称,都可以出现在对象中
- 这样,对象 obj 中就可以出现任意多个属性
- key 只是一个占位符,可以换成任意合法的变量名称
索引查询类型
索引查询类型是用来查询属性的类型,我们可以使用类似读取数组的方式读取到对象类型中的属性中的类型
type Person = {
name:string
age:number
sex:string
}
// 获取 Person 类型中的 name 属性的类型
type nameProp = Person["name"]
利用上面的方式,我们可以对类型中的属性值类型做各种操作,比如把所有属性变成可选属性:
type Person = {
name:string
age:number
sex:string
}
// 定义一个类型,把传入的类型遍历一次键,换成可选属性,再利用索引查询类型获取该类型中的属性的类型
type MyTool<T> = {
[key in keyof T] ?: T[key]
}
type toolProp = MyTool<Person>
映射类型
映射类型是指基于旧类型创建新类型(对象类型),减少重复、提升开发效率
type keys = "x" | "y" | "z"
type types = {
[key in keys]: number
}
等价于
type props = {
x: number
y: number
z: number
}
- 1. 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]。
- 2. key in keys表示 Key 可以是 keys 联合类型中的任意一个,类似于forin(letk in obj)。
- 3.使用映射类型创建的新对象类型 Type2 和类型 Type1 结构完全相同。
- 4.注意:映射类型只能在类型别名中使用,不能在接口中使用。
通过 keyof 来获取对象中的属性名作为创建类型的属性键值
假如我希望拿一个类型中的属性键值作为一属性名创建一个新的类型时,可以使用 keyof 方式获得该类型中的键值
type person = {
name: string
age: number
sex: string
}
type personProps = {
[key in keyof person]: number
}
等价于
type person = {
name: number
age: number
sex: number
}
其它使用方式还有同时查询多个索引的类型
type Props = {
a:string
b:number
c:boolean
}
type Type = Props["a" | "b"]
// Type 类型则获取到 Props 中的 a 和 b 属性的类型
// 所以 Type 类型为 string | number
获取所有属性类型到一个类型中
type Props = {
a:string
b:number
c:boolean
}
type Type2 = Props[keyof Props]
// 获取了 Props 中所有的属性的类型加到 Type2 中
// Type 类型为 string | number | boolean
类型声明文件
今天几乎所有的JavaScript应用都会引入许多第三方库来完成任务需求。这些第三方库不管是否是用TS编写的,最终都要编译成JS代码,才能发布
布给开发者使用。
我们知道是TS 提供了类型,才有了代码提示和类型保护等机制。
但在项目开发中使用第三方库时,你会发现它们几乎都有相应的TS类型,这些类型是怎么来的呢?类型声明文件
类型声明文件:用来为已存在的JS库提供类型信息。
这样在TS 项目中使用这些库时,就像用TS一样,都会有代码提示、类型保护等机制了。
TS 中的文件类型
TS 中有两种文件类型,分别如下:
- ts 文件
- 既包含类型信息又可执行代码
- 可以被编译为 .js 文件,然后执行代码
- 用途:编写程序代码的地方
- .ts 是 implementation(代码实现文件)
- .d.ts 文件
- 只包含类型信息的类型声明文件
- 不会生成 .js 文件,仅用于提供类型信息
- 用途:为 JS 提供类型信息。
- .d.ts 是 declaration (类型声明文件)
TS 中的类型文件使用
在以往建立好的 JS 众多第三方库中,如果要让这些第三方库有TS类型提示和保护的功能,那么这些库都需要重写编写对应的 .d.ts 文件
在目前解决不 JS 库中的 TS 类型提示问题上,有两种解决方案
- 第三方库官方内置自有TS类型文件
- 也就是三方库官方为他们的JS库产品添加 .d.ts 类型文件,比如Axios 就是他们自己提供类型文件的。
- 由 DefinitelyTyped 提供
- DefiniteltTyped 是一个 github 仓库,用来提供高质量的TS 类型声明文件,目前DefinitelyTyped 所提供的类型声明文件几乎函盖了所有三方JS库,比如lodash用的就是第三方的类型声明文件,其官方并没有提供类型声明文件
- https://github.com/DefinitelyTyped/DefinitelyTyped
- 可以通过 npm 来下载该仓库提供的TS类型声明包,这些包的名称格式为: @type/*
- 比如: @type/react、 @types/lodash 等
- 当安装 @types/* 类型声明包后,TS也会自动加载该类声明包,以提供该库的类型声明
- TS官方文档提供了一个网址,可以来查询 @types/* 库(因为npm已提供是否有声明文件,所以TS官方已停用查询)
- https://typescriptlang.org/dt/search
TS 中的类型文件创建
创建自己的类型声明文件
- 项目内共享类型
- 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享
- 创建 index.d.ts 类型声明文件
- 创建需要共享的类型,并使用 export 导出(TS中的类型也可以使用 import/export实现模块化功能)
- 在需要使用共享类型的 ts 文件中,通过 import 导入即可(.d.ts后缀导入时,直接省略)
- 如果多个 .ts 文件中都用到同一个类型,此时可以创建 .d.ts 文件提供该类型,实现类型共享
- 为已有 JS 文件提供类型声明
- 类型声明文件的编写与模块化方式相关,不同的模块化方式有不同的写法。但由于历史原因,JS模块化的发展经
历过多种变化(AMD、CommonJS、UMD、ESModule等),而TS支持各种模块化形式的类型声明。这就导致,类型
声明文件相关内容又多又杂。 - 基于最新的ESModule (import/export)来为已有.js文件,创建类型声明文件。
- 使用webpack搭建,通过ts-loader处理.ts 文件。
- 类型声明文件的编写与模块化方式相关,不同的模块化方式有不同的写法。但由于历史原因,JS模块化的发展经
为已有 JS 文件提供类型声明
在导入 .js 文件时,TS 会自动加载与 .js 同名的 .d.ts 文件,以支持类型声明
declare 关键字:
- 用于类型声明,为其他地方(比如 .js 文件)已存在的变量声明类型,而不是创建一个新的变量
- 对于 type、interface 等这些明确就是 TS 类型的(只能在 TS 中使用的),可以省略 declare 关键字
- 对于let、function 等具有双重含入(在Js、TS中都能用),应该使用 declare 关键字,明确指定此处用于类型声明
// index.js 文件
let count = 10
let username = "TZMing"
let position = {
x: 0,
y: 0
}
function add(x, y) {
return x + y
}
function changeDirection(direction) {
console.log(direction)
}
const formatPoint = point => {
console.log("当前坐标", point)
}
export {count, username, position, changeDirection, formatPoint}
为已有的js 文件中的变量增加 TS 类型,使用 js 同文件名的 .d.ts 文件
// index.d.ts 文件
// 对 js 文件中的 let 变量提供类型,使用 declare 关键字,声明这是为 js 已有的变量设定类型,而非定义新的类型
declare let count: number
declare let username: string
// 对于接口类型来说,不需要声明 declare 关键字,因为 js 中并没有 interface ,明确知道是TS类型
interface Point {
x: number
y: number
}
// 使用 declare 关键字,声明这是为 js 已有的变量设定类型,而非定义新的类型
declare let position: Point
// 使用 declare 关键字,声明这是为 js 已有的变量设定类型,而非定义新的类型
declare function add(x: number, y: number): number
// 使用 declare 关键字,声明这是为 js 已有的变量设定类型,而非定义新的类型
declare function changeDirection(direction: "up" | "down" | "left" | "right"): void
// type 类型不需要使用 declare 关键字,因为 js 中并没有 type ,明确知道是TS类型
type FormatPoint = (point: Point) => void
declare const formatPoint: FormatPoint
// 注意:类型提供好以后,需要使用 模块化方安中提供的模块化语法,来导出声明好的类型
// 然后,才能在其他 .ts 文件中使用
export {count, username, position, add, changeDirection, formatPoint}
下次需要引入该js文件时,就能提供TS类型约束和提示了
TS 配置文件 tsconfig.json
tsconfig.json 指定:项目文件和项目编译所需的配置项
注意:TS的配置项非常多,目前已超过100项,其它配置项可以到下面网址进行查询
http://typescriptlang.org/tsconfig
生成 tsconsif.json 文件
tsc --init
当使用 tsc 对 ts 文件进行编译时,不会使用 tsconfig 配置进行编译
// 此时只会使用默认的编译配置,不会使用 tsconfig 配置
tsc hello.ts
但当不添加指定编译文件时,tsc 会默认使用当前目录下的 tsconfig.ts 文件作为编译配置
// 会编译所在目录的ts文件并使用 tsconfig 配置
tsc
共有 0 条评论