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类型
有两种方式:
- 通过as关键字
- 通过 尖括号方式,但是这个如果在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')