ts 分享

馋奶兔 提交于 2020-08-12 00:52:28

主要内容:

  • Typescirpt 背景(快)

  • 用webpack 搭建一个ts环境(慢)

  • ts基础(快)

  • ts 进阶(慢)

  • 装饰器(慢)

  • ts在 vue项目中使用

typescript 缘起

image-20200729105945637

image-20200729105957704

什么是typescript

image-20200729110042342

为什么要用ts

image-20200729110130583

ts的现状和未来

image-20200729113135158

image-20200729113155053

image-20200729113232513

image-20200729113217024

ts的ROI

image-20200729113308010

主要内容

ts基础

ts 工程

强类型语言

image-20200729110625146

image-20200729110710735

弱类型语言

image-20200729110735941

静态类型语言和动态类型语言

image-20200729110844027

image-20200729111112015

image-20200729111322539

image-20200729111343034

image-20200729111355920

用webpack手动搭建一个ts环境

###
- npm i typescript -g
- npm list --depth=0
### 初始化
- npm init -y
- npm i typescript -g  // 全局安装,可以在任何地方使用ts编译器,tsc
- tsc --init
- tsc
### webpack
- npm i webpack webpack-cli webpack-dev-server -D
- > build
- npm i ts-loader  typescript HtmlWebpackPlugin --D
- HtmlWebpackPlugin 通过一个模板,帮助我们生成网站的首页,而且把输出文件自动切到这个文件中
- npm i webpack-merge@4.2.1 -D
###
- 修改npm脚本
- 入口 main
- start
-  "start": "webpack-dev-server --mode=development --config ./build/webpack.config.js",
-  build
    -  "build": "webpack --mode=production --config ./build/webpack.config.js"

在线编译 https://www.typescriptlang.org/play/index.html

基本类型

image-20200729111425245

类型注解

image-20200729111621687

一个角色判断的例子

image-20200729111807649

枚举

image-20200729111829713

枚举类型

一组有名字的常量集合

  • 枚举成员的值是只读类型的,一旦定义了不能修改

  • 实现原理:反向映射,枚举被编译成了一个对象,枚举成员的名称被作为了key,枚举成员的值被作为了value,value又作为key,成员的名称又被作为value;

  • 适用情况:将程序中不容易记忆的硬编码,或者在未来可能改变的常量抽取出来,定义成枚举类型,可以提高程序的可读性和可维护性

  • 在线编译 https://www.typescriptlang.org/play/index.html

    enum {
        Reporter = 1,
        Developer
    }
    
    最终被编译成:
    "use strict"
    var Role;
    (function(Role){
        Role[Role["Reporter"] = 1] = "Reporter"];
        Role[Role["developer"] = 1] = "developer"];
    }
    )(Role || {Role = {})
    
    

类型别名

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

  • 类型别名常用于联合类型

    // - 我们使用 type 创建类型别名。
    type Name = string;
    type NameResolver = () => string;
    type NameOrResolver = Name | NameResolver;
    function getName(n: NameOrResolver): Name {
        if (typeof n === 'string') {
            return n;
        } else {
            return n();
        }
    }
    

接口

  • 接口可以用来约束对象,函数,类的结构和类型,是一种代码协作的契约,必须遵守,且不能改变;

    interface List {
        id: number,
        name: string
    }
    interface Result {
        data: List[]
    }
    
    function render(result: Result){
        result.data.forEach((value) => {
            console.log(value.id,value.name)
        })
    }
    
    let result = {  // 假设result是从后端返回的接口
        data: [
            {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查
            {id: 2, name: 'B'}
        ]
    }
    
    render(result)  // 没毛病
    // 用对象字面量的方式ts会报错
    render({  // 假设result是从后端返回的接口
        data: [
            {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查
            {id: 2, name: 'B'}
        ]
    })
    
    绕过这种检查的方式有三种:
    1.将对象字面量赋值给一个变量,如上;
    2.使用类型断言, as Result,或在前面加上<Result>
        - 这两种方法是等效的,建议使用第一种方法,<Result>在React会产生歧义
    明确的告诉编译器,传入的对象类型就是Result,这样编译器就会绕过类型检查
    render({  // 假设result是从后端返回的接口
        data: [
            {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查
            {id: 2, name: 'B'}
        ]
    } as Result)
    
    render(<Result>{  // 假设result是从后端返回的接口
        data: [
            {id: 1, name: 'A', sex: 'male'}, // 只要满足要求,传入多余的字段也可以通过类型检查
            {id: 2, name: 'B'}
        ]
    })
    
    3.使用字符串索引签名
    interface List {
        id: number,
        name: string,
        [x: string]: any  // 字符串索引签名, 含义是:用任意的字符串去索引List,可以得到任意的结果,这样List可以支持多个属性了
    }
    
    

接口成员属性

1. 可选属性,属性后加 ?
interface List {
    id: number,
    name: string,
    age?: number  // 可有可无
}

2. 只读属性
interface List {
    readonly id: number, // id一般是只读属性
    name: string
}
// 注意,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候

3. 任意属性
// 有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};
// 使用 [propName: string] 定义了任意属性取 string 类型的值。
// 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}

let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
// 上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 number,number 不是 string 的子属性,所以报错了。
// 另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' }
// 的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; },
// 这是联合类型和接口的结合。

函数类型接口

  • 对方法传入的参数,以及返回值进行约束,并且可以进行批量约束;

    // 1. 用变量定义函数
    let add: (x: number, y: number) => number
    
    // 2. 用接口定义,这两种定义方式是等价的
    interface Add {
        (x: number, y: number): number
    }
    
    // 3. 类型别名,type关键字
    type Add = (x: number, y: number) => number
    
    // 4. 函数定义
    function add4(x: number, y: number) {
        return x + y;
    }
    
    let add: Add = (a, b) => a + b;
    

混合类型接口

  • 就是一个接口,既可以定义函数,又可以像对象一样,拥有属性和方法;

    // 混合类型接口定义类库
    interface Lib {  // 没有参数,也没有返回值
        (): void;
        version: string;
        do(): void;
    }
    
    function getLib() {
        let lib: Lib = (() => {}) as Lib  // 类型断言
        lib.version = '1.0'
        lib.do = () => {}
        return lib;
    }
    // 放在函数里,不暴露在全局,这样可以创建多个lib了
    let lib1 = getLib();
    lib1();
    lib1.do();
    let lib2 = getLib();
    

类型断言

语法

值 as 类型

或

<类型>值

类与接口

image-20200729111908383

交叉类型和联合类型

image-20200729112142592

交叉类型

  • 交叉类型比较适合做对象的混入;
  • 组合多个类型组成新的类型,新类型包含了原类型的所有属性
// 交叉类型: 取所有类型的并集
interface DogInterface {
    run(): void
}
interface CatInterface {
    jump(): void
}

let pet:DogInterface & CatInterface = {
    run(): void
     jump(): void
}

联合类型

// 联合类型就是声明的类型并不确定,可以为多个类型中的一个
 let a: string | number = "1"

// 字面量联合类型
// 有时候我们不仅要限制变量的类型,还需要限制变量的取值在某个特定的范围内
// 字符串联合类型
let c: 'a' | 'b' | 'c'
// 数字联合类型
let b: 1 | 2 | 3

// 对象联合类型
class Dog implements DogInterface{
     run(): void
     eat(): void
}

class Cat implements CatInterface{
     jump(): void
     eat(): void
}

enum Master {
    Boy,
    Girl
}

function getPet(master: Master){
    let pet = master === Master.Boy ? new Dog(): new Cat()
    // 如果一个对象是联合类型,在类型未确定的情况下,只能访问所有类型的公有成员,这种情况下只能访问所有类型的交集
    pet.eat()
    return pet;
}

索引类型

  • 索引类型可以实现对对象属性的查询和访问,

  • 再配合泛型约束,就使我们 能够建立对象,对象属性,以及属性值之间的约束关系

  • 索引类型的查询操作符

    • keyof T

    • 表示类型 T 的所有公共属性的字面量联合类型;

      // key的类型是a和b的字面量联合操作类型
      interface Obj {
          a: number;
          b: string;
      }
      let key: keyof Obj
      
索引访问操作符
  • T[K]

  • 表示对象T的属性k所代表的类型

    let value: Obj["a"]
    
泛型约束
  • T extends U

  • 表示泛型变量可以通过继承某个类型或某些属性

    let obj = {
        a: 1,
        b: 2,
        c: 3
    }
    function getValues(obj: any, keys: string[]){
        return keys.map(key => obj[key])
    }
    
    console.log(getValues(obj, [a, b])) // 1,2
    console.log(getValues(obj, [e, f])) // [undefind, undefind]
    // 此时ts应该报错
    // 经过改造后的函数
    
    function getValues<T,K extends keyof T>(obj: T,keys: K[]): T[K][]{
        // 先定义个泛型变量T,用它来约束obj
        // 再定义一个泛型变量K,用它来约束keys数组
        // 把 K 增加一个类型约束,让他来继承obj所有属性的联合类型,K extends keyof T;
        // 函数的返回值是一个数组[],数组的类型就是属性K对应的类型T[K]
        // 这样就通过一个索引类型把getValues改造完毕了,这时ts类型检查就发挥作用了
        // 如果我们指定一个不再obj范围内的属性,ts就会报错
    }
    
    type Dictionary<T> = { [key: string]: T }
    

条件类型

T extends U ? X : Y
// 如果类型T,可以被赋值给类型U,结果类型就是X类型,否则Y类型;
// 条件类型使类型具有了不唯一性,同时也增加了语言的灵活性;

type TypeName<T> =
    T extends string ? "string" :
    T extends number ? "number" :
    T extends boolean ? "boolean" :
    T extends undefined ? "undefined" :
    T extends Function ? "function" :
    "object";
type T1 = TypeName<string>
type T2 = TypeName<string[]>

泛型

泛型的概念

image-20200729111954471

首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:


function createArray(length: number, value: any): Array<any> {
    let result = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray(3, 'x'); // ['x', 'x', 'x']

这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型:

Array<any> 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value 的类型。

这时候,泛型就派上用场了:

function createArray<T>(length: number, value: T): Array<T> {
    let result: T[] = [];
    for (let i = 0; i < length; i++) {
        result[i] = value;
    }
    return result;
}

createArray<string>(3, 'x'); // ['x', 'x', 'x']

泛型的好处

image-20200729112209843

类型检查机制

image-20200729112247500

类型推断

image-20200729112314741

类型兼容性

image-20200729112342438

类型保护

image-20200729112400564

命名空间 namespace

  • 命名空间一个最明确的目的就是解决重名问题。

  • 想要在全局内可见的话,用export 导出

  • 可拆分

  • Shape.circle(2)

  • 命名空间和模块不要混用

  • 不要在一个模块里面使用命名空间,命名空间最好全局的环境中使用

    新语法索引

    declare 可以为外部变量提供类型声明

装饰器

ts装饰器 https://my.oschina.net/u/3150996/blog/3113969

类装饰器

类装饰器在类声明之前被声明,(紧靠着类声明),类装饰器应用于类构造函数,可以用来监视、修改或替换类定义;

// 类装饰器:普通装饰器(无法传参)
function logClass(params){
    // params 就是当前类
    console.log(params)
    params.prototype.apiUrl = "动态扩展的属性"
    params.prototype.fetch = function(){
        console.log('我是一个fetch方法')
    }
}

@logClass
class HttpClient {
    constructor(){

    }

    getData(){

    }
}

let http = new HttpClient()
console.log(http.apiUrl) // 动态扩展的属性

http.fetch() // 我是一个fetch方法

类装饰器:装饰器工厂

// 类装饰器:装饰器工厂(可以传参)
function logClass(params){
    console.log(params)
    // params调用时的实参
    return function(target){
        // target 当前类 HttpClient
          console.log(target)
        params.prototype.apiUrl = params
    }
}

@logClass('newbanker')
class HttpClient {
    constructor(){

    }

    getData(){

    }
}

let http = new HttpClient()
console.log(http.apiUrl) // newbanker

属性装饰器

属性装饰器会在运行时当做函数被调用,传入下列两个参数:

  • 对应静态成员来说是类的构造函数,对于实例成员是类的原型对象;

  • 属性成员的名称;

    // 类装饰器:装饰器工厂(可以传参)
    function logClass(params){
        console.log(params)
        // params调用时的实参
        return function(target){
            // target 当前类 HttpClient
              console.log(target)
        }
    }
    
    // 属性装饰器
    function logProperty(params){
        return function(target, attr){
            console.log(target) //
            console.log(attr) // 当前属性url
            target.attr = params
        }
    }
    
    @logClass
    class HttpClient {
      @logProperty('https://baidu.com')
        public url
    
        getData(){
    
        }
    }
    

方法装饰器

// 方法装饰器 一
function get(params: any) {
  return function(target: any, methodName:any, desc: any) {
    console.log(target) // 原型对象,可以扩展类的原型属性和方法;
    console.log('methodName', methodName)
    console.log('desc', desc)
    // 可以扩展当前实例的属性和方法
    target.apiUrl = 'http://www.sina.cn'
    target.run = function() {
      console.log('run')
    }
  }
}

class HttpClient {
  public url: any
  [propName:string]: any
  @get('http://www.baidu.com')
  getData(args: any) {

  }
}

const http = new HttpClient()

console.log(http.apiUrl) // http://www.sina.cn
http.run()

方法装饰器二
function get(params: any) {
  console.log('get params', params)
  return function(target: any, methodName:any, desc: any) {
    console.log(target) // 原型对象,可以扩展类的原型属性和方法;
    console.log('methodName', methodName)
    console.log('desc', desc)
    // 可以扩展当前实例的属性和方法
    target.apiUrl = 'http://www.sina.cn'
    target.run = function() {
      console.log('run')
    }

    // console.log(methodName) // 当前方法名 getData
    // // 修改装饰器的方法,把装饰器方法里面传入的所有参数改为string类型;
    // //  保存当前方法
    const save = desc.value
    desc.value = function(...args: any[]) {
      args = args.map((v) => {
        return String(v)
      })
      // save.apply(this, args) // 把当前方法this传进去,代表在这个方法里面调用save方法;并传入参数;
      args.push(params)
      console.log('args---', args)
      return args
    }

    // console.log(desc) // 描述信息  { value: f, xxx: xxx }
    // // 不加此方法会直接替换实例的方法,加上会修改
  }
}

class HttpClient {
  public url: any
  [propName:string]: any
  @get('http://www.baidu.com')
  getData(...args: any[]) {
    console.log('getData arg', args)
    console.log('我是getData的method')
  }
}

const http = new HttpClient()

console.log(http.apiUrl) // http://www.sina.cn
http.getData(123, '项布斯')
http.run()

装饰器执行顺序

// 类装饰器:普通装饰器(无法传参)
function logClass1(params: any) {
  return function(target: any) {
    console.log('类装饰器1111')
  }
}

function logClass2(params: any) {
  return function(target: any) {
    console.log('类装饰器2222')
  }
}

function logAttribute(params?: any) {
  return function(target: any, attrName: any) {
    console.log('属性装饰器333')
  }
}

function logMethod(params?: any) {
  return function(target: any, attrName: any) {
    console.log('方法装饰器4444')
  }
}

function logParams1(params?: any) {
  return function(target: any, attrName: any) {
    console.log('方法参数装饰器555')
  }
}

function logParams2(params: any) {
  return function(target: any, attrName: any) {
    console.log('方法参数装饰器666')
  }
}

@logClass1('http:www.baidu.con')
@logClass2('xxxx')
class HttpClient {
    @logAttribute()
    public apiUrl: string = 'newbanker'

    @logMethod()
    getData() {

    }
    // @ts-ignore
    setData(@logParams1('logParam1') attr1: any, @logParams2('logParam2') attr2: any) {

    }
}

const http: any = new HttpClient()

// 属性装饰器 > 方法装饰器 > 方法参数装饰器2 > 方法参数装饰器1 > 类装饰器2 > 类装饰器1
属性 》 方法 》 方法参数 》 类
统一类型的装饰器,如果有多个,从后往前、从下往上执行

vue项目

第三方包查找: https://microsoft.github.io/TypeSearch/

vue 官网 ts https://cn.vuejs.org/v2/guide/typescript.html#%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95

  • @Prop({ required: true }) private name!: string 非空断言
  • get xxMethod 存取器,作为计算属性使用

react项目

ts工程

如何选择ts编译工具

image-20200729112622297

tslint和eslint

ts https://ts.xcatliu.com/engineering/lint.html

image-20200729112703004

typescript和eslint

image-20200729112744932

image-20200729112809561

babel-eslint和typescript-eslint

image-20200729112843499

ts工具体系

image-20200729112943421

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!