安装工具包
Node.js和浏览器不认TS,只认JS代码,所以需要转换TS为JS,才能运行。
// 全局安装
npm i -g typescript
// 查看版本
tsc -v
运行TS的步骤
使用ts-node包,直接在Node.js中执行TS代码
// ts-node包提供了ts-node命令
npm i -g ts-node
使用方式(该命令会将TS转成JS再运行)
Ts-node hello.ts
TypeScript常用类型
TS是JS的超集,所有JS代码都是TS代码,只是增加了类型系统
// 类型注解
let name: string = ‘David’
let age: number = 18
let s: symbol = Symbol()
JS已有类型:number/string/boolean/null/undefined/symbol/object(包括数组、对象、函数等)
TS新增类型:联合类型、自定义类型(类型别名)、接口、元组、字面量、枚举、void、any等
数组类型
// 推荐第一种写法
let numArr: number[] = [1,2,3]
let strArr: Array<string> = [‘a’,’b’,’c’]
// 联合类型
let arr: (number | string)[] = [1, 2, 3, ‘a’, ‘b’]
类型别名
type customArray = (number | string) []
let arr1: customArray = [1,2, ‘3’]
let arr2: customArray = [1,2,’c’]
函数类型
function add1(num1: number, num2: number): number {
return num1 + num2
}
add1(1, 2)
// 另一种写法
const add2 = (num1: number, num2: number): number => {
return num1 + num2
}
add2(2, 3) // 5
// 无返回值函数,可选参数(可选参数只能位于必选参数后面)
function greet(name?: string): void => {
console.log(‘Hello ’, name)
}
greet(‘Frank’) // Hello Frank
greet() // Hello
对象类型
let person: {
name: string
age: number
sayHi(): void
greet?: (name: string) => void
} = {
name: ‘Frank’,
age: 18,
sayHi() {},
greet(name) {}
}
对象可选属性
function myAxios(config: { url: string; method?: string }) {}
myAxios({url: ''})
接口
interface personIF {
name: string
age: number
sayHi(): void
}
let person: personIF = {
name: 'Daniel',
age: 19,
sayHi() {}
}
接口和类型别名的对比
// 接口:只支持对象类型
interface personIF {
name: string
age: number
sayHi(): void
}
// 类型:可以为任意类型
type personIF = {
name: string
age: number
sayHi(): void
}
type numStr = number | string
接口的继承
interface point2D {
x: number
y: number
}
// point3D继承了point2D的属性方法
interface point3D extends point2D {
z: number
}
let p3: point3D = {
x: 1,
y: 2,
z: 3
}
元组类型
// 数字数组类型不严谨(可以出现多个数字)
let position1: number[] = [123, 45]
// 元组类型更严谨(特殊数组类型,明确数量和类型)
let position2: [number, number] = [123, 45]
类型推论
// 声明变量,立即赋值,可以省略类型
let name = 'Daniel'
let age = 18
// 赋值成其它类型,会报错
age = '19'
// 如果初始时没有赋值,则需要指定类型
let email: string
email = '123@abc.com'
// 下面这种情况也可以省略函数返回类型(参数类型不要省)
function add(num1: number, num2: number) {
return num1 + num2
}
类型断言
<a href="http://www.itcast.cn" id="link">链接</a>
// a标签的类型就是HTMLAnchorElement
const aLink = document.getElementById('link') as HTMLAnchorElement
aLink.href
字面量类型
// 变量str1的类型为string
let str1 = 'Hello TS'
// 变量str2的类型为'Hello TS'
const str2 = 'Hello TS'
//等价于
const str2: 'Hello TS' = 'Hello TS'
// 下面这样写会报错
let age: 18 = 19
字面量类型的使用场景
字面量类型一般用于表示一组明确的可选值列表
// 给参数指定4个字面量的值
function changeDirection(direction: 'up' | 'down' | 'left' | 'right') {
console.log(direction)
}
枚举类型
// 枚举中的值以大写字母开头(默认值是0开始的自增数值)
enum Direction { Up, Down, Left, Right }
function changeDirection(direction: Direction) {
console.log(direction)
}
// 调用枚举成员
changeDirection(Direction.Left)
// Down为11,Left为12,Right为13
enum Direction { Up = 10, Down, Left, Right}
字符串类型枚举
// 字符串枚举必须全部声明(只有数字类型枚举才有自增长行为)
enum Direction = { Up = 'UP', Down = 'DOWN', Left = 'LEFT', Right = 'RIGHT' }
一般情况下,推荐使用字面量类型+联合类型组合的方式,相比枚举更直观、简洁、高校。
any类型
不推荐使用any!除非临时使用。
let obj: any = { x: 0 }
// 等价于 let obj; obj = { x: 0 }
// 以下操作都不会有任何类型的错误提示
obj.bar = 100
obj()
const n: number = obj
// 函数的参数不指定类型也是any
function add(num1, num2) {}
typeof运算符
let p = { x: 1, y: 2 }
// 给函数参数指定类型
function formatPoint1(point: { x: number; y: number }) {}
// 下面的方法等价于上面的方法,快速使用p对象进行参数类型赋值
function formatPoint2(point: typeof p)
formatPoint2({ x: 3, y: 100 })
Class类
// 创建类
class Person {
// 第一种方法,指定类型(没有默认值)
age: number
// 第二种方法,指定默认值(推断类型)
gender = '男'
// 第三种方法不推荐
gender: string = '男'
}
// 使用类
const p1 = new Person()
const p2: Person
Class类的构造函数
一般用于实例属性的初始化
class Person {
age: number
gender: string
constructor(age: number, gender: string) {
// 构造函数不允许有返回值;将参数赋值给实例属性
this.age = age
this.gender = gender
}
}
// 两个参数对应构造函数里的值
const p = new Person(18,'男')
console.log(p.age, p.gendee) // 18 '男'
Class类的实例方法使用
class Point {
x = 2
y = 3
// void可省略
scale(n: number): void {
this.x *= n
this.y *= n
}
}
const p = new Point()
p.scale(5)
console.log(p.x, p.y) // 10 15
Class类的extends继承
类继承的两种方式:1.extends(继承父类);2.implements(实现接口)
class Animal {
move() {
console.log('爬行!')
}
}
// 子类Dog继承父类Animal,则Dog的实例对象dog就同时具有了父类Animal和子类Dog的所有属性和方法
class Dog extends Animal {
name = '二哈'
bark() {
console.log('汪汪!')
}
}
const dog = new Dog()
dog.move() // 爬行!
console.log(dog.name) // 二哈
dog.bark() // 汪汪!
Class类继承
implements实现接口
interface Singable {
name: String
sing(): void
}
class Person implements Singable {
name = 'Daniel'
sing() {
console.log('测试sing')
}
}
// Person类要实现接口Singable,必须提供所有属性和方法;其实就是对类进行一些约束
类成员对可见性:public公用的
// 父类
class Animal {
public move() {
console.log('走两步')
}
}
const whiteSheep = new Animal()
a.move() // 走两步
// 子类
class Dog extends Animal {
bark() {
consle.log('汪汪!')
}
}
const pup = new Dog()
pup.move() // 走两步
pup.bark() // 汪汪!
类成员对可见性:protected受保护的
仅对其声明所在类和子类中(非实例对象)可见
// 父类
class Animal {
// 这个方法是受保护的
// 可以在当前类的其它方法或其它子类中调用
protected move() {
console.log('走两步!')
}
run() {
this.move()
console.log('跑起来!')
}
}
const a = new Animal()
// a.move() 是无法调用的
// 子类
class Dog extends Animal {
bark() {
console.log('汪汪!')
this.move()
}
}
const d = new Dog()
类成员对可见性:private私有的。
// 父类
class Animal {
// 私有的
private __run__() {
console.log('Animall内部辅助函数-跑')
}
// 受保护的
protected move() {
this.__run__()
console.log('移动!')
}
// 公开的
walk() {
this.__run__()
this.move()
console.log('走起来')
}
}
const a = new Animal()
// 子类
class Dog extends Animal {
bark() {
// 私有方法,子类不可见
// this.__run__()
console.log('汪汪!')
}
}
const d = new Dog()
// private私有,只允许在类内部使用
// 实例都不可见
// a.__run__()
// d.__run__()
readonly只读修饰符
class Person {
// readonly修饰符只能修饰属性,不可修饰方法
// 18是默认值,也可不设默认值
readonly age: number = 18
// 不设类型,只赋值,相当于是常量(在构造函数里不可更改)
// readonly age = 17
constructor(age: number) {
// readonly只读属性,只允许在构造函数中修改
this.age = age
}
setAge() {
// 不允许在其它方法中修改只读属性
// this.age = 20
}
}
interface IPerson {
name: string
readonly age: number
}
let obj:IPerson = {
name: 'David'
age: 18
}
/*
下面写法和上面是一样的
let obj: { readonly name: string } = {
name: 'Frank'
}
*/
obj.name = 'Daniel'
// 只读属性无法修改
// obj.age = 20
类型兼容性
class Point { x:number; y:number }
class Point2D { x:number; y:number }
// 下面这种赋值不会报错,因为TS采用的是结构化类型系统
const p1: Point = new Point2D()
class Point3D { x:number; y:number; z:number }
// 成员多的可以赋值(兼容)给成员少的
const p2: Point = new Point3D()
接口兼容性
// 接口之间的兼容性,类似于class。并且class和interface之间也可兼容
interface Point { x:number; y: number }
interface Point2D { x:number; y: number }
interface Point3D { x: number; y: number; z: number }
let p1: Point
let p2: Point2D
let p3: Point3D
// 正确演示:允许的赋值方式
p1 = p2
p2 = p1
p1 = p3
// 错误演示:不允许的赋值方式
// p3 = p2
// p3 = p1
class Point3D2 { x: number; y: number; z: number }
// 类和接口之间赋值也是兼容的
p2 = new Point3D2()
类型兼容性
// 函数之间兼容性比较复杂,需要考虑:1.参数个数;2.参数类型;3.返回值类型
// 1.参数个数,参数多的兼容参数少的(参数少的可以赋值给多的)
type F1 = (a: number) => void
type F2 = (a: number, b: number) => void
let f1: F1
let f2: F2 = f1
// f1和f2返回类型一致,f1的参数类型和f2的参数类型一致,所以可以赋值
// forEach就是参数少兼容参数多的,js中省略用不到的参数很常见
const arr = ['a', 'b', 'c']
arr.forEach(() => {})
arr.forEach((item) => {})
把接口对象拆开,把每个属性看做一个个参数
interface Point2D { x: number; y: number }
interface Point3D { x: number; y: number; z: number }
type F2 = (p: Point2D) => void
type F3 = (p: Point3D) => void
let f2: F2
let f3: F3 = f2 // 参数少的赋值参数多的,允许
// f2 = f3 // 参数多的赋值参数少的,不允许
返回类型
type F5 = () => string
type F6 = () => string
let f5: F5
// 返回值相同,可以相互赋值
let f6: F6 = f5
type F7 = () => { name: string }
type F8 = () => { name: string; age: number }
let f7: F7
let f8: F8
f7 = f8 // 多个返回值的可以赋值少返回值的
返回值类型,只关注返回值类型本身即可:
type F5 = () => String
type F6 = () => String
let f5: F5
// 原始类型,类型相同,可以赋值
let f6: F6 = f5
type F7 = () => { name: string }
type F8 = () => { name: string; age: number }
let f7: F7
let f8: F8
// 对象类型:成员多的可以赋值给成员少的
f7 = f8
交叉类型
interface Person { name: string }
interface Contact { phone: string }
type PersonDetail = Person & Contact
let obj: PersonDetail = {
name: 'jack',
phone: '13012345678'
}
// 使用了交叉类型后,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型
相当于:
type PersonDetail = { name: string, phone: string }
交叉类型(&)和接口继承(extends)的对比:
1.相同点:都可以实现对象类型的组合;
2.不同点:两种方式实现类型组合时,对于同名属性之间,处理类型冲突的方式不同。
interface A {
fn: (value: number) => string
}
// 继承会报错(类型不兼容);
interface B extends A {
fn: (value: string) => string
}
interface C {
fn: (value: number) => string
}
interface D {
fn: (value: string) => string
}
type E = C & D
// 交叉类型没有错误,可以简单的理解为:
let e: E = {
fn(value: string | number) {
return ''
}
}
泛型
// 参数和返回值一致
function id(value: number): number { return value }
// 任何类型,返回任何类型:不推荐使用
function id(value: any): any { return value }
// 创建泛型函数
function id<Type>(value: Type): Type { return value }
/*
*解释:
*1.语法:在函数名称的后面添加<>(尖括号),尖括号中添加类型变量,比如此处的Type;
*2.类型变量Type,是一种特殊类型的变量,它处理类型而不是值;
*3.该类型变量相当于一个类型容器,能够捕获用户提供的类型(具体是什么类型由用户调用该函数时指定);
*4.因为Type是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型;
*5.类型变量Type,可以是任意合法的变量名称。
*/
// 调用的时候指定类型(返回值的类型也一致)
const num = id<number>(10)
const str = id<string>('a')
// 通过泛型就做到了让id函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全。
简化调用泛型函数:
function id<Type>(value: Type): Type { return value }
// let num = id<number>(10)
// 可以省略<类型>来简化泛型函数的调用
let num = id(10)
let str = id('abc')
泛型约束
// Type可以代表任意类型,无法保证一定存在length属性,比如number类型就没有length
function id<Type>(value: Type): Type {
console.log(value.length) // 会报错
return value
}
// 指定更加具体的类型
// 将类型修改为Type[](Type类型的数组),是数组就一定存在length
function id<Type>(value: Type[]): Type[] {
console.log(value.length) // 会报错
return value
}
添加约束
interface ILength { length: number }
// 类型Type必须要满足接口ILength的要求
function id<Type extends ILength>(value: Type): Type {
console.log(value.length)
return value
}
// 创建描述约束的接口ILength,该接口要求提供length属性
// 通过extends关键字使用该接口,为泛型(类型变量)添加约束
// 该约束表示:传入的类型必须具有length属性
// 注意:传入的实参(比如数组)只要有length属性即可,这也符合前面讲到的接口的类型兼容性
id(['a', 'b', 'c']) // 可以
id('abc') // 可以
id({ length: 2 }) // 可以
id(123) // 数字不可以,因为没有length属性
泛型的类型变量可以有多个,并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
// 比如,创建一个函数来获取对象中属性的值
function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key]
}
let person = { name: 'jack', age: 18 }
getProp(person, 'name')
// 添加了第二个类型变量key,两个类型变量之间使用逗号(,)分隔;
// keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
// 本示例中keyof Type实际上获取的是person对象所有键的联合类型,也就是:'name'|'age';
// 类型变量key受Type约束,可以理解为:key只能是Type所有键中的任意一个,或者说这只能访问对象中存在的属性。
getProp(18, 'toFixed') // 获取数字的属性
getProp('abc', 'split')
get(('abc', 2) // 获取字符串的索引
泛型接口
interface IdFunc<Type> {
id: (value: Type) => Type
ids: () => Type[]
}
let obj: IdFunc<number> = {
id(value) { return value },
ids() { return [1, 3, 5] }
}
// 在接口名称的后面添加<类型变量>,那么,这个接口就变成了泛型接口。
// 接口的类型变量,对接口中所有其他成员可见,也就是接口中所有成员都可以使用类型变量。
// 使用泛型接口时,需要显式指定具体的类型(比如,此处的IdFunc<number>)。
// 此时,id方法的参数和返回值类型都是number;ids方法的返回值类型是number[]。
JS中的数组在TS中就是一个泛型接口
const strs = ['a', 'b', 'c']
// forEach自动推断出item是string类型
strs.forEach(item => {})
const nums = [1, 2, 3]
// forEach自动推断出item是number类型
nums.forEach(item => {})
创建泛型类
class GenericNumber<NumType> {
defaultValue: NumType
add: (x: NumType, y: NumType) => NumType
}
// class名称后面添加<类型变量>,这个类就变成了泛型类
// 此处的add方法,采用的是箭头函数形式的类型书写方式
const myNum = new GenericNumber<number>()
myNum.defaultValue = 10
// 类似于泛型接口,在创建class实例时,在类名后面通过<类型>来指定明确的类型。
class GenericNumber<NumType> {
defaultValue: NumType
add: (x: NumType, y: NumType) => NumType
constructor(value: NumType) {
this.defaultValue = value
}
}
const myNum = new GenericNumber(100) // 赋值后,会被推断为number类型
泛型工具的类型:
1.Partial<Type> 可选属性类型
interface Props {
id: string,
children: number[]
}
// 构造出来的新类型ParticalProps结构和Props相同,但所有属性都变为可选的。
type ParticalProps = Partical<Props>
// 可以加属性
let p1: Props = {
id: '',
children: [1, 2]
}
// 因为属性是可选,所以不加属性也没问题
let p2: Props = { id: '' }
2.Readonly<Type> 只读属性类型
interface Props {
id: string
children: number[]
}
type ReadonlyProps = Readonly<Props>
let p1: ReadonlyProps = { id: '1', children: [] }
// 改变属性会报错,因为是只读类型
p1.id = '2'
3.Pick<Type, Keys> 选择属性类型
interface Props {
id: string
title: string
children: number[]
}
type PPickProps = Pick<Props, 'id' | 'title'>
// Pick工具类型有两个类型变量:1表示选择谁的属性 2表示选择哪几个属性
// 其中第二个类型变量,如果只选择一个则只传入该属性名即可
// 第二个类型变量传入的属性只能是第一个类型变量中存在的属性
// 构造出来的新类型PickProps,只有id和title两个属性类型。
4.Record<Keys, Type> 构建对象类型
type RecordObj = Record<'a' | 'b' | 'c', string[]>
/*
// 等同于下面的写法
type RecordObj = {
a: string[]
b: string[]
c: string[]
}
*/
let obj: RecordObj = {
a: ['1'],
b: ['2'],
c: ['3']
}
// Record工具类型有两个类型变量:1表示对象有哪些属性 2表示对象属性的类型
// 构建的新对象类型RecordObj表示:这个对象有三个属性分别为a/b/c,属性值的类型都是string[]
索引签名类型
绝大多数情况下,我们都可以使用对象前就确定对象的结构,并为对象添加准确的类型
使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性),此时,就用到索引签名类型了。
interface AnyObject {
[key: string]: number
}
let obj: AnyObject = {
a: 1,
b: 2
}
// 使用[key: string]来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中
// 这样,对象obj中就可以出现任意多个属性(比如:a、b等)
// key只是一个占位符,可以换成任意合法的变量名称
// 隐藏的前置知识:JS中对象({})的键是string类型的
// 数组类型实际上就是特殊对象
interface MyArray<T> {
[n: number]: T
}
let arr: MyArray<number> = [1,3, 5]
映射类型
type PropKeys = 'x' | 'y' | 'z'
type Type1 = { x: number; y: number; z:number }
// 映射类型简化
type Type2 = { [Key in PropKeys]: number }
// 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]
// Key in PropKeys表示Key可以是PropKeys联合类型中的任意一个,类似于forin(let k in obj)
// 使用映射类型创建的新对象类型Type2和类型Type1结构完全相同
// 注意:映射类型只能在类型别名中使用,不能在接口中使用
映射类型(使用keyof)
type Props = { a: number; b: string; c: boolean }
type Type3 = { [key in keyof Props]: number }
// 新生成的等同于:type Type3 = { a: number; b: number; c: number }
// 采用Props的key,采用新定义的类型
泛型工具类型(比如:Partial<Type>)都是基于映射类型实现的
type Partial<T> = {
[P in keyof T]?: T[P]
}
type Props = { a: number; b: string; c: boolean }
type PartialProps = Partial<Props>
// keyof T即keyof Props表示获取Props的所有键,也就是:'a' | 'b' | 'c'
// 在[]后面添加?(问号),表示将这些属性变为可选的,以此来实现Partial功能
// 冒号后面的T[P]表示获取T中每个键对应的类型。比如:如果是'a'则类型是number;如果是'b'则类型是string;……
// 最终,新类型ParticalProps和旧类型Props结构完全相同,只是让所有类型都变为可选了
刚刚用到的T[P]语法,在TS中叫做索引查询(访问)类型
作用:用来查询属性的类型
type Props = { a: number; b: string; c: boolean }
type TypeA = Props['a'] // 等价于 type TypeA = number
// 注意:[]中的属性必须存在于被查询类型中,否则就会报错
索引查询类型
type Props = { a: number; b: string; c: boolean; d: number }
// 使用字符串字面量的联合类型,获取属性a和b对应的类型,结果为:number | string
type TypeA = Props['a' | 'b' | 'd'] // number | string
// 使用keyof操作符获取Props中所有键对应的类型,结果为:number | string | boolean
type TypeB = Props[keyof Props] // number | string | boolean
TS中的两种文件类型
.ts文件既包含类型信息又可执行代码;可编译为.js文件;
.d.ts文件只包含类型信息的类型声明文件,不会生成.js文件,仅用于提供类型信息。
创建自己的类型声明文件
<index.ts>
import { count, songName, add, Point } from './utils.js'
console.log('count', count)
console.log('songName', songName)
console.log('add()', add(2, 3))
<utils.js>
let count = 10
let songName = '10000 Reasons'
let position = {
x: 0,
y: 0
}
function add(x, y) {
return x + y
}
function changeDirection(direction) {
console.log(direction)
}
function formatPoint = point => {
console.log('当前坐标:', point)
}
export { count, songName, position, add, changeDirection, formatPoint }
<utils.d.ts>
// 为 <untils.js> 文件提供类型声明
// declare就是为其它地方已存在的变量声明类型,而不是创建一个新的变量
// 对于type、interface等TS类型,可以省略declare关键字
declare let count: number
declare let songName: string
interface Point {
x: number
y: number
}
declare let position: Point
// 给函数参数和返回值声明类型
declare function add(x: number, y: number): number
// 函数的参数为联合类型,无返回值
declare function changeDirection(
direction: 'up' | 'down' | 'left' | 'right'
): void
type FormatPoint = (point: Point) => void
// 给函数设置参数类型
declare const formatPoint: FormatPoint
/*
* 注意:类型提供好之后,需要使用模块化方案中提供的模块化语法来导出声明好的类型。然后才能在其他.ts文件中使用。
*/
export { count, songName, position, add, changeDirection, formatPoint, Point }