Ts 技巧篇
这篇文章会持续更新,遇到了便会更新进来。
在学习过任何新的东西后,我们会因为缺乏一些技巧而不能熟练的去处理种种状况。所以这篇文章就来为大家分享一些不同场景下处理不同状况的小技巧。
在开始之前,因为或许有一部分人有需要手动尝试的需求,所以我们文章的最后一起来搭建ts的运行环境。如果是为了快速得到结果验证,推荐使用Ts官方提供的PlayGround。
现在我们先直接进入正题。
Ts 小技巧
!非空断言:
确定目标一定存在值。
function printMsg(msg?: string) {
console.log(msg.toUpperCase()); // 'msg' is possibly 'undefined'.
// console.log(msg!.toUpperCase()); // msg: string | undefined
}
printMsg("非空断言 !");
我们可以看到,在不使用非空断言的情况下 Ts 会给我们报出一个错误:'msg' is possibly 'undefined'.
就是说我们传进来的参数可能是 undefined。所以此时就可以使用非空断言,告诉 Ts 编译器:我们很确定它是有值的,从而跳过 Ts 编译阶段的检查,这样报错就不在了。
?. 可选链:
当对象的属性不存在时,会短路返回undefined; 如果存在,那么才会继续执行。 可选链其实我们在 Js 中也有去使用它 (ES11(ES2020)中增加的特性)。
type Peopel = {
name: string;
friend?: {
name: string;
age?: number;
};
};
const kaka: Peopel = {
name: "kaka",
friend: {
name: "lili",
},
};
console.log(kaka.friend?.name); // lili
console.log(kaka.friend?.age); // undefined
我们在访问朋友的姓名时,由于朋友的姓名确实存在,所以我们拿到了姓名并输出了:lili ;当我们想继续获取朋友的年纪的时候,由于我们并未设置年龄,所以可以看到返回的确实是一个 undefined。
?? 空值合并操作符:
类似于我们常用的三目运算符 (ES11增加的新特性) ,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。
按照它的定位,这就可以与我们的可选链搭配使用了:
// 依旧是上边的这个案例
console.log(kaka.friend?.name); // lili
console.log(kaka.friend?.age ?? 20); // 20
此时,我们通过与可选链操作符的搭配,便可以在左侧为 null或者 undefined 的情况下,拿到我们给到的默认值,所以可以看到控制台返回的是:20;
!!: 这个就简单说一下,代表的是取反再取反,类似于我们 Js 中的 Boolean()。
as 类型断言:
类型断言其实在前面的章节有说过,但在这里我们将它再拿出来说一说。
有时候我们会遇到这样的情况:我们会比Ts更加了解我们所要定义的某个值(通常发生在我们清楚的知道一个实体具有比它现有类型更确切的类型)。
通过类型断言的方式可以告知编译器,"我很清楚我要做什么",它会假设开发者已经进行了必要的检查。类型断言好比其他语言中的类型转换,但不会进行特殊的数据检查和结构。不受运行时的影响,只作用于编译阶段。
类型断言的两个用法:
- as 语法:
let str: string = (val as string) + "!"
- <> 语法:
let str: string = (<string>val) + "!"
两者是等价的,具体的采用取决于开发者。来看一个今天恰好在群里遇到的一个问题:
A 想抽离一个方法创建画布的方法,但是他在写的过程中发现出错了:类型“HTMLElement”缺少类型“HTMLCanvasElement”的以下属性...
其实我们可以看到,A 所创建的画布的类型并不是他所希望的,导致出现问题的原型是 Ts 类型解析不够精准(真的吗?),我们不要去管到底是不是推断的问题,继续按照思路进行类型断言,使它变为我们希望的类型:
哎! 好像确实成功了,我们将画布的类型断言成我们希望的类型,这不就解决了么?
报错也不在了:
事实上,这确实解决了这个问题,而这就是我们从刚刚到现在一直想说的:通过类型断言的方式可以告知编译器,"我很清楚我要做什么。",
但是我们不仅仅是为了学习断言,更多的是为了在学习过程中解决问题。细心的你们一定发现了:一开始的代码中 width 和 height 怎么能说 string 类型呢?最重要的是我们创建的元素,怎么会是 stirng 类型?离了大谱了就。
其实在一开始,报错的原因就是因为类型定义的错误导致的问题,而我们也稀里糊涂的用断言的方式去“纠正”了,使其做到了我们的期望。那我们可以很容易的修正宽和高的参数类型,那元素呢?
其实 TagName 的标准语法已经给了我们应该以什么类型进行约束:
然后,我们来修改函数逻辑:
最后我们再来看:
这才是我们在解决这个问题本身应该真正要做的。我们在对类型的约束上,以及断言的使用上应该是,确保不存在低级错误的情况下进行的,从而避免像这样的乌龙产生,所以我们在上边提到的真的是 Ts 类型解析不够精准? 是错误的。
那我们到底应该在什么情况下使用类型断言呢?什么时候不用呢?
我们应该在 Ts 自己猜测类型,进行类型推断并不精确的情况下,尽可能多的去使用类型断言。其实我们在上边的做法是可以的,只不过并不是 Ts 推导精确与否的问题,而是错误的将元素定义为一个错误的类型,我们又通过断言的方式去纠正了它。
那什么时候不用呢:就是开发者来告诉 Ts 类型,通过类型注解的方式。我们什么时候使用类型注解呢?
类型注解:
- 变量声明和变量初始化并不是一次性完成的:
// num: number
let num: number;
num = 2;
// count: any
let count;
count = 2
会发现,当变量声明和变量初始化并不是一次性完成的时候,我们无法通过 Ts 推断出变量的正确类型。
- 当一个函数返回 any 类型,但我们要的是一个具体类型:
const json = '{"name": "kaka", "age": 25}';
let j = JSON.parse(json);
console.log(j);
此时的 j 是 any 类型的:
那我们便可以使用类型注解了:
let j: { name: string; age: number } = JSON.parse(json);
这样我们就做到了类型的规范了。
- 当我们想要一个变量拥有一个不能推断出来的类型:
let numbers = [-1, 0, 1];
let mark = true;
for (let i = 0; i < numbers.length; i++) {
const element = numbers[i];
if (element > 0) mark = element;
}
合理的来说 我们并不知道经过判断后我们可能对数组元素进行编辑使得类型出现的元素具体是什么,而我们又需要将值保存到外部,会发现类型匹配错误了:
这时就又需要我们来进行类型注解了, 由于经过判断后我们可能对数组元素进行编辑使得类型出现不确定性,所以可以使用联合类型:
let mark: number | string | boolean = true;
这样我们就可以解决在判断内部在开发过程中使得类型不稳定的问题了,这将不会出现类型错误。
最后,我们在这里来把一开始的环境搭建问题来解决一下:
环境搭建
我们知道可以通过 node index.js
的方式,在node环境来执行我们的 Js 文件。同样,我们可以为 Ts 文件配置这样的执行环境。
首先我们需要安装并验证如下环境:
- 保证我们本地上有 node 环境,通过查看版本号查看是否存在或者安装成功:
node -v
,查看 npm:npm -v
; - 为了使得我们可以像 .js文件一样在node环境验证逻辑,需要安装 Ts 执行环境和 .ts文件的编译执行包:
npm install -g typescript ts-node
,通过指令tsc -h
来查看是否成功; - 初始化 Ts 配置文件 tsconfig.json:
tsc --init
;
将以上环境配置完成后,我们便可以通过 ts-node index.ts
的方式来执行 .ts文件了。这里简单介绍一下 ts-node 这个包,它可以帮助我们将 .ts文件进行编译 -> js文件 -> 运行js文件。
补充
在这里我们再补充一点关于 as 的一些东西,由于在前文是一个完整的案例,所以就将这一部分内容放在这里。
多态:
class Person {}
class Per extends Person { // 继承
say(){
return 'lili'
}
}
const p = new Per()
function sayName(p: Per){
(p as per).say() // 多态
}
// 传入实例p p一定会是 Person
sayName(p)
字面量推断:
const info = {
url: "https://baidu.com",
method: "GET"
}
function request(url: string, method: "GET" | "POST"){
console.log(url, method)
}
request(info.url, info.method)
// info.method: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
上面代码最后一行会报错,这是因为我们的info对象在进行字面量推理的时候,info.method其实是string类型的,但是request函数的method参数是字面量类型的,我们没办法将一个string类型赋值给一个字面量类型,所以会报错。
方案一:通过我们在上边案例中的方式,将类型进行 断言
type Method = 'GET' | 'POST'
function request(url: string, method: Method) {}
const options = {
url: "https://www.coderwhy.org/abc",
method: "POST"
}
request(options.url, options.method as Method)
最后,我们将 method 转成更具体的类型: 在第一章中我们知道 字符串也可以作为类型使用。
方案二:as const 通过 const 将宽泛的类型推断成具体的字面量
type Method = 'GET' | 'POST'
function request(url: string, method: Method) {}
const options = {
url: "https://www.coderwhy.org/abc",
method: "POST"
} as const //通过 as const将宽泛的类型推断成字面量类型
request(options.url, options.method)
方案三:为参数定义对象类型
type Method = 'GET' | 'POST'
function request(url: string, method: Method) {}
//定义参数对象类型
type Request = {
url: string,
method: Method
}
const options: Request = {
url: "https://www.baidu.com",
method: "POST"
}
request(options.url, options.method)
第三方法其实是我们在开发中最常使用的方法,但无论是哪种方法都可以解决我们面临这种场景的问题。
总结:
会发现在最后的案例中,每一种过的解决方案也都脱离不开类型注解,所以合理搭配每一种语法规则,会让我们在问题的处理上事半功倍。
我们要知道:
- as const 通过 const 可以将宽泛的类型推断成具体的字面量。
- 我们到底应该在什么情况下使用类型断言,什么时候不用,什么时候使用类型注解。
- 非空断言、可选链、空值合并等等技巧的使用。
最后想说:在这个互联网不景气的时候,不要因为一些信息影响自己。淘汰的永远都是踏步不前的,所以我们一起加油吧!
转载自:https://juejin.cn/post/7204809985548648485