标签搜索

Vue3 学习笔记(六)

sunshine
2022-12-24 / 0 评论 / 10 阅读
温馨提示:
本文最后更新于2024年08月27日,已超过86天没有更新,若内容或图片失效,请留言反馈。

为什么使用TS和TS运行环境搭建

TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成。TypeScript通过TypeScript编译器或Babel转译为JavaScript代码,可运行在任何浏览器,任何操作系统。

TS文件需要编译成JS文件

首先我们的浏览器是不认识TS文件的,所以需要把TS编译成JS文件才可以,TS官网提供了一种方式,就是去全局安装typescript这个模块,命令如下:

npm install -g typescript

安装好后,就可以使用 tsc xxx.ts命名把TS文件自动转化成对应的JS文件了,在同级目录下就会生成一个xxx.js的文件,这个文件就是编译后的文件,浏览器是可以认识的。

在TS编译JS文件的时候,会有一些细节,我们一起来看一下:

第一,在tsc命令进行转换操作的时候,是不能实时进行转换的,那么可以通过添加一个-w的参数来完成实时转换的操作

tsc xxx.ts -w

第二,在编译后,我们会发现TS文件中定义的变量会产生错误的波浪线,这主要是因为TS默认是全局环境下的,所以跟其他文件中的同名变量冲突了,那么该如何解决呢?可以把全局环境改成局部的环境,只需要把TS文件变成一个模块化的文件,那么变量就只在模块内起作用,这样就不会产生冲突,从而引发错误的波浪线。

let foo = 123;
export {};

第三,如果不想采用默认的编译方式,那么可以通过修改配置文件来改变一些默认的行为。配置文件需要叫做,tsconfig.json

可以通过tsc --init命令自动化的创建tsconfig.json这个文件,这个文件中的配置选项非常的多,我们会在后面小节中给大家进行详解的讲解,这里先修改其中的一两个配置,来感受一下配置文件的一个作用。

{
    “compilerOptions”: {
        "outDir": "./dist",    //编译后文件输出的位置    
        "module": "ES6",       //转换后模块的风格
        "target": "ES5"        //转换成JS不同语法版本
    },
    "include": ["xxx.ts"]     //只对那些文件进行编译
}

通过命令行输入tsc命令就可以自动去调用tsconfig.json这个文件了,非常的方便。

为什么要使用TS去写程序

TS编写代码的好处诸多,下面我们列出几点:

  • 友好的IDE提示
  • 强制类型,防止报错
  • 语言编写更加严谨
  • 快速查找到拼写错误
  • JS的超集,扩展新功能

下面通过几个小例子来感受一下TS比JS要优秀的点,

let a = 123;
a.map(()=>{})  // error

这段代码在TS中是会报错的,因为a是一个数字,不会有map方法,而JS是不会给我们进行提示的。

let a = 1;
if(a === 1 && a === 2) { // error
}

这段代码在TS中是会报错的,因为从逻辑上来看这段代码并没有意义,因为a同一时间不可能既是1又是2。

let a = {
  username: 'xiaoming',
  age: 20
}

这段代码TS提示会非常的好,不会夹杂一些其他的提示信息,而且一旦单词写错了,TS会有非常好的提示效果。

类型声明空间与变量声明空间

变量声明空间

在JS中我们只有变量声明空间,比如let a = 123。但是在TS中我们不仅存在变量声明空间,而且还存在类型声明空间。下面就让我们一起去探索一下类型声明空间的特点吧。

类型声明空间

可以通过type关键字来定义类型声明空间,type在TS中叫做类型别名。为了能够更好的区分两个空间,所以人为规定类型空间定义的名字首字母要大写,例如:type A = number

这里要注意,不要把两个空间进行互相赋值,会产生错误的,如下:


let a = 'hello';
type B = a;    // error

type A = string;
let a = A;    // error

但是在TS中,有的语法它既是变量声明空间也是类型声明空间,比如:类语法。

class Foo {}  // 类在TS中既是变量声明空间,也是类型声明空间
let a = Foo;  // success
type A = Foo;  // success

那么两个空间之间的关系有是什么呢?下一小节来进行学习。

类型注解与类型推断

上一个小节中,给大家留了一个思考题,那就是如何把两个空间联系到一起?主要采用类型注解来实现的。

类型注解

通过把变量空间与类型空间结合在一起的操作,就叫做类型注解,具体语法是通过冒号连接在一起的。

let a: string = 'hello'

//也可以通过类型别名进行连接
type A = string
let a: A = 'hello'

这样定义的a变量就具备了字符串类型,那么我们尝试给a修改成其他类型就会报错,现在就强制a的类型为字符串了,这也是TS强制类型的特点。

那么我们不去进行类型注解,是不是就不会强制类型了呢?其实也会强制类型的,因为TS可以进行自动类型推断的。

类型推断

TS中会对不进行类型注解的变量,进行自动类型推断的,如下:

// let a: string  ->  类型推断
let a = 'hello'
a = 123;   // error

所以在TS中不管是类型注解还是自动类型推断,对于类型都不能轻易的做修改,这样可以保证变量的类型稳定,从而减少BUG的产生。

类型分类与联合类型与交叉类型

类型分类

在TS中会把类型大致划分为三部分:

  • 基本类型:string number boolean null undefined symbol bigint
  • 对象类型:[] {} function(){}
  • TS新增类型:any never void unknown enum

对于基本类型,我们就稍微演示一下,毕竟都是JS已有的内容:

let a: string = 'hello'
let b: bigint = 1n;

对象类型也稍微演示一下,更具体的对象类型会在后面的小节中进行讲解:

let c: object = [];
c = function(){};   // success

联合类型

在TS中如何能够让一个变量支持多种不同类型呢?那么就可以采用联合类型来实现。

let a: string|number|boolean = 'hello';
a = 123;

在类型中除了有或的操作外,就一定有与的操作,那么在TS中实现类型与的行为叫做,交叉类型。

交叉类型

类型之间进行与的操作,不过一般在对象中进行使用,基本类型很少采用交叉类型。

type A = {
  username: string
}
type B = {
  age: number
}
let a: A&B = { username: 'xiaoming', age: 20 }

这里a变量,必须具备A、B两个共同指定的类型才可以。

never类型与any类型与unknown类型

在本小节中将学习一些TS中提供的新的类型,比如:never类型,any类型,unknown类型。

never类型

never类型表示永不存在的值的类型,当一个值不存在的时候就会被自动类型推断成never类型。

// let a: never ->  不能将类型“number”分配给类型“never”
let a: number & string = 123

在这段代码中a变量要求类型既是数字又是字符串,而值是一个123无法满足类型的需求,所以a会被自动推断成never类型。所以never类型并不常用,只是在出现问题的时候会被自动转成never。

有时候也可以利用never类型的特点,实现一些小技巧应用,例如可以实现判断参数是否都已被使用,代码如下:

function foo(n: 1 | 2 | 3) {
  switch (n) {
    case 1:
      break
    case 2:
      break
    case 3:
      break
    default:
        let m: never = n;  // 检测n是否可以走到这里,看所有值是否全部被使用到
      break
  }
}

any类型和unknown类型

any类型表示任意类型,而unknown类型表示为未知类型,是any类型对应的安全类型。

既然any表示任意类型,那么定义的变量可以随意修改其类型,这样带来的问题就是TS不再进行类型强制,整个使用方式根JS没有任何区别。

let a: any = 'hello';
a = 123;
a = true;
a.map(()=>{})    // success

所以说any类型是TS中的后门,不到万不得已的时候尽量要少用,如果真的有这种需求的话,可以采用any对应的安全类型unknown来进行定义。

let a: unknown = 'hello';
a = 123;
// any不进行检测了,unknown使用的时候,TS默认会进行检测
a.map(()=>{})    // error

unknown类型让程序使用的时候更加严谨,我们必须主动告诉TS,这里是一个什么类型,防止我们产生误操作。那么怎样让unknown类型不产生错误呢?就需要配合类型断言去使用,下一个小节我们一起来学习吧。

类型断言与非空断言

类型断言

类型断言主要用于当 TypeScript 推断出来类型并不满足你的需求,你需要手动指定一个类型。在上一个小节中使用unknown类型的时候,需要手动指定当前是一个什么类型,来满足TS类型的需求检测,那么就可以采用类型断言来实现。

let a: unknown = 'hello';
a = 123;
(a as []).map(()=>{})    // success

这里就不会再报错了,当然类型断言只是告诉TS不要管我们了,这个只是在编译前的处理,就是欺骗TS。而在编译后,a确实不是一个数组,所以运行到浏览器后就会报错,那么我们在使用断言的时候还是要格外的小心,不要滥用断言操作。

非空断言

在类型断言中,也有一些特殊情况,如下:

let b: string|undefined = undefined;
b.length    // error

因为b可能是字符串也可能是undefined,所以b.length的时候就会报错,这样我们可以采用非空断言来告诉TS,这个b肯定不是undefined,所以b只能是字符串,那么b.length就不会报错了。

let b: string|undefined = undefined;
b!.length   // success

总结:类型断言是一种欺骗TS的手段,在编译阶段解决类型问题的,但是最终运行的结果需要开发人员自己负责,所以使用类型断言要严谨,否则最终还会产生报错。

数组类型与元组类型

在前面小节中介绍过对象类型,但是对象类型的范围太广了,本小节将具体讲解对象类型中的数组类型及元组类型。

数组类型

定义数组类型通常用两种写法:

  1. 类型[]
  2. Array<类型>

先看第一种方式,如下:

let arr1: (number|string)[] = [1, 2, 3, 'hello'];

再看第二种方式,如下:

let arr2: Array<number|string> = [1, 2, 3, 'hello'];

第二种写法是一种泛型的写法,这个会在后面章节中进行详解的。

这里数组子项可以是数字也可以是字符串。对于数组子项的顺序以及数组子项的个数并没有限制。那么如何能够对数组的个数以及顺序都做限定类型呢?就可以采用元组类型。

元组类型

元组类型实际上就是数组类型的一种特殊形式,代码如下:

let arr3: [number, string] = [1, 'hello'];

这里会限定数组的每一项的值的类型和值的个数,对于多添加一些子项都是会报错的,属于比较严格的数组形式。

对象类型与索引签名

对象类型

在TS中可以直接对对象字面量进行类型限定,可以精确到具体的字段都具备哪些类型,如下:

type A = {
  username: string
  age: number
}

let a: A = {
  username: 'xiaoming',
  age: 20
}

对于对象类型来说,多出来的字段还是缺少的字段都会产生错误,如下:

type A = {
  username: string
  age: number
}

let a: A = {      // error
  username: 'xiaoming'
}

那么可以给age添加一个可选标识符?来表示age为可选项,写与不写都是可以的。

type A = {
  username: string
  //age是可选项 
  age?: number   
}

let a: A = {      // success
  username: 'xiaoming'
}

索引签名

那么对于多出来的字段,可以通过索引签名方式来解决,如下:

type A = {
  username: string
  //索引签名
  [index: string]: any
}

let a: A = {
  username: 'xiaoming'
  gender: 'male',
  job: 'it'
}

索引签名中的属性也可以指定number类型,不过往往只有数组中会采用这种数字类型的索引签名方式,如下:

type A = {
  [index: number]: any   
}
let a: A = [1, 2, 3, true, 'hello'];

对象类型如果想定义初始值为空值的话,可以采用类型断言来改造,如下:

type Obj = {username: string}
let obj = {} as Obj;    // success

最后来看一下对象和数组组合在一起定义的类型,如下:

let json: {username: string, age: number}[] = [];

函数类型与void类型

函数类型

在TS中对于函数的类型使用方式有很多种限定,先来看一下函数的参数:

function foo(n: number, m?: string): number{
   return 123;
}
foo(123, 'hello');
foo(123);     // success

TS中要求,实参的个数跟形参的个数必须相同,所以可以添加可选链?来实现参数可选操作。

函数还可以通过在函数中定义返回值的类型:number,可以参考上面代码。

下面看一下函数表达式的写法,及类型注解的方式。

let foo: (n: number, m: string) => number = function(n, m){
  return 123;
}

如果在前面进行了类型注解,那么就不用在函数体内进行类型的添加了。

void类型

表示函数没有任何返回值的时候得到的类型。

let foo = function(){   // void
}

当函数没有return的时候返回void类型,当return undefined的时候也可以返回void类型。那么函数定义void类型跟undefined类型也是有区别的,因为undefined 类型是不能不写return的。

let foo = function(): undefined{   // undefined 不能不写return的
}  // error

函数重载与可调用注解

函数重载

函数重载是指函数约束传入不同的参数,返回不同类型的数据,而且可以清晰的知道传入不同的参数得到不同的结果。

假如我们实现这样一个函数,如下:

function foo(n1: number, n2?: number, n3?: number, n4?: number){
}
foo(1);
foo(1, 2);
foo(1, 2, 3);
foo(1, 2, 3, 4);

这样传递几个参数都是可以的,那么我们能不能限制传递几个参数呢?比如只允许传递一个参数,两个参数,四个参数,不允许传递三个参数,这时就可以通过函数重载还实现了。

function foo(n1: number): any
function foo(n1: number, n2: number): any
function foo(n1: number, n2: number, n3: number, n4: number): any
function foo(n1: number, n2?: number, n3?: number, n4?: number){
}
foo(1);
foo(1, 2);
foo(1, 2, 3);    // error
foo(1, 2, 3, 4);

下面再来实现一个需求,要求参数的类型必须保持一致,如下:

function foo(n: number, m: number): any
function foo(n: string, m: string): any
function foo(n: number|string, m: number|string){
}
foo(1, 2);
foo('a', 'b');
foo(3, 'c');    // error

可调用注解

可调用注解提供多种调用签名,用以特殊的函数重载。首先可调用注解跟普通函数的类型注解可以起到同样的作用。

type A = () => void;
type A = {   // 可调用注解,可以针对函数重载进行类型注解的
  (): void
}
let a: A = () => {};

那么可调用注解比普通类型注解优势在哪里呢?就是可以对函数重载做类型注解,代码如下:

type A = {
  (n: number, m: number): any
  (n: string, m: string): any
}
function foo(n: number, m: number): any
function foo(n: string, m: string): any
function foo(n: number|string, m: number|string){
}

枚举类型与const枚举

枚举类型

枚举是组织收集有关联集合的一种方式,使代码更加易于阅读。其实简单来说枚举就是定义一组常量。

enum Roles {
  SUPER_ADMIN,
  ADMIN = 3,
  USER
}
console.log( Roles.SUPER_ADMIN );   // 0
console.log( Roles.ADMIN );    // 3
console.log( Roles.USER );     // 4

枚举默认不给值的情况下,就是一个从0开始的数字,是可以自动进行累加的,当然也可以自己指定数值,后面的数值也是可以累加的。

枚举也支持反向枚举操作,通过数值来找到对应的key属性,这样操作起来会非常的灵活。

enum Roles {
  SUPER_ADMIN,
  ADMIN = 3,
  USER
}
console.log( Roles[0] );    // SUPER_ADMIN
console.log( Roles[3] );    // ADMIN
console.log( Roles[4] );    // USER

枚举给我们的编程带来的好处就是更容易阅读代码,举例如下:

if(role === Roles.SUPER_ADMIN){   // 更容易阅读
}

下面来看一下,如果定义成字符串的话,需要注意一些什么?

enum Roles {
   SUPER_ADMIN = 'super_admin',
   ADMIN = 'admin',
   USER = 'user'
}

字符串形式是没有默认值的,而且不能做反向映射的。

const枚举

在枚举的前面可以添加一个const关键字。

const enum Roles {
  SUPER_ADMIN = 'super_admin',
  ADMIN = 'admin',
  USER = 'user'
}

那么没有const关键字和有const关键字的区别是什么呢?主要区别在于编译的最终结果,const方式最终编译出来的就是一个普通字符串,并不会产生一个对象,更有助于性能的体现。

总结内容

  • 了解了什么是TypeScript,在编程中的优势
  • 如何对TS文件进行编译,以及tsconfig.json的使用
  • 全面掌握TS中的各种概念,如:类型注解、类型断言等
  • 全面掌握TS中常见类型,如:数组、对象、函数等
  • 全面掌握TS中的新类型,如:枚举、元组、any、never等
感觉很棒,欢迎点赞 OR 打赏~
0
分享到:

评论 (0)

取消