执行下面的命令安装TypeScript:
npm install -g typescript
TypeScript支持类型提示,类型提示让代码自动完成更加准确:
// 布尔
let isDone: boolean = false;
// 数字
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
// 字符串
let name: string = "bob";
// 模板字符串
let name: string = `Gene`;
let age: number = 37;
let sentence: string = `Hello, my name is ${ name }.
I'll be ${ age + 1 } years old next month.`;
// 数组 let list: number[] = [1, 2, 3]; // 数组,泛型语法 let list: Array<number> = [1, 2, 3]; // 元组 let x: [string, number]; x = ['hello', 10];
// 枚举
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
// 默认情况下,枚举值从0开始,可以修改
enum Color {Red = 1, Green, Blue}
// 不限定的类型 let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // 你可以假设不限定类型有任何方法,编译器不会检查 notSure.ifItExists(); // 不限定类型数组 let list: any[] = [1, true, "free"];
// Void类型
// 用于表示没有返回值的函数
function warnUser(): void {
console.log("This is my warning message");
}
// 只能赋值undefined或null
let unusable: void = undefined;
// undefined 和 null类型 let u: undefined = undefined; let n: null = null;
默认情况下null和undefined是所有类型的子类型。 就是说你可以把 null和undefined赋值给任何类型的变量。但是如果指定了--strictNullChecks标记,则只能赋值给void或者各自的类型 —— null赋值给null,undefined赋值给undefined。
// never 永远不存在值的类型,例如:总是抛出异常或者不会有返回值的函数或者箭头函数表达式
// 总是抛出异常
function error(message: string): never {
throw new Error(message);
}
// 编译器可以推导出返回值为never类型
function fail() {
return error("Something failed");
}
// 永远不会返回
function infiniteLoop(): never {
while (true) {
}
}
表示非基本类型:即非number,string,boolean,symbol,null或undefined。
尖括号语法:
let someValue: any = "this is a string"; let strLength: number = (<string>someValue).length;
as语法:
let someValue: any = "this is a string"; let strLength: number = (someValue as string).length;
以一个对象类型为参数,产生字符串或者数字的字面值的联合,这些数字或者字符串来自对象的键:
type Point = { x: number; y: number };
type P = keyof Point; // "x" | "y"
// 如果目标类型具有字符串或数字的索引签名,则keyof操作符会返回对应的类型(而非字面值)的联合
type Arrayish = { [n: number]: unknown };
type A = keyof Arrayish; // type A = number
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // type M = string | number
// 注意这里的M是 string | number的联合,这是因为JavaScript中对象的键,会被强制转换为字符串
// obj[0]和obj["0"]总是一样的
后缀的感叹号!表示非空断言操作符,其意义是:
因为一个叹号区反,得到的是布尔值,再次取反相当于恢复“原值”(的布尔转型)。
a ?? b仅仅当a为null或者undefined时,返回操作符右侧表达式即b,否则返回a。
TypeScript的核心原则是,对所有结构基于鸭子类型识别,接口用来定义对象具有哪些字段,或者定义函数的规格。
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
interface SquareConfig {
color?: string;
width?: number;
}
interface Point {
readonly x: number;
readonly y: number;
}
接口可以用来描述函数的签名:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
let result = source.search(subString);
return result > -1;
}
接口中可以声明多个函数签名,这样的接口表示多个重载函数版本的联合:
interface Func1 {
(v1: number, v2: number): number;
(v1: string, v2: string): string;
}
const f1: Func1 = (v1, v2): any => {
return v1 + v2;
};
console.log(f1("1", "2"));
console.log(f1(1, 2));
可以用来指定数组的值类型、映射的键值类型:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
// 防止给索引赋值
interface ReadonlyStringArray {
readonly [index: number]: string;
}
interface User {
id: string;
name: string;
[key: string]: any;
}
let user: User = {
id: "alex",
name: "Alex"
prop1: 1,
"prop2": 2
};
TypeScript允许字符串、数字作为索引。
接口可以用来限制一个类必须满足的规约:
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) { }
}
interface ClockInterface {
currentTime: Date;
// 定义方法
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
// 支持多重继承
interface Square extends Shape, PenStroke {
sideLength: number;
}
let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
接口可以描述一个函数,在限定函数签名的同时,指定函数具有的额外属性:
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
当让接口继承一个类时,它会获得所有类的成员,但是不会获得这些成员的实现。
包括私有、保护成员,都会被继承。这种情况下,只有接口所继承的那个类的子类,才能实现该接口:
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}
// 抽象类
abstract class Greeter {
// 静态属性
static cname: string = "Greeter";
// 实例变量
greeting: string;
// 可选的访问限定符 private protected public
private name: string;
// 只读成员
readonly age: number;
// 构造函数
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
// 继承
class Dog extends Greeter {
greet() {
console.log('Woof! Woof!');
// 调用父类的方法
super.greet();
}
}
// 类型提示
let myAdd: (x: number, y: number) => number = function(x: number, y: number): number { return x + y; };
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
当调用该函数的时候,传入undefined或者没有传递,则默认参数生效。
在所有必须参数之后的,带有默认值的参数,都是可选参数。
给予不同的类型提示,即可实现函数重载。
// 函数参数的泛型化
function identity<T>(arg: T): T {
return arg;
}
// 泛型函数的类型提示
let myIdentity: <T>(arg: T) => T = identity;
// 显式指定类型参数
let output = identity<string>("myString");
// 自动推导类型参数
let output = identity("myString");
// 某个函数泛型化
interface GenericIdentityFn {
<T>(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
// 整个接口泛型化
interface GenericIdentityFn<T> {
(arg: T): T;
}
function identity<T>(arg: T): T {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
// 规定实际类型必须继承自Lengthwise
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
用于描述已经存在的枚举类型的规格:
declare enum Enum {
A = 1,
B,
C = 2
}
交叉类型包含所有指定的类型的特征,例如, Person & Serializable & Loggable同时是 Person 和 Serializable 和 Loggable。
function extend<T, U>(first: T, second: U): T & U {
let result = <T & U>{};
for (let id in first) {
(<any>result)[id] = (<any>first)[id];
}
for (let id in second) {
if (!result.hasOwnProperty(id)) {
(<any>result)[id] = (<any>second)[id];
}
}
return result;
}
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(): void;
}
class ConsoleLogger implements Loggable {
log() {
// ...
}
}
var jim = extend(new Person("Jim"), new ConsoleLogger());
var n = jim.name;
jim.log();
允许是指定类型集合中的任何一个:
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
function isFish(pet: Fish | Bird): {
return (<Fish>pet).swim !== undefined;
}
可以用于任何类型:
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 Container<T> = { value: T };
甚至可以在类型别名的属性中,引用自己:
type Tree<T> = {
value: T;
left: Tree<T>;
right: Tree<T>;
}
可以将字符值联合起来,形成类似于枚举的效果:
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
}
else if (easing === "ease-out") {
}
else if (easing === "ease-in-out") {
}
else {
// error! should not pass null or undefined.
}
}
}
可以为联合类型定义别名:
type Shape = Square | Rectangle | Circle;
从TypeScript 1.5开始, “内部模块”称做“命名空间”。 “外部模块”简称为“模块”。
TypeScript与ECMAScript 2015一样,任何包含顶级import或者export的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的(因此对模块也是可见的)。
任何声明都可以通过export关键字来导出:
export interface StringValidator {
isAcceptable(s: string): boolean;
}
export const numberRegexp = /^[0-9]+$/;
export class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
// 先定义再导出
class ZipCodeValidator implements StringValidator {
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export { ZipCodeValidator };
// 导出时指定导出名
export { ZipCodeValidator as mainValidator };
// 重新导出其它模块中的对象
export {ZipCodeValidator as RegExpBasedZipCodeValidator} from "./ZipCodeValidator";
// 全部重新导出
export * from "./StringValidator"; // exports interface StringValidator
import { ZipCodeValidator } from "./ZipCodeValidator";
let myValidator = new ZipCodeValidator();
// 导入并重命名
import { ZipCodeValidator as ZCV } from "./ZipCodeValidator";
let myValidator = new ZCV();
// 将整个模块导入到一个变量
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
每个模块可有最多一个默认导出:
declare let $: JQuery; export default $;
默认导出在导入时,可以使用任何名字:
import jq from "JQuery";
jq("button.continue").html( "Next Step..." );
默认导出可以的可以是匿名函数,因为它的名字不重要:
const numberRegexp = /^[0-9]+$/;
export default function (s: string) {
return s.length === 5 && numberRegexp.test(s);
}
你甚至可以导出一个字面值:
export default "123";
命名空间(以前称为内部模块)可以用来组织代码。
命名空间可以分散在多个文件中:
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
需要利用引用标签,来说明不同文件之间的依赖关系:
/// <reference path="Validation.ts" />
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
export class LettersOnlyValidator implements StringValidator {
isAcceptable(s: string) {
return lettersRegexp.test(s);
}
}
}
使用命名空间中定义的对象的语法:
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["Letters only"] = new Validation.LettersOnlyValidator();
可以为命名空间设置别名,简化访问:
namespace Shapes {
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
import polygons = Shapes.Polygons;
JSX是一种源于React的类似于XML的语法,可以嵌入在JavaScript的源码中。
要在TypeScript中使用JSX,你需要:
TypeScript具有三种JSX模式:preserve,react和react-native。 这些模式只在代码生成阶段起作用 - 类型检查并不受影响:
由于尖括号风格的断言var foo = <foo>bar;和JSX语法冲突,因而在.tsx文件中,这种风格的断言不被支持,必须使用as操作符。
要使用TypeScript编写Node.js程序,可以借助ts-node。ts-node是Node.js的TypeScript执行引擎,它能够JIT的将TypeScript编译为JavaScript,你不需要预先编译就可以在Node.js运行时上执行TypeScript。
ts-node的特性包括:
# Locally in your project. npm install -D typescript npm install -D ts-node # Or globally with TypeScript. npm install -g typescript npm install -g ts-node # Depending on configuration, you may also need these npm install -D tslib @types/node
# 执行脚本
ts-node script.ts
# 启动解释器
ts-node
# 执行命令行提供的脚本
ts-node -e 'console.log("Hello, world!")'
# 执行并打印
ts-node -p -e '"Hello, world!"'
#!/usr/bin/env ts-node
console.log("Hello, world!")
可以通过下面的代码请求ts-node模块并注册它的loader,此loader会影响后续的requires。ts-node的工作原理就是挂钩到Node的模块加载API。
require('ts-node').register({ /* options */ })
或者使用命令行参数:
node -r ts-node/register node -r ts-node/register/transpile-only
ts-node会自动查找并加载tsconfig.json文件。命令行选项--skipProject可以跳过当前项目的tsconfig.json,--project则用于明确指定项目配置文件的位置。
{
// This is an alias to @tsconfig/node12: https://github.com/tsconfig/bases
"extends": "ts-node/node12/tsconfig.json",
// 大部分配置在此
"ts-node": {
// 如果要跳过类型检查,去除下面这一行
"transpileOnly": true,
"files": true,
"compilerOptions": {
// 覆盖下面的编译选项
}
},
"compilerOptions": {
// typescript编译选项
}
}
TypeScript基本上都会使用import语法,但是可以在执行期间转换为CommonJS的require()或者保持ESM风格的import不变。
如果要转换为CommonJS风格,在package.json配置:
{
// This can be omitted; commonjs is the default
"type": "commonjs"
}
在tsconfig.json中配置:
{
"compilerOptions": {
"module": "CommonJS"
}
}
如果需要为tsc,webpack等工具保留"module": "ESNext",你可以:
{
"compilerOptions": {
"module": "ESNext"
},
"ts-node": {
"compilerOptions": {
"module": "CommonJS"
}
}
}
NodeJS的原生ESM加载器钩子还处于试验阶段,可能改变。ts-node的ESM支持会尽量保持稳定,但是它依赖于底层的Node API。
你需要在package.json中设置:
{
"type": "module"
}
在tsconfig.json中设置:
{
"compilerOptions": {
"module": "ESNext" // or ES2015, ES2020
},
"ts-node": {
// Tell ts-node CLI to install the --loader automatically, explained below
"esm": true
}
}
同时,你还需要确保--loader被传递给Node。如果使用ts-node命令行,会自动传递:
# 等价于上面配置文件中的"esm": true ts-node --esm ts-node-esm
如果你直接使用Node的命令行,则需要手工传递:
node --loader ts-node/esm ./index.ts # 或者 NODE_OPTIONS="--loader ts-node/esm" node ./index.ts
安装ts-node:
npm install --save-dev ts-node
创建Node.js运行配置,增加Node Parameters:--require ts-node/register。
使用ESM时,参考上文,即增加Node Parameters:--loader ts-node/esm。
Leave a Reply