3 个我学习 TypeScript 时希望知道的 TypeScript 小技巧
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
第一名:只读<T>
让我们从一个简单的例子开始:
我们有一个简单的函数,它接受一个数字数组,并返回一个所有元素都已排序的数组。
function sortNumbers(array: Array<number>) {
return array.sort((a, b) => a - b)
}
现在看看下面的代码,检查一下是否一切正常。想想控制台会输出什么。我建议你花点时间认真思考一下!
const numbers = [7, 3, 5];
const sortedNumbers = sortNumbers(numbers);
console.log(sortedNumbers);
console.log(numbers)
第一个输出很简单,就是[3, 5, 7]。但是现在请注意,第二个输出也是一样的!你可能会问:为什么?我把数组定义为 ,const它怎么可能改变呢?
在 JavaScript 中,数组和对象非常特殊。如果你将它们传递给一个函数,它会传递对数组或对象的引用,这意味着如果你调用某些原地函数,它会改变原始数组Array.sort。
只读模式来帮忙啦🚀
我们稍微修改一下代码:
function sortNumbers(array: Readonly<Array<number>>) {
return array.sort((a, b) => a - b)
}
但这代码编译失败。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)
}
搞定!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);
我们正在创建一个数组,它可以包含所有可用类型。虽然这可能不是最佳代码,但我们先这样吧。我们添加了一个数字、一个字符串和一个对象。现在让我们看看下面的代码,想想会发生什么:
const someArray: Array<any> = [];
// ... adding the values
someArray.forEach(entry => {
console.log(entry.age);
})
这段代码实际上是有效的 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);
})
这段代码无法编译!尝试访问时,TypeScript 显示以下错误。entry.age
// ... other code
someArray.forEach(entry => {
// Object is of type 'unknown'
console.log(entry.age);
})
使用 ` 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);
}
})
在这种情况下,我们检查了该值是否为对象,然后访问了该.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
}
虽然这是在 TypeScript 中为对象定义类型的一种有效方法,但我认为它很难记忆,而且也相当局限。
例如,如果我只想允许某些特定的键,我会创建一个像这样的字符串联合:
type AllowedKeys = 'name' | 'age';
interface Person {
[key: AllowedKeys]: unknown
}
const Human: Person = {
name: "Steve",
age: 42
}
但是,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
}
我们只需要将接口改为类型,这样就可以定义一个新类型,然后使用Record带有两个泛型参数的关键字,第一个参数是键的类型,第二个参数是对应值的类型。很简单,对吧?顺便说一句,如果你现在添加值,AllowedKeys对象会报错,Human因为它缺少这些属性,我觉得这很棒!
本文最初发表于cstrnt.dev
文章来源:https://dev.to/cstrnt/3-typescript-tricks-i-wish-i-knew-when-i-learned-typescript-2nnd