TypeScript
TypeScript 重点知识整理。
Null 和 undefined 类型
常规类型检查模式下:
类型检查器认为null和undefined赋值给一切。实际上,null和undefined是每一个类型的有效值, 并且不能明确排除它们(因此不可能检测到错误)
--strictNullChecks严格空检查模式中:
可以切换到新的严格空检查模式中。 null和undefined值不再属于任何类型的值,仅仅属于它们自己类型和any类型的值 (还有一个例外, undefined也能赋值给void)。
TypeScript2.0 操作符
严格空检查模式中前提下:
可选操作符:
x?: number; // x的类型是 number | undefined
表达式操作符:
a: number | null; // a的类型是 number | null
非空断言操作符:
let s = e!.name; // 断言e是非空并访问name属性,强行告诉编译器,不要检查这里了,我断言了!
运算x!产生一个不包含null和undefined的x的值。断言的形式类似于x和x as T
never类型:
never是永不返回函数的“返回类型”,也是变量在类型保护中永不为true的类型。
// 函数返回never必须无法执行到终点,例子:
function error(message: string): never {
throw new Error(message);
}
只读属性和索引签名:
属性或索引签名现在可以使用readonly修饰符声明为只读的。
interface Point { readonly x: number; readonly y: number; }
let b: ReadonlyArray<number> = [1, 2, 3];
TypeScript2.0 接口
interface SquareConfig {
color?: string;
width?: number;
readonly x: number; // 只读属性
// [x: number]: string;
// [x: string]: string;
// 数字索引的返回值必须是字符串索引返回值类型的子类型
}
// 函数类型接口,定义了SearchFunc方法参数和返回值的方法类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
TypeScript2.0 类
class Test{
static A: string;
B: string;
test() {
return Test.A + '-' + this.B;
}
}
等于:
let Test = (function(){
function Test() {
this.B:string;
}
Test.A:string;
Test.prototype.test = function() {
return Test.A + '-' + This.B;
}
return Test;
})();
类的属性实际绑定在方法上。
类的方法和静态属性绑在 prototype 上。
let C: Test = new Test(); // 声明了 Test 实例类型
let D: typeof Test = Test; // 声明了 Test 方法类型
TypeScript2.0 函数
let myAdd: (baseValue:number, increment:number) => number =
function(x: number, y: number): number { return x + y; };
只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
// 如果存在某一个库接口,传入的参数方法中有this: void表示不需要this的类型声明的话。以下做法:
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
this.info = e.message;
};
onClickBadVoid(this: void, e: Event) {
this.info = e.message;
};
onClickBadF = (e: Event) => {
this.info = e.message;
};
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // ① 这是报错的,因为你隐式声明了,this的类型, 这是会带入this进入方法里。
uiElement.addClickListener(h.onClickBadVoid); // ② 这是报错的,这样参数类型正确了,但是方法体内不能使用this了。this为undefined
uiElement.addClickListener(h.onClickBadF); // ③这是没问题的,因为参数类型正确,并且箭头函数会在this字面量等于上下文的Handler,可以使用this
③:缺点是每个Handler对象都会创建一个箭头函数,因为这时onClickBadF为一个类的属性,而不是方法,只不过属性指向了方法,不绑在原型链上。
①、②:方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。
TypeScript2.0 泛型
// 泛型接口
interface GenericIdentityFnA {
<T>(arg: T): T;
}
interface GenericIdentityFnB<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
// 这里让 myIdentity 指向 identity ,并不是执行 identity 函数
let myIdentity: GenericIdentityFnA = identity;
let myIdentity: GenericIdentityFnB<number> = identity;
// 泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
类有两部分:静态部分和实例部分。 泛型类指的是实例部分的类型,所以类的静态属性不能使用这个泛型类型。
TypeScript2.0 类型推论
let x = [0, 1, null]; // 候选类型为:number | null
计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型。最终的通用类型取自候选类型。
let zoo = [new Rhino(), new Elephant(), new Snake()];
这情况下候选类型为:Rhino | Elephant | Snake,以此TS计算不出来他们的通用类型,并且在这种情况下会给出{}这种类型给zoo,但是之后使用zoo访问成员的时候,就会报错了,因为{}没有任何成员。
let zoo: Animal[] = [new Rhino(), new Elephant(), new Snake()];
这情况下通用类型为:Animal[] , 候选类型为:Rhino | Elephant | Snake。
TypeScript2.0 类型兼容性
TS 的类型兼容性是建立在结构类型上的,不像 C# 或 Java 的是建立在名义类型上的,例如:
interface Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, 因为是建立在结构类型上的,结构符合
p = new Person();
可以看出,上面代码段是没问题的,但是如果在 Java 上是肯定报错的,因为:因为Person类没有明确说明其实现了Named接口。
—兼容规则—
interface Named {
name: string;
}
let x: Named;
let y = { name: 'Alice', location: 'Seattle' };
x = y;
y = x; // Error,y 对 x 说使用我的人可能要访问 location 你没有
// 解释:Named有name,y 也有name ,那就满足了①。
// 但是在这是后使用x访问成员变量时只能访问name,访问不到location.
// 就像类实现接口后,将类实例赋予接口类型的变量。
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // OK
x = y; // Error
// 解释: 函数参数满足的是③,因为,y函数体内表明可能会用到s,
// 如果将x赋予y,y就说:x你不用s那就随你便吧,就像 Array#forEach
// 如果将y赋予x,x就说:y你要用s?我这里没有你s的位置,请滚吧!
let x = () => ({name: 'Alice'});
let y = () => ({name: 'Alice', location: 'Seattle'});
x = y; // OK
y = x; // Error because x() lacks a location property
// 解释: 函数返回类型满足③
// 因为返回的类型可以和①一样理解即可,
// 返回的类型如果只有一个属性,返回多了,我也不管,也访问不到了
① 类型兼容对于对象来说为:小 兼容于 大
② 类型兼容对于函数返回值来说为:小 兼容于 大
③ 类型兼容对于函数参数来说为:大 兼容于 小
枚举类型之间是不兼容的。
TypeScript2.0 声明模块 declare
使用其他外部模块的时候要想描述非TypeScript编写的类库的类型,我们需要声明类库所暴露出的API。
我们叫它声明因为它不是“外部程序”的具体实现。 它们通常是在 .d.ts文件里定义的。
// node.d.ts (simplified excerpt)
declare module "url" {
export interface Url {
protocol?: string;
hostname?: string;
pathname?: string;
}
export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}
declare module "path" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export let sep: string;
}
使用的时候为:
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");
假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。
// declarations.d.ts
declare module "hot-new-module";
使用的时候:
import x, {y} from "hot-new-module";
x(y);
全局扩展:
// observable.ts
export class Observable<T> {
// ... still no implementation ...
}
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Array.prototype.toObservable = function () {
// ...
}
模块声明通配符
某些模块加载器如SystemJS 和 AMD支持导入非JavaScript内容。 它们通常会使用一个前缀或后缀来表示特殊的加载语法。 模块声明通配符可以用来表示这些情况。
declare module "*!text" {
const content: string;
export default content;
}
// Some do it the other way around.
declare module "json!*" {
const value: any;
export default value;
}
使用的时候:
import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);
文件保存的时候还是使用的是 .txt/.json 的格式、但是使用的时候就可以根据声明的样子来进行import 例如:./xyz.txt!text
TypeScript2.0 命名空间
外部命名空间
流行的程序库D3在全局对象d3里定义它的功能。 因为这个库通过一个 <script>标签加载(不是通过模块加载器),它的声明文件使用内部模块来定义它的类型。 为了让TypeScript编译器识别它的类型,我们使用外部命名空间声明。 比如,我们可以像下面这样写:
// D3.d.ts (部分摘录)
declare namespace D3 {
export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
};
}
export interface Event {
x: number;
y: number;
}
export interface Base extends Selectors {
event: Event;
}
}
declare let d3: D3.Base;
TypeScript2.0 模块解析
模块导入分为相对和非相对导入方式
相对导入是以/,./或../开头的。 下面是一些例子:
- import Entry from "./components/Entry";
- import { DefaultHeaders } from "../constants/http";
- import "/mod";
所有其它形式的导入被当作非相对的。 下面是一些例子:
- import * as $ from "jQuery";
- import { Component } from "angular2/core";
TypeScript 模块导入策略(node导入策略类似的,因为ts也是建立在nods之上)
比如,有一个导入语句import { b } from "./moduleB"在/root/src/moduleA.ts里,会以下面的流程来定位"./moduleB":
- /root/src/moduleB.ts
- /root/src/moduleB.tsx
- /root/src/moduleB.d.ts
- /root/src/moduleB/package.json (如果指定了"typings"属性)
- /root/src/moduleB/index.ts
- /root/src/moduleB/index.tsx
- /root/src/moduleB/index.d.ts
如果是非相对导入的话:
- /root/src/node_modules/moduleB.ts
- /root/src/node_modules/moduleB.tsx
- /root/src/node_modules/moduleB.d.ts
- /root/src/node_modules/moduleB/package.json (如果指定了"typings"属性)
- /root/src/node_modules/moduleB/index.ts
- /root/src/node_modules/moduleB/index.tsx
- /root/src/node_modules/moduleB/index.d.ts
往上一层走:
- /root/node_modules/moduleB.ts
- /root/node_modules/moduleB.tsx
- /root/node_modules/moduleB.d.ts
- /root/node_modules/moduleB/package.json (如果指定了"typings"属性)
- /root/node_modules/moduleB/index.ts
- /root/node_modules/moduleB/index.tsx
- /root/node_modules/moduleB/index.d.ts
之后继续往上一层走...
TypeScript2.0 声明文件说明
global.d.ts 用于声明全局库。
module-class.d.ts 用于声明可构造的模块 var x = require("bar"); new x()
module-function.d.ts 用于声明函数模块 var x = require("bar"); x()
module.d.ts 用于声明不能被调用和构造的模块
global-plugin.d.ts 用于声明全局插件
module-plugin.d.ts 用于声明模块插件
global-modifying-module.d.ts 用于声明能够修改全局作用域的模块
某库依赖全局库:
/// <reference types="someLib" /> // 使用三斜杠指令的 reference
function getThing(): someLib.thing;
依赖于某模块:
import * as moment from "moment";
function getThing(): moment;
依赖于UMD库:
如果你是全局库需要依赖UMD库,那就用:
/// <reference types="moment" />
function getThing(): moment;
如果你是模块库需要依赖于UMD库,那就用:
import * as someLib from 'someLib';