交叉索引怎么用(【前端进阶】-TypeScript高级类型 | 交叉类型、索引签名类型、映射类型)
前言
博主主页👉🏻蜡笔雏田学代码
专栏链接👉🏻【TypeScript专栏】
上篇文章讲解了TypeScript部分高级类型
详细内容请阅读如下:🔽
【前端进阶】-TypeScript高级类型 | 类的初始化 、构造函数 、继承 、成员可见性
今天来学习TypeScript另外一些高级类型!
感兴趣的小伙伴一起来看看吧~🤞类型兼容性
两种类型系统:
Structural Type System(结构化类型系统) Nominal Type System(标明类型系统)TS采用的是结构化类型系统 ,也叫做ducktyping(鸭子类型) ,类型检查关注的是值所具有的形状 。
也就是说 ,在结构类型系统中 ,如果两个对象具有相同的形状 ,则认为它们属于同一类型 。
//两个类的兼容性演示 class Point { x: number y: number } class Point2D { x: number y: number } const p: Point = new Point2D()解释:
Point和Point2D是两个名称不同的类 。 变量p的类型被显示标注为Point类型 ,但是 ,它的值却是Point2D的实例 ,并且没有类型错误 。 因为TS是结构化类型系统 ,只检查Point和Point2D的结构是否相同(相同 ,都具有x和y两个属性 ,属性类型也相同) 。 但是 ,如果在Nominal Type System中(比如,C# ,Java等) ,它们是不同的类,类型无法兼容 。对象之间的类型兼容性
注意:在结构化类型系统中 ,如果两个对象具有相同的形状 ,则认为它们数属于同一类型 ,这种说法并不准确 。
更准确的说法:对于对象类型来说 ,y的成员至少与x相同 ,则x兼容y(成员多的可以赋值给少的) 。
//两个类的兼容性演示 class Point { x: number y: number } class Point2D { x: number y: number } const p: Point = new Point2D() class Point3D { x: number y: number z: number } const p1: Point = new Point3D() // 错误演示: // const p2: Point3D = new Point() 类型 "Point" 中缺少属性 "z" ,但类型 "Point3D" 中需要该属性 。解释:
Point3D的成员至少与Point相同 ,则Point兼容Point3D 。 所以 ,成员多的Point3D可以赋值给成员少的Point 。接口之间的类型兼容性
除了class之外 ,TS中的其他类型也存在相互兼容的情况 ,包括:1 、接口兼容性 2 、函数兼容性等 。
接口之间的兼容性 ,类似于class。并且 ,class和interface之间也可以兼容 。(成员多的可以赋值给少的) interface Point { x: number; y: number } interface Point2D { x: number; y: number } interface Point3D { x: number; y: number; z: number } let p1: Point let p2: Point2D let p3: Point3D p1 = p2 p1 = p3 p2 = p3 // 错误演示: // p3 = p1 类型 "Point" 中缺少属性 "z",但类型 "Point3D" 中需要该属性 。 // 类和接口之间也是兼容的 class Point4D { x: number; y: number; z: number } p2 = new Point4D()函数之间的类型兼容性
函数之间兼容性比较复杂 ,需要考虑:1 、参数个数 2 、参数类型 3 、返回值类型。A. 参数个数 ,参数多的兼容参数少的(或者说,参数少的可以赋值给多的) 。
// 1 参数个数:参数少的可以赋值给参数多的 type F1 = (a: number) => void type F2 = (a: number, b: number) => void let f1: F1 let f2: F2 f2 = f1 // 错误演示: // f1 = f2 // 演示类型兼容性: let arr = [a,b,c] arr.forEach(() => {}) arr.forEach(item => {}) arr.forEach((item,index) => {}) arr.forEach((item,index,array) => {})解释:
参数少的可以赋值给参数多的 ,所以 ,f1可以赋值给f2 。 数组forEach方法的第一个参数是回调函数 ,该实例中类型为:(value: string, index: number, array: string[])=>void 。 在JS中省略用不到的函数参数实际上是很常见的 ,这样的使用方式 ,促成了TS中函数类型之间的兼容性 。 并且因为回调函数是有类型的 ,所以 ,TS会自动推导出参数item ,index ,array的类型 。B. 参数类型 ,相同位置的参数类型要相同(原始类型)或兼容(对象类型) 。
// 参数为原始类型 type F1 = (a: number) => void type F2 = (a: number) => void let f1: F1 let f2: F2 f1 = f2 f2 = f1解释:函数类型F2兼容函数类型F1 ,因为F1和F2的第一个参数类型相同 。
// 参数为对象类型 interface Point2D { x: number; y: number } interface Point3D { x: number; y: number; z: number } type F2 = (p: Point2D) => void //相当于有2个参数 type F3 = (p: Point3D) => void //相当于有3个参数 let f2: F2 let f3: F3 f3 = f2 // 错误示范: // f2 = f3解释:
注意 ,此处与前面讲到的接口兼容性冲突 。 技巧:将对象拆开,把每个属性看做一个个参数 ,则 ,参数少的(f2)可以赋值给参数多的(f3) 。C. 返回值类型,只关注返回值类型本身即可:
// 3 返回值类型 ,只需要关注返回值类型本身即可 // 原始类型: type F5 = () => string type F6 = () => string let f5: F5 let f6: F6 f6 = f5 f5 = f6 // 对象类型: type F7 = () => { name: string } type F8 = () => { name: string; age: number } let f7: F7 let f8: F8 f7 = f8 // 错误演示: // f8 = f7解释:
如果返回值类型是原始类型 ,此时两个类型要相同 ,比如 ,左侧类型F5和F6 。 如果返回值类型是对象类型 ,此时成员多的可以赋值给成员少的 ,比如 ,右侧类型F7和F8 。交叉类型
交叉类型(&):功能类似于接口继承(extends) ,用于组合多个类型为一个类型(常用于对象类型) 。
比如 ,
interface Person { name: string say(): number } interface Contact { phone: string } type PersonDetail = Person & Contact let obj: PersonDetail = { name: jack, phone: aaa, say() {return 1} }解释:使用交叉类型后 ,新的类型PersonDetail就同时具备了Person和Contact的所有属性类型。
相当于 ,
type PersonDetail = { name: string; phone: string}交叉类型和接口继承的对比
相同点:都可以实现对象类型的组合 。 不同点:两种方式实现类型组合时 ,对于同名属性之间,处理类型冲突的方式不同 。 // 对比: interface A { fn: ( value: number) => string } interface B extends A { fn: ( value: string) => string } //接口继承会报错:不能将类型“(value: string) => string ”分配给类型“(value: number) => string ”。 interface A { fn: ( value: number) => string } interface B { fn: ( value: string) => string } type C = A & B说明:以上代码 ,接口继承会报错(类型不兼容);交叉类型没有错误 ,可以简单地理解为:
let c: C = { fn(value: number | string) { return 1 } } //let c: C //c.fn(a) //c.fn(6)索引签名类型
绝大多数情况下,我们都可以在使用对象前就确定对象的结构 ,并为对象添加准确的类型 。
使用场景:当无法确定对象中有哪些属性(或者说对象中可以出现任意多个属性) ,此时 ,就用到索引签名类型了 。
interface AnyObject { [key: string]: number } let obj: AnyObject = { a: 1, b: 2, cscs: 3 }解释:
使用[key: string]来约束该接口中允许出现的属性名称 。表示只要是string类型的属性名称 ,都可以出现在对象中 。 这样 ,对象obj中就可以出现任意多个属性(比如 ,a ,b等) 。 key只是一个占位符 ,可以换成任意合法的变量名称 。 隐藏的前置知识:JS中对象({})的键是string类型的 。在JS中数组是一类特殊的对象 ,特殊在数组的键(索引)是数值类型 。
并且 ,数组也可以出现任意多个元素 。所以 ,在数组对应的泛型接口中 ,也用到了索引签名类型 。
interface MyArray<T> { [index: number]: T } let arr1: MyArray<number> = [1, 3, 5] arr1[0]解释:
MyArray接口模拟原生的数组接口,并使用[n: number]来作为索引签名类型 。 该索引签名类型表示:只要是number类型的键(索引)都可以出现在数组中 ,或者说数组中可以有任意多个元素 。 同时也符合数组索引是number类型这一前提。映射类型(in)
映射类型:基于旧类型创建新类型(对象类型) ,减少重复,提升开发效率 。
比如 ,类型PropKeys有x/y/z ,另一个类型Type1中也有x/y/z ,并且Type1中x/y/z的类型相同:
type PropKeys = X | y | z type Type1 = { x:number; y: number; z: number }这样书写没错 ,但x/y/z重复书写了两次 。像这种情况 ,就可以使用映射类型来进行简化。
type PropKeys = x | y | z type Type2 = { [Key in PropKeys]: number }解释:
映射类型是基于索引签名类型的 ,所以 ,该语法类似于索引签名类型 ,也使用了[] 。 Key in PropKeys表示Key可以是PropKeys联合类型中的任意一个 ,类似于forin(let k in obj) 。 使用映射类型创建的新对象类型Type2和类型Type1结构完全相同 。 注意:映射类型只能在类型别名中使用 ,不能在接口中使用 。映射类型除了根据联合类型创建新类型外 ,还可以根据对象类型来创建:(keyof)
type Props = { a: number; b: string; c: boolean } type Type3 = { [key in keyof Props]: number }解释:
首先 ,先执行keyof Props获取到对象类型Props中所有键的联合类型即,‘a’ | ‘b’ | ‘c’ 。 然后 ,Key in ...就表示Key可以是Props中所有的键名称中的任意一个 。分析泛型工具类型Partial的实现:
实际上 ,前面讲到的泛型工具类型(比如,Partial都是基于映射类型实现的) 。
比如 ,Partial的实现:
type Partial<T> = { [P in keyof T]?: T[P] } type Props = { a: number; b: string; c: boolean } type PartialProps = Partial<Props>解释:
keyof T即keyof Props表示获取Props的所有键 ,也就是:‘a’ | ‘b’ | ‘c’ 。 在[]后面添加?(问号) ,表示将这些属性变为可选的 ,以此来实现Partial的功能 。 冒号后面的T[P]表示获取T中每个键对应的类型 。比如 ,如果是’a’则类型是number;如果是’b’则类型是string 。 最终 ,新类型PartialProps和旧类型Props结构完全相同 ,只是让所有类型都变为可选了 。索引查询类型(1 基本使用)
刚刚用到的T[P]语法 ,在TS中叫做索引查询(访问)类型。
作用:用来查询属性的类型 。
type Props = { a: number; b: string; c: boolean } type TypeA = Props[a] //type TypeA = number解释:Props[‘a’]表示查询类型Props中属性’a’对应的类型number 。所以 ,TypeA的类型为number。
注意:[]中的属性必须存在于被查询类型中 ,否则就会报错 。
索引查询类型(2 同时查询多个)
索引查询类型的其他使用方式:同时查询多个索引的类型 。
type Props = { a: number; b: string; c: boolean } type TypeA = Props[a | b]解释:使用字符串字面量的联合类型 ,获取属性a和b对应的类型 ,结果为:string|number 。
type TypeA = Props[keyof Props] //string | number | boolean解释:使用keyof操作符获取Props中所有键对应的类型,结果为:string|number|boolean 。
今天的分享就到这里啦✨
\textcolor{red}{今天的分享就到这里啦✨}
今天的分享就到这里啦✨原创不易 ,还希望各位大佬支持一下
\textcolor{blue}{原创不易 ,还希望各位大佬支持一下}
原创不易,还希望各位大佬支持一下🤞
点赞 ,你的认可是我创作的动力!
\textcolor{green}{点赞 ,你的认可是我创作的动力!}
点赞 ,你的认可是我创作的动力!⭐️
收藏 ,你的青睐是我努力的方向!
\textcolor{green}{收藏 ,你的青睐是我努力的方向!}
收藏 ,你的青睐是我努力的方向!✏️
评论 ,你的意见是我进步的财富!
\textcolor{green}{评论 ,你的意见是我进步的财富!}
评论 ,你的意见是我进步的财富!创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!