Typescript高级: 深入理解交叉类型与联合类型

概述

  • 在TypeScript这一强大的类型系统中,开发者可以利用交叉类型(Intersection Types)和联合类型(Union Types)来设计复杂且灵活的类型结构,从而增强代码的类型安全性与表达能力
  • 本文将深入探讨这两种类型的特点、应用场景,并通过实际示例,展示如何在objtype1 & objtype2 & objtype3与objtype1 | objtype2中运用它们

交叉类型(Intersection Types)

  • 交叉类型,是将多个类型合并成一个新类型,这个新类型拥有所有输入类型的所有属性和方法

  • 使用符号 & 来定义交叉类型,它相当于数学中的"交集"概念

  • 在TS中,当你需要一个对象同时满足多个类型要求时,交叉类型就显得尤为重要

    ts 复制代码
    type type1 = { username: string, age: number };
    type type2 = { custname: string, phone: number, age: number };
    type type3 = { address: string };
    
    // type1 & type2 & type3 结果类型拥有三个类型的全部属性
    const xx: type1 & type2 & type3 = {
      username: "wangwu", 
      age: 23, 
      custname: "lisi", 
      phone: 111, 
      address: "shanghai"
    };
  • 在这个例子中,xx 需要同时包含 type1 、 type2 、 type3 的所有属性

  • 即用户名、年龄、客户名、电话号码和地址,这在设计复合对象模型时非常有用

  • 尤其是处理具有多重身份或角色的实体时

联合类型(Union Types)

  • 与交叉类型相对,联合类型通过符号|表示,允许一个值为多种类型中的任意一种

  • 它代表了"或"的关系,一个变量可以是多个类型中的任何一个,但在任一时间点只能是其中一个类型的具体实例

  • 联合类型在处理不确定类型或需要根据上下文推断类型的情况时非常有用

    ts 复制代码
    type type1 = { username: string, age: number };
    type type2 = { custname: string, phone: number, age: number };
    
    const unionObject: type1 | type2 = {
        username: "wangwu", // 有效,因为type1中存在此属性
        age: 23, // 有效,两个类型中都有此属性
        custname: "lisi", // 有效,虽然type1中没有,但type2中有
        phone: 111 // 有效,同上,仅存在于type2中
    };
    
    // 注意:访问非共享属性时需要类型断言或类型守卫
    if ('custname' in unionObject) {
        console.log(unionObject.custname); // lisi
    }
  • 在这个例子中,unionObject可以是objtype1或objtype2的实例

  • 因此它可能包含username、age、custname和phone中的任意组合

  • 处理联合类型时,需要注意只有共享属性可以直接访问

  • 非共享属性访问前需进行类型检查或类型断言

两者区别

1 ) 赋值区别

  • 对于对象类型合成的交叉类型是多个类型属性和方法的合并后的类型,属于多个类型的并集,必须是两个类型的全部属性和方法才能赋值给交叉类型变量【可选属性和方法除外】
  • 对于对象类型合成的联合类型变量可以接受联合类型中任意一种数据类型全部属性和方法,也可以是两个类型的全部属性和全部方法【可选属性和方法除外】,也可以是一种数据类型全部属性和方法+其他类型的某个属性和某个方法

2 )获取属性和方法区别

  • 交叉类型变量可以获取两个类型的任意属性和任意方法
  • 而联合类型的变量只能获取两个类型的共同属性和方法【交集属性和交集方法】

应用场景对比

1 )交叉类型最适合于构建具有复合功能的对象

  • 比如一个类可能需要同时具备多个接口的特性,或者一个对象需要承载不同维度的数据结构
  • 比如:我们把用户信息,用户角色信息合并成一个对象然后输出,当然后端可以通过连接查询的 SQL 语句来完成到前端的多对象输出,但大多需要表的外键来支持,比如用户和角色就需要角色表有用户外键
  • 对于现实生活中有必须关联在一起的实体【比如商品和库存信息】一般建议数据表用外键来支持前端多个对象的合并输出,虽然影响了效率,但也保证了表的数据合理性和完整性
  • 但如果我们临时需要随机把两个根本没有外键关联的数据表取出来的对象合并在一起输出
  • 比如用户信息和日志信息,商品信息和日志信息,订单信息和日志信息,我们就可以用交叉类型来完成
  • 因为我们不可能为了这个临时的对象合并需求把所有的这些表都建立起外键
  • 须知外键太多不仅增加了数据表维护的负担,而且也有较大的影响了表操作效率

2 )联合类型则在处理函数参数的多态性、变量可能的多种类型

* 以及不确定具体类型但需根据运行时上下文确定的场景中大放异彩

* 通过深入理解并灵活运用交叉类型和联合类型

  • 开发者可以在TypeScript项目中实现更高级的类型设计,提高代码的健壮性和可维护性
  • 无论是处理复杂的业务逻辑,还是设计高度灵活的API,这两种类型机制都是不可或缺的工具

综合应用

1 )应用场景

  • 设想一个场景,你需要在前端项目中处理按钮(Button)、链接(Link)和跳转目标(Href)的信息,每个对象携带不同的属性
  • 目标是创建一个泛型函数,能够合并任意数量的这些对象,生成一个包含所有输入对象属性的交叉类型对象,以支持更灵活的配置和数据处理

2 ) 技术要点

  • 泛型函数(Generic Functions):允许函数参数和返回值的类型作为参数,从而增加函数的通用性和灵活性
  • 交叉类型(Intersection Types):通过&操作符将多个类型合并,生成一个新类型,该类型包含所有输入类型的属性和方法
  • 泛型约束(Generic Constraints):通过extends关键字限制泛型参数必须满足的类型条件,保证类型安全

3 ) 首先,定义基础接口和枚举类型

ts 复制代码
interface Button {
  btntype: string;
  text: string;
}

interface Link {
  alt: string;
  href: string;
}

interface Href {
  linktype: string;
  target: OpenLocation;
}

enum OpenLocation {
  Self = 0,
  Blank = '_blank',
  Parent = 'parent',
}

4 ) 定义对象实例

ts 复制代码
const button: Button = {
  btntype: 'primary',
  text: '点击跳转',
};

const link: Link = {
  alt: '访问Google',
  href: 'https://www.google.com',
};

const href: Href = {
  linktype: 'External',
  target: OpenLocation.Blank,
};

5 ) 实现泛型交叉函数cross,它接受多个对象参数并返回它们的交叉类型

ts 复制代码
type Union = Record<string, any>; // 为了简化理解,定义一个泛型约束,任何对象类型

function cross<T extends Union, U extends Union, V extends Union>(
  objOne: T, 
  objTwo: U, 
  objThree?: V
): T & U & V {
  const combined = {} as T & U;

  // 合并objOne的属性
  Object.keys(objOne).forEach(key => {
    combined[key] = objOne[key];
  });

  // 合并objTwo的属性,避免覆盖已存在的键
  Object.keys(objTwo).forEach(key => {
    if (!combined.hasOwnProperty(key)) {
      combined[key] = objTwo[key];
    }
  });

  // 如果有第三个对象,则继续合并
  if (objThree) {
    const finalCombined = combined as T & U & V;
    Object.keys(objThree).forEach(key => {
      if (!finalCombined.hasOwnProperty(key)) {
        finalCombined[key] = objThree[key];
      }
    });
    return finalCombined;
  }

  return combined;
}

// 两个对象交叉
const combinedTwo = cross(button, link);
console.log(combinedTwo);

// 三个对象交叉
const combinedThree = cross(button, link, href);
console.log(combinedThree);

总结

  • 泛型函数:cross<T, U, V>定义了三个泛型参数,代表输入对象的类型。通过泛型,函数可以处理不同类型的对象而无需明确指定具体类型
  • 交叉类型:通过T & U & V,函数返回的类型是输入对象类型的交叉,这意味着返回的对象包含了所有输入对象的属性
  • 泛型约束:虽然示例中未直接使用extends语法进行泛型约束,但通过Union类型间接确保了传入的对象至少是某种对象类型,维持了类型安全
  • 通过此示例,泛型函数和交叉类型实现灵活的数据合并, 在泛型中加入约束来保证类型的安全性
  • 这不仅提升了代码的复用性和可维护性,也为复杂逻辑处理提供了强有力的类型保障
相关推荐
清灵xmf3 小时前
TypeScript 类型进阶指南
javascript·typescript·泛型·t·infer
Amd79411 小时前
Nuxt.js 应用中的 prepare:types 事件钩子详解
typescript·自定义·配置·nuxt·构建·钩子·类型
王解1 天前
Jest项目实战(2): 项目开发与测试
前端·javascript·react.js·arcgis·typescript·单元测试
鸿蒙开天组●2 天前
鸿蒙进阶篇-网格布局 Grid/GridItem(二)
前端·华为·typescript·harmonyos·grid·mate70
zhizhiqiuya2 天前
第二章 TypeScript 函数详解
前端·javascript·typescript
初遇你时动了情2 天前
react 18 react-router-dom V6 路由传参的几种方式
react.js·typescript·react-router
王解2 天前
Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性
前端·javascript·react.js·typescript·单元测试·前端框架
_jiang2 天前
nestjs 入门实战最强篇
redis·typescript·nestjs
清清ww2 天前
【TS】九天学会TS语法---计划篇
前端·typescript
努力变厉害的小超超3 天前
TypeScript中的类型注解、Interface接口、泛型
javascript·typescript