一文看完 Typescript

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容