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

TypeScript静态类型的入门理解

2021-03-072.5k 阅读

一、TypeScript 静态类型的基本概念

在深入探讨 TypeScript 静态类型之前,我们先来了解一下什么是静态类型。静态类型是指在编译阶段就确定变量的数据类型,而不是在运行时。与动态类型语言(如 JavaScript)不同,动态类型语言在运行时才会根据变量的值来确定其类型。

在 TypeScript 中,静态类型的使用带来了许多好处。它可以帮助开发者在编码阶段就发现类型不匹配的错误,提高代码的可维护性和可读性。例如,在一个大型项目中,如果一个函数期望接收一个数字类型的参数,但不小心传入了一个字符串,在动态类型语言中,这个错误可能在运行时才会暴露出来,而在 TypeScript 中,编译器会在编译阶段就指出这个问题。

下面我们来看一个简单的 TypeScript 代码示例:

let num: number = 10;
num = "ten"; // 这里会报错,因为类型不匹配

在上述代码中,我们声明了一个变量 num,并指定其类型为 number。当我们尝试将一个字符串赋值给 num 时,TypeScript 编译器会报错,提示类型不兼容。

二、基本数据类型的静态类型声明

  1. 数字类型(number) 在 TypeScript 中,数字类型涵盖了所有的数值,包括整数和浮点数。声明数字类型变量的方式如下:
let age: number = 25;
let pi: number = 3.14;
  1. 字符串类型(string) 字符串类型用于表示文本数据。可以使用单引号或双引号来定义字符串。
let name: string = 'John';
let message: string = "Hello, world!";
  1. 布尔类型(boolean) 布尔类型只有两个值:truefalse,常用于逻辑判断。
let isDone: boolean = true;
let hasError: boolean = false;
  1. null 和 undefined nullundefined 在 TypeScript 中有特殊的类型含义。null 表示空值,undefined 表示未定义的值。默认情况下,它们是所有类型的子类型。
let nothing: null = null;
let notDefined: undefined = undefined;

不过,在严格模式下(strictNullChecks 开启),它们只能赋值给自身类型和 void 类型。 5. void 类型 void 类型通常用于表示函数没有返回值。例如:

function printMessage(): void {
    console.log('This function has no return value');
}
  1. never 类型 never 类型表示那些永远不会发生的返回值。例如,一个抛出异常或无限循环的函数可以返回 never 类型。
function throwError(message: string): never {
    throw new Error(message);
}
function infiniteLoop(): never {
    while (true) {}
}

三、数组类型的静态类型声明

  1. 简单数组类型 在 TypeScript 中,可以通过两种方式声明数组类型。一种是在元素类型后面加上 [],另一种是使用 Array<元素类型> 的形式。
// 方式一
let numbers: number[] = [1, 2, 3];
// 方式二
let strings: Array<string> = ['a', 'b', 'c'];
  1. 元组类型(Tuple) 元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。例如,我们可以定义一个表示坐标的元组:
let point: [number, number] = [10, 20];

元组类型对元素的数量和顺序有严格要求。如果访问越界的元素,TypeScript 会给出警告。

let point: [number, number] = [10, 20];
let x = point[0]; // 正确
let y = point[1]; // 正确
let z = point[2]; // 这里会报错,因为元组只有两个元素

四、对象类型的静态类型声明

  1. 对象字面量类型 在 TypeScript 中,可以为对象字面量定义类型。例如,我们定义一个表示用户信息的对象:
let user: { name: string; age: number } = { name: 'Alice', age: 30 };

这里我们定义了一个 user 对象,它必须包含 name 属性(类型为 string)和 age 属性(类型为 number)。如果对象缺少或多了属性,TypeScript 会报错。

// 缺少 age 属性,会报错
let user1: { name: string; age: number } = { name: 'Bob' };
// 多了 gender 属性,会报错
let user2: { name: string; age: number } = { name: 'Charlie', age: 25, gender:'male' };
  1. 接口(Interface) 接口是 TypeScript 中用于定义对象类型的重要方式。接口可以重复使用,并且可以继承其他接口。
interface User {
    name: string;
    age: number;
}
let user: User = { name: 'David', age: 28 };

接口还支持可选属性和只读属性。

interface User {
    name: string;
    age: number;
    email?: string; // 可选属性
    readonly id: number; // 只读属性
}
let user: User = { name: 'Eve', age: 22, id: 1 };
user.id = 2; // 这里会报错,因为 id 是只读属性
  1. 类型别名(Type Alias) 类型别名也可以用于定义对象类型,它和接口有一些相似之处,但也有一些区别。
type User = {
    name: string;
    age: number;
};
let user: User = { name: 'Frank', age: 35 };

类型别名可以用于其他类型,如联合类型、交叉类型等,而接口主要用于定义对象类型。

五、函数类型的静态类型声明

  1. 函数参数和返回值类型 在 TypeScript 中,可以明确指定函数的参数类型和返回值类型。
function add(a: number, b: number): number {
    return a + b;
}
let result = add(5, 3);

如果传入的参数类型不匹配,TypeScript 会报错。

let result = add('5', 3); // 这里会报错,因为第一个参数应该是 number 类型
  1. 函数重载 函数重载允许一个函数根据不同的参数列表有不同的实现。例如,我们定义一个 print 函数,可以接受不同类型的参数并进行不同的打印操作。
function print(value: string): void;
function print(value: number): void;
function print(value: any): void {
    if (typeof value ==='string') {
        console.log('Printing string:', value);
    } else if (typeof value === 'number') {
        console.log('Printing number:', value);
    }
}
print('Hello');
print(10);

在上述代码中,我们先定义了两个函数签名,然后再实现具体的函数。这样可以让编译器根据传入的参数类型选择合适的函数实现。

六、联合类型和交叉类型

  1. 联合类型(Union Type) 联合类型表示一个值可以是多种类型中的一种。使用 | 来分隔不同的类型。
let value: string | number;
value = 'abc';
value = 123;

当使用联合类型的变量时,只能访问这些类型共有的属性和方法。

function printValue(value: string | number) {
    // 这里只能访问 string 和 number 共有的属性,如 toString()
    console.log(value.toString());
}
  1. 交叉类型(Intersection Type) 交叉类型表示一个值同时具有多种类型的属性和方法。使用 & 来连接不同的类型。
interface A {
    a: string;
}
interface B {
    b: number;
}
let obj: A & B = { a: 'hello', b: 10 };

交叉类型常用于合并多个接口的功能。

七、类型断言

类型断言是告诉编译器“相信我,我知道自己在做什么”。当你比编译器更了解某个值的类型时,可以使用类型断言。

  1. 尖括号语法
let value: any = 'hello';
let length: number = (<string>value).length;
  1. as 语法
let value: any = 'hello';
let length: number = (value as string).length;

在 TypeScript 的 JSX 语法中,必须使用 as 语法进行类型断言。

八、类型推断

TypeScript 具有类型推断功能,它可以根据变量的赋值自动推断出变量的类型。

let num = 10; // 这里 TypeScript 推断 num 为 number 类型

在函数返回值类型推断方面,TypeScript 也能做得很好。

function add(a, b) {
    return a + b;
}
let result = add(5, 3); // 这里 result 被推断为 number 类型

然而,在某些复杂情况下,可能需要手动指定类型以避免推断错误。

九、泛型

  1. 泛型函数 泛型允许我们创建可复用的组件,这些组件可以支持多种类型。例如,我们定义一个返回数组第一个元素的泛型函数。
function getFirst<T>(array: T[]): T | undefined {
    return array.length > 0? array[0] : undefined;
}
let numbers = [1, 2, 3];
let firstNumber = getFirst(numbers);
let strings = ['a', 'b', 'c'];
let firstString = getFirst(strings);

在上述代码中,<T> 是类型参数,它可以代表任何类型。当我们调用 getFirst 函数时,TypeScript 会根据传入的数组类型自动推断 T 的具体类型。 2. 泛型接口 我们也可以定义泛型接口。例如:

interface GenericIdentityFn<T> {
    (arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;

这里定义了一个泛型接口 GenericIdentityFn,它描述了一个接收和返回相同类型参数的函数。然后我们定义了一个符合该接口的泛型函数 identity,并将其赋值给 myIdentity,指定 Tnumber 类型。 3. 泛型类 泛型类可以在类的定义中使用类型参数。例如:

class Stack<T> {
    private items: T[] = [];
    push(item: T) {
        this.items.push(item);
    }
    pop(): T | undefined {
        return this.items.pop();
    }
}
let numberStack = new Stack<number>();
numberStack.push(1);
let poppedNumber = numberStack.pop();
let stringStack = new Stack<string>();
stringStack.push('a');
let poppedString = stringStack.pop();

在这个例子中,Stack 类是一个泛型类,它可以存储任何类型的数据。通过在实例化时指定类型参数,我们可以创建不同类型的栈。

十、静态类型在前端开发中的应用场景

  1. 大型项目的代码维护 在大型前端项目中,随着代码量的增加,类型错误可能会变得难以排查。使用 TypeScript 的静态类型可以在编译阶段发现许多潜在的类型问题,使得代码的维护更加容易。例如,在一个多人协作的 React 项目中,使用 TypeScript 可以明确组件的属性类型和函数的参数、返回值类型,减少因类型不匹配导致的 bug。
  2. 与后端数据交互 前端需要与后端进行数据交互,通常会接收 JSON 数据。使用 TypeScript 可以根据后端返回的数据结构定义相应的类型,确保在处理数据时不会出现类型错误。例如,后端返回一个用户信息的 JSON 对象,前端可以使用接口来定义这个对象的类型,然后在代码中安全地使用这些数据。
  3. 组件库开发 当开发前端组件库时,使用静态类型可以为组件的使用者提供清晰的接口定义。例如,开发一个按钮组件库,使用 TypeScript 可以明确按钮的属性类型,如按钮的文本、颜色、大小等属性的类型,使得其他开发者在使用组件时能够准确地传入正确类型的参数。

通过以上对 TypeScript 静态类型的深入理解和各种应用场景的介绍,相信你已经对 TypeScript 的静态类型有了较为全面的认识。在实际的前端开发中,合理运用 TypeScript 的静态类型可以显著提高代码的质量和可维护性。