为什么使用扩展类型
TS提供的类型无法满足开发需求,可以自己开发一些类型,就是扩展类型
常见扩展类型
类型别名、枚举、接口、类
枚举
枚举通常用来约束某个变量的取值范围(字面量配合联合类型也可以实现枚举的作用,能使用枚举尽量不适用字面量配合联合类型)
字面量存在的问题
type Gender = '男' | '女'
// 1、若后续将性别修改为male、female、帅哥、美女、先生、女士,则使用字面量的话需要大量修改代码,不好维护
let gender: Gender
gender = '男'
gender = '女'
// 2、字面量在编译后的js代码中不会存在,js中若要读取某个变量的具体取值范围(性别有哪些),无法实现
枚举使用方法
enum Gender {
male = '男', //写法 逻辑名称 = 真实值
female = '女'
}
let gender: Gender
gender = Gender.male
gender = Gender.female
console.log(gender); // 女,枚举通过使用逻辑名称来获取真实值
// 枚举将逻辑名称和真实值分开了,不像字面量逻辑名称和具体值都是相同的,解决了维护不方便的问题
枚举会参与具体的编译
上述ts代码编辑后结果如下,实际是编译成了对象
var Gender;
(function (Gender) {
Gender["male"] = "\u7537";
Gender["female"] = "\u5973";
})(Gender || (Gender = {}));
let gender;
gender = Gender.male;
gender = Gender.female;
console.log(gender);
数字枚举
如果枚举不赋值,默认为从0开始的数字枚举,并且数字枚举会自增
enum Level {
level1, // 相当于level1 = 0
level2, // 相当于level2 = 1
level3, // 相当于level3 = 2
}
let level: Level = Level.level1
level = Level.level2
level = 2 // 只有数字枚举可以直接赋值,字符串枚举不可以
console.log(level); // 2
数字枚举编译后和字符串枚举不一样
enum Level {
level1,
level2,
level3
}
function printLevel(level: Level) {
console.log(level);
}
printLevel(Level.level1) // 虽然数字枚举可以直接printLevel(1),但是不推荐直接用枚举值,而是通过枚举定义实现
编译后结果
var Level;
(function (Level) {
Level[Level["level1"] = 0] = "level1";
Level[Level["level2"] = 1] = "level2";
Level[Level["level3"] = 2] = "level3";
})(Level || (Level = {}));
/**
上述代码相当于
Level = {
level1 : 0,
level2 : 1,
level3 : 2,
0: 'level1',
1: 'level2',
2: 'level3'
}
*/
function printLevel(level) {
console.log(level);
}
printLevel(Level.level1);
接口
TypeScript中接口是一种用于约束类、对象、函数的标准。类比于电源接口、usb接口其实都是一些标准。 制定标准的形式:
- 文档标准,如联调的接口文档,弱标准
- 代码约束,如接口interface,强标准,会提示错误
接口约束对象
interface User {
// 写法类似类型别名,如果只是约束对象,接口和类型别名都可以使用,但是推荐使用接口形式
name: string
age: number
}
type People = {
name: string
age: number
}
let user: User = {
name: 'zs',
age: 11
}
接口约束函数
约束对象里面的成员函数
接口和类型别名编译后都不会存在于js文件中
interface User {
name: string
age: number
// sayHi: ()=>void
sayHi():void // 表示没有参数也没有返回值的函数,也可以用上面注释的方法
}
type People = {
name: string
age: number
sayHi:()=>void
// sayHi():void
}
let user: User = {
name: 'zs',
age: 11,
// sayHi: ()=>{}
sayHi(){}
}
约束普通函数
// 提取为类型别名
// type Condition = (num: number) => boolean
// 提取为类型别名
interface Condition {
// 大括号里面没有成员名称,不表示一个对象,只是一个定界符,里面是约束内容,就是接口限制函数的写法
(num: number): boolean
}
function sum(numbers: number[], callBack: Condition) {
let s = 0
numbers.forEach(n => {
if (callBack(n)) {
s += n
}
})
return s
}
// 求所有奇数的和
let odds = sum([1, 2, 3, 4, 5, 6], n => n % 2 !== 0)
// 求所有偶数的和
let evens = sum([1, 2, 3, 4, 5, 6], n => n % 2 === 0)
console.log('odds:', odds); // 9
console.log('evens:', evens); // 12
接口的继承性
通过接口的继承性可以进行多个接口标准的组合,通过extends实现继承,比类型别名通过交叉类型实现更加方便
interface A {
name: string
}
interface B extends A {
age: number
}
interface C extends A,B {
male: boolean
}
let user: C = {
name: 'zs',
age: 12,
male: false
}
交叉类型实现上述效果
type A = {
name: string
}
type B = {
age: number
}
type C = {
male: boolean
} & A & B // 通过'&'实现交叉类型
let user: C = {
name: 'zs',
age: 12,
male: false
}
区别在于:
- 子接口,不能再次定义父接口中已经声明的成员,会报错
- 交叉类型相同的成员,会进行类型交叉,即同时具备多个类型
类的使用
类的属性初始化
构造函数初始化
ts中类的定义和js是有区别的,tsconfig中设置"strictPropertyInitialization": true,则属性必须初始化
// js中这段代码是正确的,但是ts中,会报错,ts不允许动态增加属性
class User {
constructor(name, age){
this.name = name
this.age = age
}
}
const obj = {}
obj.color = 'blue' // js中不会报错,ts中会报错,ts认为创建对象的时候对象的属性应该是确定的,不能动态添加属性,容易造成隐患,同时不便于维护
因此需要使用属性列表形式定义
class User {
name: string // 编译为js后不存在这行代码
age: number // 编译为js后不存在这行代码
constructor(name: string, age: number){
this.name = name
this.age = age
}
}
属性列表默认值初始化
可选属性
class User {
name: string
age: number
// 使用属性默认值进行初始化,
//必须设置默认值,才能不通过构造函数进行初始化,
//转换成js还是通过构造函数进行初始化的
gender: '男' | '女' = '男'
pid?: string // 表示pid属性可以没有,不用进行初始化
constructor(name: string, age: number){
this.name = name
this.age = age
}
}
const user = new User('zs', 18)
user.gender = '女'
console.log(user.gender);
不可更改属性
class User {
readonly id: number // id属性不允许修改,使用readonly修饰来实现
name: string
age: number
gender: '男' | '女' = '男'
pid?: string // 表示pid属性可以没有,不用进行初始化
constructor(name: string, age: number){
this.id = Math.random()
this.name = name
this.age = age
}
}
const user = new User('zs', 18)
user.id = 123 // 报错
访问修饰符
- public 类里面的成员(属性或者方法)默认的修饰符,可以省略不写,外部可以访问
- private 只能在类的内部使用,类生成的对象无法外部直接访问
- protected 受保护的成员,只能该类和该类的子类使用
如果构造函数传递的参数只做属性的赋值操作,通过修饰符简化初始化操作
class User {
readonly id: number // id属性不允许修改,使用readonly修饰来实现
gender: '男' | '女' = '男'
pid?: string // 表示pid属性可以没有,不用进行初始化
constructor(public name: string, public age: number){
this.id = Math.random()
}
}
const user = new User('zs', 18)
访问器属性
访问器属性,有点类似vue的计算属性,用于给属性进行一些操作和限制
class User {
gender: '男' | '女' = '男'
constructor(public name: string, private _age: number){
}
set age(value: number){
// 生成器-设置器
if (value <= 0) {
this._age = 0 // 注意通常生成器都是操作私有成员的
}else if (value >= 200) {
this._age = 200
}else {
this._age = value
}
}
get age(){
// 生成器-读取器
return Math.floor(this._age)
}
}
const user = new User('zs', 18)
user.age = 200.5 // 设置年龄,对年龄通过设置器函数进行限制,通过生成器操作私有成员
console.log(user.age); // 200 读取年龄,对返回年龄格式通过读取器进行限制