typescript学习
学习ts中文官网
any比较特殊,它是最普通的类型但是允许你在上面做任何事情。 也就是说你可以在上面调用,构造它,访问它的属性等等。 记住,当你使用any时,你会失去大多数TypeScript提供的错误检查和编译器支持。tsconfig.json里某些配置的作用:
- noImplicitAny:控制是否代码里允许any类型。参考 没有隐式的
any【没有隐式的any】 - strictNullChecks:控制代码里是否允许null、undefined。 参考 严格的
null与undefined检查- 当启用了
strictNullChecks,null和undefined获得了它们自己各自的类型null和undefined。 当任何值 可能为null,你可以使用联合类型。 比如,某值可能为number或null,你可以声明它的类型为number | null。要当心,当你使用strictNullChecks,你的依赖也需要相应地启用strictNullChecks。
- 当启用了
- noImplicitAny:控制是否代码里允许any类型。参考 没有隐式的
this没有隐式的any:【this没有隐式的any】
- 在函数里使用this时,this上的方法名写错了ts不会报错(当你在类的外部使用this关键字时,它会默认获得any类型);解决的方法是在接口或函数上使用指定了类型的this参数。
函数过多过少参数的处理方法: 参考 【过多或过少的参数】
# 模块
(es6里)任何声明(比如变量,函数,类,类型别名或接口)都能够通过添加
export关键字来导出。CommonJS和AMD的环境里都有一个
exports变量,这个变量包含了一个模块的所有导出内容。- CommonJS和AMD的
exports都可以被赋值为一个对象, 这种情况下其作用就类似于 es6 语法里的默认导出,即export default语法了。虽然作用相似,但是export default语法并不能兼容CommonJS和AMD的exports。 - 为了支持CommonJS和AMD的
exports, TypeScript提供了export =语法。 export =语法定义一个模块的导出对象。 这里的对象一词指的是类,接口,命名空间,函数或枚举。- 若使用
export =导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块。 - 这里演示了Node.js (CommonJS (opens new window)),Require.js (AMD (opens new window)),UMD (opens new window),SystemJS (opens new window)或ECMAScript 2015 native modules (opens new window) (ES6)模块加载时各个怎么使用。
- CommonJS和AMD的
可选的模块加载和其它高级加载场景:文中对esmodule并没有介绍到
模块里不要使用命名空间:
- 当初次进入基于模块的开发模式时,可能总会控制不住要将导出包裹在一个命名空间里。 模块具有其自己的作用域,并且只有导出的声明才会在模块外部可见。 记住这点,命名空间在使用模块时几乎没什么价值。
- 命名空间对解决全局作用域里命名冲突来说是很重要的。 比如,你可以有一个
My.Application.Customer.AddForm和My.Application.Order.AddForm-- 两个类型的名字相同,但命名空间不同。 然而,这对于模块来说却不是一个问题。 在一个模块里,没有理由两个对象拥有同一个名字。 从模块的使用角度来说,使用者会挑出他们用来引用模块的名字,所以也没有理由发生重名的情况。
命名空间:
命名空间是位于全局命名空间下的一个普通的带有名字的JavaScript对象。 这令命名空间十分容易使用。 它们可以在多文件中同时使用,并通过
--outFile结合在一起。- 至于命名空间编译出来什么样子,可以去官网的这里 https://www.tslang.cn/play/index.html在线查看结果。
们想让这些接口和类在命名空间之外也是可访问的,所以需要使用
export。命名空间之外访问,因此需要限定类型的名称,比如
Validation.LettersOnlyValidator。别名:另一种简化命名空间操作的方法是使用
import q = x.y.z给常用的对象起一个短的名字。 不要与用来加载模块的import x = require('name')语法弄混了,这里的语法是为指定的符号创建一个别名。 你可以用这种方法为任意标识符创建别名,也包括导入的模块中的对象。注意,我们并没有使用require关键字,而是直接使用导入符号的限定名赋值。 这与使用var相似,但它还适用于类型和导入的具有命名空间含义的符号。 重要的是,对于值来讲,import会生成与原始符号不同的引用,所以改变别名的var值并不会影响原始变量的值。把声明文件离到多文件:
- 多文件中的命名空间
- 这些文件并没有使用
declare namespace而是使用namespace xxx,需要在其他 ts 里使用/// <reference path="xxx.ts" />引入后方可使用。- 命名空间是位于全局命名空间下的一个普通的带有名字的JavaScript对象;就像其它的全局命名空间污染一样,它很难去识别组件之间的依赖关系,尤其是在大型的应用中
- 这些文件并没有使用
- 多文件中的命名空间
外部命名空间:
- 这里拿D3的声明文件做了一个例子,可以看官方文档这页的最后。
- 命名空间和模块的陷阱:
不要使用使用
/// <reference>引用模块文件:一个常见的错误是使用/// <reference>引用模块文件,应该使用import。- 对模块使用
/// <reference>:编译器首先尝试去查找相应路径下的.ts,.tsx再或者.d.ts。 如果这些文件都找不到,编译器会查找 外部模块声明。
- 对模块使用
不必要的命名空间:在模块文件里使用namespace关键字。
- 再次重申,不应该对模块使用命名空间,使用命名空间是为了提供逻辑分组和避免命名冲突。 模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层。
相对 vs 非相对模块导入:
- 相对导入是以/,./或../开头的;所有其它形式的导入被当作非相对的。
- 相对导入在解析时是相对于导入它的文件,并且不能解析为一个外部模块声明。 你应该为你自己写的模块使用相对导入,这样能确保它们在运行时的相对位置。
- 非相对模块的导入可以相对于
baseUrl或通过下文会讲到的路径映射来进行解析。 它们还可以被解析成 外部模块声明 (opens new window)。 使用非相对路径来导入你的外部依赖。
- 相对导入是以/,./或../开头的;所有其它形式的导入被当作非相对的。
模块解析策略:
- 共有两种可用的模块解析策略:Node (opens new window)和Classic (opens new window)。 你可以使用
--moduleResolution标记来指定使用哪种模块解析策略。若未指定,那么在使用了--module AMD | System | ES2015时的默认值为Classic (opens new window),其它情况时则为Node (opens new window)。 - 共有两种可用的模块解析策略:Node (opens new window)和Classic (opens new window)
- Classic:
- 相对导入的模块是相对于导入它的文件进行解析的。
- 对于非相对模块的导入,编译器则会从包含导入文件的目录开始依次向上级目录遍历,尝试定位匹配的声明文件。
- node:这个解析策略试图在运行时模仿Node.js (opens new window)模块解析机制。 完整的Node.js解析算法可以在 Node.js module documentation (opens new window)找到。
- 这里对node解析模块的方式说的非常明白。
- Classic:
- TypeScript如何解析模块:
- TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件。 因此,TypeScript在Node解析逻辑基础上增加了TypeScript源文件的扩展名(
.ts,.tsx和.d.ts)。 同时,TypeScript在package.json里使用字段"types"来表示类似"main"的意义 - 编译器会使用它来找到要使用的"main"定义文件。
- TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件。 因此,TypeScript在Node解析逻辑基础上增加了TypeScript源文件的扩展名(
- 共有两种可用的模块解析策略:Node (opens new window)和Classic (opens new window)。 你可以使用
附加的模块解析标记
- Base URL:
- 设置
baseUrl来告诉编译器到哪里去查找模块。 所有非相对模块导入都会被当做相对于baseUrl。 - baseUrl的值由以下两者之一决定:
- ‘tsconfig.json’里的baseUrl属性(如果给定的路径是相对的,那么将相对于‘tsconfig.json’路径进行计算)
- 命令行中baseUrl的值(如果给定的路径是相对的,那么将相对于当前路径进行计算)
- 注意相对模块的导入不会被设置的
baseUrl所影响,因为它们总是相对于导入它们的文件。
- 设置
- 路径映射:
- TypeScript编译器通过使用
tsconfig.json文件里的"paths"来支持这样的声明映射。 - 请注意
"paths"是相对于"baseUrl"进行解析。
- TypeScript编译器通过使用
- 利用
rootDirs指定虚拟目录:- 在tsconfig.json里,可以使用
"rootDirs"来告诉编译器。每当编译器在某一rootDirs的子目录下发现了相对模块导入,它就会尝试从每一个rootDirs中导入。 rootDirs的灵活性不仅仅局限于其指定了要在逻辑上合并的物理目录列表。它提供的数组可以包含任意数量的任何名字的目录,不论它们是否存在。这允许编译器以类型安全的方式处理复杂捆绑(bundles)和运行时的特性,比如条件引入和工程特定的加载器插件。
- 在tsconfig.json里,可以使用
- 跟踪模块解析:
- 编译器在解析模块时可能访问当前文件夹外的文件。 这会导致很难诊断模块为什么没有被解析,或解析到了错误的位置。 通过
--traceResolution启用编译器的模块解析跟踪,它会告诉我们在模块解析过程中发生了什么。
- 编译器在解析模块时可能访问当前文件夹外的文件。 这会导致很难诊断模块为什么没有被解析,或解析到了错误的位置。 通过
- 为什么在exclude列表里的模块还会被编译器使用
- 合并接口:
- 接口的非函数的成员应该是唯一的。如果它们不是唯一的,那么它们必须是相同的类型。如果两个接口中同时声明了同名的非函数成员且它们的类型不同,则编译器会报错。
- 对于函数成员,每个同名函数声明都会被当成这个函数的一个重载。 同时需要注意,当接口
A与后来的接口A合并时,后面的接口具有更高的优先级。- 这个规则有一个例外是当出现特殊的函数签名时。 如果签名里有一个参数的类型是 单一的字符串字面量(比如,不是字符串字面量的联合类型),那么它将会被提升到重载列表的最顶端。
- 合并命名空间
- 与接口相似,同名的命名空间也会合并其成员。 命名空间会创建出命名空间和值,我们需要知道这两者都是怎么合并的。
- 对于命名空间的合并,模块导出的同名接口进行合并,构成单一命名空间内含合并后的接口。
- 对于命名空间里值的合并,如果当前已经存在给定名字的命名空间,那么后来的命名空间的导出成员会被加到已经存在的那个模块里。
- 除了这些合并外,你还需要了解非导出成员是如何处理的。 非导出成员仅在其原有的(合并前的)命名空间内可见。这就是说合并之后,从其它命名空间合并进来的成员无法访问非导出成员。
- 命名空间与类和函数和枚举类型合并
- 合并命名空间和类:我们必须导出 命名空间里的类,好让合并的类能访问。 合并结果是一个类并带有一个内部类。 你也可以使用命名空间为类增加一些静态属性。【自注:我们可以吧官网的例子去在线运行即可看见什么意思】
- 除了内部类的模式,你在JavaScript里,创建一个函数稍后扩展它增加一些属性也是很常见的。
- 相似的,命名空间可以用来扩展枚举型。
- 非法的合并:TypeScript并非允许所有的合并。 目前,类不能与其它类或变量合并。 想要了解如何模仿类的合并,请参考 TypeScript的混入 (opens new window)。
- 模块扩展
- 这里以
declare module "./observable"这种方式去扩展./observable模块。
- 这里以
- 全局扩展
- 以
declare global方式去扩展全局里的某个属性。
- 以
- 与接口相似,同名的命名空间也会合并其成员。 命名空间会创建出命名空间和值,我们需要知道这两者都是怎么合并的。
- TypeScript支持内嵌,类型检查以及将JSX直接编译为JavaScript。
- 想要使用JSX必须做两件事
- 给文件一个
.tsx扩展名 - 启用
jsx选项
- 给文件一个
- TypeScript具有三种JSX模式:
preserve,react和react-native。参考官网看看各有什么不同。 - TypeScript在
.tsx文件里禁用了使用尖括号的类型断言。转而使用as去断言。- 因为TypeScript也使用尖括号来表示类型断言,在结合JSX的语法后将带来解析上的困难。
- 类型检查:
- tsx对固有元素以及组件(基于值的元素)的类型检查方式不一样。
装饰器是一种特殊类型的声明,它能够被附加到类声明 (opens new window),方法 (opens new window), 访问符 (opens new window),属性 (opens new window)或参数 (opens new window)上。
在TypeScript里,当多个装饰器应用在一个声明上时会进行如下步骤的操作:
- 由上至下依次对装饰器表达式求值。
- 求值的结果会被当作函数,由下至上依次调用。
装饰器求值
- 类中不同声明上的装饰器将按以下规定的顺序应用:
- 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
- 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
- 参数装饰器应用到构造函数。
- 类装饰器应用到类。
- 类中不同声明上的装饰器将按以下规定的顺序应用:
类装饰器
- 类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中(
.d.ts),也不能用在任何外部上下文中(比如declare的类)。 - 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
- 类装饰器在类声明之前被声明(紧靠着类声明)。 类装饰器应用于类构造函数,可以用来监视,修改或替换类定义。 类装饰器不能用在声明文件中(
方法装饰器
若要启用实验性的装饰器特性,你必须在命令行或
tsconfig.json里启用experimentalDecorators编译器选项:这里有一句话:对于类属性或方法的装饰本质是操作其描述符,可以把此时的装饰器理解成是
Object.defineProperty(obj, prop, descriptor)的语法糖。方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件(
.d.ts),重载或者任何外部上下文(比如declare的类)中。- 方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
- 成员的名字。
- 成员的属性描述符。
- 方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:
元数据:TypeScript支持为带有装饰器的声明生成元数据。 你需要在命令行或
tsconfig.json里启用emitDecoratorMetadata编译器选项。tsconfig.json:
{ "compilerOptions": { "target": "ES5", "experimentalDecorators": true, "emitDecoratorMetadata": true } }1
2
3
4
5
6
7
当启用后,只要
reflect-metadata库被引入了,设计阶段添加的类型信息可以在运行时使用。
- 三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释,这包括其它的三斜线指令。 如果它们出现在一个语句或声明之后,那么它们会被当做普通的单行注释,并且不具有特殊的涵义。
- 它用于声明文件间的 依赖。三斜线引用告诉编译器在编译过程中要引入的额外的文件。
- 预处理输入文件
- 编译器会对输入文件进行预处理来解析所有三斜线引用指令。 在这个过程中,额外的文件会加到编译过程中。
- 这个过程会以一些根文件开始; 它们是在命令行中指定的文件或是在
tsconfig.json中的"files"列表里的文件。 这些根文件按指定的顺序进行预处理。 在一个文件被加入列表前,它包含的所有三斜线引用都要被处理,还有它们包含的目标。 三斜线引用以它们在文件里出现的顺序,使用深度优先的方式解析。
/// <reference types="..." />- 与
/// <reference path="..." />指令相似,这个指令是用来声明 依赖的; 一个/// <reference types="..." />指令则声明了对某个包的依赖。 - 对这些包的名字的解析与在
import语句里对模块名的解析类似。 可以简单地把三斜线类型引用指令当做import声明的包。- 例如,把
/// <reference types="node" />引入到声明文件,表明这个文件使用了@types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。
- 例如,把
- 仅当在你需要写一个
d.ts文件时才使用这个指令。 - 若要在
.ts文件里声明一个对@types包的依赖,使用--types命令行选项或在tsconfig.json里指定。 查看 在tsconfig.json里使用@types,typeRoots和types(opens new window)了解详情。
- 与
/// <reference no-default-lib="true"/>- 这个指令把一个文件标记成默认库。 你会在
lib.d.ts文件和它不同的变体的顶端看到这个注释。 - 这个指令告诉编译器在编译过程中不要包含这个默认库(比如,
lib.d.ts)。 这与在命令行上使用--noLib相似。 - 还要注意,当传递了
--skipDefaultLibCheck时,编译器只会忽略检查带有/// <reference no-default-lib="true"/>的文件。
- 这个指令把一个文件标记成默认库。 你会在
/// <amd-module />
- 用JSDoc类型表示类型信息
- 属性的推断来自于类内的赋值语句
- 构造函数等同于类
- 。。。。
结构
这里介绍了为各种文件怎么去创建声明文件。
使用依赖
- 依赖全局库
- 如果你的库依赖于某个全局库,使用
/// <reference types="..." />指令
- 如果你的库依赖于某个全局库,使用
- 依赖模块
- 如果你的库依赖于模块,使用
import语句
- 如果你的库依赖于模块,使用
- 依赖UMD库
- 如果你的全局库依赖于某个UMD模块,使用
/// <reference types指令 - 如果你的模块或UMD库依赖于一个UMD库,使用
import语句 - 不要使用
/// <reference指令去声明UMD库的依赖!
- 如果你的全局库依赖于某个UMD模块,使用
- 依赖全局库
防止命名冲突
一个简单的规则是使用库定义的全局变量名来声明命名空间类型。 比如,库定义了一个全局的值
cats,你可以这样写declare namespace cats { interface KittySettings { } }1
2
3