TypeScript的简单了解,先让自己有个概念

TypeScript是一种由Microsoft开发的编程语言,它是JavaScript的超集,通过添加静态类型系统来增强JavaScript的功能。在这篇博客中,我们将简单的了解一下TypeScript的一些基础知识点。

首先我们来看一下 TypeScript 有哪些优势吧

  • 类型就是文档,而且这份文档编译器和人都能读懂,比注释管用多了。有了类型可以在编译期检查错误,IDE可以给你智能提示提高工作利率
  • 强大的IDE支持
  • 如今前端项目越来越庞大,越来越复杂,静态类型简直就是刚需
  • 我们在进行代码重构的时候,可以很快发现一些接口或者参数不兼容的情况,提前暴露问题
  • 在调用接口的时候就可以很明确的知道应该传递哪些参数,返回值是什么类型。

TypeScript 缺点

了解完优势我们也要看一下它有那些缺点

  • 有一定的学习成本,需要理解接口(Interfaces)泛型(Generics)类(Classes)枚举类型(Enums)等前端工程师可能不是很熟悉的概念
  • 短期项目开发可能 会增加一些开发成本,毕竟要写很多类型的定义
  • 集成到构建流程需要一些工作量
  • 可能和一些库结合的还不是很完美

TypeScript 的类型检查

类型推断:
  • 基础类型推断
  • 最佳通用类型推断
  • 上下文类型推断
类型兼容:
  • 结构之间兼容:成员少的兼容成员多的
  • 函数之间兼容:参数多的兼容参数少的
类型保护:
  • 类型保护允许你使用更小范围下的对象类型

泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
编译器足够聪明,能够知道我们的参数类型,并将它们赋值给 TU,而不需要开发人员显式指定它们。

联合类型

由于 JavaScript 是一个动态语言,我们通常会使用不同类型的参数来调用同一个函数,该函数会根据不同的参数而返回不同的类型的调用结果:

function add(x, y) {
  return x + y;
}
add(1, 2); // 3
add("1", "2"); //"12"

由于 TypeScriptJavaScript 的超集,因此以上的代码可以直接在 TypeScript 中使用,但当 TypeScript 编译器开启 noImplicitAny 的配置项时,以上代码会提示以下错误信息:

Parameter 'x' implicitly has an 'any' type. Parameter 'y' implicitly has an 'any' type.

该信息告诉我们参数 x 和参数 y 隐式具有 any 类型。为了解决这个问题,我们可以为参数设置一个类型。因为我们希望 add 函数同时支持 stringnumber 类型,因此我们可以定义一个 string | number 联合类型,同时我们为该联合类型取个别名:

type Combinable = string | number;

在定义完 Combinable 联合类型后,我们来更新一下 add 函数:

function add(a: Combinable, b: Combinable) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

add 函数的参数显式设置类型之后,之前错误的提示消息就消失了。那么此时的 add 函数就完美了么,我们来实际测试一下:

const result = add("semlinker", " kakuqo"); result.split(" ");

在上面代码中,我们分别使用 ‘semlinker’ 和 ’ kakuqo’ 这两个字符串作为参数调用 add 函数,并把调用结果保存到一个名为 result 的变量上,这时候我们想当然的认为此时 result 的变量的类型为 string,所以我们就可以正常调用字符串对象上的 split 方法。但这时 TypeScript 编译器又出现以下错误信息了:

Property 'split' does not exist on type 'Combinable'.
Property 'split' does not exist on type 'number'.

很明显 Combinablenumber 类型的对象上并不存在 split 属性。问题又来了,那如何解决呢?这时我们就可以利用 TypeScript 提供的函数重载。

Type 和 Interface 的区别 (敲黑板,面试时的高频率问题)

  1. interface 可以重复声明,type 定义后不能重复声明
  2. interface 可以通过 extends 关键字来继承接口。而 type 通过 & 来实现类似于继承的功能
  3. interface 能够声明合并,type 可以声明基本类型别名,联合类型,元组等类型,严谨一点讲 interface 是定义,type 是声明

看一下下面的例子能帮助你更好的理解

interface User {
  name: string
  age: number
}
interface User {
  sex: string
}
/*
User 接口为 {
	name: string
	age: number
	sex: string
}
*/
// type 可以而 interface 不行
// type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string
// 联合类型
interface Dog {
  wang();
}
interface Cat {
  miao();
}
type Pet = Dog | Cat
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]
// type 语句中还可以使用 typeof 获取实例的 类型进行赋值
// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div
// 其他的骚操作
type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary < string, Person>;
type Callback = (data: T) => void;
type Pair = [T, T];
type Coordinates = Pair; type Tree = T | { left: Tree, right: Tree };
Objects/Functions
interface Point {
  x: number;
  y: number;
}
interface setPoint {
  (x: number, y: number): void;
}

type Point = {
  x: number;
  y: number;
}; type setPoint = (x: number, y: number) => void;

类型别名

  • 与接口类型不一样,类型别名可以用于一些其他类型,比如原始类型联合类型元组
// primitive
type Name = string;
// object
type PartialPointX = { x: number };
type PartialPointY = { y: number };
// union
type PartialPoint = PartialPointX | PartialPointY;
//tuple
type Data = [number, string];

extends 关键字

接口和类型别名都能够被扩展,但语法有所不同。此外,接口和类型别名不是互斥的。接口可以扩展类型别名,而反过来是不行的。

//interface extends interface
interface PartialPointX {
  x: number;
}
interface Point extends PartialPointX {
  y: number;
}

//type alias extends type alias
type PartialPointX = { x: number };
type Point = PartialPointX & { y: number };

//interface extends type alias
type PartialPointX = { x: number };
interface Point extends PartialPointX {
  y: number;
}

//type alias extends interface
interface PartialPointX {
  x: number;
}
type Point = PartialPointX & { y: number };

implements 关键字

一般情况下,一个类只能继承自另一个类,但是不同类之间可以有一些共同特性,这时候就可以使用 implements 关键字实现将这些共同特性提取成一个接口(interface);
类可以以相同的方式实现接口或类型别名,但类不能实现使用类型别名定义的联合类型:

interface Point {
  x: number;
  y: number;
}


class SomePoint implements Point {
  x: 1;
  y: 2;
}

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x = 1;
  y = 2;
}

type PartialPoint = { x: number } | { y: number };

// A class can only implement an object type or
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint {
  // Error
  x = 1;
  y = 2;
}

声明合并 Declaration merging

声明合并(Declaration Merging) 是 Typescript 的一个高级特性,顾名思义,声明合并就是将相同名称的一个或多个声明合并为单个定义。它最初的设计目的是为了解决早期 JavaScript 模块化开发中的类型定义问题,有兴趣的可以去了解一下。
与类型别名不同,接口可以定义多次,会被自动合并为单个接口。

interface Point {
  x: number;
}
interface Point {
  y: number;
}

const point: Point = { x: 1, y: 2 };

TypeScript 中的高级类型

简单举几个类型和范例给大家看看

Partial

PartialTypeScript中的一个工具类型,它的作用是将传入的属性变为可选项。
keyof 可以用来取得一个对象接口的所有key值 产生联合类型
in 可以遍历枚举类型,所以经常一起使用

type Partial<T> = {[ P in keyof T]?: T[P]}
// Partial<T> 接受一个泛型参数 T,并返回一个新的类型,新类型与 T 相同,但是 T 类型中的所有属性都变为可选属性
Required

它的作用是使 T 中的所有属性都变成必需的

type Required<T> = {[P in keyof T]-?:T[P]}

这里有一个有意思的用法 -?, 这里很好理解就是将可选项代表的 ? 去掉, 从而让这个类型变成必选项。与之对应的还有个+? , 这个含义自然与-?之前相反, 它是用来把属性变成可选项的。

Pick

它的作用是从T中取出一系列K的属性

type Pick<T, K extends keyof T> = { [P in K]: T[P] };
type PickUser = Pick<User, "age" | "name">
Exclude
T extends U ? X : Y

以上语句的意思就是 如果 TU 的子类型的话,那么就会返回 X,否则返回 Y

type Exclude<T,U> = T extends U ?never : T;
const str: Exclude<'a' | '1' | '2', 'a' | 'y' | 'z'> = '1';

从代码的提示,可以很好的理解,Exclude 就是将前面类型的与后面类型对比,过滤出前面独有的属性

Omit
// Pick
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
// Exclude
type Exclude<T, U> = T extends U ? never : T;

// Omit
type Omit = Pick<T, Exclude<keyof T, K>>
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;

interface User {
id: number;
age: number;
name: string;
}

// 相当于: type OmitUser = { age: number; name: string; }
type OmitUser = Omit<User, 'id'>; // 很好理解,去除掉 User 接口内的 id 属性。
还有…就不多作坠叙了

函数重载

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

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: Combinable, b: Combinable) {
  // type Combinable = string | number;
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

在以上代码中,我们为 add 函数提供了多个函数类型定义,从而实现函数的重载。在 TypeScript 中除了可以重载普通函数之外,我们还可以重载类中的成员方法。

方法重载是指在同一个类中方法同名,参数不同(参数类型不同、参数个数不同或参数个数相同时参数的先后顺序不同),调用时根据实参的形式,选择与它匹配的方法执行操作的一种技术。所以类中成员方法满足重载的条件是:在同一个类中,方法名相同且参数列表不同。下面我们来举一个成员方法重载的例子:

class Calculator {
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: string, b: number): string;
  add(a: number, b: string): string;
  add(a: Combinable, b: Combinable) {
    if (typeof a === "string" || typeof b === "string") {
      return a.toString() + b.toString();
    }
    return a + b;
  }
}
const calculator = new Calculator();
const result = calculator.add("Semlinker", " Kakuqo");

这里需要注意的是,当 TypeScript 编译器处理函数重载时,它会查找重载列表,尝试使用第一个重载定义。 如果匹配的话就使用这个。 因此,在定义重载的时候,一定要把最精确的定义放在最前面。另外在 Calculator 类中,add(a: Combinable, b: Combinable){ } 并不是重载列表的一部分,因此对于 add 成员方法来说,我们只定义了四个重载方法。

注意点:

  • 接口不能实现接口或者类,所以实现只能用于类身上,即类可以实现接口或类
  • 接口可以继承接口或类
  • 类不可以继承接口,类只能继承类
  • 可多继承或者多实现

TypeScript中的类如何实现接口

对类的实例部分进行约束
TypeScript 中,可通过接口Interfacejs中的类Class进行约束,要求被约束的类具有一定的结构(具有特定类型的属性与方法),使得项目开发更规范,提高代码的可读性与可维护性。

// 首先声明一个DogInterface接口,
// 这个接口要求被约束的内容,其内部要有
// 一个string类型的name属性、
// 以及一个不返回内容的say方法
interface DogInterface {
  name: string;
  say(): void;
}
// 然后声明一个Dog类,这个类实现了DogInterface接口。
// 这要求Dog类中要具备DogInterface接口所述的
// 所有属性、方法,否则就会报错
class Dog implements DogInterface {
  name: string;
  say(): void {
    console.log(`${this.name}wang wang wang`);
  }
  constructor(name: string) {
    this.name = name;
  }
}
// 之后这个Dog可以正常地使用了
let dog = new Dog("旺财");
dog.say(); // '旺财wang wang wang'

上面这个例子展示了如何让一个类实现某个接口

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洋茄子炒鸡蛋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值