1、TypeScript 的基础类型
| 基础类型 | 描述 |
|---|---|
| boolean | 布尔类型 |
| number | 数值类型。支持十进制、十六进制(0x)、二进制(0b)、八进制(0o) |
| string | 字符串类型。 ${name}用来在模板字符串中嵌入字符串 |
| Array | 数组。元素类型后加[],如number[];使用数字泛型Array<元素类型>,如Array |
| Tuple | 元组。表示一个已知元素数量和类型的数组,各元素的类型不必相同。如let arr: [number, string]; |
| enum | 枚举。一些有代表语义的数字或字符串的常量集合 |
| any | 任意类型 |
| void | 没有任何类型。当一个函数没有返回值时,通常定义其返回类型为void |
| never | 永不存在的值的类型。常用于异常会抛出异常或根本不会返回值的函数表达式或函数,是任何类型的子类型 |
| null和undefined | 默认情况下null和undefined是所有类型的子类型。 |
| object | 非原始数据类型 。除number、string、boolean、symbol、null、undefined之外的类型 |
enum 类型
当一个变量有几种可能的取值时,可以将它定义为枚举类型,表示一些有代表语义的数字或字符串的常量集合。
枚举类型分为:数字枚举、字符串枚举、异构枚举。
1. 数字枚举
第一项枚举成员值默认初始为0,其余以初始值为基值依次递增。
export enum Direction {
Up = 1,
Down,
}
console.log(Direction)
编译之后:
export var Direction;
(function (Direction) {
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
})(Direction || (Direction = {}));
console.log(Direction);
2. 字符串枚举
export enum Direction {
Up = 'UP',
Down = 'DOWN',
}
console.log(Direction)
编译之后:
export var Direction;
(function (Direction) {
Direction["Up"] = "UP";
Direction["Down"] = "DOWN";
})(Direction || (Direction = {}));
console.log(Direction);
3. 异构枚举
异构枚举的成员值是数字和字符串的混合。
export enum Direction {
Up = 1,
Down = 'DOWN',
}
枚举对象与普通对象的区别之一在于枚举对象的成员为只读属性。
-
数字枚举和字符串枚举的区别?
字符串枚举具有语义化,数字枚举没有。
-
const enum与enum的区别?
-
enum编译后会通过一个函数生成查找对象,const enum编译之后不会生成查找对象,因此更节省性能(现代的 v8可忽略)。export const enum Direction { Up = 'UP', Down = 'DOWN', } console.log(Direction.Up) // 编译后 console.log("UP"); -
不能访问
const enum对象,只能访问const enum对象成员,因此不能将const enum赋值给变量,也不能通过索引访问const enum。const evar1 = EVar // 会报错 console.log(EVar[0]); // 会报错 -
const enum枚举成员不支持动态计算。常量枚举成员初始值设定项只能包含文字值和其他计算的枚举值。let left = 1; export const enum Direction { Up = 1, Down, Left = left * 2, // 常量枚举成员初始值设定项只能包含文字值和其他计算的枚举值 }
小结:
一般情况都使用
enum而不使用const enum,因为enum更灵活。一般定义字符串枚举,更具有语义化,特别场景可使用数字枚举。
unknown 类型
unknown是 TypeScript 类型系统的另一种顶级类型。
由于 any 类型无法使用 TypeScript 提供的大量的保护机制,为了解决 any 带来的问题,TypeScript 3.0 引入了 unknown 类型。
-
ts 变量赋值
let A = B,当B类型为A类型或者B类型为A类型的子级时,才会通过。
因此,所有类型都可以赋值给
unknown,unknown类型只能被赋值给any类型和unknown类型本身。
-
unknown类型使用前必须先使用条件控制流或者类型断言来收窄 unknown 到指定的类型const doSomething = (val: unknown) => { if (typeof val === 'function') { val(); // 条件分析,类型收窄到 Function,调用不报错; } val.trim(); // 没有断言,报错 (val as string).trim(); }
由于`unknown`类型变量在使用前必须指定其类型,因此`unknown` 类型会比 `any` 更加安全。
2. 联合类型和交叉类型中的 unknown
联合类型取最大集合,任何类型和 `unknown` 类型的联合类型都会得到 `unknown`
```
type U1 = unknown | null; // unknown
type U2 = unknown | undefined; // unknown
type U3 = unknown | number; // unknown
type U4 = unknown | boolean; // unknown
type U5 = unknown | string[]; // unknown
type U6 = unknown | any; // any
```
在交叉类型中,取最小集合
```
type U7 = unknown & null; // null;
type U8 = unknown & undefined; // undefined;
type U9 = unknown & number; // number;
type U10 = unknown & boolean; // boolean;
type U11 = unknown & string[]; // string[]
type U12 = unknown & any; // any;
```
小结:
- 尽量用
unknown代替any;unknown类型应用场景:使用 未知的 或者 不稳定的 数据来源的数据
Tuple 类型(元组)
已知元素数量和类型的数组,各元素的类型不必相同,元组中每个属性都有一个关联的类型。
例子:
let tupleType: [string, boolean];
tupleType = ["Semlinker", true];
never 类型
never类型表示的是那些永不存在的值的类型。never类型是任何类型的子类型,因此可以赋值给任何类型。
never 类型应用在无法达到终点的函数。例如:抛出异常的函数
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
while (true) {
}
}
在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:
type Foo = string | number;
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}
在 else 分支里面,把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天改了 Foo 的类型:
type Foo = string | number | boolean;
然而忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。也就是使用never,当Foo新增类型会提醒controlFlowAnalysisWithNever方法有误。
通过这个示例,我们可以得出一个结论:使用 never 可以检测出现新增了联合类型,但没有对应的实现的情况,目的就是写出类型绝对安全的代码。
object 类型和 Object 类型
object 类型
object表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。
例如:
declare function create(o: object | null): void;
create({ prop: 0 }); // OK
create(null); // OK
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
注:使用这种类型,我们不能访问值的任何属性。
示例:
const obj: object = {a: 1};
console.log(obj); // {a: 1}
console.log(obj.a); // error: 类型“object”上不存在属性“a”。
Object 类型
TypeScript 定义了另一个与新的
object类型几乎同名的类型,那就是Object类型。该类型是所有 Object 类的实例的类型。
Object由以下两个接口来定义:
- Object 接口定义了 Object.prototype 原型对象上的属性;
- ObjectConstructor 接口定义了 Object 类的属性。
1、Object 接口定义
// node_modules/typescript/lib/lib.es5.d.ts
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
2、ObjectConstructor 接口定义
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;
readonly prototype: Object;
getPrototypeOf(o: any): any;
// ···
}
declare var Object: ObjectConstructor;
Object 类的所有实例都继承了 Object 接口中的所有属性。
示例:
Object类型也不能访问除Object 接口之外的属性。
示例:
const obj2: Object = {a: 1};
console.log(obj2.a); // error: 类型“object”上不存在属性“a”
Object vs object
-
类型
Object包括原始值,而object不包括:function func1(x: Object) { } func1('semlinker'); // OK func1(20); // OK function func2(x: object) { } func2('semlinker'); // error func2(20); // error为什么?
Object.prototype的属性也可以通过原始值访问:> 'semlinker'.hasOwnProperty === Object.prototype.hasOwnProperty true
小结:
Object包括原始值,可以访问Object 接口中的所有属性;而object不可以- object和Object类型都不能访问自己定义的属性值,需慎用。
2、类型别名简介(Type)
类型别名(type aliase),顾名思义,就是给类型起个别名。类型别名用
type关键字来定义,有了类型别名,我们书写 TS 的时候可以更加方便简洁。
示例:
type Name = string // 基本类型
type size = 'big' | 'small'; // 字面量类型
type arrItem = number | string // 联合类型
const arr: arrItem[] = [1,'2', 3]
type Person = { // 对象类型
name: Name;
grade: number;
}
type Student = Person & { grade: number } // 交叉类型
type Teacher = Person & { major: string }
type StudentAndTeacherList = [Student, Teacher] // 元组类型
const list:StudentAndTeacherList = [
{ name: 'lin', grade: 100 },
{ name: 'liu', major: 'Chinese' }
]
type 和 interface
相同点:
-
都可以定义一个对象或函数
-
都允许拓展(extends)
interface 使用 extends 实现继承, type 使用交叉类型实现继承。interface 可以 extends type, type 也可以 与 interface 类型 交叉 。
interface extends interface
interface Name { name: string; } interface User extends Name { age: number; }type 与 type 交叉
type Name = { name: string; } type User = Name & { age: number };interface extends type
type Name = { name: string; } interface User extends Name { age: number; }type 与 interface 交叉
interface Name { name: string; } type User = Name & { age: number; }
不同点:
-
interface(接口) 是 TS 设计出来用于定义对象类型的,是对对象的形状进行描述。
-
type 是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰。
-
type 可以声明基本类型、联合类型、交叉类型、元组,interface 不可以
-
type可以和映射类型一起使用,interface不可以。
type定义的对象类型,属性key可以为type类型和enum类型,
interface定义的对象类型,属性不key可以为type类型和enum类型,
示例:
type TSex = 'man' | 'women'; enum ESex {Man, Woman} type DaysObj = { [key in TSex]: string; }; type DaysObj = { [key in ESex]: string; }; interface DaysObj { [key in TSex]: string; // error:映射的类型可能不声明属性或方法 }; interface DaysObj { [key in ESex]: string; // error:映射的类型可能不声明属性或方法 };
type和interface是完全不同的概念。
interface 是接口,用于描述一个对象。
type 是类型别名,用于给各种类型定义别名,让 TS 写起来更简洁、清晰。
3、TypeScript 的类型断言和类型守卫
3.1 类型断言
TypeScript 类型断言用来告诉编译器已经指定了的确切的类型,它不应该再发出错误。类型断言一般作用于any类型、unknown类型、联合类型的变量。
类型断言不属于类型转换。是因为转换通常意味着某种运行时的支持。但是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。
as 语法或“尖括号” 语法
function getLength(arg: number | string): number {
const str = arg as string
// const str = <string>arg
if (str.length) {
return str.length
} else {
const number = arg as number
return number.toString().length
}
}
非空断言!
在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined 。
示例:
function sayHello(name: string | undefined) {
// let sname: string = name; // Error
let sname: string = name!;
}
3.2 类型守卫
类型守卫可以保证一个字符串是一个字符串,其主要思想是尝试检测属性、方法或原型,以确定如何处理值。目前主要有四种的方式来实现类型保护。
3.2.1 in 关键字
interface Admin {
name: string;
privileges: string[];
}
interface Employee {
name: string;
startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
console.log("Name: " + emp.name);
if ("privileges" in emp) {
console.log("Privileges: " + emp.privileges);
}
if ("startDate" in emp) {
console.log("Start Date: " + emp.startDate);
}
}
3.2.2 typeof 关键字
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${padding}'.`);
}
typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename,"typename" 必须是 "number", "string", "boolean" 或 "symbol"。
3.2.3 instanceof 关键字
interface Padder {
getPaddingString(): string;
}
class SpaceRepeatingPadder implements Padder {
constructor(private numSpaces: number) {}
getPaddingString() {
return Array(this.numSpaces + 1).join(" ");
}
}
class StringPadder implements Padder {
constructor(private value: string) {}
getPaddingString() {
return this.value;
}
}
let padder: Padder = new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
// padder的类型收窄为 'SpaceRepeatingPadder'
}
4、交叉类型和联合类型
4.1 交叉类型
TypeScript 交叉类型是将多个类型合并为一个新的类型,新类型包含了所有类型的特性。
示例:
interface IPerson {
id: string;
age: number;
}
interface IWorker {
companyId: string;
}
type IStaff = IPerson & IWorker;
const staff: IStaff = {
id: 'E1006',
age: 33,
companyId: 'EFT'
};
console.dir(staff)
要点:
type定义交叉类型的时候,不管类型是interface还是type,如果它们具有相同的属性名,则该属性的类型也必须相同。
type C = A & B; // 如果A、B具有相同属性name,则name的类型也必须相同
4.2 联合类型
联合类型表示一个值可以是几种类型之一。 我们用竖线(
|)分隔每个类型。
联合类型通常与 null 或 undefined 一起使用:
let id: number | string;
const sayHello = (name: string | undefined) => {
/* ... */
};
5、泛型简介
指在在定义函数、接口或者类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
泛型的语法是
<>里写类型参数,一般可以用T来表示。
常见泛型变量代表的意思:
- T(Type):表示一个 TypeScript 类型
- K(Key):表示对象中的键类型
- V(Value):表示对象中的值类型
- E(Element):表示元素类型
5.1 泛型基本使用
5.1.1 处理函数参数
function print<T>(arg:T):T {
return arg
}
泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出。
5.1.2 默认参数
interface Iprint<T = number> {
(arg: T): T
}
function print<T>(arg:T) {
return arg
}
const myPrint: Iprint = print
5.1.3 处理多个函数参数
现在有这么一个函数,传入一个只有两项的元组,交换元组的第 0 项和第 1 项,返回这个元组。
function swap(tuple) {
return [tuple[1], tuple[0]]
}
用泛型来改造一下:用 T 代表第 0 项的类型,用 U 代表第 1 项的类型。
function swap<T, U>(tuple: [T, U]): [U, T]{
return [tuple[1], tuple[0]]
}
const res = swap(['lin', 20]); // [20, 'lin']
5.1.5 定义接口
// 接口请求类型
export type RespCommon<T> = {
code: string;
data: T;
message: string;
messageArgs: Array<any>;
};
5.2 约束泛型
可以和 interface 结合来约束类型。
interface ILength {
length: number
}
function printLength<T extends ILength>(arg: T): T {
console.log(arg.length)
return arg
}
这其中的关键就是 <T extends ILength>,让这个泛型继承接口 ILength,这样就能约束泛型。我们定义的变量一定要有 length 属性,可以通过 TS 编译。
5.3 泛型的一些应用
5.3.1 泛型约束类
定义一个栈,有入栈和出栈两个方法,如果想入栈和出栈的元素类型统一,就可以这么写:
class Stack<T> {
private data: T[] = []
push(item:T) {
return this.data.push(item)
}
pop():T | undefined {
return this.data.pop()
}
}
在定义实例的时候写类型,比如,入栈和出栈都要是 number 类型,就这么写:
const s1 = new Stack<number>()
s1.push(10);
这是非常灵活的,如果需求变了,入栈和出栈都要是 string 类型,在定义实例的时候改一下就好了:
const s1 = new Stack<string>()
s1.push('aa')
特别注意的是,泛型无法约束类的静态成员。
5.3.2 泛型约束类型
使用泛型,可以动态定义type, interface 类型。
// 接口请求类型
export type RespCommon<T> = {
code: string;
data: T;
message: string;
messageArgs: Array<any>;
};
5.3.3 泛型定义数组
const arr: Array<number> = [1,2,3]
5.3.4 小结
- 泛型是指在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型。
- 泛型中的
T就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出。 - 泛型在成员之间提供有意义的约束,这些成员可以是:函数参数、函数返回值、类的实例成员、类的方法等。
用一张图来总结一下泛型的好处:
6、TypeScript 中常用的操作符(typeof,keyof, extends)
6.1 typeof
typeof 操作符用于获取变量的类型,返回的是一个类型。
let man = {
name: 'Tom',
age: 30,
}
type Man = typeof man; // type Man = { name: string; age: number; }
6.2 keyof
keyof操作符用于获取某种类型的所有键,返回键组成的联合类型。
示例1:
type Point = { x: number; y: number };
type P = keyof Point;
// ^?type P = keyof Point;
const p1: P = 'x' // 有提示'x' | 'y'
示例2:
type Test = keyof {[key: string]: string; }; // string | number ?????
// 因为JavaScript对象键总是强制为字符串,因此obj[0]始终与obj["0"]相同。
type Obj = {
[key: string]: string; // key为string类型,其实key可以取number和string
}
const obj1: Obj = {1: '2'}; // ok
示例3:
type T1 = {
[key: number]: string;
}
type Test = keyof T1; // number
示例4:
string索引签名,获取不到自定义属性名。
type Test = keyof {name: string; [key: string]: string; }; // string | number
name属性丢失???
官方介绍:如果该类型具有string或number索引签名,keyof则将返回这些类型。
示例5:
number索引签名,可以获取到自定义的属性名。
type Test = keyof {name: string; [key: number]: string; }; // number | 'name'
详情见typeof 官网介绍
6.3 T[K]
T[K],表示接口 T 的属性 K 所代表的类型,
interface IPerson {
name: string;
age: number;
}
let type1: IPerson['name'] // string
let type2: IPerson['age'] // number
6.4 extends
不同场景下代表的含义不一样:
-
表示继承/拓展的含义
class Animal { name: string; constructor(theName: string) { this.name = theName; } move(distanceInMeters: number = 0) { console.log(`${this.name} moved ${distanceInMeters}m.`); } } class Snake extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 5) { console.log("Slithering..."); super.move(distanceInMeters); } } class Horse extends Animal { constructor(name: string) { super(name); } move(distanceInMeters = 45) { console.log("Galloping..."); super.move(distanceInMeters); } } let sam = new Snake("Sammy the Python"); let tom: Animal = new Horse("Tommy the Palomino"); sam.move(); tom.move(34); -
表示约束的含义
场景:在书写泛型的时候,我们往往需要对类型参数作一定的限制,比如希望传入的参数都有 name 属性的数组我们可以这么写:
function getCnames<T extends { name: string }>(entities: T[]):string[] { return entities.map(entity => entity.cname) }这里
extends对传入的参数作了一个限制,就是 entities 的每一项可以是一个对象,但是必须含有类型为string的name属性。 -
表示分配的含义
extends判断一个类型是不是可以分配给另一个类型,返回断后的类型。如果
A extends B为true,则类型A可以分配给类型B,类型A具备类型B的一切约束条件。type A = { id: string; } type B = { id: number; age: number; } type C = { id: string; } type Bool1 = B extends A ? 'yes' : 'no'; // Bool1 => 'no' type Bool2 = C extends A ? 'yes' : 'no'; // Bool2 => 'yes'
7、TypeScript 中常用的内置工具类型(in,Partial, Required, Readonly, Record, Pick, Exclude, Omit)
7.1 映射类型
从旧类型中创建新类型的一种方式 — 映射类型。在映射类型里,新类型以相同的形式去转换旧类型里每个属性。
7.1.1 in
in用来对联合类型或者enum类型实现遍历。
例如:
type Keys = 'option1' | 'option2';
type Flags = { [K in Keys]: boolean };
7.1.2 Partial
Partial<T>构造一个类型,其中T的所有属性都设置为可选。
例如:
interface IPerson {
name: string
age: number
}
type IPartial = Partial<IPerson>
let p1: IPartial = {}
使用partial对类型进行优化
优化前:
type TMpiRunRawItem = {
rank?: string;
function?: string;
mpi_call_count?: string;
rank_list?: string;
};
let mpirunData: TMpiRunRawItem = {};
优化后:
type TMpiRunRawItem = {
rank: string;
function: string;
mpi_call_count: string;
rank_list: string;
};
let mpirunData: Partial<TMpiRunRawItem> = {};
Partial 原理
Partial的实现用到了in和keyof
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P]
}
[P in keyof T]遍历T上的所有属性?:设置为属性为可选的T[P]设置类型为原来的类型
7.1.3 Required
Required<T> 构造一个类型,其中T的所有属性都设置为必选。
例如:
interface Props {
a?: number;
b?: string;
}
const obj: Props = { a: 5 };
const obj2: Required<Props> = { a: 5 };
Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.
7.1.4 Readonly
Readonly<T>构造一个类型,其中T的所有属性都设置为只读。
例如:
interface Todo {
title: string;
}
const todo: Readonly<Todo> = {
title: "Delete inactive users",
};
todo.title = "Hello";
Cannot assign to 'title' because it is a read-only property.
7.1.5 Pick
Pick<Type, Keys>通过从Type中抽取属性集Keys来构建类型。
例如:
interface IPerson {
name: string
age: number
sex: string
}
type IPick = Pick<IPerson, 'name' | 'age'>
let p1: IPick = {
name: 'lin',
age: 18
}
Pick 原理
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
Pick映射类型有两个参数:
- 第一个参数T,表示要抽取的目标对象
- 第二个参数K,具有一个约束:K一定要来自T所有属性字面量的联合类型
7.1.6 Record
Record<Keys, Type>构造一个属性键为Keys,属性值为Type的对象类型。
示例:
type petsGroup = 'dog' | 'cat' | 'fish';
interface IPetInfo {
name:string,
age:number,
}
type IPets = Record<petsGroup, IPetInfo>;
const animalsInfo:IPets = {
dog:{
name:'dogName',
age:2
},
cat:{
name:'catName',
age:3
},
fish:{
name:'fishName',
age:5
}
}
7.2 条件类型
7.2.1 Exclude
Exclude<T, U> 返回 联合类型 T 中不包含 联合类型 U 的部分。
type Test = Exclude<'a' | 'b' | 'c', 'a' | 'd'> // Test: 'b' | 'c'
7.2.2 Extract
Extract<T, U>提取联合类型 T 和联合类型 U 的所有交集。
type Test = Extract<'a' | 'b' | 'c', 'a'> // Test: 'a'
7.2.3 Omit
Omit<T, U>从类型 T 中剔除 U 中的所有属性。与Pick相反
interface IPerson {
name: string
age: number
}
type IOmit = Omit<IPerson, 'age'> // IOmit: {name: string}
8、思考问题:
1、为什么要使用 TypeScript?
JavaScript 是弱类型且动态类型的语言,灵活多变,可以进行隐式转换 ,也可以进行类型检测,但是缺失了类型系统的可靠性。JavaScipt 缺点主要有:
- 只有在运行阶段才能发现代码的异常,使得隐藏的 bug 难以发现;
- 隐式类型转换可能产生意想不到的结果;
- 阅读代码时,难以识别复杂类型的细节。
TypeScript 是 JavaScript 的超集,任何原生的 JavaScript 代码都可以直接通过编译器检查并运行。TypeScript 的静态类型和编译大大降低了发生运行时错误的可能性,同时作为超集,TypeScript 依旧保留了JavaScript 的灵活性,
TypeScript 可以让使用 IDE 的开发者通过类型检查及时地发现错误。Bug 越早被发现,就能越早处理。
当然,TypeScript 并不能直接用于 JavaScript 解释引擎,需要编译器将其编译为 JavaScript 代码后才可进行解释,并且添加静态类型检查的 JavaScript 代码会比无类型检查的代码更多更长,类型检查也会花费更多的时间来处理编译时错误。但是,劣质的 JavaScript 代码的开发和维护也很费时,并可能引发严重错误,会消耗更多的时间成本,从总体成本上来说,使用 Typescript 仍然是值得的,对于大项目更是如此!
TypeScript 与 JavaScript 的区别
| TypeScript | JavaScript |
|---|---|
| 强类型,支持静态和动态类型 | 动态弱类型语言 |
| 可以在编译期间发现并纠正错误 | 作为一种解释型语言,只能在运行时发现错误 |
| 不允许改变变量的数据类型 | 变量可以被赋予不同类型的值 |
| 支持模块、泛型和接口 | 不支持模块,泛型或接口 |
| 最终被编译成 JavaScript 代码,使浏览器可以理解 | 可以直接在浏览器中使用 |
| JavaScript 的超集用于解决大型项目的代码复杂性 | 一种脚本语言,用于创建动态网页。 |
TypeScript的优势
- 添加了类型系统,增加了代码的可读性和可维护性,有利于协作开发;
- 静态类型和编译双重检查,可以及时发现一些类型或者接口不匹配的错误,从而大大降低了运行时发生错误的可能性;
- 增强了编辑器(IDE) 的功能,包括代码补全、接口提示、跳转到定义、等;
TypeScript的缺点
- 编写类型、接口等增加代码量,从而增加工作量;
- 类型检查也会花费更多的时间来处理编译时错误;
2、为什么滥用 any 会使得代码难以维护?
比如定义一个any类型的对象obj,需要获取obj的属性值name后才执行下一步操作,由于不知道obj是否有name属性,如果name为undefined,对后面的操作就会产生影响。
3、后端接口数据有必要定义类型吗?
有必要。
因为我们用的是TS,强类型语言,使用变量时需要对变量定义类型。而前端对后端返回的数据定义的类型取决于后端返回的数据类型。因此,后端接口数据有必要定义类型。
对于从后端请求的数据,定义类型的好处有:
-
在 IDE 中编写代码时有提示,可以知道返回的数据类型,减少书写错误;
-
数据在组件传递时,易跟踪;
当数据在组件中传递的时候,结构会发生变化。但是我们可以根据初始的数据类型进行转化,而不是重新定义类型。
type TData = {
a: number;
b: number;
c: number;
}
type ChildData1 = Omit<TData, 'a'>;
// ChildData1 = {
// b: number;
// c: number;
// }
type ChildData2 = Pick<TData, 'c'>;
// ChildData2 = {
// c: number;
// }
- 当后端的数据结构改变时,易识别。
如果对返回的数据不定义类型,隐藏的 bug 不易发现。
比如后端返回一个值a:1,如果a是字符串类型,会影响前端对(a === 1)的判断。