TypeScript 应用

11/1/2020 typescript

# TypeScript 简介

TypeScript是一种由微软开发的开源 (opens new window)、跨平台的编程语言。它是JavaScript (opens new window)的超集,最终会被编译为JavaScript代码。TypeScript添加了可选的静态类型系统、很多尚未正式发布的ECMAScript新特性(如装饰器 [1] )。2012年10月,微软发布了首个公开版本的TypeScript,2013年6月19日,在经历了一个预览版之后微软正式发布了正式版TypeScript。当前最新版本为TypeScript3.8。

# TypeScript 安装

**全局安装:**npm install -g typescript **效验:**tsc -v

# 设置 VSCode 自动编译

运行 tsc --init 创建tsconfig.json文件 修改tsconfig.json,设置js文件夹 "outDir": "./js/" 1592708340(1)

# TypeScript 语法

# TS 变量创建指定类型

// ts创建变量时指定变量类型, 当指定特定类型时, 改变为别的类型将会编译错误
let user_name: string = 'dsdasda'
user_name = 0 // 报错
// 当一个变量需要储存多个值时, 需要用到联合类型语法
let somThing: string | number = '112132'
somThing = 0
// 如果变量的声明和初始化是在同一行, 可以省略掉变量类型的声明
let user_name_1 = 'uzi' // --> let user_name_1:string = 'uzi'

# TS 基本类型

let aName: string = '貂蝉'
let dAge: number = 18
let isSingLeDog: boolean = true
let undef: undefined = undefined
let nul: null = null

# TS 数组类型

// JS中数组可以放任意值, ts数组需指定元素类型
let arrJS = [1, 'a', true, [], {}]
// ts方式一:let 数组名:类型[] = [值1, 值2]
let arrHeros: string[] = ['安其拉', '亚索', '大乔']
// ts方式二: let 数组名:Array<类型> = [值1, 值2] 该数组为泛型数组
let arrHeroAge: Array<number> = [17, 231, 23]

# TS 函数类型

// TS中函数必须指定返回值类型, 如果不指定, 默认会自动指定类型
function fun_1(): string {
    return '讨厌~~'
}
let content_1: string = fun_1()

// TS中形参必须指定类型, 且与实参参数与数量必须一致
function fun_2(u_name: string, u_age: number): string {
    return `您的名称是:${u_name}, 年龄是:${u_age}`
}
let content_2: string = fun_2('毛先生', 15)
// TS函数中指定可选类型, 可选参数可传, 也可不传
function fun_3(u_name?:string) { console.log(u_name) }
fun_3()
// 当指定了初始值, 也可不需要传入参数
function fun_4(u_name:string = '毛先生') { console.log(u_name) }
fun_4()
// TS中剩余参数的写法
function fun_5(a: number, b: number, ...allNum: number[]) {
    let num = a + b
    num += allNum.reduce((total, item):number => total += item, 0)
    console.log(num);
}
fun_5(6, 8, 456, 4561, 123);
// ts中, 方法的重载, 定义多个方法, 参数统一接受
function getInfo(name:string):string;
function getInfo(age:number):number;
function getInfo(str:any):any{
    if (typeof str === 'string')
        return `我叫:${str}`
    else
        return `我的年龄是:${str}`
}

# TypeScript 新增数据类型

# TS 元组(tuple)

tuple元组就是一个规定了元素数量和每个元素类型的数组,而每个元素的类型, 可以不相同

let tup1: [string, number, boolean] = ['讨厌', 18, true]
console.log(tup1[0])
console.log(tup1.length)

# TS 枚举(enum)

enum GunType_1 {
    M416 = 1,
    AK47 = 2,
    Goza = 3
}
// 枚举值不指定默认赋值枚举值
enum GunType_2 {
    M416, // --> 1
    AK47, // --> 2
    Goza  // --> 3
}

使用场景:判断性别类型男, 女, 未知

enum Gender {
    Boy, // 男孩 --> 1
    Girl, // 女孩 --> 2
    Unknow // 未知 --> 3
}
// 创建用户性别变量
// let usrSex: Gender = Gender.Boy
let usrSex = Gender.Boy
// 判断变量中的性别是否为 Boy
if(usrSex == Gender.Boy){
    console.log(usrSex) // 1
}else {
    console.log(usrSex) // 2 or 3
}

[^注意]:枚举项 一般用英文和数字, 而枚举值 用整型数字

# TS 任意类型(any)

any 代表任意类型, 一般在获取dom时使用

// 在接收用户输入 或 第三方代码库时, 还不能确定会返回什么类型的指, 此时也可以使用any类型
let txtName: any = document.getElementById('txtN')

# TS 无类型(void)

因为TS 函数中必须指定返回值,但有些函数是不需要返回值的,所以无返回值的函数中使用void代表无返回值的函数。

// TS 函数中必须指定返回值
function say_hi1(): string { return 'hi, 你好呀~' }
const say_hi2 = (): string => 'hi, 你好呀~'
let re1 = say_hi1()
let re2 = say_hi2()
// TS 函数中不需要返回值时需指定void
function say_hi3(): void { console.log('hi啥, 讨厌, 死鬼~~~~') }
const say_hi4 = (): void => console.log('hi啥, 讨厌, 死鬼~~~~')

# TS 不存在值(never)

never 代表不存在的值的类型, 常用作为抛出异常或无限循环的 函数返回类型

function test_1(): never {
    while (true) { }
}
function test_2(): never {
    throw new Error('讨厌, 死鬼~')
}
// never类型可以赋值给任意类型的变量
let x:never = test_1()
let y:string = test_2()

[^补充]:never类型是ts中的底部类型, 所有类型都是never类型的父类,所以never类型可以赋值给任意类型的变量

# TS 对象记载(Record)

Record 用于定义对象中key值与val值的类型,使用泛型定义,由 , 区别key与val的关系

const obj:Record<string , number> = {dwd: 60};

# TypeScript Class类 规范

# TS Class 类基本架构

class City {
    // 成员属性
    cName: string = ''
    cLevel: number
    constructor(cName: string, cLevel: number) {
        // 初始化构造器
        this.cName = cName
        this.cLevel = cLevel
    }
    about(): void {
        console.log(`您要跳${this.cName}, 难度系数为${this.cLevel}`);
    }
    static count = 50 // 静态属性
    static fun () {/*...*/} // 静态方法
}
// 创建构造对象, 参数未设置默认值或可为空, 默认必传
const citv = new City('p城', 5)
console.log(citv.cName) // 'p城'
console.log(citv.cLevel) // 5
citv.about() // ....

# TS Class 类修饰符

class City {
    // 成员属性
    private cName: string = '' // 私有属性, 类外部不可访问
    cLevel!: number // 属性值!:number 代表值可以为空(undefined)

    constructor(cName: string, cLevel: number) {
        this.cName = cName
    }
    // 私有方法, 类外部不可访问
    private about(): void { console.log(`您要跳${this.cName}, 难度系数为${this.cLevel}``); }
    // 共有方法, 类外部可访问
    public about_2(): void {/*...*/}
	// protected, 派生类公共方法, 类外部不可访问, 但继承类可访问
	protected about_3(): void {/*...*/}
}
const citv = new City('p城', 5)

console.log(citv.cName) // 属性“cName”为私有属性,只能在类“City”中访问。
console.log(citv.cLevel) // 5
citv.about() // 属性“about”为私有属性,只能在类“City”中访问。

# TS Class 继承类

class Animal {
    protected name: string; // 类与子类私有属性, 外部不可访问
    constructor(theName: string) {
        this.name = theName;
    }
    about (){ console.log(this.name); }
}
class Rhino extends Animal { // extends关键字继承父类所有方法
    constructor() {
        super('Rhino'); // 调用父类的构造器函数, 获取父类的属性与属性值
    }
    getName() {
        console.log(this.name) //此处的name就是Animal类中的name
        this.about()
    }
}

# TS Class 多态类

class Animal {
    eat() {/*...*/}
}
class Rhino extends Animal { // extends关键字继承父类所有方法
    // 类的多态, 每个类都有一样的方法, 不一样的行为
    // 如果子类没有方法, 则使用当前继承类的方法
    eat() {/*...*/}
}

# TS Class 抽象类

// 抽象类不能直接实例化, 抽象类是给予子类的一个基类
abstract class Animal {
    // 定义一个抽象方法, 继承该类时该方法必须定义
    abstract eat():any;
}
// new Animal() // 报错
class Rhino extends Animal {
    // 抽象类的子类必须实现抽象类里面的抽象方法
    eat() {/*...*/}
}

# TypeScript 接口 规范

# 对象类型接口

// TS 函数中单函数约束
function printLabel (label:string):void {
    console.log('')
}
printLabel('hahaha') // 必须传入参数
// TS 函数中对对象传参中的约束
function printLabel (labelInfo:{label:string}):void {
    console.log('')
}
printLabel({label: 'hahaha'}) // 必须传入对象, 对象中存在label参数, 并且是字符串
// 利用接口定义对象参数的约束规范
interface FullName{
    firstName: string; // 定义必须接口
    secondName: string; // 定义必须接口
    age?: number; // 定义可选接口
}
function printName (name:FullName){
    console.log(`${name.firstName}---${name.secondName}`)
}
function printInfo(name:FullName) {
    console.log(`${name.firstName}---${name.secondName}`)
}
printInfo({
    firstName: '张',
    secondName: '三'
})
printName({
    age: 20,
    firstName: '张',
    secondName: '三'
})

# 函数类型接口

// 对方法传入的参数以及返回值进行约束 批量约束 
interface encypt{
	(key:string, value:string):string;
}
var md5:encypt = function (key, value):string{
	return key + ' ' + value // 模拟加密操作
}
console.log(md5('李', '二狗'))
var sha1:encypt = function(key, value):string{
	return key + '--' + value
}
console.log(sha1('dog', 'zi'))

# 索引类型接口

// 可索引接口对数组的约束
interface UserArr {
    [index: number]: string // 定义索引值必须得是number, 元素值必须得是string
}
const arr: UserArr = ['123213213213', '12312321321']
// 可索引接口对对象的约束
interface UserObj {
    [index: string]: string // 定义索引值必须得是number, 元素值必须得是string
}
const obj: UserObj = {key: 'value', name: 123} // 报错

# 类类型接口

// 类类型接口, 与抽象类类似, 但抽象类不可以规范属性, 类接口可以
interface Animal_ {
    myName: string;
    eat(str:number): any;
}
class Dow implements Animal_ {
    myName:string
    constructor(myName:string) {
        this.myName = myName
    }
    eat(str:number) { }
}

# 接口的继承

// 接口的继承
interface Animal_gf {
    eat(): void;
}
interface Person extends Animal_gf {
    work(): void;
}
class Web implements Person{
    eat(){}
    work(){}
}
class Prog extends Web implements Person {
    
}

# 巧妙查找类型

interface Person {
  addr: {
    city: string;
    street: string;
    num: number;
  }
}
const Addr: Person["addr"] = {
  city: "city",
  street: "street",
  num: 0
}

# TypeScript 泛型 规范

泛型,软件工程中,我们不仅要创建一致定义良好的API,同时也要考虑可重用性。组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时提供了十分灵活的功能。

在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的语言。这样用户就可以以自己的数据类型来使用组件。

通俗理解就是,泛型就是解决类 接口 方法的重用性、以及对不特定数据类型的支持。

# 泛型规定函数传参

// 泛型支持不特定的数据类型, 要求传入的参数和返回的参数一致
function getData4<T>(value: T): T {
    return value
}
getData4<number>(123456)

# 泛型规定类传参

// 泛型类
class MinClass<Type> { // 接收一个泛型类型
    public list: Type[] = []; // 创建一个泛型数组, 元素类型为泛型中的类型
    add(num: Type):void { // 创建一个函数, num类型为泛型中的类型
        this.list.push(num)
    }
    min(): Type { // 返回一个属性, 返回类型为泛型中的类型
        return this.list.reduce(
            (total, item) => (total > item ? item : total),
            this.list[0]
        )
    }
}
const m = new MinClass<number | string>()
m.add(34214), m.add(312), m.add(33)
console.log(m.min);

# 泛型定义接口

// 第一种定义泛型的方法
interface ConfigFn {
    <Type>(value: Type): Type
}
const getData: ConfigFn= function<Type>(value:Type):Type {
    return value
}
getData<string>('number')
// 第二种定义泛型的方法
interface ConfigFn<Type> {
    <Type>(value: Type): Type
}
const getData = function<Type>(value:Type):Type {
    return value
}
const myGetData:ConfigFn<string> = getData

# 将类作为参数的泛型类

// 将类作为参数的类型约束
class User{
    username: string | indefined;
    password: string | indefined;
}
class MysqlDb {
    // 定义该方法只能传入User的实例
    add (user:User):boolean {
        console.log(user)
        return true;
    }
}
const u = new User()
u.username = '张山', u.password = '123456'
const Db = new MysqlDb()
Db.add(u)

// -----------------↓↓↓--------------

// 将类作为参数的泛型类
class User{
    username: string | indefined;
    password: string | indefined;
}
class MysqlDb<T> {
    // 定义该方法只能传入User的实例
    add (user:T):boolean {
        console.log(user)
        return true;
    }
}
const u = new User()
u.username = '张山', u.password = '123456'
const Db = new MysqlDb<User>() // 将泛型类传入MysqlDb中
Db.add(u)

# TypeScript 命名空间 规范

在代码量较大的情况下,为了避免各种变量命名产生冲突,可将相似功能的函数、类、接口等放置到命名空间内。

同Java的包、.Net的命名空间一样,TypeScript的命名空间可以将代码包括起来,支队外暴露需要在外部访问的对象。命名空间内对象或者属性需要通过export暴露出去,才能在外部访问

# 定义私有命名空间

namespace A {
    export const Animal = 70
}
// 两个命名空间不会产生冲突
namespace B {
    export const Animal = 60
}
console.log(A.Animal) // 70
console.log(B.Animal) // 60

# 引入外部文件命名空间

// 旧版引入命名空间
/// <reference path="./javascript-utils.ts"/>

// 新版引入命名空间
export namespace A {
    export const Animal = 70
}
// -----------↓-----------    
const {A} from './modules'

# TypeScript 装饰器 规范

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。可以这么说,装饰器监视一个方法,可以注入到类、方法、属性参数上扩展类、属性、方法、参数的功能。

常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器 装饰器的写法有:普通装饰器(无法传参)、装饰器工厂(可传参)

装饰器是过去几年中JS最大的成就之一,已是ES7的标准特性之一

装饰器的执行顺序:属性 > 方法 > 方法参数 > 类

# 普通类装饰器(无法传参)

// 定义一个普通装饰器
function logClass(params: any) {
    console.log(params);
    // params 就是当前类
    params.prototype.apiUrl = 'xxx';
    params.prototype.run = function () {
        console.log('--我是run方法--');
    }
}
@logClass // 对该构造函数使用装饰器
class HttpClient {
    // 添加动态签名
    [x: string]: any;
    constructor() { }
    getData() { }
}
const http = new HttpClient()
console.log(http.apiUrl);

# 类装饰器工厂(可传参)

// 装饰器工厂(可传参)
// 定义一个装饰器
function logClass(params: string) {
    return function (target: any) {
        console.log(target);
        // target 就是当前类
        target.prototype.apiUrl = 'xxx';
        target.prototype.run = function () {
            console.log('--我是run方法--');
        }
    }
}
@logClass('hello') // 对该构造函数使用装饰器
class HttpClient {
    // 添加动态签名
    [x: string]: any;
    constructor() { }
    getData() { }
}
const http = new HttpClient()
console.log(http.apiUrl);

# 装饰器重载类属性与方法

// 重载构造函数
function logClass(target: any) {
    // 继承原类, 重载类中的属性与方法
    return class extends target {
        apiUrl: any = '我是修改后的数据';
        getData() { console.log(this.apiUrl); }
    }
}
@logClass
class HttpClient {
    apiUrl: string | undefined
    constructor() {
        this.apiUrl = '我是构造函数里面的apiUrl'
    }
    getData() { console.log(this.apiUrl); }
}

const http = new HttpClient()
console.log(http.apiUrl);

# 属性装饰器的使用

// 定义一个属性装饰器
function logProperty(params: any) {
    return function (target: any, attr: any) {
        // target --> HttpClient.prototype
        // attr --> 'url'
        console.log(target);
        console.log(attr);
        target[attr] = params
    }
}
class HttpClient {
    // 在需要装饰的属性上方调用装饰器
    @logProperty('http:itying.com')
    public url: any | undefined
    constructor() { }
    getData() { }
}
const http = new HttpClient()

# 方法装饰器的使用

function get(params: any) {
    /** 方法装饰器
     * @param {string} target  HttpClient.prototype-->方法的原型对象
     * @param {string} methodName  成员的名称
     * @param {object} desc  成员方法的描述信息
     */
    return function (target: any, methodName: any, desc: any) {
        // 方法描述器具备类描述器特征, 可以添加, 修改类
        target.apiUrl = 'xxx'
        target.run = () => console.log('run');

        // 保存旧方法
        const oldMethod = desc.value
        // 对方法参数进行封装, 强制转换为String
        desc.value = function (...args: any[]) {
            return args.map(item => String(item))
            // 执行方法
            oldMethod.apply(this, args)
        }
    }
}
class HttpClient {
    public url: any | undefined
    // 在需要装饰的函数上方添加装饰器
    @get('http://www.itying.com')
    getData(...args: any[]) { console.log('我是getdata方法', args) }
}
const http = new HttpClient()
http.getData(123, 123, 123)

# 方法属性装饰器

function logParams(params: any) {
    /** 方法参数装饰器
     * @param {string} target  HttpClient.prototype-->方法的原型对象
     * @param {string} methodName  方法名称
     * @param {object} paramsIndex  参数当前的索引
     */
    return function (target: any, methodName: any, paramsIndex: any) {
        // 方法参数描述器具备类描述器特征, 可以添加, 修改类
        target.apiUrl = 'xxx'
        target.run = () => console.log('run')
    }
}
class HttpClient {
    public url: any | undefined
    // 在需要装饰的函数参数内传入装饰器
    getData(@logParams('xxx') uuid: any) {
        console.log('我是getdata方法', uuid)
    }
}
const http = new HttpClient()

# TypeScript 扩展语法

# 如何声明对象中某个属性

const obj = {
    age: <string|number>60 // 不推荐
    age: 60 as string|number
}
obj.age = "60"

# 使用 type 定义类型

// type 关键字可定义类型, 泛型, 等各种类型
type t1 = string;
const str:t1 = "666"

# 使用 keyof 定义键值

// 用于定义指定字符串
type k1 = keyof {
  小明: string;
  小红: string;
  小芳: string;
}
const str:k1 = "小白" // 报错

// 用于定义指定数组
type k2 = keyof {
  小明: string;
  小红: string;
  小芳: string;
}
const arr:[k2] = ["小明"]
// 继承某个泛型对象的属性
function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}
const arr: Array<k2> = ['小明']

# 使用 Extract 定义 value 值

interface Obj {
    name: string
}
const obj = {
    name: "毛先生"
}
function ():Obj[Extract<keyof Obj, string>] {
    return obj[name]
}

# 使用 & 合并接口声明

const a = { name: "", phone: "" } as { name:string } & { phone:string };
a.name
a.phone

# 使用 typeof 转换为类型

const state = {
  a: 123,
  b: 123
};
interface Store {
  state: typeof state;
}

# 用 P 代表泛型中的 key 值

type Computed<S> = {
  [P in keyof S]: () => S[P];
};

# Partial 代表接口属性可选

interface Person {
    name: string;
    age?: number;
}
type OptionalPerson = Partial<Person>

# Required 代表接口属性必有

interface Person {
    name: string;
    age?: number;
}
type RequiredPerson = Required<Person>

# Readonly 代表接口属性不可修改

interface Person {
    name: string;
    age?: number;
}
type ReadPerson = Readonly<Person>

# 使用类型中的三元判断是否为空

T extends null | undefined ? never : T

# 后缀 ! 代表值必有

dec(value!)

# 用于判断类型的返回值

const isRef = (v): v is Ref<any> => {/**/}
const isString = (v): v is string => {/**/}

# TypeScript 高级类型

# ReturnType 获取函数返回值

const getUserInfo = () => {};
type returnType = ReturnType<typeof getUserInfo>;

# Parameters 获取函数参数

const fuc = () => (a: number, b: number) => {};
type FucParams = Parameters<typeof fuc>;

# InstanceType 获取类返回值

class DrawPoster {/*...*/}
type DrawPosterType = InstanceType<typeof DrawPoster>;

# NonNullable 监测内容不为空

NonNullable<T>

# Omit 剔除不需要的对象属性

interface TState {
	name: string;
	age: number;
	like: string[];
}
interface TSingleState extends Omit<TState, "name" | "age"> {};

# Pick 保留需要的对象属性

interface TState {
	name: string;
	age: number;
	like: string[];
}
interface TSingleState extends Pick<TState, "name" | "age"> {};

# Exclude 排除可分配的类型

type Keys = 'Mr.Mao' | 'Sb.Long'
type Name = Exclude<key, 'Sb.Long'>
// Name > 'Mr.Mao'

# TypeScript 特殊指令

// @ts-expect-error: 忽略类型错误
// @ts-ignore: 忽略所有错误
// @ts-nocheck: 忽略文件所有内容
// @ts-check: 让 js 文件拥有注释类型