Featured image of post TypeScript

TypeScript

🌟TypeScript 介绍

TypeScript 是什么?

TypeScript 是 JavaScript 的超集。

TypeScript = Type + JavaScript(在JS基础之上,为JS添加了类型支持)。

TypeScript为什么要为JS添加类型支持?

对于JS 来说:需要等到代码真正去执行的时候才能发现错误(晚)。

对于TS来说:在代码编译的时候(代码执行前)就可以发现错误(早)。

并且,配合 VSCode 等开发工具,TS 可以提前到在编写代码的同时就发现代码中的错误,减少找Bug、改Bug 时间。

TypeScript相比JS的优势

- 更早(写代码的同时)发现错误,减少找Bug、改Bug时间,提升开发效率。

- 程序中任何位置的代码都有代码提示,随时随地的安全感,增强了开发体验。

- 强大的类型系统提升了代码的可维护性,使得重构代码更加容易。

- 支持最新的ECMAScript语法,优先体验最新的语法,让你走在前端技术的最前沿。

- TS类型推断机制,不需要在代码中的每个地方都显示标注类型,让你在享受优势的同时,尽量降低了成本。

安装TypeScript

npm i -g typescript

查看TypeScript版本

tsc -v

运行ts文件

tsc 文件名.ts
node 文件名.js

简化运行ts

安装

npm i -g ts-node

使用

ts-node 文件名.ts

类型声明

//定义变量
let 变量名:类型

//定义方法
function fn(变量名:类型){
  console.log(变量名)
}
//调用
fn();

🌟TypeScript 常用基础类型

JavaScript 原有的类型

js类型关键字数据类型示例
原始number数字类型let age: number = 7;
原始string字符串类型let color: string = "blue";
原始boolean布尔类型let isDone: boolean = true;
原始nullnulllet n: null = null;
原始undefinedundefinedlet u: undefined = undefined;
原始symbol独特的不可变值,通常用于对象属性标识符let sym: symbol = Symbol();
对象array数组类型// 在元素类型后面加上[] let arr: number[] = [1, 2]; // 或者使用数组泛型 let arr: Array<number> = [1, 2];
对象object对象类型let obj: object = {name: "John", age: 30};
对象function函数类型const add = (x: number, y: number):number => {return x+y};

TypeScript 新增的类型

关键字类型示例
union~联合类型,可以是多个类型中的一个
union
`let id: number
type~类型别名,用于自定义类型(可以用映射类型)type Point = { x: number, y: number }; let obj1: Point = {x: 3, y: 5}; `type arr = (number
tuple元组类型,表示已知元素数量和类型的数组let x: [string, number] = ["hello", 10];
enum枚举类型,用于定义具有标识符的数值enum Color {Red, Green, Blue}; let c: Color = Color.Green;
any任意类型,可以赋值为任意类型的变量let notSure: any = 4; notSure = "maybe a string";
void没有返回值的函数的返回类型function warnUser(): void { alert("This is my warning"); }
literal字面量类型,特定值的类型`let direction: “left”
interface接口类型,定义对象的结构和行为(不可以用映射类型)interface Person { name: string; age: number; }

不常用的类型

类型说明示例
unknown未知类型,类型安全的 any,有类型检查let value: unknown = 4; if (typeof value === "number") {}
nevernever 是其它类型(包括 null 和 undefined)的子类型,代表永远不会发生的类型,通常用于不会有返回值的函数function error(message: string): never { throw new Error(message); }
intersection交叉类型&,合并多个类型为一个类型 ( 类似于接口继承,常用于对象类型 ) 即都要满足type Person = { name: string } & { age: number };
readonly只读属性,不能被修改interface Person { readonly name: string; }
mapped types基于泛型的工具类型 ( 基于已有类型创建新的类型 )type Partial<T> = { [P in keyof T]?: T[P]; }
conditional types条件类型,根据条件返回不同类型type IsString<T> = T extends string ? true : false;

联合类型

语法:let 变量名:声明类型1|声明类型2.....;

// 值为 number 或 string 类型
let id: number | string;

// 值为 给定的几个值
let id: 15913;

// 数组为 number 数组 或 string 数组
let arr: number | string[];

// 数组的值为 number 或 string 类型
let arr: (number | string)[];

类型别名

语法:type 变量名1 = 声明的复杂长类型; 
		 let 变量名2: 变量名1 = ;

type arr = (number | string)[];
let arr1:arr=[1,'s'];
let arr2:arr=[1,2,3,'s'];

函数类型

函数的定义

//定义方法
function r1(name){
    console.log(name);
}
r1('sdja');
function r2(age:number,name:string){
    console.log(age,name);
}
r2(19,'adj1');

//方法返回值
function r3(){
}
var run3=r3();//undefined
console.log("78:"+run3);
function k1():number{
    return  19;
}
function k2():string{
    return "你好"
}

//返回void
function greeter():void{
    console.log("Hello World");
    return undefined;//可以
    return ;//可以
  	//其他的返回不可以
  	//能打印出来Hello World
}

//没有任何返回值 立刻结束
function k4():never{
    throw new Error("错误!");
}

//单独指定参数、返回值类型
function add1 (num1:number, num2:number):number{
	return numi+num2
}
const add2 = (num1:number, num2:number):number => {
	return numi+num2
}
//同时指定参数、返回值类型
const add3:(num1:number, num2:number) => number = (num1, num2) => {
	return num1+num2
}

函数参数

//没有返回值
function test():void{
    console.log('没有返回值')
}

//有返回值
function test2():number{
    let k=1+1;
    console.log('有返回值')
    return k;
}

//带参数
function test3(a:number,b:number):number{
    let o=a+b;
    return o;
}
let sum=test3(5,5);
console.log(sum);

//可选参数 (变量名?: 数据类型)
//可选参数必须跟在必需参数后面。 如果上例我们想让 firstName 是可选的,lastName 必选,那么就要调整它们的位置,把 firstName 放在后面。
//如果都是可选参数就没关系。
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}
function myslice(start?: number, end?: number) {
  console.log('开始索引'+start+'结束索引'+end);
}
myslice();		//开始索引undefined结束索引undefined
myslice(1);		//开始索引1结束索引undefined
myslice(1,3);	//开始索引1结束索引3

//给参数传默认值 (变量名:数据类型[] = 默认值)
function sum(x:number, y:number = 20) { 
  return 2x + y; 
}
let sum1 = sum(20,5);
let sum2 = sum(12);

//剩余参数 (...变量名:数据类型[])
function addNumbers(...nums:number[]) {  
  var i;   
  var sum:number = 0; 

  for(i = 0;i<nums.length;i++) { 
    sum = sum + nums[i]; 
  } 
  console.log("和为:",sum) 
} 
addNumbers(1,2,3) 
addNumbers(10,10,10,10,10)

//匿名函数
//匿名函数是一个没有函数名的函数
//匿名函数在程序运行时动态声明除了没有函数名外其他的与标准函数一样
//我们可以将匿名函数赋值给一个变量这种表达式就成为函数表达式
let Anonymous = function (){
    console.log('匿名函数!');
}
Anonymous();

//匿名带参数
let Anonymous2 = function (a:number, b:number){
    console.log(2a-2b);
}
Anonymous(8,7);

//箭头函数
//函数只有一行语句
var foo = (x:number)=>10 + x 
//函数是一个语句块
var foo2 = (x:number)=> {    
    x = 10 + x 
    console.log(x)  
}
console.log(foo(100))
console.log(foo2(100))

//函数立刻运行
(function autorun():void{
    console.log('运行函数!');
}());

对象类型

声明对象

// 声明对象-先声明后赋值
let y1:object;
y1={};
y1=function (){
}

// 声明对象-声明后立即赋值
// 多个方法或属性,在一行写时用 分号 隔开
let obj:{ name: string; age:number; say():void; greet(name:string):void } = { 
  name: '张三', 
  age: 18, 
  say() {},
  greet(name:string) {
    console.log('你好', name);
  }
};

// 多行写可省略分号
// 方法的类型也可以使用 箭头函数 `sayHi:()=>void`
let y2:{
  name:string
//sayHi():void
  sayHi:()=>void
  sayHello(name:string):void
}={
  name:'李四',
  sayHi() {},
  sayHello(name) {}
};

对象参数

//可选属性
let y3:{
    name:string,
    //可有可无
    age?:number,
    sex?:string,
}
y3={
    name:'李四',
    age:19,
}

let y4:{
    name:string,
    //任意字符串 任意类型
    [propName:string]:unknown,
}
y4={
    name:'李四',
    age:19,
}

接口类型interface a

interface User {
  name: string,
  number: number,
  call(): void
}

let user1: User = {
  name: '张三',
  number: 15522223333,
  call() {
    console.log('call');
  }
}

接口继承 extends

interface User {
  name: string,
  number: number,
  call(): void
}

interface bigUser extends User {
  age: number,
  watch(): void,
  sayHi: ()=>string
}

let user2: bigUser = {
  name: '李四',
  number: 18899996666,
  age: 25,
  call() {
    console.log('call');
  },
  watch() {
    console.log('watch');
  },
  sayHi: ():string =>{return "Hi there"} 
}

user2.call();
user2.watch();
user2.sayHi();

元组类型[]

// 允许在数组中存储不同类型的元素, 组中的每个元素都有明确的类型和位置。
// 元组可以在很多场景下用于表示 固定长度、且 各元素类型已知 的数据结构
let position: [number, number] = [123,59]

let mytuple: [number, string];
mytuple = [42,"Runoob"];

类型断言as

// 类型断言是一种告诉编译器变量的类型的方式

// 技巧:选择html元素,在浏览器控制台,通过 console.log(dir($0)) 打印DOM元素,在属性列表的最后面,即可看到该元素的类型。

const alink = document.querySelector('a') as HTMLAnchorElement; 
// const alink = <HTMLAnchorElement>document.querySelector('a'); // react中不能用这种方式

let someValue: any = "hello world";
// 类型断言告诉编译器 `someValue` 是一个字符串
let strLength: number = (someValue as string).length;
console.log(strLength);  // 输出: 11

字面量类型

let str1 = 'hello' //string类型
const str2: 'hello' = 'hello' //’hello'类型 只能是‘hello’
let age: 18 = 18 //18类型 只能是18

// 字面量类型 配合 联合类型一起使用。用来表示一组明确的可选值列表。

function changeDirection(direction: 'up' | 'down' | 'left' | 'down' ) {}
changeDirection('up') // 参数只能是四个其中一个

枚举enum

ts中的枚举

语法:enum 枚举名称{成员1,成员2....};

把枚举成员的值为数字的枚举,称为:数字枚举。
枚举成员是有值的,默认为:从0开始自增的数值。
  enum Direction {
    Up,   // 0
    Down, // 1
    Left, // 2
    Right // 3
  }
  
把枚举成员的值为字符串的枚举,称为:字符串枚举。
字符串枚举没有自增长行为,因此,字符串枚举的每个成员必须有初始值
  enum gender {
    male = '1',   // gender.male = "1"
    female = '0', // gender.female = "0"
  }

可以给枚举中的成员初始化值。也可以全部赋值
enum Direction {
  Up = 22,
  Down, // 23
  Left, // 24
  Right // 25
}

enum Direction {
  Up = 2,
  Down = 4, 
  Left = 6,
  Right = 8
}

举例

enum Direction {
  Up,
  Down,
  Left,
  Right
}

function changeDirection(direction: Direction) {
  console.log(direction);
}
// 通过枚举类型的点传参
changeDirection(Direction.Up)

如果想在ts中 表示一组明确的可选值 ,有两种方案,一种是字面量+联合类型组合(🌟更直观),一种是枚举类型

any类型

/* any类型 */
let obj: any = {x: 5}

这样均不报错
// 访问不存在的属性 或者 赋值
obj.y = 10
// 当作函数调用
obj()
// 赋值给其他类型的变量
let arr: string[] = obj

隐式具有any类型
// 声明变量不提供类型也不提供默认值
let a
// 函数参数不加类型
function(num1, num2)

类型判断typeof

可以在 类型上下文 中引用 变量 或 属性 的类型(类型查询)

  • 使用typeof操作符来获取变量str1的类型,结果与第一种(对象字面量形式的类型)相同。
  • typeof出现在类型注解的位置(参数名称的冒号后面)所处的环境就在类型上下文(区别于JS代码)。
  • 注意:typeof只能用来查询变量或属性的类型,无法查询其他形式的类型(比如,函数调用的类型)。
// 自动类型判断
let num = {num1: 2, num2: 5}
let str1 = "Hello TS"
console.log(typeof num) // object
console.log(typeof num.num1)  // number
console.log(typeof str1)  // string 

// 利用 typeof 简写类型
let num = {num1: 2, num2: 5}
function add(num: {num1: number, num2: number}) {
  return num.num1 + num.num2
}
function add2(ccc: typeof num ) {
  return ccc.num1 + ccc.num2
}
console.log(add({num1: 1, num2: 2}));  // 3
console.log(add2({num1: 1, num2: 2})); // 3

🌟TypeScript高级类型

  1. class类
  2. 类型兼容性
  3. 交叉类型
  4. 泛型 和 keyof
  5. 索引签名类型 和 索引查询类型
  6. 映射类型

class

定义

class Person {
  age: number
  gender = '男'
}

let p = new Person()
p.age = 10;

声明成员age,类型为number(没有初始值)。
声明成员gender,并设置初始值,此时,可省略类型注解(TS类型推论为string类型

构造函数 ( 不需要 返回值类型)

class Person2 {
  age: number
  gender: string

  constructor(age: number, gender: string) {
    this.age = age;
    this.gender = gender;
  }
}
let p2 = new Person2(10, '男');
console.log(p2.age, p2.gender);

成员初始化(比如,agenumber)后,才可以通过this.age来访问实例成员
需要为构造函数指定类型注解,否则会被隐式推断为any;构造函数不需要返回值类型。

实例方法 ( 需要 返回值类型)

class Point {
  x = 2
  y = 4
  scale(n: number): void {
    this.x *= n
    this.y *= n
  }
}
const p = new Point()
p.scale(10)
console.log(p.x, p.y) // 20 40

类的继承-extends(继承父类)

class Animal {
  move(){
    console.log('走路');
  }
}

class Dog extends Animal {
  bark(){
    console.log('汪汪');
  }
}

const dog = new Dog()
dog.bark()
dog.move(

类的继承-implements(实现接口)

interface Singale {
  name: string
  sing(): void
}

class Person implements Singale {
  name: string // = '张三'
  sing() {
    console.log('唱歌');
  }
}

类成员可见性

可见性修饰符包括:public(公有的) protected(受保护的) private(私有的)
常见修饰符:readonly(只读修饰符)

public

class Animal {
  public move() {console.log('走路');}
}

const a = new Animal()
a.move()

// 子类继承父类
class Dog extends Animal {
  bark() {
    console.log('汪汪');
  }
}

const dog = new Dog()
dog.move()

默认是公有 可省略

protected

class Animal {
  protected move() {console.log('Moving along');}
}

class Dog extends Animal {
  bark() {
    console.log('汪汪');
    this.move();
  }
}

const dog = new Dog();
dog.bark();
// dog.move(); error

只能在类内部和子类中使用(即可以在类中其他的方法内使用),不能被实例对象调用

private

class Animal {
  private __run__ () {
    console.log('Animal 内部辅助函数')
  }

  // 受保护的
  protected move() {
    this.__run__()
    console.log('走路')
  }

  // 公开的
  run() {
    this.move()
    this.__run__()
    console.log('跑步');
  }
}

const a = new Animal()
// a.__run__() // error private只能在类内部使用

// 子类
class Dog extends Animal {
  bark() {
    console.log('汪汪');
    this.move(); // protected 在 类内部 和 子类 中可以使用
    // this.__run__() // error private只能在 类内部 使用
  }
}

const dog = new Dog();
dog.bark();
dog.run();
// dog.move() // error protected 只能在 类内部 和 子类 中可以使用
// dog.__run__() // error private只能在 类内部 使用

只能在类内部使用 子类和实例对象均不可使用

readonly

readonly 只能修饰属性 不能修饰方法

class Person {
  readonly age: number = 18

  constructor(age: number) {
    this.age = age
  }
}

readonly 表示只读,用来防止在构造函数之外对属性进行赋值
————————————————————
class Person {
  // 注意:只要是readonLy来修饰的属性,必须手动提供明确的类型
  readonly age: number

  constructor(age: number) {
    this.age = age
  }
}

可以给赋初始值,初始值如果不加类型注解,则属性的类型即为值(字面量类型)
————————————————————
interface IPerson {
  readonly  name: string
}

let obj: IPerson = {
  name: '张三'
}

// obj.name = '李四' // error

接口中的使用
————————————————————
let obj1: { readonly name: string} = {
  name: '张三'
}

l的使用

类型兼容性

有两种类型系统

  • 结构化类型系统(TS)(也叫 duck typing 鸭子类型)
  • 表明类型系统
class Point {
  x: number
  y: number
}
class Point2D {
  x: number
  y: number
}
let p: Point = new Point2D()

在结构类型系统中,如果两个对象具有相同的形状,则认为它们属于同一类型。

class Point3D {
  x: number
  y: number
  z: number
}

let p1: Point = new Point3D()

对于对象类型来说,y的成员至少与x相同,则×兼容y(成员多的可以赋值给少的)

接口兼容性

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
p2 = p3
// p3 = p1 p3 = p2 error

class类似
————————————————————
class Point4D {
  x: number
  y: number
  z: number
}

p2 = new Point4D()

类可以和接口兼容

函数兼容性

type F1 = (a: number) => void
type F2 = (a: number, b: number) => void

let f1 = F1
let f2 = F2

f2 = f1

1、参数个数:参数少的可以赋值给参数多的
————————————————————
type F1 = (a: number) => void
type F2 = (a: number) => void

let f1 = F1
let f2 = F2

f1 = f2

2、参数类型(原始类型):相同位置的参数类型要相同或兼容
————————————————————
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

f3 = f2

2、参数类型(对象类型):将对象(对象或者接口)拆开,把每个属性看作一个参数,参数少的可以赋值给参数多的
————————————————————
type F5 = () => number
type F6 = () => number

let f5 = F5
let f6 = F6

f5 = f6

3、返回值类型(原始类型): 一样即可互相赋值
————————————————————
type F7 = () => {x: number}
type F8 = () => {x: number, y: number}

let f7 = F7
let f8 = F8

f7 = f8
3、返回值类型(对象类型): 成员多的赋值给成员少的

交叉类型(&)

interface A {
  name: string
}
interface B {
  age: number
}

let obj4: A & B = {
  name: '张三',
  age: 18
}

用于组合多个类型为一个类型(常用于对象类型,interface or class,type)

交叉类型(&) 和 接口继承(extends) 的对比

  • 相同点:都可以实现对象类型的组合
  • 不同点:两种方式实现类型组合时,对于 同名属性和方法 之间,处理类型冲突的方式不同
interface A {
  name: string
  fn:(value: number) => string
}

interface B extends A{
  name: number
  fn:(value: string) => string
}

接口继承同名 方法或属性 会报错(类型不兼容)
————————————————————
interface A {
  name: string
  fn:(value: number) => string
}
interface B {
  name: number
  fn:(value: string) => string
}

type C = A & B // c的类型是 A&B


交叉类型没有错误 可以简单理解成 
name: string | number
fn: (value: string | number) => string

泛型<>

定义

语法:在 函数/对象/接口 名称的后面添加<>,尖括号中指定具体的类型
通过泛型做到了让函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全

定义:
function id<Type>(value: Type): Type {return value}
调用:
function id<类型>(参数)


// 使用泛型来创建一个函数
function id<Type>(value: Type): Type {
  return value
}

// 调用时传入类型参数
const num = id<number>(2)
const str = id<string>('hello')

// 调用时不传入类型参数,TypeScript 会自动推断类型参数(字面量类型)
const num1 = id(2)
const str1 = id('hello')

泛型约束 extends

添加泛型约束收缩类型,主要有以下两种方式:1、指定更加具体的类型 2、添加约束

function id1<Type>(value: Type[]): Type[] {
  value.length
  return value
}

1、指定更加具体的类型
————————————————————
interface ILength {
  length: number
}

// 使用 extends 关键字来约束 Type 必须符合接口
function id2<Type extends ILength>(value: Type): Type {
  value.length
  return value
}

id2('hello')
id2([1, 2, 3])
id2({ length: 10, value: 3 })

2、添加约束

为泛型添加 类型约束(使用 keyof)

function getProperty<Type, Key extends keyof Type
                    >(obj: Type, key: Key) {
  return obj[key]
}

getProperty({name: '张三', age: 18}, 'name')
getProperty({name: '张三', age: 18}, 'age')

// 补充 (了解)
getProperty(18, 'toString')
getProperty('hello', 'slice')
getProperty('hello', 1) // 1代表索引,取得是数组中索引为1的值
getProperty([1, 2, 3], 'length')
getProperty([1, 2, 3], 1)  // 1代表索引,取得是数组中索引为1的值

1.添加了第二个类型变量Key,两个类型变量之间使用(,)逗号分隔。
2. keyof关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型.
3. 示例中 keyof Type 实际上获取的是 person 对象所有键的联合类型,也就是:'name'|'age'
4. 类型变量 Key  Type 约束,可以理解为:Key只能是Type所有键中的任意一个,或者说只能访问对象中存在的属性。

泛型接口

interface IdFunc<Type> {
  id: (value: Type) => Type
  ids: () => Type[]
}

let obj: IdFunc<number> = {
  id(value) {
    return value
  },
  ids() {
    return [1, 2, 3]
  }
}

接口的类型变量,对接口中所有其他成员可见
泛型接口在被调用时必须 显式的指定 具体的 类型(IdFunc<number>

泛型类

class GenericNumber<NumType> {
  defaultValue: NumType
  add: (x: NumType, y: NumType) => NumType
}
// 这种情况下,推荐明确指定 <类型>
const myNum = new GenericNumber<number>()
myNum.defaultValue = 50

 new 实例对象时把 number 传进去,如果想省略传入类型,则需要加构造函数
————————————————————
class GenericNumber<NumType> {
  defaultValue: NumType
  add: (x: NumType, y: NumType) => NumType

  constructor(value: NumType) {
    this.defaultValue = value
  }
}
// 此时,可以省略 <类型> 不写,类型推断出是 number
const myNum = new GenericNumber(50)
// 仍可以重新赋值
myNum.defaultValue = 50

泛型——内置工具类型

TS内置了一些常用的工具类型,来简化TS中的一些常见操作。
在type中使用

- Partial<Type>
- 用来构造(创建)一个新类型,将Type的所有属性设置为(?) 可选

- Readonly<Type>
- 用来构造一个类型,将Type的所有属性都设置为 readonly(只读)

- Pick<Type,Keys>
- 从Type中选择属性来构造新类型

- Record<Keys,Type>
- 构造一个对象类型,属性键为Keys,属性类型为Type

1、Partial 可选

interface Props {
  name: string
  children: number[]
}
type PartialProps = Partial<Props>

let p1: PartialProps = {}

// type PartialProps = {
//     name?: string | undefined;
//     children?: number[] | undefined;
// }

2、readonly 只读

interface Props {
  name: string
  children: number[]
}
type ReadonlyProps = Readonly<Props>

let p2: ReadonlyProps = {
  name: '张三',
  children: [1, 2, 3]
}
// p2.children = [5,6,8] error

3、Pick 选择属性

interface Props {
  name: string
  phone: number
  children: number[]
}

type PickProps = Pick<Props, 'name' | 'phone'>

// type PickProps = {
//   name: string;
//   phone: number;
// }

4、Record 构造一个新的对象类型

type RecordProps = Record<'a' | 'b' | 'c', number[]>  
let p4: RecordProps = {
  a: [1],
  b: [2],
  c: [3]
}

// 赋值时 abc 必须都存在
// type RecordProps = {
//   a: number[];
//   b: number[];
//   c: number[];
// }

索引签名类型[key: 类型]

当 **无法确定 **对象中有哪些 属性或方法 (或者说对象中可以出现任意多个属性),此时,就可以用 索引签名类型

- 使用[key:string]来约束该接口中允许出现的属性名称。表示只要是string类型的属性名称,都可以出现在对象中。

- 这样,对象obj中就可以出现任意多个属性(比如,a、b等)。

- key只是一个占位符,可以换成任意合法的变量名称。

- JS中对象({})的键是string类型的。

使用

interface AnyObject {
  [key: string]: number // 键是string 说明是object对象
}

let obj3: AnyObject = {
  a: 999,
  bbb: 18
}
// let obj4: AnyObject = [111, 222, 333] error

// 不能将类型“number[]”分配给类型“AnyObject”。
// 类型“number[]”中缺少类型“string”的索引签名
————————————————————
interface MyArray<T> {
  [index: number]: T  // 键是number 说明是数组
}

let arr4: MyArray<number> = [1, 2, 3, 4, 5]
arr4[2] // 3

// 索引签名类型在数组中的应用
// 数组的键(索引)是数值类型

映射类型[Key in Type]

基于 旧类型 创建 新类型(对象类型)

比如,类型PropKeys有x/y/z,另一个类型Type1中也有x/y/z,并且Type1 x/y/z的类型相同

type Propkeys = 'x' | 'y' | 'z'
type Type1 = { x: number; y: number; z: number }

这样书写没错,但x/y/z重复书写了两次。像这种情况,就可以使用映射类型来进行简化。

type Propkeys = 'x' | 'y' | 'z'
type Type2 = { [Key in PropKeys]: number }

- 映射类型是基于索引签名类型的,所以,该语法类似于索引签名类型,也使用了[]

- Key in PropKeys 表示 Key 可以是 PropKeys 联合类型中的任意一个,类似于 forin(let k in obj)

- 使用映射类型创建的新对象类型Type2和类型Type1结构完全相同

- 注意:映射类型只能在 类型别名 中使用,不能在接口中使用。

使用 —— 基于 联合类型 创建 新类型

type Propkeys = 'x' | 'y' | 'z'
type Type1 = { x: number; y: number; z: number }
type Type2 = { [k in Propkeys]: number } // // 赋值时 xyz 必须都存在
// type Type2 = {
//   x: number;
//   y: number;
//   z: number;
// }

// 类型“{ x: number; }”缺少类型“Type2”中的以下属性: y, z
// let obj2: Type2 = {x: 5} 

使用 —— 基于 对象类型 创建 新类型 keyof

type Props = { a: number; b: string; c:boolean}
type Type3={[key in keyof Props]: number }

// 1.首先,先执行 keyof Props 获取到对象类型 Props 中所有键的联合类型即,'a'|"b'|'c'。
// 2.然后,Key in... 就表示 Key 可以是 Props 中所有的键名称中的任意一个。
// type Type3 = {
//   x: number;
//   y: number;
//   z: number;
// }

索引查询类型

// 查询索引的类型
type Props = { x: number; y: string; z: boolean }
type TypeA = Props['x'] // number
type TypeB = Props['y'] // string
type TypeC = Props['z'] // boolean

// 模拟 Partial 类型
type MyPartial<T> = {
  [P in keyof T]?: T[P]
}
type PartialProps = MyPartial<Props>

索引查询类型 ——同时查询多个索引的类型

type TypeD = Props['x' | 'y'] // number | string
type TypeE = Props[keyof Props] // number | string | boolean

🌟TypeScript类型声明文件

ts 中有两种文件类型:

  • .ts 文件
  • .d.ts 文件 【为 JS 库提供类型信息】
.ts 文件:

	既包含类型信息又可执行代码。
	可以被编译为js文件,然后,执行代码。
	用途:编写程序代码的地方。
	
.d.ts 文件:

  只包含类型信息的类型声明文件。
  不会生成js文件,仅用于提供类型信息。
  用途:为JS提供类型信息。

第三方库的类型声明文件

  • 库自带类型声明文件
  • 由DefinitelyTyped提供
DefinitelyTyped是一个github仓库,用来提供高质量TypeScript类型声明。

可以通过npm/yarn来下载该仓库提供的TS类型声明包,这些包的名称格式为:@types/*。

当安装@types/*类型声明包后,TS会 自动加载 该类声明包,以提供该库的类型声明。

创建自己的类型声明文件

  • 项目内共享类型
  • 为已有JS文件提供类型声明

1、项目内共享类型

1.使用场景:

如果多个.ts文件中都用到同一个类型,此时可以创建.d.ts文件提供该类型,实现类型共享

2.实现:

- 创建index.d.ts类型声明文件
- 创建需要共享的类型,并使用export导出Ts中的类型也可以使用import/export实现模块化功能)。
- 在需要使用共享类型的.ts文件中,通过import导入即可.d.ts后缀导入时,直接省略

3.使用:

index.d.ts 文件

type Props = { x: number; y: number; }
export { Props };

a.ts 文件

import { Props } from "./index";  // 导入时,不需要加后缀[.d.ts]
let p1: Props = { x: 1, y: 2 };

2、为已有JS文件提供类型声明

1.使用场景:

在将JS项目迁移到TS项目时,为了让已有的js文件有类型声明。
成为库作者,创建库给其他人使用。

2.实现:

类型声明文件的编写与模块化方式相关

declare

declare 关键字用于声明外部的变量、模块、函数或类型,帮助 TypeScript 识别在运行时存在但在编译时未知的内容。

  1. 声明全局变量

    // 声明一个全局变量
    declare var $: any;
    
    // 使用全局变量
    $('body').addClass('active');
    
    declare var jQuery: (selector: string) => any;
    
    jQuery('#foo');
    
  2. 声明全局函数

    declare function myGlobalFunction(name: string): void;
    
    myGlobalFunction('Hello World');