TypeScript学习3

198 阅读6分钟

代码示例传送门

TypeScript接口的使用

1.声明对象类型

接口和 类型别名类似,都是用来定义对象格式的

// 常规通过类型 type 别名来声明对象类型
type InfoTypeOld = {readonly name: string, age: number}
​
const inf: InfoTypeOld = {
  name: "jj",
  age: 20
}
​
​
// 建议方法
// 接口 interface 方式声明对象
interface InfoType {
  name: string,
  readonly age: number
}
​
const info: InfoType = {
  name: "tom",
  age: 18
}

2.索引类型

interface接口可以定义索引的类型

// 通过 interface 定义索引类型
// 限定索引是number类型, 值是string类型
interface IndexLanguage {
  [index: number]: string
}
​
const frontLanguage:IndexLanguage = {
  0: "HTML",
  1: "CSS",
  2: "JavaScript",
  // "X": "VueJs"
}
​
// 限定索引是 string, 值 是number
interface languageBirthday {
  [index: string]: number
}
​
const languageYear: languageBirthday = {
  "C": 1972,
  "Java": 1995,
  "JavaScript": 1996,
  "TypeScript": 2014
}

3.函数类型

定义接口内属性为函数类型

// type CalcFn = (n1: number, n2: number) => number
interface CalcFn {
  calc: (n1: number, n2: number) => number
}
​
function calc(num1: number, num2: number, calcFn: CalcFn) {
  return calcFn.calc(num1, num2)
} 
​
const add: CalcFn = {
  calc: (num1, num2) => num1 + num2
}
​
const result = calc(20, 30, add)
console.log(result);

4.接口的继承

接口也能够继承,不过接口的继承可以继承多个,类的继承只能继承一个

接口继承后,能够合并接口的属性

interface Parent1 {
  beard: boolean 
}
​
interface Parent2 {
  color: string
}
​
interface Person extends Parent1,Parent2 {
  from: string
}
​
const p: Person = {
  beard: true,
  color: "yellow",
  from: "China"
}
​
console.log(p);

5.交叉类型

联合类型标识符是 | :表示既可以..也可以...

交叉类型标识符是 &:表示都要

// 联合类型
type WhyType = number | string
type Direction = "left" | "right"// 交叉类型
type WDType = number & stringinterface ISwim {
  swimming: () => void
}
interface IFly {
  flying: () => void
}
​
type MyType1 = ISwim | IFly
type MyType2 = ISwim & IFlyconst obj1:MyType1 = {
  flying() {}
}
​
const obj2:MyType2 = {
  flying() {},
​
  swimming() {}
}

6.接口的实现

接口中的方法类似于抽象类中的方法,属性只需要写类型,当实现的时候必须都要实现函数体。

接口的实现可以一次实现多个接口

interface IEat {
  eating: () => void
}
​
interface ISwim {
  swimming: () => void
}
// 类实现接口
class Animal {
  type: string = ""
}
​
// 继承:只能继承一个类
// 实现:实现接口,可以实现多个接口
class Fish extends Animal implements ISwim, IEat{
  constructor(type: string = "草鱼") {
    super()
    this.type = type
  }
  eating() {
    console.log("fish eating");
  }
  swimming() {
    console.log("fish swimming");
  }
}
​
const fish = new Fish()
​
​
// 面向接口编程
// 参数:只要是实现了ISwim接口的类都能够传入函数
function swim(iSwim: ISwim) {
  iSwim.swimming()
}
​
swim(fish)

7. 接口interface和别名type的区别

区别: 当实现了同名的接口时,接口内会进行合并处理;而类型别名不允许存在同名的别名。

interface IFoo {
  name: string
}
interface IFoo {
  age: number
}
​
// 同名的接口其中的属性会做合并处理
// 因此IFoo类型,必须存在两个属性
// 可以用来给全局的接口添加属性
const foo: IFoo = {
  name: "tom",
  age: 18
}
​
​
type IGo = {
  name: string
}
// type 类型别名不能同名
// type IGo = {}

8.字面量赋值

给变量初始化时,可以传入一个和包含指定类型属性的变量

interface IPerson {
  name: string,
  age: number
}
​
const info = {
  name: "tom",
  age: 18,
  height: 1.88
}
​
// 直接赋值info的内部属性,则多余height,不允许
// 当使用info对象赋值时,typescript会进行相关处理
// 处理:擦出掉多余的依然符合IPerson的话,不会报错
const p: IPerson = info
​
console.log(p);

TypeScript枚举类型

枚举可以使用下标,也可以使用属性赋值;默认是0下标开始,也可以自定义。


// 枚举常量一般使用大写,代码习惯
enum Direction {
  LEFT,
  RIGHT,
  CENTER,
  TOP = 100
}
​
const d:Direction = Direction.TOP
console.log(d); // 100
​
​
function turnDirection(direction: Direction) {
  console.log(direction); // 值是默认下标,也可以自定义
  
  switch(direction) {
    case Direction.LEFT:
      console.log("左转");
      break;
    case Direction.RIGHT:
      console.log("右转");
      break;
    default:
      console.log("起飞");
  }
}
​
​
// 两种传参方式,传的参都是一致的
turnDirection(Direction.LEFT)
turnDirection(0) 

TypeScript中的this(ThisType)

1. 对象内调用

方法作为对象的属性时,其内部this会被自动推导

 const obj1 = {
  name: "name",
  getNam() {
    return this.name // 可以自动推导为{ name:string, getName():string}类型
  },
}
​
// 使用箭头函数会报错
// 箭头函数没有自己的this
const obj2 = {
  name: "name",
  getNam: () => {
    return this.name // error: The containing arrow function captures the global value of 'this'
  },
}

2. 构造函数

使用ES5语法的函数定义类,需要推导出构造函数的类型。

推荐使用ES6语法 class来写类

function People(name: string) {
  this.name = name // error: this是any类型,无法推导出正确的类型
}
​
new People("223") // error: 'new' expression, whose target lacks a construct signature, implicitly has an 'any' type.

3. 显式声明this的类型

通过call,apply调用,传入指定的this类型

const person = {
  name: "name",
  getNam() {
    return this.name
  },
}
​
function setName(this: typeof person, name: string) {
  this.name = name
}
​
setName.call(person, 'new name')

TypeScript泛型的使用

1.认识泛型

示例:实现一个两个相同参数相加

// 不同的参数让参数过于冗长
function sum(num1: number|string|any[], num2: number|string|any[]) {
  // return num1 + num2
}

使用泛型,使参数动态化;定义函数不定义参数类型,而是让传入的参数决定;Type 是自己定义的,这个可以自定义(默认使用T)

function sum<Type>(num1: Type): Type {
  return num1
}
// 1. 明确的传入类型
sum<number>(20)
sum<{name:string}>({name: "hello"})
sum<any[]>(["hah"]) 
​
​
// 2. 类型推导
sum(50)
sum("world")

2.泛型接收参数类型

泛型让参数根据传入是定义,而不是限定死;

注意: 同一种泛型变量意味着是同一种类型,不同的才是不同的类型

function foo<T, E, O>(arg1: T, arg2:E, arg3: O) {
​
}
​
foo<number, string, boolean>(10, "30", true)
foo(true, "3", [""])

3.泛型接口

接口也能够定义属性类型

interface IPerson<T, E> {
  name: T,
  age: E
}
​
const p: IPerson<string, number> = {
  name: "tom",
  age: 18
}

4.泛型类

设置同一个类型,则传输必须为相同类型

class Person<T> {
  name: T
  age: T
  isMan: T
​
  constructor(name: T, age: T, isMan: T) {
    this.name = name
    this.age = age
    this.isMan = isMan
  }
}
​
​
const p1 = new Person("tom", "18 years old", "true")
const p2 = new Person<string>("tom", "18 years old", "true")
const p3: Person<string> = new Person("tom", "18 years old", "true")

5.泛型的类型约束

泛型也能够继承,泛型就是正常类型的使用方式

interface ILength{
  length: number
}
​
// T 继承接口 ILength ;因此参数必需拥有length属性
function getLength<T extends ILength>(arg: T) {
  return arg.length
}
​
getLength("abc") //  字符串拥有长度
getLength(["abc"]) // 数组拥有长度
getLength({length: 100}) // 拥有length属性的对象也可以
// getLength(100)  // 数字就不行了

TypeScript的模块化开发

TypeScript代码都在同一个作用域内,因此变量的命名不能重复

那如何解决该方法呢?

有两种方式:模块化 和 使用命名空间

模块化

不同的文件,在最后面导出个对象,就意味着该文件是一个独立的模块对象,这样就拥有自己的作用域。

function sum (a: number, b: number) {
  return a + b
}
​
function sub (a: number, b: number) {
  return a - b
}
​
export {
  sum,
  sub
}

命名空间

关键字 namespace: 命名空间,让函数重名不再有问题

// 内部模块
export namespace time {
  export function format(time: string) {
    return "2022-1-23"
  }
}
time.format("x")
​
​
export namespace price {
  export function format(price: string) {
    return "$9.15"
  }
}
price.format("x")

自定义全局变量和第三方插件

有时候需要自定义一个全局变量或者引入第三方插件,TypeScript无法识别;这是因为TypeScript在内部有声明变量或者模块的文件(以.d.ts结尾的),在文件内部声明想要声明的模块或者变量等。


// 手写声明模块
declare module 'lodash' {
  export function join(arr: any[]) {}
}
​
// 声明变量、类、函数
declare let myName: string = "typescript"declare function foo(): voiddeclare class Person {
  name: string
  age: number
  constructor(name: string, age: number)
}
​
// 声明文件
declare module "*.jpg"

image-20220211134651680.png

TypeScript的模块化开发

TypeScript代码都在同一个作用域内,因此变量的命名不能重复

那如何解决该方法呢?

有两种方式:模块化 和 使用命名空间

模块化

不同的文件,在最后面导出个对象,就意味着该文件是一个独立的模块对象,这样就拥有自己的作用域。

function sum (a: number, b: number) {
  return a + b
}
​
function sub (a: number, b: number) {
  return a - b
}
​
export {
  sum,
  sub
}

命名空间

关键字 namespace: 命名空间,让函数重名不再有问题

// 内部模块
export namespace time {
  export function format(time: string) {
    return "2022-1-23"
  }
}
time.format("x")
​
​
export namespace price {
  export function format(price: string) {
    return "$9.15"
  }
}
price.format("x")

自定义全局变量和第三方插件

有时候需要自定义一个全局变量或者引入第三方插件,TypeScript无法识别;这是因为TypeScript在内部有声明变量或者模块的文件(以.d.ts结尾的),在文件内部声明想要声明的模块或者变量等。


// 手写声明模块
declare module 'lodash' {
  export function join(arr: any[]) {}
}
​
// 声明变量、类、函数
declare let myName: string = "typescript"declare function foo(): voiddeclare class Person {
  name: string
  age: number
  constructor(name: string, age: number)
}
​
// 声明文件
declare module "*.jpg"