概述
TypeScript是Javascript的超集,它拥有比javascript更强的特性,例如:静态类型检查、支持ES6及以上特性例如类和模块、良好代码提示等,TypeScript的目标是增强JavaScript的可维护性和可扩展性,特别适用于大型项目和团队开发。
优势
1、静态类型检查,编译阶段就能暴露出代码的潜在错误
2、良好的类型提示
3、项目代码尤其是多人合作的大型项目,可读性和可维护性更好
劣势
1、代码灵活性不如js
2、学习成本高
3、对于中小型项目来说,效率偏低
起步
1、安装typescript
npm install typescript -g
2、编译typescript为js
tsc some.ts #将在some.ts同目录下生成一个some.js
基础类型
boolean //布尔
string //字符串
number //数字
Array //数组
Tuple //元组
enum // 枚举
any // 任意值
void // 空值
Null //null
Undefined //未定义
Never //不存在类型
unknown //未知类型
基础示例
ts中类型对变量有约束作用,类型确定后,不可再赋值为其他类型
let a:number = 10 //ok
a = 11 //正确
a = '1' //报错,不能将类型“string”分配给类型“number”
类型推断示例
ts具有类型推断功能,即会根据变量的值,自动推断变量类型
let a = '你好' //此时变量a已具有string类型
a = 1 //再次将a赋值一个数字,就会报错不能将类型“number”分配给类型“string”
array
array类型有两种写法
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3];
元组类型
let x: [string, number];
x = ['hello', 10]; // 正确
//x = [10, 'hello']; // 报错,元祖的类型需对影
//x = ['world', 20, 30]; // 报错,元祖的长度需一致
枚举类型
//ts 枚举类型
enum Color { Red, Green, Blue='blue' }
let c: Color = Color.Green;
console.log(c); // 1
console.log(Color.Red); // 0
console.log(Color.Blue); // blue
console.log(Color[1]); // Green 数字枚举可以反向取值
console.log(Color['blue']); // undefined 字符枚举不可反向取值
any类型
any类型(any类型可以视为跳过类型检查,非必需不要用any)
let notSure: any = 4;
notSure = 'maybe a string instead'; //正确
notSure = false; //正确
void类型
//void 类型表示空类型,一般用于定义无返回值的函数
function someFn(): void {
console.log('This is my warning message');
}
function someFn2(): void {
return undefined //正确,空值也可表示undefined
}
function someFn2(): void {
return null //不能将类型“null”分配给类型“void”
}
null 和 undefined
let u: undefined = undefined;
let n: null = null;
//
function nullFn(testVar:string | null | undefined){
// let res:string = testVar; //错误 ,不能将类型“string | null | undefined”分配给类型“string”
let res:string = testVar!; //正确,!表示忽略null和undefined
}
never
//never类型表示的是那些永不存在的值的类型。 never类型是任何类型的子类型,也可以赋值给任何类型;
//然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。
function stringTest(message:string): string {
return message;
}
function neverTest(message: string): never {
throw new Error(message); //throw new Error()返回的是never类型
}
function anyTest(message: string): any {
return message;
}
let neve1:number = neverTest('error message'); //正确,never类型可以赋值给任何类型
let neve2:number = stringTest('string message'); //错误,string类型不能赋值给number类型
let neve3:never = anyTest('any message'); //错误,除never外,任何类型的值都不可以赋值给never类型,包括any
接口
interface Person {
name: string;
age: number;
from?:string; //可选属性
readonly gender: string; //只读属性
}
let tony: Person = {
name: 'Tom',
age: 25,
gender:'男'
};
tony.gender = '女' //报错,gender为只读,不可更改
//接口实现类
class PersonFactory implements Person {
name: string;
age: number;
from?: string;
readonly gender: string;
constructor(name:string){
this.name = name;
}
}
函数
//函数声明
function add(x: number, y: number): number {
return x + y;
}
//函数表达式
let myAdd = function(x: number, y: number): number {
return x + y;
};
//函数类型
type FuncT = (x: number, y: number) => number;
let myAdd2: FuncT = function(x: number, y: number): number {
return x + y;
};
//可选参数
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + ' ' + lastName;
else return firstName;
}
//默认参数
function buildName2(firstName: string, lastName = 'Smith') {
return firstName + ' ' + lastName;
}
//剩余参数
function buildName3(firstName: string, ...restOfName: string[]) {
return firstName + ' ' + restOfName.join(' ');
}
函数重载
函数重载有什么用?与联合类型的区别是什么?
//函数重载
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') return Number(x.toString().split('').reverse().join(''));
else return x.split('').reverse().join('');
}
let reverseNumber:number = reverse(123);
let reverseString:string = reverse('123');
function reverse2(x: number | string): number | string {
if (typeof x === 'number') return Number(x.toString().split('').reverse().join(''));
else return x.split('').reverse().join('');
}
// let reverseNumber2:number = reverse2(123);//不能将类型“string | number”分配给类型“number”。不能将类型“string”分配给类型“number”
// let reverseString3:string = reverse2('123');//不能将类型“string | number”分配给类型“string”。不能将类型“number”分配给类型“string”
let reverseString4:number|string = reverse2('123');//正确
let reverseString5:number|string = reverse2(123);//正确
从上面的例子可以看出,函数重载的目的是为了让函数能够接受不同的参数类型,返回不同的结果类型,而联合类型是为了让一个变量能够接受多种类型的值。
联合类型
let unionType: string | number;
//联合类型的子集都可以赋值给联合类型
unionType = '123';
unionType = 123;
//联合类型不可以赋值给子集
let str:string = unionType; //错误,不能将类型“string | number”分配给类型“string”
交叉类型
interface A {
a: string;
}
interface B {
b: number;
}
let crossType: A & B;
crossType = { a: 'hello', b: 1 };
type JXB = string & number; //基础类型string和number没有交集,所以交叉类型为never
// let jxb:JXB = 'hello'; //错误,不能将类型“string”分配给类型“never”
类型断言
//类型断言可以将一个联合类型断言为其中一个类型
// 类型断言有两种形式。 其一是“尖括号”语法:
let someValue1: string|number;
someValue1 = 'this is a string'
let strLength1: number = (<string>someValue1).length;
// 另一个为as语法:
let strLength2: number = (someValue1 as string).length;
泛型
泛型基础
泛型其实就是参数类型,可以用来约束类型,使得函数和类可以适用于多种类型
function identity<T>(arg: T): T {
return arg;
}
identity<string>(string){
return string
}
identity<string>('myString'); //正确
identity<number>(1); //正确
// identity<string>(1); //错误,类型“number”的参数不能赋给类型“string”的参数
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0; //正确
myGenericNumber.add = function(x, y) { return x + y; }; //正确
// myGenericNumber.zeroValue = '123' //错误,不能将类型“string”分配给类型“number”
//类型判断
type GenericType<T> = T extends string ? number : string;
let genericType1: GenericType<string> = 1; //正确
let genericType2: GenericType<number> = '2'; //正确
泛型约束
我们也可以对传入的泛型进行约束
interface IdentityType {
name: string;
age: number;
}
interface IdentityType2 {
len: number;
run: boolean;
}
type RT = IdentityType|IdentityType2;
function identity2<T extends RT>(arg: T): T {
return arg;
}
//extends约束泛型T必须是RT的子集
identity2<string>({name:'Tom',age:25});//报错
identity2({name:'Tom',age:25}); //正确
identity2({len:123,run:true}); //正确
// identity2({name:'Tom'}); //错误,缺少age属性
keyof映射
//例如,我们有一个接口,我们想把它的所有属性变成可选的,我们可以使用类型映射:
type PartialTestKeyType<T> = {
[K in keyof T]?: T[K]
}
//再例如,我们像将一个接口的key加上固定前缀,我们可以使用类型映射:(ps,因为keyof的可以是 string|number|symbol的联合类型,所以需要用&来约束为string)
type PrefixTestKeyType<T extends Record<string, any>> = {
[k in keyof T as `prefix_${string & k}`]: T[k]
}
let prefixTest:PrefixTestKeyType<TestKeyType> = {
prefix_name:'Tom',
prefix_age:25,
prefix_eat:true
}
类型匹配判断
我们可以通过extends关键字加上? xxx : xxx来进行类型匹配判断
type TypeName<T> = T extends string ? 'string' : T;
//当我们传入的类型是string的子集时,TypeName<string>的结果为string,当传入的类型是其他的时候,TypeName<number>的结果为传入的类型。
let tmText:TypeName<string> = 'string'; //正确
let tmText2:TypeName<number> = 123; //正确
let tmText3:TypeName<boolean> = true; //正确
infer
infer是一个占位符关键字,作用是用来给匹配的未知部分取别名作为特殊类型使用
//例如,我们需要一个工具类型,来获取函数的返回值类型,可以使用infer占位符:
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
let testreturnType:MyReturnType<() => number> = 1; //正确
let testreturnType2:MyReturnType<() => {a:string}> = {a:'hello'}; //正确
//再例如,获取函数的第一个参数
type FirstArgType<T> = T extends (arg1: infer A, ...args: any[]) => any ? A : any;
let testFirstArgType:FirstArgType<(a:number,b:string)=>void> = 1; //正确
工具类型
常用工具类
下面几个类型为测试类行
interface UtilInter {
name: string
age: number
len:number
other?:string
nullAttr:null
undefinedAttr:undefined
}
type UtilFunc = (x: number, y: string) => UtilInter;
class UtilClass {
constructor(name:string,age:number,len:number){
this.name = name;
this.age = age;
this.len = len;
}
name: string
age: number
len:number
other?:string
nullAttr:null
undefinedAttr:undefined
}
Partial
作用是将T的所有属性变为可选属性
//Partial
//Partial<T>的作用是将T的所有属性变为可选属性
type PartialUtilInter = Partial<UtilInter>;
Required
作用是将T的所有属性变为必选属性
//Required
//Required<T>的作用是将T的所有属性变为必选属性
type RequiredUtilInter = Required<UtilInter>;
Readonly
作用是将T的所有属性变为只读属性
//Readonly
//Readonly<T>的作用是将T的所有属性变为只读属性
type ReadonlyUtilInter = Readonly<UtilInter>;
Record<K, T>
作用是将K中的所有属性的值转换为T类型
//Record
//Record<K, T>的作用是将K中的所有属性的值转换为T类型
type RecordUtilInter = Record<'name'|'age', number[]>;
Pick<T, K>
作用是将T中的属性K提取出来
//Pick
//Pick<T, K>的作用是将T中的属性K提取出来
type PickUtilInter = Pick<UtilInter, 'name'|'age'>;
Exclude<T, U>
作用是将T中的U去除
//Exclude
//Exclude<T, U>的作用是将T中的U去除
type ExcludeUtilInter = Exclude<keyof UtilInter, 'name'|'age'>;
Extract<T, U>
作用是将T中的U提取出来
//Extract
//Extract<T, U>的作用是将T中的U提取出来
type ExtractUtilInter = Extract<keyof UtilInter, 'name'|'age'>;
Omit<T, K>
作用是将T中的K属性去除
//Omit
//Omit<T, K>的作用是将T中的K属性去除
type OmitUtilInter = Omit<UtilInter, 'name'|'age'>;
NonNullable
作用是将T中的null和undefined去除
//NonNullable
//NonNullable<T>的作用是将T中的null和undefined去除
type NonNullableUtilInter = NonNullable<UtilInter>;
ReturnType
作用是获取函数的返回值类型
//ReturnType
//ReturnType<T>的作用是获取函数的返回值类型
type ReturnTypeUtilInter = ReturnType<UtilFunc>;
InstanceType
作用是获取构造函数的实例类型
//InstanceType
//InstanceType<T>的作用是获取构造函数的实例类型
type InstanceTypeUtilInter = InstanceType<typeof UtilClass>;
Parameters
作用是获取函数的参数类型
//Parameters
//Parameters<T>的作用是获取函数的参数类型
type ParametersUtilInter = Parameters<UtilFunc>;
ConstructorParameters
作用是获取构造函数的参数类型
//ConstructorParameters
//ConstructorParameters<T>的作用是获取构造函数的参数类型
type ConstructorParametersUtilInter = ConstructorParameters<typeof UtilClass>;
声明文件(.d.ts)
定义全局类型或变量
有些时候我们可能会定义一些全局的类型或者挂在全局的变量,在js中默认无需引入直接使用,但是这在ts中默认是会报错变量或者类型为定义
(ps: 需要注意的是,要在tsconfig.json中配置include,将.d.ts包含进去,假设.d.ts在types目录下)
{
"compilerOptions":{},
"include": ["types"]
}
例如:我们封装了一个工具类型MPartial,若直接使用,会报错找不到MPartial
//utilsTypes.ts
type MPartial<T> = {
[key in keyof T]?:T[key]
}
//app.ts
interface SomeInterFace {
name:string
}
let sp:MPartial<SomeInterFace> //报错,找不到MPartial
这时我们可以新建一个utilsTypes.d.ts文件,将要声明的全局类型放到这个声明文件中,通过declare来键字来声明(除galobal外,其他的类型声明都会默认导出全局,声明global需要在文件最后加一个 export {},否则会报错)
//utilsTypes.d.ts
declare type type MPartial<T> = {
[key in keyof T]?:T[key]
}
//app.ts
interface SomeInterFace {
name:string
}
let sp:MPartial<SomeInterFace> //正确,不报错
除了直接通过declare来声明全局类型外,还可以通过global包裹,来实现全局声明(如果global里面的命名与declare的命名重复了,declare优先级会更高)
//utilsTypes.d.ts
declare global {
type type MPartial<T> = {
[key in keyof T]?:T[key]
}
}
export {}
//app.ts
interface SomeInterFace {
name:string
}
let sp:MPartial<SomeInterFace> //正确,不报错
命名空间
为了防止命名重复,可以给申明的变量加上命名空间,如果命名空间同名,则内部的变量和合并
// test.d.ts
declare namespace Demo1 {
type AjaxMethods = 'GET' | 'POST' ;
}
declare namespace Demo2 {
type AjaxMethods = 'PUT' | 'DELETE';
}
declare namespace Demo1 {
type ResObject = {
code: number;
data: string[];
msg: string;
}
}
//app.ts
let ajx1:Demo1.AjaxMethods
let ajx2:Demo2.AjaxMethods
let res:Demo1.ResObject
声明模块
有些情况下,我们可能会依赖一些js包,或者其他没有导出的模块(如vue文件)等,这时候就需要我们自己来给这些依赖来声明模块
拿jquery举例,jquery是js包,直接在ts项目中引入是会报错的
这时候我们需要手动声明一个jquery模块
declare module 'jquery' {
type Jquery = (selector: string,context:string) => HTMLElement
const jquery: Jquery
export default jquery
}
上述声明的意思就是声明一个名为"jquery"的模块,这个模块会默认导出一个变量,变量的类型是Jquery,效果如下