发布于 2026-01-06 6 阅读
0

3 个我学习 TypeScript 时希望知道的 TypeScript 小技巧 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

3 个我学习 TypeScript 时希望知道的 TypeScript 小技巧

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

第一名:只读<T>

让我们从一个简单的例子开始:
我们有一个简单的函数,它接受一个数字数组,并返回一个所有元素都已排序的数组。

function sortNumbers(array: Array<number>) {
  return array.sort((a, b) => a - b)
}
Enter fullscreen mode Exit fullscreen mode

现在看看下面的代码,检查一下是否一切正常。想想控制台会输出什么。我建议你花点时间认真思考一下!

const numbers = [7, 3, 5];

const sortedNumbers = sortNumbers(numbers);

console.log(sortedNumbers);
console.log(numbers)
Enter fullscreen mode Exit fullscreen mode

第一个输出很简单,就是[3, 5, 7]。但是现在请注意,第二个输出也是一样的!你可能会问:为什么?我把数组定义为 ,const它怎么可能改变呢

在 JavaScript 中,数组和对象非常特殊。如果你将它们传递给一个函数,它会传递对数组或对象的引用,这意味着如果你调用某些原地函数,它会改变原始数组Array.sort

只读模式来帮忙啦🚀

我们稍微修改一下代码:

function sortNumbers(array: Readonly<Array<number>>) {
  return array.sort((a, b) => a - b)
}
Enter fullscreen mode Exit fullscreen mode

但这代码编译失败。TypeScript 报错如下,Property ‘sort’ does not exist on type ‘readonly number[]’
而这正是我们想要的!我们不能修改参数,这样就不会产生任何副作用!
太好了
但这是否意味着我们不能编写一个对数组进行排序的函数呢?当然可以。我们只需要对数组的副本进行排序,而不是对数组本身进行排序。在 JavaScript 中,有很多方法可以复制数组,例如使用展开运算符(`\b` […array])、`\cdot`array.concat()或`\ Array.from(array)cdot` array.slice()。所以,让我们使用展开运算符来完成我们的函数,使其看起来像这样:

function sortNumbers(array: Readonly<Array<number>>) {
  return [...array].sort((a, b) => a - b)
}
Enter fullscreen mode Exit fullscreen mode

搞定!TypeScript 强制执行简洁代码。顺便说一句:这也适用于对象!

如果您想了解更多关于 JavaScript 可变性的知识,请查看这篇文章。

第二点:任意 vs 未知

当您将 ESLint 与 TypeScript 结合使用时,您可能已经注意到这条消息unexpected any。至少我一直很疑惑为什么any这样不好。除了这样,还能如何声明一个变量可以包含任何值呢?让我们来看一个例子:

const someArray: Array<any> = [];

// add some values
someArray.push(1);
someArray.push("Hello");
someArray.push({ age: 42 });
someArray.push(null);
Enter fullscreen mode Exit fullscreen mode

我们正在创建一个数组,它可以包含所有可用类型。虽然这可能不是最佳代码,但我们先这样吧。我们添加了一个数字、一个字符串和一个对象。现在让我们看看下面的代码,想想会发生什么:

const someArray: Array<any> = [];

// ... adding the values
someArray.forEach(entry => {
  console.log(entry.age);
})
Enter fullscreen mode Exit fullscreen mode

这段代码实际上是有效的 TypeScript 代码,编译时不会出现任何问题。但是运行时会失败。为什么呢?因为一旦我们遍历到 ` nullor`类型的条目undefined,然后尝试访问 ` is` .age,就会抛出类似这样的错误:

Uncaught TypeError: Cannot read properties of null

我认为这是一种虚假的安全感,因为你期望一切都能正常运行。毕竟,TS 编译器已经告诉你代码没问题了。

但我们可以修复它!而且修改其实非常简单。以前我们需要像现在这样输入数组,现在Array<any>我们可以直接使用Array<unknown>`if` 语句了。同样的代码,经过这样的修改后,看起来会是这样。

const someArray: Array<unknown> = [];

// ... adding the values

someArray.forEach(entry => {
  console.log(entry.age);
})
Enter fullscreen mode Exit fullscreen mode

这段代码无法编译!尝试访问时,TypeScript 显示以下错误。entry.age

// ... other code

someArray.forEach(entry => {
  // Object is of type 'unknown'
  console.log(entry.age);
})
Enter fullscreen mode Exit fullscreen mode

使用 ` unknownis` 会强制我们在对值进行任何操作之前检查类型(或显式转换值)unknown。让我们来看一个例子:

// ... other code

type Human = { name: string, age: number };

someArray.forEach(entry => {
  // if it's an object, we know it's a Human
  if(typeof entry === 'object'){
    console.log((entry as Human).age);
  }
})
Enter fullscreen mode Exit fullscreen mode

在这种情况下,我们检查了该值是否为对象,然后访问了该.age属性。由于这是一个非常抽象的主题,这里做一个简单的总结:

any基本上就是告诉 TypeScript 编译器不要检查那段代码。any尽量避免使用!最好使用 `{{ type="type" unknown>`,因为它强制你在使用值之前检查其类型,否则代码将无法编译!

注意:不要用它typeof x === 'object'来检查某个对象是否有效,因为它对true数组也会返回 false。

第三点:使用记录对对象进行类型化

我刚开始使用 TypeScript 时,总是需要上网搜索如何定义对象类型,因为我总是记不住正确的定义,大概是这样的:

interface Person {
  [key: string]: unknown
}

const Human: Person = {
  name: "Steve",
  age: 42
}
Enter fullscreen mode Exit fullscreen mode

虽然这是在 TypeScript 中为对象定义类型的一种有效方法,但我认为它很难记忆,而且也相当局限。

例如,如果我只想允许某些特定的键,我会创建一个像这样的字符串联合:

type AllowedKeys = 'name' | 'age';

interface Person {
  [key: AllowedKeys]: unknown
}

const Human: Person = {
  name: "Steve",
  age: 42
}
Enter fullscreen mode Exit fullscreen mode

但是,TypeScript 不喜欢这样,并给我报了那个错误:

An index signature parameter type cannot be a literal type or generic type. Consider using a mapped object type instead.

呃,什么?这又是那种让你只想关闭 IDE 回到纯 JavaScript 的 TypeScript 错误。不过,有一个解决方案可以让代码更易读:

type AllowedKeys = 'name' | 'age';

// use a type here instead of interface
type Person = Record<AllowedKeys, unknown>;

const Human: Person = {
  name: "Steve",
  age: 42
}
Enter fullscreen mode Exit fullscreen mode

我们只需要将接口改为类型,这样就可以定义一个新类型,然后使用Record带有两个泛型参数的关键字,第一个参数是键的类型,第二个参数是对应值的类型。很简单,对吧?顺便说一句,如果你现在添加值,AllowedKeys对象会报错,Human因为它缺少这些属性,我觉得这很棒!

本文最初发表于cstrnt.dev

文章来源:https://dev.to/cstrnt/3-typescript-tricks-i-wish-i-knew-when-i-learned-typescript-2nnd