打怪升级之《Typescript全面进阶指南——理解原始类型和对象类型01》

前言

在当前形势下,相信很多人和我一样只是了解TypeScript,知道TypeScript是JavaScript的超集,知道TypeScript的意义在于限制各种声明类型和传参类型,但是当我们想要深入的去使用在我们的项目中的时候却手足无措,有很多的紧急的情况下我们不得不用any这样的类型来方便我们编程代码。

确实如此,由于各种限制导致损失了JavaScript的灵活性和自由性,所以TypeScript事实上是一种反人类性的存在,现在已经有非常多的人开始讨厌使用TS编程,我也如此。不过不可否认的是很多项目都是以TS为基础来开发的。因为它确实可以避免非常多由于自由、灵活开发而导致的系统崩溃。诸如一些undefined的之类的危险。

所以,TS的限制就像一个圈(帮助我们排除意外),开发者如果把自己变成一个点(精通),那么我们就可以游刃有余的使用TS 并且夸赞它的美

从现在开始学起!

原始类型的类型标注

TypeScript顾名思义就是通过强类型(Type)来限制开发者不要随意的改变一个已声明的变量或参数等。。。的类型。 那么在JavaScript中内置的原始类型我们都知道有 number / string / boolean / undefined / null , 在后期又引入了symbol 和 bigint

在TypeScript中他们的类型注解如下:

ts 复制代码
const stringConst:string = "hello world"
const numberConst:number = 28
const booleanConst:boolean = true
const nullConst:null = null
const undefinedConst:undefined = undefined
const symbolConst:symbol = Symbol("unique")
const bigintConst:bigint = 99999999999

其中 null 与 undefined 应该是经常折磨一些刚写TypeScript代码的开发 所以我们拉出来单独聊聊

null 和 undefied

null代表的是"这里有值但是是一个空值 ",undefined代表的是"这里没有值 ",那么在类型的维度上来看,它们都说是有"具体意义的类型"。在JS中我们可能经常会忽略掉,在TS中 如果两者没有开启strictNullChecks的检查下,会被视作其他类型的子类型也就是不会报错,也就是隐式具有这个类型

下面我们来接着看 void 这个TypeScript的特殊类型

void

在Javascript中void是viod expression的语法。viod操作符会执行后面的表达式并返回一个undefined。我们可以用void来触发一个立即执行函数

ts 复制代码
void function func(){
console.log("hello world")
})()

能这样使用void是因为void操作符强制将后面的函数声明转化为了表达式,因此实际上是执行了 void((function func(){})()),那么在TypeScript的原始类型中也有void, 但是不同于JavaScript的是,这里的void用于描述一个内部没有return的语句,或者没有显示return一个值的函数的返回值

ts 复制代码
function func1() {}
function func2() {
  return;
}
function func3() {
  return undefined;
}

在开启strictNullChecks的情况下,func1和func2的返回值都会被推倒成void,而第三个会被推倒为undefined,这里就解释了,在TypeScript中undefined和null会被认为是一个具有意义的实际类型,而void是返回空值。不过这三个的返回值都会是undefined

数组类型

ts 复制代码
const arr1: string[] = [];

const arr2: Array<string> = [];

这两种方式来声明类型的意思是相等的,不过有些时候使用元组(Tuple)来替代数组是更好的方式。如果这样做,一个数组的里面的值就只能存放固定长度

ts 复制代码
const arr3: [number, number, number] = [2, 3, 4];

console.log(arr3[428]); //不会报异常

const arr4: [number, number, number] = [2, 3, 4];

console.log(arr4[428]);  // 长度为"3"的元组类型"[number, number, number]"在索引"428"处没有元素

那么通过上面的写法,我们可以衍生出数组中存在不同类型的值

ts 复制代码
const arr5: [number, string, boolean] = [2, '3', false];

如果不确定数组中是否含有后面的数值可以

ts 复制代码
const arr6: [number, string?, boolean?] = [2];

在strictNullCheckes开启情况下,可选成员会被标记为 xxx | undefined,长度也会变成 1 | 2 | 3 如何提高元组的可读性?[string,number,boolean] 可能只知道类型,但是并不知道具体是要代表什么的那么我们可以这样做

ts 复制代码
const arr7: [name: string, age: number, male: boolean] = ['1', 2, true];

这样使用元组可以帮助我们进一步提升数组结构的严谨性,虽然这对很多开发者会觉得限制的太严格了会很不舒服

对象的类型

对象类型应该是我们在开发中接触最多的数据结构,很多时候TypeScript警告都是来自于对象属性的不全,或者说对象内部的值的属性存在异常而导致的。使用对象类型会有很多的误区,所以我们一定要学会如何在TypeScript中声明对象类型

在声明对象类型时我们一般使用 interface 关键词,它可以理解为时对象对外提供的接口结构:

ts 复制代码
interface IDescription {
  name: string;
  age: number;
  male: boolean;
}

const obj1: IDescription = {
  name: '1',
  age: 2,
  marry: true,
};

在对象的接口描述中,属性的值必须对应到接口的属性类型

不可以多或少类型 针对于对象内部的属性我们还可以进行修饰,包括可选和只读(Readonly)

修饰接口属性

ts 复制代码
interface IDescription {
  readonly name: string; //只读
  age: number;
  male?: boolean;   //可选
  func?: Function;  //可选
}

const obj2: IDescription = {
  name: '1',
  age: 2,
  male: true,
};
obj2.name = '3' //无法分配到 "name" ,因为它是只读属性

当我们使用可选类型的时候比如male ,它的类型将隐式的变成boolean|undefined,如果这时我们使用obj2.func()就会发生类型报错不能调用可能是未定义的方法

当我们使用只读类型的时候比如name,就会起到防止对象的属性被再次赋值的作用

那么在元组中我们也可以标记整个数组为只读,一旦被标记为只读,那些改变原数组的操作方法 都会不再具有,会报比如类型 xxx 上不存在属性"push"这种

type 与 interface

一般来说interface (接口)用来描述对象、type(类型别名)用来将一个函数签名、一组联合类型、一个工具类型等抽离成一个完整独立的类型,这样比较容易区分

object、Object、和{}

首先是Object,首先要明白JavaScript万物皆对象,原型链的顶端就是Object,这也意味着Object可以是任何类型

ts 复制代码
// 对于 undefined、null、void 0 ,需要关闭 strictNullChecks
const tmp1: Object = undefined;
const tmp2: Object = null;
const tmp3: Object = void 0;

const tmp4: Object = '1';
const tmp5: Object = 2;
const tmp6: Object = { name: '3' };
const tmp7: Object = () => {};
const tmp8: Object = [];

Boolean、Number、String。。。,都属于装箱类型,包含了很多不在预期内的类型,以String为例,它还会包括

ts 复制代码
undefined、null、void以及string
const tmp9: String = undefined;
const tmp10: String = null;
const tmp11: String = void 0;
const tmp12: String = '12';

// 以下不成立,因为不是字符串类型的拆箱类型
const tmp13: String = 15; // X
const tmp14: String = { name: '14' }; // X
const tmp15: String = () => {}; // X
const tmp16: String = []; // X

所以我们要尽力去避免使用装箱类型

object实际上就是解决了对Object的装箱类型,它代表所有非原始类型的类型,数组、对象与函数

ts 复制代码
const tmp20: object = false;  // X 不成立,值为原始类型
const tmp21: object = 599; // X 不成立,值为原始类型

最后{},可以认为{}就是一个字面量类型,内部无属性定义的空对象,既然是内部无属性定义的空对象那么就无法对这个变量进行赋值操作

ts 复制代码
const tmp22: {} = { name: '22' };

tmp22.age = 18; // X 类型"{}"上不存在属性"age"。

总结一下就是

  1. 任何时候都不要使用Object等装箱类型
  2. 当确定要使用的不是原始类型的时候 可以使用Object但是要做进步的区分
  3. 避免使用{}。{}意味着任何非 null / undefined 的值,从这个层面上看,使用它和使用 any 一样恶劣。
相关推荐
卸任3 小时前
Vibe coding工具使用心得
前端·ai编程
brien93 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance4 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M4 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写4 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李4 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易4 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
风骏时光牛马4 小时前
前沿视界
前端
kyriewen4 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员
陈随易5 小时前
3000 万人在用的翻译插件,重写一遍就是一门生意
前端·后端·程序员