TypeScript魔法指南:解锁类型安全的新境界

前言

 📫 大家好,我是南木元元,热爱技术和分享,欢迎大家交流,一起学习进步!

 🍅 个人主页:南木元元


目录

什么是TypeScript

TypeScript的优缺点

动态类型与静态类型

安装TypeScript

TypeScript基础类型

Boolean类型

Number类型

String类型

Null和Undefined类型

Any类型

Array数组类型

Tuple元组类型

Enum枚举类型

Interface接口

函数

类型推论

联合类型

类型断言

类型守卫

泛型

基本使用

泛型约束

泛型类

泛型接口

类型别名

交叉类型

结语


什么是TypeScript

TypeScript(简称 TS)是微软公司开发的一种基于JavaScript 语言的编程语言。它是JavaScript 的超集,其目的是增强 JavaScript的功能,本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。

TypeScript的优缺点

优点:

  • 增强代码的可维护性,尤其在大型项目的时候效果显著
  • 提供了一套静态类型检查机制 ,编译阶段就能检查类型发现错误
  • 周边生态繁荣,vue3已全面支持 typescript

缺点:

  • 增加一些学习成本
  • 增加前期开发的成本,毕竟需要写更多的代码

动态类型与静态类型

TypeScript属于静态类型语言。那么什么静态类型,什么是动态类型呢?

动态类型语言:运行时才做数据检查的语言,如JavaScript、Ruby、Python等。

静态类型语言:编译阶段进行数据类型检查,如C、C++、C#、Java等。

安装TypeScript

TypeScript官网地址:https://www.typescriptlang.org/zh/

使用nvm来管理Node版本:https://github.com/nvm-sh/nvm

全局安装TypeScript:

npm install -g  typescript

使用tsc全局命令:

// 查看tsc版本
tsc -v
// 编译ts文件
tsc fileName.ts

新建一个test.ts文件,随便写点什么:

const hello = (name: string) => {
    return `hello ${name}`
}
hello('南木元元')

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

var hello = function (name) {
    return "hello ".concat(name);
};
hello('南木元元');

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

TypeScript基础类型

Boolean类型

let flag: boolean = false;

Number类型

let age: number = 18;

String类型

let name: string = '元元'

Null和Undefined类型

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

let u: undefined = undefined;
let n: null = null;
// 可以将undefined赋值给number类型
let num: number = undefined;

Any类型

在 TypeScript 中,任何值都可以赋值给any类型。

let notSure: any = 369;
notSure = 'yuanyuan';
notSure = true;

Array数组类型

//定义一个数字类型的数组
let arr: number[] = [1,2,3];
arr.push(4);

Tuple元组类型

数组类型中的值只能是同种类型,如果想要在一个数组中使用不同类型的值,就可以使用元组。

//定义一个长度为2的元组
let user: [string, number] = ['yuanyuan', 18];

元组的长度是固定的,并且类型必须对应。

Enum枚举类型

使用枚举我们可以定义一些带名字的常量,可以清晰地表达意图,如周一到周日,上下左右等。

  • 数字枚举
enum Direction {
    Up,
    Down,
    Left,
    Right,
}

console.log(Direction.Up) //0

Up初始值默认为 0, 其余的成员会会按顺序自动增长。

  • 字符串枚举
enum Direction {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT',
}

console.log(Direction.Up) //UP

//用法示例:比如后端返回一个值'UP',就可以去做相应的事
let value = 'UP' 
if(value === Direction.Up) {
    //do something
}
  • 常量枚举

使用 const 关键字修饰的枚举,常量枚举会在编译阶段被删除,提升性能。

普通枚举编译后的结果:

enum Direction {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT',
}
let value: string = 'UP' 
if(value === Direction.Up) {
    //do something
}

// 编译后的结果
var Direction;
(function (Direction) {
    Direction["Up"] = "UP";
    Direction["Down"] = "DOWN";
    Direction["Left"] = "LEFT";
    Direction["Right"] = "RIGHT";
})(Direction || (Direction = {}));
var value = 'UP';
if (value === Direction.Up) {
    //do something
}

常量枚举编译后的结果:

//常量枚举
const enum Direction {
    Up = 'UP',
    Down = 'DOWN',
    Left = 'LEFT',
    Right = 'RIGHT',
}
let value: string = 'UP' 
if(value === Direction.Up) {
    //do something
}

// 编译后的结果
var value = 'UP';
if (value === "UP" /* Direction.Up */) {
    //do something
}

Interface接口

interface的作用是对对象的形状进行描述,定义的变量比接口少或多一些属性都是不允许的。

//使用interface声明接口Person
interface Person {
    name: string;
    age: number;
};
//约束了 变量yuanyuan 的形状必须和接口 Person 保持一致
let yuanyuan: Person = {
    name: 'yuanyuan',
    age: 18
};

设置可选 | 只读属性:

  • 可选属性:接口里的属性不全都是必需的,只在某些条件下存在。
  • 只读属性:只读属性只能在对象刚刚创建的时候修改其值。
interface Person {
  readonly id: number; //只读属性, 属性名前用readonly来指定
  age?: number;    //可选属性, 在可选属性名字定义的后面加一个?
};

let yuanyuan: Person = {
    id: 1
};

yuanyuan.id = 66; //会报错,不能修改只读属性

函数

  • 函数声明
function add(x: number, y: number): number {
    return x + y;
}
  • 函数表达式
const add = (x: number, y: number): number => {
    return x + y;
}
  • 接口定义函数
interface Add {
    (x: number, y: number): number
}
let add2: Add = add;

类型推论

TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型。

let x = 3;
x = true; // 报错

变量x的类型被推断为数字。

联合类型

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

let numberOrString: number | string
numberOrString = "yuayuan"
numberOrString = 18

类型断言

某些情况下,我们可能比typescript更加清楚的知道某个变量的类型。

这时可以通过类型断言这种方式手动指定一个值的类型,即告诉编译器“我很清楚自己在干什么”。

function getLength(input: string | number): number {
    const str = input as string //类型断言
    if(str.length) {
        return str.length;
    } else {
        const number = input as number
        return number.toString().length
    }
}

上述代码要返回联合类型input的长度,由于number是没有length方法的,所以可以使用类型断言来做处理。

类型守卫

类型守卫可以在运行时用于检查和过滤变量类型,通常用于条件语句中,来检查变量的类型并相应地执行不同的逻辑。常用的类型守卫有typeof和instanceof。

使用类型守卫来改写上述代码:

function getLength2(input: string | number): number {
    if(typeof input === 'string') {
        return input.length;
    } else {
        return input.toString().length;
    }
}

泛型

现在要实现一个函数,函数的参数可以是任何值,返回值就是将参数原样返回。

function echo(arg: number): number {
    return arg
}

上面的函数只能传入number类型的参数,返回类型也为number,但如果传入的是string、boolean就不能满足了。

是的,可以直接使用any,但使用 any 的话,怎么写都是 ok 的, 这就丧失了类型检查的效果。

function echo(arg: any): any {
    return arg
}

const res: string = echo(123)

上述代码中,传入并返回number类型的值,但是使用string类型的参数来接受,由于使用的是any,编译器不会报错,这就会导致问题。

这时就可以使用泛型。

基本使用

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

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

定义函数时使用尖括号<>,里面添加一个类型变量T,T用于表示类型而不是值,只有在调用的时候才会确定类型,这样就能使得参数类型与返回值类型是相同的了。

使用泛型后,上面的BUG就会被修复。

了解了泛型后,现在如果要交换元组中的两个任意类型值的位置并返回,可以这么写:

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

const res2 = swap(['string', 123]) //已经交换了位置

可以看到,两个值就已经交换了位置。 

泛型约束

下面代码会报错,因为编译器并不能证明每种类型都有length属性。

function getLength<T>(arg: T): T {
    console.log(arg.length) //报错,类型T上不存在属性length
    return arg
}

这时,可以定义一个接口来描述约束条件。 创建一个包含 .length属性的接口,使用这个接口和extends关键字来实现约束。

interface Lengthwise {
    length: number
}

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

使用:

const str = getLength('str')
const obj = getLength({length: 10})
const arr = getLength([1, 2, 3])

这里可以看出,不管你是 str,obj还是arr ,只要具有 length 属性,都是可以的。

泛型类

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;
};

泛型接口

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

interface KeyValue<T, U> {
  key: T;
  value: U;
}

const person1: KeyValue<string, number> = {key: 'yuan', value: 18}
const person2: KeyValue<number, string> = {key: 18, value: 'yuan'}

类型别名

类型别名用来给一个类型起个新名字。

type Message = string | string[];

let greet = (message: Message) => {
  // ...
};

交叉类型

交叉类型就是跟联合类型相反,用&操作符表示,交叉类型就是两个类型必须存在,常用于将多个接口类型合并成一个类型。

interface personA {
  name: string;
  age: number;
}
interface personB {
  name: string;
  gender: string;
}

let person: personA & personB = {name: "yuan", age: 18, gender: "男"};

结语

🔥如果此文对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下博主~ 

  • 142
    点赞
  • 129
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 127
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 127
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南木元元

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

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

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

打赏作者

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

抵扣说明:

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

余额充值