TypeScript 学习笔记

7、Typescript

官方文档

Atwood定律

Stack Overflow 的创立者之一 Jeff Atwood 在 2007年提出了著名的 Atwood 定律

any application that can be written in JavaScript , will eventually be written in JavaScript.

任何可以使用JavaScript来实现的应用都最终会使用 JavaScript 实现

  • web端
  • 移动端 RN uni
  • 小程序端
  • 桌面端 electron
  • 服务器端 node

类型检测缺失, 很容易造成代码崩溃等等

7.1 类型约束

为了弥补 JavaScript 类型约束上的缺陷,增加类型约束,很多公司推出了自己的方案

2014年,Facebook 推出了 flow 来对JavaScript进行类型检查

同年,Microsoft 微软,也推出了 typeScript 1.0 版本

vue2.x 采用的就是 flow 来做的类型检查

vue3.x 采用的是 TS

7.2 认识TypeScript

TS 是 拥有类型的 js 超集,它可以编译成 普通、干净、完整的 js 代码

始于JavaScript,归于 JavaScript

TS 从今天数以百万计的js开发者所熟悉的语法和语义开始。使用现有的JS代码,包括流行的js库,并从JS代码中调用TS代码

TS 代码可以编译出 纯净、简洁 的js 代码,并且可以运行再任何浏览器上、node.js 环境中、任何支持ECMAScript 3 (或更高的版本) 的 JS 引擎中

TS是一个强大的工具,用于构建大型项目

  • 类型允许JS开发者在开发JS 应用程序时,使用高效的开发工具和常用操作,比如静态检查和代码重构
  • 类型时可选的,类型推断让一些类型的注释使你的代码的静态验证有很大的不同。类型让你定义软件组件之间的接口和洞察现有 JS 库的行为

拥有先进的JS

  • TypeScript提供最新的和不断发展的JavaScript特性,包括那些来自2015年的ECMAScript和未来的提案中的特性,比如异步功能和 Decorators,以帮助建立健壮的组件;
  • 这些特性为高可信应用程序开发时是可用的,但是会被编译成简洁的ECMAScript3(或更新版本)的JavaScript;

众多项目采用TS

  • angular
  • vue3
  • vscode
  • ant-design UI库
  • 目前公司比较流行 Vue3+TS react+ts
  • 小程序也是支持TS 的
7.3 TS 的编译环境

在前面我们提到过,TypeScript最终会被编译成JavaScript来运行,所以我们需要搭建对应的环境:我们需要在电脑上安装TypeScript,这样就可以通过TypeScript的Compiler将其编译成JavaScript;

# 安装命令
npm install typescript -g
# 查看版本号 2022年1月21,版本号是 Version 4.5.5
tsc --version
7.4 TS 运行环境

方案一

  1. 第一步,通过 tsc 编译 TS 到 JS 代码
  2. 第二步,在浏览器或者Node环境下运行JS代码

方案二

通过webpack,配置本地的TypeScript编译环境和开启一个本地服务,可以直接运行在浏览器上;

部署文章链接


方案三

通过ts-node库,为TypeScript的运行提供执行环境;

# 安装ts-node
npm install ts-node -g
# 另外 ts-node 还需要安装 tslib 喝 @types/node 两个包
npm install tslib @types/node -g
# 现在就可以直接通过  ts-node 运行 TS后缀的文件
ts-node math.ts

小提示: 在写TS 的时候,默认情况下,所有的TS 文件,都是在一个作用域下编译的,所以可能会冲突,如果要把每一个文件,看成一个单独的作用域, 需要在每个文件里面最后加上 export {} 。还有其他解决方案

7.5 TS 变量的声明

声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解

var/let/const 标识符: 数据类型 = 赋值;

注意:这里的string是小写的,和String是有区别的,string是TypeScript中定义的字符串类型,String是ECMAScript中定义的一个类

类型推导:在开发中,有时候为了方便起见我们并不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过TypeScript本身的特性帮助我们推断出对应的变量类型:

let message = "ddg"
message = 123 // 这里其实会报错的

这是因为在一个变量第一次赋值时,会根据后面的赋值内容的类型,来推断出变量的类型

7.6 TS 和 JS 的数据类型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAIldHYF-1643279385255)(coderwhy-vue3-组件化.assets/image-20220121145723489.png)]

TS 是 JS 的超集

7.7 TS 的数据类型 number

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

ES6新增了二进制和八进制的表示方法,而TypeScript也是支持二进制、八进制、十六进制的表示:

let num: number = 123
num = 333
let num1: number = 100 // 十进制
let num2: number = 0b111 // 二进制,以 0b 开头
let num3: number = 0o456 // 八进制 ,以 0o 开头
let num4: number = 0x123abc // 16进制 ,以 0x 开头
7.8 TS 的数据类型 boolean
let num: boolean = true
7.9 TS 的数据类型 string
let message1: string = "hello world"
let message2: string = "ddg"

const name = "ddg"
const age = 18
const height = 1.88

let message3 = `name:${name}  age:${age} height${height}`
console.log(message3);
7.10 TS 的数据类型 Array
// 确定一个事实,names 虽然是一个数组类型,但是数组中存放的是什么类型的元素呢?
// 一个数组中,在TS 开发中,最好存放的数据类型是固定的(string)
// 在数组中存放不同的类型 是不好的习惯
// 类型注解: type annotation
const names: Array<string> = []  // 第一种写法, 但是 不推荐 (在 react jsx 、vue jsx 中是有冲突的 <div></div>)

const names2: string[] = [] // 推荐

names.push('abc')
7.11 TS 的数据类型 Object 类型
const info = {
  name: "呆呆狗",
  age: 21
}
// 如果没有写 类型注解, 他会 自动的 推导出来
7.12 null 和 undefined 类型
let n1: null = null
let n2: undefined = undefined
7.13 symbol 类型

jses6 的新知识。可以通过 symbol来定义相同的名称,因为 symbol 函数返回的是不同的值

const t1 = Symbol("title")
const t2 = Symbol("title")
const info = {
  [t1]: "呆呆狗",
  [t2]: "学生"
}
export { }
7.14 any 类型

在某些情况下,我们确实无法确定一个变量的类型,并且可能它会发生一些变化,这个时候我们可以使用any类型

let message: any = "hello world"
// 类型 设置为 any 后,可以给它赋任何值

/* 
 当进行一些 类型断言 as any
 在不想给某些JS 添加具体的数据类型的时候,用 any 不会出错, 其实就当于 原生JS
*/
message = 123
message = true
7.15 unknown 类型

unknown是TypeScript中比较特殊的一种类型,它用于描述类型不确定的变量。

function foo() {
  return "abc"
}
function bar() {
  return 123
}
let flag = true
let result: any
// let result: unknown
// unknown类型只能赋值给 any 和 unknown类型
// any类型 可以赋值给任意类型

if (flag) {
  result = foo()
} else {
  result = bar()
}

//  如果 result 设置的是 any 类型 这样赋值是没问题的
let message: string = result
let num: number = result
console.log(result);
console.log(message);

// 如果 result 设置的是 unknown类型,然后给 message 和num 赋值 是不行的

export { }
7.16 void 类型

viod 通常用来指定一个函数,是没有返回值的,那么它的返回值就是 void 类型

  • 我们可以将 null 和 undefined 赋值给 void 类型,也就是函数可以返回 null 或者 undefined
function sum(num1: number, num2: number) {
  console.log(num1 + num2);
  // 如果没有返回值,默认会返回一个  undefined
  // 其实,这个函数,默认返回值的类型 就是  void 
  // 我们可以将 null 和 undefined 赋值给 void 类型,也就是函数可以返回 null 或者 undefined
}

// sum(123, 456)
console.log(sum(123, 456));

export { }
7.17 never类型

如果一个函数中是一个死循环或者抛出一个异常,这个函数是不会有返回值的。 那么写void类型或者其他类型作为返回值类型都不合适,我们就可以使用never类型

function foo(): never {
  // 死循环
  while (true) {
    // never 就表示 这个函数永远不会有返回值
  }
}
function bar(): never {
  throw new Error('返回错误')
}

// 这里的 message 是一个联合类型,可以是 字符串,可以是 数字
// 防止别人再增加了 message的类型注解后,没有写 相应的 case
function handleMessage(message: string | number | boolean) {
  switch (typeof message) {
    case 'string':
      console.log('string 处理方式');
      break
    case 'number':
      console.log('number 处理方式');
      break
    case 'boolean':
      console.log('boolean 处理方式');
      break
    default:
      // 永远不可能来到这里,所以可以给它赋值
      const check: never = message
  }
}
handleMessage(true)
// 如果 想要给 handleMessage 函数,传递一个布尔类型的值呢?
// 可以在  message 的类型注解 哪里,再加一个 Boolean类型
// 但是这样,还需要 再加 一个  case 'boolean'
7.18 tuple 类型
  • 首先,数组中通常建议存放相同类型的元素,不同类型的元素是不推荐放在数组中。(可以放在对象或者元组中)
  • 其次,元组中每个元素都有自己特性的类型,根据索引值获取到的值可以确定对应的类型;

基本使用

// tuple 元组,多种元素组合
// 元组类型,可以确定数组每一项的类型,
// 如果直接给数组写个any 类型,  不太安全
// ddg 19 180
const info: [string, number, number] = ["ddg", 18, 180]
const name = info[0]
console.log(name.length);
const age = info[1]
// console.log(age.length);

export { }

应用场景

// hooks : useState
// const [counter,setCounter] = useState(10)
// 如果要是  useState 里面要是一个数组的话,太不安全了

function useState(state: any) {
  // state 可能是一个 数组,字符串,数字,布尔,等等
  let currentState = state
  const changeState = (newState: any) => {
    currentState = newState
  }

  // 这样以后,我们在外面调用这个函数,并且解构, 两个值都是 any 类型
  // const arr: any[] = [currentState, changeState]
  const arrTuple: [any, (newState: any) => void] = [currentState, changeState]

  // return arr
  return arrTuple
}
// 这个时候, counter 还是 any 类型,而 setCounter 是一个函数类型
const [counter, setCounter] = useState(10)

export { }

应用场景优化

// hooks : useState
// const [counter,setCounter] = useState(10)
// 如果要是  useState 里面要是一个数组的话,太不安全了

// 如果要优化,就要用到 泛型
function useState<T>(state: T) {
  // state 可能是一个 数组,字符串,数字,布尔,等等
  let currentState = state
  const changeState = (newState: T) => {
    currentState = newState
  }

  // 这样以后,我们在外面调用这个函数,并且解构, 两个值都是 any 类型
  // const arr: any[] = [currentState, changeState]
  const arrTuple: [T, (newState: T) => void] = [currentState, changeState]

  // return arr
  return arrTuple
}
// 这个时候就没问题了
const [counter, setCounter] = useState(10)
const [flag, setFlag] = useState(true)
export { }
7.19 函数的参数类型
7.19.1 函数的参数和返回值
// 给参数 加上类型注解
// 给返回值加上类型注解,没有返回值的时候 就是  viod , 给返回值加上类型注解,一般写在函数的参数 括号的后面
// 在开发中,通常情况下,可以不写返回值的类型(自动推导)
function sum(num1: number, num2: number): number {
  // 上面这两个参数  是必须要传递的, 传一个就会报错
  return num1 + num2
}
  • 和变量的类型注解一样,我们通常情况下不需要返回类型注解,因为TypeScript会根据 return 返回值推断函数的返回类型;某些第三方库处于方便理解,会明确指定返回类型,但是这个看个人喜好
7.19.2 匿名函数的参数类型
function foo(message: string) {
  // 通常情况下,在定义一个函数时,都会给参数加上类型注释的
}

const names = ["abc", "c", "a"]
names.forEach((item) => {
  // 可以不给 item 加类型
  // item 的类型是根据上下文的环境推导出来的
  // 当我们把我们的一个函数作为参数传递给另外一个函数的时候,某些情况下,它会自动推导出参数的类型,这个时候可以不添加的类型注解
})
7.19.3 函数参数是对象类型
// 参数是一个对象
function printPoint(point: { x: number, y: number }) {
  // point: { x: number, y: number }   这个对象 里面的 每个键 可以用逗号或者分号进行隔开
  console.log(point.x);
  console.log(point.y);
}
printPoint({ x: 20, y: 30 })

// 参数是一个字符串
function printString(message: string) { }
7.19.4 函数的参数 可选
// 参数是一个对象
// 如果需要用户传 x,y,z,z是可选的呢?  ,z?:number , 在z 后面加个问号就可以了
function printPoint(point: { x: number, y: number, z?: number }) {
  // point: { x: number, y: number }   这个对象 里面的 每个键 可以用逗号或者分号进行隔开
  console.log(point.x);
  console.log(point.y);
}
printPoint({ x: 20, y: 30 })
printPoint({ x: 20, y: 30, z: 1111 })

// 参数是一个字符串
function printString(message: string) { }
export { }
7.19.5 联合类型

TypeScript的类型系统允许我们使用多种运算符,从现有类型中构建新类型。

  • 联合类型(Union Type)
  • 联合类型是由两个或者多个其他类型组成的类型
  • 表示可以是这些类型中的任何一个值
  • 联合类型中的每一个类型被称之为联合成员(union’s members
// number | string 联合类型
function printID(id: number | string) {
  // 使用联合类型的值的时候,要特别的小心
  // narrow :  缩小
  if (typeof id === 'string') {
    // ts 帮助确定 id 一定是 string 类型
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}
printID(123)
printID('abc')
export { }
7.19.6 类型别名
// type 定义类型别名
type IDType = string | number | boolean
type PointType = {
  x: number,
  y: number,
  z: number
}
function printId(id: IDType) {
}
function Point(id: PointType) {
}
export { }
7.20 类型断言 as

有时候TypeScript无法获取具体的类型信息,这个我们需要使用类型断言(Type Assertions)

比如我们通过 document.getElementById,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它

具体的类型

TypeScript只允许类型断言转换为 更具体 或者 不太具体 的类型版本,此规则可防止不可能的强制转换

不太具体,指的就是 any 或者 unknown

// <img id="ddg"/>
const el = document.getElementById("ddg") as HTMLImageElement
el.src = "url地址"  // 会报错: 类型“HTMLElement”上不存在属性“src”。
// document.getElementById("ddg") as HTMLImageElement 这样修改就不会报错了
// 把普遍的类型 转成  具体的类型

class Person { }

class Student extends Person {
  studying() { }
}

function sayHello(p: Person) {
  // p.studying() // 直接访问是 访问不到的 
  (p as Student).studying()
}

const stu = new Student()
sayHello(stu)

// 3.  建议不要这样做
const message = "hello world"
// const num: number = message
// const num: number = (message as any) as number    这个可以
// const num: number = (message as unknown) as number   这个也可以
export { }
7.21 非空类型断言
// message ? => undefined | string
function printMessage(message?: string) {
  // 别人有可能传,也有可能不传
  // 如果不传,这样会报错
  // console.log(message.length);

  // 但是,我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言
  // 非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测
  // ! 表示的含义就是 message 一定有值
  console.log(message!.length);
}
printMessage("hello world")
// printMessage()

虽然逃避了检查,但是代码还是不够严谨

7.22 可选链的使用 es11(es2020)

可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性

  • 可选链使用可选链操作符 ?.
  • 它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行
type Person = {
  name: string,
  friend?: {
    name: string,
    age?: number,
  }
}

const info: Person = {
  name: "呆呆狗",
  // friend: {
  //   name: "kobe"
  // }
}
// 另外一个文件中,
console.log(info.name);
//console.log(info.friend);   // info.friend 有可能取到,有可能取不到。friend 是可选的

console.log(info.friend?.name);
// info 的 friend 可能有值 也可能没有, 如果没有friend,后面的代码不再执行,整个表达式返回 undefined

7.23 ?? !!

!!

  • 将一个其他类型转换成boolean类型
  • 类似于 Boolean(变量)的方式

??

  • 它是ES11增加的特性
  • 空值合并操作符(??)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数
const message = "hello wolrd"
// const flag = Boolean(message)
// console.log(flag);
// !! 本身就是 js 的特性
const flag = !!message
console.log(flag);

let message: string | null = null
// 如果 message 为 null 给他一个默认值
const content = message ?? "你好啊"
console.log(content);

export { }
7.24 字面量类型

字面量更常见的是 联合类型,

// 如果是 let 定义,那它的类型是  string
let message = "hello world"
// 如果是 const 定义的,那它的类型是 const message2: "hello world"
// 也就是说 hello world 就是一个类型,叫做 字面量类型
const message2 = "hello world"

let num: 123 = 123
// num = 321 修改字面类的值,是不可能的, 类型必须和 值 相等


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

align = "right" // 这样赋值没问题, 赋的值 必须是 类型的其中一个

export { }
7.25 字面量推理
// 没有写类型注解,他会自动推导
const info = {
  name: "ddg",
  age: 20
}

info.name = "kobe"

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

}

// 方案1: 写死 类型注解 .. 建议方案1
// type Request = {
//   url: string,
//   method: Method
// }

// const options: Request = {
//   url: "http://www.daidaigou.top",
//   method: "POST"
// }


// 方案3 
const options = {
  url: "http://www.daidaigou.top",
  method: "POST"
} as const

// 在传递的时候,第二个参数会报错,因为 options.method 这个属性的类型会 自动推导出是 string类型 , 而函数的第二个参数是 Method
// request(options.url, options.method)

// request(options.url, options.method)

// 方案2 用类型断言
request(options.url, options.method as Method)

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

常见的类型保护有以下几种

  • typeof
  • 平等缩小(比如 === , ! = =)
  • instanceof
  • in
  • 等等……
// 1.typeof 的类型缩小
function printID(id: number | string) {
  // id 是一个联合类型,不能随便进行处理
  // 整个语句的过程就叫做: 类型缩小
  if (typeof id === 'string') {
    // 在这里 拿到的 id  一定是一个 string 类型
    console.log(id.toUpperCase());
  } else {
    console.log(id);

  }
}

// 2.平等的类型缩小 (===  == !== !=  switch)

function printDireaction(direction: 'left' | 'right' | 'top' | 'bottom') {
  // if (direction === "left") {

  // }else if () {

  // }

  // switch(direction){
  //   case 'left':  等等
  // }
}

// 3.instanceof 
function printTime(time: string | Date) {
  if (time instanceof Date) {
    // 如果是 date 类型
  } else {
    console.log('xxx');
  }
}

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)
// work 传的 是一个 Student 或 Teacher的实例

// 4. in
type Fish = {
  swimming: () => void
}

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

function walk(animal: Fish | Dog) {
  if ('swimming' in animal) {
    // 就是判断属性,这里的 Fish Dog 不是类,只是 字面量
    animal.swimming()
  } else {
    animal.running()
  }
}

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

  }
}
walk(fish)
7.27 函数类型
7.27.1 函数类型
// 1.函数作为参数时,在参数中如何编写类型
function foo() { }
function bar(fn: () => void) {
  fn()
}

bar(foo)

// 2,定义常量时,编写函数的类型
const add: (num1: number, num2: number) => number = (num1: number, num2: number) => {
  return num1 + num2
}
7.27.2 参数的可选类型
// y -> undefined | number
function foo(x: number, y?: number) {

}
// 第一个参数,必须是 必选的 !

foo(20, 30)
foo(40)
7.27.3 参数的默认值
// 先写 必选参数  - 有默认值的参数 - 可选参数
function foo(x: number = 20, y: number = 100) {
  console.log(x, y);

}
foo(undefined, 40)
7.27.4 函数的剩余参数
function sum(...nums: number[]) {
  console.log(nums);

}
sum(10, 20, 30, 40, 50)
7.27.5 this
// this 是可以被 推导出来,  info对象(TS推导出来的)
const info = {
  name: "呆呆狗",
  eating() {
    console.log(this.name + "  eating");

  }
}
info.eating()
// 在TS中 this 是不能乱用的
// 独立函数 推导不出来 this
// 解决方案:传一个参数,并且放在第一位
function eating(this: { name: string }) {
  console.log(this.name + "  eating");

}
const info = {
  name: "呆呆狗",
  eating: eating,

}
// 隐式绑定
info.eating()


// 显示绑定
// 如果 传递了 this ,就不能直接写  eating()
eating.call({ name: "2222" })

export { }
7.27.6 函数的重载

在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?

  • 在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用
  • 一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现
// 函数的重载:函数名称相同,但是参数不同的几个函数,就是函数的重载
// TS 的函数重载,重上往下匹配,函数体 写在最后

// 别的语言写法
// function add(num1: number, num2: number) { }
// function add(num1: number, num2: number, num3: number) { }

// TS 函数重载写法
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
}

console.log(add(20, 30));
console.log(add('123', '456'));

// 在函数的重载中,实现函数是不能直接被调用的。必须要  重上往下 进行匹配
// add({name:"呆呆狗"},{age:29})

// 如果能通过联合类型  简单实现,那就用联合类型

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

// console.log(getLength("abc"));
// console.log(getLength([12, 23, 11, 23]));

// 实现方式二:函数重载 实现
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([12, 23, 11, 23]));
7.28 类
  • 在早期的JavaScript开发中(ES5)我们需要通过函数和原型链来实现类和继承,从ES6开始,引入了class关键字,可以更加方便的定义和使用类
  • TypeScript作为JavaScript的超集,也是支持使用class关键字的,并且还可以对类的属性和方法等进行静态类型检测
  • 实际上在JavaScript的开发过程中,我们更加习惯于函数式编程
    • 比如React开发中,目前更多使用的函数组件以及结合Hook的开发模式
    • 比如在Vue3开发中,目前也更加推崇使用 Composition API
  • 但是在封装某些业务的时候,类具有更强大封装性,所以我们也需要掌握它们
  • 类的定义我们通常会使用class关键字:
    • 在面向对象的世界里,任何事物都可以使用类的结构来描述;
    • 类中包含特有的属性和方法;
7.28.1 类的定义
// 类的属性必须初始化,要么直接在定义的时候进行初始化,要么用constructor 初始化
class Person {
  // 属性和方法
  // 可以在定义属性后面,直接进行赋值,初始化
  // name: string = ""
  // age: number = 0
  name: string
  age: number

  // 也可以用 constructor 来进行初始化
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }

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

  }
}
const p = new Person("ddg", 21)
export { }
7.28.2 类的继承
class Person {
  name: string = ""
  age: number = 0
  eating() {
    console.log("eating");
  }
}
class Student extends Person {
  sno: number = 0
  studying() {
    console.log("studying");
  }
}
class Teacher extends Person {
  titlle: string = ""
  teaching() {
    console.log("Teachering");
  }
}

const stu = new Student()
console.log(stu);
stu.eating()

class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  eating() {
    console.log("Person eating");
  }
}
class Student extends Person {
  sno: number
  constructor(name: string, age: number, sno: number) {
    super(name, age)  // 调用父类的构造方法
    this.sno = sno
  }
  // 子类对父类的方法,不满意 可以进行重写
  eating() {
    console.log("Student eating");
    // 如果 在 子类里面,非得 调用 父类的 方法呢?
    super.eating()
  }
  studying() {
    console.log("studying");
  }
}


const stu = new Student("ddg", 21, 201813212)
console.log(stu);
stu.eating()

export { }
7.28.3 类的成员修饰符

在TypeScript中,类的属性和方法支持三种修饰符: public、private、protected

  • public 修饰的是在任何地方可见、公有的属性或方法,默认编写的属性就是public的;
  • private 修饰的是仅在同一类、当前类中可见、私有的属性或方法;
  • protected 修饰的是仅在类自身及子类中可见、受保护的属性或方法;
// protected: 在类内部和子类中可以访问,
class Person {

  protected name: string = ""
  getName() {
    return this.name
  }
}
const p = new Person()
console.log(p.name); // 这样是不可以访问的 会报错

export { }
7.28.4 readonly只读属性
class Person {
  // 只读属性,可以在构造器中赋值,赋值完毕后,就不可以修改了
  // 属性本身不能进行修改,但是如果它是对象类型,对象中的属性是可以修改的。类似于 const 
  readonly name: string
  age?: number
  readonly friend?: Person
  constructor(name: string, friend?: Person) {
    this.name = name
    this.friend = friend
  }
}

const p = new Person("ddg", new Person("boke"))

// 此时的 name 是 只能访问,不能修改
// p.name = "123"
console.log(p.name);
console.log(p.friend);

// 不可以直接修改 friend
// p.friend = new Person("hames")
if (p.friend) {
  p.friend.age = 30
}
7.28.5 getter 和 setter
class Person {
  private _name: string
  constructor(name: string) {
    this._name = name
  }
  // 访问器  setter / getter
  // 建议:私有属性,下划线开头
  set name(newName) {
    this._name = newName
  }
  get name() {
    return this._name
  }

}

const p = new Person("呆呆狗")
// console.log(p.name);

p.name = "呆呆狗------"
console.log(p.name);
7.28.6 静态成员
class Student {
  static time: string = "20:00"
  static attdendClass() {
    console.log('去学校');
  }

}
// 可以通过  类,直接进行访问
console.log(Student.time);
console.log(Student.attdendClass());

export { }
7.28.7 抽象类
  • 抽象函数,是可以没有 函数体的, 抽象函数必须在抽象类中
  • 抽象类 不能被实例化,也就是不能通过 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()
    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")
7.28.8 类的类型
class Person {
  name: string = "123"
  eating() { }
}
const p = new Person()

// 类 本身,也是可以作为一个类型的
const p1: Person = {
  name: "呆呆狗",
  eating() { }
}

function printPerson(p: Person) {
  console.log(p.name);
}
printPerson(new Person())
printPerson({ name: "打", eating: function () { } })
7.29 接口
7.29.1 接口定义对象类型
// 第一种 通过类型(type)别名来声明对象类型
// type InfoType = { name:string,age:number}

// 第二种,通过 接口 interface
// interface 类型名称
// 接口后面的 类型名称,一般前面都会加一个大写的 I 
interface InfoType {
  readonly name: string,
  age: number
}

const info: InfoType = {
  name: "ddg",
  age: 12
}

// 因为加了只读属性,所以会报错
// info.name = "@22"
7.29.2 索引类型
// interface 来定义索引类型
interface IndexLanguage {
  //  index 是一个形参
  // [index: number]: string | boolean
  [index: number]: string
}

const frontLanguage: IndexLanguage = {
  0: "html",
  1: "css",
  2: "js",
  3: "vuejs",
  // 4: true
}

interface ILanguageYear {
  // name 就是一个形参
  [name: string]: number
}

const languageYear = {
  "c": 1972,
  "java": 1995,
  "js": 1996,
  "ts": 2014
}
7.29.3 函数类型
interface CalcFn {
  // 接口定义 函数类型
  (n1: number, n2: number): number
}

// 还是推荐用  type 来定义函数类型
// type 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)
7.29.4 接口继承

吧多个接口结合在一起,有两种方式

  1. 接口继承
  2. 交叉类型
interface ISwim {
  swmming: () => void
}
interface IFly {
  flying: () => void
}

interface IAaction extends ISwim, IFly {
  // 接口的继承,支持多继承
}

const action: IAaction = {
  swmming() { },
  flying() { }
}
7.29.5 交叉类型
// 一种组合类型的方式 : 联合类型
type DdgType = number | string
type Direaction = "left" | "right" | "center"

// 另一种组合类型的方式:交叉类型
// 这里的 & 表示的是:既符合 前面的类型,也得符合 后面的类型
type oneType = number & string

// &:它的意义

interface ISwim {
  swmming: () => void
}
interface IFly {
  flying: () => void
}

type mytype1 = ISwim | IFly
type mytype2 = ISwim & IFly

const obj: mytype1 = {
  // swmming 和 flying二选一即可
  swmming() {}
}
const obj2: mytype2 = {
  // 两个都写才是可以
  swmming() {},
  flying() { }
}
export { }
7.29.6 接口的实现
interface IEat {
  eating: () => void
}
interface ISwim {
  swmming: () => void
}

// 类实现接口
class Animal {

}
// 类的继承:只能实现单继承 
// 实现:实现接口,可以实现多个接口
class Fish extends Animal implements ISwim, IEat {
  eating() {
    console.log('fish eating');
  }
  swmming() {
    console.log('fish swmming');
  }
}
class Person implements ISwim {
  swmming() {
    console.log('person swimming');

  }
}
// 编写一些公共的api : 面向接口编程
function swimAction(swimable: ISwim) {
  swimable.swmming()
}

// 1.所有实现了接口的类 对应的对象,都可以传入
swimAction(new Fish())
swimAction(new Person())
7.29.7 interface和 type 的区别
  • 我们会发现interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?
    • 如果是定义非对象类型(函数、联合类型),通常推荐使用type,比如Direction、Alignment、一些Function;
  • 如果是定义对象类型,那么他们是有区别的
    • interface 可以重复的对某个接口来定义属性和方法;
    • 而type定义的是别名,别名是不能重复的;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiR6veYA-1643279385256)(coderwhy-vue3-组件化.assets/image-20220127120910909.png)]

interface IFoo {
  name: string
}
interface IFoo {
  age: number
}
// TS 允许定义 两个名字一样的 接口。TS内部会把这两个接口合并的

const foo: IFoo = {
  age: 18,
  name: "sting"
}


// type 不允许两个 名称一样的 别名, 就是别名不能重复
// type IBar = {
//   name: string
// }
// type IBar = {
//   age: number
// }
7.29.8 字面量赋值
interface IPerson {
  name: string,
  age: number,
  height: number
}

const info = {
  name: "ddg",
  age: 19,
  height: 2.0,
  address: "北京市"
}
// 在类型检查的时候,info 赋值给 IPerson ,两者对比,先擦除 address,然后比较如何相同,则允许赋值,不相同则不允许
// freshness 擦除
const p: IPerson = info

// 它的意义
function p2(obj: IPerson) { }
// p2({ name: "ddh", age: 20, height: 1.8, address: "北京" }) // 会报错,因为多传了一个 address 属性
const obj2 = { name: "ddh", age: 20, height: 1.8, address: "北京" }
p2(obj2)
7.29.9 枚举类型
  • 枚举其实就是将一组可能出现的值,一个个列举出来,定义在一个类型中,这个类型就是枚举类型
  • 枚举允许开发者定义一组命名常量,常量可以是数字、字符串类型
// type Direction = "left" | "Right" | "Top" | "Bottom"

enum Direction {
  LEFT,
  RIGHT,
  TOP,
  BOTTOM
}


function turnDirection(direction: 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)
enum Direction {
  // 这四个属性的默认值 都是0  ,也可以修改值
  // 如果只修改第一个,第一个改成100, 那后面三个 会递增的
  // 如果只修改第四个,第一个还是0,第二个还是1,第三个还是2
  // 它的值,也可以是字符串,一般是字符串或者数字
  LEFT = 100,
  RIGHT = 200,
  TOP = 300,
  BOTTOM = 400
}


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)

export { }
7.30 泛型
  • 软件工程的主要目的是构建不仅仅明确和一致的API,还要让你的代码具有很强的可重用性
    • 比如我们可以通过函数来封装一些API,通过传入不同的函数参数,让函数帮助我们完成不同的操作
    • 但是对于参数的类型是否也可以参数化呢
  • 什么是类型的参数化?
    • 我们来提一个需求:封装一个函数,传入一个参数,并且返回这个参数;
  • 如果我们是TypeScript的思维方式,要考虑这个参数和返回值的类型需要一致:
7.30.1 基本使用
// 类型的参数化

// 在定义这个函数时, 我不决定这些参数的类型
// 而是让调用者以参数的形式告知,我这里的函数参数应该是什么类型
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")
7.30.2 泛型的基本补充
  • T:Type的缩写,类型

  • K、V:key和value的缩写,键值对

  • E:Element的缩写,元素

  • O:Object的缩写,对象

function foo<T, E>(arg1: T, arg2: E) { }
foo<number, string>(10, '你好啊~~~')
7.30.3 泛型接口
interface IPerson<T1 = string, T2 = number> {
  name: T1,
  age: T2
}

// 这里必须要写对应的类型,不会自动推导
const p: IPerson<string, number> = {
  name: "ddg",
  age: 20
}

const p2: IPerson = {
  name: "ddg1",
  age: 30
}
7.30.4 泛型类
class Point<T> {
  x: T
  y: T
  z: T

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

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 <>)
export { }
7.30.5 泛型的类型约束

比如string和array都是有length的,或者某些对象也是会有length属性的

那么只要是拥有length的属性都可以作为我们的参数类型,那么应该如何操作呢?

interface ILength {
  length: number
  // 表示:如果是一个对象类型,则必须有 length 属性
}

// extends 继承某个类型,
// 对这个泛型 进行一个约束
function getLength<T extends ILength>(arg: T) {
  return arg.length // 得有length 这个属性,传过来以后才不会报错
}

// getLength(123) // number类型是没有 length 属性的
getLength("abc") // 因为字符串本身就是有 length属性
getLength(["abc", "cba"])
getLength({ length: 100 })
console.log(getLength({ length: 100 }));
console.log(getLength(["abc", "cba"]));
7.31 模块化开发

TypeScript支持两种方式来控制我们的作用域

  • 模块化:每个文件可以是一个独立的模块,支持ES Module,也支持CommonJS
  • 命名空间:通过namespace来声明一个命名空间

命名空间namespace

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

export namespace time {
  // 在 这个 time 花括号外,想要拿到 ,就必须要  export 导出
  export function format(time: string) {
    return "2022-22-02"
  }
  export function foo() { }
  export let name: string = "呆呆狗"
}

export namespace price {
  export function format(price: number) {
    return "99.9999"
  }
}

time.format
// time.foo
// main.ts
import { time, price } from './utils/format';
console.log(time.format('xxx'));
7.32 类型查找

之前我们所有的typescript中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型:

const imgId = document.getElementById('image') as HTMLImageElement

大家是否会奇怪,我们的HTMLImageElement类型来自哪里呢?甚至是document为什么可以有getElementById的方法呢?

其实这里就涉及到typescript对类型的管理和查找规则了

  • 我们之前编写的typescript文件都是 .ts 文件,这些文件最终会输出 .js 文件,也是我们通常编写代码的地方
  • 还有另外一种文件 .d.ts 文件,它是用来做类型的声明(declare)。 它仅仅用来做类型检测,告知typescript我们有哪些类型

那么typescript会在哪里查找我们的类型声明呢?

  • 内置类型声明
  • 外部定义类型声明 (一般是第三方库自带类型声明文件,比如axios)
  • 自己定义类型声明
7.33 内置类型声明
  • 内置类型声明是typescript自带的、帮助我们内置了JavaScript运行时的一些标准化API的声明文件;
  • 包括比如Math、Date等内置类型,也包括DOM API,比如Window、Document等

内置类型声明通常在我们安装typescript的环境中会带有的 官网地址

7.34 外部定义类型声明

比如 axios 就是外部定义类型声明

node_modules => axios =>index.d.ts 。 axios已经帮我们弄好了

  • 外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明。
  • 这些库通常有两种类型声明方式:
    • 方式一:在自己库中进行类型声明(编写.d.ts文件),比如axios
    • 方式二:通过社区的一个公有库DefinitelyTyped存放类型声明文件
      • 该库的GitHub地址:https://github.com/DefinitelyTyped/DefinitelyTyped/
      • 该库查找声明安装方式的地址:https://www.typescriptlang.org/dt/search?search=
      • 比如 react 的 :npm i react ,npm i @types/react --save-dev
7.35 自己定义类型声明

一般在 src 下 新建一个 .d.ts 结尾的文件

// declare 就是声明的
declare module 'lodash' {
  export function join(arr: any[]): void { }
}
// main.ts   ,  前提没有执行过  npm i @types/lodash --save-dev
import lodash from 'lodash'
console.log(lodash.join(["1"]));

声明变量

在根文件的 index.html 文件中
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js
  "></script>
  <script>
    let whyName = "coderwhy"
    let whyAge = 18
    let whyHeight = 1.88

    function whyFoo() {
      console.log("whyFoo")
    }

    function Person(name, age) {
      this.name = name
      this.age = age
    }
  </script>
// main.ts
import { add, sub } from './utils/math';

import { time, price } from './utils/format';

// import lodash from 'lodash'
// console.log(lodash.join(["1"]));


// TS 里面,引入一个图片 也会报错
import nhltImg from './img/nhlt.jpg'

console.log(add(20, 30));
console.log(sub(20, 30));

console.log(time.format('xxx'));

console.log(whyName);
console.log(whyAge);
console.log(whyHeight);
console.log(whyFoo());

const p = new Person("ddg", 20)
console.log(p);

$.ajax({})
// ddg.d.ts
// .d.ts 文件不需要写实现,只定义即可
// declare 就是声明的
// 这是声明的模块
declare module 'lodash' {
  export function join(arr: any[]): void { }
}

// 也可以声明 变量 / 函数 / 类
declare let whyName: string
declare let whyAge: number
declare let whyHeight: number
declare function whyFoo(): void


declare class Person {
  name: string
  age: number
  constructor(name: string, age: number)
}

// 声明文件
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'
declare module '*.svg'
declare module '*.gif'

// 声明命名空间
// 举个例子,在 index.html 中引入了 jq 的 cdn地址
declare namespace $ {
  export function ajax(settings: any): any
}

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值