TypeScript:TS常用特性总结

1、常用类型

1. 交叉类型

交叉类型就是通过 & 符号,将多个类型合并为一个类型。(一般来说在做交叉运算的时候,不会用到简单类型上,只会用到对象上面)

interface T1 {
  name: string;
}
interface T2 {
  age: number;
}

type T3 = T2 & T1

const a: T3 = {
  name: 'xm',
  age: 20,
}

❗️拓展:typescript交叉类型类型冲突

当交叉类型的多个类型发生冲突时,例如:

冲突的属性类型会变为交叉类型,上例中的属性name会变为:string & number,(鉴于string & number 永远不会有任何此类型的值,系统就将此类型标记为了never类型)。但如果冲突的属性类型存在交集,则会变成交叉类型。

已下例为示:

//定义接口 T1 类型
interface T1 {
    name:string,
    age:{attr1:string}
}
//定义接口 T2 类型
interface T2 {
    age:{attr2:number}
}
//通过‘&’关键字获取到交叉类型T3
type T3 = T2 & T1

var t3:T3 = {
    name:'ts',
    age:{attr1:'T1 的age',attr2:12}
}
console.log(t3)

打印t3结果:

{
    "name": "ts",
    "age": {
        "attr1": "T1 的age",
        "attr2": 12
    }
}

2. 联合类型

联合类型就是通过 | 符号,表示一个值可以是几种类型之一。

const a: string | number = 1

❗️拓展:如何使用联合类型 ?

const f1 = (a: number | string) => {
  "即不能把 a 当做 nunmber"
  "也不能把 a 当做 string"
  "那么,怎么使用 a 变量呢"
}

答:想办法把类型区分开来

你不拆开就只能使用 number 和 string 同时拥有的方法和属性,如 toString()

// 我们声明了联合类型之后,一定会把它们拆开,这个拆开的过程叫做类型收窄(Narrowing)
const f1 = (a: number | string) => {
  if ( typeof a === 'number' ) {
    a.toFixed(2)
  } else {
    a.split(',')
  }
}

3. 字面量类型

字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型,对应的字符串字面量、数字字面量、布尔字面量分别拥有与其值一样的字面量类型,具体示例如下:

{
  let specifiedStr: 'this is string' = 'this is string';
  let specifiedNum: 1 = 1;
  let specifiedBoolean: true = true;
}

因为字面量类型也是一种类型,所以可以与其它基本类型用于生成联合类型

//通过‘字面量类型’,‘数字类型’,’布尔类型‘组成的联合类型
type btnType = 'default'|'primary'|number|boolean

//字面量类型
const  btnType1 = "default"
const  btnType2 = "primary"
//数字类型
const btnTypeNum = 12
//布尔类型
const btnTypeBool = false

打印结果:

❗️拓展:字面量类型是集合类型的子类型

字面量类型是集合类型的子类型,它是集合类型的一种更具体的表达。比如 'this is string' (这里表示一个字符串字面量类型)类型是 string 类型(确切地说是 string 类型的子类型),而 string 类型不一定是 'this is string'(这里表示一个字符串字面量类型)类型,如下具体示例:

{
  let specifiedStr: 'this is string' = 'this is string';
  let str: string = 'any string';
  specifiedStr = str; // ts(2322) 类型 '"string"' 不能赋值给类型 'this is string'
  str = specifiedStr; // ok 
}

这里,我们通过一个更通俗的说法来理解字面量类型和所属集合类型的关系。比如说我们用“马”比喻 string 类型,即“黑马”代指 'this is string' 类型,“黑马”肯定是“马”,但“马”不一定是“黑马”,它可能还是“白马”“灰马”。因此,'this is string' 字面量类型可以给 string 类型赋值,但是 string 类型不能给 'this is string' 字面量类型赋值,这个比喻同样适合于形容数字、布尔等其他字面量和它们父类的关系。

4.字符串模板类型

字符串模板类型就是通过 ES6 的模板字符串语法,对类型进行约束。

type https = `https://${string}`
const a:https = `https://www.baidu.com`

❗️拓展:

TS 中模板字符串类型 与 JS 模板字符串非常类似,,通过 ${} 包裹,

1. 模板字符串类型的目的就是将多个字符串组装在一起

type name = "Echoyya";
type sayHaha = `hi ${name} haha`; // type name = "Echoyya";

2.模板字符串具备分发的机制可以组成联合类型

实现:marign-left、 margin-top、 margin-bottom.....

type Direction = "left" | "right" | "top" | "bottom";
type size = "10" | "20";
type AllMargin = `margin-${Direction}:${size}px;`;

2、运算符

1. 非空断言运算符

非空断言运算符 ! 用于强调元素是非 null 非 undefined,告诉 Typescript 该属性会被明确的赋值。

// 你可以使用类型断言将其指定为一个更具体的类型:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;

// 也可以使用尖括号语法(注意尽量不要在 .tsx 文件内使用,会产生冲突),是等价的:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");

// 当你明确的知道这个值不可能是 null 或者 undefined 时才使用 ! 
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed());
}

2. 可选运算符

可选链运算符 ?. 用于判断左侧表达式的值是否是 null 或 undefined,如果是将停止表达式运行。

const name = object?.name

3. 空值合并运算符

空值合并运算符 ?? 用于判断左侧表达式的值是否是 null 或 undefined,如果是返回右侧的值。

const a = b ?? 0

3、操作符

1. keyof

keyof 用于获取某种类型的所有键,其返回值是联合类型。

const person: keyof {
  name: string,
  age: number
} = 'name'   

等价于

const person: 'name' | 'age' = 'name'

2. typeof

typeof 用于获取对象或者函数的结构类型。

//对象
const a2 = {
  name: 'xm',
}
type T1 = typeof a2 // { name: string }

//函数
function fn1(x: number): number {
  return x * 10
}
type T2 = typeof fn1 // (x: number) => number

3. in

in 用于遍历联合类型。

const obj = {
  name: 'xm',
  age: 20,
  sex:''
}
type T5 = {
  [P in keyof typeof obj]: string
}

等价于

{ name: string, age: string, sex:string }

4. T[K]

T[K] 用于访问索引,得到索引对应的值的联合类型。

interface I3 {
  name: string,
  age: number
}

type T6 = I3[keyof I3] // string | number

4、类型别名

类型别名用来给一个类型起个新名字。类型别名常用于联合类型。这里需要注意的是我们仅仅是给类型取了一个新的名字,并不是创建了一个新的类型

type Message = string | string[]
let greet = (message: Message) => {
  // ...
}

5、类型断言

类型断言就是告诉浏览器我非常确定的类型。

// 尖括号 语法
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;

6、类型缩小

我们可以通过某些操作将变量的类型由一个较为宽泛的集合缩小到相对较小、较明确的集合

 let func = (anything: string | number) => {
    if (typeof anything === 'string') {
      return anything; // 类型是 string 
    } else {
      return anything; // 类型是 number
    }
  };

7、泛型

泛型就是通过给类型传参,得到一个更加通用的类型,就像给函数传参一样。 如下我们得到一个通用的泛型类型 T1,通过传参,可以得到 T2 类型 string[]、T3 类型 number[]; T 是变量,我们可以用任意其他变量名代替他。

type T1<T> = T[]
type T2 = T1<string> // string[]
type T3 = T1<number> // number[]

1. 常见泛型定义

  • T:表示第一个参数
  • K: 表示对象的键类型
  • V:表示对象的值类型
  • E:表示元素类型

2. 泛型接口

泛型接口和上述示例类似,为接口类型传参:

interface I1<T, U> {
  name: T;
  age: U;
}

type I2 = I1<string, number>

3. 泛型约束

Typescript 通过 extends 实现类型约束。让传入值满足特定条件;

interface IWithLength {
  length:number
}

function echoWithLength<T extends IWithLength>(arg:T):T{
  console.log(arg.length)
  return arg
}

echoWithLength('str')

通过 extends 约束了 K 必须是 T 的 key。

function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

let tsInfo = {
    name: "Typescript",
    supersetOf: "Javascript",
}

let supersetOf: string =
    getProperty(tsInfo, 'supersetOf'); // OK

let superset_of: string =
    getProperty(tsInfo, 'superset_of'); // Error

4. 泛型参数默认值

泛型参数默认值,和函数参数默认值一样,在没有传参时,给定默认值。

interface I4<T = string> {
  name: T;
}
const S1: I4 = { name: '123' } // 默认 name: string类型
const S2: I4<number> = { name: 123 }

5. 泛型工具类型

  • typeof

    typeof 的主要用途是在类型上下文中获取变量或者属性的类型;
    interface Person {
      name: string;
      age: number;
    }
    const sem: Person = { name: "semlinker", age: 30 };
    type Sem = typeof sem; // type Sem = Person
    
    // 使用Sem类型
    const lolo: Sem = { name: "lolo", age: 5 } 
  • keyof

    keyof 可以用于获取某种类型的所有键,其返回类型是联合类型。
    interface Person {
      name: string;
      age: number;
    }
    
    type K1 = keyof Person; // "name" | "age"
    type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
    type K3 = keyof { [x: string]: Person };  // string | number
  • 映射类型

    映射类型,它是一种泛型类型,可用于把原有的对象类型映射成新的对象类型。
    interface TestInterface{
        name:string,
        age:number
    }
    
    // 我们可以通过+/-来指定添加还是删除
    // 把上面定义的接口里面的属性全部变成可选
    type OptionalTestInterface<T> = {
      [p in keyof T]+?:T[p]
    }
    
    // 再加上只读
    type OptionalTestInterface<T> = {
      +readonly [p in keyof T]+?:T[p]
    }
    
    type newTestInterface = OptionalTestInterface<TestInterface>
  • Partial

    Partial 将类型的属性变成可选
    type Partial<T> = {
      [P in keyof T]?: T[P]
    }
    
    type T1 = Partial<{
      name: string,
    }>
    
    const a: T1 = {} // 没有name属性也不会报错
  • Required

    Required 将泛型的所有属性变为必选。
    // 语法-?,是把?可选属性减去的意思
    type Required<T> = {
      [P in keyof T]-?: T[P]
    }
    type T2 = Required<{
      name?: string,
    }>
    
    // ts报错,类型 "{}" 中缺少属性 "name",但类型 "Required<{ name?: string | undefined; }>" 中需要该属性。ts(2741)
    const b: T2 = {}
  • Readonly

    Readonly 将泛型的所有属性变为只读。
    type T3 = Readonly<{
      name: string,
    }>
    
    const c: T3 = {
      name: 'tj',
    }
    
    c.name = 'tj1' // ts 报错,无法分配到 "name" ,因为它是只读属性。ts(2540)
  • Pick

    Pick 从类型中选择一下属性,生成一个新类型。
    // 例子一
    type Pick<T, K extends keyof T> = {
      [P in K]: T[P]
    }
    
    type T4 = Pick<
      {
        name: string,
        age: number,
      },
      'name'
    >
    
    /*
    这是一个新类型,T4={name: string}
    */
    const d: T4 = {
      name: 'tj',
    }
    
    // 例子二
    interface Todo {
      title: string;
      description: string;
      completed: boolean;
    }
    
    type TodoPreview = Pick<Todo, "title" | "completed">;
    
    const todo: TodoPreview = {
      title: "Clean room",
      completed: false,
    };
  • Record

    Record 将 key 和 value 转化为 T 类型。
    // 例子一
    type Record<K extends keyof any, T> = {
      [key in K]: T
    }
    
    const e: Record<string, string> = {
      name: 'tj',
    }
    
    const f: Record<string, number> = {
      age: 11,
    }
    
    
    // 例子二
    interface PageInfo {
      title: string;
    }
    
    type Page = "home" | "about" | "contact";
    
    const x: Record<Page, PageInfo> = {
      about: { title: "about" },
      contact: { title: "contact" },
      home: { title: "home" },
    }; 
  • Exclude

    Exclude 将某个类型中属于另一个的类型移除掉。
    type Exclude<T, U> = T extends U ? never : T;
    
    type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
    type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
    type T2 = Exclude<string | number | (() => void), Function>; // string | number
  • Extract

    Extract 从 T 中提取出 U。
    type Extract<T, U> = T extends U ? T : never;
    
    type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
    type T1 = Extract<string | number | (() => void), Function>; // () =>void
  • Omit

    Omit 使用 T 类型中除了 K 类型的所有属性,来构造一个新的类型。
    type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
    
    interface Todo {
      title: string;
      completed: boolean;
      description: string;
    }
    
    type TodoPreview = Omit<Todo, "description">;
    
    /*
    {
      title: string;
      completed: boolean;
    }
    */
  • Parameters

    Parameters<T> 的作用是用于获得函数的参数类型组成的元组类型。
    funtion test(id:number, name:string){ ... }
    type TestArgsType = Parameters<typeof test>;
    /*
    [id:number,name:string]
    */
  • ReturnType

    用来得到一个函数的返回值类型。
    type Func = (value: number) => string; 
    const foo: ReturnType<Func> = "xxx";

8、类型别名与接口区别

1. 定义

接口:接口的作用就是为这些类型命名和为你的代码或第三方代码定义数据模型。

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

2. Objects / Functions

两者都可以用来描述对象或函数的类型,但是语法不同。

// interface
interface Point {
  x: number;
  y: number;
}

interface SetPoint {
  (x: number, y: number): void;
}

// type
type Point = {
  x: number;
  y: number;
};

type SetPoint = (x: number, y: number) => void;

3. type定义其他类型

与接口不同,类型别名还可以用于其他类型,如基本类型(原始值)、联合类型、元组。

// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

// dom
let div = document.createElement('div');
type B = typeof div;

4. 接口可以定义多次,类型别名不可以

与类型别名不同,接口可以定义多次,会被自动合并为单个接口。

interface Point { x: number; }
interface Point { y: number; }
const point: Point = { x: 1, y: 2 };

5.扩展

两者都可以扩展,接口通过 extends 来实现。类型别名的扩展就是交叉类型,通过 & 来实现。

// 接口扩展接口
interface PointX {
    x: number
}

interface Point extends PointX {
    y: number
}

// 类型别名扩展类型别名
type PointX = {
    x: number
}

type Point = PointX & {
    y: number
}

// 接口扩展类型别名
type PointX = {
    x: number
}
interface Point extends PointX {
    y: number
}

// 类型别名扩展接口
interface PointX {
    x: number
}
type Point = PointX & {
    y: number
}

posted on 2024-03-25 13:58  梁飞宇  阅读(1055)  评论(0)    收藏  举报