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

TypeScript 4.0+新特性全景解读

2024-11-042.5k 阅读

一、TypeScript 4.0 简介

TypeScript 4.0 带来了众多令人兴奋的新特性和改进,进一步提升了开发效率与代码质量。它在保持对 JavaScript 兼容性的同时,强化了类型系统,为开发者提供了更强大、更灵活的工具。

二、TypeScript 4.0 新特性

(一)联合类型的推断改进

在 TypeScript 4.0 之前,联合类型的推断有时会表现出一些局限性。例如,当我们有如下代码:

function handleValue(value: string | number) {
    if (typeof value ==='string') {
        return value.length;
    } else {
        return value.toFixed(2);
    }
}

在这个例子中,TypeScript 能够根据 typeof 检查来正确推断 value 在不同分支中的类型。然而,在更复杂的场景下,之前版本的推断可能不够智能。

TypeScript 4.0 对联合类型的推断进行了改进。比如对于函数重载的场景:

function printValue(value: string): void;
function printValue(value: number): void;
function printValue(value: string | number) {
    if (typeof value ==='string') {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

在 4.0 中,TypeScript 能够更准确地在函数体内部基于传入参数的联合类型进行类型推断,使得代码更加健壮,减少了不必要的类型断言。

(二)模板字面量类型的增强

  1. 类型推断更精确 模板字面量类型在 4.0 中得到了进一步增强。考虑以下代码:
type Color ='red' | 'green' | 'blue';
type Shade = 'light' | 'dark';
type ColorShade = `${Shade}-${Color}`;
let colorShade: ColorShade = 'light-red';

这里通过模板字面量类型 ColorShade,TypeScript 4.0 能够精确推断出允许的值。如果我们尝试赋值 'yellow -red',TypeScript 会报错,因为 yellow 并不在 Shade 类型中。这种精确的类型推断有助于在开发过程中尽早发现错误。 2. 映射类型与模板字面量结合 我们还可以将映射类型与模板字面量类型结合使用。例如:

type Keys = 'prop1' | 'prop2';
type Prefix = 'prefix_';
type PrefixedKeys = {
    [K in Keys as `${Prefix}${K}`]: string;
};
let obj: PrefixedKeys = {
    prefix_prop1: 'value1',
    prefix_prop2: 'value2'
};

通过这种方式,我们可以动态地创建带有特定前缀的对象类型,这在构建一些通用工具库或者处理特定格式的数据时非常有用。

(三)unknown 类型的改进

  1. 与 never 的区分更明确 unknown 类型表示任何类型的值,但在使用之前必须进行类型检查。在 TypeScript 4.0 中,unknownnever 类型的区分更加明确。never 类型表示永远不会出现的值,而 unknown 表示可能是任何值。
function handleUnknown(value: unknown) {
    if (typeof value === 'number') {
        return value.toFixed(2);
    } else if (typeof value ==='string') {
        return value.toUpperCase();
    } else {
        return;
    }
}
function handleNever(): never {
    throw new Error('This function never returns');
}

在这个例子中,handleUnknown 处理 unknown 类型的值,需要通过类型检查来进行操作。而 handleNever 函数返回 never 类型,意味着它永远不会正常返回。 2. 类型缩小的增强 TypeScript 4.0 增强了对 unknown 类型的类型缩小功能。例如:

function handleValueAgain(value: unknown) {
    if (Array.isArray(value)) {
        return value.length;
    }
}

在这个例子中,通过 Array.isArray 检查,TypeScript 能够将 value 的类型从 unknown 缩小到 Array<any>,从而可以安全地访问 length 属性。

(四)ES 模块支持的改进

  1. 导入类型的新语法 TypeScript 4.0 引入了新的语法来导入类型。在之前,我们通常这样导入类型:
import { SomeType } from './module';
let value: SomeType;

在 4.0 中,可以使用以下语法:

import type { SomeType } from './module';
let value: SomeType;

这种 import type 语法使得代码更加清晰,明确表明导入的是类型而不是值,有助于提高代码的可读性和维护性。 2. 动态导入类型 TypeScript 4.0 还支持动态导入类型。例如:

async function loadModule() {
    const module = await import('./module');
    let value: typeof module.SomeType;
}

通过这种方式,我们可以在动态导入模块后,安全地使用模块中导出的类型。

(五)函数参数默认值类型推断改进

在 TypeScript 4.0 之前,函数参数默认值的类型推断有时会出现一些问题。例如:

function greet(name = 'world') {
    return `Hello, ${name}`;
}
greet();
greet(123); // 之前可能不会报错,尽管这里传入数字不合适

在 TypeScript 4.0 中,函数参数默认值的类型推断更加严格。如果我们像上面这样传入不合适的类型,TypeScript 会报错,确保函数的使用更加安全。

function greet(name: string = 'world') {
    return `Hello, ${name}`;
}
greet();
greet(123); // TypeScript 4.0 会报错

这样可以避免在运行时因为传入错误类型的参数而导致的错误。

三、TypeScript 4.1 新特性

(一)模板字面量类型的递归条件类型

  1. 递归类型定义 TypeScript 4.1 引入了递归条件类型与模板字面量类型的结合。例如,我们可以定义一个递归类型来处理嵌套对象的属性路径:
type GetPropType<Obj, Path extends string> =
    Path extends `${infer Head}.${infer Tail}`
      ? Head extends keyof Obj
          ? GetPropType<Obj[Head], Tail>
           : never
      : Path extends keyof Obj
          ? Obj[Path]
           : never;

interface User {
    name: string;
    age: number;
    address: {
        city: string;
        country: string;
    };
}
let propType: GetPropType<User, 'address.city'>; // propType 的类型为 string

在这个例子中,GetPropType 类型通过递归地解析模板字面量类型 Path,获取嵌套对象中指定路径的属性类型。这对于处理复杂的对象结构和属性访问非常有用。 2. 实际应用场景 在数据获取和处理的场景中,我们可能需要根据动态的属性路径来获取对象中的值。通过这种递归条件类型与模板字面量类型的结合,可以在类型层面确保属性路径的正确性,避免运行时的错误。例如,在一个 API 数据处理函数中,我们可以根据用户提供的属性路径来获取相应的数据,并且 TypeScript 可以在编译时检查路径的有效性。

(二)改进的控制流分析

  1. 空值检查增强 TypeScript 4.1 对空值检查的控制流分析进行了改进。考虑以下代码:
function printLength(str: string | null) {
    if (str) {
        console.log(str.length);
    }
}

在 4.1 中,TypeScript 能够更智能地识别 if (str) 这样的空值检查,确保在 if 块内部 str 不会为 null。相比之前的版本,这种控制流分析更加精确,减少了不必要的类型断言。 2. 多条件判断的类型推断 对于多个条件判断的情况,TypeScript 4.1 的控制流分析也得到了优化。例如:

function handleValue(value: string | number | null) {
    if (typeof value ==='string') {
        return value.length;
    } else if (typeof value === 'number') {
        return value.toFixed(2);
    } else if (value === null) {
        return 'Value is null';
    }
}

在这个例子中,TypeScript 能够根据不同的条件分支,准确地推断出 value 在每个分支中的类型,使得代码更加安全和可维护。

(三)更好的错误信息

  1. 类型不匹配错误更清晰 当发生类型不匹配的错误时,TypeScript 4.1 提供了更清晰的错误信息。例如,如果我们有如下代码:
let num: number = '123';

TypeScript 4.1 的错误信息会更明确地指出 string 类型不能赋值给 number 类型,并且会提示可能的错误原因,帮助开发者更快地定位和解决问题。 2. 模板字面量类型错误提示 对于模板字面量类型相关的错误,TypeScript 4.1 也提供了更详细的提示。例如,当我们定义了一个模板字面量类型,但是赋值不符合该类型时:

type ColorShade = 'light - red' | 'dark - blue';
let shade: ColorShade = 'light - green';

TypeScript 4.1 会清晰地指出 'light - green' 不在 ColorShade 类型的允许值范围内,方便开发者调整代码。

四、TypeScript 4.2 新特性

(一)类属性的支持

  1. 类属性的类型推断 TypeScript 4.2 开始支持在类中定义属性并进行类型推断。例如:
class User {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}
let user = new User('John', 30);

在这个例子中,nameage 属性的类型在定义时就明确了,并且 TypeScript 能够根据构造函数的参数来推断属性的类型。这使得类的定义更加清晰,也有助于代码的维护和重构。 2. 可选类属性 我们还可以定义可选的类属性:

class Product {
    name: string;
    price: number;
    description?: string;
    constructor(name: string, price: number, description?: string) {
        this.name = name;
        this.price = price;
        if (description) {
            this.description = description;
        }
    }
}
let product1 = new Product('Book', 10);
let product2 = new Product('Pen', 5, 'A writing pen');

在这个例子中,description 属性是可选的,通过 ? 标记。TypeScript 能够正确处理这种可选属性的类型推断和使用。

(二)改进的函数类型兼容性

  1. 逆变参数类型的改进 在函数类型兼容性方面,TypeScript 4.2 对逆变参数类型进行了改进。考虑以下代码:
type Animal = { name: string };
type Dog = Animal & { breed: string };

function handleAnimal(animal: Animal) {
    console.log(animal.name);
}

function handleDog(dog: Dog) {
    console.log(dog.name, dog.breed);
}

let func: (param: Dog) => void = handleAnimal; // 在 4.2 之前可能会报错,4.2 中允许这样的赋值

在这个例子中,handleAnimal 函数接受 Animal 类型的参数,handleDog 函数接受更具体的 Dog 类型的参数。在 TypeScript 4.2 中,允许将 handleAnimal 赋值给期望 (param: Dog) => void 类型的变量,因为 DogAnimal 的子类型,这种改进使得函数类型的兼容性更加合理。 2. 协变返回类型的一致性 TypeScript 4.2 还在协变返回类型方面保持了更好的一致性。例如:

function getAnimal(): Animal {
    return { name: 'Generic Animal' };
}

function getDog(): Dog {
    return { name: 'Buddy', breed: 'Golden Retriever' };
}

let getPet: () => Dog = getAnimal; // 4.2 会报错,因为返回类型不兼容,保持了协变返回类型的一致性

在这个例子中,getAnimal 返回 Animal 类型,getDog 返回 Dog 类型。在 4.2 中,将 getAnimal 赋值给期望返回 Dog 类型的 getPet 会报错,因为 Animal 不是 Dog 的子类型,确保了协变返回类型的正确性。

(三)元组类型的改进

  1. 元组类型的展开 TypeScript 4.2 支持在函数参数和返回值中展开元组类型。例如:
function addNumbers([a, b]: [number, number]): number {
    return a + b;
}
let result = addNumbers([1, 2]);

function getTuple(): [string, number] {
    return ['value', 10];
}
let [str, num] = getTuple();

在这个例子中,通过展开元组类型,我们可以更方便地处理函数参数和返回值中的元组数据。这种改进使得元组类型的使用更加灵活和直观。 2. 元组类型与剩余参数的结合 我们还可以将元组类型与剩余参数结合使用:

function printValues(...values: [string, number, ...boolean[]]) {
    console.log(values[0], values[1]);
    values.slice(2).forEach((bool) => console.log(bool));
}
printValues('text', 10, true, false);

在这个例子中,通过 ...boolean[] 这种形式,我们可以在元组类型中包含可变数量的布尔值,使得函数参数的定义更加灵活。

五、TypeScript 4.3 新特性

(一)可选链和空值合并操作符的类型兼容性

  1. 可选链操作符的类型推断 TypeScript 4.3 对可选链操作符 ?. 的类型推断进行了改进。例如:
interface User {
    address?: {
        city: string;
    };
}
let user: User | null = null;
let city = user?.address?.city;

在这个例子中,TypeScript 4.3 能够正确推断出 city 的类型为 string | undefined。这使得在使用可选链操作符时,类型的处理更加准确,减少了不必要的类型断言。 2. 空值合并操作符的类型兼容性 对于空值合并操作符 ??,TypeScript 4.3 也改进了其类型兼容性。例如:

let value1: string | null | undefined = null;
let value2 = value1?? 'default value';

在这个例子中,TypeScript 4.3 能够正确推断出 value2 的类型为 string。这确保了在使用空值合并操作符时,类型的处理符合预期,提高了代码的安全性。

(二)模板字面量类型中的 infer 类型变量

  1. infer 在模板字面量类型中的使用 TypeScript 4.3 允许在模板字面量类型中使用 infer 类型变量。例如:
type ParsePath<Path extends string> =
    Path extends `${infer Head}/${infer Tail}`
      ? [Head, ...ParsePath<Tail>]
      : [Path];

let pathSegments: ParsePath<'users/john/profile'>; // pathSegments 的类型为 ['users', 'john', 'profile']

在这个例子中,通过在模板字面量类型中使用 infer,我们可以解析字符串路径并将其转换为数组类型。这在处理路由路径、文件路径等场景中非常有用,能够在类型层面进行路径的解析和验证。 2. 结合条件类型的复杂应用 我们还可以将其与条件类型结合,实现更复杂的类型转换。例如:

type ParseValue<Value extends string> =
    Value extends `${infer Num extends number}`
      ? Num
      : Value;

let parsedValue: ParseValue<'123'>; // parsedValue 的类型为 123
let parsedValue2: ParseValue<'text'>; // parsedValue2 的类型为 'text'

在这个例子中,根据字符串是否能解析为数字,使用 infer 在模板字面量类型中进行不同的类型推断。

(三)改进的导入类型语法

  1. 更简洁的导入语法 TypeScript 4.3 进一步简化了导入类型的语法。在之前版本中,我们可能这样导入类型:
import { SomeType } from './module';
import type { AnotherType } from './module';

在 4.3 中,可以合并为:

import { type SomeType, type AnotherType } from './module';

这种语法使得导入类型的代码更加简洁,提高了代码的可读性。 2. 与默认导入的结合 TypeScript 4.3 还允许在默认导入时使用类型导入语法。例如:

import type DefaultType, { type OtherType } from './module';

这在处理既有默认导出又有类型导出的模块时非常方便,能够清晰地区分值导入和类型导入。

六、TypeScript 4.4 新特性

(一)新的控制流分析规则

  1. 函数调用的控制流分析 TypeScript 4.4 对函数调用的控制流分析进行了改进。例如,考虑以下代码:
function isString(value: unknown): value is string {
    return typeof value ==='string';
}

function handleValue(value: unknown) {
    if (isString(value)) {
        console.log(value.length);
    }
}

在这个例子中,TypeScript 4.4 能够更准确地识别 isString 函数对 value 类型的缩小作用。即使 isString 函数定义在其他地方,TypeScript 也能在调用处正确进行控制流分析,确保在 if 块内 valuestring 类型。 2. 逻辑运算符的控制流分析 对于逻辑运算符 &&||,TypeScript 4.4 的控制流分析也得到了增强。例如:

let value: string | null | undefined;
if (value && value.length > 0) {
    console.log('Value is a non - empty string');
}

在这个例子中,TypeScript 4.4 能够根据 && 运算符的特性,正确推断出在 if 块内 value 既不是 null 也不是 undefined,并且是一个长度大于 0 的 string 类型,使得代码的类型安全性更高。

(二)模板字面量类型的条件类型约束

  1. 条件类型约束的应用 TypeScript 4.4 允许在模板字面量类型中使用条件类型约束。例如:
type CapitalizeFirstLetter<Str extends string> =
    Str extends `${infer First}${infer Rest}`
      ? `${Uppercase<First>}${Rest}`
      : Str;

type Result = CapitalizeFirstLetter<'hello'>; // Result 的类型为 'Hello'

在这个例子中,通过条件类型约束,我们可以对模板字面量类型进行更灵活的转换。这里将字符串的首字母转换为大写,并且 TypeScript 能够准确推断出转换后的类型。 2. 结合其他类型操作 我们还可以将其与其他类型操作结合使用。例如:

type ReplaceString<Str extends string, Find extends string, Replace extends string> =
    Str extends `${infer Before}${Find}${infer After}`
      ? `${Before}${Replace}${After}`
      : Str;

type ReplacedResult = ReplaceString<'hello world', 'world', 'TypeScript'>; // ReplacedResult 的类型为 'hello TypeScript'

在这个例子中,通过结合条件类型约束和模板字面量类型,实现了字符串替换的类型层面操作。

(三)改进的类型别名和接口合并

  1. 更宽松的合并规则 TypeScript 4.4 对类型别名和接口的合并规则进行了改进,使其更加宽松。例如,我们有如下定义:
interface User {
    name: string;
}
type User = {
    age: number;
} & User;

在之前的版本中,这种定义可能会导致错误,但在 4.4 中,TypeScript 能够正确合并 User 接口和 User 类型别名,得到一个包含 nameage 属性的类型。这使得代码在进行类型扩展和重构时更加灵活。 2. 属性冲突的处理 当合并的类型或接口存在属性冲突时,TypeScript 4.4 也提供了更清晰的错误提示。例如,如果我们有:

interface User {
    name: string;
}
type User = {
    name: number;
} & User;

TypeScript 4.4 会明确指出 name 属性的类型冲突,帮助开发者快速定位和解决问题。

七、TypeScript 4.5 新特性

(一)对私有字段和方法的支持

  1. 私有字段的定义与使用 TypeScript 4.5 开始支持私有字段,通过 # 前缀来定义。例如:
class MyClass {
    #privateField: string;
    constructor(privateField: string) {
        this.#privateField = privateField;
    }
    getPrivateField() {
        return this.#privateField;
    }
}
let myClass = new MyClass('private value');
console.log(myClass.getPrivateField());
// console.log(myClass.#privateField); // 报错,无法从类外部访问私有字段

在这个例子中,#privateField 是一个私有字段,只能在类内部访问。这增强了类的封装性,提高了代码的安全性和可维护性。 2. 私有方法的实现 我们还可以定义私有方法:

class MyClass2 {
    #privateMethod() {
        return 'This is a private method';
    }
    publicMethod() {
        return this.#privateMethod();
    }
}
let myClass2 = new MyClass2();
console.log(myClass2.publicMethod());
// console.log(myClass2.#privateMethod()); // 报错,无法从类外部访问私有方法

私有方法可以在类内部被其他方法调用,但不能从类外部直接调用,进一步强化了类的封装。

(二)改进的模板字面量类型推断

  1. 更智能的字符串字面量推断 TypeScript 4.5 对模板字面量类型的字符串字面量推断更加智能。例如:
type Color ='red' | 'green' | 'blue';
type Shade = 'light' | 'dark';
type ColorShade = `${Shade}-${Color}`;
function getColorShade(shade: Shade, color: Color): ColorShade {
    return `${shade}-${color}` as ColorShade;
}
let shade1 = getColorShade('light','red');

在这个例子中,TypeScript 4.5 能够更准确地推断出 getColorShade 函数返回值的类型,确保返回值符合 ColorShade 类型定义。即使没有显式的类型断言,也能正确进行类型推断。 2. 结合泛型的模板字面量推断 当模板字面量类型与泛型结合时,TypeScript 4.5 的推断也得到了改进。例如:

type Prefix<Str extends string> = `prefix_${Str}`;
function createPrefixedValue<Value extends string>(value: Value): Prefix<Value> {
    return `prefix_${value}` as Prefix<Value>;
}
let prefixedValue = createPrefixedValue('example');

在这个例子中,TypeScript 4.5 能够根据泛型 Value 准确推断出 createPrefixedValue 函数返回值的类型,使得代码更加类型安全。

(三)改进的 JSX 支持

  1. 更灵活的 JSX 类型定义 TypeScript 4.5 为 JSX 提供了更灵活的类型定义。例如,在 React 项目中,我们可以更方便地定义自定义组件的类型:
import React from'react';

interface MyButtonProps {
    text: string;
    onClick: () => void;
}

const MyButton: React.FC<MyButtonProps> = ({ text, onClick }) => {
    return <button onClick={onClick}>{text}</button>;
};

在这个例子中,TypeScript 4.5 能够更好地处理 React.FC 类型,使得自定义组件的类型定义更加简洁和准确。 2. JSX 元素属性的类型检查 对于 JSX 元素的属性,TypeScript 4.5 的类型检查更加严格和准确。例如,如果我们在 MyButton 组件中遗漏了 text 属性或者传入了错误类型的 onClick 属性,TypeScript 会清晰地报错,帮助开发者快速发现和修复问题。

八、TypeScript 4.6 新特性

(一)const 断言的改进

  1. 数组和对象字面量的 const 断言 TypeScript 4.6 对 const 断言在数组和对象字面量上的应用进行了改进。例如:
const arr = [1, 2, 3] as const;
// arr[0] = 4; // 报错,因为 arr 是 const 断言的数组,不可变
type ArrType = typeof arr; // ArrType 的类型为 readonly [1, 2, 3]

const obj = { name: 'John', age: 30 } as const;
// obj.age = 31; // 报错,因为 obj 是 const 断言的对象,不可变
type ObjType = typeof obj; // ObjType 的类型为 { readonly name: "John"; readonly age: 30; }

在这个例子中,const 断言使得数组和对象字面量具有只读属性,并且 TypeScript 能够准确推断出其类型。这在定义一些不可变的数据结构时非常有用,提高了代码的安全性。 2. 函数参数的 const 断言 我们还可以在函数参数中使用 const 断言。例如:

function handleArray(arr: readonly number[]) {
    // 这里只能读取数组,不能修改
}
const myArr = [1, 2, 3] as const;
handleArray(myArr);

在这个例子中,通过 const 断言,确保了传递给 handleArray 函数的数组是只读的,避免在函数内部意外修改数组。

(二)改进的类型兼容性检查

  1. 函数类型兼容性的细化 TypeScript 4.6 对函数类型兼容性的检查更加细化。例如,对于函数参数的可选性和剩余参数的处理:
function func1(a: number, b?: string) {}
function func2(...args: [number, string?]) {}

let func3: typeof func1 = func2; // 在 4.6 之前可能存在兼容性问题,4.6 中可以正确处理

在这个例子中,TypeScript 4.6 能够更准确地判断 func2func1 的兼容性,即使它们的参数定义形式略有不同。这使得函数类型的赋值更加灵活,同时保证类型安全。 2. 类类型兼容性的改进 对于类类型的兼容性,TypeScript 4.6 也有改进。例如:

class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}
class Dog extends Animal {
    breed: string;
    constructor(name: string, breed: string) {
        super(name);
        this.breed = breed;
    }
}

let animal: Animal = new Dog('Buddy', 'Golden Retriever');

在这个例子中,TypeScript 4.6 能够更清晰地处理子类与父类之间的类型兼容性,确保代码在继承和类型赋值方面更加稳健。

(三)新的类型查询操作符

  1. satisfies 操作符 TypeScript 4.6 引入了 satisfies 操作符。例如:
interface User {
    name: string;
    age: number;
}
let userData = { name: 'John', age: 30 } satisfies User;
// 这里 userData 的类型会被推断为 { name: string; age: number; } & User

在这个例子中,satisfies 操作符用于检查一个对象字面量是否满足某个类型,并且会根据该类型进行更精确的类型推断。这在定义对象时,既能保证对象符合特定类型,又能获得更细化的类型推断,有助于提高代码的可读性和维护性。 2. 实际应用场景 在配置文件的处理中,我们可以使用 satisfies 操作符来确保配置对象符合预期的类型。例如:

interface Config {
    apiUrl: string;
    debug: boolean;
}
let config = { apiUrl: 'https://example.com', debug: true } satisfies Config;

这样可以在定义配置对象时,明确其符合 Config 类型,并且获得准确的类型推断,避免在后续使用中因为类型不匹配而导致的错误。

九、TypeScript 4.7 新特性

(一)装饰器元数据 API 的支持

  1. 装饰器元数据的定义与获取 TypeScript 4.7 开始支持装饰器元数据 API。例如,我们可以定义一个装饰器来添加元数据:
import 'reflect - metadata';
const metadataKey = 'description';

function describe(value: string) {
    return function (target: any, propertyKey: string) {
        Reflect.defineMetadata(metadataKey, value, target, propertyKey);
    };
}

class MyClass {
    @describe('This is a sample method')
    sampleMethod() {}
}

let description = Reflect.getMetadata(metadataKey, MyClass.prototype,'sampleMethod');
console.log(description); // 输出: This is a sample method

在这个例子中,通过 Reflect.defineMetadata 方法,我们可以在装饰器中为类的方法添加元数据,然后使用 Reflect.getMetadata 方法获取这些元数据。这在实现一些基于元数据的功能,如依赖注入、验证等方面非常有用。 2. 类装饰器的元数据处理 对于类装饰器,也可以进行类似的元数据操作:

import 'reflect - metadata';
const classMetadataKey = 'class - description';

function classDescribe(value: string) {
    return function (target: any) {
        Reflect.defineMetadata(classMetadataKey, value, target);
    };
}

@classDescribe('This is a sample class')
class AnotherClass {}

let classDescription = Reflect.getMetadata(classMetadataKey, AnotherClass);
console.log(classDescription); // 输出: This is a sample class

通过这种方式,我们可以为整个类添加元数据,为代码的进一步扩展和功能实现提供基础。

(二)改进的类型推断与控制流分析

  1. 复杂条件下的类型推断 TypeScript 4.7 对复杂条件下的类型推断进行了改进。例如,当我们有多个嵌套的条件判断时:
function handleValue(value: string | number | null) {
    if (value!== null) {
        if (typeof value ==='string') {
            return value.length;
        } else if (typeof value === 'number') {
            return value.toFixed(2);
        }
    }
}

在这个例子中,TypeScript 4.7 能够更准确地在嵌套的条件块中进行类型推断,确保在不同的条件分支中 value 的类型被正确识别。这提高了代码在复杂逻辑下的类型安全性。 2. 控制流分析的优化 TypeScript 4.7 对控制流分析进行了优化,特别是在处理 if - else 链和 switch 语句时。例如:

function handleSwitch(value: string | number) {
    switch (typeof value) {
        case'string':
            return value.toUpperCase();
        case 'number':
            return value.toFixed(2);
    }
}

在这个例子中,TypeScript 4.7 能够根据 switch 语句的条件,准确地推断出 value 在不同分支中的类型,使得代码更加可靠。

(三)模板字面量类型中的映射类型

  1. 映射类型在模板字面量中的应用 TypeScript 4.7 允许在模板字面量类型中使用映射类型。例如:
type Keys = 'prop1' | 'prop2';
type Prefix = 'prefix_';
type PrefixedKeys = {
    [K in Keys as `${Prefix}${K}`]: string;
};
let obj: PrefixedKeys = {
    prefix_prop1: 'value1',
    prefix_prop2: 'value2'
};

在这个例子中,通过映射类型和模板字面量类型的结合,我们可以动态地创建带有特定前缀的对象类型。这在构建一些通用的工具库或者处理特定格式的数据时非常方便,能够在类型层面进行灵活的定义和操作。 2. 结合条件类型的复杂应用 我们还可以将其与条件类型结合,实现更复杂的类型转换。例如:

type Keys2 = 'prop1' | 'prop2';
type Condition = true;
type ModifiedKeys = {
    [K in Keys2 as Condition extends true? `modified_${K}` : K]: string;
};
let obj2: ModifiedKeys = {
    modified_prop1: 'value1',
    modified_prop2: 'value2'
};

在这个例子中,根据 Condition 的值,通过条件类型在模板字面量类型中动态地修改对象属性的前缀,展示了在类型层面进行复杂逻辑操作的能力。

十、TypeScript 4.8 新特性

(一)改进的类型检查与错误信息

  1. 更详细的类型错误提示 TypeScript 4.8 提供了更详细的类型错误提示。例如,当我们有如下代码:
let num: number = '123';

TypeScript 4.8 的错误信息不仅会指出 string 类型不能赋值给 number 类型,还会提供更多关于可能导致错误的上下文信息,例如在哪个文件、哪一行代码出现了这个问题,以及可能的修正建议。这使得开发者能够更快地定位和解决类型错误。 2. 复杂类型结构的错误提示 对于复杂的类型结构,如嵌套的对象类型、联合类型等,TypeScript 4.8 的错误提示也更加清晰。例如:

interface User {
    name: string;
    age: number;
    address: {
        city: string;
        country: string;
    };
}
let user: User = {
    name: 'John',
    age: 30,
    address: {
        city: 'New York'
        // 缺少 country 属性,4.8 会给出更明确的错误提示
    }
};

在这个例子中,TypeScript 4.8 会明确指出 address 对象中缺少 country 属性,帮助开发者快速发现和修复问题。

(二)模板字面量类型中的条件类型改进

  1. 更灵活的条件类型嵌套 TypeScript 4.8 允许在模板字面量类型中进行更灵活的条件类型嵌套。例如:
type Condition1 = true;
type Condition2 = false;
type ResultType = Condition1 extends true
  ? Condition2 extends true
        ? 'both true'
         : 'first true, second false'
    : 'first false';

let result: ResultType; // result 的类型为 'first true, second false'

在这个例子中,通过多层条件类型的嵌套,TypeScript 4.8 能够准确地推断出 ResultType 的类型。这种灵活性在处理复杂的类型逻辑时非常有用,例如根据不同的运行时条件生成不同的类型。 2. 结合其他类型操作符 我们还可以将条件类型与其他类型操作符结合使用。例如:

type Keys = 'prop1' | 'prop2';
type Prefix = 'prefix_';
type ConditionalPrefix<Key extends Keys, Cond extends boolean> = Cond extends true
  ? `${Prefix}${Key}`
    : Key;
type ConditionalObject<Cond extends boolean> = {
    [K in Keys as ConditionalPrefix<K, Cond>]: string;
};
let obj: ConditionalObject<true> = {
    prefix_prop1: 'value1',
    prefix_prop2: 'value2'
};

在这个例子中,通过结合条件类型和映射类型,根据条件 Cond 动态地生成带有不同前缀的对象类型,展示了在类型层面进行复杂操作的能力。

(三)ES 模块的进一步优化

  1. 导入类型的性能优化 TypeScript 4.8 对导入类型的性能进行了优化。在大型项目中,当有大量的类型导入时,这一优化能够显著提高编译速度。例如,在一个包含多个模块和大量类型导入的项目中,使用 TypeScript 4.8 进行编译会比之前的版本更快,减少了开发者等待编译的时间。
  2. 导入路径的解析改进 TypeScript 4.8 对导入路径的解析也进行了改进。它能够更好地处理相对路径和绝对路径的导入,并且在遇到导入路径错误时,提供更准确的错误信息。例如,如果导入路径拼写错误,TypeScript 4.8 会明确指出错误的路径以及可能的正确路径,帮助开发者快速修复导入问题。