跳至主要內容

TypeScript 基础入门

Mr.Cao...大约 25 分钟

TypeScript 基础入门

一、什么是TypeScript

typeScriptopen in new window是javaScript的一个超集**(以下简称ts),javascript(以下简称js)**支持的语法js的语法ts基本全部都支持,因为js是弱类型语言,设计程序的时候容易出错,而ts本质上是为js添加了可选的静态类型和基于类的面向对象编程。

​ ts提供了js最新的语法比如说async、awite、Decorators等等,并且ts不断发展一直支持js的新特性,面向js next,下图展示ts和es各个版本之间的关系8图片来源于网络

2CLF1paczMrbUSm
2CLF1paczMrbUSm

1.1 ts和js对比

jsts
脚本语言,主要用于web前端开发,nodejs可用于服务端开发js超集,功能和js一样,但有利于程序稳定
开发成本低,代码bug率高开发成本高,一旦开发完毕程序健壮
作为一种解释型语言,只能在运行时发现错误可以在编译期间发现并纠正错误
弱类型,没有静态类型选项强类型,支持静态和动态类型
浏览器能直接识别最终还得通过编译器编译成js代码
不支持模块泛型接口支持模块泛型接口
社区支持以及大量文档和解决问题的支持社区的支持仍在增长,有很大上升空间

由此可见新项目直接上ts,很大程度上决定了公司web端水平

1.2 安装和运行

1、安装

直接运行npm全局安装

npm install typescript -g

2、编译

tsc hello.ts 
# helloworld.ts => helloworld.js

但是有些时候学习的时候却不必要安装typescrt有在线文档编辑open in new window器足够学习用了

3、配置tsconf.json文件

如果一个目录下存在一个tsconfig.json文件,那么它意味着这个目录是TypeScript项目的根目录。 tsconfig.json文件中指定了用来编译这个项目的根文件和编译选项。tsconfigopen in new window配置方法讲解。

二、ts基础类型

2.1布尔类型

let isShow:boolean=true

2.2 Number类型

let num:number=1

2.3字符串类型

let str:string='this is a string'

2.4 array类型

let arr:number[]=[1,2,3,4]
let list: Array<number> = [1, 2, 3]; // Array<number>泛型语法

2.5 Enum 类型

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举

特性

/**
 * Enum 类型
 * 特性
 * A-数字枚举
 *  1、默认情况下,NORTH 的初始值为 0,其余的成员会从 1 开始自动增长。换句话说,Direction.SOUTH 的值为 1,
 *  Direction.EAST 的值为 2,Direction.WEST 的值为 3
 *  2、可以指定一个的值,这个值后边的自增
 * B-字符串枚举
 *  1、每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化
 * C-异构枚举 ->字符串和数字的组合
 *  1、
 */
// 数字枚举
enum DirectionNumber {
  NORTH,
  SOUTH,
  EAST = 100,
  WEST,
}
let numberNorth: DirectionNumber = DirectionNumber.WEST// 101
// 上边编译后的代码如下
(function (DirectionNumber) {
    DirectionNumber[DirectionNumber["NORTH"] = 0] = "NORTH";
    DirectionNumber[DirectionNumber["SOUTH"] = 1] = "SOUTH";
    DirectionNumber[DirectionNumber["EAST"] = 100] = "EAST";
    DirectionNumber[DirectionNumber["WEST"] = 101] = "WEST";
})(DirectionNumber || (DirectionNumber = {}));
// 字符串枚举
enum DirectionString {
  NORTH = 'NORTH',
  SOUTH = 'SOUTH',
  EAST = 'EAST',
  WEST = 'WEST',
}
let stringSouth = DirectionString.SOUTH // SOUTH
// 编译后的代码如下
// 字符串枚举
(function (DirectionString) {
    DirectionString["NORTH"] = "NORTH";
    DirectionString["SOUTH"] = "SOUTH";
    DirectionString["EAST"] = "EAST";
    DirectionString["WEST"] = "WEST";
})(DirectionString || (DirectionString = {}));

// 异构枚举
enum Enum {
  A,
  B,
  C = 'c',
  D = 'd',
  E = 8,
  F,
}
let eEnum: Enum = Enum.F // 9
//编译之后
(function (Enum) {
    Enum[Enum["A"] = 0] = "A";
    Enum[Enum["B"] = 1] = "B";
    Enum["C"] = "c";
    Enum["D"] = "d";
    Enum[Enum["E"] = 8] = "E";
    Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));

2.6 Any类型

  • 顶级类型可以随便修改不同类型的值,最好不用用了和其他的没什么区别
  • any 类型本质上是类型系统的一个逃逸舱
  • TypeScript 允许我们对 any 类型的值执行任何操作
  • Any 类型什么操作都是符合要求的但是这个东西又是危险的用了ts跟没有用差不多
  • 所以很多公司一般情况下禁止定义Any类型的变量
    代码如下
let notSure: any = 111
notSure = '111'
notSure = true
notSure = [1, 23]
notSure = ' 111'
// any类型可执行任何操作
notSure.trim()
notSure.foo.bar // OK
notSure.trim() // OK
notSure() // OK
new notSure() // OK
notSure[0][1] // OK

2.7 Unknown

  • 所有类型也都可以赋值给 unknown,unknown也是ts的顶级类型
  • 只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值
  • unknown类型的数据不能执行任何方法,因为我们也不知道他到底存了什么值
  • unknown类型的值只能赋给unknown和any类型
let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK
// unknown类型不能执行任何方法
let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

2.8 Tuple类型

  • 定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值
  • js中本身没有元组,是ts中特有的类型,工作方式和数组一样
  • 元组的属性和值是一一匹配的不能多也不能少,也不能不匹配
  • 如果定义的和赋值的类型不匹配和长度不匹配也会报错
let tupleType: [string, boolean];
tupleType = ["Semlinker", true];
// 如果初始化时候出现类型不匹配,编译阶段报错
tupleType = [true, "Semlinker"];// error
// 初始化 的时候必须提过每个属性的值,否则也会报错
tupleType = ["Semlinker"];error

2.9 Void 类型

  • void 类型就像是与 any 类型相反,表示没有任何类型
  • 常见用于一个函数没有返回值
  • 声明一个void的变量没有用处,因为他的值只能是null或者undefined
function sayHello(): void {
  console.log('hello world')
}
sayHello()
// let voidValue2: void = 11 //Error
let voidValue: void = null
let voidValue1: void = undefined

2.10 Null 和 Undefined 类型

  • undefined 和 null 两者有各自的类型分别为 undefined 和 null
  • 默认情况下 null是所有类型的子类型。
  • 就是说可以把 null赋值给 任何 类型变量。
  • 如果指定了--strictNullChecks 标记,null 和 undefined 只能赋值给 void 和它们各自的类型。
let u: undefined = undefined
let n: null = null
let uNumber: number
let nString: string
uNumber = n
nString = n

2.11 Never 类型

  • never 类型表示的是那些永不存在的值的类型。
  • 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型
function error(message: string): never {
  throw new Error(message)
}
function loop(): never {
  while (true) {}
}
// 可以利用 never 类型的特性来实现全面性检查
type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
  if (typeof foo === "string") {
    // 这里 foo 被收窄为 string 类型
  } else if (typeof foo === "number") {
    // 这里 foo 被收窄为 number 类型
  } else {
    // foo 在这里是 never
    const check: never = foo;
  }
}

三、ts断言

有时候我们自己写的程序自己肯定知道干嘛,所以我们就需要告诉ts我这个变量是什么类型的这就是断言
断言有两种写法
一种是尖括号语法,一个是as语法
代码如下

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
strLength:number=(someValue as string).length

四、类型守卫

  • 类型保护实际上就是js中对某个变量做操作时候先确认类型在操作
  • 类型检测就是执行程序运行时的一种表达式
  • 用于确保该值在一定返回之内
  • 主要用来检测属性,方法和原型
  • 目前主要有四种的方式来实现类型保护

4.1 in 关键字

// 主要用来检测属性是否存在
interface Admin { 
    name: string,
    privileges:string[]
}

interface Employee { 
    name: string,
    startDate:Date
}
type UnkonownEmployee = Admin | Employee

function printEmployeeInformation(emp:UnkonownEmployee) { 
    console.log('name', emp.name)
    if ('privileges' in emp) { 
        console.log(emp.name,'is privileges',emp.privileges)
    }
    if ('startDate' in emp) { 
        console.log(emp.name,emp.startDate,'开始干活')
    }
}
let employee: Employee = {
    name: 'caotudou',
    startDate: new Date()
}

4.2 typeof 关键字

  • typeof 类型保护只支持两两种形式 === 和 !==
  • 只能用来检测string、number、boolean和symbol四种类型
// 主要用来检测变量是否为指定的目标值
function padLeft(value: string, padding: string | number):string { 
    if (typeof padding === "number") { 
        return Array(padding+1).join("*")+value
    }
    if (typeof padding === 'string') { 
        return padding +value
    }
}

4.3 instanceof

  • 跟js原生方法一致为了检测属性是不是属于该对象
interface Padder {
    getPaddingString(): string
}
class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces).join('#')
    }
}
class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value
    }
}
let padder: Padder = new SpaceRepeatingPadder(11)
if (padder instanceof SpaceRepeatingPadder) {
    console.log(padder.getPaddingString())
}

4.4 自定义类型保护

// 自定义类型保护的类型谓词

function isNumber(x: any):x is number { 
    return typeof x==='number'
}
function isString(s: any): s is string { 
    return typeof s==='string'
}

五、联合类型和类型别名

5.1 联合类型

  • 联合类型就是给一变量定义多个类型,通常跟undefined或null一起使用
  • 写法是用|连接两个类型
function sayHello(name: string | undefined): void { 
    console.log(name)
}

5.2 可辨识联合类型

  • 本质就是一个联合类型有多个类型中且多个类型有公共属性
  • 它包含 3 个要点:可辨识、联合类型和类型守卫。
// 可辨识- >多个类型中都有一个相同类型
enum CarTransmission {
    Automatic = 200,
    Manual = 300
}
interface Motorcycle { 
    vType: 'motorcycle',
    make:number
}
interface Car { 
    vType: 'car',
    transmission:CarTransmission
}
interface Truck { 
    vType: 'truck',
    capacity:number
}
// 上述定义的三个接口中都有一个vType的属性,这就是可辨识
// 联合类型
type Vehicle = Motorcycle | Car | Truck
// Vehicle就可以表示不同的车辆 形成一个联合类型
// 这时候要用这个Vehicle这个类型的时候就需要在方法里边做判断,就是类型守卫
// 一下这样写是错误的
const EVALUATION_FACTOR = Math.PI; 
/*
	function evaluatePrice(vehicle: Vehicle) {
  		return vehicle.capacity * EVALUATION_FACTOR;
	}
*/
// 要这样写
interface VtypeObj {
    'car': Function,
    'motorcycle': Function,
    'truck': Function
}
const vTypeObj: VtypeObj = {
    car() {
        return this.transmission * EVALUATION_FACTOR
    },
    motorcycle() {
        return this.make * EVALUATION_FACTOR
    },
    truck() {
        return this.capacity * EVALUATION_FACTOR
    }
}
function evaluatePrice(vehicle: Vehicle) {
    console.log(vTypeObj[vehicle.vType].call(vehicle))
}
const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);

上诉代码中用了对象巧妙的判断了只有有这个属性的时候才进行操作,实现了类型守卫

5.3 类型别名

类型别名用来给一个类型起个新名字。

type Message= string
let greet = (message: Message) => {
  // ...
};

交叉类型

  • 所谓交叉类型其实就是将多个类型合并为一个类型,它包含了合并之前的类型的所有类型
  • 交叉完的类型定义必须含有所有类型的成员
  • 交叉类型用& 连接
interface IsPerson { 
    id: string,
    age:number
}
interface IsWorker { 
    companyId:string
}
type IStaff = IsPerson & IsWorker

const staff: IStaff = {
  id: 'E1006',
  age: 33,
  companyId: 'EFT'
};

七 ts 函数

ts函数与js函数区别

ts函数其实与函数没有任何区别,但是ts函数比js函数多了一个函数重载

tsjs
含有类型无类型
箭头函数箭头函数
函数类型无函数类型
必填和可选参数所有参数都是可选的
默认参数默认参数
剩余参数剩余参数
函数重载无函数重载

7.2 箭头函数

箭头函数与js箭头函数没有任何区别,不再过多叙述

7.3 参数类型和返回类型

function createUserId(name: string, id: number): string {
  return name + id;
}

7.4 函数类型

let IdGenerator: (chars: string, nums: number) => string;

function createUserId(name: string, id: number): string {
  return name + id;
}

IdGenerator = createUserId;

7.5 可选参数及默认参数

// 可选参数
function createUserId(name: string, age?: number, id: number): string {
  return name + id;
}

// 默认参数
function createUserId(
  name: string = "Semlinker",
  age?: number,
  id: number
): string {
  return name + id;
}

7.6 剩余参数

function push(array, ...items) {
  items.forEach(function (item) {
    array.push(item);
  });
}

let a = [];
push(a, 1, 2, 3);

7.7 函数重载

函数重载open in new window百度上很详细,函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。要解决前面遇到的问题,方法就是为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用。但是js不支持函数重载,后边定义会覆盖前边的定义。ts中是支持函数重载的。

1 函数重载

// 函数重载
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) { 
    if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}
console.log(add(1,2))
  • 如上述代码,创建了5个add函数,每个函数接受的参数类型和返回类型都不相同,这时候程序就会根据我们传入的参数类型选择性的去执行匹配的函数
  • 当传入add(1,2)时候返回3
  • 当传入add(1,'2')时候返回12

2 方法重载

方法重载是指在同一个类中方法同名,参数不同(参数类型不同、参数个数不同或参数个数相同时参数的先后顺序不同),调用时根据实参的形式,选择与它匹配的方法执行操作的一种技术。所以类中成员方法满足重载的条件是:在同一个类中,方法名相同且参数列表不同

class Calculator {
    add(a: number, b: number): number;
    add(a: string, b: string): string;
    add(a: string, b: number): string;
    add(a: number, b: string): string;
    add(a: Combinable, b: Combinable) {
        if (typeof a === "string" || typeof b === "string") {
            return a.toString() + b.toString();
        }
        return a + b;
    }
}
const calculator = new Calculator();
const result = calculator.add("Semlinker", " Kakuqo");

ts编译器处理函数重载时候,先去查找重载列表,使用第一个重载定义。 如果匹配的话就使用这个,所以最精确的要放在最上边定义
在 Calculator 类中,add(a: Combinable, b: Combinable){ } 并不是重载列表的一部分,因此对于 add 成员方法来说,我们只定义了四个重载方法

八、TypeScript 数组

数组这一块和js一样,不多介绍,只需要知道定义数组两种方法

let colorArr:string[]=['red','black']
let colorArr:Array<string>=['red','black']

简单的数组的方法,具体方法参见数组方法open in new window

// 数组解构
let x: number; let y: number ;let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;
// 数组展开运算符
let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];
// 数组遍历
const color_arr:string[]=['red','green','black']
color_arr.forEach((item, index, arr) => { 
    console.log(item,index,arr)
})

九、TypeScript 对象

对象这一块和js一样,不多介绍参考mdn-对象open in new window

// 解构
let person = {
  name: "Semlinker",
  gender: "Male",
};

let { name, gender } = person;
// 对象展开运算符
let personWithAge = { ...person, age: 33 };
// 剩余运算
let {name,...rest}=personWithAge
console.log(rest)// {gender: "Male", age: 33}

十、TypeScript 接口

  • 主要用于描述对象
  • 接口定义 interface 关键字
  • 只读属性(readonly)和可选属性(?)
  • 如果属性是数组,也有只读数组(ReadonlyArray)
  interface Person {
        readonly name: string,
        age?: number,
        firends: ReadonlyArray<string>
    }
    let person: Person = {
        name: 'cao',
        age: 12,
        firends: ['x', 'y']
    }
    // person.name = 'zhang' //error
    // person.firends[1]='z' //error

十一、TypeScript 类

ts的类和js类基本上一致 通过class关键字创建,也有静态方法、静态属性、成员方法和成员属性 都需要通过new 关键字实例化一个对象,this指向也与js一致
不同之处在于每个属性需要加上类型区分

11.1 类的属性与方法

    class Person {
        // 静态属性
        static sayClass: string = 'hello Person'
        // 成员属性
        sname: string;
        // 构造函数
        constructor(name: string) {
            this.sname = name
        }
        // 静态方法
        static getClassName() {
            return "Class name is Person";
        }
        // 成员方法
        sayHello() {
            return "Hello, " + this.sname;
        }
    }
    let person = new Person("world");
    console.log(Person.getClassName())
    console.log(person.sayHello())
    console.log(Person.sayClass)

本着学习的原则,顺便提一下静态属性静态方法和成员属性成员方法的区别,最大的区别一个是原型上的属性,一个是类本身的方法,因为js的类实际上是一个语法糖,本质是个函数对象我们可以看一下babel生成的es5的代码,如下

    var Person = /** @class */ (function () {
        // 构造函数
        function Person(name) {
            this.sname = name;
        }
        // 静态方法
        Person.getClassName = function () {
            return "Class name is Person";
        };
        // 成员方法
        Person.prototype.sayHello = function () {
            return "Hello, " + this.sname;
        };
        // 静态属性
        Person.sayClass = 'hello Person';
        return Person;
    }());

11.2 访问器

在js或者ts中我们可以用gettersetter,来实现对数据的统一管理,入口和出口都管理防止出现坏数据

let name='caotodou'
    class Person { 
        private _age: number
        get age() { 
            return this._age
        }
        set age(age: number) {
            if (name === 'caotodou') { 
                this._age=age
            }
         }

    }
    let person = new Person()
    person.age = 100
    console.log(person.age)

11.3 类的继承

继承 (Inheritance) 是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。ts继承和js一样都是通过extends实现继承

class Animal {
  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

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

let sam = new Snake("Sammy the Python");
sam.move();

11.4 ECMAScript 私有字段

在 TypeScript 3.8 版本就开始支持ECMAScript 私有字段,使用方式如下:

class Person {
  #name: string;

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

  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}

let semlinker = new Person("Semlinker");

semlinker.#name; //error
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

与常规属性(甚至使用 private 修饰符声明的属性)不同,私有字段要牢记以下规则:

  • 私有字段以 # 字符开头,有时我们称之为私有名称;
  • 每个私有字段名称都唯一地限定于其包含的类;
  • 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
  • 私有字段不能在包含的类之外访问,甚至不能被检测到。

十二、TypeScript 泛型

软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性。 组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。
在像 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以以自己的数据类型来使用组件。
泛型(Generics)是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型。

12.1 泛型接口

interface GenericIdentityFn<T> {
  (arg: T): T;
}

12.2 泛型类

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;
}

12.3 泛型变量

在ts世界中,看到T和E等这类大写字母,不要害怕,没什么本质区别,只是一个约定,这些字母表示的变量类型都是一个泛型,但是我们有约定的泛型变量:

  • T(Type):表示一个TypeScript 类型
  • K(Key):表示对象中的键类型
  • V(Value):表示对象中的值类型
  • E(Element):表示元素类型

12.4 泛型工具类型

为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType ,这里只介绍Partial工具类型,我们先要了解一些基础知识才能学习工具类型

1. typeof

在 ts 中,typeof 操作符可以用来获取一个变量声明或对象的类型。

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

const sem: Person = { name: 'semlinker', age: 30 };
type Sem= typeof sem; // -> Person

function toArray(x: number): Array<number> {
  return [x];
}

type Func = typeof toArray; // -> (x: number) => number[]

2. keyof

keyof操作符可以用来一个对象中的所有 key 值:

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

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
type K3 = keyof { [x: string]: Person };  // string | number

3.inopen in new window

in用来遍历枚举类型

type Keys = "a" | "b" | "c"
type Obj =  {
  [p in Keys]: any
} 

4.infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用

type RetrunType<T>=T extends (
	...args:any[]
)=>infer R ? R :any

以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用

5.extends

有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。

interface ILengthwise {
  length: number;
}

function loggingIdentity<T extends ILengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
// 现在这个泛型函数被定义了约束,因此它不再是适用于任意类型:
loggingIdentity(3);  // Error, number doesn't have a .length property
loggingIdentity({length: 10, value: 3});

6. Partial

Partial<T>的作用就是将某个类型里的属性全部变为可选项 ?
定义

/**
 * node_modules/typescript/lib/lib.es5.d.ts
 * Make all properties in T optional
 */
type Partial<T> = {
  [P in keyof T]?: T[P];
};

在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。

interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return { ...todo, ...fieldsToUpdate };
}

const todo1 = {
  title: "organize desk",
  description: "clear clutter",
};

const todo2 = updateTodo(todo1, {
  description: "throw out trash",
});

在上面的 updateTodo 方法中,我们利用 Partial<T> 工具类型,定义 fieldsToUpdate 的类型为 Partial<Todo>,即:

{
   title?: string | undefined;
   description?: string | undefined;
}

十三、TypeScript 装饰器

13.1 什么是装饰器

装饰器在面向对象oop编程中显得尤为重要,python是在语法层面就支持装饰器的,而js的装饰器本质上是语法糖,是通过对象等方法写成的
装饰器open in new window本质上是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象,比方说在函数执行时候想打印一个日志,但是又不想影响其调用方式代码结构,就可以用装饰器解决

13.2 装饰器分类

  • 类装饰器(Class decorators)
  • 属性装饰器(Property decorators)
  • 方法装饰器(Method decorators)
  • 参数装饰器(Parameter decorators)

13.3 类装饰器

声明

declare type ClassDecorator = <TFunction extends Function>(
  target: TFunction
) => TFunction | void;

是不是一脸懵逼,看了例子就不会这样了

function Greeter(target: Function):void{ 
	target.prototype.greet = function () { 
    	console.log("我是类装饰器")
	}
}
@Greeter
class Greeting {}
let myGreeting = new Greeting()
myGreeting.greet()

如果要是有参数怎么办?
那我们就需要改一下啦,用闭包、让装饰器函数返回一个函数,装饰的时候先调用一次装饰器即可

// 携带参数的装饰器
function Greeter(greeting:string){
	return function(target:Function){
		target.prototype.greet=function():void{
			console.log(greeting)
		}
	}
}
//使用
@Greeter("hello Greeter")
class Greeting {}
let myGreeting = new Greeting()
myGreeting.greet()

13.4 属性装饰器

属性装饰器顾名思义是用来装饰类的属性,接受两个参数

  • target:Object -被装饰得类
  • propertyKey: string | symbol -被装饰类的属性名
    声明方法
declare type PropertyDecorator=(target:Object,propertyKey:string|symbol)=>void

举例子

// 跟踪某个对象的属性被改变
// 属性装饰器
    function logProperty(target: any, key: string) {
        console.log(target)
        delete target[key]
        const backingField = "_" + key
        // 给对象增加backingField属性
        Object.defineProperty(target, backingField, {
            enumerable: true,
            writable: true,
            configurable: true
        })
        // 定义读写方法
        const getter = function (this: any) {
            const currVlaue = this[backingField]
            console.log(`Get: ${key} => ${currVlaue}`);
            return currVlaue
        }
        const setter = function (this: any, newVal: any) {
            console.log(`Set: ${key} => ${newVal}`);
            this[backingField] = newVal;
        }
        Object.defineProperty(target, key, {
            enumerable: true,
            configurable: true,
            writable: true,
            get: getter,
            set: setter
        })
    }
    class Person {

        @logProperty
        public name: string;

        constructor(name: string) {
            this.name = name;
        }
    }
    let person = new Person("cat")
    // 这样每次变化name的值,都会打印一下

13.5 方法装饰器

方法装饰器就是用来装饰类的方法,接受三个参数

  • target : Object -被装饰的类
  • propertyKey :string | symbol -方法名
  • descriptor : TypePropertyDescript - 属性描述符
    声明
declare type MethodDecorator = <T>(target:Object,propertyKey:string | symnol,descriptor:TypePropertyDescript<T>)=>TypePropertyDescript<T>|void
// 例子
function LogOutput(tarage: Function, key: string, descriptor: any) {
  let originalMethod = descriptor.value;
  let newMethod = function(...args: any[]): any {
    let result: any = originalMethod.apply(this, args);
    if(!this.loggedOutput) {
      this.loggedOutput = new Array<any>();
    }
    this.loggedOutput.push({
      method: key,
      parameters: args,
      output: result,
      timestamp: new Date()
    });
    return result;
  };
  descriptor.value = newMethod;
}

class Calculator {
  @LogOutput
  double (num: number): number {
    return num * 2;
  }
}

let calc = new Calculator();
calc.double(11);
// console ouput: [{method: "double", output: 22, ...}]
console.log(calc.loggedOutput); 

13.6 参数装饰器

参数装饰器顾名思义,是用来装饰函数参数,它接收三个参数:

  • target: Object - 被装饰的类
  • propertyKey: string | symbol - 方法名
  • parameterIndex: number - 方法中参数的索引值
    参数装饰器声明:
declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, 
  parameterIndex: number ) => void
// 例子
function Log(target: Function, key: string, parameterIndex: number) {
  let functionLogged = key || target.prototype.constructor.name;
  console.log(`The parameter in position ${parameterIndex} at ${functionLogged} has
 been decorated`);
}

class Greeter {
  greeting: string;
  constructor(@Log phrase: string) {
 this.greeting = phrase; 
  }
}

十四、编译上下文

14.1 tsconfig.json 的作用

  • 用于标识 TypeScript 项目的根路径;
  • 用于配置 TypeScript 编译器;
  • 用于指定编译的文件。

14.2 tsconfig.json 重要字段

  • files - 设置要编译的文件的名称;
  • include - 设置需要进行编译的文件,支持路径模式匹配;
  • exclude - 设置无需进行编译的文件,支持路径模式匹配;
  • compilerOptions - 设置与编译流程相关的选项。

14.3 compilerOptions 选项

compilerOptions 支持很多选项,常见的有 baseUrl、 target、baseUrl、 moduleResolution 和 lib 等。
compilerOptions 每个选项的详细说明如下:

{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}

十五、参考文章

大量参考该公众号一篇文章,有些demo和描述是也是复制的,如有侵权,请联系我

原文地址:了不起的 TypeScript 入门教程open in new window
aX4rmTiFxq6kPyh

上次编辑于:
贡献者: Caofangshuai
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8