TypeScript(一):基础

base: https://tasaid.com/Blog/20171011231943.html

前言

最近随着项目的不断迭代,团队协作之间出现了一个很大的问题:全局的数据属性和数据类型总是被篡改。而且,一些全局的公共方法在使用时,总需要看着API文档才能知道其定义:需要传入什么参数、返回什么参数。
而且,一旦其他同学修改了全局方法而没有来得及更新API文档,通知到每一位同学,那么就会造成其他调用的地方出现不可避免的bug。
在上述过程中涉及到以下问题:

  • 接口该如何描述自身的参数及返回值
  • 数据格式该如何描述
  • 在迭代中,接口参数及返回值对应的API文档该如何及时更新。

紧跟上述问题,我们就好发现在JavaScript中缺少了一样很重要的东西:静态类型。
因为有静态类型我们才知道接口需要什么参数、返回什么值、返回值是什么类型、参数是否可空等等。
在vue中使用的是 ** Flow ** 。

Flow

flow is a static type checker for javascript

flow是facebook公布的JavaScript静态类型检查器,它可以检查JavaScript中的一些bug,如:数据类型隐式转换、数据类型检查等。

// @flow
let a:number = 2;
function foo(b:tring):boolean{
    return false;
}

使用babel转换之后:

let a = 2;
function foo(b){
    return false;
}

TypeScript

相比与 ** Flow ** ,TypeScript是一门语言,它是JavaScript的超集。而且, ** Flow ** 的使用是对JavaScript的一种侵入式修改,我们想要的是一种无侵入式的。
TypeScript 的定位是做静态类型语言,而 Flow 的定位是类型检查器。

那么什么是TypeScripe呢?
TypeScript 简称 TS。TypeScript 是 JavaScript 的超集,就是在 JavaScript 上做了一层封装,封装出 TypeScript 的特性,当然最终代码可以编译为 JavaScript。
随着项目工程越来越大,越来越多的前端意识到静态数据类型的重要性,随着 TypeScript 的逐渐完善,支持者越来越多,静态数据类型的需求越来越强。
JavaScript 行至今日,灵活,动态让它活跃在编程语言界一线。而灵活,动态使得它又十分神秘,只有运行才能得到答案。类型的补充填充了 JavaScript 的缺点,从 TypeScript 编译到 JavaScript,多了静态类型检查,而又保留了 JavaScript 的灵活动态。

简单来说:动态代码一时爽,重构全家火葬场。

静态类型

这里给出一下静态类型检查和类型推导的例子:

let isDone: boolean = true
let myName: string = 'kaka'
let decLiteral: number = 10
let hexLiteral: number = 0x1000

function foo(): void {
    console.log('我没有返回值!')
}

//声明一个void类型 只能将它只为undefined null
const unusable: void = undefined
//没有类型声明 ts会进行类型推导
const myAge = 25
//等价于
const myAge2: number = 25

//联合类型
function bar(somethine: string | object): string {
    return somethine.toString()
}

//interface 定义接口
interface Person {
    name: string
    age?: number
    readonly idCard: string
    [propName: string]: any //任意属性
}

let kaka: Person = {
    name: 'kaka',
    idCard: '1234565',
    age: 25,
    firstName: 'yang',
}

//枚举
enum Days {
    Sun,
    Mon,
    Tue,
    Wed,
    Thu,
    Fri,
    Sat,
}

console.log(Days[0]);
console.log(Days['Sun']);

enum Days2 {
    Sun = 2,
    Mon = 5,
    Tue,
    Wed,
    Thu,
    Fri,
    Sat, 
} 
console.log(Days2['Sun']);
console.log(Days2['Mon']);
console.log(Days2['Tue']);

数组

let array : number[] = [1,2,3];
let array2 :Array<number> = [1,2,3,4];

//使用接口
interface Student {
    name:string,
    age:number
}

let student:Array<Student> = [
    {
        name:'kaka',
        age:0
    }
]

//可索引类型
interface Tree {
    [index:number]:string
}

let tree:Tree;

tree = ['1','2']

public 修饰的方法或属性是公有的 可以被任意访问 类的属性或方法默认为public
private 只能被父类自己访问 不能在声明它的类外部使用
protected 只能被父类和子类访问
static 只能使用类名进行访问

//抽象类 子类必须实现抽象类中的抽象方法
abstract class Person {
    public name:string = 'kaka';
    private weight:number = 25;
    protected sex:string = '男';

    abstract makeSound():void;

    move(distance:number):void {
        console.log(this.name + '已经前进了:' + distance + '步')
    }
}

class Boy extends Person {
    constructor(name,weight,sex){
        super();
        this.name = name;
        // this.weight = weight
        this.sex = sex
    }

    makeSound(){
        console.log('makeSound')
    }

}

let tom = new Boy('tom',100,25);

tom.makeSound();

tom.move(100)



abstract class Animal {
    eat(food:string):void {
        console.log('eat:' + food)
    }
    abstract sleep():void
}


class Dog extends Animal {
    public name:string;

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

    run(){}

    sleep(){
        console.log('Dog sleep')
    }

    private pri(){}

    protected pro(){}

    readonly legs:number = 4

    static food:string = 'bones'

}

let dog = new Dog('wangcai');

// dog.pri()

// dog.pro()

// console.log(dog.food)

console.log(Dog.food)

dog.eat('🍌')


class WorkFlow {
    step1(){
        console.log('🍌调用类step1')
        return this
    }

    step2(){
        console.log('🍎调用类step2')
        return this
    }
}

class MyFlow extends WorkFlow {
    next(){
        return this
    }
}

let myFlow = new MyFlow()

myFlow.next().step1().next().step2().step1().step2() 

接口

interface IPriceData {
    /** id */
    id: number
    /** 市场价格 */
    m: string
    /** 折扣价 */
    op: string
}


type IPriceDataArray = IPriceData[];

function getData(){
     // Promise的泛型参数使用了IPriceDataArray类型,then里面返回的数据就是IPriceDataArray类型
    return new Promise<IPriceDataArray>((resolve,reject) => {
        // fetch('https://xxxxxxx/prices/pgets?ids=P_100012&area=&source=', data => {
            // console.log(data)
            resolve([
                {
                    id:10,
                    m:'kaka',
                    op:'kaka'
                }
            ])
        // })
    })
}

getData().then(data => {
    console.log(data)
});



//高级实现

interface clockconstructor {
    new (hour:number,minute:number): Clockinstance
}

interface Clockinstance {
    tick()
}

function createClock (
    ctor: clockconstructor,
    hour: number,
    minute: number
): Clockinstance{
    return new ctor(hour,minute)
}

class DigitalClock implements Clockinstance {
    constructor(h:number,m:number) {

    }
    tick(){
        console.log("beep beep");
    }
}

class AnalogClock implements Clockinstance {
    constructor(h: number, m: number) {}
    tick() {
        console.log("tick tock");
    }
}

let digitalClock = new DigitalClock(24,59);
let analogClock = new AnalogClock(23,59);

digitalClock.tick()

接口和Type:

  • 相同点
    • 都可描述一个对象或者函数
    • 都可以扩展
  • 不同点
    • interface
      • interface 可以进行声明合并,而type不行
    • type
      • type 可以声明基本类型别名、联合类型、元组等
      • type 可以通过 typeof 获取实例类型进行赋值
//1
interface User {
    name:string,
    age:number
}

// type User = {
//     name:string,
//     age:number
// }

// interface SetUser {
//     (name:string,age:number): void
// }

type SetUser = {
    (name:string,age:number):void
}

const fn:SetUser = function(name:string,age:number){
    console.log(name,age)
}

fn('kaka',25)


//2
interface Sex {
    sex:string
}

interface BSex extends Sex {
    age:number
}

// type Sex = {
//     sex:string
// }

// type BSex = Sex & {
//     age:number
// }


//复杂的转化
// type FName = {
//   name: string;
// };
// interface FUser extends FName {
//   age: number;
// }
interface FName {
  name: string;
}
type FUser = FName & {
  age: number;
};


//不同点

//interface 能够声明合并
interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

/*
User 接口为 {
  name: string
  age: number
  sex: string 
}
*/

//type 可以声明基本类型别名,联合类型,元组等类型

type UName = {
    name:string
}

type USex = {
    sex:string
}

type Boy = UName | USex;

const boy: Boy = {
    name:'l',
    sex:'girl'
}

interface Dog {
    wong();
}
interface Cat {
    miao();
}
type Pert = Dog | Cat;

type PertList = [Dog,Cat]


断言

function add(something: string | number): void {
    // 可以使用类型断言,将 something 断言成 string
    if ((<string>something).length) {
        console.log((<string>something).length)
    } else {
        console.log(something.toString().length)
    }
}

//使用as 进行推断
function sum(something: string | number): void {
    if ((something as string).length) {
        console.log((something as string).length)
    } else {
        console.log(something.toString().length)
    }
}

add('12')
sum(12)

//如果联合属性太复杂 可以给类型起个别名
// 使用 type 创建类型别名,类型别名常用于联合类型

type Name = string
type NameResolver = () => string
type NameOrAge = Name | NameResolver

function getName(name: NameOrAge): string {
    if (typeof name === 'string') {
        return name
    } else {
        return name()
    }
}


泛型

泛型就是解决类、接口、方法的复用性、以及对不确定数据类型的支持

//泛型约束
interface LengthWise {
    length:number
}

function getLength<T extends LengthWise> (arg:T):T{
    console.log("获取泛型T:",arg.length)
    return arg
}

getLength({
    length:100,
    name:'k'
})

function identity<T>(arg:T): T{
    return arg
}

let func = identity<string>('123')


//泛型类

class Girl<T> {
    public x:T;
    public y:T;
    addSum: (x:T,y:T) => T;
    // constructor(x,y){
    //     this.x = x;
    //     this.y = y
    // }
}

let girl = new Girl<number>();

girl.x = 1;
girl.y = 12;

console.log(girl.x)
console.log(girl.y)

girl.addSum = function(x:number,y:number): number{
    return 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 getString(value:string):string {
    return value
}

function getData<T>(value:T): T {
    return value
}


getData<number>(12)
getData<string>('12')


//泛型接口

interface Teacher<T> {
    (name:T) : T
}

let MrsMa:Teacher<number> = function(x:number){
    return x
}

MrsMa(12)


interface Teacher {
    <T>(name:T) : T
}
let MrsMa:Teacher = function<T>(value:T):T {
    return value
}

MrsMa<number>(12)

装饰器

顾名思义,”装饰器” (也叫 “注解”)就是对一个 类/方法/属性/参数 的装饰。
它是对这一系列代码的增强,并且通过自身描述了被装饰的代码可能存在的行为改变。
简单来说,装饰器就是对代码的描述

/**
 * 顾名思义,"装饰器" (也叫 "注解")就是对一个 类/方法/属性/参数 的装饰。
 * 它是对这一系列代码的增强,并且通过自身描述了被装饰的代码可能存在的行为改变。
 * 简单来说,装饰器就是对代码的描述
 *
 */
import 'reflect-metadata';

var validate = function(){
    return function (
        target: any,
        targetKey: string,
        descriptor: PropertyDescriptor
    ) {
        console.log(target, targetKey, descriptor);
        //保存原来的方法
        let method = descriptor.value;
        //重写原有的方法
        descriptor.value = function(newValue:string){
            // 检查是否是空字符串
            if (!newValue) {
                throw Error('name is invalid')
            } else {
                // 否则调用原来的方法
                method.call(this, newValue)
            }
        }

    }
}

class User {
    name: string
    id: number
    constructor(name: string, id: number) {
        this.name = name
        this.id = id
    }

    // 调用装饰器
    @validate()
    changeName(newName: string) {
        this.name = newName
    }
}


let user = new User('kaka', 25)

user.changeName('');

console.log(user.name)

//类装饰器 重写类的构造函数

const A  = function(){
    return function(constructor){
        console.log(constructor);
        return class extends constructor {
            name = 'hudie'
        }
    }
}
@A()
class B {
    public name:string;
    constructor(name:string){
        this.name = name
    }
}

let b = new B('kaka');

console.log(b.name)


//方法装饰器
/**
 * target:对于类的静态成员,指的是类的构造函数。对于类的实例成员,指的是类的原型对象
 * targetKey:成员的名称
 * descriptor:成员的属性描述符。
 *  {value: any, writable: boolean, enumerable: boolean, configurable: boolean}
 */
function C(){
    return function (target:any,targetKey:string,descriptor:PropertyDescriptor){
        console.log('🍎:',target)
        console.log('🍎:',targetKey)
        console.log('🍎:',descriptor)
    }
}

class D {
    public name:string;
    constructor(name:string){
        this.name = name;
    }
    @C()
    getName(){
        return this.name
    }

    @C()
    static getAge(){
        return 10
    }
}

let d = new D('kaka');

console.log(d.getName())

D.getAge()


//访问器修饰符  同方法修饰器 只是用于访问器上
//不同点就是修饰符使用的是 访问器修饰符 
//{get: function, set: function, enumerable: boolean, configurable: boolean}
function E(){
    return function(target:any,targetKey:string,descriptor:PropertyDescriptor){
        console.log('🍎:',target)
        console.log('🍎:',targetKey)
        console.log('🍎:',descriptor)
    }
}

class F {
    private _name: string = 'kaka';
    // 装饰在访问器上
    @E()
    get name () {
        return this._name
    }
}

let f = new F();

console.log(f.name)


/**
 * 属性修饰器
 * 在运行时会被当作函数使用 传入两个参数
 *  target: 对于静态成员来说 是类的构造函数  对于实例成员来说 是类的原型对象
 *  targetKey: 成员名称
*/

function G(){
    return function(target:any,targetKey:string){
        console.log('🍎:',target)
        console.log('🍎:',targetKey)
    }
}

class H {
    //实例成员 --> 类的原型对象
    @G()
    public name:string;
    //静态属性 --> 类的构造函数
    @G()
    static money:number = 1000;

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

let h = new H('kaka');

console.log(h.name)


/**
 * 参数修饰器
 * 参数装饰器表达式会在运行时当作函数被调用,传入下列 3个参数:
 *  - target : 对于静态成员来说 指的是类的构造函数。 对于实例成员来说 指的是类的原型对象
 *  - targetKey : 成员名称
 *  - parameterIndex : 参数在函数参数中的索引
*/

function I(){
    return function(target:any,targetKey:string,parameterIndex:number){
        console.log('🍎:',target)
        console.log('🍎:',targetKey)
        console.log('🍎:',parameterIndex)
    }
}

class J {
    public name:string;
    constructor(name:string){
        this.name = name;
    }
    setName(value:string, @I() key:string){
        this.name = value;
    }
}

let j = new J('kaka');


//

import 'reflect-metadata';

const requiredKey = Symbol.for("required:key");

//定制一个参数装饰器
var required = function(){
    console.log('参数装饰器1')
    return function(target:any,targetKey:string,index:number){
        console.log('参数装饰器2')
        const rules = Reflect.getMetadata(requiredKey,target) || [];
        rules.push(index);
        // console.log(rules,'---')
        Reflect.defineMetadata(requiredKey,rules,target)
    }
}

var validateEmptyStr = function(){
    console.log('方法装饰器1')
    return function(target:any,targetKey:string,descriptor:PropertyDescriptor){
        let methods = descriptor.value;
        console.log('方法装饰器2')
        let rules = Reflect.getMetadata(requiredKey,target)
        // console.log(rules)
        descriptor.value = function (){
            let args = arguments;
            // console.log(args,'args')
            let rules = Reflect.getMetadata(requiredKey,target) as Array<number>;
            if(rules && rules.length){
                rules.forEach(key => {
                    if(!args[key]){
                       throw Error(`arguments${key} is invalid`)
                    }
                })
            }
            return methods.apply(this,arguments)
        }
    }
}

class User {
    name:string;
    id:number;

    constructor(name:string,id:number){
        this.name = name;
        this.id = id;
    }
    @validateEmptyStr()
    setName(@required() value){
        this.name = value
    }
}

let u = new User('kaka',25);
u.setName('hudie')


/**
 * 元数据反射
 * 反射就是在运行时动态获取一个对象的一切信息:属性、方法等。其特点在于动态类型反推导。
 * 在Typescript中就是在设计阶段对对象注入元数据,在运行阶段读取注入的元数据。
*/

function meta(){
    return function(target:any,targetKey:string,descriptor:PropertyDescriptor){
        //获取成员类型
        let type = Reflect.getMetadata('design:type',target,targetKey);
        console.log(type)//[Function: Function]
        //获取参数类型
        let paramsType = Reflect.getMetadata('design:paramtypes',target,targetKey);
        console.log(paramsType)//[ [Function: String] ]
        //获取返回值类型
        let returnType = Reflect.getMetadata('design:returntype',target,targetKey);
        console.log(returnType)//[Function: String]
        //获取所以元数据的key(有TypeScript注入)
        let keys = Reflect.getMetadataKeys(target,targetKey);
        console.log(keys)//[ 'design:returntype', 'design:paramtypes', 'design:type' ]
    }
}

class User {
    @meta()
    say (myName: string): string {
        return `hello, ${myName}`
    }

}

引入和编译

快速使用
//安装
yarn add typescript 
//编译
tsc index.ts

我们还可以使用ts-node进行快速运行:

//安装
yarn add ts-node -g
//执行
ts-node index.ts
tsconfig.json

tsconfig.json 是 TypeScript 的编译选项文件,通过配置它来定制 TypeScript 的编译细节。

  • 直接调用 tsc,编译器会从当前目录开始去查找 tsconfig.json 文件,逐级向上搜索父目录。
  • 调用 tsc -p,可以指定一个包含 tsconfig.json文件的目录进行编译。如果没有找到 tsconfig.json 文件,TypeScript 会编译每个文件并在对应文件的同级目录产出。

如果你要编译的是一个 Node 项目,请先安装 Node 编译依赖: npm i @types/node --save-dev,否则会出现 Node 内置模块无法找到的情况。

一个tsconfig.json的描述文件:

{
  // 编译选项
  "compilerOptions": {
    // 输出目录
    "outDir": "./output",
    // 是否包含可以用于 debug 的 sourceMap
    "sourceMap": true,
    // 以严格模式解析
    "strict": true,
    // 采用的模块系统
    "module": "esnext",
    // 如何处理模块
    "moduleResolution": "node",
    // 编译输出目标 ES 版本
    "target": "es5",
    // 允许从没有设置默认导出的模块中默认导入
    "allowSyntheticDefaultImports": true,
    // 将每个文件作为单独的模块
    "isolatedModules": false,
    // 启用装饰器
    "experimentalDecorators": true,
    // 启用设计类型元数据(用于反射)
    "emitDecoratorMetadata": true,
    // 在表达式和声明上有隐含的any类型时报错
    "noImplicitAny": false,
    // 不是函数的所有返回路径都有返回值时报错。
    "noImplicitReturns": true,
    // 从 tslib 导入外部帮助库: 比如__extends,__rest等
    "importHelpers": true,
    // 编译过程中打印文件名
    "listFiles": true,
    // 移除注释
    "removeComments": true,
    "suppressImplicitAnyIndexErrors": true,
    // 允许编译javascript文件
    "allowJs": true,
    // 解析非相对模块名的基准目录
    "baseUrl": "./",
    // 指定特殊模块的路径
    "paths": {
      "jquery": [
        "node_modules/jquery/dist/jquery"
      ]
    },
    // typescript 语法检测支持的版本库,注意不是 polyfill!只是为了有对应版本的代码特性提示!
    "lib": [
      "es2015",
      "es2015.promise"
    ]
  }
}

完整 tsconfig 配置选项的可以参考 这里,或者 tsconfig 的 json-schema

注意: TypeScript 不会做 Polyfill,例如从 es6 编译到 es5,TypeScript 编译后不会处理 es6 的那些新增的对象的方法,如果需要 polyfill 需要自行处理!
完整的编译选项请参阅 TypeScript 中文网TypeScript 官网

编译

关于编译,个人比较倾向于使用Gulp。这里给出我的个人配置。
gulpfile.js:

const { src,dest,watch,series,task } = require('gulp')
const del = require('del');
const ts = require('gulp-typescript');
const tsProject = ts.createProject("tsconfig.json");

function clean(cb){
    return del(['dist'],cb)
}

function build(){
    return watch('src/**/*.ts',{ events:'all',delay:500,ignoreInitial:false },function(){
        return src('src/**/*.ts')
        .pipe(tsProject())
        .pipe(dest("dist"))
    })
}


exports.default =  series(clean,build)

执行npx gulp即可将src下的ts文件编译到dist目录下。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!