TS的 @types - 类型定义完整指南 - 译文3

这是一篇Angular的课程文章,原文链接在此,我只做翻译。

文章看的懵懵懂懂,不是很了解在说什么,又感觉学到了点啥。

1.DT类型的依赖使用

类型化可执行和DefinitivelyTyped是怎么回事?

如果我们前往@types scoped包的npm页面,我们可以看到那里发生了什么--@types

我们可以看到,现在所有 DefintivelyTyped 的内容都可以在 @types scoped 包下使用,我们不需要再使用 typings 可执行文件来下载类型定义。

我们现在可以简单地使用npmTypescript编译器会隐式地获取任何安装在node_modules/@types文件夹内的类型定义,并在编译过程中透明地包含它。

2.使用@types

什么时候使用@types?

@types scope包中包含了很多库的类型定义,比如ExpressSequelizeJQuery等。所以,如果你缺少一些类型定义,一定要去那里看看,但要先确定两件事。

检查你所使用的软件包是否已经内置了类型,如果是,则更倾向于使用这些类型。
检查类型定义是否已经在编译器中,稍后会有更多的介绍。

@types里面的类型定义是超级有用的,但是在某些情况下,其中的一些类型可能已经不合适了。我们用Promises库举个例子。

3.@types/request-promise

使用Request Promise构建类型安全承诺调用

让我们先安装@types中可用的request-promise的类型定义,如果你一直在编码,其是卸载axios的好时机,以避免库冲突。

npm uninstall axios --save

npm install --save-dev @types/request-promise

现在我们已经有了request-promise的类型定义,这就是我们希望能够编写的程序。

import * as rp from 'request-promise';


interface Lesson {
    id:number;
    description: string;
}


function getLesson(lessonId:number): Promise<Lesson> {
    return rp.get(`lessons/${lessonId}`);
}

const promise  = getLesson(1);

promise.then(lesson => {
    .... 我们希望这个lesson变量的类型是隐式的Lesson类型。
});

但在这个阶段,我们得到了一个错误。

Error:(11, 38) TS2304: Cannot find name 'Promise'.

4.'Cannot find name'报错

了解 形如"Cannot find name 'Promise' "的常见问题

所以看起来我们添加到程序中的节点运行时类型定义并不包括承诺。所以我们来看看@types,看看我们能在哪里找到它们。

请阅读有关结论部分,但现在我们说,我们会从@types中为Promises带来一些类型定义。

npm install --save-dev @types/es6-promise

那么,这将起到什么作用呢?电脑将安装ES6 promises的类型定义,其中包括一个Promise类型的通用参数。

所以现在我们可以说这个函数返回一个LessonPromise,并让编译器正确地推断出变量Lesson的类型。

这正是我们想要的,但是有一个巨大的陷阱。而且这也是一个很好的例子,说明我们不应该系统地取用@types中所有可用的类型,而应该精挑细选。

5.es6-promise

使用es6-promise有什么好处?

为了了解问题所在,我们试试下面的程序,它应该会抛出一个错误。

import * as rp from 'request-promise';

interface Lesson {
    id:number;
    description: string;
}


function getLesson(lessonId:number): Promise<Lesson> {
    return rp.get(`lessons/${lessonId}`)
        .then((lesson:any) =>  Promise.resolve(lesson.description));
}

你能看出问题所在吗?函数返回的是一个字符串的Promise,因为lesson已经被``then子句转换为字符串。

但程序在编译时却没有任何错误。那么这到底是怎么回事呢?

6.Typescript类型系统

并非所有的类型定义都完全利用Typescript类型系统

正如我们之前所看到的,并不是所有的类型定义都能最大程度地利用类型系统。这也是因为Typescript编译器的功能一直在快速发展,不是所有的类型定义都能利用所有最新的功能。

这可能是很好的,因为我们可能不想在程序中一直使用属型,所以在API中返回any并假设调用者会添加类型注释也是一个可行的解决方案。

但在这种情况下,我们真的希望使用带有通用参数的Promise,因为它真的很适合。在我们的程序中指定Promise期望返回的数据类型是什么,并使用这些信息来检查程序的类型,这真的很有意义。

那么我们可以做什么呢?原来Typescript编译器本身就有一大堆Type定义可以使用,其中一个就是Promises

7.编译器选入类型

什么是编译器选入类型,什么时候应该使用它们,为什么?

看看编译器的选项,这里的--lib标志

编译器中捆绑了大量可用的类型定义,包括例如ES6本身的所有类型定义,其中包括Promises

所以我们可以简单地通过使用lib编译器标志来利用这些。

{
    "compilerOptions": {
        "module": "commonjs",
        "target": "es5",
        "noImplicitAny": true,
        "sourceMap": false,
        "lib": ["es6"]
    }
}

所以有了这个功能,编译器就知道了编译器内置的一个Promise类型。那么如果我们在开启该功能的情况下编译我们的程序,我们会得到什么呢?

最初我们会得到一些错误。

node_modules/@types/es6-promise/index.d.ts(42,19): error TS2300: Duplicate identifier 'Promise'.
node_modules/typescript/lib/lib.es2015.iterable.d.ts(145,11): error TS2300:     Duplicate identifier 'Promise'.
...

8.'duplicate type definition'报错

为什么我有时会得到这个 "duplicate type definition "的错误?

在这种情况下,我们在两个地方有一个名为 Promise 的类型定义。

一个是通过@types/es6-promise
另一个是通过我们选择的内置编译器类型。
那么我们如何解决这个重复的类型定义问题呢?解决办法是卸载我们通过@types for promises安装的类型。

npm uninstall --save-dev @types/es6-promise

现在我们只会得到这个错误。

test.ts(11,12): error TS2322: Type 'Bluebird<any>' is not assignable to type 'Promise<Lesson>'.
Property '[Symbol.toStringTag]' is missing in type 'Bluebird<any>'.

请一定要看一下结论,总结一下这里的情况。

原来@types/request-promise毕竟已经自带了Promise类型定义:因为这些类型定义使用了内部的Bluebird类型定义(Bluebird是一个很好的promise库,比如Sequelize--一个Node ORM使用的)。

那么这时候我们能做什么呢?因为目前看来@types/request-promise的类型定义与ES6内置类型定义中的类型不兼容。

这只是暂时的情况,因为Bluebird promises曾经和ES6 promises兼容了很久,其实当你看到这篇文章的时候,这个问题很可能已经解决了。

并不是因为一个API返回一个显式的any,我们就需要在自己的程序中使用any

所以作为一般准则,最好选择自带内置类型的包,并通过类型推理尽量使用这些类型,只有在需要的时候才导入第三方类型,并在每个时刻挑选最适合的类型。

9.如何找到报错

我们要找的错误会是什么样的呢?

让我们试着看看Typescript是否能用一个更简单的例子(取自这个问题报告)捕捉到我们试图抛出的错误。这个例子如何。

interface Foo {
    a: string
    b: number
}

class Hello {
    getFoo(): Promise<Foo> {
        return Promise.resolve("a string")
    }
}

这将如期抛出以下错误。

test.ts(31,16): error TS2322: Type 'Promise<string>' is not assignable to type 'Promise<Foo>'.
  Type 'string' is not assignable to type 'Foo'.

所以编译器已经做好了处理检测这个错误的准备。只是现有的库类型定义需要随着时间的推移而发展,以利用这个功能,很可能总是存在某种差距。

10.处理库和编译器

处理库和编译器之间的差距

Typescript编译器会对节点模块中可用的任何类型定义应用最新的类型检查,包括@types

为了避免这种情况,并确保只有我们的程序被编译器检查,我们可以使用标志 skipLibChecktrue

这可以防止较新版本的编译器针对古老的库抛出错误,而且还有一个额外的好处,就是可以明显地提高构建的性能。

好了,现在我们已经到了尾声,这有点像过山车,但这非常接近你在使用Typescript开发时将会发现的日常问题。

让我们尝试着去理解这一切,并尝试着在下一节中来一些有效使用类型系统的一般准则。

如果你想学习Typescript,使用它来构建一个小型的Express REST API,使用Sequelize(以类型安全的方式)查询一个SQL数据库,请看我们的完整Typescript课程

TSClass

上一篇

TS的 @types - 类型定义完整指南 - 译文2

下一篇

TS的 @types - 类型定义完整指南 - 译文4

# TS 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×