你不知道的Typescript基础

233 阅读9分钟

介绍

为了让程序有价值,我们需要能够处理最简单的数据单元:数字,字符串,结构体,布尔值等。 TypeScript支持与JavaScript几乎相同的数据类型,此外还提供了实用的枚举类型方便我们使用。

基础类型

  • String

JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。 像其它语言里一样,我们使用string表示文本数据类型。 和JavaScript一样,可以使用双引号(")或单引号(')表示字符串。

let S = 'charles'

let str:string = 'charles'

你还可以使用_模版字符串_,它可以定义多行文本和内嵌表达式。 这种字符串是被反引号包围(``),并且以${ expr }这种形式嵌入表达式

let name: string = `charles`;
let age: number = 23;
let sentence: string = `Hello, my name is ${ name }.

I'll be ${ age + 1 } years old next month.`;
  • Number

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

let N = 23

let num:number = 23
  • Boolean

最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean(其它语言中也一样)。

let B = true

let bol:boolean = true

Array

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值分别为string和number类型的元组。

//*** 为重点
let arrS= ['charlse','jay','leo']

let arrstr:string[] = ['charlse','jay','leo'] //***

let arrstr2:Array<string> = ['charlse','jay','leo'] //***泛形

示例

  • arr to Number
// 数组内全部是数字类型
let arrN = [18,20,23]

let arrNum:number[] = [18,20,23]

let arrNum2:Array<number> = [18,20,23]
  • arr to Boolean
// arr数组内全部是布尔类型

let arrB = [true,false]

let arrBol:boolean[] = [true,false]

let arrBol2:Array<boolean> = [true,false]
  • arr to Mixed
//数组内混合数字、布尔、字符串类型

let arrM = ['charles',23,true]

let arrMix:(string | number | boolean)[] = ['charles',23,true]

let arrMix2:[string,number,boolean]= ['charles',23,true,]  //弊端必须按照格式来,一对一的约束,只能3个元素

let arrMix3:Array<( string | number | boolean)> = ['charles',23,true]
  • arr to Obj
let arrO = [
  {
    name:'charlse',
    boy:true,
    age:23,
  },
  {
    name:'jay',
    boy:true,
    age:30,
  },
  {
    name:'kim',
    boy:false,
    age:18,
  }
]

// 接口定义后面会详细介绍
interface user {
  name:string;
  boy:boolean;
  age:number
}

// 最佳方法
let arrObj:Array<user>= [
  {
    name:'charlse',
    boy:true,
    age:23,
  },
  {
    name:'jay',
    boy:true,
    age:30,
  },
  {
    name:'kim',
    boy:false,
    age:18,
  }
]
// 第二种方式
let arrObj2:user[] = [
  {
    name:'charlse',
    boy:true,
    age:23,
  },
  {
    name:'jay',
    boy:true,
    age:30,
  },
  {
    name:'kim',
    boy:false,
    age:18,
  }
]

// 不推荐这么写 
let arrObj3 :{
  name:string;
  boy:boolean;
  age:number
}[] = [
  {
    name:'charlse',
    boy:true,
    age:23,
  },
  {
    name:'jay',
    boy:true,
    age:30,
  },
  {
    name:'kim',
    boy:false,
    age:18,
  }
]

Tuple

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。比如,你可以定义一对值分别为string和number类型的元组。

//弊端必须按照格式来,一对一的约束,只能3个元素
let arrMixed1:[string,number,boolean]= ['charles',23,true,]    //ok
let arrMixed2:[string,number,boolean]= ['charles',true,23,]   //err 不能将类型"Boolean"分配给类型"Number"

Enum

enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。

enum  MSG{
  '操作成功'=200,
  '密码错误',
  '账号错误',
  '请求异常'
}
  • 使用业务场景
// https://github.com/goddits/v3-ts-demo/blob/master/src/utils/requset.ts 完整版

// 响应拦截
$http.interceptors.response.use( res =>{
  const code:number = res.data.code
  if(code !== 200){
    MSG[code]
    return Promise.reject(res.data)
  }
  return res.data
},err =>{
  console.log(err);
}

Any

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用any类型来标记这些变量:

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean

在对现有代码进行改写的时候,any类型是十分有用的,它允许你在编译时可选择地包含或移除类型检查。 你可能认为Object有相似的作用,就像它在其它语言中那样。 但是Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用任意的方法,即便它真的有这些方法:

let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.

注意:应避免使用Object,而是使用非原始object类型。

Void

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是void:

示例

// 有返回值
function fn(a:number,b:number):number {
    return a + b
}
// 无返回值
function fn1(a:number,b:number):void {
  console.log(a+b); 
}

Null 和 Undefined

TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null。 和void相似,它们的本身的类型用处不是很大:

let U = undefined

let und:undefined = undefined

let n = null

let nul:null = null

Never

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

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

下面是一些返回never类型的函数:

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

Object

object表示非原始类型,也就是除number,string,boolean,bigint,symbol,null或undefined之外的类型。

使用object类型,就可以更好的表示像Object.create这样的API。例如:

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(23); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

示例

let obj = {
  name:'charles',
  boy:true,
  age:23,
}

let obj1:{
  name:string,
  boy:boolean,
  age:number
} = {
  name:'charles',
  boy:true,
  age:23,
}

let obj2:{
  name:string,
  boy:boolean,
  age?:number //不一定有的值
} = {
  name:'charles',
  boy:true,
}

类型断言

有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型。

通过_类型断言_这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。

类型断言有两种形式。 其一是“尖括号”语法:

let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;

另一个为as语法:

let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有as语法断言是被允许的。

interface接口

接口初探

interface User {
  name: string;
  boy: boolean;
  age: number;
}

// 简单示例
let user: User = {
  name: 'charles',
  boy: true,
  age: 23,
}

接口应用场景

interface Ilist {
  items: {
    id: string;
    name: string;
    intro: string
  }[]
}

interface Idata {
  success: boolean;
  code: number;
  message: string;
  data: Ilist
}


// 复杂示例
let data: Idata = {
  "success": true,
  "code": 20000,
  "message": "成功",
  "data": {
    "items": [
      {
        "id": "1",
        "name": "charles",
        "intro": "毕业于家里蹲大学"
      }
    ]
  }
}

接口继承

interface Ilist {
  items: {
    id: string;
    name: string;
    intro: string
  }[]
}

interface Idata {
  success: boolean;
  code: number;
  message: string;
  data: Ilist
}

interface Ires extends Idata {
  children?:[] //继承了Idata 相当追加了一个数组children  ?代表可选属性  [] | undefiend
}

let data2: Ires = {
  "success": true,
  "code": 20000,
  "message": "成功",
  "data": {
    "items": [
      {
        "id": "1",
        "name": "charles",
        "intro": "毕业于家里蹲大学"
      }
    ]
  }
}

可索引的类型

与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]或ageMap["daniel"]。 可索引类型具有一个_索引签名_,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子:

interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["charles", "jay"];

let myStr: string = myArray[0];

接口继承类

当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的 private 和 protected 成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。

当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 除了继承自基类,子类之间不必相关联。 例:

class Person {
  private state: any;
}

interface SelectableControl extends Person {
  select(): void;
}

class Button extends Control implements SelectableControl {
  select() {}
}

class TextBox extends Control {
  select() {}
}

class ImageControl implements SelectableControl {
// Error: Class 'ImageControl' incorrectly implements interface 'SelectableControl'.
//  Types have separate declarations of a private property 'state'.
  private state: any;
  select() {}
}

在上面的例子里,SelectableControl包含了Control的所有成员,包括私有成员state。 因为state是私有成员,所以只能够是Control的子类们才能实现SelectableControl接口。 因为只有Control的子类才能够拥有一个声明于Control的私有成员state,这对私有成员的兼容性是必需的。

在Control类内部,是允许通过SelectableControl的实例来访问私有成员state的。 实际上,SelectableControl就像Control一样,并拥有一个select方法。 Button和TextBox类是SelectableControl的子类(因为它们都继承自Control并有select方法)。而对于 ImageControl 类,它有自身的私有成员 state 而不是通过继承 Control 得来的,所以它不可以实现 SelectableControl 。

type

type 会给一个类型起个新名字。 type 有时和 interface 很像,但是可以作用于原始值(基本类型),联合类型,元组以及其它任何你需要手写的类型

基本使用

type Name = string; // 基本类型
type NameFun = () => string; // 函数
type NameOrRFun = Name | NameFun; // 联合类型
function getName(n: NameOrRFun): Name {    if (typeof n === 'string') {
        return n;
    } 
    return n();
}

interface 和 type区别

interface 和 type 很像,很多场景,两者都能使用。但也有细微的差别:

  • 类型:对象、函数两者都适用,但是 type 可以用于基础类型、联合类型、元祖。
  • 同名合并:interface 支持,type 不支持。
  • 计算属性:type 支持, interface 不支持。

总的来说,公共的用 interface 实现,不能用 interface 实现的再用 type 实现。是一对互帮互助的好兄弟。

函数

返回值约束

function BackFn(a:number,b:number) : number {
  return a + b
}
BackFn(1,2)

可选参数

JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined。 在TypeScript里我们可以在参数名旁使用?实现可选参数的功能。 比如,我们想让b是可选的:

function OptionFn(a:number,b?:number) : number {
  if(!!b){
    b = 123
  }
  return a 
}
OptionFn(1,2)

可选参数必须跟在必须参数后面。 如果上例我们想让a是可选的,那么就必须调整它们的位置,把a放在后面。

函数参数默认值

在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是undefined时。 它们叫做有默认初始化值的参数。 让我们修改上例,把b的默认值设置为1。

function DefaultFn(a:number,b:number=1) : number {
  return a + b
}
DefaultFn(1)

箭头函数约束

let ArrowFn:(params1:number,params2:string) => string = (a:number,b:string):string => {
  return a + b
}
ArrowFn(1,'2')

如果对于ES6不熟悉请走直通车ECMAScript 6 入门-阮一峰

类基本使用

class Person {

  userName:string;
  userAge:number;

  constructor(name:string,age:number){
    this.userName = name;
    this.userAge = age
  }
  run(a:number,b:number):number{
    return a+b;
  }
}
let p1 = new Person('charles',23).run(1,2)

修饰符

  • readonly 只读
  • public 公开的,在任何地方都可以使用
  • protected 受保护的,只能在当前类或者当前类的子类部使用
  • private 私有的,当前类的内部使用

示例

class Persons {

  protected userName:string;
  readonly userAge:number;

  constructor(name:string,age:number){
    this.userName = name;
    this.userAge = age
  }
  run(){
    return this.userName;
  }
}
let p2 = new Persons('charles',23)
p2.run()

抽象类 :abstract

  • 不完成具体功能
  • 抽象类不能new
  • 抽象类可以继承,如果要继承,就必须实现该类的抽象方法
abstract class Person3 {
 abstract run():void;
 abstract change():void;
}

class children extends Person3 {
  run():void {

  };
  change(): void {
    
  };
}
// let p3 = new Person3() 不支持new

抽象类使用场景

abstract class Db{
  abstract connection():void;
  abstract auth():void;
}

class mySql extends Db{
  connection(): void {
    
  };
  auth(): void {
    
  }
}

implements 对于类的约束

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

interface Ip2{
  change():void
}

class Person4 implements Ip1,Ip2 {
  name: string;
  age:number; 
  change(){

  }
}

泛形

函数使用泛形单个写法

function fn1<T>(arg:T):T{
    return arg
  }
  fn1<number>(23)
  fn1<string>('charls')

函数使用泛形多个写法

function fn2<T,U>(name:T,age:U){
    return `${name}is${age}`
  }
  fn2<string,number>('charlse',23)
  fn2<string[],boolean>(['charlse'],true)

注意:T就是一个标识符,也是可以写成其他代替,但是一般都是T,U,M

接口的泛形写法

interface Idata {
    length:number;
  }

  function fn3<T extends Idata >(arg:T):number{
    return arg.length;
  }
fn3<string>('charlse')
fn3<number[]>([1,2,3])
fn3<string[]>(['charles','jay'])
fn3<{}[]>([{name:'charlse',age:23},{name:'jay',age:18}])

泛形结合class使用

class Person5 <T,U>{
  userName:T;
  userAge:U;
  constructor(name:T,age:U){
    this.userName = name
    this.userAge = age
  }
}
let p5 = new Person5<string,number>('charles',23)

参考资料: JavaScript With Syntax For Types.

希望大家都能从中收获满满,每一个案例都需要静心过一遍!重要事情说三遍:一定要敲、要敲、要敲。