2021不得不学的Typescript

2,100 阅读7分钟

ts作为一门新技术,这两年是越来越火,经过了一段时间的学习和理解之后,写了这篇文章,通过记录ts的核心知识点来带大家轻松掌握typescript,希望能够打动屏幕面前的你。

Typescript基础语法

TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。接下来我们简单介绍一下这几种类型的用法.

基础类型

// 布尔类型
let isFlag:boolean = true

// 数值类型
let myNumber:number = 24

// 字符串类型
let str:string = 'ykk'

// 数组类型, 有两种表示方式,第一种可以在元素类型后面接上[],表示由此类型元素组成的一个数组
let arr:number[] = [1,2,3]
//当数组存在不同类型时
let arr1: (number | string)[] = [1, '2', 3]

// 数组类型, 使用数组泛型(有关泛型后面会详细分析)
let arr:Array<number> = [4,5,6]

// 元组类型, 允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
let yuan: [string, number];
// 初始化yuan
yuan = ['yuan', 12]; // 正确
yuan = [12, 'yuan']; // 错误

// 枚举类型, 可以为一组数值赋予友好的名字
enum ActionType { online, offline, deleted }
let action:ActionType = ActionType.offline    // 1

// any, 表示任意类型, 可以绕过类型检查器对这些值进行检查
let color:any = 1
color = 'red'

// void类型, 当一个函数没有返回值时,通常会设置其返回值类型是 void
function getName(): void {
    console.log("This is my name");
}

// object类型, 表示非原始类型,也就是除number,string,boolean,symbol,null或undefined之外的类型
let obj:object;
obj = {num: 1}

// 对象类型也可以写成
const t = {
    name:string,
    age:number
} = {
    name:'ykk',
    age:18
}

// 函数类型也属于一种对象类型(该实例返回值必须是number类型)
const getTt:() => number = () => {
    return 123;
}

//never类型(永远不会执行never之后的逻辑)
function errorEmitter(): never {
    throw new Error();
    console.log(123)
}

接口(Interface)

interface Person {
    name: string;
    age: number;
    phone: number;
}

let man:Person = {
    name: 'ykk',
    age: 18,
    phone: 13711111111
}

类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。其次我们还可以定义可选属性只读属性可选属性表示了接口里的某些属性不是必需的,所以可以定义也可以不定义。·可读属性·使得接口中的某些属性只能读取而不能赋值,如下:

interface Person {
  name: string;
  age?: number;
  readonly phone: number;
}

在实际场景中, 我们往往还会遇到不确定属性名和属性值类型的情况, 这个时候我们可以利用索引签名来设置额外的属性和类型, 如下:

interface Person {
  name: string;
  [propName: string]: any; //这里表示除了有name之外还可以有其他的任何属性,但是属性名必须是string类型,属性值任意类型都可以
}

当然接口也可以进行继承

//此时human这个interface就拥有Person的所有属性,并且在调用的时候也必须满足Proson的要求,否则就会报错
interface human extends Person {
    weight:55
}

和js的class一致, typescript的类有公共(public)私有(private)受保护(protected)的访问类型。

  • public 在TypeScript里,成员都默认为 public,表示允许它在定义类的外部被调用
  • private 表示它能在定义的类内使用,不能在该类的外部使用
  • protected 和private类似, 但是protected允许在类内及继承的子类中使用

示例:

    class Person {
        public name:string = 'ykk';
        private age:number = 18;
        protected weight:number = 55;
        constructor(){

        }
    }
    
    class Man extends Person {
        public say(){
            return this.weight
        }
    }
    
    let p = new Person()
    let m = new Man()
    console.log(p.name) //ykk
    console.log(p.age) //报错age是private属性
    console.log(m.say()) //55

由于在js中,gettersetter不能直接使用,我们需要通过一个Object.defineProperty来定义触发,那么在ts中就简单多了在类中直接能声明:

class Person {
    private _food: string = 'apple'
    get food() {
        return this._food
    }
    set food(name: string) {
        this._food = name
    }
}
let p = new Person()
console.log(p.food) //apple
p.food="cookie" //这里重新设置food

typescript中static这个关键字是把这个方法或者属性直接挂在类上,而不是挂在new出来的实例。设计模式中经典的单例模式,用它最合适不过了!

class Demo {
    private static instance: Demo;
    peivate contructor(public name:string){}
    
    static getInstance(){
        if(!this.instance){
            this.instance = new Demo('ykk')
        }
        return this.instance;
    }
}

const demo1 = Demo.getInstance();
const demo2 = Demo.getInstance();
console.log(demo1.name)
console.log(demo2.name)

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法, 这里需要注意的是不能创建一个抽象类的实例

abstract class MyAbstract {
    constructor(public name: string) {}
    say(): void {
        console.log('say name: ' + this.name);
    }
    abstract sayBye(): void; // 必须在派生类中实现
}

class SubMyAbstract extends MyAbstract {
    constructor() {
        super('ykk'); // 在派生类的构造函数中必须调用 super()
    }
    sayBye(): void {
        console.log('bye');
    }
    getOther(): void {
        console.log('loading...');
    }
}

let department: MyAbstract; // 允许创建一个对抽象类型的引用
department = new SubMyAbstract(); // 允许对一个抽象子类进行实例化和赋值
department.say();
department.sayBye();

department = new MyAbstract(); // 错误: 不能创建一个抽象类的实例
department.getOther(); // 错误: 方法在声明的抽象类中不存在

Typescript进阶语法

联合类型和类型保护

所谓联合类型是用于限制传入的值的类型只能是 | 分隔的每个类型,所以 number | string | boolean表示一个值可以是 number, string,或 boolean。例如:

interface person1 {
  name: string;
  age: number;
}

interface person2 {
  hby: string;
  age: number;
}
let man:person1 | person2

如果一个值是联合类型,那么我们只能访问它们中共有的部分(共有的属性与方法),由于只能访问共有,导致我们在想要访问某一个的时候ts会提示报错,这时我们就需要类型保护

let man:person1 | person2;
man = {
    name: 'ykk',
    age: 18,
    hby: 'basketball'
}
//使用as直接断言,告诉ts在哪里去找
if((me as person1).name) {
  console.log((me as person1).name); 
}

if((me as person1).name) {
  console.log((me as person2).hby); 
}
//使用in
  if(('name' in me)) {
    console.log(me.name); 
  }
  
  if('hby' in me) {
    console.log(me.hby); 
  }
  //使用typeof
 function add(one:number|string,two:number|string){
     if(typeof one=="string"||typeof two=="string"){
         retrun `${one}${two}`
     }
     retrun one+two
 }
 //使用instanceof
  class a{
      num:1
  }
function b(obj:object|a){
    if(obj instanceof a){
        retrun obj.num
    }
}

泛型

什么是泛型呢,我的理解就是泛指的类型,那他在ts中应该怎么写呢?

//定义是用尖括号表示一个变量
function iSay<T>(arg: T): T {
    return arg;
}
// 调用的时候去声明类型
let come = iSay<number>(123); 

当然了泛型有多种使用方式,接下来咱们一一探索。

函数泛型
//传入一个数组
function say<T>(arr:T[]){
    ...
};
say<number>([11,22,33]);

//传入多个泛型
function say<T, F>(name:T, age:F){
    ...
};
say<string, number>('ykk', 18);
类中的泛型
class say<T>{
    constructor(name:T){
        ...
    }
}
var t = new say<string>("1")
泛型的继承
class say<T extends number|string>{
    constructor(one:T){
        ...
    }
}
var t = new say<string>("1") //这时候表示泛型只能是number类型或者string类型其中的一种,否则会报错
泛型中使用keyof

泛型中使用keyof顾名思义就是遍历一个interface,并且每次的泛型类型就是当前interface的key.

interface persons {
    name:string,
    age:number,
    get:boolean
}
var p = {
    name:'ykk',
    age:18,
    get:false
}
function add<T extends keyof persons>(key:T):persons[T]{
    return p[key]
}
//如此一来我们便能知道返回值的准确类型了
var p1 = add('name')
console.log(p1) //ykk

命名空间

ts在我们使用的时候如果用面向对象的方式声明多个类生成实例的时候,你会发现在全局就会多出几个实例,这样就会导致全局污染,如此一来,我们便需要namespace这个关键字,来防止全局污染。

namespace Main{
    class circle {
        ...
    }
    
    class rect {
        ...
    }
    
    //如果想要导出给外部使用,需要导出
    export class san{
        ...
    }
}
//这样在全局只会有一个Main供我们使用了

声明全局变量

对于使用未经声明的全局函数或者全局变量, typescript往往会报错。最常见的例子就是我们在引入js文件的时候,往往会报下面的错误:

1621932637(1).jpg 这个时候有两种解决方式:

  • 按照提示执行相对于的npm install @types/xxx的命令。
  • 可以在对应位置添加xxx.d.ts文件, 并在里面声明我们所需要的变量, ts会自动检索到该文件并进行解析,如下:
//定义一个全局类型, 并编写相应的逻辑让ts识别相应的js语法
declare var superagent;
...

当然我们还可以定义更多有用的声明, 这里就不一一举例了.

写在最后

ts的核心基础基本讲完了,由于是笔记类型的文章,所以有很多值得大家一起探讨的地方,希望大家多多提出宝贵的意见。当然了ts还有很多更加高深的用法实现,包括和vue, react的联合使用,后面也会去加油学习,fighting!!!