前端技术

一文看完 Typescript

2021-02-25  本文已影响0人  单炒饭

Typescript

基础类型

// 布尔 boolean
let flag:boolean = true;

// 数字类型 number
let num:number = 100;

// 字符串类型 string
let str:string = "Hello";

// 数组类型 array
let arr:number[] = [1,2,3,4,5];
let arr:Array<number> = [1,2,3,4,5];

// 元组类型 tuple(数组的增强可指定多种类型)
let arr:[string,number] = [1,"a"];

// 枚举类型 enum(用于标识状态和固定值)
enum Flag{
    success=1,
    error=-1
};
let f:Flag = Flag.success;

enum Color {red=2,blue,orange};     // 如果不定义值,则是其索引值
let c:Color = Color.blue;           // 3,上一个值有定义则在此基础上+1

// 任意类型 any(跟js原本的变量无区别)
let str:any = "Hello";
let num:any = 123;

// null 和 undefined
let num:number | undefined;
num = 123;

// void 类型(无任何值类型,常用于方法无返回值)
function fun():void{
}

// never 从来不会出现的类型(通常不建议使用)
let num:never;
num = (()=>{
    throw new Error("错误");
})();

联合类型

即允许多个类型同时存在

let val:number|string;
val = 123;
val = "Hello";

交叉类型

使用 & 符号将多个类型叠加到一起成为的一种新的类型,基包含了多个类型的所有特性。

interface Student {
    id: string;
    age: number;
}
interface Worker {
    companyId: string;
}

type A = Student & Worker;      // A 此时包含了 Student 和 Worker 特性
let x: A;
x.age = 5;                      // 合法
x.companyId = 'CID5241';        // 合法
x.id = 'ID3241';                // 合法


// 合并对象演示
function extend<T, U>(first: T, second: U): T & U {
    let result = {} as T & U    // result 是要返回结果,类型断言为 T&U
    for(let id in first){       // 不能将类型 T 分配给类型 T&U,故需使用断言
        result[id] = first[id] as any
    }
    for(let id in second){
        result[id] = second[id] as any
    }
    return result;              // 返回结果,类型是 T & U
}
class A{
    constructor(public name: string){}
}
class B{
    constructor(public id:number,public age:number){}
}

let c = extend(new A('Hello'), new B(100,12));
c.name;                         // 'Hello'
c.id;                           // 100
c.age;                          // 12

断言

断言不是类型转换,通过断言告诉编译器『我完全知道自己在干什么』

// 尖括号语法
let val: any = "this is a string";
let len: number = (<string>val).length;

// as语法
let val: any = "this is a string";
let len: number = (val as string).length;

显示赋值断言

显式赋值断言是 TS2.7 引入的一个新语法。明确告诉编译器:该属性或变量已经赋过值了。

// 用在类属性上
class C {
    foo!: number;
    constructor() {
        this.initialize();
    }
    initialize() {
        this.foo = 0;
    }
}

// 用在变量上
let x!: number[];
initialize();
x.push(4);

function initialize() {
    x = [0, 1, 2, 3];
}

函数

// 参数和返回值类型都要指定
function run(a:number, b:number):string{
    return a+b.toString();
}

// 可选参数
function run(a:number, b?:number):string{
    let val = b ? b : a;
    return val.toString();
}

// 默认参数
function run(a:number=100):string{
    return a.toString();
}

// 剩余参数(扩展运算符来接收参数)
function run(...arg:number[]):number{
    let result = 0;
    for(let i:number=0; i<arg.length; i++){
        result += arg[i];
    };
    return result;
}

// 函数重载(即多个同名函数,但参数不一样。即会出现函数重载)
function run(a:string):string;
function run(a:number):string;
function run(a:string|number){
    return typeof a === 'string' ? "字符" : "数字";
};
run("Hello");   // 字符
run(100);       // 数字

/*
类的三个修饰符
---
public      公有    在类、子类、类外都可访问(默认)
protected   保护    在类、子类可访问
priveate    私有    在类可访问,子类、类外都不可访问
*/

// 类的基本结构
class Person{
    name:string;                // 属性,前面省略了 public 关键词
    constructor(a:string){      // 构造函数

    }
    run():void{                 // 类的方法,前面省略了 public 关键词
        console.log(this.name);
    }
    static run():void{          // 静态方法,通过 Person.run 调用

    }
}

// 类的继承
class Person1 extends Person {
    constructor(a:string){
        super(a);               // 调用父类的构造函数
    }
    work():void{                // 子类自己的方法

    }
}

// 多态,即父类定义一个方法不去实现,让继承它的子类去实现,每个子类有不同的表现
class Animal{
    name:string;
    constructor(a:string){
    }
    eat():void{                 // 定义一个方法让子类去实现

    }
}
class Dog extends Animal{
    constructor(a:string){
        super(a);               // 调用父类的构造函数
    }
    eat():void{                 // 子类自己实现的方法
        console.log("吃骨头");
    }
}

// 抽象类,即只供其它类继承使用基类,本身不能被实例化。使用 abstract 关键字定义抽象类和抽象方法
abstract class Animal{
    name:string;
    constructor(a:string){
        this.name = a;
    }
    abstract eat():void;        // 定义一个方法让子类去实现,abstract 方法只可在 abstract 类中
}
class Dog extends Animal{
    constructor(a:string){
        super(a);               // 调用父类的构造函数
    }
    eat():void{                 // 子类中必须实现父类中定义的抽象方法
        console.log("吃骨头");
    }
}

存取器

class A {
    constructor(){}
    private _name:string | undefined;
    set name(str:string) {
        this._name = str;
    }
    get name():string{
        return `My name is ${this._name}`;
    }
}

let a = new A();
a.name = "Andy";
a.name;                         // "My name is Andy"

三斜线指令

仅能放在文件的最顶端才可生效。

/// <reference path="..." />
// 常用于引入 `.d.ts`文件。用于告诉编译器在编译时要引入的额外文件

/// <reference types="node" />
// 引入到声明文件,表明这个文件使用了 @types/node/index.d.ts 里面声明的名字

/// <amd-module name="moduleName" />
// 用于指定 AMD 模块的名称(否则默认为匿名)

/// <reference no-default-lib="true"/>
// 标记为默认库,lib.d.ts 文件就有此项

/// <reference lib="es2017.string" />
// 等效于使用 -lib es2017.string 进行编译

接口

规范的定义,用于起到限制的作用。接口不关心内部状态数据,也不关心实现细节。只是限制必须提供某些方法。类于 java,同时增加了接口类型、函数、可索引、类等。

// 属性接口
interface FullName {
    firstName:string;
    secondName?:string; // 可选属性,该参数可传可不传
}
function fun(name:FullName):void{
    console.log(name.firstName);
}
let obj = {             // 传入的参数必须包含 firstName、secondName是可选的
    firstName:"张",
    secondName:"三"
};
fun(obj);

// 函数类型接口(对方法传入的参数以及返回值进行约束)
interface encrypt {
    (key:string, value:string):string;
}
let md5:encrypt = function(key:string, value:string):string{
    return key+value;
}
md5("a","b");

// 可索引接口(约束数组、对象)
interface UserArr{              // 约束数组
    [index:number]:string;
}
let arr:UserArr=['a','b'];

interface UserObj{              // 约束对象
    [index:string]:string
}
let obj:UserObj={name:'a'}

// 类的类型接口(对类的约束,和抽象类相似)
interface Animal{               // 约束类
    name:string;
    eat(str:string):void;
}
class Dog implements Animal {   // 实现接口
    name:string;
    constructor(name:string){
        this.name = name;
    }
    eat(str:string):void{
    }
}

// 接口扩展(接口继承接口)
interface Animal{
    eat():void;
}
interface Person extends Animal {       // 继承接口
    work():void;
}
class Web implements Person{            // 实现接口
    name:string;
    constructor(name:string){
        this.name = name;
    }
    eat():void{

    }
    work():void{

    }
}

泛型

解决类、接口方法的复制用,并对不特定数据类型支持。

// T即是泛型,具体什么类型是调用这个方法的时候决定的 
function fun<T>(value:T):T{         // 定义了传入与返回的类型一至
    return value;
}
fun<number>(123);                   // 传入的是数字,返回也必须是数字

// 泛型类
class Min<T>{
    list:T[]=[];
    add(value:T):void{
        this.list.push(value);
    }
    min():T{
        let minVal = this.list[0];
        for(let i:number=0; i<this.list.length; i++){
            if(minVal > this.list[i]){
                minVal=this.list[i];
            };
        };
        return minVal;
    }
}
let m1 = new Min<number>();         // 实例化时才决定了 T 的类型为数字
m1.add(1);
m1.add(2);
m1.min();                           // 1

let m2 = new Min<string>();         // 实例化时才决定了 T 的类型为字符
m2.add("a");
m2.add("b");
m2.min();                           // "a"

// 泛型接口(方式一)
interface Config{                   // 定义的泛型接口
    <T>(value:T):T;
}
let fun:Config = function<T>(value:T):T {   // 方法实现跟接口一致
    return value;
}
fun<string>("a");                   // "a",使用时才决定了其类型
fun<number>(1);                     // 1,使用时才决定了其类型

// 泛型接口(方式二)
interface Config<T>{                // 定义泛型接口
    (value:T):T;
}
function fun<T>(value:T):T{         // 方法实现跟接口一致
    return value;
}
let f:Config<string>=fun;           // 决定了其类型
fun("a");                           // "a"

混入 Mixins

类继承之外的一种通过可重用组件创建类的方式。通过把类当成接口,又不实现其定义的方法并具有混入类的属性及方法。

class Mixin1{                       // 混入类1
    constructor(public name:string){}
    log1():void{
        console.log("方法一",this.name);
    }
}

class Mixin2{                       // 混入类2
    constructor(public age:number){}
    log2():void{
        console.log("方法二",this.age);
    }
}

class A implements Mixin1,Mixin2 {  // 把 Mixin1、Mixin2 当成接口
    name!:string;                   // 定义占位,因为要实现接口中的属性
    age!:number;                    // 定义占位,因为要实现接口中的属性
    log1!:()=>void;                 // 定义占位,因为要实现接口中的方法
    log2!:()=>void;                 // 定义占位,因为要实现接口中的方法
    constructor(name:string, age:number){
        this.name = name;
        this.age = age;
    }
    log():void{
        console.log("自身的方法");
    }
}

// 混入方法,遍历 Mixins 的所有方法、属性,并复制到定义的占位目标上去
function applyMixins(base:any, mixins:any[]){
    mixins.forEach(mixin => {
        Object.getOwnPropertyNames(mixin.prototype).forEach(name => {
            base.prototype[name] = mixin.prototype[name];
        });
    });
}
// 应用混入
applyMixins(A,[Mixin1,Mixin2]);

let a = new A("Andy",12);
a.log();
a.log1();
a.log2();

文件模块

// export 可多次调用
// module.ts
export function module1():any[]{
    return ["a"]
};
export function module2():string{
    return "a";
};
// 或
function module1():any[]{
    return ["a"]
};
function module2():string{
    return "a";
};
export {module1, module2};          // 统一暴露

// main.ts
import {module1, module2 as m2} form "./module";
module1();                          // ["a"]
m2();                               // "a"

// export.default 每个文件内只能用一次
// module.ts
export.default function():void{
}

// main.ts
import module form "./module";
module();

命名空间

与 Java、c# 基本一致

// 命名空间下的接口、类默认均为私有
namespace A{
    interface Animal {
        name:string;
        eat():void;
    }
    // 使用 export 暴露出去才可在外部调用
    export class Dog implements Animal{
        name:string;
        constructor(name:string){
            this.name = name;
        }
        eat(){
        }
    }
}
let d = new A.Dog("2ha");
d.eat();

// 命名空间也可以使用 export 暴露出去被外部调用
export namespace A{
    interface Animal {
        name:string;
        eat():void;
    }
    export class Dog implements Animal{
        name:string;
        constructor(name:string){
            this.name = name;
        }
        eat(){
        }
    }
}

装饰器

ES7的标准,装饰器本身就是一个方法。装饰器只能用于类、类的方法之上(可多个)。普通函数不能使用,因为普通函数存在变量提升。

// 类的装饰器方法
function logClass(str:string){
    return function(target:Function){
        target.prototype.url = str;
    }
}

@logClass("http://a")
class HttpClient{
    url?:string;
    constructor(){
    }
    getData():void{
    }
}
let http:HttpClient = new HttpClient();
http.url;                           // "http://a"

// 属性装饰器
function logProperty(str:string){
    return function(target:any, name:string){
        target[name] = str;         // target 为类的原型对象
    }
}
class HttpClient{
    @logProperty("a")
    url:string|undefined;
    constructor(){
    }
    getData():void{
    }
}
let http:HttpClient = new HttpClient();
console.log(http.url)               // "a"

// 方法装饰器(用得较多)
function logFun(str:string){
    return function(target:any,name:string,des:PropertyDescriptor):PropertyDescriptor{
        let oldValue:Function = target[name];
        
        // 修改方法。注意:ES5 、ES6的描述内容有差别
        des.value = function(){
            console.log("调用了装饰器");
            return oldValue.call(this,str);
        }
        return des;
    }
}
class HttpClient{
    constructor(){
    }
    @logFun("a")
    getData(str:string):string{
        return str;
    }
}
let http:HttpClient = new HttpClient();
console.log(http.getData("b"))               // "a"

// 方法参数装饰器(常用来扩展类,使用场景较少)
function logParams(str:string){
    return function(target:any,name:string,index:Number):void{
        console.log(target);                // target 类的原型链
        console.log(name);                  // name   方法名称
        console.log(index);                 // index  参数的索引
    }
}
class HttpClient{
    constructor(){
    }
    getData(@logParams("uuid") uid:string){
    }
}

执行顺序:属性装饰器 > 方法装饰器 > 方法参数装饰器 > 类装饰器,同一类型装饰器有多个则是从后往前执行。

Javascript类型检查

TS在2.3之后版本可以使用 --checkJs 对 .js 文件进行类型检查和错误提示。

可以通过添加注释来控制检查规则,部分规则如下:

// @ts-nocheck   忽略类型检查
// @ts-check     则检查某些.js文件
// @ts-ignore    忽略本行错误

https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html

上一篇 下一篇

猜你喜欢

热点阅读