typescript小纸条

97 阅读10分钟

概述

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,效果如下