TypeScript 学习笔记

TypeScript 学习笔记 

  • 参考:王红元老师的Vue3课程

TypeScript引入

  • JavaScript缺点:没有类型检测
// 当前foo函数, 在被其他地方调用时, 没有做任何的参数校验
// 1> 没有对类型进行校验
// 2> 没有对是否传入参数进行校验
function foo(message) {
  if (message) {
    console.log(message.length);
  }
}

foo("Hello World");
foo("你好啊,李银河");

foo(123)
foo()

// 永远执行不到
console.log("渲染界面成千上万行的JavaScript代码需要执行, 去渲染界面")
  • TypeScript是拥有类型的JavaScript超集,它可以编译成普通、干净、完整的JavaScript代码。
  • 不仅仅增加了类型约束,而且包括一些语法的扩展,比如枚举类型(Enum)、元组类型(Tuple)等。
  • 官网:TypeScript: JavaScript With Syntax For Types.
  • 安装环境:
# 安装命令
npm install typescript -g

# 查看版本
tsc --version
  • 示例:
let message: string = 'hello typescript'

function foo(payload: string) {
  console.log(payload.length)
}

// foo(123) //报错
foo("aaa")

export { }  //ts默认全局作用域,加上export说明这是个模块

使用ts-node

  • 安装ts-node
npm install ts-node -g
  • 另外ts-node需要依赖 tslib 和 @types/node 两个包:
npm install tslib @types/node -g
  • 可以直接通过 ts-node 来运行TypeScript的代码:
ts-node math.ts

webpack安装

安装tslint

  • npm install tslint -g
  • tslint --init 

变量的声明

  • 声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解
var/let/const 标识符: 数据类型 = 赋值;
  • 比如我们声明一个message,完整的写法如下:
    • 注意:这里的string是小写的,和String是有区别的
    • string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类
  • 在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型:
// 1.类型注解
// 2.var/let/const
// 3.string和String的区别
// 4.类型推导
var name: string = "why"
let age: number = 18
const height: number = 1.88

// string: TypeScript中的字符串类型
// String: JavaScript的字符串包装类的类型
const message: string = "Hello World"

// 默认情况下进行赋值时, 会将赋值的值的类型, 作为前面标识符的类型
// 这个过程称之为类型推导/推断
// foo没有添加类型注解
let foo = "foo"
// foo = 123 //报错
export {}

JavaScript类型

number类型

  • 数字类型是我们开发中经常使用的类型,TypeScript和JavaScript一样,不区分整数类型(int)和浮点型 (double),统一为number类型。
let num: number = 123
num = 222

let num1: number = 100 
let num2: number = 0b100
let num3: number = 0o100
let num4: number = 0x100

console.log(num1, num2, num3, num4)

boolean类型

let flag: boolean = true
flag = 20 > 30

string类型

let message1: string = 'hello world'
let message2: string = "Hello World"

// 个人习惯: 默认情况下, 如果可以推导出对应的标识符的类型时, 一般情况下是不加
const name = "why"
const age = 18
const height = 1.88

let message3 = `name:${name} age:${age} height:${height}`
console.log(message3)

export {}

Array类型

  • 如果添加其他类型到数组中,那么会报错
// 确定一个事实: names是一个数组类型, 但是数组中存放的是什么类型的元素呢?
// 不好的习惯: 一个数组中在TypeScript开发中, 最好存放的数据类型是固定的(比如string)
// 类型注解: type annotation
const names1: Array<string> = [] // 不推荐(react jsx中是有冲突   <div></div>)
const names2: string[] = [] // 推荐

Object类型

  • object对象类型可以用于描述一个对象:

  •  但是从myinfo中我们不能获取数据,也不能设置数据

  • 写成即可访问:
const info = {
  name: "why",
  age: 18
}

console.log(info.name)

Symbol类型

  • 在ES5中,如果我们是不可以在对象中添加相同的属性名称的,比如下面的做法:

  • 通常我们的做法是定义两个不同的属性名字:比如identity1和identity2。
  • 但是我们也可以通过symbol来定义相同的名称,因为Symbol函数返回的是不同的值:
const title1 = Symbol("title")
const title2 = Symbol('title')

const info = {
  [title1]: "程序员",
  [title2]: "老师"
}

export {}

null和undefined类型

  • 在 JavaScript 中,undefined 和 null 是两个基本数据类型。
  • 在TypeScript中,它们各自的类型也是undefined和null,也就意味着它们既是实际的值,也是自己的类型:
let n1: null = null
let n2: undefined = undefined

TypeScript类型

any类型

  • 在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型(类似 于Dart语言中的dynamic类型)。
  • any类型有点像一种讨巧的TypeScript手段:
    • 我们可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法;
    • 我们给一个any类型的变量赋值任何的值,比如数字、字符串的值;
// 当进行一些类型断言 as any
// 在不想给某些JavaScript添加具体的数据类型时(原生的JavaScript代码是一样)
let message: any = "Hello World"

message = 123
message = true
message = {

}
   
console.log(message)
const arr: any[] = []
  • 如果对于某些情况的处理过于繁琐不希望添加规定的类型注解,或者在引入一些第三方库时,缺失了类型注解,这个时候 我们可以使用any:
    • 包括在Vue源码中,也会使用到any来进行某些类型的适配;
unknown类型
  • unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。
function foo() {
  return "abc"
}

function bar() {
  return 123
}

// unknown类型只能赋值给any和unknown类型
// any类型可以赋值给任意类型

let flag = true
let result: unknown // 最好不要使用any
if (flag) {
  result = foo()
} else {
  result = bar()
}
// let message: string = result  //报错
// let num: number = result  //报错
console.log(result)

export {}

void类型

  • void通常用来指定一个函数是没有返回值的,那么它的返回值就是void类型:
    • 我们可以将null和undefined赋值给void类型,也就是函数可以返回null或者undefined

  • 这个函数我们没有写任何类型,那么它默认返回值的类型就是void的,我们也可以显示的来指定返回值是void:

never类型

  • never 表示永远不会发生值的类型,比如一个函数:
    • 如果一个函数中是一个死循环或者抛出一个异常,那么这个函数会返回东西吗?
    • 不会,那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型;

  •  应用:
function handleMessage(message: string | number | boolean) {
  switch (typeof message) {
    case 'string':
      console.log("string处理方式处理message")
      break
    case 'number':
      console.log("number处理方式处理message")
      break
    case 'boolean':
      console.log("boolean处理方式处理message")
      break
    default:
      const check: never = message
  }
}

handleMessage("abc")
handleMessage(123)
handleMessage(true)

tuple类型

  • tuple是元组类型,很多语言中也有这种数据类型,比如Python、Swift等

  • 那么tuple和数组有什么区别呢? 
    • 首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
    • 其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型;

  •  应用案例:
function useState<T>(state: T) {
  let currentState = state
  const changeState = (newState: T) => {
    currentState = newState
  }
  // const info: [string, number] = ["abc", 18]
  const tuple: [T, (newState: T) => void] = [currentState, changeState]
  return tuple
}

const [counter, setCounter] = useState(10);
setCounter(1000)
const [title, setTitle] = useState("abc")
const [flag, setFlag] = useState(true)

函数的参数类型

参数的类型注解

  • 声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型:
// 给参数加上类型注解: num1: number, num2: number
// 给返回值加上类型注释: (): number
// 在开发中,通常情况下可以不写返回值的类型(自动推导)
function sum(num1: number, num2: number) {
  return num1 + num2
}

// sum(123, 321)

函数的返回值类型

  • 可以添加返回值的类型注解,这个注解出现在函数列表的后面

  •  和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型:
  • 某些第三方库处于方便理解,会明确指定返回类型,但是这个看个人喜好

匿名函数的参数

  • 匿名函数与函数声明会有一些不同:
    • 当一个函数出现在TypeScript可以确定该函数会被如何调用的地方时;
    • 该函数的参数会自动指定类型;
// 通常情况下, 在定义一个函数时, 都会给参数加上类型注解的
function foo(message: string) {

}

const names = ["abc", "cba", "nba"]
// item根据上下文的环境推导出来的, 这个时候可以不添加的类型注解
// 上下文中的函数: 可以不添加类型注解
names.forEach(function(item) {
  console.log(item.split(""))
})
  • 我们并没有指定item的类型,但是item是一个string类型:
    • 这是因为TypeScript会根据forEach函数的类型以及数组的类型推断出item的类型;
    • 这个过程称之为上下文类型(contextual typing,因为函数执行的上下文可以帮助确定参数和返回值的类型

对象类型

  • 在对象我们可以添加属性,并且告知TypeScript该属性需要是什么类型;
  • 属性之间可以使用 , 或者 ; 来分割,最后一个分隔符是可选的;
  • 每个属性的类型部分也是可选的,如果不指定,那么就是any类型;
// Point: x/y -> 对象类型
// {x: number, y: number}
function printPoint(point: {x: number, y: number}) {
  console.log(point.x);
  console.log(point.y)
}

printPoint({x: 123, y: 321})

可选类型

  • 对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个?:
// Point: x/y/z -> 对象类型
// {x: number, y: number, z?: number}
function printPoint(point: {x: number, y: number, z?: number}) {
  console.log(point.x)
  console.log(point.y)
  console.log(point.z)
}

printPoint({x: 123, y: 321})
printPoint({x: 123, y: 321, z: 111})

联合类型

// number|string 联合类型
function printID(id: number|string|boolean) {
  // 使用联合类型的值时, 需要特别的小心
  // narrow: 缩小
  if (typeof id === 'string') {
    // TypeScript帮助确定id一定是string类型
    console.log(id.toUpperCase())
  } else {
    console.log(id)
  }
}

printID(123)
printID("abc")
printID(true)
  • 可选类型补充
// 让一个参数本身是可选的
// 一个参数一个可选类型的时候, 它其实类似于是这个参数是 类型|undefined 的联合类型
function foo(message?: string) {
  console.log(message)
}

foo()

类型别名

// type用于定义类型别名(type alias)
type IDType = string | number | boolean
type PointType = {
  x: number
  y: number
  z?: number
}

function printId(id: IDType) {

}

function printPoint(point: PointType) {
  
}

类型断言as

  • 有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Typ
  • TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换:
// <img id="why"/>
// 1.类型断言 as
const el = document.getElementById("why") as HTMLImageElement
el.src = "url地址"


// 2.另外案例: Person是Student的父类
class Person {

}
class Student extends Person {
  studying() {

  }
}
function sayHello(p: Person) {
  (p as Student).studying()
}
const stu = new Student()
sayHello(stu)


// 3.了解: as any/unknown
const message = "Hello World"
const num: number = (message as unknown) as number

非空类型断言!

  • 当我们编写下面的代码时,在执行ts的编译阶段会报错:
    • 这是因为传入的message有可能是为undefined的,这个时候是不能执行方法的;

  • 但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言:
    • 非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测;

  •  示例:
// message? -> undefined | string
function printMessageLength(message?: string) {
  // if (message) {
  //   console.log(message.length)
  // }
  // vue3源码
  console.log(message!.length)
}

printMessageLength("aaaa")
printMessageLength("hello world")

可选链的使用

  • 可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性:
    • 可选链使用可选链操作符 ?.;
    • 它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行;
    • 虽然可选链操作是ECMAScript提出的特性,但是和TypeScript一起使用更版本;
type Person = {
  name: string
  friend?: {
    name: string
    age?: number,
    girlFriend?: {
      name: string
    }
  }
}

const info: Person = {
  name: "why",
  friend: {
    name: "kobe",
    girlFriend: {
      name: "lily"
    }
  }
}


// 另外一个文件中
console.log(info.name)
// console.log(info.friend!.name)
console.log(info.friend?.name)
console.log(info.friend?.age)
console.log(info.friend?.girlFriend?.name)


// if (info.friend) {
//   console.log(info.friend.name)

//   if (info.friend.age) {
//     console.log(info.friend.age)
//   }
// }

??和!!的作用

const message = "Hello World"

// const flag = Boolean(message)
// console.log(flag)

const flag = !!message
console.log(flag)
let message: string|null = "Hello World"

const content = message ?? "你好啊, 李银河"
// const content = message ? message: "你好啊, 李银河"
console.log(content)

字面量类型

// "Hello World"也是可以作为类型的, 叫做字面量类型
const message: "Hello World" = "Hello World"

// let num: 123 = 123
// num = 321  //报错

// 字面量类型的意义, 就是必须结合联合类型
type Alignment = 'left' | 'right' | 'center'

let align: Alignment = 'left'
align = 'right'
align = 'center'
// align = 'hehehehe'  //报错

字面量推理

// const info = {
//   name: "why",
//   age: 18
// }

// info.name = "kobe"  //不能修改 报错


type Method = 'GET' | 'POST'
function request(url: string, method: Method) {}

// type Request = {
//   url: string,
//   method: Method
// }
// const options : Request


const options = {
  url: "https://www.coderwhy.org/abc",
  method: "POST"
} as const

// request(options.url, options.method as Method)  //上面不用as const 可以这样写
request(options.url, options.method)

类型缩小

  • 什么是类型缩小呢?
    • 类型缩小的英文是 Type Narrowing;
    • 我们可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径;
    • 在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小;
    • 而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards);

typeof

  • 在 TypeScript 中,检查返回的值typeof是一种类型保护:因为 TypeScript 对如何typeof操作不同的值进行编码。
type IDType = number | string
function printID(id: IDType) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase())
  } else {
    console.log(id)
  }
}

平等缩小

  • 可以使用Switch或者相等的一些运算符来表达相等性(比如===, !==, ==, and != ):
//  平等的类型缩小(=== == !== !=/switch)
type Direction = "left" | "right" | "top" | "bottom"
function printDirection(direction: Direction) {
  // 1.if判断
  // if (direction === 'left') {
  //   console.log(direction)
  // } else if ()

  // 2.switch判断
  // switch (direction) {
  //   case 'left':
  //     console.log(direction)
  //     break;
  //   case ...
  // }
}

instanceof

  • JavaScript 有一个运算符来检查一个值是否是另一个值的“实例”:
function printTime(time: string | Date) {
  if (time instanceof Date) {
    console.log(time.toUTCString())
  } else {
    console.log(time)
  }
}

class Student {
  studying() {}
}

class Teacher {
  teaching() {}
}

function work(p: Student | Teacher) {
  if (p instanceof Student) {
    p.studying()
  } else {
    p.teaching()
  }
}

const stu = new Student()
work(stu)
  • Javascript 有一个运算符,用于确定对象是否具有带名称的属性:in运算符
    • 如果指定的属性在指定的对象或其原型链中,则in 运算符返回true;
type Fish = {
  swimming: () => void
}

type Dog = {
  running: () => void
}

function walk(animal: Fish | Dog) {
  if ('swimming' in animal) {
    animal.swimming()
  } else {
    animal.running()
  }
}

const fish: Fish = {
  swimming() {
    console.log("swimming")
  }
}

walk(fish)

TypeScript函数类型

// 1.函数作为参数时, 在参数中如何编写类型
function foo() {}

type FooFnType = () => void
function bar(fn: FooFnType) {
  fn()
}

bar(foo)

// 2.定义常量时, 编写函数的类型
type AddFnType = (num1: number, num2: number) => number
const add: AddFnType = (a1: number, a2: number) => {
  return a1 + a2
}
  • 示例:
function calc(n1: number, n2: number, fn: (num1: number, num2: number) => number) {
  return fn(n1, n2)
}

const result1 = calc(20, 30, function(a1, a2) {
  return a1 + a2
})
console.log(result1)

const result2 = calc(20, 30, function(a1, a2) {
  return a1 * a2
})
console.log(result2)

TypeScript函数类型解析

  • 在某些语言中,可能参数名称num1和num2是可以省略,但是TypeScript是不可以的:

参数的可选类型

  • 可以指定某个参数是可选的:

  •  这个时候这个参数x依然是有类型的,它是什么类型呢? number | undefined
  • 另外可选类型需要在必传参数的后面:

默认参数

  • 从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:

  •  这个时候y的类型其实是 undefined 和 number 类型的联合

剩余参数

  • 从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。

可推导的this类型

  • 上面的代码是可以正常运行的,也就是TypeScript在编译时,认为我们的this是可以正确去使用的:
  • TypeScript认为函数 sayHello 有一个对应的this的外部对象 info,所以在使用时,就会把this当做该对象。

不确定的this类型

  • 但是对于某些情况来说,我们并不知道this到底是什么?

  • 这段代码运行会报错的:
    • 这里我们再次强调一下,TypeScript进行类型检测的目的是让我们的代码更加的安全;
    • 所以这里对于 sayHello 的调用来说,我们虽然将其放到了info中,通过info去调用,this依然是指向info对象的;
    • 但是对于TypeScript编译器来说,这个代码是非常不安全的,因为我们也有可能直接调用函数,或者通过别的对象来 调用函数;

指定this的类型

  • 这个时候,通常TypeScript会要求我们明确的指定this的类型:

函数的重载

  • 引例:
/**
 * 通过联合类型有两个缺点:
 *  1.进行很多的逻辑判断(类型缩小)
 *  2.返回值的类型依然是不能确定
 */
function add(a1: number | string, a2: number | string) {
  if (typeof a1 === "number" && typeof a2 === "number") {
    return a1 + a2
  } else if (typeof a1 === "string" && typeof a2 === "string") {
    return a1 + a2
  }

  // return a1 + a2;
}

add(10, 20)
  • 示例:
// 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载
function add(num1: number, num2: number): number; // 没函数体
function add(num1: string, num2: string): string;

function add(num1: any, num2: any): any {
  if (typeof num1 === 'string' && typeof num2 === 'string') {
    return num1.length + num2.length
  }
  return num1 + num2
}

const result = add(20, 30)
const result2 = add("abc", "cba")
console.log(result)
console.log(result2)

// 在函数的重载中, 实现函数是不能直接被调用的
// add({name: "why"}, {age: 18})

export {}
  • 示例2
/ 实现方式一: 联合类型
function getLength(args: string | any[]) {
  return args.length
}

console.log(getLength("abc"))
console.log(getLength([123, 321, 123]))

// 实现方式二: 函数的重载
// function getLength(args: string): number;
// function getLength(args: any[]): number;

// function getLength(args: any): number {
//   return args.length
// }

// console.log(getLength("abc"))
// console.log(getLength([123, 321, 123]))

类的使用

  • 我们可以声明一些类的属性:在类的内部声明类的属性以及对应的类型
    • 如果类型没有声明,那么它们默认是any的;
    • 我们也可以给属性设置初始化值;
    • 在默认的strictPropertyInitialization模式下面我们的属性是必须初始化的,如果没有初始化,那么编译时就会报错;
    • 如果我们在strictPropertyInitialization模式下确实不希望给属性初始化,可以使用 name!: string语法;
  • 类可以有自己的构造函数constructor,当我们通过new关键字创建一个实例时,构造函数会被调用;
  • 构造函数不需要返回任何值,默认返回当前创建出来的实例;
  • 类中可以有自己的函数,定义的函数称之为方法;
class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log(this.name + " eating")
  }
}

const p = new Person("why", 18)
console.log(p.name)
console.log(p.age)
p.eating()

类的继承

class Person {
  name: string
  age: number

  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

  eating() {
    console.log("eating 100行")
  }
}

class Student extends Person {
  sno: number

  constructor(name: string, age: number, sno: number) {
    // super调用父类的构造器
    super(name, age)
    this.sno = sno
  }

  eating() {
    console.log("student eating")
    super.eating()
  }

  studying() {
    console.log("studying")
  }
}

const stu = new Student("why", 18, 111)
console.log(stu.name)
console.log(stu.age)
console.log(stu.sno)

stu.eating() 

类的多态

class Animal {
  action() {
    console.log("animal action")
  }
}

class Dog extends Animal {
  action() {
    console.log("dog running!!!")
  }
}

class Fish extends Animal {
  action() {
    console.log("fish swimming")
  }
}

class Person extends Animal {

}

// animal: dog/fish
// 多态的目的是为了写出更加具备通用性的代码
function makeActions(animals: Animal[]) {
  animals.forEach(animal => {
    animal.action()
  })
}

makeActions([new Dog(), new Fish(), new Person()])

类的成员修饰符

  • public是默认的修饰符,也是可以直接访问的
class Person {
  private name: string = ""

  // 封装了两个方法, 通过方法来访问name
  getName() {
    return this.name
  }

  setName(newName) {
    this.name = newName
  }
}

const p = new Person()
console.log(p.getName())
p.setName("why")
// protected: 在类内部和子类中可以访问
class Person {
  protected name: string = "123"
}

class Student extends Person {
  getName() {
    return this.name
  }
}

const stu = new Student()
console.log(stu.getName())

只读属性readonly

  • 如果有一个属性我们不希望外界可以任意的修改,只希望确定值后直接使用,那么可以使用readonly:
class Person {
  // 1.只读属性是可以在构造器中赋值, 赋值之后就不可以修改
  // 2.属性本身不能进行修改, 但是如果它是对象类型, 对象中的属性是可以修改
  readonly name: string
  age?: number
  readonly friend?: Person
  constructor(name: string, friend?: Person) {
    this.name = name
    this.friend = friend
  }
}

const p = new Person("why", new Person("kobe"))
console.log(p.name)
console.log(p.friend)

// 不可以直接修改friend
// p.friend = new Person("james")
if (p.friend) {
  p.friend.age = 30
} 

getters/setters

  • 在前面一些私有属性我们是不能直接访问的,或者某些属性我们想要监听它的获取(getter)和设置(setter)的过程, 这个时候我们可以使用存取器。
class Person {
  private _name: string
  constructor(name: string) {
    this._name = name
  }

  // 访问器setter/getter
  // setter
  set name(newName) {
    this._name = newName
  }
  // getter
  get name() {
    return this._name
  }
}

const p = new Person("why")
p.name = "coderwhy"
console.log(p.name)

静态成员

class Student {
  static time: string = "20:00"

  static attendClass() {
    console.log("去学习~")
  }
}

console.log(Student.time)
Student.attendClass()

抽象类abstract

  • 什么是 抽象方法? 在TypeScript中没有具体实现的方法(没有方法体),就是抽象方法。
    • 抽象方法,必须存在于抽象类中;
    • 抽象类是使用abstract声明的类;
  • 抽象类有如下的特点:
    • 抽象类是不能被实例的话(也就是不能通过new创建)
    • 抽象方法必须被子类实现,否则该类必须是一个抽象类;

function makeArea(shape: Shape) {
  return shape.getArea()
}


abstract class Shape {
  abstract getArea(): number
}


class Rectangle extends Shape {
  private width: number
  private height: number

  constructor(width: number, height: number) {
    super()
    this.width = width
    this.height = height
  }

  getArea() {
    return this.width * this.height
  }
}

class Circle extends Shape {
  private r: number

  constructor(r: number) {
    super()
    this.r = r
  }

  getArea() {
    return this.r * this.r * 3.14
  }
}

const rectangle = new Rectangle(20, 30)
const circle = new Circle(10)

console.log(makeArea(rectangle))
console.log(makeArea(circle))
// makeArea(new Shape())
// makeArea(123)
// makeArea("123")

类的类型

  • 类本身也是可以作为一种数据类型的:
class Person {
  name: string = "123"
  eating() {

  }
}

const p = new Person()

const p1: Person = {
  name: "why",
  eating() {

  }
}

function printPerson(p: Person) {
  console.log(p.name)
}

printPerson(new Person())
printPerson({name: "kobe", eating: function() {}})

接口的声明

  • 通过type可以用来声明一个对象类型:
// 通过类型(type)别名来声明对象类型
// type InfoType = {name: string, age: number}

// 另外一种方式声明对象类型: 接口interface
// 在其中可以定义可选类型
// 也可以定义只读属性
interface IInfoType {
  readonly name: string
  age: number
  friend?: {
    name: string
  }
}

const info: IInfoType = {
  name: "why",
  age: 18,
  friend: {
    name: "kobe"
  }
}

console.log(info.friend?.name)
console.log(info.name)
// info.name = "123"
info.age = 20

索引类型

  • 使用interface来定义对象类型,这个时候其中的属性名、类型、方法都是确定的,但是有时候我们会遇 到类似下面的对象:
// 通过interface来定义索引类型
interface IndexLanguage {
  [index: number]: string
}

const frontLanguage: IndexLanguage = {
  0: "HTML",
  1: "CSS",
  2: "JavaScript",
  3: "Vue"
}


interface ILanguageYear {
  [name: string]: number
}

const languageYear: ILanguageYear = {
  "C": 1972,
  "Java": 1995,
  "JavaScript": 1996,
  "TypeScript": 2014
}

函数类型

  • 前面我们都是通过interface来定义对象中普通的属性和方法的,实际上它也可以用来定义函数类型:

  •  当然,除非特别的情况,还是推荐大家使用类型别名来定义函数:

  •  示例:
// type CalcFn = (n1: number, n2: number) => number
// 可调用的接口
interface CalcFn {
  (n1: number, n2: number): number
}

function calc(num1: number, num2: number, calcFn: CalcFn) {
  return calcFn(num1, num2)
}

const add: CalcFn = (num1, num2) => {
  return num1 + num2
}

calc(20, 30, add)

接口继承

  • 接口和类一样是可以进行继承的,也是使用extends关键字:
    • 并且我们会发现,接口是支持多继承的(类不支持多继承)

  •  示例:
interface ISwim {
  swimming: () => void
}

interface IFly {
  flying: () => void
}


interface IAction extends ISwim, IFly {

}

const action: IAction = {
  swimming() {

  },
  flying() {
    
  }
}

交叉类型

  • 交叉类似表示需要满足多个类型的条件;
  • 交叉类型使用 & 符号;
  • 看下面的交叉类型:
    • 表达的含义是number和string要同时满足;
    • 但是有同时满足是一个number又是一个string的值吗?其实是没有的,所以MyType其实是一个never类型;

  •  交叉类型的应用
// 一种组合类型的方式: 联合类型
type WhyType = number | string
type Direction = "left" | "right" | "center"


// 另一种组件类型的方式: 交叉类型
type WType = number & string

interface ISwim {
  swimming: () => void
}

interface IFly {
  flying: () => void
}

type MyType1 = ISwim | IFly
type MyType2 = ISwim & IFly

const obj1: MyType1 = {
  flying() {

  }
}

const obj2: MyType2 = {
  swimming() {

  },
  flying() {
    
  }
}
  • 我们会发现interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?
    • 如果是定义非对象类型,通常推荐使用type,比如Direction、Alignment、一些Function;
  • 如果是定义对象类型,那么他们是有区别的:
    • interface 可以重复的对某个接口来定义属性和方法;
    • 而type定义的是别名,别名是不能重复的;

  •  示例:
document.getElementById("app") as HTMLDivElement
window.addEventListener

interface Window {
  age: number
}
window.age = 19
console.log(window.age)


type IBar = {
  name: string
  age: number
}

type IBar = {
}

字面量赋值

interface IPerson {
  name: string
  age: number
  height: number
}

function printInfo(person: IPerson) {
  console.log(person)
}

// 代码会报错
// printInfo({
//   name: "why",
//   age: 18,
//   height: 1.88,
//   address: "广州市"
// })

const info = {
  name: "why",
  age: 18,
  height: 1.88,
  address: "广州市"
}

printInfo(info)
  • 这是因为TypeScript在字面量直接赋值的过程中,为了进行类型推导会进行严格的类型限制。
  • 但是之后如果我们是将一个 变量标识符 赋值给其他的变量时,会进行freshness擦除操作

枚举类型

  • 枚举类型是为数不多的TypeScript特性有的特性之一:
    • 枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型;
    • 枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型;
// type Direction = "left" | "Right" | "Top" | "Bottom"
enum Direction {
  LEFT = "LEFT",
  RIGHT = "RIGHT",
  TOP = "TOP",
  BOTTOM = "BOTTOM"
}

let name: string = "abc"
let d: Direction = Direction.BOTTOM

function turnDirection(direction: Direction) {
  console.log(direction)
  switch (direction) {
    case Direction.LEFT:
      console.log("改变角色的方向向左")
      break;
    case Direction.RIGHT:
      console.log("改变角色的方向向右")
      break;
    case Direction.TOP:
      console.log("改变角色的方向向上")
      break;
    case Direction.BOTTOM:
      console.log("改变角色的方向向下")
      break;
    default:
      const foo: never = direction;
      break;
  }
}

turnDirection(Direction.LEFT)
turnDirection(Direction.RIGHT)
turnDirection(Direction.TOP)
turnDirection(Direction.BOTTOM)

泛型

// 类型的参数化
// 在定义这个函数时, 我不决定这些参数的类型
// 而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型
function sum<Type>(num: Type): Type {
  return num
}

// 1.调用方式一: 明确的传入类型
sum<number>(20)
sum<{name: string}>({name: "why"})
sum<any[]>(["abc"])

// 2.调用方式二: 类型推到
sum(50)
sum("abc")

泛型的基本补充

function foo<T, E, O>(arg1: T, arg2: E, arg3?: O, ...args: T[]) {
}

foo<number, string, boolean>(10, "abc", true)

泛型接口

interface IPerson<T1 = string, T2 = number> {
  name: T1
  age: T2
}

const p: IPerson = {
  name: "why",
  age: 18
}

 泛型类

class Point<T> {
  x: T
  y: T
  z: T

  constructor(x: T, y: T, z: T) {
    this.x = x
    this.y = y
    this.z = z
  }
}

const p1 = new Point("1.33.2", "2.22.3", "4.22.1")
const p2 = new Point<string>("1.33.2", "2.22.3", "4.22.1")
const p3: Point<string> = new Point("1.33.2", "2.22.3", "4.22.1")

const names1: string[] = ["abc", "cba", "nba"]
const names2: Array<string> = ["abc", "cba", "nba"] // 不推荐(react jsx <>)

泛型约束

  • 有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
    • 比如string和array都是有length的,或者某些对象也是会有length属性的;
    • 那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?
interface ILength {
  length: number
}

function getLength<T extends ILength>(arg: T) {
  return arg.length
}

getLength("abc")
getLength(["abc", "cba"])
getLength({length: 100})

模块化开发

  • TypeScript支持两种方式来控制我们的作用域:
    • 模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS;
    • 命名空间:通过namespace来声明一个命名空间

命名空间namespace

  • 命名空间在TypeScript早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名 冲突的问题。

类型的查找

  • typescript文件:.d.ts文件
    • 我们之前编写的typescript文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方;
    • 还有另外一种文件 .d.ts 文件,它是用来做类型的声明(declare)。 它仅仅用来做类型检测,告知typescript我们有哪些类型
  • 那么typescript会在哪里查找我们的类型声明呢?
    • 内置类型声明;
    • 外部定义类型声明;
    • 自己定义类型声明

内置类型声明

  • 内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件;
    • 包括比如Math、Date等内置类型,也包括DOM API,比如Window、Document等;
  • 内置类型声明通常在我们安装typescript的环境中会带有的;

外部定义类型声明和自定义声明

  • 外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明。
  • 这些库通常有两种类型声明方式:
  • 什么情况下需要自己来定义声明文件呢?
    • 情况一:我们使用的第三方库是一个纯的JavaScript库,没有对应的声明文件;比如lodash
    • 情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;

声明变量-函数-类

声明模块

  • 我们也可以声明模块,比如lodash模块默认不能使用的情况,可以自己来声明这个模块:

  • 声明模块的语法: declare module '模块名' {}
    • 在声明模块的内部,我们可以通过 export 导出对应库的类、函数等

declare文件

  • 在某些情况下,我们也可以声明文件:
    • 比如在开发vue的过程中,默认是不识别我们的.vue文件的,那么我们就需要对其进行文件的声明;
    • 比如在开发中我们使用了 jpg 这类图片文件,默认typescript也是不支持的,也需要对其进行声明;

 declare命名空间

  • 比如我们在index.html中直接引入了jQuery:
    • CDN地址: https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
    • 我们可以进行命名空间的声明:

  •  在main.ts中就可以使用了:

tsconfig.json文件

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值