何以解忧
何以解忧
发布于 2024-05-19 / 85 阅读
0
0

TypeScript 5分钟上手

安装TypeScript

有两种主要的方式来获取TypeScript工具:

  • 通过npm(Node.js包管理器)

  • 安装Visual Studio的TypeScript插件

Visual Studio 2017和Visual Studio 2015 Update 3默认包含了TypeScript。 如果你的Visual Studio还没有安装TypeScript,你可以下载它。

针对使用npm的用户:

> npm install -g typescript

构建你的第一个TypeScript文件

在编辑器,将下面的代码输入到greeter.ts文件里:

function greeter(person) {
    return "Hello, " + person;
}

let user = "Jane User";

document.body.innerHTML = greeter(user);

编译代码

我们使用了.ts扩展名,但是这段代码仅仅是JavaScript而已。 你可以直接从现有的JavaScript应用里复制/粘贴这段代码。

在命令行上,运行TypeScript编译器:

tsc greeter.ts

输出结果为一个greeter.js文件,它包含了和输入文件中相同的JavsScript代码。 一切准备就绪,我们可以运行这个使用TypeScript写的JavaScript应用了!

接下来让我们看看TypeScript工具带来的高级功能。 给 person函数的参数添加: string类型注解,如下:

function greeter(person: string) {
    return "Hello, " + person;
}

let user = "Jane User";

document.body.innerHTML = greeter(user);

类型注解

TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。 在这个例子里,我们希望 greeter函数接收一个字符串参数。 然后尝试把 greeter的调用改成传入一个数组:

function greeter(person: string) {
    return "Hello, " + person;
}

let user = [0, 1, 2];

document.body.innerHTML = greeter(user);

重新编译,你会看到产生了一个错误。

greeter.ts(7,26): error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

类似地,尝试删除greeter调用的所有参数。 TypeScript会告诉你使用了非期望个数的参数调用了这个函数。 在这两种情况中,TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解。

要注意的是尽管有错误,greeter.js文件还是被创建了。 就算你的代码里有错误,你仍然可以使用TypeScript。但在这种情况下,TypeScript会警告你代码可能不会按预期执行。

接口

让我们开发这个示例应用。这里我们使用接口来描述一个拥有firstNamelastName字段的对象。 在TypeScript里,只在两个类型内部的结构兼容那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements语句。

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = { firstName: "Jane", lastName: "User" };

document.body.innerHTML = greeter(user);

最后,让我们使用类来改写这个例子。 TypeScript支持JavaScript的新特性,比如支持基于类的面向对象编程。

让我们创建一个Student类,它带有一个构造函数和一些公共字段。 注意类和接口可以一起共作,程序员可以自行决定抽象的级别。

还要注意的是,在构造函数的参数上使用public等同于创建了同名的成员变量。

class Student {
    fullName: string;
    constructor(public firstName, public middleInitial, public lastName) {
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
}

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person : Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = new Student("Jane", "M.", "User");

document.body.innerHTML = greeter(user);

重新运行tsc greeter.ts,你会看到生成的JavaScript代码和原先的一样。 TypeScript里的类只是JavaScript里常用的基于原型面向对象编程的简写。

运行TypeScript Web应用

greeter.html里输入如下内容:

<!DOCTYPE html>
<html>
    <head><title>TypeScript Greeter</title></head>
    <body>
        <script src="greeter.js"></script>
    </body>
</html>

在浏览器里打开greeter.html运行这个应用!

可选地:在Visual Studio里打开greeter.ts或者把代码复制到TypeScript playground。 将鼠标悬停在标识符上查看它们的类型。 注意在某些情况下它们的类型可以被自动地推断出来。 重新输入一下最后一行代码,看一下自动补全列表和参数列表,它们会根据DOM元素类型而变化。 将光标放在 greeter函数上,点击F12可以跟踪到它的定义。 还有一点,你可以右键点击标识,使用重构功能来重命名。

这些类型信息以及工具可以很好的和JavaScript一起工作。 更多的TypeScript功能演示,请查看:https://www.tslang.cn/samples/index.html

Visual Studio picture


Typescript 3.1

原文:https://www.tslang.cn/docs/release-notes/typescript-3.1.html

元组和数组上的映射类型

TypeScript 3.1,在元组和数组上的映射对象类型现在会生成新的元组/数组,而非创建一个新的类型并且这个类型上具有如push()pop()length这样的成员。 例子:

type MapToPromise<T> = { [K in keyof T]: Promise<T[K]> };

type Coordinate = [number, number]

type PromiseCoordinate = MapToPromise<Coordinate>; // [Promise<number>, Promise<number>]

MapToPromise接收参数T,当它是个像Coordinate这样的元组时,只有数值型属性会被转换。 [number, number]具有两个数值型属性:01。 针对这样的数组,MapToPromise会创建一个新的元组,01属性是原类型的一个Promise。 因此PromiseCoordinate的类型为[Promise<number>, Promise<number>]

函数上的属性声明

TypeScript 3.1提供了在函数声明上定义属性的能力,还支持const声明的函数。只需要在函数直接给属性赋值就可以了。 这样我们就可以规范JavaScript代码,不必再借助于namespace。 例子:

function readImage(path: string, callback: (err: any, image: Image) => void) {
    // ...
}

readImage.sync = (path: string) => {
    const contents = fs.readFileSync(path);
    return decodeImageSync(contents);
}

这里,readImage函数异步地读取一张图片。 此外,我们还在readImage上提供了一个便捷的函数readImage.sync

一般来说,使用ECMAScript导出是个更好的方式,但这个新功能支持此风格的代码能够在TypeScript里执行。 此外,这种属性声明的方式允许我们表达一些常见的模式,例如React无状态函数型组件(SFCs)里的defaultPropspropTpes

export const FooComponent => ({ name }) => (
    <div>Hello! I am {name}</div>
);

FooComponent.defaultProps = {
    name: "(anonymous)",
};

[1] 更确切地说,是上面那种同态映射类型。

使用typesVersions选择版本

由社区的反馈还有我们的经验得知,利用最新的TypeScript功能的同时容纳旧版本的用户很困难。 TypeScript引入了叫做typesVersions的新特性来解决这种情况。

在TypeScript 3.1里使用Node模块解析时,TypeScript会读取package.json文件,找到它需要读取的文件,它首先会查看名字为typesVersions的字段。 一个带有typesVersions字段的package.json文件:

{
    "name": "package-name",
    "version": "1.0",
    "types": "./index.d.ts",
    "typesVersions": {
        ">=3.1": { "*": ["ts3.1/*"] }
    }
}

package.json告诉TypeScript去检查当前版本的TypeScript是否正在运行。 如果是3.1或以上的版本,它会找出你导入的包的路径,然后读取这个包里面的ts3.1文件夹里的内容。 这就是{ "*": ["ts3.1/*"] }的意义 - 如果你对路径映射熟悉,它们的工作方式类似。

因此在上例中,如果我们正在从"package-name"中导入,并且正在运行的TypeScript版本为3.1,我们会尝试从[...]/node_modules/package-name/ts3.1/index.d.ts开始解析。 如果是从package-name/foo导入,由会查找[...]/node_modules/package-name/ts3.1/foo.d.ts[...]/node_modules/package-name/ts3.1/foo/index.d.ts

那如果当前运行的TypeScript版本不是3.1呢? 如果typesVersions里没有能匹配上的版本,TypeScript将回退到查看types字段,因此TypeScript 3.0及之前的版本会重定向到[...]/node_modules/package-name/index.d.ts

匹配行为

TypeScript使用Node的semver ranges去决定编译器和语言版本。

多个字段

typesVersions支持多个字段,每个字段都指定了一个匹配范围。

{
    "name": "package-name",
    "version": "1.0",
    "types": "./index.d.ts",
    "typesVersions": {
        ">=3.2": { "*": ["ts3.2/*"] },
        ">=3.1": { "*": ["ts3.1/*"] }
    }
}

因为范围可能会重叠,因此指定的顺序是有意义的。 在上例中,尽管>=3.2>=3.1都匹配TypeScript 3.2及以上版本,反转它们的顺序将会有不同的结果,因此上例与下面的代码并不等同。

{
    "name": "package-name",
    "version": "1.0",
    "types": "./index.d.ts",
    "typesVersions": {
        // 注意,这样写不生效
        ">=3.1": { "*": ["ts3.1/*"] },
        ">=3.2": { "*": ["ts3.2/*"] }
    }
}


评论