typescript学习(一)

227 阅读12分钟

数据类型

  • 原始类型
let bool: boolean = true
let num: number = 123
let str: string = 'abc'
  • 数组
let arr1: number[] = [1, 2, 3]
let arr2: Array<number | string> = [1, 2, 'a']
  • 元组
let tuple: [number, string] = [1, 'a']  // 元组中的元素类型和个数必须匹配
  • 函数
let add = (x: number, y: number): number => x + y
// 定义一个函数
let compute: (x: number, y: number) => number
// 
compute = (a, b) => a + b
  • 对象
let obj: { x: number, y: number } = { x: 1, y: 2 }
// 对象中包含属性和方法
let person: {
    name: string
    age: number
    say: () => void
    sing: (sing: string) => void
}
person = {
    name: 'abc',
    age: 18,
    say: function() {
    	console.log(this.name)
    },
    sing: function(sing: string) {
    	console.log(sing)
    }
}
  • symbol
let s1: symbol = Symbol()
  • undefined null
let un: undefined = undefined
let nu: null = null
  • void
let npReturn = () => {}
  • any
let x
  • never
// 抛错、死循环都会使得变量的类型为never类型
let error = () => {
	throw new Error('error')
}
let endless = () => {
	while(true) {}
}
  • 枚举
// 数字枚举
enum Role {
    Reporter = 1,
    Developer,
    Maintainer,
    Owner,
    Guest 
}
console.log(Role.Developer);   // 2
// 字符串枚举
enum Message {
    Success = '恭喜你,成功了',
    Fail = '抱歉,失败了'
}
// 异构枚举---数字枚举和字符串枚举
enum Answer {
    N,
    Y = 'Yes'
}
// 枚举成员
enum Char {
    // const
    a,
    b = Char.a,
    c = 1 + 3,
    // computed
    d = Math.random(),
    e = '123'.length
}
console.log(Char.a)   // 0
console.log(Char.b)   // 0
console.log(Char.c)   // 4
console.log(Char.d)   // 0.4829982047336656
console.log(Char.e)   // 3
// 常量枚举
const enum Month {
    Jan,
    Feb,
    Mar 
}
let month = [Month.Jan, Month.Feb, Month.Mar]
// 枚举类型
enum E {a, b}
enum F {a = 0, b = 1}
enum G {a = 'aa', b = 'bb'}
let e: E = 3
let f: F = 3
console.log(e)  // 3
console.log(E.a)  // 0
// e === f 不同的枚举成员不能相互比较
let e1: E.a = 1
let e2: E.b
console.log(e1)  // 1
// e1 === e2

let e3: E.a = 1
e1 === e3
let g1: G = G.b
let g2: G.a = G.a

接口

接口的作用,在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这心类里方法的实现细节,它只规定了这批类里必须提供某种方法,提供了这些方法的类就可以满足实际需要,typescript中接口类似与Java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。

  • 属性接口
// 方式一: ts中自定义传入参数对json数据进行约束
function printLabel(labelInfo:{label:string}):void{
    console.log(labelInfo);
}
printLabel({label:'aaa'})
// 方式二: 使用interface定义接口
interface FullName{
    firstName:string
    secondName:string
}
function printName(name:FullName){
    // 必须传入firstName secondName
    console.log(name.firstName + '----' + name.secondName)
}
var obj = {
    age: 20,
    firstName: 'zz', 
    secondName: 'sss'
} 
// 传入的参数必须包含firstName secondName
printName(obj)
  • 接口可选参数
interface FullName1 {
    firstName: string
    secondName?: string  // 使用 ? 标识是可选参数
}
function printName1(name: FullName1) {
    // 必须传入firstName secondName
    console.log(name.firstName + '----' + name.secondName)
}
printName1({
    firstName:'first',
    // secondName:'second'
})

  • 函数类型接口: 对传入的参数以及返回值进行约束
interface encrypt{
    (key:string,value:string):string
}

var md5:encrypt = function(key:string,value:string):string{
    // 模拟加密操作
    return key+value
}

console.log(md5('name','张三'));

  • 可索引接口:数组、对象的约束(不常用)
interface UserArr{
    [index:number]:string
}

var arr1:UserArr = ['111','aaaa']
console.log(arr1[0]);

interface UserObj{
    [index:string]:string
}

var arr2:UserObj = {name:'aaa', age: '18'}
console.log(arr2.name);

// 直接定义数组的方式
var arr:string[] = ['111','aaaa']
  • 类类型接口
interface Animal{
    name:string
    eat(str:string):void
}

class Dog implements Animal{
    name:string
    constructor(name:string){
        this.name = name
    }
    eat(){
        console.log(this.name);
    }
}
var d = new Dog('asdd')

d.eat()

  • 接口的扩展

interface Animal1{
    eat():void
}
interface Person extends Animal1{
    work():void
}

class Web implements Person {
    public name:string
    constructor(name:string) {
        this.name = name
    }
    eat(){
        console.log(this.name + '喜欢吃馒头');
    }
    work(){
        console.log(this.name + '不喜欢工作');
    }
}
var w = new Web('andy')
w.work()
  • 接口案例---ajax请求函数
interface Config{
    type:string
    url:string
    data?:string
    dataType:string
}

function ajax(config:Config){
    var xhr = new XMLHttpRequest
    // 请求类型 请求地址 异步请求
    xhr.open(config.type,config.url,true)
    xhr.send(config.data) 
    xhr.onreadystatechange = function(){
        if (xhr.readyState === 4 && xhr.status === 200){
            console.log('成功');
            if (config.dataType === 'json')
                console.log(JSON.parse(xhr.responseText));
            console.log(xhr.responseText);
        }
    }
}

ajax({
    type:'get',
    url:'https:www.baidu.com',
    dataType:'json'
})

下面是一个类的例子

class Person {
    name: string; // name属性 前面省略了 public 关键词
    constructor(n: string) {  // 构造函数
        this.name = n

    }
    getName(): string {
        return this.name
    }
    setName(name:string):void{
        this.name = name
    }
}
var p = new Person('andy')
p.setName('ddd')
alert(p.getName())
  • ts实现类的继承
class Person {
    name:string
    constructor(name:string){
        this.name = name
    }
    run():string{
        return `${this.name}在运动`
    }
}

class Web extends Person{
    constructor(name:string){
        super(name)
    }
}
var w = new Web('李斯')
alert(w.run())
  • 类的三种修饰符

public: 公有类型 在类里面、子类、外部都可以访问
protected:保护类型 在类里、子类可以访问
private: 私有类型 在类里面可以访问

class Person {
    public name: string
    // protected name: string
    // private name: string

    constructor(name: string) {
        this.name = name
    }
    run(): string {
        return `${this.name}在运动`
    }
}

class Web extends Person {
    constructor(name: string) {
        super(name)
    }
    run(): string {
        return `${this.name}在运动--子类`
    }
    work(): string {
        return `${this.name}在工作--子类`
    }
}
var w = new Web('李斯')
// 子类访问
alert(w.work())

var p1 = new Person('嬴政')

// 类里面
alert(p1.run())

// 外部访问
alert(p1.name)
  • 静态方法和属性---只能通过类名来访问
class  Person{
	public name: string
    static sex: string = '男' 
    constructor(name: string) {
    	this.name = name
    }
    run(): string {
    	return `${this.name}在运动`
    }
     static print():void{
        alert('print方法' + Person.sex)  // 通过类名访问类的静态方法
    }
}
class Web extends Person {
    constructor(name: string) {
        super(name)
    }
    run(): string {
        return `${this.name}在运动--子类`
    }
    work(): string {
        return `${this.name}在工作--子类`
    }
}
var w = new Web('李斯')
var p1 = new Person('嬴政')

Person.print()
  • 多态

父类定义一个方法不去实现,让继承它的子类去实现,每一个子类都有不同的表现

// 定义父类
class Animal{
    name:string
    constructor(name:string){
        this.name = name
    }
    eat(){}
}
// 定义子类实现父类中的方法
class Dog extends Animal{
    constructor(name:string){
        super(name)
    }
    eat(){
        return this.name + '吃粮食'
    }
}

class Cat extends Animal {
    constructor(name: string) {
        super(name)
    }
    eat() {
        return this.name + '吃老鼠'
    }
}

var dog = new Dog('dog')
alert(dog.eat())
  • 抽象类

抽象类,它是提供给其他类继承的基类,不能被直接实例化 用abstract关键字定义抽象类,抽象类中的额抽象方法不包含具体实现并且必须在派生类中实现

抽象方法只能放在抽象类中

抽象类和抽象方法用来定义的标准,Animal 这个类要求它的子类必须包含eat方法

abstract class Animal1 {
    public name:string
    constructor(name:string){
        this.name = name
    }
    abstract eat():any;
}

class Dog1 extends Animal {
    constructor(name:string){
        super(name)
    }
   eat(){
        alert(this.name + '吃粮食')
   }
}

var d = new Dog1('aaaa')
d.eat()

函数

  • 可选参数---可选参数必须配置到参数的最后
function getInfo(name: string, age?: number): string {
	if (age) 
    	return `${name}---${age}`
     return `${name}-----年龄保密`
}
alert(getInfo('zhangsan'))
  • 默认参数
function getInfo1(name: string, age: number = 20): string {
    if (age)
        return `${name}---${age}`
    return `${name}---年龄保密`
}
alert(getInfo1('zhangsan', 23))
  • 剩余参数
 function sum(a:number, b: number, c: number, d: number): number {
     return a + b +c + d
 }

//  alert(sum(1,2,3,4))
  • 三点运算符
function sum1(...result:number[]): number {
    var sum = 0
    for(var i = 0;i<result.length;i++)
        sum += result[i]
    return sum
}
function sum2(a: number,...result: number[]): number {
    var sum = a
    for (var i = 0; i < result.length; i++)
        sum += result[i]
    return sum
}
console.log(sum1(1, 2, 3, 4)) // ...result:number[]
// console.log(sum1([1,2,3,4])) // result:number[]

 console.log(sum2(1,3,5,2))
  • 函数重载

// 函数重载

function getInfo2(name: string) : string;
function getInfo2(age: number): string;
function getInfo2(str: any): any{
    if(typeof str === 'string')
        return '我叫' + str
    return '我的年龄是:' + str
}
alert(getInfo2('zs'))

泛型

泛型:软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性,组件不仅能够支持当前的数据类型, 同时也能支持未来的数据类型,这在创建大型系统时为你提供十分灵活的功能。 在像Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。 通俗理解泛型就是解决 类 接口 方法的复用性,以及对不特定数据类型的支持

定义一个函数返回值时string

function getData(val:string):string {
    return val
}

如果想要定义一个函数返回值为string或者number

function getData(val:string):string {
    return val
}

function getData2(val:number):number {
    return val
}

如果使用any类型就意味放弃了类型检查

function getData2(val:any):any {
    return val
}
  • 函数的泛型 这样就出现了泛型
function getData<T>(val:T):T {
    return val
}

console.log(getData<number>(123))
getData<string>('aaa')

function getData<T>(val:T):any {
    return 'val'
}

console.log(getData<number>(123))
  • 类的泛型
class MinClass<T> {
    public list:T[] = []

    add(num:T):void {
        this.list.push(num)
    }
    min():T {
        var minNum = this.list[0]
        for (let i = 0; i < this.list.length; i++) {
            if (minNum > this.list[i]) {
                minNum = this.list[i]
            }
        }
        return minNum
    }
}
var m = new MinClass<string>() // 指定类的类型为string
m.add('z')
m.add('b')
m.add('c')

console.log(m.min())
  • 案例
/*
  功能:定义一个操作数据库 支持 Mysql Mongodb
  要求:Mysql Mssql Mongodb功能一样 都有add delete get方法
  注意:约束统一的规范以及代码重用
  解决方案:需要约束规范所以要定义接口需要代码重用所以用到泛型
    1、接口:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范
    2、泛型通俗理解:泛型就是解决 类 接口 方法的复用性
*/
// 定义一个泛型接口
interface DBI<T> {
    add(info: T): boolean;
    update(info: T, id: number): boolean;
    delete(id: number): boolean;
    get(id: number): any[];
}

// 定义一个操作MySQL数据库的类 注意:要实现泛型接口 这个类也应该是一个泛型类
class MysqlDb<T> implements DBI<T> {
    add(info: T): boolean {
        console.log(info)
        return true
    }
    update(info: T, id: number): boolean {
        throw new Error("Method not implemented.");
    }
    delete(id: number): boolean {
        throw new Error("Method not implemented.");
    }
    get(id: number): any[] {
        throw new Error("Method not implemented.");
    }
    
}


// 定义一个操作Mssql数据库的类
class MsSqlDb<T> implements DBI<T> {
    add(info: T): boolean {
        throw new Error("Method not implemented.");
    }
    update(info: T, id: number): boolean {
        throw new Error("Method not implemented.");
    }
    delete(id: number): boolean {
        throw new Error("Method not implemented.");
    }
    get(id: number): any[] {
        throw new Error("Method not implemented.");
    }
    
}

// 操作用户表  定义一个User类和数据表做映射
class User {
    username:string | undefined;
    password:string | undefined;

}
var u = new User()
u.username = '张三'
u.password = '123456'

var oMysql = new MysqlDb<User>();  // 类作为参数来约束数据传入的类型

oMysql.add(u)

泛型工具

Partical

Partical<T>:将泛型中全部属性变为可选的。

type Partial<T> = {
    [P in keyof T]?: T[P]
}

举个例子:

type Animal = {
    name: string,
    category: string,
    age: number,
    eat: () => string
}

使用 Partial 包裹一下:

type PartOfAnimal = Partical<Animal>;
const dog: PartOfAmimal = { name: 'aaa' }

Record

Record<K, T>:将 K 中所有的属性值转化为 T 类型,常用来声明一个普通的 object 对象。

type Record<K extends keyof any, T> = {
    [key in K]: T
}

keyof any对应的类型为 number | string | symbol,也就是可以做对象键(索引index)的类型集合。 举个例子:

const obj: Record<string, string> = { 'name': 'mbg', 'tag': 'aaa' }
// 或
type T11 = Record<'a' | 'b' | 'c', Person>; // -> { a: Person; b: Person; c: Person; }

Pick

Pick<T, K>:将 T 类型中的 K 键列表提取出来,生成新的子键值对类型。

type Pick<T, K extends keyof T> = {
    [P in K]: T[P]
}

使用上面定义的 Animal ,看下 Pick 的使用。

const bird: Pick<Animal, 'name' | 'age'> = { name: 'bird', age: 1 }

Exclude

Exclude<T, U>:在 T 类型中,去除 T 类型和 U 类型的交集,返回剩余部分。

type Exclude<T, U> = T extends U ? never : T

举个例子:

type T1 = Exclude<'a' | 'b', 'a' | 'b' | 'c'> // 'c'

type T2 = Exclude<string | number | (() => void), Function> // string | number

Omit

Omit<T, K>:去除类型 T 中包含 K 的键值对。

type Omit = Pick<T, Exclude<keyof T, K>>

在定义中,第一步先从 T 的key中去掉与 K 重叠的key,接着使用 Pick 把 T 类型和剩余的key提取出来。 使用 Animal 的举例:

const OmitAnimal: Omit<Animal, 'name' | 'age'> = { category: 'lion', eat: () => { console.log('eat')} }

ReturnType

ReturnType<T>:获取 T 类型(函数)对应的返回值类型。

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

稍微简化下:

type ReturnType<T extends func> = T extends () => infer R ? R : any

通过使用 infer 推断返回值类型,然后返回此类型。 举个例子:

function foo(x: string | number): string | number

type FooType = ReturnType<foo>  // string | number

Required

Required<T>:将类型 T 中所有的属性变为必选项。

type Required<T> = {
    [P in keyof T]-?: T[P]
}

-?可以理解为 TS 把?可选属性减去的意思。

除了这些以外,还有很多的内置的类型工具,TypeScript Handbook,同时 Github 上也有很多第三方类型辅助工具,如utility-types等。

模块

模块的概念(官方):
关于术语的一点说明:请务必注意一点,typescript1.5里术语已经发生了改变。内部模块现在称作命名空间。外部模块现在简称模块在其自身的作用域中执行,而不是在全局作用域里。

自己理解:
    我们可以把一些公共的功能单独抽离成一个文件作为一个模块
    模块里的变量 函数 类等默认是私有的,如果我们要在外部访问里面的数据(变量、函数、类)
    我们需要通过export暴露模块里面的数据(变量 函数 类)
    暴露后通过import引入模块就可以使用里面暴露的数据
export  function getData():any[] {
     console.log('获取数据库中的数据')
     return [
         {title: '123'},
         {title: '245'},
     ]
 }
 
import { getData } from './modules/db'

getData()

命名空间

命名空间:

在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内。同Java的包,Typescript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象。 命名空间内的对象通过export导出。

命名空间和模块的区别:

命名空间:内部模块,主要用于组织代码,避免命名冲突。

模块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间

在一个ts文件中使用命名空间,避免变量命名冲突

namespace A{
    interface Animal{
        name: string;
        eat(): void;
    }
    
    export class Dog implements Animal{
        name: string;
        constructor(theName: string){
            this.name = theName
        }
        eat() {
            console.log(`${this.name}吃狗粮`)
        }
    }
    export class Cat implements Animal {
        name: string;
        constructor(theName: string){
            this.name = theName
        }
        eat() {
            console.log(`${this.name}吃猫粮`)
        }
    }
}


let dog = new A.Dog('狗狗')
dog.eat()

在外部导出

export namespace B{
    interface Animal{
        name: string;
        eat(): void;
    }
    
    export class Dog implements Animal{
        name: string;
        constructor(theName: string){
            this.name = theName
        }
        eat() {
            console.log(`${this.name}吃狗粮`)
        }
    }
    export class Cat implements Animal {
        name: string;
        constructor(theName: string){
            this.name = theName
        }
        eat() {
            console.log(`${this.name}吃猫粮`)
        }
    }
}

import {B} from './modules/a';
let cat = new B.Cat('猫猫')
cat.eat()

装饰器

装饰器是一种特殊类型的声明,它能够被附加到类声明、属性、方法或形参上,可以修改类的行为。

通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。

常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器

装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)

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

  • 若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:
{
    "compilerOptions": {
        "target": "ES5",
        "experimentalDecorators": true
    }
}
  • 普通装饰器(无法传参)
function sealed(target) {
    // do something with "target" ...
}
  • 装饰器工厂(可以传参)
function color(value: string) { // 这是一个装饰器工厂
    return function (target) { //  这是装饰器
        // do something with "target" and "value"...
    }
}

类装饰器

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

传入一个参数

// 使用普通装饰器
function logClass(target: any) {
    console.log(target)
    target.prototype.apiUrl = '动态扩展的属性'
    target.prototype.run = function() {
        console.log('我是一个run方法')
    }
}

@logClass
class HttpClient{
    constructor() {

    }
    getData() {

    }  
} 
var http:any = new HttpClient()
console.log(http.apiUrl)
http.run()
// 装饰器工厂
function logClass1(params: string) {
    return function(target: any) {
        console.log(target)
        console.log(params)
        target.prototype.apiUrl = '动态扩展的属性'
        target.prototype.run = function() {
        console.log('我是一个run方法')
    }
    }
}

@logClass1('hello')
class HttpClient1{
    constructor() {

    }
    getData() {

    }  
} 
var http1:any = new HttpClient1()
console.log(http1.apiUrl)
http1.run()
  • 重载构造函数的例子
function logClass2(target: any){
    console.log(target)
    return class extends target{
        apiUrl2: any = '我是修改过的apiUrl'
        getData() {

        }
    }
}
@logClass2
class HttpClient2{
    public apiUrl2: string | undefined
    constructor() {
        this.apiUrl2 = '我是构造函数的api'
    }
    getData() {
        console.log(this.apiUrl2)
    }  
} 

var http2 = new HttpClient2()
http2.getData()

属性装饰器

属性装饰器表达式会在运行时当作函数调用,传入下列2个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字---装饰的属性名称

function logProperty(params: any) {
    return function(target: any, attr: any) {
        console.log(target)  // 原型对象
        console.log(attr)
        target[attr] = params
    }
}

class HttpClient3{
    @logProperty('http://www.baidu.com')
    public url: any | undefined
    constructor() {

    }
    getData() {
        console.log(this.url)
    }  
} 
var http3:any = new HttpClient3()
http3.getData()

方法装饰器

它会被应用到方法的属性描述符上,可以用来监视、修改或者替换方法定义

方法装饰器会在运行时传入下列三个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、成员的名字
3、成员的属性描述符
注意  如果代码输出目标版本小于ES5,属性描述符将会是undefined。

如果方法装饰器返回一个值,它会被用作方法的属性描述符。

注意  如果代码输出目标版本小于ES5返回值会被忽略。

function get(params: any){
    return function(target:any,methodName:any,desc: any){
        console.log(target);
        console.log(methodName);
        console.log(desc);   
        // 可以使用类装饰器来实现
        target.apiUrl = params // 可以扩展类中的属性和方法和类装饰器一样
        target.run = function(){
            console.log('run')
        }
        
        // 修改getData方法,把装饰器方法里面传入的所有参数修改为string类型
        // 1、保存当前的方法
        var oMethod = desc.value
        desc.value = function(...args: any[]) {  // 扩展getData方法
            args = args.map(item => String(item))
            console.log(args)
            oMethod.apply(this, args) //再调用类中原有的getData方法, 同时将扩展的结果传入
        }
    }
}

class HttpClient4{
    // public url: any | undefined
    constructor() {

    }
    @get('hello')
    getData() {
        console.log('getData')
    }  
} 

var http4: any = new HttpClient4()
console.log(http4.apiUrl)
http4.run()
http4.getData(123, 'xxx')

方法参数装饰器

参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型添加一些元素数据,传入下列3个参数:

1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、方法的名字
3、参数在函数参数列表中的索引

function logParams(params: any){
    return function(target: any, methodName: any, paramsIndex: any) {
        console.log(target);
        console.log(methodName);
        console.log(paramsIndex);  
        target.apiUrl = params 
    }
}

class HttpClient5{
    public url: any | undefined
    constructor() {

    }
    getData(@logParams('www.baidu.com') uuid: any) {
        console.log(uuid)
    }  
} 

var http5: any = new HttpClient5()
http5.getData(12345)
console.log(http5.apiUrl)

装饰器的执顺序:

属性装饰器
方法装饰器
方法参数装饰器
类装饰器
如果有多个从后往前执行

类型保护

Typescript能够在特定的区块中保证变量属于某种确定的类型

可以在此区块中放心地引用此类型的属性,或者调用用此类型的方法

enum Type { Strong, week }
class Java {
    helloJava() {
        console.log('hello java')
    }
    java: any
}

class Javascript {
    helloJavascript() {
        console.log('hello javascript')
    }
    javascript: any
}

let lang = type === Type.Strong ? new Java() : new Javascript()此时,lang的类型不确定,无法准确的调用对应的属性和方法。

解决方法

  • 方式一:使用断言跳过类型检查
function getLanguage(type: Type, x: string | number) {
    let lang = type === Type.Strong ? new Java() : new Javascript()
    if((lang as Java).helloJava) {
        (lang as Java).helloJava()
    }else{
        (lang as Javascript).helloJavascript()

    }
    return lang
}
  • 方法二:使用instanceof
function getLanguage(type: Type, x: string | number) {
    let lang = type === Type.Strong ? new Java() : new Javascript()
    // instanceof
    if(lang instanceof Java) {
        lang.helloJava()
    }
    else{
        lang.helloJavascript()
    }
    return lang
}
  • 方法三:使用in
function getLanguage(type: Type, x: string | number) {
    let lang = type === Type.Strong ? new Java() : new Javascript()
    // in
     if('java' in lang){
        lang.helloJava()
    }else{
        lang.helloJavascript()
    }
    return lang
}
  • 方法四:使用typeof
function getLanguage(type: Type, x: string | number) {
    let lang = type === Type.Strong ? new Java() : new Javascript()
    // typeof
    if (typeof x === 'string') {
        x.length
    }else{
        x.toFixed(2)
    }
    return lang
}
  • 方法四:定义一个函数isJava
function isJava(lang: Java | Javascript): lang is Java {
    return (lang as Java).helloJava !== undefined
} 

function getLanguage(type: Type, x: string | number) {
    let lang = type === Type.Strong ? new Java() : new Javascript()
    //
    if (isJava(lang)){
        lang.helloJava()
    }else{
        lang.helloJavascript()
    }
    return lang
}

高级类型

交叉类型

interface DogInterface{
    run(): void
}
interface CatInterface{
    jump(): void
}

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

联合类型

let a: number | string = 1
let b: 'a' | 'b' | 'c' = 'a'

class Dog implements DogInterface{
    run(){}
    eat(){}
}

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

}
enum Master { Boy, Girl }
function getPet(master: Master) {
    let pet = master === Master.Boy ? new Dog() : new Cat()
    pet.eat()  // 只能取到两个类共有的方法
    return pet
}
  • 可区分的联合模式---利用两种类型的共有属性,来创建不同类型的保护区块
interface Square {
    kind: 'square';
    size: number
}
interface Rectangle {
    kind: 'rectangle';
    width: number;
    height: number
}
interface Circle {
    kind: 'circle';
    r: number
}

type Shape = Square |  Rectangle | Circle

function area(s: Shape) {
    switch (s.kind) {
        case 'square':
            return s.size * s.size
        case "rectangle":
            return s.height * s.width
        case 'circle':
             return Math.PI * s.r ** 2
        default:
            return ((e: never) => new Error(e))(s)
    }
}

console.log(area({kind: 'circle', r: 1}))

索引类型

实现对object类型的查询和访问

  • keyof T 类型查询操作符
interface Obj {
    a: number,
    h: string
}
let key: keyof Obj
// key的类型为联合类型: 'a' | 'h'
  • T[k] 索引访问操作符
let value: Obj['a']

// value 的类型为number
  • T extends U
  • 索引类型的使用
let obj1 = {
    a: 1,
    b: 2,
    c: 3
}
// T 为 Obj 类型  K为 'a' | 'b' | 'c'
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][]{
    return keys.map(key => obj[key])
} 

console.log(getValues(obj1, ['a', 'b']))

console.log(getValues(obj1, ['c', 'd']))  // 此时,Obj中不存在 'd' 所以会报错

映射类型

TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。

// 定义一个接口
interface Obj1{
    a: string,
    b: number,
    c: boolean

}
  • 将Obj1中的属性映射为只读类型

type readonlyObj1 = Readonly<Obj1>

/*
type readonlyObj1 = {
    readonly a: string;
    readonly b: number;
    readonly c: boolean;
}
*/
  • 将Obj1中的属性映射为可选类型
type PartialObj1 = Partial<Obj1>
/*
type PartialObj1 = {
    a?: string | undefined;
    b?: number | undefined;
    c?: boolean | undefined;
}
*/
  • 映射为obj1的子集
type PickObj1 = Pick<Obj1, 'a' | 'b'>
/*
type PickObj1 = {
    a: string;
    b: number;
}
*/

以上几种映射,也被称作为同态:只作用于obj1属性,而不作用于其他属性

  • Record 映射

Record并不需要输入类型来拷贝属性,所以它不属于同态。非同态类型本质上会创建新的属性,因此它们不会从它处拷贝属性修饰符。

type RecordObj1 = Record<'x'|'y', Obj1>
/*
type RecordObj1 = {
    x: Obj1;
    y: Obj1;
}
*/

条件类型

条件类型的实现方式:T extends 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 T1 = "string"
type T2 = TypeName<string[]>  // type T1 = "string"

// (A | B) extends U ? X : Y  ===> (A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | string[]>  // type T3 = "string" | "object"

// 过滤掉T继承U 的类型
type Diff<T, U> = T extends U ? never : T
type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'>  // type T4 = "b" | "c"
// Diff<'a', 'a' | 'e'> | Diff<'b', 'a' | 'e'>  | Diff<'c', 'a' | 'e'>
// nerve | 'b' | 'c'
// 'b' | 'c'


// 去除不需要的类型undefined null
type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null>  // Diff<'a' | 'b' | 'c', 'a' | 'e'> 

TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:

  • Exclude<T, U> -- 从T中剔除可以赋值给U的类型。--- T4
  • Extract<T, U> -- 提取T中可以赋值给U的类型。 --- T6
  • NonNullable<T> -- 从T中剔除null和undefined。--- T5
  • ReturnType<T> -- 获取函数返回值类型。--- T7
  • InstanceType<T> -- 获取构造函数类型的实例类型。
type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'>   // type T6 = "a"

// ReturnType<T> 获取一个函数返回值的类型
type T7 = ReturnType<() => string>  // type T7 = string

声明合并

“声明合并”是指编译器将针对同一个名字的两个独立声明合并为单一声明。 合并后的声明同时拥有原先两个声明的特性。 任何数量的声明都可被合并;不局限于两个声明。

接口合并

interface A {
    x: number;
    foo(bar: number): number // 5
    foo(bar: 'a'): number  // 2
}

interface A{
    y: number
    foo(bar: string): string  // 3
    foo(bar: number[]): number[]  // 4
    foo(bar: 'b'): number  // 1
}
// 此时函数声明的列表顺序为接口之间就近原则 接口内部从上往下, 但是函数声明存在字面量会存在提升
let ab: A = {
    x: 1,
    y: 2,
    foo(bar: any) {
        return bar
    }
}

命名空间合并

namespace Animals {
    export class Zebra { }
}

namespace Animals {
    export interface Legged { numberOfLegs: number; }
    export class Dog { }
}

// 等同于:

namespace Animals {
    export interface Legged { numberOfLegs: number; }

    export class Zebra { }
    export class Dog { }
}

命名空间---函数合并

此时,相当于给函数添加了一个静态属性

function Lib() {}

namespace Lib {
    export let version = 1.0
}

console.log(Lib.version)

命名空间---类合并

此时,相当于给类添加了静态属性

class C {}

namespace C {
    export let status = 1
}

console.log(C.status)

命名空间---枚举合并

扩展枚举的属性

enum Color {
    Red,
    Blue 

}

namespace Color {
    export function mix() {}
}

console.log(Color)
// { '0': 'Red', '1': 'Blue', Red: 0, Blue: 1, mix: [Function: mix] }

模块扩展

第三方类库扩展

import m from 'moment'
declare module 'moment' {
  export function myFun(): void
}

m.myFun = () => {}

全局扩展

/*
* globalLib 全局的类库名
*/
declare global {
  namespace globalLib {
    function doAnything(): void
  }
}
globalLib.doAnything = () => {}