TypeScript 快速入门

106 阅读9分钟

安装&运行

npm install -g typescript     // 安装 TypeScript
npm install -g ts-node        // 安装 ts-node, 直接运行 ts 代码

变量声明

let 申明变量可以再次赋值, const 声明常量,不能再次被赋值

let money: number = 199;
money = 166;
const counter: number = 12;
// counter = 13;   // Cannot assign to 'counter' because it is a constant.

基本类型

boolean 布尔类型

let isDone: boolean = false;

number 数字类型

TypeScript 中所有的数字都是浮点数,这些浮点数的类型都是 number

let score: number = 96.5; // 小数

let age: number = 12; // 十进制

let tag: number = 0x10; // 十六进制

let flag: number = 0b11; // 二进制

let errorCode: number = 0o17; // 八进制

string 字符串类型

字符串可以使用 双引号 “” 和单引号 ‘’ 表示, 同时支持字符串模板,反引号 ${ expr } 表示多行文本和 表达式

let username: string = "bob";
username = "bob mark";
let desc = ` Hello, my name is ${username},
and I will be ${age + 1} years old next month.
`;

数组类型 Array / []

声明数组有两种方式: 一种是元素类型后面跟上中括号 [] , 另一种是数组泛型 Array<元素类型>

let address: string[] = ["北京", "上海", "武汉"];
let address2: Array<string> = ["北京", "上海", "武汉"];
let scores: Array<number> = [90.5, 93, 99.1];
let names: (string | null)[] = ["tom", null];  // 数组元素为联合类型

Tuple 元组类型

// 元组类型表示一个已知元素数量和类型的数组, 各元素类型不必相同

let userInfo: [string, number] = ["jack", 22];

enum 枚举类型

数字枚举

默认情况为数字枚举, 每项枚举值自动基于 前一个值 自增,如果没指定具体数值,则第一条从 0 开始

enum LoadState {
  Loading, // 0
  Success, // 1
  Failure, // 2
}
let success = LoadState.Success;
let failure = LoadState.Failure;
console.log(success);   // 1
console.log(failure);   // 2

可以手动指定每项的索引, 也可以根据索引找到对应的枚举项名字

enum LoadState {
  Loading = 1,
  Success = 5,
  Failure,
}
let success = LoadState.Success;
let failure = LoadState.Failure;
console.log(success); // 5
console.log(failure); // 6
let state = LoadState[5];
console.log(state);    // Success

字符串枚举

默认情况下,枚举索引是从 0 开始的,我们可以手动指定, 也可以根据索引找到对应的元素名字

enum Color {
  Red = "red",
  Green = "green",
  Blue = "blue",
}
let green: Color = Color.Green;
console.log(green);   // green

any 类型

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量:

let notSure: any = 2;
notSure = "text";
notSure = false;
let anyArr: any[] = [10, "text", false];
anyArr[0] = true;
anyArr[0] = 100;

void 类型

void 表示没有任何类型。 当一个函数没有返回值时,其返回值类型就是 void

function print(): void {
  console.log("print .....");
}

声明一个void类型的变量没有什么大用,因为你只能为它赋予 undefined 和 null

let noUse: void = undefined;

undefined, null 类型

TypeScript里,undefined 和 null 两者各自有自己的类型分别叫做 undefined 和 null

let u: undefined = undefined;
let n: null = null;

默认情况下null和undefined是所有类型的子类型。 就是说你可以把 null和undefined赋值给number类型的变量,然而,当你指定了--strictNullChecks标记,null和undefined只能赋值给void和它们各自类型。 这能避免 很多常见的问题。 也许在某处你想传入一个 string或null或undefined,你可以使用联合类型string | null | undefined

never 类型

never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。返回never的函数必须存在无法达到的终点

function error(message: string): never {
  throw new Error(message);
}

object 类型

object 表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型。

function create(*o*: object | null) {}
create({ prop: 0 }); // OK
create(null); // OK

函数类型

// 定义函数类型的变量 myAdd
let myAdd: (a: number, b: number) => number = function (
  x: number,
  y: number
): number {
  return x + y;
};
// 调用
let result = myAdd(1, 2)

类型断言

类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用,

类型断言有两种方式表示,一种是 尖括号<>, 一种是 as 关键字

let message: any = "this is string";
// any 类型转换成 string 类型,再调用 string 类型的 length 属性
let messageLength: number = (<string>message).length;
let messageLength2: number = (message as string).length;

函数

基本使用

函数调用时, 参数个数和类型要和 函数声明的 一一对应。

function add(x: number, y: number): number {
  return x + y;
}
add(1, 2)
// add(1, 2, 3)  // error 
// add('1', 2)      // error

若指定了--strictNullChecks标记, 就不能将 undefined 传给 number 类型

// 调用 add() 函数
add(undefined, undefined); // error: Argument of type 'undefined' is not assignable to parameter of type 'number'

可选参数

在 JavaScript里,每个参数都是可选的,可传可不传,没传参的时候,它的值就是undefined。

而在 TypeScript 里是 在参数名旁使用问号 ? 实现可选参数。

可选参数必须跟在非可选参数后面

function buildName(firstname: string, lastname?: string): string {
  if (lastname) {
    // 如果 lastname 有值,则拼接上 lastname
    return firstname + " " + lastname;
  }
  return firstname;
}
buildName("jack")
buildName("jack", 'chen')
// error: A required parameter cannot follow an optional parameter.
// function buildName2(firstname?: string, lastname: string) {
//
// }

默认值参数

与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。 如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined值来获得默认值

// 默认值参数在末尾, 调用时可以少传一个参数
function getName(firstname: string, lastname: string = "zhang") {
  return firstname + " " + lastname;
}
getName("jack"); // jack zhang
getName("jack", "chen"); // jack chen

// 默认值参数在开头,调用时,需要传 undefined 才能取到 默认值
function getUser(address: string = "北京", name: string) {
  return "name: " + name + ", address: " + address;
}
getUser(undefined, "刘强东"); // name: 刘强东, address: 北京
getUser("上海", "黄峥"); // name: 黄峥, address: 上海

剩余参数

必要参数,默认参数和可选参数有个共同点:它们表示某一个参数。 有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。 在JavaScript里,你可以使用 arguments来访问所有传入的参数。在TypeScript里,你可以把所有参数收集到一个变量里

剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...)后面给定的名字,你可以在函数体内使用这个数组

function getPhones(usingPhone: string, ...usedPhone: string[]) {
  console.log(usingPhone + " " + usedPhone.join(" "));
}
getPhones("iPhone");     // iPhone 
getPhones("iPhone", "SAMSUNG", "HUAWEI", "XIAOMI", "VIVO");  // iPhone SAMSUNG HUAWEI XIAOMI VIVO

接口

在TypeScript里,只要两个类型内部的结构兼容那么这两个类型就是兼容的。 这就允许我们在实现接口时候只要保证包含了接口要求的结构就可以,而不必明确地使用 implements语句。

interface Teacher {
  name: string;
  age: number;
  say: () => void;
}

let zhao: Teacher = {
  name: "Android",
  age: 15,
  say: () => {
    console.log("name = " + zhao.name + ",age = " + zhao.age);
  },
};
zhao.say();   // name = Android,age = 15

可选成员

如果说我们在一个对象当中,我们某个成员是可有可无的,那对于约束这个对象的接口来说,我们可以使用可选成员

interface CardItem {
  title: string;
  subtitle?: string;
}

let card: CardItem = {
  title: "title",
};
let card2: CardItem = {
  title: "title",
  subtitle: "subtitle",
};

只读成员

interface CardItem {
  title: string;
  subtitle?: string;
  readonly createTime: number;
}

let card: CardItem = {
  title: "title",
  createTime: 19002236521,
};

card.title = "new title";
// card.createTime = 2221212122; // error: Cannot assign to 'createTime' because it is a read-only property

类声明

class Student {
  private name: string;
  private age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  // setter
  set setName(name: string) {
    this.name = name;
  }
  // getter
  get getName() {
    return this.name;
  }
  greet() {
    console.log("my name is " + this.name + ", age = " + this.age);
  }
}
let stu: Student = new Student("mary", 12);
stu.greet();
stu.setName = "jack";
let tempName = stu.getName;

构造函数的参数使用 public 修饰等同于创建了 同名的成员变量

// 构造函数的参数使用 public 修饰等同于创建了 同名的成员变量, 等同于下面:
class User {
  constructor(public name: string, public age: number) {}
  greet() {
    console.log("name: " + this.name + ", age = " + this.age);
  }
}

// 与上面等同
class User2 {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
  greet() {
    console.log("name: " + this.name + ", age = " + this.age);
  }
}

类 public,protected, private

  • public: 可以任意地方访问, 默认为 public
  • protected: 可以在本类和派生类中访问
  • private: 只能在本类中访问

只读属性 readonly

readonly修饰的属性为只读属性, 只读属性只能在属性声明时或构造函数里初始化, 初始化后就不允许再更改了。

class Computer {
  readonly name: string;   // 只能在构造函数或声明时初始化赋值
  readonly memorySize: number = 32;
  constructor(name: string) {
    this.name = name;
  }
}
let macbookpro = new Computer("MacBook Pro");
macbookpro.memorySize = 64;// Cannot assign to 'memorySize' because it is a read-only property       

static

类的实例成员,仅当类被实例化的时候才会被初始化赋值,而类 static 属性存在于类本身上面而不是类的实例上。

class Grid {
  static origin = { x: 0, y: 0 };
  calculateDistanceFromOrigin(point: { x: number; y: number }) {
    let xDist = point.x - Grid.origin.x;
    let yDist = point.y - Grid.origin.y;
    return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }
  constructor(public scale: number) {}
}
let grid1 = new Grid(1.0); // 1x scale
let grid2 = new Grid(5.0); // 5x scale

继承

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  move() {
    console.log("move");
  }
}
class Bird extends Animal {
  constructor(name: string) {
    super(name);
  }
  fly() {
    console.log("fly");
  }
}
let bird: Bird = new Bird("bird");
bird.fly();
bird.move();

抽象类

abstract class Drive {
  abstract run();
}

class Car extends Drive {
  run() {
    console.log("汽车 driving");
  }
}
class Metro extends Drive {
  run() {
    console.log("地铁 driving");
  }
}

类当做接口使用

TypeScript 中 接口可以继承自类

class Point {
  x: number;
  y: number;
}
interface Point3d extends Point {
  z: number;
}
let point3d: Point3d = { x: 1, y: 2, z: 3 };

泛型

泛型: 指在定义函数,接口,类时, 不预先指定函数参数,类,接口中属性的具体类型,而是在使用时指定类型的特性

泛型函数

// 泛型函数
function echo<T>(t: T) {
  console.log(t);
}
echo<string>("hello");
echo<number>(12);

泛型类

// 泛型类
class ListNode<T> {
  data: T;
  next: ListNode<T> | null;

  constructor(data: T, next: ListNode<T> | null) {
    this.data = data;
    this.next = next;
  }
}

let head: ListNode<string> = new ListNode<string>("head", null);
let head2: ListNode<number> = new ListNode<number>(-1, null);

泛型约束

interface Length {
  length: number;
}

// 约束传入 data 的类型必须要有 length 属性
function printLength<T extends Length>(data: T) {
  console.log("length = " + data.length);
}

printLength("China");
printLength({ name: "China", length: 3 });
printLength(23)   // error: Argument of type 'number' is not assignable to parameter of type 'Length'

Type

Type又叫类型别名(type alias), 作用是给一个类型起一个新名字,不仅支持interface定义的对象结构,还支持基本类型、联合类型、交叉类型、元组等任何你需要手写的类型。

type num = number; // 基本类型
type stringOrNum = string | number; // 联合类型
type person = {name: string}; // 对象类型
type user = person & { age: number } // 交叉类型
type data = [string, number]; // 元组
type fun = () => void; // 函数类型