Typescript 最佳实践
文章列表:
为了更好的阅读体验, 可以看.
一年前刚接触 Typescript 的时候, 觉得它加大了代码工作量. 写一大堆东西.为了找某个类型东奔西跑, 引入第三库还经常报错.
然而现在的我想说: 真香.
我们经常吐槽别人代码可维护性特别低, 总是希望别人能够主动的写注释, 可是写注释却没有任何方式可以进行约束. 这下好了, 类型就是最好的注释, 用 Typescript, 可以大大提高代码的可维护性.
一. 如何处理第三方库类型相关问题
Typescipt 所提供的第三方库类型定义不仅约束我们的输入调用, 还能为我们提供文档. 现在, NPM 上的第三方类型定义种类繁多,很难保证类型定义是正确的. 也很难保证所有使用的第三方库都有类型定义.
那么, 在这个充满未知的过程中,如何才能正确使用TypeScript中的第三方库呢?
下面列举了四种常见的无法正常工作的场景以及对应的解决方法:
库本身没有自带类型定义
库本身没有类型定义, 也没有相关的@type
类型声明库有误
类型声明报错
1. 库本身没有自带类型定义
查找不到相关的库类型. 举个栗子
在初次将 react 改造支持 typescript 时, 想必很多人都会遇到 module.hot 报错. 此时只需要安装对应的类型库即可.
安装 @types/webpack-env
2. 库本身没有类型定义, 也没有相关的@type
那只能自己声明一个了. 随便举个栗子.
declare module "lodash"
3. 类型声明库有误
推动解决官方类型定义的问题, 提issue, pr
Import 后通过 extends 或者 merge 能力对原类型进行扩展
忍受类型的丢失或不可靠性
使用 // @ts-ignore 忽略
4. 类型声明报错
在 compilerOptions 的添加"skipLibCheck": true, 曲线救国
二. 巧用类型收缩解决报错下面列举了几种常见的解决方法:
类型断言
类型守卫 typeof in instanceof 字面量类型保护
双重断言
1、 类型断言
类型断言可以明确的告诉 TypeScript 值的详细类型,
在某些场景, 我们非常确认它的类型, 即使与 typescript 推断出来的类型不一致. 那我们可以使用类型断言.
语法如下:
<类型>值
值 as 类型 // 推荐使用这种语法. 因为<>容易跟泛型, react 中的语法起冲突
举个例子, 如下代码, padding 值可以是 string , 也可以是 number, 虽然在代码里面写了 Array(), 我们明确的知道, padding 会被parseint 转换成 number 类型, 但类型定义依然会报错.
function padLeft(value: string, padding: string | number) {
// 报错: Operator '+' cannot be applied to
// types 'string | number' and 'number'
return Array(padding + 1).join(" ") + value;
}
解决方法, 使用类型断言. 告诉 typescript 这里我确认它是 number 类型, 忽略报错.
function padLeft(value: string, padding: string | number) {
// 正常
return Array(padding as number + 1).join(" ") + value;
}
但是如果有下面这种情况, 我们要写很多个 as 么?
function padLeft(value: string, padding: string | number) {
console.log((padding as number) + 3);
console.log((padding as number) + 2);
console.log((padding as number) + 5);
return Array((padding as number) + 1).join(' ') + value;
}
2、 类型守卫
类型守卫有以下几种方式, 简单的概括以下
typeof: 用于判断 "number","string","boolean"或 "symbol" 四种类型.
instanceof : 用于判断一个实例是否属于某个类
in: 用于判断一个属性/方法是否属于某个对象
字面量类型保护
上面的例子中, 是 string | number 类型, 因此使用 typeof 来进行类型守卫. 例子如下:
function padLeft(value: string,padding: string | number) {
if (typeof padding === 'number') {
console.log(padding + 3); //正常
console.log(padding + 2); //正常
console.log(padding + 5); //正常
//正常
return Array(padding + 1).join(' ') value;
}
if (typeof padding === 'string') {
return padding + value;
}
}
相比较 类型断言 as , 省去了大量代码. 除了 typeof , 我们还有几种方式, 下面一一举例子.
instanceof :用于判断一个实例是否属于某个类
class Man {
handsome = 'handsome';
}
class Woman {
beautiful = 'beautiful';
}
function Human(arg: Man | Woman) {
if (arg instanceof Man) {
console.log(arg.handsome);
console.log(arg.beautiful); // error
} else {
// 这一块中一定是 Woman
console.log(arg.beautiful);
}
}
in : 用于判断一个属性/方法是否属于某个对象
interface B {
b: string;
}
interface A {
a: string;
}
function foo(x: A | B) {
if ('a' in x) {
return x.a;
}
return x.b;
}
字面量类型保护
有些场景, 使用 in, instanceof, typeof 太过麻烦. 这时候可以自己构造一个字面量类型.
type Man = {
handsome: 'handsome';
type: 'man';
};
type Woman = {
beautiful: 'beautiful';
type: 'woman';
};
function Human(arg: Man | Woman) {
if (arg.type === 'man') {
console.log(arg.handsome);
console.log(arg.beautiful); // error
} else {
// 这一块中一定是 Woman
console.log(arg.beautiful);
}
}
3、双重断言
有些时候使用 as 也会报错,因为 as 断言的时候也不是毫无条件的. 它只有当S类型是T类型的子集,或者T类型是S类型的子集时,S能被成功断言成T.
所以面对这种情况, 只想暴力解决问题的情况, 可以使用双重断言.
function handler(event: Event) {
const element = event as HTMLElement;
// Error: 'Event' 和 'HTMLElement'
中的任何一个都不能赋值给另外一个
}
如果你仍然想使用那个类型,你可以使用双重断言。首先断言成兼容所有类型的any
function handler(event: Event) {
const element = (event as any) as HTMLElement;
// 正常
}
三. 巧用 typescript 支持的 js 最新特性优化代码
1. 可选链 Optional Chaining
let x = foo?.bar.baz();
typescript 中的实现如下:
var _a;
let x = (_a = foo) === null ||
_a === void 0 ? void 0 : _a.bar.baz();
利用这个特性, 我们可以省去写很多恶心的 a && a.b && a.b.c 这样的代码
2. 空值联合 Nullish Coalescing
let x = foo ?? '22';
typescript 中的实现如下:
let x = (foo !== null && foo !== void 0 ?
foo : '22');
四. 巧用高级类型灵活处理数据typescript 提供了一些很不错的工具函数. 如下图
类型索引
为了实现上面的工具函数, 我们需要先了解以下几个语法:
keyof : 获取类型上的 key 值
extends : 泛型里面的约束
T[K] : 获取对象 T 相应 K 的元素类型
type Partial<T> = {
[P in keyof T]?: T[P]
}
在使用 props 的时候, 有时候全部属性都是可选的, 如果一个一个属性写 ? , 大量的重复动作. 这种时候可以直接使用 Partial<State>
Record 作为一个特别灵活的工具. 第一个泛型传入对象的key值, 第二个传入 对象的属性值.
type Record<K extends string, T> = {
[P in K]: T;
}
我们看一下下面的这个对象, 你会怎么用 ts 声明它?
const AnimalMap = {
cat: { name: '猫', title: 'cat' },
dog: { name: '狗', title: 'dog' },
frog: { name: '蛙', title: 'wa' },
};
此时用 Record 即可.
type AnimalType = 'cat' | 'dog' | 'frog';
interface AnimalDescription {
name: string, title: string
}
const AnimalMap:
Record<AnimalType, AnimalDescription> = {
cat: { name: '猫', title: 'cat' },
dog: { name: '狗', title: 'dog' },
frog: { name: '蛙', title: 'wa' },
};
never, 构造条件类型
除了上面的几个语法. 我们还可以用 never , 构造条件类型来组合出更灵活的类型定义.
语法:
never: 从未出现的值的类型
// 如果 T 是 U 的子类型的话,那么就会返回 X,否则返回 Y
构造条件类型 : T extends U ? X : Y
type Exclude<T, U> = T extends U ? never : T;
// 相当于: type A = 'a'
type A = Exclude<'x' | 'a', 'x' | 'y' | 'z'>
更简洁的修饰符: - 与 +
可以直接去除 ? 将所有对象属性变成必传内容.
type Required<T> = { [P in keyof T]-?: T[P] };
// Remove readonly
type MutableRequired<T> = {
-readonly [P in keyof T]: T[P]
};
infer: 在 extends 条件语句中待推断的类型变量。
// 需要获取到 Promise 类型里蕴含的值
type PromiseVal<P> =
P extends Promise<infer INNER> ? INNER : P;
type PStr = Promise<string>;
// Test === string
type Test = PromiseVal<PStr>;
五. 辨别 type & interface
在各大类型库中, 会看到形形色色的 type 和 interface . 然而很多人在实际中却不知道它们的区别.
官网的定义如下:
An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.
An interface can have multiple merged declarations, but a type alias for an object type literal cannot.
从一张图看出它们两的区别:
建议: 能用 interface 实现,就用 interface , 如果不能才用 type.
为了更好的阅读体验, 《typescrit 最佳实践》
- 欢迎关注「前端加加」,认真学前端,做个有专业的技术人...
Typescript 最佳实践的更多相关文章
- Typescript 开发环境的最佳实践
Typescript 开发环境的最佳实践 0️⃣ git init(略) 1️⃣️️ 初始化:$ yarn add -D ts-node typescript 2️⃣ 生成 tsconfig.json ...
- nodejs 实践:express 最佳实践(七) 改造模块 connect2 解析
nodejs 实践:express 最佳实践(七) 改造模块 connect2 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的 ...
- nodejs 实践:express 最佳实践(八) egg.js 框架的优缺点
nodejs 实践:express 最佳实践(八) egg.js 框架的优缺点 优点 所有的 web开发的点都考虑到了 agent 很有特色 文件夹规划到位 扩展能力优秀 缺点 最大的问题在于: 使用 ...
- 中小型前端团队代码规范工程化最佳实践 - ESLint
前言 There are a thousand Hamlets in a thousand people's eyes. 一千个程序员,就有一千种代码风格.在前端开发中,有几个至今还在争论的代码风格差 ...
- 思索 p5.js 的最佳实践
思索 p5.js 的最佳实践 本文写于 2020 年 12 月 18 日 p5.js 是一个 JavaScript 库,用于为艺术家.设计师提供更容易上手的创意编程. 它有着完整的一套基于 Canva ...
- ASP.NET跨平台最佳实践
前言 八年的坚持敌不过领导的固执,最终还是不得不阔别已经成为我第二语言的C#,转战Java阵营.有过短暂的失落和迷茫,但技术转型真的没有想象中那么难.回头审视,其实单从语言本身来看,C#确实比Java ...
- 《AngularJS深度剖析与最佳实践》简介
由于年末将至,前阵子一直忙于工作的事务,不得已暂停了微信订阅号的更新,我将会在后续的时间里尽快的继续为大家推送更多的博文.毕竟一个人的力量微薄,精力有限,希望大家能理解,仍然能一如既往的关注和支持sh ...
- ASP.NET MVC防范CSRF最佳实践
XSS与CSRF 哈哈,有点标题党,但我保证这篇文章跟别的不太一样. 我认为,网站安全的基础有三块: 防范中间人攻击 防范XSS 防范CSRF 注意,我讲的是基础,如果更高级点的话可以考虑防范机器人刷 ...
- 快速web开发中的前后端框架选型最佳实践
这个最佳实践是我目前人在做的一个站点,主要功能: oauth登录 发布文章(我称为"片段"),片段可以自定义一些和内容有关的指标,如“文中人物:12”.支持自定义排版.插图.建立相 ...
随机推荐
- Libevent:2设置
Libevent有一些整个进程共享的全局设置.这些设置会影响到整个的库.因此必须在调用Libevent其他函数之前进行设置,否则,LIbevent就会陷入不一致的状态. 一:Libevent中的日志信 ...
- TreeSet之用外部比较器实现自定义有序(重要)
Student.java package com.sxt.set5; public class Student{ private String name; private int age; priva ...
- poj 1092 Farmland (Geometry)
1092 -- Farmland 怎么最近做几何题都这么蛋疼,提交C++过不了交G++就过了.据我估计,原因是用了atan2这个函数,或者是其他一些函数造成了精度的影响.不管怎样,这题最后还是过了~ ...
- IntelliJ IDEA和Eclipse设置JVM运行参数
打开 IDEA 安装目录,看到有一个 bin 目录,其中有两个 vmoptions 文件,需针对不同的JDK进行配置: 32 位:idea.exe.vmoptions64 位:idea64.exe.v ...
- H3C SSH配置例子
- es6新增语法之`${}`
这是es6中新增的字符串方法 可以配合反单引号完成拼接字符串的功能 1.反单引号怎么打出来?将输入法调整为英文输入法,单击键盘上数字键1左边的按键. 2.用法step1: 定义需要拼接进去的字符串变量 ...
- 数(aqnum)
数(aqnum) 3.1 题目描述 秋锅对数论很感兴趣,他特别喜欢一种数字.秋锅把这种数字命名为 农数 ,英文名为 AQ number . 这种数字定义如下: 定义 1 一个数 n 是农数,当且仅当对 ...
- 极简触感反馈Button组件
一个简单的React触感反馈的button组件 import React from 'react'; import './index.scss'; class Button extends React ...
- html5 canvas 3d屏保 源码
<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head> <met ...
- Python--day69--ORM聚合查询和分组查询
聚合查询和分组查询 聚合 aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典. 键的名称是聚合值的标识符,值是计算出来的聚合值.键的名称是按照字段和聚合 ...