TypeScript 入门

449 阅读9分钟

TypeScript

TypeScript是JavaScript的超集,编译成JavaScript,兼容性比较好,可以使用es新特性的同时,通过配置还能编译成es3的JavaScript。

生态比较健全,和vscode契合起来特别顺畅,代码提示等等功能,好处很多,尤其是开发大型项目,长时间迭代的话,上TypeScript是很明智的选择

但是缺点也很明显,多了很多概念,比如里面有 泛型,接口,枚举等等,提高了学习成本

但是同时TypeScript 是渐进式的,就算你不了解TypeScript只是了解一点点TypeScript也可以直接使用,直接写JavaScript也是可以的

安装

初始化项目


yarn init --yes

yarn add TypeScript --dev

创建Index.ts文件


const hello =(name:string)=>{
console.log(name)
}

hello('TypeScript')

在控制台运行

tsc index 命令,会发现目录会多出一个index.js文件,打开看,发现刚刚写的ts代码编译成了es5的JavaScript,

配置tsconfig.json

运行


yarn tsc --init

发现多出了一个tsconfig.json文件,打开发现里面有很多配置选项,我们可以先关注其中几个


    "target": "es5", /* 编译后的es版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */

    "module": "commonjs", /*导入导出的使用哪个规范: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */

    "outDir": "dist", /* 编译后的js放在哪个地方. */

    "rootDir": "src",      /* 编译前的ts文件放置目录. */ 

修改下项目结构,把刚才的代码放置到src下,

运行tsc,发现编译后的js都放置在dist文件目录下

TypeScript 报错提示语言设置

默认是根据vscode初始化时配置的语言包,如果想改成中文可以通过设置搜索typescript locale 上找到设置项

原始类型

const a: string = '123'; //字符串
const b: number = 123; // 数字
const c: boolean = true; // 布尔值
const e: void = undefined; //空值,没有返回值
const f: null = null; //空值
const g: undefined = undefined; // 空值
const h: symbol = Symbol(); //symbol

但是这时候会发现symbol居然报红了,鼠标移上去会发现他有个提示

通过提示我们把tsconfig.json里面的"target":"es5"改成es2015,回来发现代码正常了,因为Symbol是es2015新增的数据类型,因此没办法通过简单编译成es5代码,因此如果想让代码编译成es5的同时又能支持es2015新特性,可以通过另外一个方法,就是看到tsconfig.json下面有个lib配置项

他的意思是说引入的第三方模块,我们把他改成 "lib": ["ES2015","DOM"],添加了两个第三方模块,一个es2015,另外一个是浏览器环境所需的dom和bom,这里我们只需要配置一个DOM即可,

作用域

比如不同文件,定义同一个名称的变量时,会提示重复定义错误,比如我们创建一个新的文件,定义一个在index.ts定义过的a变量,会发现提示说变量a也在index.ts中定义

解决这个问题方法有两种,就是给他一个作用域,比如可以通过自执行函数包裹起来,另外也可以通过在代码尾部添加上一个export {},TypeScript就会认为这是单独一个模块代码

Object 类型

TypeScript的Object类型不单指是Object,也包括Function,Array

如果想定义一个对象类型,则使用{},在对象内部也可以定义每一个属性的类型

const obj: Object = function () {}; //[] ,{}
const o: {
  name: string;
  age: number;
} = {
  name: 'silan',
  age: 27
};

TypeScript 数组类型

const arr1:Array<number|string> =[4,2,'text'] //元素为数字或者字符串的数组
const arr2:(number|string)[]=[1,2,'text2']
//函数中使用
function sum(...args:number[]){
  args.reduce((prev,current)=>prev+current,0)
}

TypeScript 元组类型

元组是明确元素数量和类型的一个数组


const tuple :[number,string]=[18,'test']
const [age,name] = tuple
console.log(age,name) // 18 test

TypeScript 枚举

在项目中通常会使用到 0,1,2 这种数字来代表某个状态,在TypeScript里面可以通过使用enum枚举来实现

enum gender {
man = 0,
female = 1,
other = 3
}

如果不给他定义值,那它就会默认从0开始,如果给第一个枚举成员设定值某个数字,那之后的成员则按照这个数字递增 比如

enum gender {
man = 12,
female , //13
other //14
}

当然也可以使用字符串枚举,但是必须要给每一个元素设定值

常量枚举

因为枚举和其他类型声明不一样,他最终会被编译到js代码里面,而不是只是单纯的做类型约束,如果没有声明常量枚举,则会发现代码被打包成可以通过键和值访问到的对象,就是说可以通过值获取到键,也可以通过键获取都值

如果不需要此功能可以声明一个常量枚举,只需在enum关键字前面加上一个const

const
enum gender {
man ,
female ,
other
}
let genderArray = [gender.man, gender.female, gender.other];

看到编译后的代码,是只把使用到的枚举成员内容赋值在使用到的内容里

函数类型

函数声明的类型约束

在参数后面和大括号前面分别标明参数类型和返回值类型,如果没有返回值则设置:void ,值得注意的是,TypeScript 里面使用了函数类型约束,那么函数的形参和实参的数量就必须得一样

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

可选参数

如果想设置其中某个参数为可选参数,则可以通过?还进行标明

function add(a:number,b:number,c?:string):void{
  console.log( a+b)
}
add(1,2)

参数默认值

如果想使用es6的参数默认值,则最后一个参数默认为可选参数

function add(a: number, b: number, c: string = 'result is'): void {
  console.log(c + a + b);
}
add(1, 2);

接收任意的参数

可以通过rest来实现

function add(a: number, b: number, ...rest: number[]): void {
  console.log(a + b, rest);
}
add(1, 2, 4, 5, 6, 7, 8);

函数表达式

const func2 =function(a:number,b:number):number{
return a+b
}

任意类型

使用any关键字:

function stringfy(value:any){
  return JSON.stringify(value)
}

TypeScript 隐式类型推断

如果我们没有对一个变量进行类型注解,那么TypeScript就会根据这个变量的使用情况进行类型推断,比如age变量初始化赋值为数字类型,那么之后在将它赋值为字符串就会报错

类型断言

在某些情况下,TypeScript 没办法通过类型推断进行正确的判定,

const nums: number[] = [10, 2, 5];
const res = nums.find((i) => i > 0);

比如在这里,我们要从一个全是number类型的数组里面找到第一个大于0的数字,但是TypeScript这里却推断出我们找到的结果是number或者undefined,认为有可能找不到,所以是undefined,但是作为开发者,我们可以看到定义的数组里面全是大于0的数字,因此不可能存在undefined的情况。

所以我们要通过断言,明确告诉TypeScript,这是绝对会返回number类型 有两种方式:

  1. 通过as关键字
  2. 通过 尖括号方式,但是这个如果在React里面会和标签产生冲突,因此推荐第一种
const nums: number[] = [10, 2, 5];
const res= nums.find((i) => i > 0);
const num1 = <number>res
const num2 = res as number

可以看到 num1 类型是number类型,指的注意的是,推断并不是类型转换

接口

是一种规范或者契约,可以约束对象的属性类型,通过interface 关键字来声明一个接口类型

interface Post {
  title: string
  age: number
}

function printPost(post: Post) {
  console.log(post.title, post.age);
}
printPost({
  title: 'test',
  age: 12
});

可选成员和只读成员

可选成员没啥好说的,就是类似于可选参数,也是通过?来表示

只读成员要通过readonly进行声明,意思就是说初始化赋值后就不能在对他进行修改

interface Post {
  title: string;
  subTitle?:string //可选成员
  age: number;
 readonly summary:string // 只读成员
}

const obj:Post={
  title: 'test',
  age: 12,
  summary: 'test summary'
};

obj.summary='123' //会报错

动态成员

比如在某些场景无法确定对象的属性个数,就可以使用接口动态类型进行约束,比如这里声明了一个Cache接口类型,他的键名和值都是字符串类型

interface Cache {
  [prop: string]: string;
}

const cache: Cache = {};
cache.test = 'test';

类也就是es6的class 关键字声明的一个类,TypeScript 对此进行了扩充,比如private关键和protected 关键字,他们两个都不能再实例对象中访问,但是protected 属性可以在子类构造函数中访问 readonly只读属性,定义后就不能再进行修改


class Person {
  public name: string;
  private age: number; // 私有属性
  protected gender: string; // 受保护的属性
readonly from:string
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
    this.gender = 'man';
this.from = 'China'
  }
  say(msg: string) {
    console.log(msg);
  }
}

const silan = new Person('silan',27)
console.log(silan.age,silan.name,silan.gender)

class Developer extends Person{
  constructor(name:string,age:number){
    super(name,age)
    console.log(this.gender) // 这里是可以访问的
  }
}


类与接口

定义一个接口类型,来约束类,使用的时候通过 implements 使用

interface EatAndRun {
  eat(food: string): void;
  run(distance: number): void;
}
class Person implements EatAndRun {
  eat(food: string): void {
    console.log('我在进餐' + food);
  }
  run(distance:number):void{
    console.log('我在走路'+distance)
  }
}

但是这里有个问题就是一个接口里面有两个成员,但是如果我有些类只是有里面其中一个方法,eat或者run,那么就比较麻烦,需要改造接口,因此还有种比较好的方式,就是拆分接口类型。


interface Eat {
  eat(food: string): void;
}
interface Run {
  run(distance: number): void;
}

class Person implements Eat,Run { // 通过逗号隔开接口类型
  eat(food: string): void {
    console.log('我在进餐' + food);
  }
  run(distance: number): void {
    console.log('我在走路' + distance);
  }
}
class Runner implements Run {
  run(distance: number): void {
    console.log('我在走路' + distance);
  }
}

抽象类

抽象类可以包含具体的实现,而接口只是成员的抽象,不包含具体实现

abstract class Person {
  eat(food: string): void {
    console.log('我在进餐' + food);
  }
  abstract run(distance: number): void //抽象方法
}

class Man extends Person {
  run(distance: number): void{
    console.log('我在走路'+distance)
  }
}
const silan = new Man() // 在使用的时候,可以拥有抽象类的方法
silan.eat('哈密瓜')
silan.run(4000)

泛型

定义函数的时候没有指明具体类型,等在使用的时候才传递类型

通常用法是用一个参数T来表示不明确类型,在调用的时候在通过尖括号<>传入类型,比如以下是一个生成指定长度数组的方法,数组里面可能是数字也可能是字符串

在定义的时候,通过<T>来表示不明确类型都用T来表示,在根据需求传入<number>或者<string>


function createArray<T>(length:number,value:T):T[]{
  const arr = Array<T>(length).fill(value)
  return arr
}

const arr = createArray<number>(3, 100); // 传入number
const strArr = createArray<string>(3,'test') //传入 string

类型声明

比如我们在使用一些没有内置TypeScript的第三方库时,需要对其进行类型约束,就可以用到类型声明, 只需通过declare 关键字定义指定函数名的参数类型和返回值类型即可

import {handleInput} from 'xxx'
declare handleInput(input:string):string
const res = handleInput('hello ts')