Typescript 基础用法

基础类型

  • number, string, boolean, array, tuple, enum, any, void, null, undefined, never, Object, 类型断言

  • 数组

let list: number[] = [1, 2, 3]; // 推荐这种写法
let list: Array<number> = [1, 2, 3];
const arr: (number | string)[] = [1, "2"];
  • 元祖(Tuple)
// 注意下面的元祖只能有两个元素,多了的访问会报错
let x: [string, number]; // 也就是定义了多种类型元素的数组
// 元组一般应用场景是 csv 或 excel 文件的格式
  • 枚举
// 有以下 js 代码
const Status = {
  OFFLINE: 0,
  ONLINE: 1,
  DELETED: 2,
};

function getResult(status) {
  if (status === Status.OFFLINE) {
    return "offline";
  } else if (status === Status.ONLINE) {
    return "online";
  } else if (status === Status.DELETED) {
    return "deleted";
  }
  return "error";
}

// 在 ts 中可以用枚举类型进行编写
// 它的值是它的索引值
enum Status {
  OFFLINE,
  ONLINE,
  // ONlINE = 4, 可以直接设置索引值,后面的值会直接 +1
  DELETED,
}

console.log(status[0]); // 打印出 'OFFLINE'
  • any
// 在重构 js代码 时很有用
let notSure: any = 4;
notSure = "may";
let list: any[] = [1, true, "free"];
  • void
// 和 any 相反,表示没有任何类型,一般用在函数中
function warnUser(): void {
  console.log("This is my word");
}
  • null,undefined
// 可以这样设置,但在 strict模式 下会报错
let num: number = 3;
num = null;
  • never
// 它是任何类型的子类型,一般用在函数返回值为报错或是无法结束的
function error(message: string): never {
  throw new Error(message);
}

function infinite(): never {
  while (true) {}
}
  • object

类型注解(type annotation)

// 就是我们告诉TS变量是什么
// 如果 TS 能够自动分析变量类型,我们就什么也不需要做了
// 如果 TS 无法分析变量类型的话,我们就需要使用类型注解
let count: number;
count = 123;

// 如果不加变量注释,TS 就不会知道变量的类型,需要自己加
function getTotal(firstNumber: number, secondNumber: number) {
  return firstNumber + secondNumber;
}

类型推断

// 此时 TS 能够推断出 num 为 number 类型
let num = 1;
// 在之后强制定义类型,告诉电脑我知道自己在干什么
let someValue: any = "this is a string";
// 以下两种写法一样
let strLength: number = (<string>someValue).length;
// jsx 只支持下一种
let strLength: number = (someValue as string).length;

类型别名

  type User = {name: string, age: number}
  const objectArr: User[] = [{name: 'dell', age: 28}
  // TS 不会强制你的类的每一项都是类的实例
  class Teacher {
    name: string;
    age: nubmer;
  }
  const objectArr: Teacher[] = [{name: 'dell', age: 28}, new Teacher()]

接口

interface

// 只要你声明的这个属性有这个属性且类型对了,就不会报错
interface LabelledValue {
  label: string;
}
// 不会报错
let myObj = { size: 10, label: "Size 10 Object" };

interface Person {
  name: string;
  age?: number;
  say(): string;
}
// 类应用 interface
class User implements Person {
  greeting = "dell";
}

// interface 和 type 相类似,但并不完全一致

可选属性

interface Square {
  // 表示这个属性可以有也可以没有
  color?: string
  area: num
}
// 使用场景
function createSquare(config: SquareConfig): Square {
  let newSquare = { color: 'white', area: 100 }
  if (config.color) {
    newSquare.color = config.color
  }
  if (config.width) {
    newSquare.area = config.width * config.width
  }
  return newSquare
}

let mySquare = createSquare(config: { color: 'block'})

只读属性

// 保证属性不能被修改
interface Point {
  readonly x: number;
  readonly y: number;
}
// 不能对数组的项进行修改,甚至不能用方法
let ro: ReadonlyArray<number> = [1, 2, 3];
/* 注意 number[] 和 ReadonlyArray<number> 不是同一类型,
 * 不能互相转换,但是可以用类型断言
 */

多个属性

interface Person {
  name: string;
  age?: number;
  // 此时可以加上任意属性和属性值
  [propName: string]: any;
}

// 不报错
getPersonName({
  name: "dell",
  sex: "male",
});

函数类型

// 函数类型定义参数和返回值
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
// 注意参数名不用保持一致,参数也可以不用定义类型,它会进行类型推断
mySearch = function (source: string, subString: string): boolean {
  let result = source.search(subString);
  return result > -1;
};

// 结构函数参数会导致语法问题,此时应该如下处理
function add({ first, second }: { first: number; second: number }): number {
  return first + second;
}

// 有时一些内置函数会返回没有类型注释的值,比如 JSON.parse 此时需要自己注释
interface Person {
  name: string;
  age: number;
}
const rawData = '{"name": "dell"}';
const newData: Person = JSON.parse(rawData);

可索引类型

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];

class Greeter {
  greeting: string;

  constructor(message: string) {
    this.greeting = message;
  }

  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = newGreeter("news");

继承

class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  move(distance: number = 0) {
    console.log(`${this.name} mobed ${distance}m`);
  }
}

class snake extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distance: number = 5) {
    console.log("Slithering...");
    super.move(distance);
  }
}

私有域

// 当没有加上修饰符时默认是 public 在类的内部外部都可使用
class Person {
  // 此时外部访问不到,但是子类可以访问
  protected age: number;
  // 此时外部和子类都不可访问
  private name: string;
  // 此时这个属性只读
  readonly gender: string;
  // 此时不能实例化这个类
  protected constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  private department: string;

  constructor(name: string, departmeent: string) {
    super(name);
    this.department = department;
  }
  // 此时报错,因为 age 是 protect 的属性
  getElevatorPitch() {
    return `Hello, my age is ${this.age}`;
  }
}

// 如果两个类拥有相同的 private 属性,则不能兼容
class Animal {
  // 可以直接在参数前加上访问修饰符,尽量不用参数属性
  constructor(private name: string) {
    this.name = name;
  }
}
class Employee {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
}
Animal = Employee; // 报错

存取器

let passcode = "secret passcode";

class Employee {
  private _fullName: string;
  get fullName(): string {
    return this._fullName;
  }

  set fullName(newName: string) {
    if (passcode && passcode === "secret passcode") {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}

// 单例模式
class Demo {
  private static instance: Demo;
  // 将 constructor 变为 private,则在外部不能使用 new 来直接调用类
  private constructor() {}
  static getInstance() {
    if (!this.instance) {
      this.instance = new Demo();
    }
    return this.instance;
  }
}

const demo1 = Demo.getInstance();

静态属性

class Grid {
  static origin = { x: 0, y: 0 };
  scale: number;
}

抽象类

// 抽象类通常是作为其他类的基类使用,一般不能被直接实例化
abstract class Department {
  name: string;

  contructor(name: string) {
    this.name = name;
  }

  printName(): void {
    consle.log("Department name " + this.name);
  }

  // 子类必须自己实现抽象方法
  abstract printMeeting(): void;
}

class AccountingDepartment extends Department {
  constructor() {
    super("Accounting ad Auditing");
  }

  printMeeting(): void {
    console.log("The Accounting Department meets each Monday at 10 am");
  }

  genterateReports(): void {
    console.log("Generating accounting reports...");
  }
}

let department: Department = new AccountingDepartment();
// 此处因为抽象类中没有这个方法直接调用会报错
department.genterateReports(); // 报错

类的高级技巧

class Greeter {
  constructor(message?: string) {
    this.greeting = message;
  }
}
// typeof 可以改变静态属性
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there";

接口继承

class Point {
  x: number;
  y: number;
}

interface Poinit3d extends Poing {
  z: number;
}

let point3d: Point3d = { x: 1, y: 2, z: 3 };

函数

function buildName(firstName: string, lastName?: sring) {
  return firstName + " " + lastName;
}

let result1 = buildName("Bob");
let result2 = buildName("Bob", "Adams", "Sr.");
let result3 = buildName("Bob", "Adams");
let result4 = buildName(undefined, "Adams");

this

interface Card {
  suit: string;
  card: number;
}

interface Deck {
  suits: string[];
  cards: number[];
  // 这样就定义了 this,这个 this 不是一个真正的参数
  // 设定 this: void 表示不能访问 this
  // 如果想访问 this 可以使用箭头函数
  createCardPicker(this: Deck): () => Card;
}

let deck: Deck = {
  suits: ["hearts", "spades", "clubs", "diamonds"],
  cards: Array(52),
  createCardPicker: function () {
    return () => {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);

      return {
        suit: this.suits[pickedSuit],
        card: pickedCard % 13,
      };
    };
  },
};

重载

// 以下是同一函数重载的声明,当传入参数不同时的声明
// 注意要将最精确的放在前面
function pickCard({suit: string; card: number}[]):number
function pickCard(number):{suit: string; card: number}

// interface 也可实现函数重载
interface JQuery {
  (readyFunc: () => void): void
  (selector: string): JqueryIntance
}

泛型

// 泛型 generic 泛指的类型
// <T> 使得函数的参数和返回值的类型一致
function identity<T>(arg: T): T {
  return arg
}
let output = identity<string>('myString')
// 自动推断类型
let output = identity('myString')

function loggingIdentity<T>(arg: T[]): T[] {
  consoel.log(arg.length)
}

// 泛型还能定义两个
function join<T, P>(first: T, second: P) {
  return `${first}${second}`
}
join<number, string>(1'1')

// 在类中使用泛型
interface Item {
  name: string
}
class DataManager<T extends Item> {
  constructor(private data: T[]) {}
  getItem(index: number): string {
    return this.data[index].name
  }
}

// 用于限制泛型只能为 number 或 string 类型
class DataManager<T extends number | string> {
  // ...
}

const data = new DataManager([{name: 'dell'}])
data.getItem(0)

const func: <T>(param: T) => T = <T>(param: T) => {
  return param
}

// keyof 语法
// 用于推断出对象中对应 key 的值的类型
interface Person {
  name: string;
  age: number;
  gender: string;
}

class Teacher {
  constructor(private info: Person) {}
  // getInfo(key: string) {
    // 注意这里的 key 值无法保证为 Person 的属性,所以 ts 会报错
    // 可以用 if key === 来进行判断,但 ts 无法判断返回值是为 string 还是 number 还是 undefined
    // return this.info[key]
  // }

  // 此时可以完美识别 Person 的属性
  getInfo<T extends keyof Person>(key: T):Person[T] {
    return this.info[key]
  }
}

const teacher = new Teacher({
  name: 'dell',
  age: 18,
  gender: 'male'
})

const test = teacher.getInfo('name')

泛型接口

// 推荐方式
interface GenericIdentityFn<T> {
  (arg: T): T;
}
// 也可以设为各种字母
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;
};

泛型约束

interface lengthwise {
  length: number;
}
function loggingIdentity<T extends lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
// K 表示存在 T 的属性中
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}
// 工厂函数
function create<T>(c: { new (): T }): T {
  return new c();
}

class BeeKeeper {
  hasMask: boolean;
}

高级类型

交叉类型

// 几种类型之和,可以用于 assign 方法
function extend<T, U>(first: T, second: U): T & U {
  let result = {} as T & U;
  for (let id in first) {
    result[id] = first[id]; // 报错
    result[id] = first[id] as any; // 通过
  }
  for (let id in second) {
    if (!result.hasOwnProperty(id)) {
      result[id] = 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());
jim.name; // 可以调用
jim.log(); // 可以调用

联合类型

// 几种类型之一
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    return padding + value;
  }
  throw new Error(`Expected string or number got ${padding}`);
}

padLeft("Hello world", true); // 报错

interface Bird {
  fly();
  layEggs();
}

interface Fish {
  swim();
  layEggs();
}

function getSmallPet(): Fish | Bird {
  // ..
}
// 只能使用共有成员
let pet = getSmallPet();
pet.layEggs(); // 通过
pet.swim(); // 报错

类型保护

// 联合类型才会应用类型保护
interface Bird {
  fly()
  layEggs()
}

interface Fish {
  swim()
  layEggs()
}

function getSmallPet(): Fish | Bird {
  // ..
}
// 类型保护的一种:类型未识
function isFish(pet: Fish | Bird):pet is Fish {
  return (pet as Fish).swim !== undefined
}

if (isFish(pet)) {
  pet.swim()
} else {
  pet.fly()
}

// instanceof 判断
// 如果是实例可以使用 instanceof 判断
if(pet instanceof Bird) {
  pet.fly()
}

// 类型断言
function trainAnial(animal: Bird | Dog) {
  if (animal.fly) {
    (animal as Bird).sing()
  } else {
    (animal as Dog).bark()
  }
}

// in 语法做类型保护,可代替上面的 isFish 函数
function trainAnial(animal: Bird | Dog) {
  if ('fly' in animal) {
    animal.sing()
  } else {
    animal.bark()
  }
}

// typeof 做类型保护
// 如果是基本类型可以直接用 if typeof 判断之后调用方法
function add(first: string | number, second: string | number) {
  if (typeof first === 'string' || typeof second === 'string') {
    return `${first}${second}`
  }
  return first + second
}

可以为 null 的类型

function f(x: number, y?: number) {
  return x + (y || 0);
}

f(1, undefined); // 通过
f(1, null); // 报错,只能为 undefined 和 number

function broken(name: string | null): string {
  function postfix(epither: string) {
    // 直接告诉编辑器 name 不为 null
    return name!.chart(0) + ". the " + epither;
  }
  // 这里避免了为 null
  name = name || "Bob";
  return postfix(name);
}

字符串字面量类型

type Easing = "ease-in" | "ease-out" | "ease-in-out";

class UIElement {
  animate(dx: number, dy: nuber, easing: Easing) {
    if (easing === "ease-in") {
      // ..
    } else if (easing === "ease-out") {
    } else if (easing === "ease-in-out") {
    } else {
      // 这里执行不到
    }
  }
}

let button = new UIElement();
button.animate(0, 0, "ease-in");
button.animate(0, 0, "uneasy"); // 报错

命名空间

// 以 /// 开头来表示 namespace 所依赖的 namespace
///<reference path='./components.ts' />

namespace name {
  //...
  export property var a = '111'
}

console.log(name.a) /// '111'

装饰器

类装饰器

// 装饰器本身就是一个函数,它会在类创建完成之后立即对类进行装饰
// 而不是在创建实例之后进行修饰
// 它能拿到类的构造函数然后归构造函数进行一些修改
function decorator(constructor) {
  constructor.prototype.getName = () => console.log("kankan");
}
@decorator
class Test {}

// 先装饰的装饰器会后被执行
@decorator1
@decorator2
class Test {}

// 如果想分情况使用装饰器,可以通过一个函数返回一个装饰器
function testDecorator() {
  return function (constructor: any) {
    constructor.prototype.getName = () => {
      console.log("dell");
    };
  };
}
@testDecorator()
class Test {}

// 标准写法
// 有 new 的意思是这是一个构造函数
function testDecorator() {
  return function <T extends new (...args: any[]) => {}>(constructor: T) {
    return class extends constructor {
      getName() {
        return this.name;
      }
    };
  };
}

const Test = testDecorator()(
  class {
    name: string;
    constructor(name: string) {
      this.name = name;
    }
  }
);

const test = new Test("dell");
test.getName();

方法装饰器

// 装饰器对应的参数和 Object.defineProperty(obj, prop, descriptor) 非常类似
// 普通方法,target 对应的是类的 prototype, key 是装饰的方法的名字
// 如果是静态方法,target 对应的是类的构造函数
// descriptor 是用来控制函数的属性特性的
// 它会等类创建完成后立即进行修改
function getNameDecorator(
  target: any,
  key: string,
  descriptor: PropertyDescriptor
) {
  descriptor.writable = true;
}

class Test {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  @getNameDecorator
  getName() {
    return this.name;
  }
}

访问器的装饰器

// 装饰器对应的参数和 Object.defineProperty(obj, prop, descriptor) 非常类似
// target 对应的是类的 prototype, key 是装饰的方法的名字
// descriptor 是用来控制函数的属性特性的
// get 或 set 不能用相同的装饰器
// 它会等类创建完成后立即进行修改
function visitDecorator(target: any, key: string, descriptor: PropertyDescriptor) {
  descriptor.writable = true
}

class Test {
  private _name: string
  constructor (name: string) {
    this._name = name
  }

  get name {
    return this._name
  }

  @visitDecorator
  set name (name: string) {
    this._name = name
  }
}

属性装饰器

// target 对应的是类的 prototype, key 是装饰的方法的名字
// 返回值可以定义属性的特性
// 它会等类创建完成后立即进行修改
function nameDecorator(target: any, key: string): any {
  const descriptor: PropertyDescriptor = {
    writable: false,
  };
  return descriptor;
}

class Test {
  @nameDecorator
  name = "Dell";
}

参数装饰器

// target 对应的是类的 prototype, key 是装饰的方法的名字, paramIndex 为参数所在的名字
// 返回值可以定义属性的特性
// 它会等类创建完成后立即进行修改
function paramDecorator(target: any, method: string, paramIndex: number): any {
  const descriptor: PropertyDescriptor = {
    writable: fasle,
  };
  return descriptor;
}

class Test {
  getInfo(@paramDecorator name: string, age: number) {
    console.log(name, age);
  }
}