美文网首页
Typescript学习概要

Typescript学习概要

作者: stevekeol | 来源:发表于2020-07-14 00:00 被阅读0次

大致印象

  • TypeScript 增加了静态类型、类、模块、接口和类型注解.
  • TypeScript 可用于开发大型的应用
  • TypeScript 使用类型和接口等概念来描述正在使用的数据,这使开发人员能够快速检测错误并调试应用程序
  • TypeScript 中的数据要求带有明确的类型
  • TypeScript 为函数提供了缺省参数值。
  • TypeScript 引入了 JavaScript 中没有的“类”概念。
  • TypeScript 中引入了模块的概念,可以把声明、数据、函数和类封装在模块中。

一.快速上手

安装typescript

npm install -g typescript

编译代码

[踩坑]: 报错“此系统禁止运行脚本”
[解决方案]: 以管理员身份打开powershell, 键入:set-ExecutionPolicy RemoteSigned, 再选择Y/A即可

tsc demo.ts

类型注解

TypeScript里的类型注解是一种轻量级的为函数或变量添加约束的方式。

//TypeScript提供了静态的代码分析,它可以分析代码结构和提供的类型注解
function greeter(person: string) {
    return "Hello, " + person;
}

let user = [0, 1, 2];

console.log(greeter(user));
// Argument of type 'number[]' is not assignable to parameter of type 'string'.

接口

接口是利用关键字interface声明一个函数的传入参数应该是什么样子的“组合类型”。
当传入的参数对象中的个别参数少了/类型不匹配,则报错;参数多了没关系。

interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person: Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = { firstName: "Jane"};
let user = { firstName: "Jane", lastName: 'jiege'};
let user = { firstName: "Jane", lastName: 'jiege', age: 28};

console.log(greeter(user));

typescript支持基于类的面向对象编程(TypeScript里的类只是JavaScript里常用的基于原型面向对象编程的简写)

//该类带有一个构造函数和一些公共字段
class Student {
    fullName: string; //构造函数的公共字段需注明类型

    //构造函数的参数,需注明暴露程度(public,private等等)
    //在构造函数的参数上使用public等同于创建了同名的成员变量
    constructor(public firstName, public middleInitial, public lastName) {
        this.fullName = firstName + " " + middleInitial + " " + lastName;
    }
}

//类和接口可以一起工作,开发者可自行决定抽象的级别
interface Person {
    firstName: string;
    lastName: string;
}

function greeter(person : Person) {
    return "Hello, " + person.firstName + " " + person.lastName;
}

let user = new Student("Jane", "M.", "User");

console.log(greeter(user));

二. Typescript和React的配合

因为create-react-app中已经默认内嵌了typescript,因此该github项目已经archived了


三. Typescript和Express、MongoDB的配合

四. Typescript和微信小程序的配合(Taro支持选择Typescript)


五. Typescript基础知识

基本类型

//boolean
let isDone: boolean = false;
//number
//同js,ts中的所有数字都是浮点数
//ts支持十进制、十六进制、二进制、八进制
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
let binaryLiteral: number = 0b1010;
let octalLiteral: number = 0o744;
//string
let name: string = `my name is ${name}`;
//Array

//声明1
//当类型后面跟着的是[]时候,表示左边的变量是一个数组,数组元素类型是该类型
let name: string[] = ['zhang', 'jie'];

//声明2
//数组泛型,尖括号里面是元素的类型
let names:Array<string> =  ['zhang', 'jie'];
//元组
//当一个数组的元素的数量和类型确定时,其中各元素的类型不必相同可以声明一个元组类型

let x: [string, number, boolean] = ['stevekeol', 28, true];
//当访问一个越界的元素,会使用联合类型替代
//当访问一个已知索引的元素,会得到正确的类型
//enum

enum Color {Red, Green, Blue}
let c: Color = Color.Green;

console.log(c); //c返回的是序列号1(默认情况下,元素编号从0开始)

//可以手动赋值
enum Color {Red = 1, Green, Blue}

//可以全部手动赋值
enum Color {Red = 1, Green = 2, Blue = 4}

//枚举类型最大的好处时可以根据枚举的值得到它的名字
let colorName: string = Color[2];
//Any 
//表示任何类型都可以

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; 

//当你只知道数组一部分元素类型时
let list: any[] = [1, true, "free"];
//void
//表示任何类型都没有

//表示该函数没有返回值
function warnUser(): void {
    console.log('You are my girl');
}
//undefined, null

//undefined, null是所有类型的子类型,即可以赋值给其它类型(除非指定了--strictNullChecks标记)
//也许在某处你想传入一个 string或null或undefined,你可以使用联合类型string | null | undefined
//鼓励尽可能多的使用--strictNullChecks
//never类型表示的是那些永不存在的值的类型
//object
//表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
//类型断言
//比如对于any类型,有时候你明确知道它具体是什么类型
let str: any = 'You are my girl'

//第一种类型断言
let leng: number = (<string>str).length;

//第二种类型断言
let leng: number = (str as string).length;

接口

// interface的基本用法
interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);
//interface的可选属性
interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {
  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({color: "black"});
//只读属性
interface Point {
    readonly x: number;
    readonly y: number;
}

//ReadonlyArray声明数组时,可以确保数组创建后不会被修改
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!

//readonly和const的区别是,如果你想声明一个属性,则用readonly,如果想声明一个变量,则用const
//函数的参数会逐个进行检查,要求对应位置上的参数类型是兼容的

//调用签名
interface SearchFunc {
    (source: string, substring: string): boolean;
}

let mySearch: SearchFunc = function(source: string, substring: string) {
    return source.search(substring) > -1
}
//可索引的类型

//可以用索引签名,描述对象索引的类型
interface StringArray {
  [index: number]: string;
}
let myArray: StringArray =  ["Bob", "Fred"];
let myStr: string = myArray[0];

//TS支持数字和字符串两种索引,可以同时使用两种索引,但是需要注意,数字索引的返回值的类型必须是字符串索引返回的类型的子类型。
class Animal {
    name: string;
}
class Dog extends Animal {
    breed: string;
}
interface NotOkay {
    [x: number]: Animal; // error
    [x: string]: Dog;
}

//返回的类型要跟索引签名中的返回类型一致 ??????????????????????
interface NumberDictionary {
  [index: string]: number;
  length: number;    // 可以,length是number类型
  name: string       // 错误,`name`的类型与索引类型返回值的类型不匹配
}

//将索引签名设置为只读,防止给索引签名赋值
interface ReadonlyStringArray {
    readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
//类的接口 ????????????????????????
interface ClockInterface {
    currentTime: Date;
    setTime(d: Date); //在接口中描述一个方法,在类中实现它
}
class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

//类的静态部分和实例部分的区别 ????????????????????????????????
//接口描述了类的公共部分,而不是公共和私有两部分
//定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数 createClock,它用传入的类型创建实例。
interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick();
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
    return new ctor(hour, minute);
}
class DigitalClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
//继承接口

interface Shape {
  color: string;
}
interface Square extends Shape {
  sideLength: number;
}
let test = <Square>{};
test.color = 'green';
test.sideLength = 28;


//单个接口继承多个接口
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;
//接口继承类

//当接口继承了一个类类型时,它会继承类的成员但不包括其实现
//当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)
class Control {
    private state: any;
}
interface SelectableControl extends Control {
    select(): void;
}
class Button extends Control implements SelectableControl {
    select() { }
}
class TextBox extends Control {
    select() { }
}
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
    select() { }
}

//类

//传统的JS使用函数和基于原型的继承来创建可重用的组件,面向对象的开发者而言,基于类的继承且对象是由类构建出来的
//ES6支持使用基于类的面向对象的方式
//TS也支持使用基于类的面向对象的方式(TS中可以使用ES6吗?可以的话,继承这一块代码层面怎么实现)

class Greeter {
  greeting: string; //属性
  constructor(message: string) { //构造方法
    this.greeting = message;
  }
  greet() { //方法
    return `Hello, ${this.greeting}`;
  }
}
let greeting = new Greeter('stevekeol');
//继承

class Animal { //超类(基类)
    move(distanceInMeters: number = 0) {
        console.log(`Animal moved ${distanceInMeters}m.`);
    }
}
class Dog extends Animal { //子类(衍生类)
    bark() {
        console.log('Woof! Woof!');
    }
}
const dog = new Dog();
dog.bark();
dog.move(10);

//复杂继承
//派生类包含了一个构造函数,它 必须调用 super(),它会执行基类的构造函数。 而且,在构造函数里访问 this的属性之前,我们 一定要调用 super()

class Animal {
  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}`);
  }
}
class Snake extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 5) {  //子类中重写父类的方法
    console.log('Slithering...');
    super.move(distanceInMeters); //此处调用父类的同名方法,适用于局部调整父类的方法
  }
}
class Horse extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 45) {
    console.log('Slithering...');
    super.move(distanceInMeters);
  }
}
let sam = new Snake('stevekeol is cool');
//和sam实例构造方式的区别是:tom实例只能使用Animal中的方法,不能使用Horse中自定义的方法
//即使 tom被声明为 Animal类型,但因为它的值是 Horse,调用 tom.move(34)时,它会调用 Horse里重写的方法
let tom: Animal = new Horse('zhangjie niubi'); 
sam.move();
tom.move(34);
//private修饰符

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");
//如果一个类型里包含一个 private成员,那么只有当另外一个类型中也存在这样一个 private成员, 并且它们都是来自同一处声明时,这两个类型是兼容的。
//Animal和 Rhino共享了来自 Animal里的私有成员定义 private name: string,因此它们是兼容的.
// 对于 protected成员也使用这个规则
animal = rhino;
animal = employee; // 错误: Animal 与 Employee 不兼容.

//protected修饰符

//protected修饰的变量,在其衍生类中也可访问使用,但是其实例中早已经不可用了
class Person {
    protected name: string;
    constructor(name: string) { this.name = name; }
}
class Employee extends 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");
console.log(howard.getElevatorPitch());
console.log(howard.name); // 错误

//构造函数使用protected修饰符

//则该类虽然能被继承,但是不能在包含它的类外被实例化
class Person {
    protected name: string;
    protected constructor(theName: string) { this.name = theName; }
}
// Employee 能够继承 Person
class Employee extends 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' 的构造函数是被保护的.

//readonly修饰符

//只读属性必须在声明时或构造函数里被初始化
class Octopus {
    readonly name: string;
    readonly numberOfLegs: number = 8;
    constructor (theName: string) {
        this.name = theName;
    }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.

//参数属性

//即在构造函数的参数前面添加一个限定符,则声明和赋值会同时进行;
//使用 private限定一个参数属性会声明并初始化一个私有成员;对于 public和 protected来说也是一样。
class Octopus {
    readonly numberOfLegs: number = 8;
    constructor(readonly name: string) {
    }
}

//存取器(有效的控制对对象成员的访问)
let password = 'zhangjie';
class Employee {
  private _fullName: string;

  get fullName(): string {
    console.log('getting...');
    return this._fullName;
  }

  set fullName(newName: string) {
    if(password && password === 'zhangjie') {
      console.log('setting');
      this._fullName = newName;
    } else {
      console.log('Error: unAuthed');
    }
  }
}
let employee = new Employee();
employee.fullName = 'stevekeol';
if(employee.fullName) {
  console.log(employee.fullName);
}

//类的静态属性(在类本身上而非实例上)

//每个实例都会用到的属性(但不是实例本身的属性)
//如同在实例属性上使用 this.前缀来访问属性一样,这里我们使用 '类名.'来访问静态属性
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);
let grid2 = new Grid(5.0);

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

//抽象类(不直接实例化,都是作为基类衍生出派生类再实例化)

abstract class Department {
  constructor(public name: string) {}
  printName(): void {
    console.log(`Department is ${this.name}`);
  }
  abstract printMeeting(): void; //抽象类中的抽象方法(仅定义方法签名但不包含方法体)不包含具体实现,且必须在派生类中实现
}
class AccountingDepartment extends Department {
  constructor() {
    super('YuZhou'); //派生类的构造函数中必须调用super()
  }
  printMeeting(): void {
    console.log('hello world'); //派生类中实现抽象类的抽象方法
  }
  generateReports(): void {
    console.log('generatied!');
  }
}
let department: Department = new AccountingDepartment(); //可以创建一个对抽象类型的引用
department.printName();
department.printMeeting();
department.generateReports(); //错误: 方法在声明的抽象类中不存在

//高级用法 - 构造函数 ?????????????????????

class Greeter {
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {
        if (this.greeting) {
            return "Hello, " + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}
let greeter1: Greeter = new Greeter();
console.log(greeter1.greet());
//取Greeter的类型而非实例的类型,即构造函数的类型,它包含了类所有的静态成员和构造函数
let greeterMaker: typeof Greeter = Greeter; 
greeterMaker.standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());

//高级技巧 - 类当接口使用
class Point {
    x: number;
    y: number;
}
interface Point3d extends Point {
    z: number;
}
let point3d: Point3d = {x: 1, y: 2, z: 3};

函数

//函数的完整类型
let myAdd: (x: number, y: number) => number =
    function(x: number, y: number): number { return x + y; };

//函数的简写类型
function add(x: number, y: number): number {
    return x + y;
}
//可选参数
//1. '?'必须跟在参数名称后面
//2. 可选参数必须跟在必须参数后面
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
let result3 = buildName("Bob", "Adams");  // ah, just right
//默认参数
function buildName(firstName: string, lastName = "Smith") {
  //...
}
//剩余参数
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
// this的绑定
let deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {

        //此处返回箭头函数,直接在函数返回时就将this绑定,而不是调用时再绑定
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);
            console.log(this);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13}; //返回一个对象时的写法
        }
    }
}

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
//this参数 --- 重点学习

//this参数是个假的参数,它出现在参数列表的最前面
interface Card {
    suit: string;
    card: number;
}
interface Deck {
    suits: string[];
    cards: number[];
    createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    // NOTE: The function now explicitly specifies that its callee must be of type Deck
    createCardPicker: function(this: Deck) {
        return () => {
            let pickedCard = Math.floor(Math.random() * 52);
            let pickedSuit = Math.floor(pickedCard / 13);

            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
console.log("card: " + pickedCard.card + " of " + pickedCard.suit);
//带this的回调函数 -------?????????????????????
interface UIElement {
    addEventListener(onclick: (this: void, e: Event) => void): void;
}

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!因为addClickListener要求函数带有this:void

//迫不得已的解决办法(箭头函数不会捕获this,所以可以传给期望this: void的函数)
//每个 Handler对象都会创建一个箭头函数。
//方法只会被创建一次,添加到 Handler的原型链上。 它们在不同 Handler对象间是共享的。
class Handler {
    info: string;
    onClickGood = (e: Event) => { this.info = e.message }
}
//重载

//为同一个函数提供多个函数类型定义来进行函数重载
//在定义重载的时候,一定要把最精确的定义放在最前面
//function pickCard(x): any并不是重载列表的一部分

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number; //对象数组的类型声明的写法
function pickCard(x: number): {suit: string; card: number; };

function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];

let pickedCard1 = myDeck[pickCard(myDeck)];
console.log("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
console.log("card: " + pickedCard2.card + " of " + pickedCard2.suit);

泛型

function identify(arg: number): number {
    return arg;
}

function identify(arg: number): number {
    return arg;
}

//多个泛型类型的使用
function identities<T, U>(arg1: T, arg2: U): [T, U] {
  return [arg1, arg2];
}

//使用any类型时会丢失信息:输出和输入的类型不见得一致
function identify(arg: any): any {
    return arg;
}

//使用类型变量(一种只表示类型而不是值的特殊变量)
function identify<T>(arg: T): T {
    return arg;
}

//该版本的identify函数称之为泛型函数。既可以适用于多个类型,又可以保证不丢失类型的信息。
let output1 = identify<string>('mystring');
let output2 = identify('mystring'); //类型推论(更普遍的写法)


//使用泛型变量时注意泛型的通用性
function loggingIdentity<T>(arg: T): T {
    console.log(arg.length);  //报错:T不见得一定有length属性
    return arg;
}
 
//泛型数组的使用
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length); 
    return arg;
}

//泛型数组的改写
function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);
    return arg;
}

/************************************************/

//泛型函数的类型
function identify<T>(arg: T): T {
    return arg;
}

//泛型类型参数在最前面
let myIdentify: <T>(arg: T) => T = identify;

//使用带有调用签名的对象字面量来定义泛型函数 ????????
let myIdentify: {<T>(arg: T): T} = identify;

//创建泛型接口,并使用
interface GenericIdentityFn {
    <T>(arg: T): T;
}
function identify<T>(arg: T): T {
    return arg;
}
let myIdentify: GenericIdentityFn = identify;


//把非泛型函数签名作为泛型类型一部分????????????????
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 = ""; //并没有什么限制只能使用number类型
myGenericNumber.add = (x, y) => x + y;

//泛型约束(一个类型参数,且它被另一个类型参数所约束)
function getProperty(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'.

//泛型里使用类类型(使用泛型创建工厂函数时,需要引用构造函数的类类型)
function create<T>(c: {new(): T; }): T {
    return new c();
}

//泛型里使用类类型(使用原型属性推断并约束构造函数与类实例的关系)????????????????
class BeeKeeper {
    hasMask: boolean;
}
class ZooKeeper {
    nametag: string;
}
class Animal {
    numLegs: number;
}
class Bee extends Animal {
    keeper: BeeKeeper;
}
class Lion extends Animal {
    keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}
createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

枚举

//使用枚举我们可以定义一些带名字的常量
enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

enum Response {
    No = 0,
    Yes = 1,
}
function respond(recipient: string, message: Response): void {
    // 通过枚举的属性来访问枚举成员,和枚举的名字来访问枚举类型
}
respond("Princess Caroline", Response.Yes)

//枚举成员的值可以是常量/计算出来的
//枚举-反向映射
enum Enum{
    A,
    B
}
let a = Enum.A;
console.log(Enum[a]);

类型推断

//使用这些表达式的类型来推断出一个最合适的通用类型,如果没有找到最佳通用类型的话,类型推断的结果为联合数组类型
let zoo = [new Rhino(), new Elephant(), new Snake()];

声明合并

//接口合并

//当接口 A与后来的接口 A合并时,后面的接口具有更高的优先级
//每组接口里的声明顺序保持不变,但各组接口之间的顺序是后来的接口重载出现在靠前位置。
//如果签名里有一个参数的类型是 单一的字符串字面量那它将会被提升到重载列表的最顶端。
interface Box {
    height: number;
    width: number;
}
interface Box {
    scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};

//合并命名空间

//合并之后,从其它命名空间合并进来的成员无法访问非导出成员
namespace Animals {
    export class Zebra { }
}
namespace Animals {
    export interface Legged { numberOfLegs: number; }
    export class Dog { }
}

//合并命名空间和类

//合并结果是一个类并带有一个内部类(TypeScript使用这个功能去实现一些JavaScript里的设计模式)
class Album {
    label: Album.AlbumLabel;
}
namespace Album {
    export class AlbumLabel { }
}

装饰器

启用实验性的装饰器特性, 需要:

//方案1:命令行
tsc --target ES5 --experimentalDecorators

//方案2:tsconfig.json:
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}

启用实验性质的元数据,需要:

//先安装依赖包
npm i reflect-metadata --save

//方案1:命令行
tsc --target ES5 --experimentalDecorators --emitDecoratorMetadata

//方案2:tsconfig.json
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
    }
}

装饰器是一种特殊类型的声明,它能够被附加到类声明方法访问符属性参数

装饰器使用@expression形式,expression求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息作为参数传入。

//如@test装饰器
function test(target) {
  //do something with target ...
}
//装饰器工厂
function color(value: string) {
    return function (target) {
        //do something with target
    }
}
//装饰器组合
@f
@g
x
//由上至下依次对装饰器表达式求值
//求值的结果会被当作函数,由下至上依次调用,如同复合函数f(g(x))
function f() {
    console.log("f(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("f(): called");
    }
}

function g() {
    console.log("g(): evaluated");
    return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
        console.log("g(): called");
    }
}

class C {
    @f()
    @g()
    method() {}
}

类中不同声明上的装饰器将按以下规定的顺序应用:

  • 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个实例成员。
  • 参数装饰器,然后依次是方法装饰器,访问符装饰器,或属性装饰器应用到每个静态成员。
  • 参数装饰器应用到构造函数。
  • 类装饰器应用到类。
//类装饰器
function sealed(constructor: Function) {
    Object.seal(constructor);
    Obejct.seal(constructor.prototype);
}

//此时将密封该类的构造函数和原型
@sealed
class Greeter {
    greeting: string;
    constructor(message: string) {
        this,greeting = message;
    }
    greet() {
        return `hello, ${this.greeting}`;
    }
}
//类装饰器 - 重载构造函数 ??????????????????
function classDecorator<T extends {new (...args: any[]): {}}>(constructor: T) {
    return class extends constructor {
        newProperty = 'new property';
        hello = 'overide';
    }
}

@classDecorator
class Greeter {
    property = 'property'; //此处是用到类型推断了吗?
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter('world'));
// 方法装饰器

// 会被应用到方法的属性描述符上,用来监视、修改、替换方法定义
// 装饰器工厂返回的函数的三个参数的含义分别是?
function enumerable(value: boolean) {
    return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.enumerable = value;
    }
}

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

    //修改属性描述符的enumable属性
    @enumerable(false)
    greet() {
        return `hello, ${this.greeting}`;
    }
}

console.log(new Greeter('zhangjie').greet());
//访问器装饰器

//会被应用到访问器的属性描述符,用来监视,修改,替换访问器的定义
//因为装饰器应用到一个属性描述符时,联合了get和set访问器,而非分开声明的。因此TS不允许同时装饰一个成员的get和set访问器。
function configurable(value: boolean) {
    return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
        descriptor.configurable = value;
    }
}

class Point {
    private _x: number;
    private _y: number;
    constructor(x: number, y: number) {
        this._x = x;
        this._y = y;
    }

    @configurable(false)
    get x() {
        return this._x;
    }

    @configurable(false)
    get y() {
        return this._y;
    }
}
//属性装饰器

// 属性装饰器表达式会在运行时当做函数被调用,传入两个参数:
// 1. 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
// 2. 成员的名字

// 反射API用于在运行时检测一个对象的元数据:实例的名字和类型,实现了哪些接口,属性的名字和类型,构造函数的参数名和类型等
//可用的元数据设计键有: design:type, design:paramtypes, design.returntype,分别是类型元数据,参数类型元数据,返回值类型元数据

import 'Reflect-metadata'; //该库需要额外安装
const formatMetadataKey = Symbol('format');
function fromat(formatString: string) {
    return Reflect.metadata(formatMetadataKey, formatString);
}

function getFormat(target: any, propertyKey: string) {
    return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

class Greeter {
    @format('hello, %s')
    greeting: string;

    constructor(message: string) {
        this.greeting = message;
    }
    greet() {
        let formatString = getFormat(this, 'greeting');
        return formatString.replace('%s', this.greeting);
    }
}

//或者
function logType(target : any, key : string) {
   var t = Reflect.getMetadata("design:type", target, key);
   console.log(`${key} type: ${t.name}`);
}
class Demo{ 
   @logType // apply property decorator
   public attr1 : string;
}
//输出:attr1 type: String
//参数装饰器(只能用来监视一个方法的参数是否被传入)

//参数装饰器应用在类的构造函数、方法声明中
//参数装饰器接受三个参数:
//1.对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
//2.成员的名字
//3.参数在函数参数列表中的索引
import "Reflect-metadata";
const requiredMetadataKey = Symbol('required');

function requried(target: Object, propertyKey: string | symbol, parameterIndex: number) {
    let existingRequiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) || [];
    existingRequiredParameters.push(parameterIndex);
    Reflect.defineMetadata(requiredMetadataKey, existingRequiredParameters, target, propertyKey);
}

function validate(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<Function>) {
    let method = descriptor.value;
    descriptor.value = function () {
        let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
        if (requiredParameters) {
            for (let parameterIndex of requiredParameters) {
                if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined) {
                    throw new Error("Missing required argument.");
                }
            }
        }

        return method.apply(this, arguments);
    }
}

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

    //该装饰器将greet方法包裹在一个函数里在调用原先的函数前先验证函数参数
    @Validate
    greet(@required name: string) {
        return `Hello ${name}, ${this.greeting}`;
    }
}

高级类型

//交叉类型 - Intersection Types
//把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{}; //定义一个交叉类型的对象
    for(let key in first) {
        (<any>result)[key] = (<any>first)[key]; //这里为什么要用any?
    }
    for(let key in second) {
        if(!result.hasOwnProperty(key)) {
            (<any>result)[key] = (<any>second)[key];
        }
    }
    return result;
}

class Person {
    constructor(public name: string) {} //此处的public不能省(构造函数中的参数属性)
}

interface Loggable {
    log(): void;
}

class ConsoleLogger implements Loggable {
    log() {
        console.log('consoleLogger');
    }
}

let person = extend(new Person('zhangjie'), new ConsoleLogger());

console.log(person);
console.log(person.name);
person.log();
//联合类型

//联合类型表示一个值可以是几种类型之一
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
//typeof 类型保护
function padLeft(value: string, padding: string | number) {
    //类型保护
    //1.类型必须是number, string, boolean, symbol其中之一,否则TS不会将其视为类型保护
    //2.类型保护只有两种形式:typeof v === 'typename' 或 typeof v !== 'typename'
    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}'.`);
}

//instanceof 类型保护
//将细化为instanceof右侧的构造函数的prototype的类型或者构造签名所犯回的联合类型
...
if (padder instanceof SpaceRepeatingPadder) {
    padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // 类型细化为'StringPadder'
}
...
//类型别名
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 Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
    animate(dx: number, dy: number, easing: Easing) {
        if(easing === 'ease-in') {
            console.log('ease-in');
        } else if(easing === 'ease-out') {
            console.log('ease-out');
        } else if(easing === 'ease-in-out') {
            console.log('ease-in-out');
        } else {
            //error
        }
    }
}

let button = new UIElement();
button.animate(0, 0, 'ease-in');
button.animate(0, 0, 'uneasy'); //error!

//字符串字面量类型还可以用于区分函数重载
function createElement(tagName: "img"): HTMLImageElement;
function createElement(tagName: "input"): HTMLInputElement;
// ... more overloads ...
function createElement(tagName: string): Element {
    // ... code goes here ...
}
//多态的this类型
class BasicCalculator {
    public constructor(protected value: number = 0) {}
    public currentValue(): number {
        return this.value;
    }
    public add(num: number): this {
        this.value += num;
        return this;
    }
    public mutiply(num: number): this {
        this.value *= num;
        return this;
    }
    //...
}

let test = new BasicCalculator(2)
                .mutiply(5)
                .add(1)
                .currentValue();

console.log(test);


//BasicCalculator返回了this类型,因此派生类在继承的同时,还能保持接口的连贯性,即返回的this可以多台的指向ScientificCalculator
class ScientificCalculator extends BasicCalculator {
    public constructor(value = 0) { //此处为什么不对value声明类型?是用了类型推断么?
        super(value);
    }
    public sin() {
        this.value = Math.sin(this.value);
        return this;
    }
    //...
}

let test2 = new ScientificCalculator(2)
                .mutiply(5)
                .sin() //不然此处mutiply()返回的没有sin()方法
                .add(1)
                .currentValue();

console.log(test2);
//索引类型

//常见的业务场景:从对象中选取属性的子集
function pluck(obj, names) {
    return names.map(item => obj[key]);
}


//感悟这种声明方式
//(索引类型查询操作符)keyof T的结果为T上已知的公共属性名的联合('name' | 'age')
//(索引访问操作符)T[K]是一种类型!
function pluck<T, K extends keyof T>(obj: T, names: K[]): T[K][] {
    return names.map(item => obj[item]);
}

interface Person {
    name: string;
    age: number;
}

let person: Person = {
    name: 'stevekeol',
    age: 28
}

let strings: string[] = pluck(person, ['name']);

console.log(strings);
//索引类型和字符串索引签名 ?????????

//eyof和 T[K]与字符串索引签名进行交互。 
//如果你有一个带有字符串索引签名的类型,那么 keyof T会是 string。 
//并且 T[string]为索引签名的类型
interface Map<T> {
    [key: string]: T;
}
let keys: keyof Map<number>; // string
let value: Map<number>['foo']; // number
//映射类型(新类型以相同的形式去转换旧类型中的每个属性),如可选/只读.
type Readonly<T> = {
    readonly [P in keyof T]: T[P]; 
}

type Partial<T> = {
    [P in keyof T]?: T[P];
}

type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;
type Keys = 'option1' | 'option2'; //字符串字面量联合的Keys,包含了要迭代的属性名的集合
type Flags = {
    [K in Keys]: boolean; //类型变量K,会依次绑定到每个属性上
}

//等同于

type Flags = {
    option1: boolean;
    option2: boolean;
}

//真正的应用中,(keyof和索引访问类型)常常会基于已存在的一些类型,按照一定的方式转换字段。
//(属性列表是keyof Person; 结果类型是Person[P])
//编辑器在添加任何新属性之前可以拷贝所有存在的属性修饰符(只读/可选)
type NullablePerson = { [P in keyof Person]: Person[P] | null };
type PartialPerson = { [P in keyof Person]?: Person[P] };

//进阶为通用版本
type NullablePerson<T> = { [P in keyof T]: T[P] | null };
type PartialPerson<T> = { [P in keyof T]?: T[P] };

//其它例子 ???????????
type Proxy<T> = {
    get(): T;
    set(value: T): void;
}

type Proxify<T> = {
    [P in keyof T]: Proxy<T[P]>;
}

function proxify<T>(obj: T): Proxify<T> {
    //... wrap proxies
}
let proxyProps = proxify(props);

//TS标准库(Readonly,Partial,Pick,Record...)
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
}

//?
type Record<K extends string, T> = {
    [P in K]: T;
}

//Readonly,Partial,Pick都是同态的(映射只作用于T的属性)
//Record不是同态的,因为它不需要输入类型来拷贝属性。
//非同态类型本质上会创建新的属性,因为它们不会从他处拷贝属性修饰符。

type ThreeStringProps = Record<'prop1' | 'prop2' | 'prop3', string>

//由映射类型进行推断
function unproxify<T>(t: Proxify<T>): T {
    let result = {} as T;
    for (const k in t) {
        result[k] = t[k].get();
    }
    return result;
}

let originalProps = unproxify(proxyProps);

相关文章

  • Typescript学习概要

    大致印象 TypeScript 增加了静态类型、类、模块、接口和类型注解. TypeScript 可用于开发大型的...

  • TypeScript 介绍与环境的搭建

    概要 TypeScript介绍 相关网址:TypeScript - JavaScript that scales....

  • Typescript

    TypeScript(TS)部分 TypeScript学习笔记

  • typescript学习

    typescript学习

  • TypeScript入门教程(一)

    学习网址:文档简介 · TypeScript中文网 一、Typescript介绍 1. TypeScript 是由...

  • typescript

    title: typescript学习tags: typescript学习 [toc] 泛型 基本使用 两种使用方...

  • TypeScript 基础

    以下为学习极客时间 《TypeScript 开发实战》的学习记录。 TypeScript 环境配置 安装 ts: ...

  • Typescript 学习笔记六:接口

    目录: Typescript 学习笔记一:介绍、安装、编译 Typescript 学习笔记二:数据类型 Types...

  • TypeScriptz学习笔记

    TypeScriptz学习笔记 标签(空格分隔): TypeScript 撩课学院 安装TypeScript Ty...

  • Typescript

    学习笔记 菜鸟教程 《菜鸟教程》-TypeScript简介 《菜鸟教程》-TypeScript安装 《菜鸟教程》-...

网友评论

      本文标题:Typescript学习概要

      本文链接:https://www.haomeiwen.com/subject/fpmcfktx.html