前言
反思了现在为什么越来越多的公司开始使用TypeScript开发应用,而大部分小厂还是热衷javascript,在多人协作的团队,代码的可读性、结构清晰、低耦合、易扩展显的尤为重要。
JavaScript
TypeScript 是 JavaScript的一个超集
,它对JavaScript 做了一系列的增强,包括增加了静态类型
、接口
、类
、泛型
、方法重载
等。有了javascipt的功底,如果还有其他静态类型语言如java、dart等的基础,那么学习TypeScript也会更加容易上手。
JavaScript
是动态类型语言
,在代码编译阶段不会对变量进行类型检测,从而增加了代码执行阶段的错误概率,这也是为什么前端程序员频繁使用console.log
进行调试。在不同类型变量的赋值时,js还会自动进行类型转换,从而带来代码缺陷的可能性进一步增加。
JavaScript没有命名空间,需要手动创建命名空间,来进行模块化。并且,JavaScript 允许同名函数的重复定义,后定义的会覆盖之前定义的函数,这也给大型协同开发的项目带来很多麻烦。
TypeScript
简介
TypeScript
是静态类型语言
,是一种面向对象的编程语言,它通过类型注解提供编译时的静态类型检查。
TypeScript解决javascript上述的一系列问题:包括
在代码编译阶段的变量的类型检测,会提前暴露潜在的类型错误问题。并且在代码执行阶段,不允许不同类型变量之间的赋值。
TypeScript的类型注解,赋予了 IDE 更强的代码补全能力,更人性化的代码提示,从而给开发带来更多的便利之处。
TypeScript 还增加了模块类型
,自带命名空间,方便了大型应用的模块化开发。
特性
数据类型
- 基础数据类型包括:Boolean、Number、String、Array、Enum、Any、Unknown、Tuple、Void、Null、Undefined、Never。
- Enum 枚举:编程要避免使用硬编码,配置化的代码可以让代码更易维护。
// 数字枚举在不设置默认值的情况下,默认第一个值为0,其他依次自增长
enum TASK_STATUS {
UNPLAYED,
ONGOING,
FINISHED,
OBSOLETE
}
let status: TASK_STATUS = TASK_STATUS.UNPLAYED; // 0 - Any 类型:不建议使用。Any 类型为顶层类型,所有类型都可以被视为 any 类型,使用 Any 也就等同于让 TypeScript 的类型校验机制失效。
- Unknown 类型:优先考虑用 Unknown 代替 Any。Unknown 类型也是顶层类型,它可以接收任何类型,但它与 Any 的区别在于,它首次赋值后就确定了数据类型,不允许变量的数据类型进行二次变更。
- Tuple 元组:支持数组内存储不同数据类型的元素。
1
2let tuple: [string, boolean];
tuple= ["ghostwang", true];
- Enum 枚举:编程要避免使用硬编码,配置化的代码可以让代码更易维护。
- Void:当函数没有返回值的场景下,通常将函数的返回值类型设置为 void。
类型注解
TypeScript 通过类型注解提供编译时的静态类型检查,在 : 冒号后面注明变量的类型即可。
1 | const str: string = 'ghostwang'; |
接口
面向对象编程,实现程序解耦的关键就是接口,它只定义属性和方法,不涉及任何具体的实现细节。接口是对实体或行为进行抽象,它是让复杂的逻辑抽离变的更加可扩展的关键。
1 | interface Car { |
类
- 类除了包括属性和方法、继承、getter 和 setter方法之外,还新增了私有字段。私有字段不能在包含的类之外访问,但是可以从一个公有的getter方法中拿到。
- 属性和方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class CommonPerson {
constructor(gender: string) {
this.gender = gender;
}
static name: string = "ghostwang";
gender: string;
getName() {
return this.name;
}
// 成员方法
getGender() {
return 'Gender: ' + this.gender;
}
}
const p = new Person("男");
p.name // 'ghostwang'
p.getGender // '男' - getter 和 setter
1 | class Person { |
继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Person {
name: string;
constructor(nameStr: string) {
this.name = nameStr;
}
walk(distance: number = 0) {
console.log(`${this.name} walked ${distance}米`);
}
}
class GhostWang extends Person {
constructor(nameStr: string) {
// 执行基类的构造函数,把参数传进去
super(nameStr);
}
walk(distance = 5) {
super.walk(distance);
}
}
const mongo = new GhostWang('mongo');
mongo.move(); // 输出:'mongo walked 5米'私有字段
泛型接口
1
2
3interface identityFn<T> {
(arg: T): T;
}泛型类
1
2
3
4
5
6
7
8
9class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
};泛型变量
使用大写字母 A-Z 定义的类型变量都属于泛型,常见泛型变量如下:
T(Type):表示一个 TypeScript 类型
K(Key):表示对象中的键类型
V(Value):表示对象中的值类型
E(Element):表示元素类型
交叉类型
交叉类型就是将多个类型合并为一个类型。通过 & 运算符定义。如下示例中,将 Person 类型和 Company 类型合并后,生成了新的类型 Staff,该类型同时具备这两种类型的所有成员。
1
2
3
4
5
6
7
8
9
10
11
12
13interface Person {
name: string;
gender: string;
}
interface Company {
companyName: string;
}
type Staff = Person & Company;
const staff: Staff = {
name: 'ghostwang',
gender: 'female',
companyName: 'hz'
};联合类型
联合类型就是由具有或关系的多个类型组合而成,只要满足其中一个类型即可。通过 | 运算符定义。如下示例中,函数的入参为 string 或 number 类型即可。
1
2
3function fn(param: string | number): void {
console.log("This is the union type");
}类型保护
类型保护就是在我们已经识别到当前数据是某种数据类型的情况下,安全的调用这个数据类型对应的属性和方法。常用的类型保护包括 in 类型保护、typeof 类型保护、instanceof 类型保护和 自定义 类型保护。具体见以下示例:
in 类型保护
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17interface Person {
name: string;
gender: string;
}
interface Employee {
name: string;
company: string;
}
type UnknownStaff = Person | Employee;
function getInfo(staff: UnknownStaff) {
if ("gender" in staff) {
console.log("Person info");
}
if ("company" in staff) {
console.log("Employee info");
}
}typeof 类型保护
1
2
3
4
5
6function processData(param: string | number): unknown {
if (typeof param === 'string') {
return param.toUpperCase()
}
return param;
}instanceof 类型保护:和 typeof 类型用法相似,它主要是用来判断是否是一个类的对象或者继承对象的。
1
2
3
4
5
6function processData(param: Date | RegExp): unknown {
if (param instanceof Date) {
return param.getTime();
}
return param;
}自定义 类型保护:通过类型谓词 parameterName is Type 来实现自定义类型保护。如下示例,实现了接口的请求参数的类型保护。
1
2
3
4
5
6
7
8
9interface ReqParams {
url: string;
onSuccess?: () => void;
onError?: () => void;
}
// 检测 request 对象包含参数符合要求的情况下,才返回 url
function validReqParams(request: unknown): request is ReqParams {
return request && request.url
}开发小技巧
需要连续判断某个对象里面是否存在某个深层次的属性,可以使用 ?.
1
2if(result && result.data && result.data.list) // JS
if(result?.data?.list) // TS联合判断是否为空值,可以使用 ??
1
2let temp = (val !== null && val !== void 0 ? val : '1'); // JS
let temp = val ?? '1'; // TS不要完全依赖于类型检查,必要时还是需要编写兜底的防御性代码。
因为类型报错不会影响代码生成和执行,所以原则上还是会存在 fn(‘str’) 调用的可能性,所以需要 default 进行兜底的防御性代码。
1 | function fn(value:boolean){ |
- 对于函数,要严格控制返回值的类型.
// 推荐写法1
2
3
4
5function getLocalStorage<T>(key: string): T | null {
const str = window.localStorage.getItem(key);
return str ? JSON.parse(str) : null;
}
const data = getLocalStorage<DataType>("USER_KEY"); - 利用 new() 实现工厂模式
TypeScript 语法实现工厂模式很简单,只需先定义一个函数,并声明一个构造函数的类型参数,然后在函数体里面返回 c 这个类构造出来的对象即可。以下示例中,工厂函数构造出来的是 T 类型的对象。
1 | function create<T>(c: { new(): T }): T { |
- 优先考虑使用 Unknown 类型而非 Any
- 使用 readonly 标记入参,保证参数不会在函数内被修改
1
2
3
4
5
6
7function fn(arr:readonly number[] ){
let sum=0, num = 0;
while((num = arr.pop()) !== undefined){
sum += num;
}
return sum;
} - 使用 Enum 维护常量表,实现更安全的类型检查
1
2
3
4
5
6
7
8
9// 使用 const enum 维护常量
const enum PROJ_STATUS {
PENDING = 'PENDING',
PROCESS = 'PROCESS',
COMPLETED = 'COMPLETED'
}
function handleProject (status: PROJ_STATUS): void {
}
handleProject(PROJ_STATUS.COMPLETED) - 建议开启以下编译检查选项,便于在编译环境发现潜在 Bug
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16{
"compilerOptions": {
/* 严格的类型检查选项 */
"strict": true, // 启用所有严格类型检查选项
"noImplicitAny": true, // 在表达式和声明上有隐含的 any类型时报错
"strictNullChecks": true, // 启用严格的 null 检查
"noImplicitThis": true, // 当 this 表达式值为 any 类型的时候,生成一个错误
"alwaysStrict": true, // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
/* 额外的检查 */
"noUnusedLocals": true, // 有未使用的变量时,抛出错误
"noUnusedParameters": true, // 有未使用的参数时,抛出错误
"noImplicitReturns": true, // 并不是所有函数里的代码都有返回值时,抛出错误
"noFallthroughCasesInSwitch": true,// 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
}
}
- 本文链接:https://cong1223.github.io/2021/01/03/TypeScript%E8%BF%9B%E9%98%B6%E4%B9%8B%E8%B7%AF/
- 版权声明:本博客所有文章除特别声明外,均默认采用 许可协议。
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub IssuesGitHub Discussions