哪里有理解的不对的地方请各位大佬指出,感谢!
接口
接口定义
语法:
interface关键字
// 这就是一个接口,里面有两个属性,一个name是string类型,一个age是number类型
interface PersonType {
name: string,
age: number
}
可选属性
语法:
?,意思是这属性写也可以,不写也没事,一些不是必要的参数就可以使用可选属性
interface PersonType {
name: string,
age?: number
}
// 没有写age属性也是可以的
let xy:PersonType = {
name: '小杨'
}
只读属性
语法:
readonly,如果你不希望之后修改此属性,你就可以加上readonly
TS当中提供了一种可读的数组的类型
ReadonlyArray,此时你可以看到被ReadonlyArray修饰的数组则不能重新赋值~,但是
- 此时我想把
ReadonlyArray修饰的ro数组给到另外一个变量,会报错,但是可以用断言(as)的方式解决(断言慎用)
- 此时可以看到,类型“readonly number[]”为“readonly”,不能分配给可变类型“number[]”。但是我们可以用 断言 的方式解决~
readonly 和 const
嗯,被
const定义的是常量也是不可以被修改的(对象类型除外(对象的属性还是可以被修改的),我这里指的基本类型),如果你是定义的变量就用const修饰,如果是属性 就可以使用readonly
类型断言
嗯,怎么说呢,比如你拿到一个any类型的数据,但是你清楚的知道它就是string类型, 这时候你就可以通过类型断言的方式跟编辑器说:“嘿!我知道我在干什么!”
尖括号语法
<类型>变量名
- 这里很奇怪哈,我看文档和网上别人的资料
尖括号语法确实如此,我也不知道哪里的问题,可能是不支持?哈哈,确实更推荐下面的as语法,这种尖括号写法貌似会和jsx写法里面冲突~
as 语法
变量名 as 类型
let msg: any = "hello world"
let a: string = msg as string
更加推荐使用
as的方式使用类型断言~~~~
非空断言
语法:在变量后面加上一个
!, 当你明确知道某个值不可能为undefined和null时,你可以用非空断言的方式跟编辑器说: ”嘿,它是一定有值的,相信我!“
function fun(val: string | null | undefined) {
// error: 输入'字符串| 空 | undefined' 不可分配给类型 'string'。类型“undefined”不可分配给类型“string”。
const str: string = val
// success
const str2: string = val!
// error: 'val' 可能是 'null' 或 'undefined'
const num = val.length
// success
const num2 = val!.length
}
确定赋值断言
语法:可以在变量或者实例后面加上
!,如果你非常确定该属性会一定被赋值,则可以告诉编译器:“我会为它赋值的,相信我,你别报错了,放心!!!”
- 话不多说,上案例!!!
let num:number
// 在分配之前使用变量“num”
console.log("num===>",num * 2) // Variable 'num' is used before being assigned.
initialize()
function initialize() {
num = 10
}
很明显看到提示了吧,因为num还没赋值呢,你就去使用它做运算了~,此时可以使用确定赋值断言解决该问题
let num!:number
console.log("num===>",num * 2) // NaN
initialize()
function initialize() {
num = 10
}
- 可以看到已经没有出现红色波浪线,但是结果需要注意可不是20哦。他依然是先执行的输出语句 再往下执行的~
接口表示函数类型
// 定义接口
interface FuncC {
(test01: string,test02:string):boolean
};
let test01: FuncC;
test01 = (param1,param2)=> {
return true // 此处也必须返回boolean类型
};
test01('1','2') // 这里必须传入2个参数且必须是string类型
注意:这里的
参数名称和接口定义的参数名称可以不一样,类型、个数匹配即可
可索引的类型
个人理解:比如一个对象你不知道它里面的属性且类型不确定,就可以使用索引类型
有两种索引签名:string索引和number索引
string签名索引
// 这里定义了objType,它具有索引签名,索引签名为string,
interface ObjType{
[propName:string]: any
}
const obj:ObjType = {
name: "金金金",
age: 23,
sex: "男",
isPerson: true
} // 这里可以写任何类型的属性值
number类型索引
// 这个索引签名表示了当用 `number`去索引`ObjType`时会得到`string`类型的返回值
interface ObjType{
[propName:number]: string
}
const arr:ObjType = ["1","2","3"]
console.log(arr) // ["1", "2", "3"]
- 可以同时两种索引类型一起使用
注意:两种类型同时使用的时候,number索引的返回值必须是string索引返回值类型的子级,这是因为当使用
number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。
// 动物
class Animal {
name: string;
}
// 小狗
class Dog extends Animal {
breed: string;
}
// 错误:使用数值型的字符串索引,有时会得到完全不同的Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
// 正确
interface NotOkay {
[x: number]: Animal;
[x: string]: Animal;
}
// 正确
interface NotOkay {
[x: number]: Dog;
[x: string]: Animal;
}
索引类型和其他属性同时存在
// error
interface NumberDictionary {
[index: string]: number;
length: number;
name: string; // 这里会报错
age: number;
}
// success
// 这是因为字符串可索引类型的返回值为number类型,存在string类型的返回值就会报错!必须是number类型或者number子类型。
interface NumberDictionary {
[index: string]: number;
length: number;
name: number;
age: number;
}
// success
interface NumberDictionary {
[index: string]: number|string;
length: number;
name: string;
age: number;
}
使用场景:比如有一个对象,你不知道
属性名或属性个数确定,此时可以使用可索引签名
// 例如:定义一个person类型,名字,年龄,性别字段是必须的,其余不确定,此时就可以使用可索引签名
interface PersonType {
name: string,
age: number,
sex: string,
[propName:string]: string | number
}
const person1:PersonType = {
name :'金金金',
age: 22,
sex: '男'
}
字符串索引和数字索引区别
感觉就是为了区分吧。
如果你写的是string索引签名,就说明是通过 .属性 方式读取数据。
如果你写的是number索引签名,则是通过 test[0] 方式。
// number索引签名 strat -------------------------
// success
interface MyObj {
[index: number]: string;
}
let myObj: MyObj = ['hello', 'world'];
console.log(myObj[0]); // hello
console.log(myObj[1]); // world
// error
interface MyObj {
[index: string]: string; // 这里换成了string索引签名
}
// 类型“string[]”不可分配给类型“MyObj”。“string[]”类型中缺少“string”类型的索引签名。
let myObj: MyObj = ['hello', 'world'];
// number索引签名 end -------------------------
// string索引签名 strat -------------------------
// success
interface MyObj {
[index: string]: string;
}
let myObj: MyObj = {
left: 'hello',
right: 'world'
};
console.log(myObj.left); // hello
console.log(myObj.right); // world
// error
interface MyObj {
[index: number]: string; // 这里换成了number索引签名
}
let myObj: MyObj = {
// 会报错,说不存在left
left: 'hello',
right: 'world'
};
// 需要注意!!! 如下方式是可以的!!!
interface MyObj {
[index: number]: string;
}
// 因为 TypeScript 能够自动将字符串字面量转换为对应的数字类型
let myObj: MyObj = {
'0': 'hello',
'1': 'world'
};
console.log(myObj[0]); // hello
console.log(myObj[1]); // world
console.log(myObj['0']); // hello
console.log(myObj['1']); // world
// string索引签名 end -------------------------
类类型
我理解的意思就是 嗯,我想想,就是强制一个类符合某种契约吧
interface TestInterface {
test: string
}
// 实现TestInterface接口,需要注意这里需要写个构造函数(constructor)给test赋值,不然会提示没赋值初始值以及没有在构造函数中未明确分配
class TestInterfaceImpl implements TestInterface {
test: string;
constructor(test: string) {
this.test = test
}
}
- 你也可以在接口中描述一个方法,在类里实现它,如同下面的
setTime方法一样
interface Test2Interface {
test: string;
setTime(d: Date):Date
}
class Test2InterfaceImpl implements Test2Interface {
test: string;
constructor(test: string) {
this.test = test
};
setTime(d:Date): Date{
return new Date(d.getTime())
};
}
继承接口
接口也可以互相继承,这样能很方便的共用一些属性。
extends关键字
interface Animal {
name: string
}
interface Dog extends Animal{
age: number
}
const xiaohuang:Dog = {
name: '',
age: 0
}
xiaohuang.name = "小黄"
xiaohuang.age = 12
console.log(xiaohuang.name) // "小黄"
也可以同时继承多个接口
// 宽度
interface ShapeWidth {
width: number
}
// 高度
interface ShapeHeight {
height: number
}
// 形状
interface Shape extends ShapeWidth,ShapeHeight {
name: string
}
let round:Shape = {
name: "圆形",
width: 220,
height: 220
}
混合类型
- 一个
对象可以同时做为函数和对象使用,并带有额外的属性
这个后面补========================================================
类
-
类(Class):定义了一件事物的抽象特点,包含它的属性和方法
-
对象(Object):类的实例,通过
new生成
简单使用
class Dog {
name: string;
// 这里是构造函数,一般用来初始化对象的
constructor(cName:string) {
// 这里的this代表我们访问的当前类的成员,也就是name属性。
this.name = cName
}
say() {
return 'hello,我叫' + this.name
}
}
const xiaohuang = new Dog('小黄')
console.log(xiaohuang)
const msg = xiaohuang.say()
console.log(msg)
我自己的理解:类就是形容一件事物的抽象特点,比如一只小狗,你可以给它取名字(name属性),今年多大了(age属性)以及它会叫主人跟人打招呼(方法)等等
- 定义:用
class关键字定义一个类,- 命名:
类名首字母大写驼峰命名- 使用:用new关键字,会得到一个实例,使用这个实例对象就可以获取其中的属性、方法等。
类的继承
- 先放代码,后解释。
class Person {
wear(parameter: string = '') {
return parameter
}
}
class Chinese extends Person{
drink() {
console.log('我喜欢喝阔落~ ~ ~')
}
}
const xiaoming = new Chinese()
const wearMsg = xiaoming.wear('我今天穿了一件白色的衣服')
console.log(wearMsg)
xiaoming.drink()
如上所示,
Chinese(派生类)继承了Person(基类),从而也可以使用他的方法,new出来的Chinese实例可以直接访问到Person上的方法属性等。
其实我更喜欢叫 子类(派生类) 和 父类(基类) 哈哈
- 使用:用
extends关键字代表继承
- 看一个更复杂的例子
// 动物
class Animal {
name: string;
constructor(name: string) {
this.name = name
};
move(action:string){
console.log(this.name + ':' +action)
};
}
// 龙
class Dragon extends Animal {
constructor(name:string) {
super(name)
}
move() {
console.log('Dragon ~ move')
super.move('我是在天上飞的~')
}
}
// 兔
class Rabbit extends Animal {
constructor(name:string) {
console.log('Rabbit ~ move')
// this.name = name // 这里是不行的!!!super语句要放在this语句前面!!! 在派生类的构造函数中访问“this”之前必须调用“super”。(17009)
super(name)
}
move() {
super.move('我是在地上跑的~')
}
}
const tuzi = new Rabbit('小白兔')
const long = new Dragon('大黑龙')
tuzi.move()
long.move()
运行结果
解释一下上面代码的意思:
首先有一个
Animal类,有name名字属性和mover移动方法,接着两个类分别继承了Animate,并且在构造函数里面使用了super()以及重写了父类的move方法super()
1. 构造函数里面的super():代表调用基类(父类)的构造函数,必须调用一次!!!
2. 方法里面的super():代表调用基类里面的方法,语法是super.方法名(),不是必须!!!
3. 特别注意:super语句一定要写在this语句前面,不然会报错。
4. 在构造函数里面访问this属性之前,我们一定要调用super(),这是ts的规则
- ts中文网是这么解释的。
公共、私有、受保护的修饰符。
公共(public)
class Dog {
public name: string;
public age: number;
public constructor(name:string, age:number) {
this.name = name;
this.age = age;
}
}
------------------这两个其实是等价的。你不写默认就是public---------------------
class DogCopy {
name: string;
age: number;
constructor(name:string, age:number) {
this.name = name;
this.age = age;
}
}
在
ts里面,你不写默认就是public
私有(private)
class Person {
private name: string;
constructor(name: string) {
this.name = name
}
}
const xiaoming = new Person('小明')
console.log(xiaoming)
// Property 'name' is private and only accessible within class 'Person'.(2341)
console.log(xiaoming.name)
- 用
private修饰的不能在外部访问,只能在本类中访问- 你可以看到右侧还是输出了 小明,没错,因为
TypeScript编译器会将TypeScript代码编译成JavaScript代码,而在JavaScript中并没有访问修饰符的概念,因此实际上JavaScript运行时并不会对私有属性进行限制,所以这也是为什么会输出的原因,我们应该避免掉这种情况,报红色波浪线你就应该注意- 也是有办法滴~一般都是在构造函数当中赋值,或者存取器(
set/get)当中去赋值,后面会讲到!!!
受保护的(protected)
这个和private很像
- 不同之处
- private:只能在本类中访问
- protected:可以在派生类(子类)当中访问,(new出来的实例访问不行哦,会出现红色波浪线)
class Person {
protected name: string;
constructor(name: string) {
this.name = name
}
}
class Employee extends Person {
private employeeName: string;
constructor(name: string,employeeName:string) {
super(name)
this.employeeName = employeeName
}
say() {
// protected 修饰的可以在派生类当中访问。
return `hello, 我叫${this.name},我在${this.employeeName}工作!`
}
}
let xiaoming = new Employee('小明', '销售部')
console.log(xiaoming.say())
- 构造函数也可以使用
protected,被protected修饰的构造函数不能够被直接实例化,但是可以被继承!!!
- 错误示范
readonly修饰符
被
readonly修饰的意思 代表是只读的。只能在属性初始化、构造函数当中赋值。
class Person {
readonly name: string;
constructor(name: string) {
this.name = name
}
}
let xiaohuang = new Person('小黄')
xiaohuang.name = '小明' // Error: Cannot assign to 'name' because it is a read-only property.(2540)
class Person2 {
name: string = '小明';
constructor(name: string) {
this.name = name
}
}
let xiaoming = new Person2('小杨')
xiaoming.name = '小红'
参数属性
构造函数里的参数前面加上修饰符。比如在构造函数的一个参数前面加上
private,意思就是声明并初始化了一个私有属性。对于public和protected来说也是一样。
- 下面我写一个例子,一看就明白了。
- 两个类,一个构造函数里面没使用参数属性,一个使用了参数属性(用的是private修饰的。代表是私有的)
class Person{
constructor( name:string) {
console.log(name)
}
}
let xiaoming = new Person('小明');
console.log(xiaoming)
class Person2{
constructor(private name:string) {
console.log(name)
}
}
let xiaohuang = new Person2('小黄');
console.log(xiaohuang)
xiaohuang.name='私有属性不可以在外部被修改'
存取器
语法:在函数的前面加上
set/get
我的理解是 这个存取器相当于就是一个类内部暴露出set和get方法供外部使用的。set和get处理逻辑都是在本类内部处理的。外部调取相对应的方法,相当于逻辑都封装在内部吧,只暴露方法供外部使用。安全性很好!
注意:存取器要求你将编译器设置为输出ECMAScript 5或更高。不支持降级到ECMAScript 3。
- 如下例子你可以看到,当访问name属性的时候给出了一个提示,因为name属性是被private修饰的,外部不能修改。存取器就很好的解决了这个问题。
class Person {
private name: string;
public age: number;
constructor(name:string, age:number) {
this.name = name
this.age = age
};
}
let xiaoMing = new Person('小明',22)
xiaoMing.name = '小黄' // Property 'name' is private and only accessible within class 'Person'.(2341)
xiaoMing.age = 20
- 存取器写法
class Person {
private name: string;
public age: number;
constructor(name:string, age:number) {
this.name = name
this.age = age
};
// 注意set这里不能指定返回类型,你可以在这里面写逻辑判断。
set setName(sName: string) {
if (sName==='小黄') {
this.name = sName;
} else {
alert('信息不合法!')
}
};
get getName():string {
return this.name
};
}
let xiaoMing = new Person('小明',22)
xiaoMing.setName = '小黄'; // 将 '小黄' 赋值给 name 属性
console.log(xiaoMing.getName) // "小黄"
xiaoMing.age = 20
/**
Person: {
"name": "小黄",
"age": 20
}
*/
console.log(xiaoMing)
静态属性
语法:
static关键字修饰。
- 可以 写在
属性或者方法前面- 不可以 写在
构造函数前面- 被
static修饰的方法或者属性是直接存在类上面的,new出来的实例不存在被static修饰的方法以及属性- 被
static修饰的方法以及属性的调用方式是:类名.属性/方法来调用
- 还有一个注意点:被
static修饰的属性不可以使用name等,会和一些其他的内置属性存在冲突!!!
class Person {
/**
* Error:Static property 'name' conflicts with built-in property 'Function.name' of constructor function 'Person'.(2699)
* 翻译:静态属性“name”与构造函数“Person”的内置属性“Function.name”冲突。(2699)
* 解释:因为 定义的 Person 类的静态属性名 name 冲突了 JavaScript 内置类型 Function 的构造函数属性 name,因为 name 已经是构造函数的属性之一, 所以给 Person 类添加 name 静态属性时就会产生冲突
*/
static name: string; // Error
static myName: string;
age: number;
constructor(name: string, age: number) {
this.age = age;
Person.myName = name; // static修饰的属性需要 类名.属性 调用。
};
static say() {
}
}
const xiaoyang = new Person('小杨',23)
console.log(xiaoyang)
console.log(xiaoyang.myName) // Error
console.log(Person.myName) // success
抽象类
语法:
abstract关键字,一般是给派生类(子类)继承 实现的。
可以在类和方法前面加abstract,
- 用
abstract修饰的方法所在的类必须也加上abstractabstract所修饰的方法和接口的方法有点相似。两者都是定义签名但不包含方法体。且派生类需要实现所有的抽象方法。- 和
接口(interface)不同,抽象类里面可以包含成员的实现细节。接口则不行。- 抽象方法必须包含
abstract关键字并且可以加访问修饰符(private、public等),接口方法也可以加访问修饰符。
abstract class Person {
private name: string;
constructor(name: string) {
this.name = name;
};
set setName(name: string) {
this.name = name;
};
get getName() {
return this.name;
};
abstract say():void;
jump():void {
console.log(`${this.name}会跳舞`)
};
}
class XiaoMing extends Person {
constructor(name: string) {
super(name)
};
say() {
console.log(`${this.getName}:嗨!`)
}
}
const xiaoming = new XiaoMing('小明')
console.log(xiaoming)
xiaoming.say() // "小明:嗨!"
xiaoming.jump() // "小明会跳舞"
构造函数
语法:
constructor(){}
构造函数会在我们使用new创建类实例的时候被调用。并且会得到一个类的实例。
- 基础使用
class Person {
private name: string;
constructor(name:string) {
this.name = name;
};
say():string {
return `哈喽:我叫${this.name}`
}
}
let xiaoming: Person
xiaoming = new Person('小明')
const msg = xiaoming.say()
console.log(msg) // "哈喽:我叫小明"
- typeof 妙用
你可以看见一句这样的代码:
let Person2: typeof Person = Person;
这里创建了一个叫做Person2的变量。 这个变量保存了Person这个类或者说保存了Person这个类的构造函数。 然后我们使用typeof Person,意思是取Person类的类型,而不是实例的类型。 或者更确切的说,"告诉我Person标识符的类型",也就是构造函数的类型。 这个类型包含了类的所有成员和构造函数。其实就是两个变量名引用了同一处地址。
好处:当我们在其他地方需要引用Person类型时,可以使用Person2替代,这样就不需要直接使用Person了。如果以后我们需要修改Person类型的名称,那么只需要修改一处,即将Person修改为新的名称,而无需在代码的所有引用处都进行修改。
class Person {
static msg = '上午好';
name: string="";
getMsg() {
if (this.name) {
return this.name
} else {
return Person.msg
}
};
}
let xiaoming:Person = new Person();
console.log('xiaoming.getMsg--->',xiaoming.getMsg());
let Person2: typeof Person = Person;
Person2.msg = '下午好';
console.log(Person2.name); // 注意这里输出的是类名。===》 "Person"
let xiaohuang:Person = new Person2();
console.log('xiaohuang.getMsg--->',xiaohuang.getMsg());
console.log(Person === Person2); // true
- 假如person改名字了,typeof这样赋值类型的好处就体现出来了。看代码。
class Chinese {
static msg = '上午好';
name: string="";
getMsg() {
if (this.name) {
return this.name
} else {
return Chinese.msg
}
};
}
let Person2: typeof Chinese = Chinese; // 只需要修改这里的名称,其它引用Person2地方的代码无需修改
Person2.msg = '下午好';
console.log(Person2.name);
把类当作接口使用
类定义会创建两个东西:类的实例类型和一个构造函数。 因为类可以创建出类型,所以你能够在允许使用接口的地方使用类
这个我也不好形容。理解的不到位, 类也可以被接口继承。
- 查询了一波,供参考
class Person {
name: string = "";
}
interface XiaoMing extends Person {
age: number;
sex: string
}
const xiaoming:XiaoMing = {name: '小明',age:22,sex:'男'}
泛型
基础使用
function test<T>(num: T) : T {
return num;
}
// 显示指定泛型为string类型
const result = test<string>('指定了<string>,只能写string类型的数据')
console.log('result值--->',result)
console.log('result值--->',typeof result)
// 自动推导出类型为number
const result2 = test(1)
console.log('result2值--->', result2)
console.log('result2类型---->', typeof result2)
使用泛型变量
// error!!!
function test<T>(num: T) : T {
// 假设此时我们想看长度,因为T没有明确类型,不确定是否有length这个属性,则会爆红给出提示
console.log(num.length);
}
// success!!! 可以这样写
function test2<T>(num: T[]) : T[] {
console.log(num.length);
return num;
}
// success!!! 也可以这么写
function test3<T>(num: Array<T>) : Array<T> {
console.log(num.length);
return num;
}
泛型函数类型
泛型函数的类型与非泛型函数的类型没什么不同,只是有一个类型参数在最前面,像函数声明一样
function test<T>(num: T) : T {
return num;
}
// 使用
let demo: <T>(num: T) => T = test;
// 也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以
let demo2: <U>(num: U) => U = test;
// 调用签名的对象字面量方式
let demo3: {<T>(num: T): T} = test;
泛型接口
// 定义泛型接口
interface GenericIdentityFn {
<T>(age:T) :T,
}
function identity<T>(arg: T): T {
return arg;
}
// 这里可以随便写什么类型
let test:GenericIdentityFn = identity;
test(1) // success
test('1') // success
test(true) // success
// 锁定参数类型方式!!!
// 我们可能想把泛型参数当作整个接口的一个参数
interface GenericIdentityFn2<T> {
(age:T) :T
}
function identity2<T>(arg: T): T {
return arg;
}
// 这里需要显性的给指定类型(指定了string,就只能传入string类型的数据)
let test2:GenericIdentityFn2<string> = identity2;
test2('测试') // success
test2(1) // error: Argument of type 'number' is not assignable to parameter of type 'string'.(2345)
注意,无法创建泛型枚举和泛型命名空间。
泛型类
泛型类看上去与泛型接口差不多。 泛型类使用(
<>)括起泛型类型,跟在类名后面。
class GenericNumber<T> {
zeroValue: T;
addAndSub: (x: T, y: T) => T;
constructor(zeroValue: T, addAndSub: (x: T, y: T) => T) {
this.zeroValue = zeroValue;
this.addAndSub = addAndSub
}
}
// 加法
const number = new GenericNumber<number>(0, (x, y) => x + y);
number.zeroValue = 1;
const result = number.addAndSub(1, 111)
console.log('result', result)
// 减法也行,把操作的参数抽出来
// const option = (x: number, y: number) => x + y; // 加法操作
const option = (x: number, y: number) => x - y; // 减法操作
const number2 = new GenericNumber<number>(0, option);
const result2 = number2.addAndSub(2,1);
console.log('result2', result2);
// 也可以使用字符串类型
const stringAdd = new GenericNumber<string>('0', (x, y) => x + y);
const result3 = stringAdd.addAndSub('11', '22')
console.log('result3', result3)
泛型约束
先看下这个例子
function test<T>(agrs: T): T {
console.log(agrs.length)
return agrs;
}
- 你会发现,报了一个错误,大概意思是说T类型上面不存在length这个属性,因为不确定T是什么类型的,此时就可以用泛型约束解决这个问题
- 我这里定义了一个接口规范
- 泛型函数T继承了这个接口(相当于就继承了它的属性length)
- 使用泛型函数,传入对应参数
注意: 传入数字,布尔值等会报错,只能传入 有length属性的 或者 对象含有length属性的。
interface LengthInterface {
length : number
}
function test<T extends LengthInterface>(agrs: T): T {
console.log(agrs.length)
return agrs;
}
test({length:100});
test([1,2,3,4]);
test('11111111111111');
在泛型约束中使用类型参数
- 我这里是限制了
value参数必须为obj参数当中的属性,利用extends keyof T
function test<T, V extends keyof T>(obj: T, value: V) {
return obj[value];
}
const obj = {
a: 'a1',
b: 'b1',
c: 'c1',
d: 'd1'
}
const result = test(obj , 'a');
console.log('result--->', result); // 'a1'
const result2 = test(obj , 'a1'); // error
在泛型中使用类类型
- 指定了Numbr只能输入number类型的数据,string同等。
class Person<T> {
private name: T;
constructor(name: T) {
this.name = name;
}
set setName(name: T) {
this.name = name;
}
get getName(): T {
return this.name;
}
}
const obj = new Person<string>('小丽');
const objName = obj.getName;
console.log('objName====>', objName);
const obj2 = new Person<number>(11);
const obj2Name = obj2.getName;
console.log('obj2Name====>', obj2Name);
使用泛型创建工厂函数
- 需要引用构造函数的类类型
// function create<T>(c: {new(): T}): T{
// return new c();
// }
// 也可以这么写
function create<T>(c: new()=> T): T{
return new c();
}
class Person {
}
const person = create<Person>(Person);
console.log('person',person)
枚举
- 使用场景:例如你列表有一个字段 需要展示订单状态,状态为1需要展示 已支付,状态为2显示 未支付等等,你就可以使用枚举将他们统一管理起来,方便管理以及服用。
数字枚举
- 不给值,值则从0开始依次递增
enum OrderType {
One,
Two,
Three,
Four
}
console.log(OrderType.One) // 0
console.log(OrderType['Two']) // 1
console.log(OrderType['Three']) // 2
console.log(OrderType['Four']) // 3
- 第一个给默认值,其余不给,其余值则在第一个上面依次递增
enum OrderType {
One = 1,
Two,
Three,
Four
}
console.log(OrderType.One) // 1
console.log(OrderType['Two']) // 2
console.log(OrderType['Three']) // 3
console.log(OrderType['Four']) // 4
字符串枚举
enum StrngType {
No = "0",
Yes = "1",
}
异构枚举(不推荐这么写)
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES"
}
枚举成员做为类型
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
let c: Circle = {
kind: ShapeKind.Square,
// ~~~~~~~~~~~~~~~~ Error!
radius: 100,
}
此时这里的
kind只能是ShapeKind.Circle
反向映射
- 注意:字符串枚举成员不可反向映射。
enum Enum {
A = 123
}
let a = Enum.A;
console.log('a---->', a) // 123
let nameOfA = Enum[a];
console.log(nameOfA) // "A"
const枚举
const枚举
const enum Directions {
Up,
Down,
Left,
Right
}
let directions = Directions.Up
非const枚举
enum Directions {
Up,
Down,
Left,
Right
}
let directions = Directions.Up
- 常量枚举在编译阶段会被删除
- 普通枚举并不会
不需要操作枚举对象本身,而是直接使用枚举成员的值进行比较或其他操作,这时使用 const 枚举更加合适。它将避免创建额外的枚举对象,使得代码更加简洁和高效
高级类型
交叉类型
- 语法:
&
// 人
interface Person {
name: string;
age: number;
}
// 衣服
interface Clothing {
color: string;
clothing: string;
}
// 这里用了交叉类型,需要传递四个属性,少或者多都不行
function test(params: Person & Clothing) {
console.log('params' ,params)
}
const from = {
name: "小明",
age: 23,
color: "红色",
clothing: "花纹"
}
test(from);
此时有两个接口,人和衣服,我想要一个穿着衣服的人,此时就可以用交叉类型。
交叉类型必须包含所有的属性
联合类型
- 语法:
|
function add(num1: number, num2: number | string) {
if (typeof num2 === "number") {
console.log(num1 - num2)
} else if (typeof num2 === "string") {
console.log(num1 + num2)
}
}
add(1, 10)
通过传入不同类型执行不同的操作,联合类型可限制符合某几种其中的一种
联合类型 符合其中一个类型 即可