TypeScript
中文网:www.tslang.cn/ 个人翻译
JS 开发中的问题
(其实就目前来看,我所理解的 JS,就以下遇到的问题,我觉得这是一个语言严谨性的体现,不过只是我的看法,目前我感觉是这样)
- 使用了错误的或者不存在的变量、函数或者成员(比如函数名称、变量书写等错误)
- 把一个不确定的类型当作一个确定的类型
- 在使用
null和undefined(比如,can not read "" of undefined)
JS 最不舒服的地方
- js 语言本身的特性,决定了他不适合大型的复杂项目
- 弱类型语言,某个变量的类型可以随意更换
- 解释性,代码错误发生的时间,在代码运行的时候
TS
TS是JS的超集,是一个可选的、静态的类型系统
- 超集
整数、正整数、负整数中,整数就是正整数和负整数的超集
- 类型系统 对代码中的所有的标识符(变量、函数、参数、返回值)进行类型检查
- 可选的
不是说必须要用
TS,用JS也是 ok 的 - 静态的 无论是浏览器环境还是 node 环境,无法直接识别 TS 代码 静态的涵义:静态的类型检查发生的时间在编译的时候,而不是在运行的时候
tsc:ts 编辑器
tsc: ts -> js,转换器 www.typescriptlang.org/在线编译
额外减轻开发压力
有了类型检查,增强了面向对象的开发
js 中也有类和对象,js 也支持面向对象开发,但是实际上,我们很少去写一个类因为 js 没有类型检查,很多面向对象的场景实行有问题
使用TS可以编写出完善的面向对象代码
基本类型约束
TS 是一个可选的静态的类型系统(不是必须的)
如何进行类型约束
仅需要在 变量、函数的参数、函数的返回值位置加上:类型
ts 在很多场景中可以完成类型推导
any: 表示任意类型,对该类型,ts 不进行类型检查
小技巧,如何区分数字字符串和数字,关键看怎么读? 如果按照数字的方式朗读,则为数字;否则,为字符串。
源代码和编译结果的差异
编译结果中没有类型约束信息,有问题的话会在源代码中很容易的看出啦,所以编译结果是没有任何问题的JS代码
基本类型
number:数字string:字符串boolean:布尔- 数组 (必须要约束,数组的每一项是说明类型);
- 例如:
let num:number[]=[1,2,3](语法糖); let num:Array<number>=[1,2,3]- 当然你不手动写约束,它也可以推导出来
- 例如:
object: 对象null和undefined
null 和 undefined 是所有其他类型的子类型,它们可以赋值给其他类型
-
- 在这个是时候会发生这样的情况:
let num :string = null,这不是我们希望的
- 在这个是时候会发生这样的情况:
解决
通过在配置文件中添加strictNullChecks:true,可以获得更严格的空类型检查,null 和 undefined 只能赋值给自身。
其他常用类型
- 联合类型:多种类型任选其一
// 在严格模式下,下面的代码会报错
let num: number = undefined;
// 联合模式,不会报错
let num: number | undefined = undefined;
类型保护
配合类型保护进行判断
类型保护:当对某个变量进行类型判断之后,在判断的语句块中便可以确定它的确切类型,typeof 可以触发类型保护。
-
void类型:通常用于约束函数的返回值,表示该函数没有任何返回 -
never类型:通常用于约束函数的返回值,表示该函数永远不可能结束 -
字面量类型:使用一个值进行约束,表示被约束的变量,只能被赋予约束的值
-
let arr: []; //表示 arr 只能是空数组
-
-
元祖类型(Tuple): 一个固定长度的数组,并且数组中每一项的类型确定
-
let arr: [string, number];
-
-
any类型: any 类型可以绕过类型检查,因此,any 类型的数据可以赋值给任意类型-
let res: any = "字符串"; let num: number = res; //所以存在隐患
-
类型别名
对已知的一些类型定义名称
type 类型名 = ...
type user = {
name: string,
age: number,
gander: "male" | "female",
};
//约束函数的返回值是,每一项是 user 类型的数组
function getUsers(): user[] {
return [];
}
函数的相关约束
函数重载:在函数实现之前,对函数调用的多种情况进行声明 (我他妈直呼卧槽)
可选参数:可以在某些参数名后加上问号,表示该参数可以不用传递(参数非必须)。可选参数必须在参数列表的末尾。
注意:默认参数也被认为是可选参数
扩展类型-枚举
扩展类型:类型别名、枚举、接口、类
枚举通常用于约束某个变量的取值范围。
字面量和联合类型配合使用,也可以达到同样的目标。
字面量类型的问题
-
在类型约束位置,会产生重复代码。可以使用类型别名解决该问题。
-
逻辑含义和真实的值产生了混淆,会导致当修改真实值的时候,产生大量的修改。
-
type gender = "男" | "女"; let person = { gen: gender, }; <!-- 那么对于person.gen的赋值只能是"男" | "女", 假如需要更改成"帅哥"|"美女",那么需要修改的地方太多了 -->
-
-
字面量类型不会进入到编译结果。
枚举
如何定义一个枚举:
enum 枚举名{
枚举字段1 = 值1,
枚举字段2 = 值2,
...
}
enum Gender {
male = "帅哥", //把逻辑含义和真实的值清晰的分开
female = "美女",
}
let gender: Gender;
gender = Gender.male; //逻辑含义的值
枚举会出现在编译结果中,编译结果中表现为对象。
枚举的规则:
- 枚举的字段值可以是字符串或数字
- 数字枚举的值会自动自增
- 被数字枚举约束的变量,可以直接赋值为数字
- 数字枚举的编译结果 和 字符串枚举有差异
最佳实践:
- 尽量不要在一个枚举中既出现字符串字段,又出现数字字段
- 使用枚举时,尽量使用枚举字段的名称,而不使用真实的值
枚举拓展:位枚举(枚举的位运算)
针对数字枚举
位运算:两个数字换算成二进制后进行的运算
enum Permission {
Read = 1, // 0001
Write = 2, // 0010
Create = 4, // 0100
Delete = 8 // 1000
}
//1. 如何组合权限
//使用或运算
//0001
//或
//0010
//0011
let p: Permission = Permission.Read | Permission.Write;
//2. 如何判断是否拥有某个权限
//0011
//且
//0010
//0010
function hasPermission(target: Permission, per: Permission) {
return (target & per) === per;
}
//判断变量p是否拥有可读权限
//3. 如何删除某个权限
//0011
//异或:相同取0,不同取1
//0010
//0001
p = p ^ Permission.Write;
console.log(hasPermission(p, Permission.Write));
模块化(相关配置文件的配置)
相关配置:
| 配置名称 | 含义 |
|---|---|
| module | 设置编译结果中使用的模块化标准 |
| moduleResolution | 设置解析模块的模式 (一般设置为 node) |
| noImplicitUseStrict | 编译结果中不包含"use strict" |
| removeComments | 编译结果移除注释 |
| noEmitOnError | 错误时不生成编译结果 |
| esModuleInterop | 启用 es 模块化交互非 es 模块导出 |
前端领域中的模块化标准:ES6、commonjs、amd、umd、system、esnext
TS 中如何书写模块化语句 编译结果??
TS 中如何书写模块化语句
TS 中,导入和导出模块,统一使用 ES6 的模块化标准
编译结果中的模块化
可配置
TS 中的模块化在编译结果中:
- 如果编译结果的模块化标准是
ES6: 没有区别 - 如果编译结果的模块化标准是
commonjs:导出的声明会变成exports的属性,默认的导出会变成exports的default属性;- 总结一下,好理解一些。在
TS文件中支持的是ES6模块化标准,但是对于编译的JS结果可能是Common JS或者ES6(这个我们可以在配置文件中配置)
- 总结一下,好理解一些。在
启用 es 模块化交互非 es 模块导出
// import fs from "fs"; //module.exports={},这里算是默认导入,有问题,当然在配置文件中开启ts与CommonJS交互就可以了
// import * as fs from "fs"; //这样就可以
// import {readFile} from "fs"//这样也可以,不过只导入具体的函数
// fs.readFileSync("./")
// import myModule from "./myModule"
// import myModule = require("./myModule")
如何在 TS 中书写 commonjs 模块化代码
TS 作为 JS 的超集,当然可以使用 JS 中的方式,但是这样你不会获得类型检查
需要使用以下语法
导出:export = xxx
导入:import xxx = require("xxx")
模块解析
模块解析:应该从什么位置寻找模块(就是找路径)
TS 中,有两种模块解析策略
- classic:经典
- node:node 解析策略(唯一的变化,是将 js 替换为 ts)
- 相对路径
require("./xxx") - 非相对模块
require("xxx")
- 相对路径
接口和类型兼容性
扩展类型-接口
接口:inteface
扩展类型:
类型别名、枚举、接口、类
TypeScript 的接口:用于约束类、对象、函数的契约(标准)
契约(标准)的形式:
- API 文档,弱标准
- 代码约束,强标准
和类型别名一样,接口,不出现在编译结果中
-
接口约束对象
// // 定义接口 // interface User { // name: string; // age: number; // } // // 接口约束对象 // let people: User = { // name: "John", // age: 36, // }; // // 类型别名 // type User = { // name: string; // age: number; // }; // // 类型别名约束对象 // let people: User = { // name: "John", // age: 36, // }; -
接口约束函数
- 函数为对象中的成员
// 定义接口 interface User { name: string; age: number; //不出现在编译结果中(函数无法执行), //在接口中只能对方法做约束,不能实现 sayHellow: () => void; // sayHellow(): void;//效果一样 } // 接口约束对象 let people: User = { name: "John", age: 36, sayHellow: () => { console.log("你好"); }, };- 接口直接约束函数
// 接口约束函数 interface Condition { <!-- 这里的大括号不表示对象了,表示定界符 --> (n: number): boolean; } function sum(numbers: number[], callBack: Condition):number { let res = 0; numbers.forEach((n) => { if (callBack(n)) { // 条件真 res += n; } }); return res; } console.log(sum([1, 2, 3, 4], (n) => n % 2 == 0)); -
类型别名约束函数
// 类型别名约束函数
type Condition = (n: number) => boolean;
// type Condition = { //效果一样
// (n: number): boolean,
// };
function sum(numbers: number[], callBack: Condition): number {
let res = 0;
numbers.forEach((n) => {
if (callBack(n)) {
// 条件真
res += n;
}
});
return res;
}
console.log(sum([1, 2, 3, 4], (n) => n % 2 == 0)); //6
接口可以继承
可以通过接口之间的继承,实现多种接口的组合
// 接口的继承;
interface A {
obj1: number;
}
interface B extends A {
obj2: string;
}
let n: B = {
obj1: 10,
obj2: "hello",
};
使用类型别名可以实现类似的组合效果,需要通过&,它叫做交叉类型
type A = {
obj1: number,
};
type B = {
obj2: string,
};
type C = {
obj3: boolean,
} & A &
B;
let n: C = {
obj1: 10,
obj2: "hello",
obj3: true,
};
它们的区别:
- 子接口不能覆盖父接口的成员
- 交叉类型会把相同成员的类型进行交叉(就是相同成员就会具有交叉的约束)
readonly
只读修饰符,修饰的目标是只读
只读修饰符不在编译结果中
interface User {
// 只读修饰符,限定只能最开始的时候给id赋值
readonly id: number;
name: string;
age: number;
}
let u: User = {
id: 1,
name: "john",
age: 20,
};
类型兼容性
B->A(B 赋值给 A,B 需要满足 A 的特征),如果能完成赋值,则 B 和 A 类型兼容
鸭子辨型法(子结构辨型法):目标类型( B )需要某一些特征,赋值的类型( A )只要能满足该特征即可
-
基本类型:完全匹配
-
对象类型:鸭子辨型法
-
类型断言 当直接使用
对象字面量赋值的时候,会进行更加严格的判断<!-- 还可以在我们要断言的变量前面这样断言 <断言的类型> --> interface Duck { sound: "嘎嘎嘎" swin(): void } let person = { name: "伪装成鸭子的人", age: 11, sound: "嘎嘎嘎" as "嘎嘎嘎",//类型断言,string->字面量"嘎嘎嘎" swin() { console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音"); } } let isDuck: Duck = person; //可以赋值 let isDuck2: Duck = { //不可以,直接把对象字面量赋值,会更加严格的判断 name: "伪装成鸭子的人", age: 11, sound: "嘎嘎嘎" as "嘎嘎嘎",//类型断言,string->字面量"嘎嘎嘎" swin() { console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音"); } } let duck: Duck = { sound: "嘎嘎嘎" as "嘎嘎嘎",//类型断言,string->字面量"嘎嘎嘎" swin() { console.log(this.name + "正在游泳,并发出了" + this.sound + "的声音"); } };
-
-
函数类型
- 参数:传递给目标函数的参数可以少,但不可以多,对函数参数没有严格要求你
- 返回值:要求返回必须返回;不要求返回,你随意;
interface Condition { (n: number, i: number): boolean; } function sum(numbers: number[], callBack: Condition) { let s = 0; for (let i = 0; i < numbers.length; i++) { const n = numbers[i]; if (callBack(n, i)) { s += n; } } return s; } const result = sum([3, 4, 5, 7, 11], (n) => n % 2 !== 0); console.log(result);