MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

TypeScript基本类型与复合类型的区分

2024-01-083.6k 阅读

一、TypeScript 基本类型概述

TypeScript 作为 JavaScript 的超集,为 JavaScript 带来了静态类型检查的能力。在 TypeScript 中,基本类型是构成类型系统的基础单元。这些基本类型直接映射到 JavaScript 中的原始数据类型,同时又增加了类型安全的特性。

  1. 布尔类型(boolean)
    • 定义与特性:布尔类型只有两个值,truefalse。它主要用于表示逻辑判断的结果,比如一个条件是否成立。在 TypeScript 中,声明布尔类型变量时,编译器会确保该变量只能被赋予 truefalse 值。
    • 代码示例
let isDone: boolean = false;
// 以下赋值会报错,因为 '1' 不是布尔类型
// isDone = 1; 
  1. 数字类型(number)
    • 定义与特性:TypeScript 中的数字类型表示所有的数值,包括整数和浮点数。与 JavaScript 一样,TypeScript 中的数字采用 64 位双精度浮点格式。这意味着它可以表示非常大或非常小的数值,同时也支持常见的数学运算。
    • 代码示例
let myNumber: number = 42;
let pi: number = 3.14;
let largeNumber: number = 1e10;
  1. 字符串类型(string)
    • 定义与特性:字符串类型用于表示文本数据。TypeScript 支持使用单引号(')、双引号(")或模板字符串(```)来定义字符串。模板字符串特别强大,它允许嵌入表达式,增强了字符串的构建灵活性。
    • 代码示例
let name1: string = 'John';
let name2: string = "Doe";
let greeting: string = `Hello, ${name1} ${name2}`;
  1. null 和 undefined
    • 定义与特性null 表示空值,undefined 表示未定义的值。在 TypeScript 中,它们自身也分别是一种类型。默认情况下,nullundefined 是所有类型的子类型,这意味着它们可以赋值给其他类型的变量。但是,当开启 strictNullChecks 编译选项时,它们只能赋值给 nullundefined 类型的变量,以及 void 类型(稍后会介绍)。
    • 代码示例
let myNull: null = null;
let myUndefined: undefined = undefined;
// 在非 strictNullChecks 模式下
let someValue: number = null; 
// 在 strictNullChecks 模式下,上述赋值会报错
  1. void 类型
    • 定义与特性void 类型通常用于表示函数没有返回值。它与 nullundefined 相关,在 strictNullChecks 关闭时,nullundefined 可以赋值给 void 类型。但一般来说,void 类型主要用于函数返回类型的标注。
    • 代码示例
function logMessage(message: string): void {
    console.log(message);
    // 这里不能有 return 语句返回具体值
}
  1. never 类型
    • 定义与特性never 类型表示那些永不存在的值的类型。比如,一个总是抛出异常或根本不会有返回值的函数,其返回类型就是 never。它是所有类型的子类型,但没有类型是 never 的子类型(除了 never 本身)。
    • 代码示例
function throwError(message: string): never {
    throw new Error(message);
}
function infiniteLoop(): never {
    while (true) {}
}
  1. any 类型
    • 定义与特性any 类型是 TypeScript 中最灵活的类型,它允许变量被赋予任何类型的值。当你不确定一个值的类型,或者你想要绕过类型检查时,可以使用 any 类型。然而,过度使用 any 类型会削弱 TypeScript 的类型安全优势。
    • 代码示例
let notSure: any = 42;
notSure = 'hello';
notSure = true;
  1. unknown 类型
    • 定义与特性unknown 类型与 any 类型类似,它也表示任何类型的值。但与 any 不同的是,unknown 类型更加安全。当一个变量是 unknown 类型时,你不能随意对它进行操作,必须先进行类型断言或类型检查,以确保操作的安全性。
    • 代码示例
let value: unknown = 'test';
// 以下操作会报错,因为不能直接对 unknown 类型进行字符串方法调用
// let length: number = value.length; 
if (typeof value ==='string') {
    let length: number = value.length;
}

二、TypeScript 复合类型概述

复合类型是由基本类型或其他复合类型组合而成的类型。它们为描述更复杂的数据结构和关系提供了强大的能力。

  1. 数组类型
    • 定义与特性:数组类型表示一组有序的值。在 TypeScript 中,有两种方式来定义数组类型。一种是使用 类型[] 的语法,另一种是使用 Array<类型> 的泛型语法。数组中的所有元素都必须是相同的类型(除非使用 any[]unknown[])。
    • 代码示例
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];
// 以下赋值会报错,因为字符串不能放入数字数组
// numbers.push('four'); 
  1. 元组类型
    • 定义与特性:元组类型是一种特殊的数组类型,它允许在一个数组中存储不同类型的元素,并且元素的数量和顺序是固定的。元组类型常用于表示有固定结构的数据,比如一个包含 xy 坐标的点。
    • 代码示例
let point: [number, number] = [10, 20];
// 以下赋值会报错,因为元素类型或数量不匹配
// point = [10, 'twenty']; 
// point = [10]; 
  1. 枚举类型
    • 定义与特性:枚举类型用于定义一组命名的常量。它可以让代码更具可读性和可维护性,特别是在表示一组相关的选项或状态时。TypeScript 支持数字枚举和字符串枚举。数字枚举的值默认从 0 开始递增,也可以手动指定初始值。
    • 代码示例
// 数字枚举
enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}
// 字符串枚举
enum Status {
    Success ='success',
    Failure = 'failure'
}
let myDirection: Direction = Direction.Up;
let myStatus: Status = Status.Success;
  1. 对象类型
    • 定义与特性:对象类型用于描述具有特定属性和方法的对象。在 TypeScript 中,可以使用接口(interface)或类型别名(type)来定义对象类型。对象类型可以指定属性的类型,包括必填属性、可选属性和只读属性等。
    • 代码示例
// 使用接口定义对象类型
interface User {
    name: string;
    age: number;
    email?: string;
    readonly id: number;
}
let user: User = {
    name: 'Alice',
    age: 30,
    id: 123
};
// 以下赋值会报错,因为不能修改只读属性 id
// user.id = 456; 
// 使用类型别名定义对象类型
type Point = {
    x: number;
    y: number;
};
let myPoint: Point = {x: 10, y: 20};
  1. 函数类型
    • 定义与特性:函数类型描述了函数的参数和返回值的类型。在 TypeScript 中,可以使用函数类型表达式来定义函数类型,明确参数的个数、类型以及返回值的类型。这有助于在调用函数时进行严格的类型检查。
    • 代码示例
// 定义函数类型
type AddFunction = (a: number, b: number) => number;
let add: AddFunction = function (a, b) {
    return a + b;
};
// 以下调用会报错,因为参数类型不匹配
// add('1', '2'); 
  1. 联合类型
    • 定义与特性:联合类型允许一个变量具有多种类型中的一种。使用竖线(|)来分隔不同的类型。当一个变量是联合类型时,只能访问这些类型共有的属性和方法,或者通过类型断言来访问特定类型的属性和方法。
    • 代码示例
let value: string | number;
value = 'hello';
value = 42;
// 以下操作会报错,因为 string 和 number 没有共同的 length 属性
// console.log(value.length); 
if (typeof value ==='string') {
    console.log(value.length);
}
  1. 交叉类型
    • 定义与特性:交叉类型是将多个类型合并为一个类型,新类型具有所有参与交叉的类型的特性。使用 & 运算符来表示交叉类型。交叉类型常用于将多个对象类型合并,以创建具有多个类型属性的新类型。
    • 代码示例
interface A {
    a: string;
}
interface B {
    b: number;
}
let ab: A & B = {a: 'test', b: 123};

三、基本类型与复合类型的本质区分

  1. 数据结构的复杂度
    • 基本类型:基本类型表示的是单一、不可再分的数据值。例如,number 类型只是一个单独的数值,boolean 类型只有两个简单的逻辑值。它们不包含其他数据结构,是构成更复杂数据的基础单元。
    • 复合类型:复合类型通过组合基本类型或其他复合类型,形成更复杂的数据结构。比如数组是一组相同类型元素的有序集合,元组是不同类型元素的固定结构集合,对象是具有不同属性的集合等。复合类型能够描述现实世界中更复杂的数据关系。
  2. 类型的组合方式
    • 基本类型:基本类型是独立存在的,它们之间不能直接组合形成新的基本类型。例如,不能将 numberstring 组合成一个新的基本类型。
    • 复合类型:复合类型可以通过多种方式进行组合。比如联合类型是将多个类型进行“或”的组合,交叉类型是将多个类型进行“与”的组合。对象类型可以通过接口继承或类型别名的扩展来组合不同的属性集合。
  3. 使用场景
    • 基本类型:主要用于表示简单的数据值,如一个人的年龄(number)、是否完成任务(boolean)、姓名(string)等。在函数参数、局部变量等场景中,如果只需要处理单一类型的值,基本类型就足够了。
    • 复合类型:适用于描述复杂的数据结构和关系。例如,在处理用户信息时,可能需要使用对象类型来包含姓名、年龄、地址等多个属性;在处理一组数据时,可能会使用数组类型;在表示不同状态的组合时,联合类型就很有用。
  4. 类型检查的严格程度
    • 基本类型:类型检查相对简单直接。当一个变量被声明为 number 类型,就只能赋予 number 类型的值,否则编译器会报错。
    • 复合类型:类型检查更为复杂。以对象类型为例,不仅要检查对象的属性是否存在,还要检查属性的类型是否匹配。对于联合类型,需要在使用时根据实际类型进行处理,以避免类型错误。
  5. 内存占用与性能
    • 基本类型:基本类型通常在内存中占用相对较小的空间,并且在操作上具有较高的性能。例如,number 类型的数值运算在现代 JavaScript 引擎中经过了高度优化。
    • 复合类型:复合类型的内存占用和性能取决于其具体结构。例如,数组的内存占用取决于元素的数量和类型,对象的内存占用取决于属性的数量和类型。复杂的复合类型可能会在内存分配和访问上带来一定的性能开销。

四、代码示例对比

  1. 基本类型示例
// 布尔类型示例
let isLoggedIn: boolean = true;
function checkLoginStatus(status: boolean) {
    if (status) {
        console.log('User is logged in.');
    } else {
        console.log('User is not logged in.');
    }
}
checkLoginStatus(isLoggedIn);

// 数字类型示例
let price: number = 10.99;
function calculateTotal(quantity: number) {
    return price * quantity;
}
let total = calculateTotal(5);
console.log(`Total: $${total}`);

// 字符串类型示例
let message: string = 'Hello, world!';
function greet(name: string) {
    return message.replace('world', name);
}
let personalizedGreeting = greet('John');
console.log(personalizedGreeting);
  1. 复合类型示例
// 数组类型示例
let numbers: number[] = [1, 2, 3, 4, 5];
function sumArray(arr: number[]) {
    return arr.reduce((acc, num) => acc + num, 0);
}
let sum = sumArray(numbers);
console.log(`Sum of array: ${sum}`);

// 元组类型示例
let coordinates: [number, number] = [10, 20];
function distanceFromOrigin(point: [number, number]) {
    return Math.sqrt(point[0] * point[0] + point[1] * point[1]);
}
let dist = distanceFromOrigin(coordinates);
console.log(`Distance from origin: ${dist}`);

// 对象类型示例
interface Product {
    name: string;
    price: number;
    inStock: boolean;
}
let myProduct: Product = {
    name: 'Widget',
    price: 19.99,
    inStock: true
};
function displayProduct(product: Product) {
    console.log(`Product: ${product.name}, Price: $${product.price}, In Stock: ${product.inStock? 'Yes' : 'No'}`);
}
displayProduct(myProduct);

// 联合类型示例
let input: string | number;
input = 42;
function processInput(value: string | number) {
    if (typeof value ==='string') {
        console.log(`Length of string: ${value.length}`);
    } else {
        console.log(`Square of number: ${value * value}`);
    }
}
processInput(input);

// 交叉类型示例
interface A {
    a: string;
}
interface B {
    b: number;
}
let ab: A & B = {a: 'test', b: 123};
function printAB(obj: A & B) {
    console.log(`a: ${obj.a}, b: ${obj.b}`);
}
printAB(ab);

通过上述代码示例,可以更直观地看到基本类型和复合类型在使用方式、功能以及应用场景上的区别。基本类型专注于简单数据的表示和操作,而复合类型则为处理复杂数据结构和关系提供了丰富的工具。在实际的前端开发中,正确理解和运用基本类型与复合类型,能够提高代码的可读性、可维护性以及类型安全性。

五、实际开发中的应用场景

  1. 基本类型在表单验证中的应用
    • 在前端开发中,表单是用户输入数据的重要方式。基本类型常用于表单数据的验证。例如,用户注册表单中,年龄字段通常使用 number 类型进行验证。
    • 代码示例
function validateAge(age: number) {
    if (age < 0 || age > 120) {
        throw new Error('Invalid age');
    }
    return true;
}
let userAge: number = 25;
try {
    validateAge(userAge);
    console.log('Age is valid');
} catch (error) {
    console.log(error.message);
}
  1. 复合类型在数据接口响应处理中的应用
    • 当从后端获取数据时,响应数据通常具有复杂的结构,这就需要使用复合类型来准确地描述数据。例如,获取用户列表的接口,响应可能是一个包含多个用户对象的数组。
    • 代码示例
interface User {
    id: number;
    name: string;
    email: string;
}
function handleUserListResponse(response: User[]) {
    response.forEach(user => {
        console.log(`User ID: ${user.id}, Name: ${user.name}, Email: ${user.email}`);
    });
}
// 模拟接口响应数据
let userList: User[] = [
    {id: 1, name: 'Alice', email: 'alice@example.com'},
    {id: 2, name: 'Bob', email: 'bob@example.com'}
];
handleUserListResponse(userList);
  1. 联合类型在组件属性处理中的应用
    • 前端组件可能需要接受不同类型的属性。例如,一个按钮组件可能接受字符串类型的文本,也可能接受一个 React 元素作为自定义内容。
    • 代码示例(以 React 和 TypeScript 为例)
import React from'react';

type ButtonContent = string | React.ReactElement;

interface ButtonProps {
    content: ButtonContent;
}

const Button: React.FC<ButtonProps> = ({content}) => {
    return <button>{typeof content ==='string'? content : content}</button>;
};

// 使用字符串作为内容
<Button content="Click me" />;
// 使用 React 元素作为内容
<Button content={<span>Custom <b>content</b></span>} />;
  1. 交叉类型在功能混合中的应用
    • 在一些情况下,可能需要将多个功能组合到一个对象中。例如,一个具有可点击和可拖拽功能的元素,可以通过交叉类型来定义其属性。
    • 代码示例
interface Clickable {
    onClick: () => void;
}
interface Draggable {
    onDrag: () => void;
}
interface ClickableDraggable extends Clickable, Draggable {}

let element: ClickableDraggable = {
    onClick: () => console.log('Clicked'),
    onDrag: () => console.log('Dragged')
};
element.onClick();
element.onDrag();

通过这些实际开发场景的示例,可以看出基本类型和复合类型在前端开发的不同环节都有着不可或缺的作用。合理运用它们能够使代码更加健壮、易于维护。

六、总结区分要点及注意事项

  1. 区分要点
    • 数据结构:基本类型表示简单、不可再分的数据,复合类型由基本类型或其他复合类型组合而成,用于描述复杂数据结构。
    • 类型组合:基本类型独立,不能直接组合成新基本类型;复合类型可通过联合、交叉等多种方式组合。
    • 使用场景:基本类型用于简单数据处理,复合类型用于复杂数据结构和关系的处理。
    • 类型检查:基本类型检查简单直接,复合类型检查更复杂,涉及属性、元素类型等多方面。
  2. 注意事项
    • 基本类型:在使用基本类型时,要注意类型的严格匹配。例如,不要将 string 类型的值错误地赋给 number 类型的变量。同时,要注意 nullundefined 在不同编译选项下与其他基本类型的关系。
    • 复合类型:对于复合类型,要准确地定义其结构和类型。在使用联合类型时,要注意处理不同类型的兼容性,避免运行时错误。在定义对象类型时,要明确属性的可选性、只读性等特性,以保证代码的健壮性。

在前端开发中,深入理解 TypeScript 的基本类型与复合类型的区分,并在实际项目中正确运用,能够显著提升代码质量,减少潜在的错误,提高开发效率。