Typescript的运行环境
node、npm/yarn、typescript(用于编译成JS的cli)、ts-node(typescript+node,TS的运行时)
Typescript的类型
与JS一致的7种数据类型
- number
- string
- boolean
- symbol
- null
- undefined
- object
字面量类型
type Teacher = "teacher"; // Teacher类型的变量只允许赋值为"teacher"
type Profession = "teacher" | "student"; // Profession类型的变量只允许赋值为"teacher"或"student"
const str = 'hello';
type Hello = typeof str; // typeof str的结果是'hello', 所以Hello是字面量类型'hello'的别名
枚举类型(及其反向映射)
枚举的语法:
enum Fruit {
apple = 2, // 假如未指定,则默认从0开始递增。也可以给所有枚举值指定为字符串。
banana,
orange,
}
const num: number = Fruit.banana; // 3
反向映射:数字枚举时,既可以根据属性名获取枚举值,也可以根据枚举值获取属性名
const num: number = Fruit.banana; // 3
const name: string = Fruit[num]; // 'banana'
函数类型
function func1(arg1: string, arg2: number): boolean { // 定义ts函数
return true;
}
const func2 = (arg1: string, arg2: number): boolean => true; // 定义ts函数并赋值给func2
const func3: (arg1: string, arg2: number) => boolean = (arg1, arg2) => true; // 声明类型并赋值
字典类型/接口/函数类型 interface
表达字典类型是interface最常用的使用场景:
interface A {
a: number;
b: boolean;
c: string[];
d(x: number, y: number): number;
e: (x: number, y: number) => number;
}
const va: A = {
a: 123,
b: true,
c: ['hello', 'ts'],
d(x, y){
return x + y;
},
e: (x, y) => x + y,
};
interface还具有表达接口的能力。所谓接口就是对对象结构的约定
interface B {
a?: number; // ? 表示该属性并非必需
readonly b: string; // readonly 表示该属性只可读不可写
[propName: string]: any; // 索引签名
}
const vb: B = {
b: 'hello ts',
}
额外属性检查:一个interface类型被赋值对象字面量时有个特殊情况,该字面量会被检查是否具有interface规定之外的额外属性。若要跳过这种情况,可以使用类型断言或不要直接赋字面量
interface Person {
name: string;
}
const alice = { name: 'Alice', age: 18 };
const person: Person = alice; // 类型检查通过,因为被赋值的不是对象字面量,不会检查额外属性
const anotherPerson: Person = { // 类型检查报错,因为被赋值的是对象字面量,经过检查发现了额外属性age,它不存在于Person接口
name: 'Bob',
age: 17,
};
其实根据后面介绍的类型兼容,interface类型被赋值对象字面量时如果缺少属性也会不兼容,所以实际上上述情况下被赋值必须跟interface规定的结构完全一样,属性不能多也不能少
interface还可以用于描述函数类型
interface Func {
(x: number, y: number): boolean;
}
const compare: Func = (x, y) => x > y;
interface是可以继承/多继承的
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
const square: Shape = {
color: 'red',
penWidth: 12,
sideLength: 100,
};
混合类型
// 比如可以用接口表示一个变量既是函数又可以作为对象
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;
交叉类型与联合类型
type CrossType = string & number; // 交叉类型。type关键字表示类型别名
type UnionType = string | number; // 联合类型
const c: CrossType = 123; // ts类型检查报错,因为123不能满足既是string又是number(实际上任何赋值都不能满足,所以CrossType实际上是never类型)
const u: UnionType = 123; // ts类型检查通过,因为UnionType只要是string或者number都可以满足
// 交叉类型一般用于merge两个字典类型从而得到新类型,不过如果存在冲突的话则冲突属性会变成never类型(无论如何赋值都会类型检查报错)
type ICrossType = A & B;
类型断言
- 尖括号形式
const oneStr: any = "this is a string";
const len: number = (<string>oneStr).length;
- as形式
const oneStr: any = "this is a string";
const len: number = (oneStr as string).length;
以上两种断言效果等价,但是在tsx文件中只能使用as形式,大概是因为尖括号在tsx中有特殊的含义(表示组件实例)
类型保护
如果一个值是交叉类型,我们可以访问各子类型的所有属性;但如果一个值是联合类型,我们只能访问各子类型共有的属性,所以就会出现下面的情况:
interface Student {
learn(): void;
}
interface Teacher {
teach(): void;
}
type Person = Student | Teacher;
function doSomething(person: Person): void {
if (person.learn !== undefined) {
person.learn(); // 类型检查报错, 因为learn并不是Student和Teacher类型的共有属性
}
}
虽然代码符合逻辑,能够正常执行,但是为了避免类型检查报错,我们可以采取“类型保护”措施,比如:
- 类型断言
function doSomething(person: Person): void {
if (person.learn !== undefined) {
(person as Student).learn(); // 将person断言为Student类型就能使用learn属性了
}
}
- 类型谓词
function isStudent(person: Person): person is Student {
return (person as Student).learn !== undefined; // 输入Person类型的值,返回布尔值,但注意返回类型是主谓宾结构
}
function doSomething(person: Person): void {
if (isStudent(person)) { // ts会根据谓词得知person为Student类型,这样就允许使用learn属性了
person.learn();
}
}
- 使用typeof或者instanceof进行类型判断
利用这两个关键词对类型进行判断,作用跟类型谓词是一样的,只是写起来更简单。typeof只能判断基本类型,instanceof可以判断构造函数
class Student {
constructor(){}
learn(){}
}
class Teacher {
constructor(){}
teach(){}
}
type Person = Student | Teacher;
function doSomething(person: Person): void {
if (person instanceof Student) {
person.learn();
}
}
类型兼容
某个变量已经声明为某种类型(或者隐含地被TS编译器推导为某种类型)之后,再被赋值时必须兼容该类型才能类型检查通过。一般来说只有以下情况是兼容的:
- 声明类型包含了赋值类型,比如
let a: string; a = 'hello'这句里a的类型是string, 它是所有字面量类型的集合,而'hello'的类型是字面量类型'hello',所以前者包含了后者 - 赋值类型与声明类型完全一致(其实可以归结为1的情况)
- 声明类型和赋值类型都是interface,且赋值类型不是字面量(无额外属性检查)且后者包含前者规定的所有属性(繁赋简)(其实可以归结为1的情况)
- 声明类型和赋值类型都是函数,且前者的函数签名包含后者的所有形参(简赋繁)
类型推断
当初始化某个变量时,如果没有指定其类型,则TS编译器会根据赋值自动推断出其类型
let v = 123; // TS推断为number类型
v = 'hello'; // Type '"hello"' is not assignable to type 'number'
类
// 用interface契约来约束class的结构
interface ClockInterface {
currentTime: Date; // 约束实例属性或方法
setTime(time: Date): void; // 约束原型方法
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(time: Date) {
this.currentTime = time;
}
constructor(){}
}
// 其它约束
class Employee {
static company = "bat"
readonly code: string; // 只读属性
private _fullName: string;
get fullName() { // 类存取器, 约束类的原型属性
return this._fullName;
}
set fullName(name: string) { // 类存取器, 约束类的原型属性
this._fullName = name;
}
}
// 类的继承
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
getName() {
return this.name;
}
move(n: number) {
console.log(`${this.name} moves ${n} meters`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name); // 这里的super表示基类构造函数,在子类的构造函数里访问this之前必须先调用super
}
move(n: number) { // 覆盖基类的同名方法
super.move(n); // 这里的super表示基类的原型
console.log('Snake');
}
}
// 抽象类:只能作为基类使用的类,不会被直接实例化;与接口(约束结构)不同的是它可以包含成员的实现细节
abstract class Base {
abstract makeSound(word: string): void; // abstract关键字修饰的表示抽象方法,它不包含具体实现但要求派生类中必须实现,这一点与接口类似
move(len: number): void {
console.log(`${len} meters moved`);
}
}
class Son extends Base {
makeSound(word: string){
console.log('yell');
}
}
类成员的访问修饰符
// 类成员的默认修饰符为public,表示可以自由访问
// private修饰符表示只能在类定义体内部访问
// protected修饰符表示只能在类定义体内部或者派生类中访问
// 构造函数也可以被标记成 protected。 这意味着这个类不能在包含它的类外被实例化,但是能被继承。比如,
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
class Employee extends Person { // Employee 能够继承 Person
private department: string;
constructor(name: string, department: string) {
super(name);
this.department = department;
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.
// 修饰符兼容性举例
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Rhino extends Animal {
constructor() { super("Rhino"); }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
let animal = new Animal("Goat");
let rhino = new Rhino();
let employee = new Employee("Bob");
rhino = animal
rhino = employee // error
类具有两种类型:静态类型(类函数/构造器本身的类型),实例类型(类实例化以后具有的类型),前面我们说的类型都是后者。如果要描述静态类型可以这样:
interface InstanceInterface { // 实例类型
name: string;
age: number;
}
interface ConstructorInterface { // 静态类型,类似于函数接口
new (name: string, age: number): InstanceInterface;
}
类可以当作接口使用
class Point {
x: number;
y: number;
}
interface Point3d extends Point {
z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};
泛型
泛型就是高阶类型(概念类比于高阶函数、高阶组件),是一种类型生成器,我们通过向其传入参数来输出实际类型
下面介绍的泛型函数,其参数是运行时指定的;而泛型类型和泛型类,其参数则是在代码里手动声明
泛型函数
基础写法
function hello<T>(arg: T): T {
return arg;
}
const hello = <T>(arg: T): T => arg;
之前说过interface也可以表述函数类型,所以泛型函数也可以这么写
interface Func {
<T>(arg: T): T;
}
const func: Func = arg => arg;
// 或如下,花括号{}就代表了interface
const func: { <T>(arg: T): T } = arg => arg;
使用泛型函数的两种方法:
- 用类型断言指定类型:
const temp = hello<string>('hello world') - 利用类型推断,让TS运行时根据传入的参数类型自动确定T的类型:
const temp = hello('hello world');
泛型类型
type Type<T> = T;
type NumberType = Type<number>; // number
// 函数类型也可以用泛型类型来表达
// ⚠️注意这里表示的是泛型的函数类型,而不是泛型函数
interface GenericIdentityFn<T> {
(arg: T): T;
}
const myIdentity: GenericIdentityFn<number> = arg => arg;
泛型类
// 泛型类与泛型接口的定义方式一致
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; };
泛型约束
指的是对泛型参数施加的限制条件
// 约束了T必须包含length属性
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
// 约束了K必须是T的属性之一
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // okay
getProperty(x, "m"); // error: Argument of type 'm' isn't assignable to 'a' | 'b' | 'c' | 'd'.
// 工厂函数:约束了泛型参数T必须是类c的实例类型
// 注意此处c的类型{new(): T; }表示它是一个类,且其实例类型为T
function create<T>(c: {new(): T; }): T {
return new c();
}