今天继续来分享ts的相关概念,枚举,ts模块化,接口和类型兼容性
ts的扩展类型:类型别名,枚举,接口和类
枚举
基础概念
枚举通常用于约束某个变量的取值范围。当然字面量和联合类型配合使用,也可以达到同样的目标。
为什么使用枚举
但是使用字面量和联合联系会存在一个问题
- 逻辑含义和真实值容易混淆,修改真实值的时候,会产生大量的修改
- 字面量类型不会进入编译结果
使用枚举的话就不会出现这种问题
如何定义枚举
/*
enum 枚举名{
枚举字段1 = 值1,
枚举字段2 = 值2,
..
}
*/
//实例
enum Gender {
Male = "帅哥",
Female = "美女"
}
// 先生 女士 男 女 male female
let gender: Gender;
gender = Gender.Male;
gender = Gender.Female;
编译后的js代码
var Gender;
(function (Gender) {
Gender["Male"] = "\u5E05\u54E5";
Gender["Female"] = "\u7F8E\u5973";
})(Gender || (Gender = {}));
// 先生 女士 男 女 male female
let gender;
gender = Gender.Male;
gender = Gender.Female;
下面给个使用联合类型的例子
type Gender = "男" | "女";
let g:Gender;
g = '女';
g = '男';
编译后的js代码
let g;
g = '女';
g = '男';
可以非常清晰的看出枚举的好处,会把枚举里的属性也编译到结果里面,避免了代码里面大量的真实值。
枚举的规则
- 枚举的字段值可以是字符串或数字
- 数字枚举的值会自动自增
enum Level {
level1 : 1,
level2,
level3
}
let l: Level = Level.level1;
console.log(l)
l = Level.level2;
console.log(l)
l = Level.level3;
console.log(l)
控制台输出结果
1
2
3
-
被数字枚举约束的变量,可以直接赋值为数字
-
数字枚举的编译结果 和 字符串枚举有差异
小练习
这里使用枚举来做测试
//权限
enum Permission {
Read = 1, //0001
Write = 2, //0010
Create = 4, //0100
Delete = 8 //1000
}
//1.使用或运算组合权限
let p: Permission = Permission.Read | Permission.Write;//这样p就具备read和write权限
//2.判断是否拥有某个权限,使用&,且运算两边都是1才为1
function hasPermisson(target: Permission, per: Permission) {
return (target & per) === per;
}
//3.如何删除某个权限
//通过异或运算,相同取0,不同取1
p = p ^ Permission.Write;
模块化
配置名称 | 含义 |
---|---|
module | 设置编译结果中使用的模块化标准 |
moduleResolution | 设置解析模块的模式 |
noImplicitUseStrict | 编译结果中不包含"use strict" |
removeComments | 编译结果移除注释 |
noEmitOnError | 错误时不生成编译结果 |
esModuleInterop | 启动es模块化交互非es模块导出 |
前端模块化标准:ES6、commonjs、amd、umd、system,esnext
TS中如何书写模块化语句
TS中,导入和导出模块,统一使用ES6的模块化标准
编译结果中的模块化
可配置
TS中的模块化在编译结果中:
- 如果编译结果的模块化标准是ES6:没有区别
- 如果编译结果的模块化标准是commonjs:导出的声明会变成exports的属性,默认的导出会变成exports的default属性;
默认是用的es6语法
import fs from "fs";
import myModule from "./myModule";
如何在TS中书写commonjs模块化代码
如果想用commonjs语法的话需要在tsconfig.json里面配置
{
"compilerOptions": { //编译选项
"module": "CommonJS", //配置编译目标使用的模块化标准
},
}
代码:
//导出
export = xxx
//导入
import xxx = require(xxx)
示例
//modules.ts
export = {
name: 'kakarote',
sum(a: number, b: number) {
return a + b;
}
}
//index.ts
import myModule = require('./myModule');
模块解析
关于ts的一个模块解析策略,它有两种模块解析策略
具体可以查看中文网:https://www.tslang.cn/docs/handbook/module-resolution.html,也可以去官网里查看
-
classic:经典模块解析策
//一、对于相对路径引入的模块流程 //root/src/folder/A.ts import { b } from "./moduleB" //这样引入的话首先它会查找 //1. /root/src/folder/moduleB.ts //2. /root/src/folder/moduleB.d.ts //二、对于非相对路径的导入 //编译器则会从包含导入文件的目录开始依次向上级目录遍历,尝试定位匹配的声明文件。 //root/src/folder/A.ts import { b } from "moduleB" //它会这么查找 //1./root/src/folder/moduleB.ts //2./root/src/folder/moduleB.d.ts //3./root/src/moduleB.ts //4./root/src/moduleB.d.ts //5./root/moduleB.ts //6./root/moduleB.d.ts //7./moduleB.ts //8./moduleB.d.ts
-
node:模块解析策略
//一、对于相对路径引入的解析模块流程 ///root/src/moduleA.js var x = require("./moduleB"); //1.它会监测:/root/src/moduleB.js文件是否存在 //2.监测/root/src/moduleB目录是否包含package.json模块,如果package.json指定了main模块,包含了{ "main": "lib/mainModule.js" },那么它就会引用/root/src/moduleB/lib/mainModule.js //3.监测/root/src/moduleB目录是否包含index.js,如果存在它就会被当做那个文件夹的main模块 //二、非相对模块的解析流程 ///root/src/moduleA.js var x = require("moduleB") //1./root/src/node_modules/moduleB.js //2./root/src/node_modules/moduleB/package.json (如果指定了"main"属性) //3./root/src/node_modules/moduleB/index.js //4./root/node_modules/moduleB.js //5./root/node_modules/moduleB/package.json (如果指定了"main"属性) //6.root/node_modules/moduleB/index.js //7./node_modules/moduleB.js //8./node_modules/moduleB/package.json (如果指定了"main"属性) //9./node_modules/moduleB/index.js 注意Node.js在步骤(4)和(7)会向上跳一级目录。
接口
TypeScript的接口:用于约束类、对象、函数的契约(标准)
契约(标准)的形式:
-
API文档,弱标准
-
代码约束,强标准
和类型别名一样,接口,不出现在编译结果中
接口约束对象
interface User {
name: string
age: number
sayHello: () => void
}
//定义类型别名
// type User = {
// name: string,
// age: number,
// sayHello: () => void
// }
let u: User = {
name: "sdsad",
age: 33,
sayHello() {
console.log('asdasdsasa')
}
}
接口可以继承
class Banner extends React.Component{
}
可以通过接口之间的继承,实现多种接口的组合
使用类型别名可以实现类型的组合效果,需要通过&
,它叫做交叉类型
它们的区别:
- 子接口不能覆盖父接口的成员
- 交叉类型会把相同成员的类型进行交叉
readonly
只读修饰符,修饰的目标是只读
只读修饰符不在编译结果中
类型兼容性
B->A,如果能完成赋值,则B和A类型兼容
鸭子辨型法(子结构辨型法):目标类型需要某一些特征,赋值类型只要能满足该特征即可
例子:
interface Duck {
sound: "嘎嘎嘎",
swim(): void
}
let person:Duck = {
name: "伪装成鸭子的人",
age: 1,
sound: "嘎嘎嘎" as "嘎嘎嘎",
swim() {
console.log(this.name + '正在游泳,并发出了' + this.sound + "的声音")
}
}
如果直接给person设置Duck类型约束会报错
但是可以使用下面这种方式
interface Duck {
sound: "嘎嘎嘎",
swim(): void
}
let person = {
name: "伪装成鸭子的人",
age: 1,
sound: "嘎嘎嘎" as "嘎嘎嘎",
swim() {
console.log(this.name + '正在游泳,并发出了' + this.sound + "的声音")
}
}
let duck: Duck = person;
如果直接用对象的字面量形式就会报错,但是赋值的形式它就会忽略,只要有sound和swim就不会报错
函数类型
我们可以看到arr的forEach的一个函数的ts定义
forEach(callbackfn: (value: number, index: number, array: number[]) => void, thisArg?: any): void
它这里要求这个回调函数的参数有这些,value,index,array,但是这里我们传递的时候可以少传递,但是不能多传递。
[34, 3].forEach(it => console.log(it));
Tips:
参数值:传递给目标函数参数可以少,不能多
返回值:如果函数要求返回一定要返回,不要求返回则可以随意
结语
今天的学习回顾就到这里了!!