目录
介绍
TypeScript是JavaScript的超集。它类似于JavaScript,但具有超能力。
Vanilla JavaScript是一种动态类型语言。例如,如果您将数字类型分配给变量,然后在代码中进一步将字符串分配给相同的变量,那么JavaScript编译得很好。只有在生产中出现问题时才会出现错误。
如果您反对使用TypeScript之类的静态类型工具,则可以使用JavaScript linter提供一些类型检查。在我们的示例中,linter将有助于捕获错误。然而,Linter有其局限性。例如,linter不支持联合类型——我们将在本文中进一步探讨这一点——它们也不能lint复杂的对象结构。
TypeScript为JavaScript提供了更多类型功能,使您能够更好地构建代码库。它在编译时对您的代码库进行类型检查,并有助于防止可能使其投入生产的错误。
TypeScript以许多不同的方式改进了JavaScript开发。但在本文中,我们将重点关注六个领域,包括使用TypeScript的有用提示和技巧。其中一些技巧将讨论TypeScript支持函数式编程的方式。
只读类型
函数式编程通常需要不可变的变量——并且通过扩展,也需要不可变的对象。具有四个属性的对象在其整个生命周期中必须具有相同的属性,并且这些属性的值在任何时候都不能改变。
TypeScript通过Readonly实用程序类型使这成为可能。这是没有它的类型:
...
type Product = {
name: string
price: number
}
const products: Product[] = [
{
name: "Apple Watch",
price: 400,
},
{
name: "Macbook",
price: 1000,
},
]
products.forEach(product => {
// mutating here
product.price = 500
})
...
在代码中,我们改变了price属性。由于新值是数字数据类型,TypeScript不会抛出错误。但是使用Readonly,我们的代码如下:
...
const products: Readonly<Product>[] = [
{
name: "Apple Watch",
price: 400,
},
{
name: "Macbook",
price: 1000,
},
]
products.forEach(product => {
// mutating here
product.price = 500
})
...
正如我们在此屏幕截图中看到的,该price属性是只读的,不能被分配另一个值。试图改变这个对象的值会抛出一个错误。
联合类型
TypeScript允许您以引人注目的方式组合类型。两种或多种类型的组合称为联合类型。您使用"|" 符号来创建联合类型。让我们看一些例子。
数字和字符串联合类型
有时,您需要一个变量作为数字。其他时候,您需要相同的变量才能成为string。
使用TypeScript,您可以通过执行以下简单操作来实现此目的:
function(id: number | string) {
...
}
此处声明的联合类型的挑战是您无法调用id.toUpperCase,因为TypeScript不知道您是要在函数声明期间传递字符串还是数字。因此,要使用该toUpperCase方法,您必须使用typeof === "string"检查id是否是string。
但是,如果它不能对所有组成特定联合的成员应用标准方法,TypeScript不会抱怨。
限制可接受的类型
使用联合,您还可以限制变量可接受的数据类型值。您可以使用文字类型来执行此操作。这是一个例子:
function(type: "picture" | "video") {
...
}
该联合类型包括图片和视频的字符串文字类型。此代码会导致其他字符串值引发错误。
识别联合
联合的另一个好处是,您可以拥有不同结构的对象类型,每个对象类型都有一个共同的区别属性。这是一个例子:
...
type AppleFruit = {
color: string;
size: "small" | "large"
}
type OrangeFruit = {
isRipe: boolean;
count: number;
}
function describeFruit(fruit: AppleFruit | OrangeFruit) {
...
}
...
在这段代码中,我们有一个联合类型Fruit,它由两种不同的对象类型组成:AppleFruit和OrangeFruit。这两种水果没有共同的特性。这种差异使得TypeScript在我们使用的时候很难知道水果是什么,如下面的代码和截图所示:
...
function describeFruit(fruit: AppleFruit | OrangeFruit) {
if (fruit.color) {
// throw error...see Figure B.
}
}
...
此屏幕截图中的错误表明橙色类型上的color不存在。有两种解决方案。
第一个解决方案是以更可接受的方式检查该color属性是否存在。就是这样:
...
function describeFruit(fruit: AppleFruit | OrangeFruit) {
if ("color" in fruit) {
// now typescript knows fruit is of the apple type
}
}
...
我们检查color属性是否在fruit对象中。使用此检查,TypeScript可以正确推断类型,如下图所示:
第二种解决方案是使用有区别的联合。这种方法意味着具有清楚地区分两个对象的属性。TypeScript可以使用该属性来了解在特定时间正在使用哪种类型。就是这样:
...
type AppleFruit = {
name: "apple";
color: string;
size: "small" | "large";
}
type OrangeFruit = {
name: "orange";
isRipe: boolean;
count: number;
}
function describeFruit(fruit: AppleFruit | OrangeFruit) {
if (fruit.name === "apple") {
// apple type detected
}
}
...
由于这两种类型都有name属性,fruit.name因此不会抛出错误。并且,使用name属性的值,TypeScript可以确定fruit类型。
交集类型
与涉及type1, type2, _或_type3的联合类型相比,交集类型是type1,type2 和 type3。
这些类型之间的另一个显著区别是,虽然联合类型可以是字符串或数字,但交集类型不能是字符串和数字。数据不能同时是字符串和数字。因此,交集类型涉及对象。
既然我们已经讨论了联合和交集类型之间的区别,让我们来探索一些做交集的方法:
...
interface Profile {
name: string;
phone: string;
}
interface AuthCreds {
email: string;
password: string;
}
interface User: Profile & AuthCreds
...
Profile和AuthCreds是相互独立存在的接口类型的示例。这种独立性意味着您可以创建一个Profile类型的对象和另一个AuthCreds类型的对象,并且这些对象可能不会关联在一起。但是,您可以将这两种类型相交以制作更大的类型:User。此类型的结构是一个具有四个属性的对象:name、email 、password和phone,所有字符串类型。
现在你可以像这样创建一个User对象:
...
const user:User = {
name: "user";
phone: "222222",
email: "user@user.com"
password: "***"
}
...
TypeScript泛型
有时,当您创建一个函数时,您知道它的返回类型。这是一个例子:
...
interface AppleFruit {
size: number
}
interface FruitDescription {
description: string;
}
function describeFruit(fruit: AppleFruit): AppleFruit & FruitDescription {
return {
...fruit,
description: "A fruit",
}
}
const fruit: AppleFruit = {
size: 50
}
describeFruit(fruit)
...
在此示例中,该describeFruit函数接受AppleFruit类型的fruit参数。它返回由AppleFruit和FruitDescription类型组成的交集类型。
但是,如果您希望此函数返回不同水果类型的描述,该怎么办?泛型在这里是相关的。这是一个例子:
...
interface AppleFruit {
size: number
}
interface OrangeFruit {
isOrangeColor: boolean;
}
interface FruitDescription {
description: string;
}
function describeFruit<T>(fruit: T): T & FruitDescription {
return {
...fruit,
description: "A fruit",
}
}
const appleFruit: AppleFruit = {
size: 50
}
describeFruit(appleFruit)
const orangeFruit: OrangeFruit = {
isOrangeColor: true
}
describeFruit<OrangeFruit>(orangeFruit)
...
泛型函数describeFruit接受不同的类型。代码在调用函数时确定要传递的水果类型。
我们第一次调用describeFruit时,TypeScript会自动推断T是AppleFruit因为appleFruit是属于那种类型。
下一次,我们在调用函数之前要使用“OrangeFruit”指定T的OrangeFruit类型。
这些行做同样的事情,但在某些情况下,自动推理可能不准确。
在我们的示例中,我们可以将不同的类型传递给该函数,它只是返回我们传递的类型的FruitDescription交集。
这是使用泛型将类型传递给函数的示例:
该describeFruit函数有一个类型,正如我们最初用使用OrangeFruit定义的那样。
使用TypeScript的路径别名
你可能通常import,如下所示:
...
import Button from "../../../../components/Button"
...
此命令import可能位于需要此组件的不同文件中。当您更改“Button”文件的位置时,您还需要在使用它的各个文件中更改此import行。此调整还会导致在版本控制中跟踪更多文件更改。
我们可以通过使用别名路径来改进我们的导入方式。
TypeScript使用“tsconfig.json”文件来存储配置,使其能够按照您的需要工作。在里面,有paths属性。此属性允许您为应用程序中的不同目录设置路径别名。下面是它的样子,使用compilerOptions、baseUrl和paths:
...
{
"compilerOptions": {
"baseUrl": ".", // required if "paths" is specified.
"paths": {
"components/*": ["./src/components/*"] // path is relative to the baseUrl
}
}
}
...
使用组件别名,您现在可以像这样导入:
...
import Button from "components/Button"
...
无论您在目录中有多深,使用此命令都能正确解析“Button”文件。
现在,当您更改组件的位置时,您所要做的就是更新paths属性。这种方法意味着更一致的文件和更少的版本控制文件更改。
使用具有内置TypeScript支持的库
当您使用不支持TypeScript的库时,您会错过TypeScript的好处。即使是一个简单的错误——例如对参数或对象属性使用错误的数据类型——也可能在没有TypeScript提供的警告的情况下让你头疼。如果没有此警报,您的应用程序可能会崩溃,因为它需要一个字符串,但您传递了一个数字。
并非每个库都支持TypeScript。当你安装一个支持TypeScript的库时,它会安装带有TypeScript声明文件的分布式代码。
结论
在本文中,我们探讨了TypeScript如何改进JavaScript编码。我们还讨论了TypeScript如何支持一些函数式编程技术。这种能力使TypeScript适合面向对象编程(OOP)开发人员和函数式程序员。
下一步,您可以深入了解TypeScript的配置选项。TypeScript提供了这个详尽的资源来帮助您构建下一个应用程序。
https://www.codeproject.com/Articles/5322795/TypeScript-Tips-and-Tricks