TypeScript 5.4 正式发布

公众号:程序员白特,欢迎一起交流学习~

原文作者:前端充电宝

3 月 6 日,TypeScript 发布了 v5.4 版本,该版本带来了以下更新:

  • 类型缩小会在闭包中保留
  • 引入新的实用程序类型 NoInfer<T>
  • 新增Object.groupBy 和 Map.groupBy
  • 新的模块解析选项
  • 新的模块导入检查机制
  • TypeScript 5.5 即将弃用的功能

类型缩小会在闭包中保留

TypeScript 通过类型缩小来优化代码,但在闭包中并不总是保留这些缩小后的类型。从TypeScript 5.4开始,当在非提升函数中使用参数或let变量时,类型检查器会查找最后的赋值点,从而智能地进行类型缩小。然而,如果变量在嵌套函数中被重新分配,即使这种分配不影响其类型,也会使闭包中的类型细化无效。

// TypeScript的类型缩小在闭包中通常不保留  
function exampleFunction(input: string | number) {  
    if (typeof input === "string") {  
        input = parseInt(input); // 假设想要将字符串转为数字  
    }  
  
    return () => {  
        // 在这里,TypeScript不知道input是string还是number  
        // 因为在闭包创建后,input可能已经被修改  
        console.log(input.toString()); // 错误!'input'可能是number,没有toString方法  
    };  
}  

TypeScript 5.4后,当在闭包外部对变量进行最后一次赋值时,类型缩小会在闭包中保留:

function improvedFunction(input: string | number) {  
    let value;  
    if (typeof input === "string") {  
        value = parseInt(input);  
    } else {  
        value = input;  
    }  
  
    return () => {  
        // 在这里,TypeScript知道value是number,因为这是在闭包创建后的最后一次赋值  
        console.log(value.toString()); // 正确!因为现在我们知道value是number  
    };  
}

引入新的实用程序类型 NoInfer

TypeScript的泛型函数能够根据传入的参数自动推断类型。但在某些情况下,这种自动推断可能不符合预期,导致不合法的函数调用被接受,而合法的调用却被拒绝。为了处理这种情况,开发者通常需要添加额外的类型参数来约束函数的行为,确保类型安全。但这种做法可能会使代码看起来更加复杂,特别是当这些额外的类型参数在函数签名中只使用一次时。

TypeScript 5.4 引入了 NoInfer<T> 实用类型,允许开发者明确告诉编译器哪些类型不应该被自动推断。这避免了不合法的函数调用被接受,增强了代码的类型安全性。

考虑以下函数,它接受一个用户ID列表和一个可选的默认用户ID。

function selectUser<U extends string>(userIds: U[], defaultUserId?: U) {  
  // ...  
}  
  
const userIds = ["123", "456", "789"];  
selectUser(userIds, "000"); // 错误地被接受,因为"000"不在userIds中

在这个例子中,即使"000"不在userIds数组中,selectUser函数的调用也会被接受,因为TypeScript自动推断默认用户ID可以是任何字符串。

TypeScript 5.4 中:

function selectUser<U extends string>(userIds: U[], defaultUserId?: NoInfer<U>) {  
  // ...  
}  
  
const userIds = ["123", "456", "789"];  
selectUser(userIds, "000"); // 正确的错误,因为"000"不在userIds中

通过使用 NoInfer<T> 告诉 TypeScript 不要推断默认用户ID的类型,从而确保只有当默认用户ID在userIds数组中时才接受调用。这增强了代码的类型安全性,避免了潜在的错误。

新增 Object.groupBy 和 Map.groupBy

TypeScript 5.4 引入了两个新方法:Object.groupBy 和 Map.groupBy,它们用于根据特定条件将数组元素分组。

  • Object.groupBy 返回一个对象,其中每个键代表一个分组,对应的值是该分组的元素数组。
  • Map.groupBy 返回一个`` Map 对象,实现了相同的功能,但允许使用任何类型的键。

使用 Object.groupBy 和 Map.groupBy 可以方便地根据自定义逻辑对数组进行分组,无需手动创建和填充对象或 Map。然而,在使用 Object.groupBy 时,由于对象的属性名必须是有效的标识符,因此可能无法覆盖所有情况。此外,这些方法目前仅在 esnext 目标或特定库设置下可用。

假设有一个学生数组,每个学生都有姓名和成绩。我们想要根据成绩将学生分为“优秀”和“及格”两组。

const students: { name: string, score: number }[] = [  
  { name: "Alice", score: 90 },  
  { name: "Bob", score: 75 },  
  { name: "Charlie", score: 85 },  
  // ...其他学生  
];  
  
const groupedStudents: { excellent: any[], passing: any[] } = {  
  excellent: [],  
  passing: []  
};  
  
for (const student of students) {  
  if (student.score >= 80) {  
    groupedStudents.excellent.push(student);  
  } else {  
    groupedStudents.passing.push(student);  
  }  
}

使用 Array.prototype.groupBy 方法,可以更简洁地实现相同的功能。

const students: { name: string, score: number }[] = [  
  { name: "Alice", score: 90 },  
  { name: "Bob", score: 75 },  
  { name: "Charlie", score: 85 },  
  // ...其他学生  
];  
  
const groupedStudents = students.groupBy(student => {  
  return student.score >= 80 ? "excellent" : "passing";  
});  
  
// 使用时可以直接访问分组  
console.log(groupedStudents.get("excellent")); // 输出优秀学生数组  
console.log(groupedStudents.get("passing")); // 输出及格学生数组

在这个例子中,groupBy 方法根据每个学生的成绩将学生数组分为“优秀”和“及格”两组,并返回一个 Map 对象,其中键是分组名称,值是对应的学生数组。这种方法更加简洁且易于理解。

新的模块解析选项

TypeScript 5.4 引入了一个新的模块解析选项 bundler,它模拟了现代构建工具(如Webpack、Vite 等)确定导入路径的方式。当与 --module esnext 配合使用时,它允许开发者使用标准的 ECMAScript 导入语法,但禁止了 import ... = require(...) 这种混合语法。

同时,TypeScript 5.4 还增加了一个名为 preserve 的模块选项,该选项允许开发者在 TypeScript 中使用 require(),并更准确地模拟了构建工具和其他运行时环境的模块查找行为。当设置 module 为 preserve 时,构建工具会隐式地成为默认的模块解析策略,同时启用 esModuleInterop 和 resolveJsonModule

假设有一个使用 TypeScript 编写的项目,并且想从一个名为 my-lib 的库中导入两个模块 moduleA 和 moduleB。这个库提供了 ES 模块和 CommonJS 模块两种格式。在 TypeScript 配置中,你可能这样设置:

// tsconfig.json  
{  
  "compilerOptions": {  
    "module": "commonjs",  
    "moduleResolution": "node"  
  }  
}

然后代码中这样导入:

import * as moduleA from 'my-lib/moduleA';  
import * as moduleB = require('my-lib/moduleB');

在这种情况下,TypeScript 可能会为两个导入生成相同的路径,因为它们都使用了 Node.js 的模块解析策略。

在 TypeScript 5.4 中,如果想更精确地控制导入的路径,特别是当库提供了基于导入语法的不同实现时,可以使用 preserve 模块选项和构建工具模块解析策略:

// tsconfig.json  
{  
  "compilerOptions": {  
    "module": "preserve",  
    // 隐式设置:  
    // "moduleResolution": "bundler",  
    // "esModuleInterop": true,  
    // "resolveJsonModule": true  
  }  
}

然后,可以这样编写代码:

import * as moduleA from 'my-lib/moduleA'; // 使用 ES 模块导入  
const moduleB = require('my-lib/moduleB'); // 使用 CommonJS 模块导入

现在,TypeScript 会根据 my-lib 的 package.json 文件中的 exports 字段来决定使用哪个文件路径。如果库为 ES 模块和 CommonJS 模块提供了不同的文件,TypeScript 将根据导入的语法(import 或 require)选择正确的文件。

这意味着开发者可以更精细地控制模块导入的行为,确保与库的意图一致,尤其是在处理那些提供条件导出的库时。

新的模块导入检查机制

TypeScript 5.4 引入了新的模块导入检查机制,确保导入的属性与全局定义的 ImportAttributes 接口相匹配。这种检查提高了代码的准确性,因为任何不符合该接口的导入属性都会导致编译错误。

在早期的 TypeScript 版本中,开发者可以自由地为 import 语句指定任何导入属性,而不会有严格的类型检查。这可能导致运行时错误,因为导入的属性可能与实际的模块不匹配。

// 假设存在一个全局的模块定义,但没有明确的导入属性类型  
import * as myModule from 'my-module' with { custom: 'value' };

在上述代码中,custom 属性是自由定义的,没有与任何全局接口或类型进行匹配,这增加了出错的风险。

在 TypeScript 5.4 及以后的版本中,开发者必须确保导入属性与全局定义的 ImportAttributes 接口相符。这确保了类型安全,并减少了潜在的运行时错误。

// 全局定义的导入属性接口  
interface ImportAttributes {  
    validProperty: string;  
}  
  
// 在模块中导入时,必须使用符合 ImportAttributes 接口的属性  
import * as myModule from 'my-module' with { validProperty: 'someValue' };  
  
// 下面的导入将引发错误,因为属性名称不匹配  
import * as myModule from 'my-module' with { invalidProperty: 'someValue' };  
// 错误:属性 'invalidProperty' 不存在于类型 'ImportAttributes' 中

在这个新版本中,如果开发者尝试使用不符合 ImportAttributes 接口的导入属性,TypeScript 编译器将抛出错误,从而避免了潜在的错误。

TypeScript 5.5 即将弃用的功能

TypeScript 5.0 已经废弃了以下选项和行为:

  • charset
  • target: ES3
  • importsNotUsedAsValues
  • noImplicitUseStrict
  • noStrictGenericChecks
  • keyofStringsOnly
  • suppressExcessPropertyErrors
  • suppressImplicitAnyIndexErrors
  • out
  • preserveValueImports
  • 在项目引用中的prepend
  • 隐式OS特定的newLine

为了在 TypeScript 5.0 及更高版本中继续使用这些已废弃的选项和行为,开发人员必须指定一个新的选项 ignoreDeprecations,并将其值设置为 "5.0"。

注意,TypeScript 5.4 将是这些已废弃选项和行为按预期运作的最后一个版本。在预计于 2024 年 6 月发布的 TypeScript 5.5 中,这些选项和行为将变成严格的错误,使用它们的代码将需要进行迁移以避免编译错误。因此,建议开发人员尽早迁移其代码库,以避免未来兼容性问题。

#前端##我的实习求职记录##2022届毕业生现状##我的求职思考##23届找工作求助阵地#
全部评论

相关推荐

努力成为C语言高手:质疑大祥老师,理解大祥老师,成为大祥老师
点赞 评论 收藏
分享
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务