TypeScript简单入门

603 阅读6分钟

1、TypeScript是什么?

TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。 在线TypeScript演练场

1.1 TypeScript与javascript区别

image.png

1.2 JavaScript 数据类型

String、Number、Boolean、Object(Array、Function)、Symbol、undefined、null

1.3 TypeScript 新增数据类型

void、any、never、元组、枚举、高级类型

2、基础类型

字符串类型

我们使用 string 表示文本数据类型。 和 JavaScript 一样,可以使用双引号 " 或单引号 ' 表示字符串, 反引号 ` 来定义多行文本和内嵌表达式。

let str: string = 'abc'

数字类型

和 JavaScript 一样,TypeScript 里的所有数字都是浮点数。这些浮点数的类型都是 number。 除了支持十进制和十六进制字面量,TypeScript 还支持 ECMAScript 2015 中引入的二进制和八进制字面量。

let a: number = 6 //十进制
let hexA: number = 0xf00d //十六进制
let binaryA: number = 0b1010 //二进制
let octalA: number = 0o744   //八进制

布尔类型

我们使用 boolean 表示布尔类型,表示逻辑值 true / false

let bool: boolean = true

Null & Undefined

null 表示对象值缺失,undefined 表示未定义的值。

let un: undefined = undefined
let nu: null = null

void

用于标识方法返回值的类型,表示该方法没有返回值。

function noReturn (): void {
  console.log('No return value')
}
const a =():void=>{console.log('No return value')}
  • undefined 并不是保留字段可以被赋值,所以设置undefined时,建议使用 void 0

任意类型any

声明为 any 的变量可以赋予任意类型的值。

let x: any
x = 1
x = 'a'
x = {}

let arr: any[] = [1, 'a', null]

never

never 类型表示的是那些永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;变量也可能是 never 类型,当它们被永不为真的类型保护所约束时。

never 类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是 never 的子类型或可以赋值给 never 类型(除了 never 本身之外)。 即使 any 也不可以赋值给 never

// 返回never的函数必须存在无法达到的终点 
function error(message: string): never {
throw new Error(message); 
}
  • 类型推断:变量在声明时并未赋值,类型推断为 any

若其他类型需要被赋值为 nullundefined 时, 在 tsconfig.json 中将 scriptNullChecks 设置为 false。或者 使用联合类型。

Symbol

symbol 类型的值是通过 Symbol 构造函数来创建

let s: symbol = Symbol()

3、复杂类型

数组 array

定义数组的方式有二种:
第一种:可以在元素类型后加上 []
第二种:可以使用数组泛型 Array<元素类型>。 此外,在元素类型中可以使用联合类型。 符号 | 表示或。

let arr1: number[] = [1, 2, 3]
let arr2: Array<number> = [1, 2, 3]
let arr3: Array<number | string> = [1, 2, 3, 'a']

//数组的元素必须是规定好的类型,否则就会报错
let a:number[] = [1,2,3,'4'] // error '4'string不是number类型

元组 tuple

用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型必须相同。

let tuple:[string,number] = ['ali',18]

可以调用数组 push 方法添加元素,但并不能读取新添加的元素。

tuple.push('a')
console.log(tuple) // ['ali',18, "a"]
tuple[2] // Error: Tuple type '[number, string]' of length '2' has no element at index '2'.

对象 object

直接 let a: object; 是不是没有什么意义,因为 js 中对象太多了

我们可以这样来用

let obj:{age:number,is:boolean};
obj = {
    age:18,
    is:true,
}

属性必须在类型 { name: string; age: number; }

函数 funtion

我们要规定函数的 输入类型返回类型

() 里面是输入的形参的 类型

=> 代表是 返回值 的类型

: 后面的都是声明类型,和代码逻辑没有什么关系

传入多余的参数会报错,可选参数用?表示,注意:可选参数后不能再加参数了

//第一种写法
function sum(a:number,b:number,c?:number):number{
   return a+b;
}

//第二种写法
const sum = (a:number,b:number):number=>a+b

console.log(sum(1,2,3))

interface 来描述函数类型

注意一点 之前用的 => 来表示返回值类型

这里是在 (): 返回值类型

interface Isum{
    (a:number,b:number):number;
}

const newSum:Isum = sum

接口 interface

它能很方便的帮我们定义 Ojbect 类型,可以灵活的处理属性多个类型及是否必选存在/可选/只读

interface 属性中
表示不是必要的属性
|表示联合类型:确定是哪几种类型后可用这个
readonly 不可改变的,定义完后就不能修改,跟 const 有点像,不过 const 是针对变量, readonly 是针对属性

interface Iobj {
    a:stringnumber;//可以是字符串或者数值
   readonly age:number;
   love?:string;
}
  
let obj:Iobj = {
    a:'jom',
    age:18,
}  

obj.age = 30 //Cannot assign to 'age' because it is a read-only property

联合类型 union types

对于一个变量的类型可能是几种类型的时候我们可以使用 any ,但是 any 的范围是不是有点大了,尽量避免使用, 我们如果知道是其中的哪几种类型的话,我们就可以使用 联合类型| 分隔 注意:在没有赋值之前,只能访问共同的方法、属性,比如下面的例子,number 没有length 属性

let a:number|string;

a.length //Property 'length' does not exist on type 'string | number'.\
           Property 'length' does not exist on type 'number'

a='haha';
a=1;

断言 type inference

在上面例子中,我们声明了一个属性类型为 number | string 它不能调用 length 方法
这是机器没法判断这个类型,但是我们可以指定类型 string 这里我们就可以用到 类型断言 类型断言有两种形式: <类型>as

function sum(a:number|string):void{
  const str = a as string  //as
  const str = <string>a    //<>
  console.log(str.length) 
}

两种形式是等价的。 但当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。

类型守卫 type guard

遇到联合类型的时候,使用 类型守卫可以 缩小范围

实现以下和上面一样的方法

function sum(a:number|string):void{
    if(typeof a ==='string'){
        console.log(a.length) 
    }
}

类型守卫 除了 typeof 之外 ,还有 instanceofin

4、枚举

我们使用 enum 表示枚举类型。 枚举成员值只读,不可修改。 一般适用于声明一个常量

数字枚举

数字枚举支持反向映射

初始值为 0, 逐步递增,也可以自定义初始值,之后根据初始值逐步递增。

enum fiveLamp {
    white,
    red,
}
console.log(fiveLamp.white) // 0
console.log(fiveLamp[1]) // 'red'
console.log(five)//{ "0": "white", "1": "red", "white": 0, "red": 1 }

字符串枚举

字符串枚举不支持反向映射

enum Message {
  Success = '成功',
  Fail = '失败'
}

常量枚举

在枚举关键字前添加 const,该常量枚举会在编译阶段被移除。

const enum Month {
  Jan,
  Feb,
  Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]

编译后:

"use strict";
var month = [0 /* Jan */, 1 /* Feb */, 2 /* Mar */]; // [0 /* Jan */, 1 /* Feb */, 2 /* Mar */]

外部枚举

外部枚举(Ambient Enums)是使用 declare enum 定义的枚举类型。

declare enum Month {
  Jan,
  Feb,
  Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]

编译后:

"use strict";
let month = [Month.Jan, Month.Feb, Month.Mar];

declare 定义的类型只会用于编译时的检查,编译结果中会被删除。所以按照上述例子编译后的结果来看,显然是不可以的。因为 Month 未定义。

  • declareconst 可以同时存在

5、类

ES6 中就有 类的概念了,在 TS 中对类添加一些功能
下面是个简单的例子,定义了一个Parent的父类,再定义了它的一个子类Child

3个访问修饰符:

Public:修饰的属性或方法是共有的 在 任何地方 都能访问

Private:修饰的属性或方法是私有的 只有 本类 中访问

Protected:修饰的属性或方法是受保护的 在 本类子类中 能够访问

比如指定父类中 age 访问权限为 private ,只能在 Parent 访问,子类访问会出错

class Parent {
    public name:string
    private age:number
    constructor(name:string){
        this.name = name
    }
    play(){
        console.log(`${this.name}在玩`)
    }
}

class Child extends Parent{
    private child_age:number
    constructor(name:string){
        super(name)
        this.child_age = super.age;//报错Property 'age' is private and only accessible within class 'Parent'.
    }
}

我们可以设置访问权限为 protected ,这样子类就能访问

静态属性 static

上面的 name age 这两个属性都是通过 实例 去访问的

使用 static 修饰的属性是通过 类 去访问,是每个实例共有的

同样 static 可以修饰 方法,用 static 修饰的方法称为 类方法,可以使用类直接调用

class Parent {
    static name:string
}
console.log(Parent.name)

只读 readonly

我们给属性添加上 readonly 就能保证该属性只读不能修改,如果存在 static 修饰符,写在其后

class Parent {
  static  readonly name:string = 'hahaha'
}
Parent.name='哈哈哈哈哈哈'//Cannot assign to 'name' because it is a read-only property.

抽象类 abstract

TS 新增的抽象类,不能被实例化,适用于直接使用该类创建实例(不能被new)只能被继承

class 前面 添加 abstract 修饰符,

在抽象类中 可以写 抽象方法 ,抽象类没有方法体

举个例子:一个动物的抽象类,有个叫的方法,不可能 每个动物的叫声一样吧,我们可以把它设置为抽象方法,具体功能子类进行实现(该怎么叫就由子类来写)

abstract class Animal {
    public name:string
    constructor(name:string){
        this.name = name
    }
    abstract barking():void
}

class Cat extends Animal{
    constructor(name:string){
        super(name)
    }
    barking(){
        console.log('喵~')
    }
}

6、接口继承接口

  • 跟类继承类一样,相同的对象难免是可以继承的,接口也可以继承接口,连接方式也是使用extends
interface addIce{
  ice:string
}
interface addSugar extends addIce{
  sugar:string
}
let milk:addSugar={
  ice:'少冰',
  sugar:'半糖'
}

  • 上述例子中定义了两个接口分别是addIce addSugar,而addSugar继承了addIceice属性,所以在使用addSugar接口的时候形状必须一致。
  • milk变量中因为使用了addSugar接口,所以属性如果不符合形状就会报错。

7、接口继承类

  • TypeScript中我们可以使用接口来继承类。
class MilkTea {
  ice:string
  sugar:string
  constructor(ice:string,sugar:string){//constructor是一个构造方法,用来接收参数
      this.ice = ice;
      this.sugar = sugar;
  };
}
interface doSthing extends MilkTea{
}
let milk:doSthing={
  ice:'少冰',
  sugar:'半糖'
}
  • 在上面的例子中,我们的interface接口规定了形状,而这个形状本身是没有东西的,但是它继承了MilkTea类的icesugar属性,所以就规定了使用这个接口interface的变量需要有icesugar属性。

  • 如上我们的milk给了它一个接口doSthing,所以这个变量需要有icesugar属性。

  • 接口跟类又不是同一种东西那怎么可以继承呢?虽然这两不是一个东西,但是我们在定义一个class的时候其实也是定义了一种类型。

  • 就拿上面的MilkTea类来说,我们创建这个类的时候其实也是创建了一个MilkTea的类型,我们可以这样使用。

class MilkTea {
  ice:string
  sugar:string
  constructor(ice:string,sugar:string){//constructor是一个构造方法,用来接收参数
      this.ice = ice;
      this.sugar = sugar;
  };
}
let milk:MilkTea={
  ice:'少冰',
  sugar:'半糖'
}

  • 这样是没有问题的,所以当我们doSthing接口继承时其实是集成了MilkTea这个类型,而这个类型大家是不是觉得很熟悉,他就是interface的写法类似啊,所以我们继承了一个类实际上就是继承了一个接口。

  • 值得一提的是,接口继承类的时候,只会继承它的实例属性和实例方法,继承不了构造函数。

8、泛型

泛型就像一个占位符一个变量,在使用的时候我们可以将定义好的类型像参数一样传入,原封不动的输出

不用泛型的话,这个函数可能是下面这样:

function identity(arg: number): number { 
    return arg; 
}

或者,我们使用any类型来定义函数:

function identity(arg: any): any {
    return arg; 
}

使用any类型会导致这个函数可以接收任何类型的arg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的,所以可按下面方式:
这里 T 是相当于一个占位符,在方法(变量,接口....等等)后面添加 <T>

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

多个参数也是类似写法

function identity<T,U>(arg:[T,U]):[T,U]{
    return arg; 
}
//在使用的时候,聪明的就判断出 传入的类型,并修改了 `T`,`U`
identity(['1',1])

也可以使用 interface 来约束 泛型

T 后面 extends Ilen ,定义 Ilen 里面代码表示,T 必须要有 length 属性

这样在 方法里面调用 params.length 就不会报错

interface Ilen{
    length:number;
}
function getLenght<T extends Ilen>(params:T):number{
    return params.length
}

getLenght('sffsdf');
getLenght(11);  //报错Argument of type 'number' is not assignable to parameter of type 'Ilen'

在类中使用泛型

class Cat<T>{
    eat(name:T){
        console.log('这只小猫吃了'+name)
    }
}

let newCat =  new Cat<string>()
newCat.eat('老鼠')

newCat.eat(111)//报错

在接口中使用泛型

interface Iobj<T,U>{
  key:T
  value:U
}

const a:Iobj<string,number> = {key:'admin',value:1}
const b:Iobj<string,boolean> = {key:'admin',value:false}

9、类型别名type

type a  = string|number
let b:a  = 11

10、交叉类型&

把类型都组合起来,变量赋值必须满足 交叉类型

interface Iobj{
  key:number;
}

type newObj  = Iobj&{value:string}

let a:newObj  = {key:1,value:'hhh'}