跳至主要內容

Typescript基础知识

CharHua大约 21 分钟TypeScriptTypeScript静态类型干货

Typescript基础知识

一、类型声明

在TypeScript中定义变量需要指定 标识符 的类型。

一、格式

1、基本定义

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

var/let/const 变量名: 类型 =;

function fn(参数: 类型, 参数: 类型): 类型{
    ...
}

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

在TypeScript中并不建议再使用var关键字了。

2、自动类型判断

  • TS拥有自动的类型判断机制
  • 当对变量的声明和赋值是同时进行的,TS编译器会自动判断变量的类型
  • 所以如果你的变量的声明和赋值时同时进行的,可以省略掉类型声明

二、类型

1、类型汇总

类型例子描述
number1, -33, 2.5任意数字
string'hi', "hi", hi任意字符串
booleantrue、false布尔值true或false
SymbolSymbol('title')声明symbol类型的值
nullnull实际值为null
undefinedundefined实际值为undefined
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*不确定的变量,(类型安全的any)
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
object任意的JS对象
array[1,2,3]任意JS数组
tuple[4,"zwh"]元组,TS新增类型,固定长度数组
enumenum枚举,TS中新增类型

2、基本数据类型案例

1、number
let num: number = 123;

其他进制:

let num1: number = 100 //十进制
let num2: number = 0b100 //二进制
let num3: number = 0o100 //八进制
let num4: number = 0x100 //十六进制
2、string
let message: string = "Hello World"

当然,可以根据个人习惯: 默认情况下, 如果可以推导出对应的标识符的类型时, 一般情况下可以不加类型注解:

const name = "why";
const age = 18;
const height = 1.88;
3、boolean
let flag: boolean = true
4、array

在数组中存放不同的类型是不好的习惯,因此在TypeScript开发中, 一个数组中最好存放的数据类型是固定的。

方式一:

const names1: Array<string> = [] //不推荐,在react中jsx容易冲突

方式二(推荐):

const names2: string[] = [] 
5、object(todo)
const info = {
  name: "zwh",
  age: 18
}
let obj: object ={}
6、null和undefined
let n1: null = null
let n2: undefined = undefined
7、symbol
const title1 = Symbol("title")
const title2 = Symbol('title')

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

3、Typescript数据类型案例

1、any

在不想给某些JavaScript添加具体的数据类型时(原生的JavaScript代码是一样)

let message: any = "Hello World"

message = 123
message = true
message = {}
2、unknow
let notSure: unknown = 4;
notSure = 'hello';
function foo() {
  return "abc";
};
function bar() {
  return 123;
};

let flag = true;
let result: unknown; // 最好不要使用any
if (flag) {
  result = foo();
} else {
  result = bar();
}
  • unknown类型只能赋值给any和unknown类型
  • any类型可以赋值给任意类型
let result: unknown;
let message: string = result; //报错
3、void
let unusable: void = undefined;
4、never
function error(message: string): never {
  throw new Error(message);
}

其他应用场景,如封装核心库时:

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

此时调用时传入布尔值:

handleMessage(true) //编译报错

会报错,因为case不到后会执行default,此时message为boolean类型,不能赋值给never类型,报错,间接提示缺少boolean类型的case判断:

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
  }
}
5、tuple
let x: [string, number];
x = ["hello", 10]; 

或者:

const info: [string, number, number] = ["why", 18, 1.88]
const name = info[0] //此时name为string类型

此处对比any类型,用元组类型,可以让name为对应类型,而any的话name为any类型。

应用场景:

react中:

function useState(state: any) {
  let currentState = state
  const changeState = (newState: any) => {
    currentState = newState
  }

  const tuple: [any, (newState: any) => void] = [currentState, changeState]
  return tuple
}

const [counter, setCounter] = useState(10);
setCounter(1000)

const [title, setTitle] = useState("abc")

4、参数返回值类型

1、普通类型参数
function sum(num1: number, num2: number) {
  return num1 + num2
}
2、返回值类型
 (): number

在开发中,通常情况下可以不写返回值的类型(自动推导)。

3、匿名函数参数
const names = ["abc", "cba", "nba"];
names.forEach(function(item) {
  console.log(item.split(""))
});
//item根据上下文的环境推导出来可以不添加的类型注解

根据上下文中可推到类型的函数可以不添加类型注解。

4、参数对象类型
function printPoint(info: {name string, age: number}) {
  console.log(point.name);
}
5、可选参数
参数名?:类型
(name:string,age?:number) //age为可选参数

其实它原理类似:

number | undefined
function setName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + lastName;
    } else {
        return firstName;
    }
}
setName('z'); //第二个参数可选
setName('z','wh');
  • 可选参数必须在必选类型后面。

  • 没有传给可选参数时,其值默认为 undefined

6、剩余参数
function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}
let a = [];
push(a, 1, 2, 3);
7、参数默认值
function foo(y: number, x: number = 20) {
  console.log(x, y)
}

foo(30)
  • 建议参数顺序:必选参数,默认值参数,可选参数

5、高级类型

1、联合类型

1、基本使用

格式如:

number|string|..
function printID(id: number|string|boolean) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

printID(123);
printID("zwh");
  • 用联合类型时需注意判断,例如string类型使用了Number对象方法会报错,所以最好函数体内有相应判断。

2、可选参数与联合类型

一个参数一个可选类型的时候, 它其实类似于是这个参数是 该类型|undefined 的联合类型

function foo(message?: string) {
  console.log(message)
}

foo();
2、类型别名

类型别名可以给类型起个新的名字

  • 他需要用到一个关键字:type
  • 仅仅给类型起个名字,不是创建新类型
type IDType = string | number | boolean;
type PointType = {
  x: number
  y: number
  z?: number
};
3、类型断言

1、语法:

 尖括号 语法
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as 语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;

2、运用:

  • 但我们知道某个值得类型,而TS时无法具体判断出来时,我们可以给它指定类型。他类似于仅作用在类型层面进行强制类型转换。
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2); // 提示 ts(2322)

给它作类型断言:

const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2) as number; //正常不报错
  • 有时TS无法获取某个值得具体类型,例如用 document.getElementById 获取某个元素,TypeScript只知道该函数会返回 HTMLElement ,但并不知道它具体的类型,此时我们可以让他知道具体类型:
const el = document.getElementById("why") as HTMLImageElement
el.src = "xxxxx.xxx";

当然也能解决这种情况:

class Person {
};
class Student extends Person {
  studying() {
  }
};
function sayHello(p: Person) {
  (p as Student).studying();//若不做类型断言,TS以为去Person取studying()
};

const stu = new Student();
sayHello(stu);

3、非空类型断言

使用后缀表达式:

!
  • x! 将从 x 值域中排除 null 和 undefined
let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531)
  • 当然与可选参数配合使用时:
function printMessageLength(message?: string) {
  // if (message) {
  //   console.log(message.length)
  // }
  console.log(message!.length)
}

4、确定赋值断言

  • 允许在实例属性和变量声明后面放置一个 ! 号,从而告诉 TypeScript 该属性会被明确地赋值
let x: number; //报错
let x!: number; //正常
init();
console.log(2 * x); 

function init() {
  x = 10;
}
4、交叉类型

交叉类型可以将多个类型合并为一个类型,用( &)分隔每个类型

它可以与联合类型组合使用,例如:

type id = number | string;
type NewId = number & CrossType; // number;
  • 但交叉类型合并普通类型时,会转化为never类型:
type CrossType = number & string;// never
const value:CrossType = 1 //报错
  • 所以,它主要用途是合并对象时,产生新的数据结构,以便及时规范对象合并后的数据结构。
5、字面量类型
const message: "Hello World" = "Hello World"
  • 此时"Hello World"也是可以作为类型, 叫做字面量类型

用联合定义字符串字面量

type String1 = 'name1'|'name2';

用联合定数字字面量

type Num1 = 1 | 0;

注册时只能指定一个值。

  • 字符串字面量类型还可以用于区分函数重载:
function createElement(tagName: "img"): HTMLImageElement; 
function createElement(tagName: "input"): HTMLInputElement; 
//...
function createElement(tagName: string): Element { 
//...
}
  • 不能将字面量类型赋值给string类型时,可以用类型断言。
6、枚举类型
  • 使用关键字 enum 定义枚举类型
enum Direction {
  LEFT,
  RIGHT,
  TOP,
  BOTTOM
}
console.log(Direction.LEFT) //->0
console.log(Direction.RIGHT) //->1
console.log(Direction.TOP) //->2
console.log(Direction.BOTTOM) //->3
  • 通过上面,实际上枚举最后值为数字。
  • 我们也可以给枚举类型的key赋值:
enum Direction {
  LEFT = "LEFT",
  RIGHT = "RIGHT",
  TOP = "TOP",
  BOTTOM = "BOTTOM"
}

enum Direction {
  LEFT=10,
  RIGHT,
  TOP,
  BOTTOM
} //10,11,12,13

enum Direction {
  LEFT,
  RIGHT,
  TOP,
  BOTTOM=10
} //0,1,2,10

6、类型其他补充

1、可选链

可选链是ES11(ES2020)中增加的特性。

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

const info: Person = {
  name: "zwh",
  friend: {
    name: "zwh2",
    girlFriend: {
      name: "lyq"
    }
  }
}


// 另外一个文件中
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)
2、!!运算符
const message = "Hello World"
const flag = Boolean(message)
console.log(flag) //->true

也可以这样:

const flag = !!message
console.log(flag) //->true
3、??运算符
let message: string|null = "Hello World"

const content = message ?? "hello World";

它实际类似于三元运算符:

const content = message ? message : "hello World";
4、类型缩小
  • 通过类似于 typeof padding === "number" 的判断语句确定类型,来改变TypeScript的执行路径

  • 我们编写的 typeof padding === "number 可以称之为类型保护(type guards)

  • 几种方法:typeof、平等缩小(===、-=等)、instanceof、in

1、用typeof判断,缩小类型

type IDType = number | string
function printID(id: IDType) {
  if (typeof id === 'string') {
    console.log(id.toUpperCase())
  } else {
    console.log(id)
  }
}

2、用if===/==/!=,switch判断字面量类型

type Name = 'n1' | 'n2';
if(name === 'n1'){...}...

3、用instanceof判断类型

function printTime(time: string | Date) {
  if (time instanceof Date) {
    console.log(time.toUTCString())
  } else {
    console.log(time)
  }
}

4、用in判断

type Fish = {
  swimming: () => void
}

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

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

7、函数类型

1、函数类型定义

函数也可以有自己的类型。可以编写函数类型的表达式(Function Type Expressions),来表示函数类型。

定义函数类型:

(a1:number,a2:string) => 返回值类型

1、函数类型作为参数时

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

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)
}
2、函数参数
  • 函数的参数可以是可选参数,也可以是剩余参数
  • 函数的参数可以有默认值

以上可以在类型-参数返回值类型查看具体应用

3、函数的this

1、可推导的this:

  • 大部分情况,函数的this可以被TS推到出来,例如以下:
const info = {
  name: "why",
  eating() {
    console.log(this.name + " eating")
  }
}

info.eating()

2、不明确的this:

1、隐式绑定:

  • 有些时候TS不知道 this 具体指向,或者this存在不安全问题:
function eat() {
  console.log(this.name+'在吃饭')
}
const info = {
  name:'zwh',
  eat
}
info.eat();

上面这段代码存在不安全问题,因为eat()函数中的this可能指向别的对象(被单独调用时)。

  • 所以我们可以给函数的this指定类型:
type ThisType = { name: string };
function eat(this: ThisType) {
  console.log(this.name+'在吃饭')
}
const info = {
  name:'zwh',
  eat
}
info.eat(); //->zwh在吃饭
  • 指定this类型的同时,也可以给函数传参
type ThisType = { name: string };
function eat(this: ThisType,msg: string) {
  console.log(this.name+msg)
}
const info = {
  name:'zwh',
  eat
}
info.eat('在吃饭'); //->zwh在吃饭

2、显示绑定:

上面的this案例中,this被对象调用时,其实是隐式绑定的。

  • 我们也可以给this显示绑定值:
type ThisType = { name: string };
function eat(this: ThisType,msg: string) {
  console.log(this.name+msg)
}
const info = {
  name:'zwh',
  eat
}
eat.call({ name:'charhua' },'在吃饭'); //->charhua在吃饭
eat.apply({ name:'charhua' },['在吃饭']); //->charhua在吃饭
3、函数的重载

当我们想给函数传不同类型的参数时,可以用联合类型。但它存在一些缺点:

  1. 需要进行逻辑判断
  2. 不能确定返回值类型

因此我们可以用函数的重载。

  • 函数的重载:存在函数名相同, 但是参数不同的几个函数

1、定义函数重载:

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"); //正常
  • 注意:函数重载的实现函数(真正函数),并不能直接被调用。例如:
add( {name:'zwh'} , {name:'charhua'} );//报错

所以,我们不能传除了定义的重载函数类型之外的参数。

2、联合类型与函数重载的选择

  • 多数情况下尽量选择联合类型

二、类

TS支持用 class 关键字定义类,并且可以对类的属性和方法进行静态类型检测。

一、类的定义

1、参数默认值

  • 定义类时,类的参数需要初始化默认值。
class Person {
  name: string='zwh';
  age: number=20;
  
  eating() {
    console.log(this.name + " eating")
  }
}
//实例化
const p = new Person();

2、使用构造函数

  • 当然可以使用构造函数,让初始化时传入参数值,这样类里面定义参数就不用默认值了。
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("zwh", 20)

二、类的特性

1、类的继承

1、关键字extends
  • 继承父类时需要用到关键字:extends
class Person {
  name: string = "";
  age: number = 0;

  eating() {
    console.log("eating");
  }
}

class Student extends Person {
  sno: number = 0;

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

const stu = new Student();
stu.name='zwh';
stu.eating();//->eating
2、super()
  • 包含构造函数的子类必须调用 super(),因为super()会调用父类的构造方法。
class Person {
  name: string
  age: number
  constructor(name: string, age: number) {
    this.name = name
    this.age = age
  }
  eating() {
    console.log("eating")
  }
}

class Student extends Person {
  sno: number

  constructor(name: string, age: number, sno: number) {
    // super调用父类的constructor
    super(name, age)
    this.sno = sno
  }
  studying() {
    console.log("studying")
  }
}

const stu = new Student("zwh", 20, 19050000);
  • 子类也可以重写父类的方法:
class Student extends Person {
  sno: number
  constructor(name: string, age: number, sno: number) {
    // super调用父类的constructor
    super(name, age)
    this.sno = sno
  }
  eating() {
    console.log("student eating")
    super.eating()
  } //重写父类eating()
  studying() {
    console.log("studying")
  }
}

2、多态

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()])
  • 使用多态:为了写出更加具备通用性的代码

三、成员修饰符

1、public

  • 修饰公有成员

  • public 修饰的属性可以在任何地方被读写,默认编写的属性方法为public。

2、private

  • 修饰私有成员

  • private 修饰的成员只能在本类中可见。

  • 私有属性变量名命名可以在前_,例如:

    _name:string='zwh';
    

3、protected

  • 修饰受保护成员

  • protected修饰的成员可以在自身类和子类中可见。

class Person {
  protected name: string = "123"
}

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

四、类其他特性

1、只读属性

  1. 使用readonly修饰成员,则该成员为只读属性,它可以在构造器中赋值,但赋值后不能再修改了。
  2. 成员属性本身不可修改,但该成员为对象类型时,其对象里的属性可以被修改。
class Person {
  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"));

p.friend={} //直接修改readonly成员,报错
if (p.friend) {
  p.friend.age = 30
} //修改readonly成员对象的属性,正常

2、Getter和Setter

  • 使用getset修饰成员,可以监听该成员被访问时的过程。
  • getset修饰成员时可以访问私有属性,这样外部可以间接访问私有属性。
class Person {
  private _name: string
  constructor(name: string) {
    this._name = name
  }
  //设置getter访问器
  get name() {
    return this._name;
  }
  //设置setter访问器
  set name(newName) {
    this._name = newName;
  }
}

const p = new Person("zwh");
p.name = "charhua";//可以间接访问私有属性

3、静态成员

  • 使用关键字static定义静态成员。
  • 静态成员通过类名调用。
class Person {
  static id:string='zwh';
  static eating(){
    console.log('eating');
  }
}

console.log(Person.eating())
console.log(Person.id)

4、类的类型

  • 类也可以作为类型。
class Person {
  name: string = "zwh";
  eating() {};
}

const personZWH: Person = {
  name: "zwh",
  eating() {};//Person类型有的属性方法必须全部有
}

五、抽象类

  • 使用关键字abstract声明抽象类。
  • 抽象类不能被实例化。
  • 抽象方法存在于抽象类中。
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(30, 35);
const circle = new Circle(20);

console.log(makeArea(rectangle));
console.log(makeArea(circle));

三、接口

  • 接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。

一、接口声明

1、接口定义

  • 使用关键字:interface 定义接口。
  • 社区规范上常在接口名前加大写 I。
interface IInfo {
  name: string;
  age: number;
  hobby: string[];
  friends: {
    name: string;
    age: number;
    hobby: string[];
  }
}

2、接口使用

  • 注意,没有定义接口其他例如可选属性的,必选属性都要加上。
const infos: IInfo ={
  name:'zwh',
  age:20,
  hobby:['打篮球','音乐'],
  friends:{
    name:'charhua',
    age:20,
    hobby:['打篮球','音乐']
  }
}

二、接口属性

1、可选属性

  • 接口里的属性可定义为可选属性。
interface IInfo {
    name: string;
    age: number;
    hobby?: string[];
}

const infos: IInfo = {
    name: 'zwh',
    age: 20
}

2、只读属性

  • 接口里的属性可定义为只读属性。
interface IInfo {
   readonly name: string;
    age: number;
    hobby?: string[];
}

const infos: IInfo = {
    name: 'zwh',
    age: 20
}

3、任意属性

  • 但我们想给接口定义更多不确定类型的属性,可以这样:
interface IUser {
  user: string;
  age: number;
  eat(): void;
  [propName: string]: any;
}
const user: IUser = {
  user: 'zhangsan',
  age: 24,
  eat() {
    console.log(this.name, '恰饭');
  },
  message: '' // 这里定义的任意类型属性或者方法不会报错,TS会跳出类型检查。
};

三、接口类型

1、对象类型

  • 我们可以通过类型别名 type 定义对象类型。也可以通过接口定义对象类型。
interface IInfoType {
   readonly name: string; //定义只读属性
    age: number;
    hobby?: string[]; //定义可选属性
}

2、函数类型

  • 可以在接口中定义函数类型。
interface ISumFn {
  (n1: number, n2: number): number;
}
//function声明函数
function sum(num1: number, num2: number, calcFn: ISumFn) {
  return calcFn(num1, num2);
}
//变量声明函数
const add: ISumFn = (num1, num2) => {
  return num1 + num2;
}

3、索引类型

  • 可以通过接口来定义索引类型。
interface IndexLanguage {
  [index: number]: string
}

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

四、接口的继承

  • 一个接口可以继承其他接口
interface ISwim {
  swimming: () => void;
}
interface IFly {
  flying: () => void;
}

interface IAction extends ISwim, IFly {

}

const action: IAction = {
  swimming() {},
  flying() {}
}

五、接口的实现

1、接口类类型

  • 接口可以描述类类型。
interface IPerson {
  name: string;
  age: number;
  eat(): void;
}
interface ISwim {
  swimming: () => void;
}
interface IEat {
  eating: () => void;
}

2、实现接口

我们继承只能实现一个接口,但是使用类接口可以实现多个接口。

  • 使用关键字 implements 实现接口。
class Animal implements ISwim, IEat {
  swimming() {
    console.log("Fish Swmming");
  }

  eating() {
    console.log("Fish Eating");
  }
}
  • 继续类继承:
class Fish extends Animal {
  swimming() {
    console.log("Fish Swmming");
  }

  eating() {
    console.log("Fish Eating");
  }
}
  • 接口实现:
class Person implements ISwim {
  swimming() {
    console.log("Person Swimming");
  }
}
  • 公共调用入口:
function swimAction(swimable: ISwim) {
  swimable.swimming();
}
  • 实现了接口的类的实例对象都可传入:
swimAction(new Fish());
swimAction(new Person());
  • 当然也可以传入接口本身类型:
swimAction({swimming: function() {}});

六、接口其他补充

1、接口字面量赋值报错

  1. 定义一个接口:
interface IPerson {
  name: string
  age: number
  height: number
}
  • 再定义某个对象,并给接口赋值:
const info: Iperson = {
  name: "zwh",
  age: 20,
  height: 2,
  address: "广东省"
} //报错,不能这样直接进行字面量赋值

const info = {
  name: "zwh",
  age: 20,
  height: 2,
  address: "广东省"
}
const p: IPerson = info; //不报错
console.log(p);//也打印address,因为TS内部实现freshness擦除操作
  • 当然这样也不报错:
function printInfo(person: IPerson) {
  console.log(person);
}
const info = {
  name: "zwh",
  age: 20,
  height: 2,
  address: "广东省"
}

printInfo(info);
  • 但是这样会报错:
printInfo({
  name: "zwh",
  age: 20,
  height: 2,
  address: "广东省"
}) //报错

2、接口与type的选择

1、对于定义非对象类型,建议哟type类型别名去定义。

2、如果定义对象类型:

  1. interface可以定义重复接口名的接口。
  2. type定义的是类型的别名,不能重复名字。

四、泛型

  • 当我们需要一致性和可复用性的API,此时参数类型不能固定,而是可以由调用者决定参数类型,此时可以用泛型。

一、泛型基本使用

  • 泛型由一个<T>占位,通常放在标识符前面,其T是Type的缩写,当然参数类别可以为其他词。

  • 常见缩写参考:

  1. T : 代表 Type 在定义泛型时通常用作第一个类型变量名称。
  2. K : 表示对象中的 key 类型。
  3. V : 表示对象中的 value 类型。
  4. E : 表示 Element 元素类型。

1、默认类型

1、基本使用
function sum<Type>(num: Type): Type {
  return num;
}
//传入明确的类型
sum<number>(20);
sum<{name: string}>({name: "zwh"});
sum<any[]>(["abc"]);

//自动推导类型
sum(50)
sum("abc")
2、多个参数
function bar<T, E>(n1: T,s1: E){
    console.log(n1,s1);
}
bar<number,string>(1,'zwh');
3、使用可选类型
function foo<T, E, O>(arg1: T, arg2: E, arg3?: O) {};

foo<number, string, boolean>(10, "abc", true);
4、使用剩余参数
function foo<T, E, O>(arg1: T, arg2: E, arg3?: O, ...args: T[]) {};

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

2、类型别名

  • 使用类型别名声明对象泛型:
type userType<T> = { name: T; age: number };

let user: userType<string> = { 
    name: 'zwh', 
    age: 24 
};

二、泛型接口

  • 定义接口时也可以使用泛型。
interface IPerson<T1 = string, T2 = number> {
  name: T1
  age: T2
}

const p: IPerson = {
  name: "zwh",
  age: 18
}
  • 当然在接口内部方法上也可以定义泛型:
interface IPerson {
  name: string;
  eat: <T>(food: T) => T;
}

三、泛型类

  • 我们可以定义一个泛型类,并实例化它:
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("34", "45", "47")
const p2 = new Point<string>("45", "64", "35")
const p3: Point<string> = new Point("53", "35", "24")

四、泛型类型约束

interface ILength {
  length: number;
}

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

getLength("abc");
getLength(["abc", "cba"]);
getLength({length: 100});
  • 这里 extends 表示泛型必须包含某种类型,从而在调用时约束了传入的类型。

五、命名空间

  • 当确实存在相同名字的变量,可以用命名空间。
  • 当然访问命名空间时成员需要加上:命名空间标识名.XXX变量名
  • 想要外界能访问命名空间里的成员,需要 export 导出成员
export namespace time {
  export function format(time: string) {
    return "2022.1.1";
  }
  let name: string = "abc"; //未导出,外界访问不到
}

export namespace price {
  export function format(price: number) {
    return "100.00";
  }
}
  • 通常命名空间不是必须的,因为可以加前缀让变量名不重复。

六、模块化

  • TS中任何包含顶级的 importexport 的文件都被当成一个模块。相反如果一个模块不带有 importexport 声明,那么它的内容是被视为全局可见的。
  • 意味着你在不同文件下出现相同变量名会报错,你可以这样解决:
//代码最底下添加
import '';
export {};

一、导入导出声明

// a.ts
export const str: string = '';
// b.ts
import { str } from './a.ts';

当然也可以使用类似于ESM的语法。如:

export default '哈哈哈';

七、类型查找

  • TS中对类型有很严格的限制,所有少不了它对类型的管理和类型的查找规则。
  • TS用.d.ts文件来存放类型声明,它仅仅用来做类型检测。
  • 通常TS给我们很多内置的类型,例如HTMLImageElement这个类型,使得我们可以直接使用它,而不用额外去声明它。
  1. TS类型查找从这几个方面入手:
  • 内置的类型声明
  • 外部定义的类型声明(如一些库自带.d.ts文件)
  • 自定义类型声明(自己在xxx.d.ts文件中声明)

一、声明类型

  • 在XXX.d.ts文件中,使用 declare关键字来声明成员。

1、声明模块

declare module 'lodash' {
  export function join(arr: any[]): void
}

2、声明变量

declare let myName: string

3、声明函数和类

declare function Foo(): void
declare class Person {
  name: string
  //...
}

4、声明文件

declare module '*.jpg'
declare module '*.jpeg'
declare module '*.png'

5、声明命名空间

declare namespace $ {
  export function ajax(settings: any): any
}
  • 之后直接用 $. 取。

八、内置工具类型

待更新...

九、装饰器

待更新...

十、内容说明

以上内容的代码部分参考自网络,部分为个人总结。

TypeScript官网:TypeScriptopen in new window