TypeScript笔记

安装工具包

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 }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 基础数据类型 JS的八种内置类型 tips:默认情况下,null和undefined是所有类型的子类型,可以将其赋...
    前端不好玩阅读 962评论 0 1
  • 1. 安装TS 查看是否TS安装成功 显示图片中内容表示安装成功 2. 编译TS代码 Node.js/浏览器,只认...
    墨谦客阅读 99评论 0 1
  • 安装TypeScript包 使用步骤 npm init -y来初始化项目,生成package.json文件。 在终...
    快乐小码仔阅读 1,377评论 0 10
  • 一、ts介绍 是javascript的超集,遵循最新的ES5 ES6规范。ts扩展了js的语法。 二、安装与编译 ...
    聪明的傻瓜子_阅读 316评论 0 0
  • ts 是什么? Typescript 是JavaScript的超集,遵循ES5/ES6规范,扩展了Javascri...
    陈嘻嘻啊阅读 508评论 0 1