TypeScript

目录

  • TS介绍
  • 安装TS
  • 数据类型
    • 原始类型
    • 数组
    • 函数
    • object、Object、{}
    • 元组
    • viod
    • never
    • any
    • unknown
  • 类型推断
  • 类型断言
    • 非空断言
    • 确定赋值断言
  • 字面量类型
  • 类型拓宽
  • 类型缩小
  • 联合类型
  • 交叉类型
  • 类型别名
  • 接口
    • 接口和类型别名的区别
  • 枚举
  • 泛型
    • 泛型约束
    • interface也可以使用泛型
    • 泛型工具类型
      • typeof
      • keyof
      • in

TS介绍

  1. TS是以JS为基础构建的语言 他的目的不是取代JS 而是扩展JS TS完全支持JS 在JS的基础上增加了许多功能 其中最主要的功能是在JS的基础上引入类型 让JS从动态类型的语言变成了静态类型的语言 给变量赋予了类型 可以在任何支持JS的平台中执行 但TS不能被JS解释器直接执行 所以我们需要把TS编译为JS 执行器最终执行的还是JS 只是我们写的是TS
  2. 为什么需要TS:因为JavaScript是弱类型,很多错误只有在运行时才会被发现,而TypeScript提供了一套静态检测机制,可以帮助我们在编译时就发现错误
  3. TS特点:支持最新的JavaScript新特特性,支持代码静态检查,支持诸如C、C++、Java、Go等后端语言中的特性 (枚举、泛型、类型转换、命名空间、声明文件、类、接口等)

安装TS

  1. 安装TS:npm i -g typescript,指定TS版本安装:npm i -g [email protected]
  2. 在线开发TypeScript的云环境——Playground
  3. TS代码写在.ts文件中,编译TS代码为JS:tsc hello.ts -w,再使用node运行JS
  4. 安装ts-node:npm i -g ts-node。上面那个步骤运行TS太麻烦,安装个TS-node直接运行TS
  5. 执行:ts-node index.ts,执行TS文件

数据类型

  1. 声明变量类型时,在变量名称后面加: 数据类型。带冒号的:都是在声明数据类型。
  2. 原始类型:number、string、boolean、symbol
    相应原始类型的包装对象:Number、String、Boolean、Symbol
    从类型兼容性上看,原始类型兼容对应的包装对象,反过来包装对象不兼容对应的原始类型。
let num: number;
let Num: Number;
Num = num; // ok
num = Num; // ts(2322)报错

原始类型

JavaScript中以下类型被视为原始类型:string、boolean、number、bigint、symbol、null和undefined。

let str: string = "jimmy";
let num: number = 24;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let big: bigint = 100n;
let sym: symbol = Symbol("me"); 

// 默认情况下,null和undefined是所有类型的子类型。就是说,你可以把null和undefined赋值给其他类型。
str = null;
bool = undefined;
// 但是如果你在tsconfig.json指定了"strictNullChecks":true ,null和undefined只能赋值给void和它们各自的类型。

数组

数组的定义方式:

  1. 在元素类型后面接上[]:let arr:string[] = ["1","2"];
  2. 使用数组泛型Array<元素类型>let list: Array = [1, 2, 3];

此外,TypeScript还提供了ReadonlyArray类型,它与Array相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。

函数

  1. 函数声明
function sum(x: number, y: number): number {
    return x + y;
}
// x: number:这个number代表输入x的数据类型
// y: number:这个number代表输入y的数据类型
// function sum(x: number, y: number): number:最后的这个number代表返回值的类型

const add = function sum(x: number, y: number = 10, z?: number): number {// 声明式函数
// y: number = 10:y变量的默认值为10
// z?: number:z变量后的这个?代表z变量可选,可选参数后面不允许再出现必需参数
// add是函数类型
    return x + y + z;
}
// 现在我们想把add赋值给变量add2,那么add2也必须是个函数
const add2: (x: number, y: number, z?: number) => number = add;
// => number:这个=>是TS中声明函数类型返回值的方法

用接口定义函数类型

// SearchFunc是接口名,它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface SearchFunc {
  (source: string, subString: string): boolean;
}
// 使用
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
  let result = source.search(subString);
  return result > -1;
}

剩余参数。有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。在TS里,你可以把所有参数收集到一个变量里。剩余参数会被当做个数不限的可选参数。可以一个都没有,同样也可以有任意个。编译器创建参数数组,名字是你在省略号(…)后面给定的名字,你可以在函数体内使用这个数组。

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}
let a = [];
push(a, 1, 2, 3);

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。

type Types = number | string
//我们为add函数提供了多个函数类型定义,从而实现函数的重载
function add(a:number,b:number):number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;

function add(a:Types, b:Types) {
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
  return a + b;
}
const result = add('Semlinker', ' Kakuqo');
result.split(' ');

object、Object、{}

{}和Object可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而object则表示非原始类型。
Object不仅是object的父类型,同时也是object的子类型。

type isLowerCaseObjectExtendsUpperCaseObject = object extends Object ? true : false; // true
type isUpperCaseObjectExtendsLowerCaseObject = Object extends object ? true : false; // true
upperCaseObject = lowerCaseObject; // ok
lowerCaseObject = upperCaseObject; // ok

元组

元组类型允许表示一个已知元素数量和类型的数组。

let x: [string, number]; // 类型必须匹配且数组x中的元素个数必须为2
x = ['hello', 10]; // OK 
x = ['hello', 10,10]; // Error 
x = [10, 'hello']; // Error
// 如果一个数组中可能有多种类型,数量和类型都不确定,那就直接any[]

支持解构赋值

let employee: [number, string] = [1, "Semlinker"];
let [id, username] = employee;
console.log(`id: ${id}`);//id: 1
console.log(`username: ${username}`);//username: Semlinker

元组类型的可选元素:通过 ? 号来声明元组类型的可选元素

let optionalTuple: [string, boolean?];

剩余元素:元组类型里最后一个元素可以是剩余元素,形式为...X,X是数组类型。剩余元素代表元组类型是开放的,可以有零个或多个额外的元素。

type RestTupleType = [number, ...string[]];// 表示带有一个number元素和任意数量string类型元素的元组类型。
let restTuple: RestTupleType = [666, "Semlinker", "Kakuqo", "Lolo"];
console.log(restTuple[0]);
console.log(restTuple[1]);

只读的元组类型:可以为任何元组类型加上readonly关键字前缀,以使其成为只读元组

const point: readonly [number, number] = [10, 20];

viod

void表示没有任何类型,和其他类型是平等关系,不能直接赋值,你只能为它赋予null和undefined(在strictNullChecks未指定为true时)。声明一个void类型的变量没有什么大用,我们一般也只有在函数没有返回值时去声明。值得注意的是,方法没有返回值将得到undefined,但是我们需要定义成void类型,而不是undefined类型。否则将报错

never

never类型表示的是那些永不存在的值的类型。值会永不存在的两种情况:

  1. 如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了);
  2. 函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。
// 异常
function err(msg: string): never { // OK
  throw new Error(msg); 
}

// 死循环
function loopForever(): never { // OK
  while (true) {};
}

never类型同null和undefined一样,也是任何类型的子类型,也可以赋值给任何类型。但是没有类型是never的子类型或可以赋值给never类型(除了never本身之外),即使any也不可以赋值给never

let ne: never;
let nev: never;
let an: any;

ne = 123; // Error
ne = nev; // OK
ne = an; // Error
ne = (() => { throw new Error("异常"); })(); // OK
ne = (() => { while(true) {} })(); // OK

可以利用never类型的特性来实现全面性检查

type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // 这里 foo 被收窄为 string 类型
  } else if (typeof foo === "number") {
    // 这里 foo 被收窄为 number 类型
  } else {
    // foo 在这里是 never
    const check: never = foo;
  }
}

假如后来有一天你的同事修改了Foo的类型:type Foo = string | number | boolean;
然而他忘记同时修改controlFlowAnalysisWithNever方法中的控制流程,这时候else分支的foo类型会被收窄为boolean类型,导致无法赋值给never类型,这时就会产生一个编译错误。通过这个方式,我们可以确保controlFlowAnalysisWithNever方法总是穷尽了Foo的所有可能类型。 通过这个示例,我们可以得出一个结论:使用never避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

any

任何类型都可以被归为any类型。变量如果在声明的时候,未指定其类型,那么它会被识别为any类型。在any上访问任何属性都是允许的,也允许调用任何方法。

let a: any = 666;
a = "Semlinker";
a = false;

let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);

使用any类型,就无法使用TypeScript提供的大量的保护机制。因此,TypeScript3.0引入了unknown类型。

unknown

unknown与any一样,所有类型都可以分配给unknown,二者最大区别是:任何类型的值都可以赋值给any,同时any类型的值也可以赋值给任何类型。任何类型的值都可以赋值给unknown,但unknown只能赋值给unknown和any类型。

let notSure: unknown = 4;
let uncertain: any = notSure; // OK

let notSure: any = 4;
let uncertain: unknown = notSure; // OK

let notSure: unknown = 4;
let uncertain: number = notSure; // Error

如果不缩小类型,就无法对unknown类型执行任何操作。我们可以使用typeof、类型断言等方式来缩小范围

function getDogName() {
 let x: unknown;
 return x;
};
const dogName = getDogName();
// 直接使用
const upName = dogName.toLowerCase(); // Error
// typeof
if (typeof dogName === 'string') {
  const upName = dogName.toLowerCase(); // OK
}
// 类型断言 
const upName = (dogName as string).toLowerCase(); // OK

类型推断

TypeScript会根据上下文环境自动推断出变量的类型,我们把TypeScript这种基于赋值表达式推断类型的能力称之为类型推断。在TypeScript中,具有初始化值的变量、有默认值的函数参数、函数返回的类型都可以根据上下文推断出来。

let str = 'str'// 没有声明str的类型,但TS有一个类型推断,会在我们没有指定类型时,推断出一个类型,此时str已经是字符串类型了

如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成any类型而完全不被类型检查

类型断言

类型断言:当TS不确定一个联合类型的变量到底是哪个类型时,我们只能访问此联合类型的所有类型里共用的属性和方法,而有时我们确实需要在不确定类型时就访问其中一个类型的属性和方法。
TS的类型断言用来告诉编译器,我比编译器更了解这个类型,并且不应该再报错。

// 尖括号 语法
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;

// 联合类型无法断言成另外的类型
function getLength(input: string | number): boolean{
	return <boolean> input
}

非空断言

非空断言:后缀表达式操作符!可以用于断言操作对象是非null和非undefined类型。具体而言,x!将从x值域中排除null和undefined。

let mayNullOrUndefinedOrString: null | undefined | string;
mayNullOrUndefinedOrString!.toString(); // ok
mayNullOrUndefinedOrString.toString(); // ts(2531)

确定赋值断言

确定赋值断言:在实例属性和变量声明后面放置一个!号,从而告诉TypeScript该属性会被明确地赋值。

let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
  x = 10;
}

字面量类型

在TypeScript中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。
TypeScript支持3种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型

{
  let specifiedStr: 'this is string' = 'this is string';
  let str: string = 'any string';
  // 'this is string'(这里表示一个字符串字面量类型)类型是string类型,而string类型不一定是'this is string'(这里表示一个字符串字面量类型)类型
  specifiedStr = str; // ts(2322) 类型 '"string"' 不能赋值给类型 'this is string'
  str = specifiedStr; // ok 
}

字面量类型常用于把多个字面量类型组合成一个联合类型,用来描述拥有明确成员的实用的集合。

interface Config {
    size: 'small' | 'big';
    isEnable:  true | false;
    margin: 0 | 2 | 4;
}

类型拓宽

我们将const定义为一个不可变更的常量,在缺省类型注解的情况下,变量的类型为赋值字面量类型

{
  // 变量: 类型
  const str = 'this is string'; // str: 'this is string'
  const num = 1; // num: 1
  const bool = true; // bool: true
}

使用let定义的变量,在缺省类型注解的情况下,变量的类型转换为了赋值字面量类型的父类型

{
  let str = 'this is string'; // str: string
  let num = 1; // num: number
  let bool = true; // bool: boolean
}

将TypeScript的字面量子类型转换为父类型的这种设计称之为 “literal widening”,即字面量类型的拓宽
类型拓宽:所有通过let或var定义的变量、函数的形参、对象的非只读属性,如果指定了初始值却未显式添加类型注解,那么它们被TS推断出来的类型就是指定的初始值字面量类型拓宽后的类型。

{
  const specifiedStr: 'this is string' = 'this is string'; // 类型是 '"this is string"'
  let str2 = specifiedStr; // 即便使用 let 定义,类型是 'this is string'
}

除了字面量类型拓宽之外,TypeScript对某些特定类型值也有类似类型拓宽的设计,比如对null和undefined的类型进行拓宽,通过let、var定义的变量如果满足未显式声明类型注解且被赋予了null或undefined值,则推断出这些变量的类型是any:

{
  let x = null; // 类型拓宽成 any
  let y = undefined; // 类型拓宽成 any
  const z = null; // 类型是 null
  let anyFun = (param = null) => param; // 形参类型是 null
  let z2 = z; // 类型是 null
  let x2 = x; // 类型是 null
  let y2 = y; // 类型是 undefined
}

但是在严格模式下,一些比较老的版本中(2.0)null和undefined并不会被拓宽成any。
对于对象,TypeScript 的拓宽算法会将其内部属性视为将其赋值给let关键字声明的变量,进而来推断其属性的类型。

//obj的类型为{x:number}。
//你可以将obj.x赋值给其他number类型的变量,而不是string类型的变量,并且它还会阻止你添加其他属性。
const obj = { 
  x: 1,
};
obj.x = 6; // OK 
// Type '"6"' is not assignable to type 'number'.
obj.x = '6'; // Error
// Property 'y' does not exist on type '{ x: number; }'.
obj.y = 8; // Error
// Property 'name' does not exist on type '{ x: number; }'.
obj.name = 'semlinker'; // Error

控制拓宽过程的方法:

  1. 使用const

类型缩小

类型缩小:在TypeScript中,我们可以通过某些操作将变量的类型由一个较为宽泛的集合缩小到相对较小、较明确的集合。可以通过typeof类型判断、字面量类型等值判断(===)或其他控制流语句(包括但不限于 if、三目运算符、switch 分支)将联合类型收敛为更具体的类型

联合类型

联合类型表示取值可以为多种类型中的一种,使用|分隔每个类型,常与null或undefined一起使用。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven'; // OK
myFavoriteNumber = 7; // OK

联合类型还有下面这种用法:1、2或’click’被称为字面量类型,用来约束取值只能是某几个值中的一个。

let num: 1 | 2 = 1;
type EventNames = 'click' | 'scroll' | 'mousemove';

交叉类型

交叉类型是将多个类型合并为一个类型,使用&定义。常用于将多个接口类型合并成一个类型,从而实现等同接口继承的效果,也就是所谓的合并接口类型。

// IntersectionType同时拥有了id、name、age所有属性
type IntersectionType = { id: number; name: string; } & { age: number };
  const mixed: IntersectionType = {
    id: 1,
    name: 'name',
    age: 18
}

如果合并的多个接口类型存在同名属性:

  1. 如果同名属性的类型兼容,比如一个是number,另一个是number的子类型、数字字面量类型,合并后,属性的类型就是两者中的子类型。
  2. 如果同名属性的类型不兼容,比如一个是number,另一个是string,合并后,属性的类型就是number和string两个原子类型的交叉类型。

类型别名

类型别名用来给一个类型起个新名字。类型别名常用于联合类型。
场景:传入的参数允许传入两种类型,一种是string,一种是函数类型,函数类型返回字符串,如果参数是string,直接返回参数,如果参数是函数类型,返回函数的执行结果。这就是典型的联合类型。

type NameResolver = () => string// 定义第二个参数
type NameOrResolver = string | NameResolver// 定义函数参数
function getName(n: NameOrResolver):string{
	if(typeof n === 'string'){
		return n;
	}else{
		return n();
	}
}

接口

接口(Interfaces)是一个很重要的概念,它是对行为的抽象,也是对对象的形状的描述,而具体如何行动需要由类(classes)去实现(implement)

interface Person {
    name: string;
    age: number;
}
let tom: Person = {
	//变量tom是Person类型的,他的【形状】必须和Person一致,【必须】且【只能】有name和age,
	//并且name是string类型,age是number类型
    name: 'Tom',
    age: 25
};

可选、只读属性:

interface Person {
  readonly name: string;// 只读属性用于限制只能在对象刚刚创建的时候修改其值
  age?: number;
}

任意属性:有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性。确定属性和可选属性的类型都必须是任意属性的类型的子集。一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型

interface Person {
    name: string;
    age?: number; // 这里真实的类型应该为:number | undefined
    [propName: string]: string | number | undefined;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};

用接口描述函数的类型:

// 普通函数
function plus(a: number, b: number): number{return a + b}
// 用接口描述函数的类型,括号里定义输入和输入类型,后面: number定义返回值类型
interface IPlus{(a: number, b: number): number}
const a: IPlus = plus

interface IPlus2<T>{(a: T, b: T): T}// 用接口描述函数的类型,使用泛型
const a: IPlus2<number> = plus// 指定泛型类型

接口和类型别名的区别

给类上的方法提供权限管理,因为有些东西我们不希望暴露给外面使用,此时修饰符就出场了
成员都默认为 public
public修饰的属性和方法是公有的,可以在任何地方被访问到
当成员被标记成 private时,它就不能在声明它的类的外部访问,子类都访问不到。
当成员被标记成 private时,它就不能在声明它的类的外部访问,只有我和我的子类可以访问。
在属性/方法前面加上static,表示静态属性/静态方法(静态属性/静态方法的定义和实例没有关系),表示类上面可以直接访问的属性和方法,不需要实例化,在类上可以直接调用
不同类之间有一些共同的特性,使用子类继承父类的办法很难完成,这是我们可以把这些特性提取成接口,使用interface关键字来实现。

interface Radio{
	switchRadio(): void;
}
interface Battery{
	checkBatteryStatus();
}
// 接口可以继承接口
interface RadioWithBattery extends Radio{
	checkBatteryStatus();
}
class Car implements Radio{
	switchRadio(){}
}
// 继承多个接口
class Cellphone implements Radio, Battery{
	switchRadio(){}
	checkBatteryStatus(){}
}

枚举

数字枚举enum

enum Direction {// 枚举成员会被赋值,从0依次递增,Up的值为 0, Down的值为 1等等。
    Up,
    Down,
    Left,
    Right
}
console.log(Direction.Up);//0
console.log(Direction[0]);//Up
enum Direction {// Up初始化为1。其余的成员从1开始自动增长。Direction.Up的值为1,Down为2,Left为3,Right为4。
    Up = 1,
    Down,
    Left,
    Right
}

字符串枚举:给每一项枚举成员都添加一个字符串的值

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

常量枚举。不加const的枚举编译后有一大堆的逻辑,常量枚举会内联枚举的用法,并且不会把这些枚举编译成JS代码。只有常量值可以进行常量枚举,我们这里讲的都是常量值,枚举还有另一种值:计算值

const enum Directions {
    Up,
    Down,
    Left,
    Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]

泛型

泛型:定义函数接口或类的时候,先不指定具体的类型,而是在使用时再指定类型。可以把他看成一个占位符,等用到的时候才动态填入用到的类型值
TypeScript_第1张图片

function echo<T>(arg: T): T{return arg}

我们给echo添加了类型变量T。 T帮助我们捕获用户传入的类型(比如:number),之后我们就可以使用这个类型。 之后我们再次使用了 T当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。
可以引入希望定义的任何数量的类型变量。
TypeScript_第2张图片

function swap<T, U>(tuple: [T, U]): [U, T]{return [tuple[1], tuple[0]]}

泛型约束

function echo<T>(arg: T[]): T[]{return arg}// 规定输入输出都是含有T的数组
// 对泛型进行约束,泛型只包含含有length属性的变量
interface IWithLength{
	length: number
}
// 用extends关键字约束传入的泛型必须含有length属性
function echo<T extends IWithLength>(arg: T): T{return arg.length}
console.log(echo(3));// 报错。3没有length属性

无论什么类型被推入队列、被弹出的类型都与推入的类型一样,在类名后面加泛型,在实例化的时候,要在类名后面指定类型

class Queue<T>{// 在类名后面加泛型
	private data = [];
	push(item: T) {return this.data.push(item)}// 加泛型
	popo(): T{return this.data.shift()}// 加泛型
}
const queue = new Queue<number>();// 在实例化的时候,要在类名后面指定类型

interface也可以使用泛型

interface KeyPair<T,U>{
	key: T;
	value: U
}
let kp1: KeyPair<number,string> = {key: 123, value: "str"}

泛型工具类型

typeof

typeof:在类型上下文中获取变量或者属性的类型,除了可以获取对象的结构类型之外,它也可以用来获取函数对象的类型

interface Person {
  name: string;
  age: number;
}
const sem: Person = { name: "semlinker", age: 30 };
type Sem = typeof sem; // type Sem = Person

function toArray(x: number): Array<number> {
  return [x];
}
type Func = typeof toArray; // -> (x: number) => number[]

keyof

keyof:用于获取某种类型的所有键,其返回类型是联合类型。

in

in:遍历枚举类型

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

使用第三方库时,需要引入他的声明文件,获得对应的代码补全,接口提示等功能。
声明文件.d.ts
ts文件会获得.d.ts文件声明的所有文件
TS配置文件tsconfig.json

你可能感兴趣的:(前端,typescript,前端)