TypeScript现在基本上是JS强类型超集语言的事实标准了,作为一个JS工作者,学习也是必须的事情。虽然之前因为使用上的便利性以及语言性能的问题,我一直都没有真的把TS放在心上,这次算是为了自己的职业突破,开始着手进行学习和准备。
而VsCode是微软开始拥抱开源以来的一个非常不错的实践范例,作为TS同一家公司的产品,支持当然非常好。所以这次也是一并尝试。
TS是JS的超集,兼容JS,所以使用上问题不会太大。但有一个问题需要预先解决,就是TS是无法在Node和浏览器内直接运行的,需要转译成JS才能运行。
转译成Node需要:
安装TypeScript:
npm install -g typescript
编写tsc配置文件jsconfig.json
:
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"sourceMap": true,
"rootDir": "src",
"outDir": "dist"
},
"include": [
"src/**/*"
],
"exclude": [
"dist",
"node_modules"
]
}
这里有个坑:outDir
还有rootDir
都是放在compilerOptions
这个节点下面的,而不是放在根目录下的。这个范例居然很少,我一直都没找到,试了好久才试出来,不得不说文档实在是太差。
命令行下运行tsc进行转译:
tsc -p jsconfig.json --watch
这样,代码就能在node里简单运行了。
any
void
,--strictNullChecks
开关可以在转译的时候进行空检查联合类型
never
:
就这几个需要注意下。
const的使用需要注意,一旦申明之后,后续不可将整个变量替换:
const a = {"x": 1}; // defined
则不可再
a = {"x": 2}; // failed
但可以单独对对象内部进行改动:
a.x = 3; // ok
接口在TS里就是类型申明,可以理解为Golang中的struct或者C语言中的struct。仅仅只是对一种类型的数据结构进行命名,方便后续的使用。
此外,类型的申明中可以包含可选属性
,该属性可以存在,也可以不存在。申明时,使用名字后面加问号的方式:color?
。
只读类型:
readonly
最简单判断该用readonly还是const的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const,若做为属性则使用readonly
索引签名:
[propName: string]: any;
:带有任意数量的其它属性
函数接口:
interface SearchFunc {
(source: string, subString: string): boolean; // (参数): 返回值
}
接口也可以由class使用implements
关键字来实现,这和其他语言基本没区别。
接口可以使用extends
关键字相互扩展。
tsc的--noImplicitThis
可以协助你找出有问题的this
。
但在启用该选项的同时,tsc会对this
的类型进行检查,所以在编写代码的时候需要小心处理。可以使用显式申明的方式来告知tsc,这里的this是什么类型的,或者应不应该进行检查。
修改的方法是,提供一个显式的this参数。 this参数是个假的参数,它出现在参数列表的最前面:
function f(this: void) {
// make sure `this` is unusable in this standalone function
}
// e.g:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
当然this申明也是可以带参数的:
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used this here. using this callback would crash at runtime
this.info = e.message;
};
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
但这个范例会报错,因为之前我们要求UIElement的回调是void类型的this,而这个下面的范例给的是一个Handler类型的this,类型检测就报错了。
这里可以将申明改为void的this,但同时也无法在函数内使用this。或者使用箭头函数,但每个Handler对象都会创建自己的箭头函数闭包。e.g:
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use this here because it's of type void!
console.log('clicked!');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
// OR
class Handler {
info: string;
onClickGood = (e: Event) => { this.info = e.message }
}
联合类型:
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: string | number) {
// ...
}
遇到联合类型时候的类型断言:
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function getSmallPet(): Fish | Bird {
// ...
}
let pet = getSmallPet();
pet.layEggs(); // okay
pet.swim(); // errors
// 断言 e.g:
if ((<Fish>pet).swim) {
(<Fish>pet).swim();
} else {
(<Bird>pet).fly();
}
function isFish(pet: Fish | Bird): pet is Fish {
return (<Fish>pet).swim !== undefined;
}
类型别名,以type关键字创建一个新的类型名:
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === 'string') {
return n;
}
else {
return n();
}
}
// 类型别名不能出现在声明右侧的任何地方。
type Yikes = Array<Yikes>; // error
另一个重要区别是类型别名不能被extends和implements。
在TypeScript中,假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。
// declarations.d.ts
declare module "hot-new-module";
// 简写模块里所有导出的类型将是 any
import x, {y} from "hot-new-module";
x(y);
EOF