12.typeof和keyof运算符
1.typeof
JavaScript ⾥⾯, typeof 运算符只可能返回⼋种结果,⽽且都是字符串。
typeof undefined; // "undefined"
typeof true; // "boolean"
typeof 1337; // "number"
typeof "foo"; // "string"
typeof {}; // "object"
typeof parseInt; // "function"
typeof Symbol(); // "symbol"
typeof 127n // "bigint
TypeScript 将 typeof 运算符移植到了类型运算,它的操作数依然是⼀个值,但是返回的不是字符串,⽽是该值的TypeScript 类型。
const a = { x: 0 };
type T0 = typeof a; // { x: number }
type T1 = typeof a.x; // number
ts中的typeof是 根据已有的值 来获取值的类型 来简化代码的书写
const myVariable = 42;
type MyVariableType = typeof myVariable; // MyVariableType 的值为 "number"
2.keyof
keyof运算符接受⼀个对象类型作为参数,返回该对象的所有键名组成的联合类型。
keyof 类型别名 keyof {}
练习
type Person={name:string,age:number}
type MyType=keyof Person //name|age
//type MyType=keyof {name:string,age:number} //name|age
let a:MyType="name"
13.映射类型
映射类型:基于旧类型创建新类型(对象类型),减少重复,提升开发效率。
映射类型只能在类型别名中使⽤,不能在接⼝中使⽤。
in后⾯跟的是联合类型,也可以是通过keyof⽣成的联合类型
//基本使⽤
type My="a"|"b"|"c"
type MyType={[key in My]:number} //{a:number,b:number,c:number}
//配合keyof使⽤
type Props={a:number,b:string,c:boolean}
type MyType={[key in keyof Props]:Props[key]} //{a:number,b:string,c:boolean}
//此时的key不是关键字,可以随便命名
let obj:MyType={a:123,b:"2",c:true}
配合可选属性使⽤
type My="a"|"b"|"c"
type MyType={[key in My]?:number} //{a?:number,b?:number,c?:number}
let obj:MyType={a:123}
配合只读属性使⽤
type My="a"|"b"|"c"
type MyType={readonly [key in My]:number} //{readonly a:number,readonly b:number,readonly c:number}
let obj:MyType={a:123,b:2,c:88}
obj.a=888 //报错
14.接⼝
1.基本使⽤
在 TypeScript 中,我们使⽤接⼝(Interfaces)来定义对象的类型。
我们之前定义对象类型都是 形如{name:string,age:number}这种形式
但是如果我的别的对象也是这种结构,我们不⾄于每个对象都重新声明⼀遍这个类型吧,所以就需要⽤到接⼝
接⼝其实就是相当于定义⼀个模板,以后声明的对象都得根据这个模板要求来
interface Person{
name:string,
age:number,
salary:number
}
let obj:Person={name:"张三",age:18,salary:3500}
接⼝中同样⽀持只读属性,可选属性,任意属性
interface Person{
name:string,
age:number,
readonly salary:number,
like:string[],
run?:()=>void,
[propName:string]:string|number|string[]|(()=>void)//这个是任意属性
}
//propName代表属性名,肯定是字符串,propName只是形参,可以换成别的名字 任意属性只有⼀个没有多个,它代表了其他的所有属性
//任意属性的类型⼀定是其他类型(包含可选属性)的⽗类
//后续可以参考第七条下的第二条任意属性
2.接⼝的继承
如果你需要创建⼀个新的接⼝,⽽这个新的接⼝中的部分内容我已经在已存在的接⼝中定义过了,那么可以直接继承,⽆需重复定义
语法:
interface 接⼝名 extends 另⼀个接⼝名
只要存在接⼝的继承,那么我们实现接⼝的对象必须同时实现该接⼝以及他所继承的接⼝的所有属性
interface Person{
name:string,
age:number,
address:string
}
interface Girl extends Person{
height:number,
hobby:string[]
}
interface Boy extends Person{
salary:number,
car:string
}
let xf:Girl={
name:"⼩芳",
age:18,
address:"北京",
height:170,
hobby:["逛街","买买买"]
}
⼀个接⼝可以被多个接⼝继承,同样,⼀个接⼝也可以继承多个接⼝,多个接⼝⽤逗号隔开
继承多个接⼝,必须同时实现继承每⼀个接⼝定义的属性
interface Pro{
phone:string,
coat:string
}
interface Girl extends Person,Pro{
height:number,
hobby:string[]
}
interface Boy extends Person{
salary:number,
car:string
}
let xf:Girl={
name:"⼩芳",
age:18,
address:"北京",
height:170,
hobby:["逛街","买买买"],
phone:"华为",
coat:"安踏"
}
多层继承:需要实现该接⼝以及所继承的接⼝和继承接⼝的接⼝
(就类似于子继承父,也要继承父级继承的爷爷的接口)
interface Person{
name:string,
age:number,
address:string
}
interface Girl extends Person{
height:number,
hobby:string[]
}
interface Xh extends Girl{
hair:string
}
let xh:Xh={hair:"红⾊",height:170,hobby:["买买买"],name:"⼩红",age:18,address:"北京"}
3.接⼝同名会合并
名字相同的接⼝不会冲突,,只要接口中的属性名不重复,就会合并为⼀个
interface Person{
name:string,
age:number,
address:string
}
interface Person{
salary:number
}
let xm:Person={name:"⼩明",age:17,address:"beijng",salary:3500}
4.接⼝中使⽤联合类型
interface Person{
name:string,
age:number,
address:string
hobby:string[]|(()=>void)
}
5.接⼝也可以⽤于定义数组
但是不推荐,定义数组还是优先使⽤我们之前讲的⽅式
interface MyArr{
[index:number]:string
}
let arr:MyArr=["@","3"]
6.接⼝也可以定义函数
interface MyArr{
(a:number):number
}
let fn:MyArr=function(a:number){return 123}
7.接⼝和类型别名的区别
- interface 与 type 的区别有下⾯⼏点。
(1) type 能够表示⾮对象类型,⽽ interface 只能表示对象类型(包括数组、函数等)。
(2) interface 可以继承其他类型, type 不⽀持继承。
(3)同名 interface 会⾃动合并,同名 type 则会报错
(4) interface 不能包含属性映射(mapping), type 可以
interface Point {
x: number;
y: number;
}
// 正确
type PointCopy1 = {
[Key in keyof Point]: Point[Key];
};
// 报错
interface PointCopy2 {
[Key in keyof Point]: Point[Key];
};
15.交叉类型
交叉类型(Intersection Types)⽤于组合多个类型,⽣成⼀个包含所有类型特性的新类型。可以
理解为将多个类型合并为⼀个更⼤的类型,新类型拥有所有原始类型的成员。使⽤ & 符号表示交
叉类型。
type Person={name:string,age:number}
type Emp={salary:number,address:string}
type C=Person&Emp&{height:number}
let obj:C={name:"张三",age:18,salary:3500,address:"beijing",height:180}
注意不要对基本类型使⽤,只能对对象类型使⽤
type C=string&number
16.类型断⾔
1.基本⽤法
类型断⾔就是我明确的知道我这个数据肯定是字符串,告诉编译器你不⽤检测他了。
语法:
值 as 类型
//或者
<类型>值
/*
在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使⽤前者,即 值 as 类型。
形如 <Foo> 的语法在 tsx 中表示的是⼀个 ReactNode,在 ts 中除了表示类型断⾔之外,也可能是表示
⼀个泛型。
故建议⼤家在使⽤类型断⾔时,统⼀使⽤ 值 as 类型 这样的语法
*/
2…将⼀个联合类型断⾔为其中⼀个类型
当 TypeScript 不确定⼀个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型
中共有的属性或⽅法
// type C=string|number
function fn(m:string|number){
(m as string).substring(1)
}
fn(100)//错误
注意:类型断⾔只能欺骗ts编译器,让他不报错,⽆法避免项⽬运⾏时的错误,所以使⽤断⾔要谨慎
interface Boy{
name:string,
make:()=>number
}
interface Girl{
name:string,
cost:()=>void
}
function fn(obj:Boy|Girl){
(obj as Boy).make()
}
案例
let student={} as {name:string}
student.name="张三"
3.将任何⼀个类型断⾔为any
let num:number=1;
console.log((num as any).length)
4.将any断⾔为任意类型
let a:any=5;
console.log((a as number).length) //报错
5.将⽗类断⾔为⼦类
class Students{
make(){
console.log("make")
}
}
class Xm extends Students{
run(){
console.log("run")
}
}
let a=new Students();
(a as Xm).run() //编译通过,运⾏报错
6.⾮空断⾔ !
type MyFn=()=>number
function fn(m:MyFn | undefined){
let num=m!()
let num2=m()//错误写法
}
7.双重断⾔ (不推荐使⽤)
interface Girl{
name:string,
cost:()=>void
}
interface Boy{
name:string,
make:()=>void
}
function fn(obj:Girl){
obj as any as Boy
}
18.泛型
泛型(Generics)是指在定义函数、接⼝或类的时候,不预先指定具体的类型,⽽在使⽤的时候再指定
类型的⼀种特性
简单来说泛型其实就是类型参数
在定义的时候定义形参(类型变量) ,使⽤的时候传⼊实参(实际的类型)
1.函数中使⽤泛型
function identity<T>(arg: T): T {
return arg;
}
identity<Number>(100)
//或者identity(100) 在 TypeScript 中,在调⽤泛型函数时,如果没有显式地指定泛型类型参数,
//编译器会进⾏类型推断。根据传⼊的实际参数类型,编译器可以推断出泛型类型参数的类型,使得函数调⽤仍然是正
确的
//多个类型参数
function identity<T,U>(arg: T,arg2 : U): T {
return arg;
}
identity<Number,String>(4,"hello")
类型的推断不⼀定都是正确的,要谨慎使⽤,下⾯即是案例
function fn2<T>(arr1:T[],arr2:T[]):T[]{
return arr1.concat(arr2)
}
fn2([1,2],["a","b"])//错误
fn2<number|string>([1,2],["a","b"])//正确
2.接⼝中使⽤泛型
interface Person<N> {
name: string;
age: N;
}
const xiong: Person<string> = {
name: "xiong",
age: "18"
// age: 18 //报错
};
3.类中使⽤泛型
class Person<T>{
name:T,
age:number
constructor(name:T,age:number){
this.name = name
this.age = age
}
}
const xiong = new Person<string>('xiong',18)
console.log(xiong.name,xiong.age) // xiong 18
泛型除了能使⽤基本类型 string number
案例
function fn<T>(a:T):T{
return a
}
interface Person{
name:string,
age:number
}
type C="a"|"b"|"c"
fn<(()=>void)>(()=>{console.log(1)})
fn<Person>({name:"⼩明",age:18})
fn<C>("b")
fn<string[]>(["a","b"])
泛型也可以⽤来定义数组
let arr2:Array<number>=[2,3,4] //Array是⼀个内置接⼝,接受⼀个T类型
4.类型别名中使⽤泛型
type C<T>={value:T}
let obj:C<string>={value:"hello"}
5.多个类型参数
function fn<T,U>(arr:T[],f:(arg:T)=>U):U[]{
return arr.map(f)
}
fn<string,number>(["1","2","3"],(item)=>parseInt(item))