一篇文章让你 TS 快速入门

170 阅读24分钟

为什么要学ts

我们知道,JavaScript 是一门非常灵活的编程语言:
这种灵活性就像一把双刃剑,一方面使得 JavaScript 蓬勃发展,无所不能,从 2013 年开始就一直蝉联最普遍使用的编程语言排行榜冠军[3];另一方面也使得它的代码质量参次不起,维护成本高,运行时错误多。

说点实际的原因,最主要使用它的原因是它能减少错误。 而且,ts 的学习成本极低。TypeScript 代码能完全在 JavaScript 中运行,只要了解 JavaScript 即可开始使用 TypeScript。它还包括了 ES5、ES5+ 和其他一些诸如泛型、类型定义、命名空间等特征的集合,而且能够编译成普通的 JavaScript 代码。也就是说,你可以在任何设备、浏览器或操作系统上运行 TypeScript,它全平台兼容

什么是 TypeScript

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

简而言之,TypeScript是JavaScript的超集,具有可选的类型并可以编译为纯JavaScript。从技术上讲TypeScript就是具有静态类型的 JavaScript 。

TypeScript优缺点

优点

增强代码的可维护性,尤其在大型项目的时候效果显著

友好地在编辑器里提示错误,编译阶段就能检查类型发现大部分错误

支持最新的JavaScript新特特性

周边生态繁荣,vue3已全面支持 typescript

缺点

需要一定的学习成本

和一些插件库的兼容并不是特别完美,如以前在 vue2 项目里使用 typescript就并不是那么顺畅

增加前期开发的成本,毕竟你需要写更多的代码(但是便于后期的维护)

一、安装环境

安装typescript

首先,我们可以新建一个空文件夹,用来学习 ts,例如我在文件夹下新建了个 helloworld.ts

npm install -g typescript // 全局安装 ts

不记得自己是否已经安装过 typescript 的,可以使用以下命令来验证:

tsc -v

如果出现版本,则说明已经安装成功

Version 5.1.6

生成 tsconfig.json 配置文件

tsc --init

执行命令后我们就可以看到生成了一个 tsconfig.json 文件,里面有一些配置信息,可以对其做一些配置修改,我们暂不管他

在我们helloworld.ts文件中,随便写点什么

  1. const str:string = "会者定离,一期一祈";
  2. console.log(str);

控制台执行 tsc helloworld.ts 命令,目录下生成了一个同名的 helloworld.js 文件,代码如下

  1. `var str = "会者定离,一期一祈";
  2. console.log(str);`

在控制台输入下面的命令开启了监听模式 实时监测helloworld.ts 同步到helloworld.js

tsc helloworld.ts -w

通过tsc命令,发现我们的typescript代码被转换成了熟悉的js代码

我们接着执行

node helloworld.js

即可看到输出结果

二、TypeScript 基础类型

js的数据类型

number 、string、 null 、 undefined、 boolean、 object、  function、 array

ts新增的数据类型

any任意类型 enum 枚举 联合类型 字面量类型 unknown void

Boolean 类型

声明一个变量flag,同时指定它的类型为boolean

const flag: boolean = true;

Number 类型

声明一个变量a,同时指定它的类型为number

const count: number = 123;

String 类型

声明一个变量str,同时指定它的类型为string

const name: string = '土豆';

Enum 枚举类型

枚举类型用于定义数值集合,使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。,如周一到周日,方位上下左右等

  • 普通枚举

enum Color {
  RED,
  PINK,
  BLUE,
}
const red: Color = Color.RED;
console.log(red); // 0

初始值默认为 0 其余的成员会会按顺序自动增长 可以理解为数组下标

  • 设置默认值
enum Color {
  RED = 2,
  PINK,
  BLUE,
}
const pink: Color = Color.PINK;
console.log(pink); // 3
  • 字符串枚举
enum Color {
  RED = "红色",
  PINK = "粉色",
  BLUE = "蓝色",
}
 
const pink: Color = Color.PINK;
console.log(pink); // 粉色
  • 常量枚举

使用 const 关键字修饰的枚举,常量枚举与普通枚举的区别是,整个枚举会在编译阶段被删除 我们可以看下编译之后的效果

const enum Color {
  RED,
  PINK,
  BLUE,
}
 
const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
console.log(color); //[0, 1, 2]
 
//编译之后的js如下:
var color = [0 /* RED */, 1 /* PINK */, 2 /* BLUE */];
// 可以看到我们的枚举并没有被编译成js代码 只是把color这个数组变量编译出来了

Array 类型

对数组类型的定义有两种方式:

const arr: number[] = [1,2,3];

const arr2: Array<number> = [1,2,3];

元组(tuple)类型

上面数组类型的方式,只能定义出内部全为同种类型的数组。对于内部不同类型的数组可以使用元组类型来定义

元组( Tuple )表示一个已知数量和类型的数组,可以理解为他是一种特殊的数组

元组,元组就是固定长度的数组     语法:[类型, 类型, 类型]

const tuple: [number, string] = [666, "zhangzhang"];

需要注意的是,元组类型只能表示一个已知元素数量和类型的数组,长度已指定,越界访问会提示错误。例如,一个数组中可能有多种类型,数量和类型都不确定,那就直接any[]。

undefined和null

默认情况下 null 和 undefined 是所有类型的子类型。 也就是说你可以把 null 和 undefined 赋值给其他类型。

  let a: undefined = undefined;
  let b: null = null;
 
  let str: string = 'zhangzhang';
  str = null; // 编译正确
  str = undefined; // 编译正确

如果你在tsconfig.json指定了"strictNullChecks":true ,即开启严格模式后, null 和 undefined 只能赋值给 void 和它们各自的类型。 null 和 undefined 只能给它们自己的类型赋值

// 启用 --strictNullChecks
let x: number;
x = 1; // 编译正确
x = undefined;    // 编译错误
x = null;    // 编译错误

但是 undefined 可以给 void 赋值

let c:void = undefined // 编译正确
let d:void = null // 编译错误

any 类型

any会跳过类型检查器对值的检查,任何值都可以赋值给any类型 any类型就是js(建议不要用)

ts会有命名规范,不能命名为any

any 表示的是任意类型,一个变量设置类型为any后相当于对该变量关闭了TS的类型检测

使用TS时,不建议使用any类型

声明变量如果不指定类型,则TS解析器会自动判断变量的类型为any (隐式的any)

any是可以赋值任何类型

  let value: any = 1;
  value = "zhangmazi"; // 编译正确
  value = []; // 编译正确
  value = {};// 编译正确

void 类型

void 意思就是无效的, 一般只用在函数上,告诉别人这个函数没有返回值。

  function sayHello(): void {
    console.log("时长两年半");
  }

never 类型

抛出异常

never 类型表示的是那些永不存在的值的类型。 例如never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型

值会永不存在的两种情况:

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

Unknown 类型

unknown与any一样,所有类型都可以分配给unknown:

unknown 表示未知类型的值

只能赋值给unkonwn any

  let value: unknown = 1;
  value = "zhangzhang"; // 编译正确
  value = false; // 编译正确

那这里小伙伴们就要有疑问了 unknown和any有什么区别

任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给unknown和any

三、对象类型

这里所说的对象类型,就是我们常说的函数、{}、数组、类

object, Object 和 {} 类型

  • object object 类型用于表示所有的非原始类型,即我们不能把 number、string、boolean、symbol等 原始类型赋值给 object。在严格模式下,null 和 undefined 类型也不能赋给 object。
let object: object;
object = 1; // 报错
object = "zhangzhang"; // 报错
object = true; // 报错
object = null; // 报错
object = undefined; // 报错
object = {}; // 编译正确
  • Object

大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型 所以所有原始类型、非原始类型都可以赋给 Object(严格模式下 null 和 undefined 不可以)

let bigObject: Object;
object = 1; // 编译正确
object = "zhangzhang"; // 编译正确
object = true; // 编译正确
object = null; // 报错
ObjectCase = undefined; // 报错
ObjectCase = {}; // ok
  • {}

{} 空对象类型和大 Object 一样 也是表示原始类型和非原始类型的集合

在 TypeScript 中,我们通过 Class 关键字来定义一个类

//类
// class 类名 {
//     属性名: 类型;
//     constructor(参数:类型){
//       this.  属性名 =  参数
//      }
//     方法名(){
 
//      }
// }
class Person {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  sayHi(): void {
    console.log(`Hi, ${this.name}`);
  }
}

数组

const arr1: number[] = [1, 2, 3];
const arr2: Array<number> = [1, 2, 3];

函数

函数声明

function chicken(name: string, time: string): string {
  return name+'练习时长'+time;
}

函数表达式

const chicken = function(name: string, time: string): string {
  return name+'练习时长'+time;
}

接口定义函数

interface chicken {
  (name: string, time: string): string;
}

可选参数

function chicken(name: string, time?: string): string {
  return time ? name+'练习时长'+time :name+'无师自通' ;
}

默认参数

function chicken(name: string, time: string='两年半'): string {
  return  name+'练习时长'+time;
}

剩余参数

function chicken(name:string,time:string,...hobby: string[]): string {
    let hobbys = [];
    for (let i = 0; i < hobby.length; i++) {
      hobbys.push(hobby[i])
    }
    return name +'练习时长'+time+'爱好'+hobbys;
  }

四、类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。

let str='土豆'
str=18;//报错

上面的代码等价于

let str:string='土豆'
str=18;//报错

通过上述示例我们可以看出,我们没有给 str 指定明确类型的时候,typescript 会推断出 str 的类型是 string。

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

let str
str='土豆'//正确
str=18 //正确

五、类型断言

再好用的语言也毕竟是语言,某些情况下,我们可能比typescript更加清楚的知道某个变量的类型,所以我们可能希望手动指定一个值的类型

类型断言有两种方式

  • 尖括号写法
let str: any = "我比电脑聪明";
let strLength: number = (<string>str).length;
  • as写法
let str: any = "我比电脑聪明";
let strLength: number = (str as string).length;

非空断言

在上下文中当类型检查器无法断定类型时,可以使用缀表达式操作符 ! 进行断言操作对象是非 null 和非 undefined 的类型,即x!的值不会为 null 或 undefined

  let user: string | null | undefined;
  console.log(user!.toUpperCase()); // 编译正确
  console.log(user.toUpperCase()); // 错误

确定赋值断言

let value:number
console.log(value); // Variable 'value' is used before being assigned.

我们定义了变量, 没有赋值就使用,则会报错

在某些情况下,我们在申明变量时并不需要赋值,那么通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。

let value!:number
console.log(value); // undefined 编译正确

六、联合类型

联合类型用|分隔,表示取值可以为多种类型中的一种

可以使用 | 来连接多个类型(联合类型)

let status:string|number
status='有的人活着他已经死了,有的人死了他还活着'
status=1

七、类型别名

类型别名用来给一个类型起个新名字。它只是起了一个新名字,并没有创建新类型。类型别名常用于联合类型。

type count = number | number[];
function sum(value: count) {}

八、交叉类型

交叉类型就是跟联合类型相反,用&操作符表示,交叉类型就是两个类型必须存在

interface IpersonA{
  name: string,
  time: string
}
interface IpersonB {
  name: string,
  hobby: string
}
 
let person: IpersonA & IpersonB = { 
    name: "土豆",
    time: '两年半',
    hobby: "rap"
}

person 即是 IpersonA 类型,又是 IpersonB 类型

注意:交叉类型取的多个类型的并集,但是如果key相同但是类型不同,则该key为never类型

interface IpersonA {
    name: string
}
 
interface IpersonB {
    name: number
}
 
function testAndFn(params: IpersonA & IpersonB) {
    console.log(params)
}
 
testAndFn({name: "土豆"}) // error TS2322: Type 'string' is not assignable to type 'never'.

九、类型守卫 类型保护是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内。 换句话说,类型保护可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值。类型保护与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。

换句话说:类型守卫是运行时检查,确保一个值在所要类型的范围内

目前主要有四种的方式来实现类型保护:

  • in关键字
interface InObj1 {
    name: string,
    time: string
}
interface InObj2 {
    name: string,
    hobby: string
}
function isIn(arg: InObj1 | InObj2) {
    // time 在 arg 打印 time
    if ('name' in arg) console.log('time')
    // hobby 在 arg 打印 y
    if ('hobby' in arg) console.log('hobby')
}
isIn({name:'土豆', time:'两年半'});
isIn({name:'土豆', hobby:'rap'});
  • typeof关键字
function isTypeof( val: string | number) {
  if (typeof val === "number") return 'number'
  if (typeof val === "string") return 'string'
  return '啥也不是'
}

typeof 只支持:typeof 'x' === 'typeName' 和 typeof 'x' !== 'typeName',x 必须是 'number', 'string', 'boolean', 'symbol'。

  • instanceof
function createDate(date: Date | string){
    console.log(date)
    if(date instanceof Date){
        date.getDate()
    }else {
        return new Date(date)
    }
}
  • 自定义类型保护的类型谓词
function isNumber(num: any): num is number {
    return typeof num === 'number';
}
function isString(str: any): str is string{
    return typeof str=== 'string';
}

十、接口

我们使用接口来定义对象的类型。接口是对象的状态(属性)和行为(方法)的抽象(描述)

简单理解就是:为我们的代码设定上一种约定

我们使用关键字interface来声明接口

interface Person {
    name: string;
    time: string;
}
let man: Person = {
    name: '土豆',
    time: '两年半'
};

我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 man 的形状必须和接口 Person 一致。

接口一般首字母大写。(当然挺多人也习惯 I 大写字母开头,用来表示这是一个接口)

设置接口可选|只读

interface Person {
  readonly name: string;
  age?: number;
}
  • 可选属性,我们最常见的使用情况是,不确定这个参数是否会传,或者存在。

  • 只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray 类型,它与 Array 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改

索引签名

有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

interface Person {
  name: string;
  age?: number;
  [prop: string]: any; //  prop字段必须是 string类型 or number类型。 值是any类型,也就是任意的
}
 
const p1:Person = { name: "土豆" };
const p2:Person = { name: "土豆", age: 28 };
const p3:Person = { name: "土豆", sex: 1 }

我们规定 以 string 类型的值来索引,索引到的是一个 any 类型的值

接口与类型别名的区别

实际上,在大多数的情况下使用接口类型和类型别名的效果等价,但是在某些特定的场景下这两者还是存在很大区别。

TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型。

type(类型别名)会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型。起别名不会新建一个类型 - 它创建了一个新名字来引用那个类型。给基本类型起别名通常没什么用,尽管可以做为文档的一种形式使用。

接口和类型别名都可以用来描述对象或函数的类型,只是语法不同

type MyTYpe = {
  name: string;
  say(): void;
}
 
interface MyInterface {
  name: string;
  say(): void;
}

都允许扩展

  • interface用extends来扩展
interface MyInterface {
  name: string;
  say(): void;
}
 
interface MyInterface2 extends MyInterface {
  sex: string;
}
 
let person:MyInterface2 = {
  name:'土豆',
  sex:'男',
  say(): void {
    console.log("土豆土豆,我是地瓜!");
  }
}
  • type 使用 & 实现扩展
type MyType = {
  name:string;
  say(): void;
}
type MyType2 = MyType & {
  sex:string;
}
let value: MyType2 = {
  name:'土豆',
  sex:'男',
  say(): void {
    console.log("土豆土豆,我是地瓜!!");
  }
}

不同点

  • type可以声明基本数据类型别名/联合类型/元组等,而interface不行
// 基本类型别名
type UserName = string;
type UserName = string | number;
// 联合类型
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
  • interface能够合并声明,而type不行
interface Person {
  name: string
}
interface Person {
  age: number
}
// 此时Person同时具有name和age属性

十一、泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

举个例子,比如我们现在有个这样的需求,我们要实现一个这样的函数,函数的参数可以是任何值,返回值就是将参数原样返回,也就是给啥反啥,比如说传入string,那返回的自然也要是string

你可能会这样写:

function getValue(arg:string):string  {
  return arg;
}

现在需求有变,需要返回一个 number 类型的值,你会觉得,那我用上面学到的联合类型就可以实现了:

function getValue(arg:string | number):string | number  {
  return arg;
}

没错 万恶的需求又要变了 它可能要我们返回一个 boolean 类型,string 数组甚至任意类型呢,难道有多少个就写多少个联合类型?那多丑啊

你突然灵光一现,是的,我们直接用 any大法 就解决了!

function getValue(arg:any):any  {
  return arg;
}

虽然 any 大法好,很多时候 any 也确实能够解决不少问题,但是这样也不符合我们的需求了(我们需要传啥类型反啥类型),而现在传入和返回都是 any 类型,传入和返回并没有统一

作为无所不能的程序员,我们不能说不行 那试试其他办法?

这个时候就要祭出我们的泛型了

基本使用

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性

上面的需求,我们如果用泛型来解决的话:

function getValue<T>(arg:T):T  {
  return arg;
}

是不是又简约好看还又省事

泛型的语法是尖括号 <> 里面写类型参数,一般用 T 来表示第一个类型变量名称,其实它可以用任何有效名称来代替,比如我们用NIUBI也是编译正常的

泛型就像一个占位符一个变量,在使用的时候我们可以将定义好的类型像参数一样传入,原封不动的输出

使用

我们有两种方式来使用:

  • 我们自己定义要使用的类型,比如:
getValue<string>('土豆土豆'); // 定义 T 为 string 类型
  • 利用 typescript 的类型推断,比如:
getValue('土豆土豆') // 自动推导类型为 string

多个参数

其实并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。比如我们引入一个新的类型变量 U

function getValue<T, U>(arg:[T,U]):[T,U] {
  return arg;
}
 
// 使用
const str = getValue(['土豆', 18]);

image.png typescript 给我们自动推断出输入、返回的类型

泛型约束

在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:

function getLength<T>(arg:T):T  {
  console.log(arg.length); // 报错,不能调用 length 属性
}

因为泛型 T 不一定包含属性 length,那么我想 getLength 这个函数只允许传入包含 length 属性的变量,该怎么做呢

这时,我们可以使用extends关键字来对泛型进行约束

interface Lengthwise {
  length: number;
}
 
function getLength<T extends Lengthwise>(arg:T):T  {
  console.log(arg.length); 
  return arg;
}

泛型接口

在定义接口的时候指定泛型

interface KeyValue<T,U> {
  key: T;
  value: U;
}
 
const person1:KeyValue<string,number> = {
  key: '土豆',
  value: 18
}
const person2:KeyValue<number,string> = {
  key: 20,
  value: '地瓜'
}

泛型类

class Test<T> {
  value: T;
  add: (x: T, y: T) => T;
}
 
let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
  return x + y;
};

泛型类型别名

type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

泛型参数的默认类型

我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。有点 js 里函数默认参数的意思。

function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

泛型工具类型

  • typeof

关键词除了做类型保护,还可以从实现推出类型,

//先定义变量,再定义类型
let p1 = {
  name: "土豆",
  age: 18,
  hobby: "rap",
};
type People = typeof p1;
function getName(p: People): string {
  return p.name;
}
getName(p1);
  • keyof

可以用来获取一个对象接口中的所有 key 值

interface Person {
  name: string;
  age: number;
  hobby: "rap" | "唱跳";
}
 
type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';
 
function getValueByKey(p: Person, key: PersonKey) {
  return p[key];
}
let val = getValueByKey({ name: "土豆", age: 18, hobby: "rap" }, "name");
console.log(val); // 土豆
  • in

用来遍历枚举类型:

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

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any;

infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。

  • extends

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。

interface Lengthwise {
  length: number;
}
 
function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:

loggingIdentity(3);  // Error, number doesn't have a .length property

当我们传入合法的类型的值,即包含 length 属性的值时:

loggingIdentity({length: 10, name: '土豆'}); // 编译正确
  • 索引访问操作符

使用 [] 操作符可以进行索引访问:

interface Person {
  name: string;
  age: number;
}
 
type x = Person["name"]; // x is string

内置工具类型

  • Required

将类型的属性变成必选

interface Person {
    name?: string,
    age?: number,
    hobby?: string[]
}
 
const user: Required<Person> = {
    name: "土豆",
    age: 18,
    hobby: ["rap"]
}
  • Partial

与 Required 相反,将所有属性转换为可选属性

interface Person {
    name: string,
    age: number,
}
 
const shuge:Person = {
  name:'土豆'
} // error  Property 'age' is missing in type '{ name: string; }' but required in type 'Person'.

从上面知道,如果必传而我们少穿传了的话,就会报错

我们使用 Partial 将其变为可选

type User = Partial<Person>
 
const shuge: User={
  name:'土豆'
} // 编译正确
  • Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉,剩余的属性构成新的类型

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
  • Extract

和 Exclude 相反,Extract<T,U> 从 T 中提取出 U。

type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void

适用于:并集类型

  • Readonly

把数组或对象的所有属性值转换为只读的,这就意味着这些属性不能被重新赋值。

interface Person {
  name: string;
  age: number;
  hobby?: "rap" | "jump";
}
 
let p: Readonly<Person> = {
  name: "hello",
  age: 10,
  hobby: "rap",
};
p.age = 11; // error  Cannot assign to 'age' because it is a read-only property.
  • Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。

type Property = 'key1'|'key2'
type Person = Record<Property, string>;
 
const p: Person = {
  key1: "hello 啊",
  key2: "树哥",
};
  • Pick

从某个类型中挑出一些属性出来

type Person = {
  name: string;
  age:number;
  hobby:string
}
 
type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }
 
const user:P1={
  name:'土豆',
  age:18
}
  • Omit

与Pick相反,Omit<T,K> 从T中取出除去K的其他所有属性

interface Person {
  name: string,
  age: number,
  hobby: string
}
type P1 = Omit<Person, "age" | "hobby">
const user:P1  = {
  name: '树哥'
}

十二、tsconfig.json

在文章开头环境安装部分,记得我们有生成一个 tsconfig.json 文件,那么这个文件究竟有什么用呢

tsconfig.json 是 TypeScript 项目的配置文件。

tsconfig.json 包含 TypeScript 编译的相关配置,通过更改编译配置项,我们可以让 TypeScript 编译出 ES6、ES5、node 的代码。

重要字段

  • files - 设置要编译的文件的名称;
  • include - 设置需要进行编译的文件,支持路径模式匹配;
  • exclude - 设置无需进行编译的文件,支持路径模式匹配;
  • compilerOptions - 设置与编译流程相关的选项。
  • compilerOptions 选项

{

/* 基本选项 */
"target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
"module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
"lib": [],                             // 指定要包含在编译中的库文件
"allowJs": true,                       // 允许编译 javascript 文件
"checkJs": true,                       // 报告 javascript 文件中的错误
"jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
"declaration": true,                   // 生成相应的 '.d.ts' 文件
"sourceMap": true,                     // 生成相应的 '.map' 文件
"outFile": "./",                       // 将输出文件合并为一个文件
"outDir": "./",                        // 指定输出目录
"rootDir": "./",                       // 用来控制输出目录结构 --outDir.
"removeComments": true,                // 删除编译后的所有的注释
"noEmit": true,                        // 不生成输出文件
"importHelpers": true,                 // 从 tslib 导入辅助工具函数
"isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

/* 严格的类型检查选项 */
"strict": true,                        // 启用所有严格类型检查选项
"noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true,              // 启用严格的 null 检查
"noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

/* 额外的检查 */
"noUnusedLocals": true,                // 有未使用的变量时,抛出错误
"noUnusedParameters": true,            // 有未使用的参数时,抛出错误
"noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

/* 模块解析选项 */
"moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
"baseUrl": "./",                       // 用于解析非相对模块名称的基目录
"paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
"rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
"typeRoots": [],                       // 包含类型声明的文件列表
"types": [],                           // 需要包含的类型声明文件名列表
"allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

/* Source Map Options */
"sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
"mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
"inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
"inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

/* 其他选项 */
"experimentalDecorators": true,        // 启用装饰器
"emitDecoratorMetadata": true          // 为装饰器提供元数据的支持