ts

数据类型

  • 简单声明定义
let str: string = "jimmy";
let num: number = 24;
let bool: boolean = false;
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 2};
let big: bigint = 100n;
let sym: symbol = Symbol("me"); 
/**
 * 1 number和bigint 虽然number和bigint都表示数字,但是这两个类型不兼容,互相赋值会抛出一个类型不兼容的 ts(2322) 错误。
 * 2、null和undefined 默认情况下 null 和 undefined 是所有类型的子类型。 就是说你可以把 null 和 undefined 赋值给其他类型。
 * 如果你在tsconfig.json指定了"strictNullChecks":true ,null 和 undefined 只能赋值给 void 和它们各自的类型。
 */
  • 对数组类型的定义有两种方式:类型[] | Aarray<类型>
//简单类型定义
let arr: string[] = ["a","b“];
let arr2: Array<string> = ["a","b"];
//联合类型定义
let arr: (number | boolean)[] = [1,true];
let arr: Array<number | boolean> =  [1,true]

//定义复杂的类型:interface是接口 给元素或者对象 定义指定成员 

interface info {
  height: number;
  weight: number;
}
interface Arrobj {
  name: string;
  info: info;
  hobbies: Array<string>;
  info2: Array<info>;
  info3: info[];
}
let arr_1: Arrobj[] = [
  {
    name: "jimmy",
    info: { height: 100, weight: 100 },
    hobbies: ["basketball", "TV"],
    info2: [{ height: 1, weight: 2 }],
    info3: [{ height: 1, weight: 2 }],
  },
];
// 联合
let arr_2: (Arrobj | string)[] = [ // Array<(Arrobj | string)>
  {
    name: "jimmy",
    info: { height: 100, weight: 100 },
    hobbies: ["basketball", "TV"],
    info2: [{ height: 1, weight: 2 }],
    info3: [{ height: 1, weight: 2 }],
  },
  "1",
];
  • type类型别名,给自定义类型起个新名字。或者 字符串字面量类型用来约束取值只能是某几个字符串中的一个。所以 类型别名常用于联合类型
type order = string;//指定类型string
type order = string | number;//指定类型 string | number
type Point3 = {
  x: number;
  y: number;
};
let obj22:Point3 = {
  x: 1,
  y: 1,
}
type SetPoint3 = (x: number, y: number) => void;

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

// union
type PartialPointX = { x: number };
type PartialPointY = { y: number };
type PartialPoint = PartialPointX | PartialPointY;//表示对象可以x属性number类型,y属性number类型,同时跟任意一个存在都行,但是不能一个都不存在
let obxj2222:PartialPoint = {
  x:1,
  y:1,
}
// 定于tuple类型
type Data = [number, string];
let arrFDS:Data = [1,"2"]
//  定于dom类型
let div = document.createElement("div");
type B = typeof div;
// 扩展
// 两者的扩展方式不同,但并不互斥。接口可以扩展类型别名,同理,类型别名也可以扩展接口。
// 接口的扩展就是继承,通过 extends 来实现。类型别名的扩展就是交叉类型,通过 &(并集) 来实现。
interface info {
  height: number;
  weight: number;
}
type order= info;// 指定类型 对象 height 跟weight属性
type order= 1 | 2;// 指定类型 对象 height 跟weight属性
interface info {
  height: number;
  weight: order;
}
// 接口扩展接口
interface PointX {
  x: number;
}

interface Pointe extends PointX {
  y: number;
}

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

type Pointg = Pointf & {
  y: number;
};
// 接口扩展类型别名
type Pointh = {
  x: number;
};
interface Pointj extends Pointh {
  y: number;
}

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

  • enum枚举类型,可循环,可通过下标,属性名访问:用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
enum Days {
  Sun, Mon, Tue, Wed, Thu, Fri, Sat
}
let weeks : Days = Days.Wed;
// console.log(Days["Sun"] === 0); // true
//通过下标反向映射
let week = Days [0]; // Sun
let weekIndex= Direction["Sun"]; // 0
//也可手动赋值
enum FetchMethods{
  POST = "post",
  GET = "get",
}
console.log(FetchMethods["POST "] === ''post"); 
//异构枚举,异构部分只能在后面
enum Enum {
  A,
  B,
  C = "C",
}
enum 作为类型
enum Days {
  Sun, Mon, Tue, Wed, Thu, Fri, Sat = "Sat"
}
let a: Days = 1;
let a1 =  Days [0];
let a2 =  Days ["Sat"];
// let week:Days = Days2 [0]; // err week的的指定类型Days,取值只能是下标或者对应的值
// let week:Days = "Mon"; // err week的的指定类型Days,取值只能是下标或者对应的值

let week0:Days = Days.Sun; //ok  0 week的的指定类型Days,取值只能是下标或者对应的值
let week6:Days = Days.Sat; //ok  Sat  week的的指定类型Days,取值只能是下标或者对应的值

函数

/**
 * 函数声明
 * x: number, y: number 参数类型 ():number返回参数类型
 */
function sum(x:number,y:number) :number{
  return x + y;
}
/**
 * 函数表达式
 */
let sum2:(x:number,y:number) => number
sum = sum2
let sum2:(x:number,y:number) => number = function (x: number, y: number): number {
  return x + y;
};
/**
 * 用接口定义函数类型
 */
interface SearchFunc {
  (x:number,y:number) :number
}
let sum:SearchFunc  =  function (x: number, y: number): number {
  return x + y;
};
/**
 * 可选参数:在参数后面 + ?
 */
function sum(x:number,y?:number) :number{
  return x + y;
}
/**
 * 可选参数:默认参数
 */
function sun(x:number,y:number = 1) :number{
}
/**
 * 剩余参数,ES6中,可以使用...arg的方式获取函数中的剩余参数arg是个类数组,...扩展运算符转数组
 */
function sun(x:number,...arg:number[]):number{

}
sun(1,2,3)

TypeScript 数组

  • 数组解构
let x: number;
let y: number; 
let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;
  • 数组展开运算符,,摊开
let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];
  • 数组遍历
let colors: string[] = ["red", "green", "blue"];
for (let i of colors) {
  console.log(i);
}

TypeScript 对象

  • 对象解构
let person = {
  name: "Semlinker",
  gender: "Male",
};

let { name, gender } = person;

  • 对象展开运算符,摊开
let person = {
  name: "Semlinker",
  gender: "Male",
  address: "Xiamen",
};

// 组装对象
let personWithAge = { ...person, age: 33 };

// 获取除了某些项外的其它项
let { name, ...rest } = person;

Tuple元组

  • Tuple元组:数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。元组最重要的特性是可以限制数组元素的个数和类型,它特别适合用来实现多值返回。
// 元组用于保存定长定数据类型的数据
let arr:[string,number];//数组arr必须是string类型或者number类型,且长度是2,否则报错,如果一个数组中可能有多种类型,数量和类型都不确定,那就直接any[]
/**
 * 元组类型的解构赋值
 * 我们可以通过下标的方式来访问元组中的元素,元组也是支持解构赋值
 */
let arr:[number,string] = [1,"str"]
let [id,name] = arr
console.log(`id: ${id}`);//1
console.log(`name: ${name}`);//str
在解构赋值时,如果解构数组元素的个数是不能超过元组中元素的个数,否则也会出现错误,比如:
let [id,name,hobby] = arr

/**
 * 元组类型的可选元素
 * boolean ?表示这个元素是可选的,可要可不要
 */
let arr :[string,number?];
/**
 * 元组类型的剩余元素,形式为 ...X,这里 X 是数组类型,也可是自定义类型,接口
 */
type RestTupleType = [number, ...string[]];
let restTuple: RestTupleType = [666, "Semlinker", "Kakuqo", "Lolo"];
/**
 * 只读的元组类型readonly 以使其成为只读元组。具体的示例如下:
 */
const point: readonly [number, number] = [10, 20];

void

  • void表示没有任何类型,和其他类型是平等关系,不能直接赋值,(除非在strictNullChecks未指定为true时,只能为它赋予null和undefined)。声明一个void类型的变量没有什么大用,我们一般也只有在函数没有返回值时去声明。
 let a1: void; 
 a = 1//报错
function fun2(): void {//没有return默认返回 undefined,但是也不能把void改成undefined,返回值需要定义成void类型,而不是undefined类型
  console.log("this is TypeScript");
};
fun2();

never

  • never类型表示的是那些永不存在的值的类型,值会永不存在的两种情况:
  1. 如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了
  2. 函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。

unknown

  • unknown与any的最大区别是: 任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给unknown和any
let notSure: unknown = 4;
notSure = "maybe a string instead"; // OK
notSure = false; // OK

let notSure3: unknown = 4;
let uncertain3: number = notSure3; // Error,unknown 只能赋值给unknown和any

// 如果不缩小类型,就无法对unknown类型执行任何操作:
function getDog() {
  return '123'
 }
 const dog: unknown = {hello: getDog};//可以正常赋值,但是
 dog.hello(); // Error
//这种机制起到了很强的预防性,更安全,这就要求我们必须缩小类型,我们可以使用typeof、类型断言等方式来缩小未知范围:

对象

  • 小object 代表的是所有非原始类型,也就是说我们不能把 number、string、boolean、symbol等 原始类型赋值给 object。在严格模式下,null 和 undefined 类型也不能赋给 object,JavaScript 中以下类型被视为原始类型:string、boolean、number、bigint、symbol、null 和 undefined,引用类型为非原始类型,只有非原始类型可以赋值
  • 大Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。同样,在严格模式下,null 和 undefined 类型也不能赋给 Object
  • {}空对象类型和大 Object 一样,也是表示原始类型和非原始类型的集合,并且在严格模式下,null 和 undefined 也不能赋给 {}
let lowerCaseObject: object;
lowerCaseObject = 1; // error
let ObjectLiteral:Object 或者  {}  ;
ObjectLiteral = 1; // ok

let lowerCaseObject2: object;
let lowerCaseObject3: Object;
lowerCaseObject2 = lowerCaseObject3// ERROE,原始类型不能赋值给小o,小o不能赋值于大O
let lowerCaseObject4: {};
lowerCaseObject2 = lowerCaseObject4// ERROE,非原始类型可以复制小o,但是lowerCaseObject未赋值就使用,但是只要赋值就可以
let lowerCaseObject4: {} = {}
lowerCaseObject2 = lowerCaseObject4//ok
let lowerCaseObject5: Object = {};

//大O赋值小o不行,小o赋值大O可以
//通过extends 尽管可以互相赋值,extends 也为true,但是Object 并不完全等价于小 object,{}同理
type isLowerCaseObjectExtendsUpperCaseObject = object extends Object ? true : false; // true
type isUpperCaseObjectExtendsLowerCaseObject = Object extends object ? true : false; // true
upperCaseObject = lowerCaseObject; // ok
lowerCaseObject = upperCaseObject; // ok

type isLiteralCaseObjectExtendsUpperCaseObject = {} extends Object ? true : false; // true
type isUpperCaseObjectExtendsLiteralCaseObject = Object extends {} ? true : false; // true
upperCaseObject = ObjectLiteral;// ok
ObjectLiteral = upperCaseObject;// ok

//大赋值小不行,小赋值大可以 其他简单类型同理
 let numl: number;
 let Numl: Number;
//  Numl = numl; // ok
//  num = Num; // ts(2322)报错

/**
 * 在示例中的第 3 行,我们可以把 number 赋给类型 Number,但在第 4 行把 Number 赋给 number 就会提示 ts(2322) 错误。因此,我们需要铭记不要使用对象类型来注解值的类型,因为这没有任何意义。
 */

类型推断

let str: string = 'this is string';
//TypeScript 会根据上下文环境自动推断出变量的类型,无须我们再写明类型注解。
let str = 'this is string'
//函数也一样
  /** 根据参数的类型,推断出返回值的类型也是 number */
  function add(a: number, b: number) {
    return a + b;
  }
const x1= add1(1, 1); // 推断出 x1 的类型也是 number
// 如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查,可以赋值任何类型
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

类型断言, 推断具体什么类型

  • 变量或者返回值 as 具体类型
  • 尖括号 语法 <具体类型>或者返回值
  • TypeScript 类型检测无法做到绝对智能,有时会碰到我们比TypeScript 更清楚实际类型的情况
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2); // 报错 ts(2322)
//为什么呢,find可能会返回一个 > 2的数,但是数组并没有  > 2 的 就返回undefined,此时我们不能把类型 undefined 分配给类型 number。
const arrayNumber: number[] = [1, 2, 3, 4];
const greaterThan2: number = arrayNumber.find(num => num > 2) as number;

let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
对象
interface Props {
  name: string;
  age: number;
  money?: number;
}

let p: Props = {
  name: "兔神",
  age: 25,
  money: -100000,
  girl: false,
} as Props; // OK,告诉程序不需要属性检查,绕开属性检查
以上两种方式虽然没有任何区别,但是尖括号格式会与react中JSX产生语法冲突,因此我们更推荐使用 as 语法。
/**
 * 非空断言:无法断定类型,可以用于断言操作对象是非 null 和非 undefined 类型
 */
忽略 undefined 和 null 类型
function myFunc(numGenerator: NumGenerator | undefined) {
  // const num1 = maybeString; // undefined() Error 
  const num2 = numGenerator!; //OK
}
调用函数时忽略 undefined 类型

type NumGenerator = () => number;
function myFunc(numGenerator: NumGenerator | undefined) {
// Object is possibly 'undefined'.(2532)
// Cannot invoke an object which is possibly 'undefined'.(2722)
const num1 = numGenerator(); // Error
const num2 = numGenerator!(); //OK
}

//确定赋值断言,变量声明后面放置一个 ! 号,表示属性会被明确地赋值
let x: number;
console.log(2 * x1); // Error,x在赋值前使用了
x1 = 10;

//确定!赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。
let x!: number;
console.log(2 * x1); // ok
x1 = 10;

类型守卫

  • in关键字,判断是否含有某个属性
interface Admin {
  name: string;
  privileges: string[];
}
interface Employee {
  name: string;
  startDate: Date;
}
type UnknownEmployee = Employee | Admin;
function printEmployeeInformation(emp: UnknownEmployee) {
  console.log("Name: " + emp.name);
  if ("privileges" in emp) {
    console.log("Privileges: " + emp.privileges);
  }
  if ("startDate" in emp) {
    console.log("Start Date: " + emp.startDate);
  }
}
  • typeof 关键字,判类类型
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
      return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

  • instanceof 关键字
    在TypeScript中, instanceof 运算符与JavaScript中的作用相同,用于检查对象的原型链上是否存在构造函数的 prototype 属性。
interface Person{
  getPaddingString(): string;   //定义一个方法,返回值是string
  constructor(public name: string) { }
}
class Worker {
    constructor(public job: string) { }
}
const person= new Person('John');
const worker = new Worker('Builder');
console.log(person instanceof Person); // true
console.log(worker instanceof Worker); // true
console.log(person instanceof Worker); // false
在这个例子中,john instanceof Person将返回true因为person 是Person的实例。worker instanceof Worker也是一样的。而worker instanceof Worker将返回false因为worker 不是Worker的实例。
TypeScript中instanceof的使用方法和JavaScript完全相同,但在使用时需要确保左侧对象不是null或undefined,否则会抛出运行时错误。在这个例子中,checkInstance函数首先检查对象是否为null或undefined,然后再使用instanceof来判断对象是否为Person的实例。
function checkInstance(obj: any): void {
    if (obj === null || obj === undefined) {
        console.log('The object is null or undefined.');
    } else if (obj instanceof Person) {
        console.log('The object is an instance of Person.');
    } else {
        console.log('The object is not an instance of Person.');
    }
}
 
checkInstance(john); // The object is an instance of Person.
checkInstance(null); // The object is null or undefined.
checkInstance(undefined); // The object is null or undefined.

// 类型收窄
class SpaceRepeatingPadder implements person{
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}

class StringPadder implements person{
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}
let person: Person= new SpaceRepeatingPadder(6);
if (padder instanceof SpaceRepeatingPadder) {
  // padder的类型收窄为 'SpaceRepeatingPadder',有点像typeOf
}
  • 自定义类型保护的类型谓词
function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

字面量类型

  • 字面量可以表示值,也可以表示类型,
  • 字面量类型有三种:字符串字面量类型、数字字面量类型、布尔字面量类型
  • 定义单个的字面量类型并没有太大的用处,它真正的应用场景是可以把多个字面量类型组合成一个联合类型,用来描述拥有明确成员的实用的集合
  let specifiedStr: 'this is string' = 'this is string';
  let specifiedNum: 1 = 1;
  let specifiedBoolean: true = true;
//但是字面量类型跟数据类型比并不相同,他们属于包含关系,'this is string'类型 相当于string类型的子类型,所以字面量类型可以赋值给正常数据类型,反之不行
  let specifiedStr: 'this is string' = 'this is string';
  let specifiedStr: 'this is string' = 'this is string1';//ERROE,this is string1不能匹配this is string

  let str: string = 'any string';
  // specifiedStr = str; // ERROR,ts(2322) 类型 '"string"' 不能赋值给类型 'this is string'
  str = specifiedStr; // ok 
  //字面量类型组合成一个联合类型
  type Direction = 'up' | 'down';
// 接口成员 字面量类型
  interface Conifg{
     size:"mini" | "small" | "big",
     margin: 0 | 2 | 4;
  }
 // const 声明,在缺省类型注解的情况下,类型推断成 本身值,let不是
  const str = 'this is string'; // str类型: 'this is string'
  let str = 'this is string'; // str类型: string

类型拓宽

  • 类型拓宽当无法精确知道一个类型变量的具体类型时,就会把改变量的类型推断为它的拓展类型。拓宽通常发生在使用泛型或者联合类型时,当类型不能精确匹配时,编译器会选择一个更宽泛的类型来代表这些不确定的类型。
  • 通过 let 或 var 定义的变量、函数的形参、对象的非只读属性,如果赋予了初始值但是未添加类型注解,那么它们推断出来的类型就是指定的初始值字面量类型,这也是字面量类型拓宽。
// 联合类型的类型拓宽
type A = string | number;
type B = A; // B 的拓宽类型是 `string | number`
// 泛型类型的类型推断与拓宽
function genericFunction<T>(arg: T) {
    let widened: T;  // 泛型变量T的拓宽类型是any
    widened = "string" as any;  // 显式地将类型标记为any以展示类型拓宽
}
 
// 泛型类型约束下的类型拓宽
function constrainedFunction<T extends string | number>(arg: T) {
    let widened: T;  // 约束下,泛型变量T的拓宽类型是string | number
    widened = "string" as string | number;  // 显式地将类型标记为string | number
}

let str = 'this is string'; // 类型推断是 string
let strFun = (str = 'this is string') => 类型推断是  string;
const specifiedStr = 'this is string'; // 类型推断是 'this is string'
let str2 = specifiedStr; // 类型是 'string'
let strFun2 = (str = specifiedStr) => str; // 类型是 string;
let x = null; // 类型拓宽成 any,旧版本null 和 undefined 并不会被拓宽成“any”
let y = undefined; // 类型拓宽成 any
const z = null; // 类型还是 null
let anyFun = (param = null) => param; // 类型是 null
let z2 = z; // 类型是 null
let x2 = x; // 类型是 null
let y2 = y; // 类型是 undefined

//坑
interface Vector3 {
    x: number;
    y: number;
    z: number;
  }
function getComponent(vector: Vector3, axis: "x" | "y" | "z") {
    return vector[axis];
 }
 let xll:string = "x";
 let vec = { x: 10, y: 20, z: 30 };
 // 类型“string”的参数不能赋给类型“"x" | "y" | "z"”的参数。
 getComponent(vec, xll); // Error
//为什么会出现上述错误呢?通过 TypeScript 的错误提示消息,我们知道是因为变量 x 的类型被推断为 string 类型,
//而 getComponent 函数期望它的第二个参数有一个更具体的类型,为"x" | "y" | "z" 字面量类型。这在实际场合中被拓宽了,所以导致了一个错误。如果需要纠正的话:   let xll:'x' = "x" 
const arr = ['x', 1];
// 上述 arr 变量的类型应该是什么?这里有一些可能性:
// ('x' | 1)[]
// ['x', 1]
// [string, number]
// readonly [string, number]
// (string | number)[]
// readonly (string|number)[]
// [any, any]
// any[]

// 没有更多的上下文,TypeScript 无法知道哪种类型是 “正确的”,它必须猜测你的意图。尽管 TypeScript 很聪明,但它无法读懂你的心思。它不能保证 100% 正确,正如我们刚才看到的那样的疏忽性错误。

/**
 * TypeScript 提供了一些控制拓宽过程的方法。其中一种方法是使用 const。如果用 const 而不是 let 声明一个变量,那么它的类型会更窄。事实上,使用 const 可以帮助我们修复前面例子中的错误:利用const不能重新赋值
 */
const x = "x"; // type is "x" ,而非字符串
let vec = { x: 10, y: 20, z: 30 };
getComponent(vec, x); // OK
然而,const 并不是万灵药。对于对象和数组,仍然会存在问题。
const obj = { 
    x: 1,
  };
obj.x = 6; // OK 
obj.x = '6'; // Error,Type '"6"' is not assignable to type 'number'.
怎么解决呢
const obj: { x: number | string } = {
    x: 1 
  };
  // Type is { x: 1; y: number; }
const obj2 = {
    x: 1 as const,
    y: 2,
  }; 
  obj2.x =1//ok
 obj2.x =2//error
  obj2.x =4//ok
  obj2.x ="11"//err
  // Type is { readonly x: 1; readonly y: 2; }
  const obj3 = {
    x: 1, 
    y: 2 
  } as const;
 // Type is number[]
const arr1 = [1, 2, 3]; 
// Type is readonly [1, 2, 3]
const arr2 = [1, 2, 3] as const;

类型缩小,

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

any

  • any 与 unknown的最大区别是: 任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给unknown和any

联合类型、类型别名、交叉类型

  • 联合类型表示取值可以为多种类型,使用 | 分隔
let myFavoriteNumber: string | number;
let num2: 1 | 2 = 1;
type Message = string | string[];
type EventNames = "click" | "scroll" | "mousemove";
  • 类型别名用来给一个类型起个新名字。类型别名常用于联合类型
type Message = string | string[];
  • 交叉类型是将多个类型合并为一个类型,使用&定义交叉类型。
type Useless = string & number;
// 交叉类型真正的用武之地就是将多个接口类型合并成一个类型,从而实现等同接口继承的效果,也就是所谓的合并接口类型,如下代码所示:
type IntersectionType = { id: number; name: string; } & { age: number };
const mixed: IntersectionType = {
  id: 1,
  name: 'name',
  age: 18
}
// 在上述示例中,我们通过交叉类型,使得 IntersectionType 同时拥有了 id、name、age 所有属性,这里我们可以试着将合并接口类型理解为求并集。
-  同名基础类型属性的合并
interface X {
  c: string;
  d: string;
}

interface Y {
  c: number;
  e: string
}

type XY = X & Y;
type YX = Y & X;

let p: XY;
let q: YX;
//此时 XY 类型或 YX 类型中成员 c 的类型是不是可以是 string 或 number 类型呢?
这是因为混入后成员 c 的类型为 string & number,即成员 c 的类型既可以是 string 类型又可以是 number 类型。很明显这种类型是不存在的,所以混入后成员 c 的类型为 never。
- 同名非基础类型属性的合并
interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }


interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

let abc: ABC = {
  x: {
    d: true,
    e: 'semlinker',
    f: 666
  }
};

console.log('abc:', abc);
由上图可知,在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。

interface是接口 给元素或者对象 定义指定成员

interface Person {
  name: string;
  age: number;
}
let jack: Person = {
  name: "Tom",
  age: 25,
};
// 给变量定义接口:少一些属性也是不允许的;/多一些属性也是不允许的:
let tom: Person = {
  name: 'Tom'
};
let tom: Person = {
   name: 'Tom',
   age: 25,
   gender: 'male'
};
//可见,赋值的时候,变量的形状必须和接口的形状保持一致。
// 但是也提供了只读可选
interface Person {
  readonly name: string; //只读,只能在对象刚刚创建的时候修改其值
  age2?: number; //可选
}
//数组还提供了个只读的类,它与 Array<T> 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。
let a3: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = [1, 2, 3, 4];;
// ro[0] = 12; // error!
// ro.push(5); // error!
// ro.length = 100; // error!
// a3 = ro; // error!

任意属性:可以在对象中添加任意类型的属性

// 有时候我们希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性,这时我们可以使用 索引签名 的形式来满足上述要求。
// 索引签名语法:[propName: string]: any 或者 [propName: string]:其他类型, 也可以是联合类型 [propName: string] :string | number
// any 或者其他类型,或者联合类型 就是添加的属性的类型,同时确定属性和可选属性的类型都必须是它的子集
// [propName: string] string是对象key的类型,接收,string,number,symbol


interface Person2 {
  name: string;//必选
  age?: number;//可选
  [propName: string]: any; //任意属性
}
let tom3: Person2 = {
  name: "Tom",
  gender: "male",//gender可以任意属性
  ...
};
//一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的子集。实例代码:
interface Person{
  name: string;
  age?:number;   // 报错,应该是string类型
                 // 因为number 是 number的子集但是不是string的子集,反过这里写一个string也不行
                 // 因为string 是 string的子集但是不是number的子集
  [propName: string]:string
}

//正确如下
interface Person{
  name: string;
  age?:number;
  [propName: string]:any;
}
//或者如下
interface Person{
  name: string;
  age?:number;
  [propName: string]:string | number | | undefined | null;
}
type ConsistentObject = {
  knownProp: boolean| number; // ERROR 这里的联合类型都必须是 string | number | undefined | nul的子集,其中一个不是都不行
  knownProp?: null | number; //OK 这里的联合类型都必须是 string | number | undefined | nul的子集,其中一个不是都不行
  [propName: string]: string | number | undefined | null;
};
//一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用any、联合类型
//索引签名会覆盖对象的所有已知属性

// 所谓的鸭式辨型法就是即具有鸭子特征的认为它就是鸭子
interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj); // OK

interface LabeledValue {
  label: string;
}
function printLabel(labeledObj: LabeledValue) {
  console.log(labeledObj.label);
}
printLabel({ size: 10, label: "Size 10 Object" }); // Error

// 上面代码,在参数里写对象就相当于是直接给labeledObj赋值,这个对象有严格的类型定义,所以不能多参或少参。而当你在外面将该对象用另一个变量myObj接收,myObj不会经过额外属性检查,但会根据类型推论为let myObj: { size: number; label: string } = { size: 10, label: "Size 10 Object" };,然后将这个myObj再赋值给labeledObj,此时根据类型的兼容性,两种类型对象,参照鸭式辨型法,因为都具有label属性,所以被认定为两个相同,故而可以用此法来绕开多余的类型检查。

接口和类型别名都可以用来描述对象的形状或函数签名:
接口
interface Point {
  x: number;
  y: number;
}
interface SetPoint {
  (x: number, y: number): void;
}
类型别名 注意
type Point = {
  x: number;
  y: number;
};
type SetPoint = (x: number, y: number) => void;
  • 接口与类型别名的区别
    接口只能是对象成员,类型别名type可以任意,接口能继承类型别名,类型别名不能,只能交叉类型合并&
    Declaration merging 接口重新定义会合并,类型别名重新定义则会覆盖
    interface Point { x: number; }
    interface Point { y: number; }
    const point: Point = { x: 1, y: 2 };

接口跟类型别名的继承Extend

  • 接口和类型别名都能够被扩展,但语法有所不同。此外,接口和类型别名不是互斥的。接口可以继承类型别名或者其他类,而反过来是不行的,类型别名通过&继承接口或者其他类型别名
//Interface extends interface
interface PartialPointX { x: number; }
interface Point extends PartialPointX { 
  y: number; //继承x,扩展y,Point自身的属性
}

//Type alias extends type alias
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };// 继承x,扩展y

//Interface extends type alias
type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }// 继承x,扩展y

//反过来类型继承接口不行
interface PartialPointX  { x: number}
type Point = { y: number};
type Pointx = Point extends PartialPointX{
  z:number
} 
//只能用 & 继承 Type alias extends interface
interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };// 继承x,扩展y

implements关键字

  • 接口或者指定类型定义了一组成员(方法、属性等),而通过 implements,你可以告诉 TypeScript 编译器,这个类将提供接口中指定的所有成员。但是联合类型不行,接口的属性在父类必须都定义,父类可以加
interface Point {
  x: number;
  y: number;
}

class SomePoint implements Point {
  //x = 1;//err不定义都不行,必须两个都在,也可以在constructor初始化赋值
  //y = 2;//err不定义都不行,必须两个都在,也可以在constructor初始化赋值
   z:3//可以加
}
class SomePoint implements Point {
  x: number;
  y: number;
  z:number;
  constructor(theName: string) {
    this.x = 1;
    this.y = 2;
    this.z = 3;
  }
}

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x = 1;
  y = 2;
}

type PartialPoint = { x: number; } | { y: number; };

// A class can only implement an object type or 
// intersection of object types with statically known members.
class SomePartialPoint implements PartialPoint { // Error
  x = 1;
  y = 2;
}

TypeScript 类

class Greeter {
  // 静态属性
  static cname: string = "Greeter";
  // 成员属性
  greeting: string;

  // 构造函数 - 执行初始化操作,方法不需要
  constructor(message: string) {
    this.greeting = message;
  }

  // 静态方法
  static getClassName() {
    return "Class name is Greeter";
  }

  // 成员方法
  greet() {
    return "Hello, " + this.greeting;
  }
}

let greeter = new Greeter("world");

  • private(私有的):是一个访问修饰符,它用于限制一个类的属性或方法的可见性。当一个属性或方法被标记为 private 时,它只能在其所在的类中被访问。private 访问修饰符是一种封装的方式,它可以保护类的内部状态,防止外部代码直接修改。
class Person {
  private name: string;
  constructor(name: string) {
    this.name = name;
  }
  public greet() {
    console.log(`Hello, my name is ${this.name}!`);
  }
}
//在这个例子中,name 属性只能在 Person 类中被访问,不能在 Person 类外部被访问。这意味着以下的代码会导致一个错误:
let john = new Person("John");
console.log(john.name); // Error: Property 'name' is private and only accessible within class 'Person'.
但是可以这样
let john = new Person("John");
john.greet(); //Hello, my name is John!
  • public(公共的) 是一个[访问修饰符],它用于指定类的属性或方法的可见性。当一个属性或方法被标记为 public 时,它可以在任何地方被访问,包括类的内部和外部。有指定访问修饰符,那么属性或方法默认为 public
  • static 用于定义类的静态成员属于类本身,而不是类的实例的成员。你可以直接通过类来访问静态成员,而不需要创建类的实例(实例也访问不了)。
class MyClass {
  static myStaticProperty = "Hello, world";
  static myStaticMethod() {
    return "Hello, world";
   console.log(myStaticProperty )//error
  console.log(MyClass.myStaticProperty  )//error
  }
}
//直接通过类访问
console.log(MyClass.myStaticProperty); // "Hello, world"
console.log(MyClass.myStaticMethod()); // "Hello, world" 通过类来访问静态成员
let MyClass  = new MyClass ()
console.log(MyClass.myStaticProperty); // error
  • 私有字段
class Person {
  #name: string;
  constructor(name: string) {
    this.#name = name;
  }
  greet() {
    console.log(`Hello, my name is ${this.#name}!`);
  }
}
let semlinker = new Person("Semlinker");
semlinker.#name;//error
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
//区别,#可以重复定义跟继承一样的属性,private不行
class A {
  #test = 2;
}
class B extends A {//ok
  #test = 1;
}

class C {
  private test = 2;
}
class D extends C {//err
  private test = 1;
}

访问器,定义set方法设置类的私有属性,get方法获取

let passcode = "Hello TypeScript";
class Employee {
  private _fullName: string;
  get fullName(): string {
    return this._fullName;
  }
  set fullName(newName: string) {
    if (passcode && passcode == "Hello TypeScript") {
      this._fullName = newName;
    } else {
      console.log("Error: Unauthorized update of employee!");
    }
  }
}
let employee = new Employee();
employee.fullName = "Semlinker";
if (employee.fullName) {
  console.log(employee.fullName);
}

继承 extends

class Animal {
  name: string;
  constructor(theName: string) {
   //在这里初始化,name,方法不需要
    this.name = theName;
  }
  
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Snake extends Animal {
  type:string//扩展自己的属性
  constructor(name: string) {
    this.type = "stu"
    super(name); // 调用父类的构造函数,继承父类方法属性
  }
  //去掉这个move,实例化直接调sam.move();,调的是Animal的move方法
  move(distanceInMeters = 5) {
    console.log("Slithering...");
   //调用父类
    super.move(distanceInMeters);
  }
}

let sam = new Snake("Sammy the Python");
sam.move();

抽象类

  • 使用 abstract 关键字声明的类,我们称之为抽象类。抽象类不能被实例化,主要用于定义一些通用的方法和行为,由子类去实现这些方法和行为。抽象类通常用于为子类提供一个模板,确保子类实现特定的接口或行为。
abstract class Person {
  constructor(public name: string){}
  // 抽象方法,没有实现  ,只是定义
  abstract say(words: string) :void;
  // 抽象类中的具体实现方法  
  move(): void {  
        console.log("The animal moves.");  
    }  
}
// Cannot create an instance of an abstract class.(2511)
const lolo = new Person(); // Error 抽象类不能被直接实例化

// 只能定义一个具体的子类,继承自抽象类  
class Developer extends Person {
  constructor(name: string) {
    super(name);
  }
  //实现父类的抽象方法say
  say(words: string): void {
    console.log(`${this.name} says ${words}`);
  }
}
const developer = new Developer("lolo");
developer.say("I love ts!"); // lolo says I love ts!
developer.move( ); // The animal moves.

类方法重载

//我们重载了 ProductService 类的 getProducts 成员方法:
class ProductService {
    getProducts(): void;
    getProducts(id: number): void;
    getProducts(id?: number) {
      if(typeof id === 'number') {
          console.log(`获取id为 ${id} 的产品信息`);
      } else {
          console.log(`获取所有的产品信息`);
      }  
    }
}

const productService = new ProductService();
productService.getProducts(666); // 获取id为 666 的产品信息
productService.getProducts(); // 获取所有的产品信息 ···

泛型

  • 当前的数据类型,同时也能支持未来的数据类型,他们就是个类型
    T
    K(Key):表示对象中的键类型;
    V(Value):表示对象中的值类型;
    E(Element):表示元素类型。

Extract
Extract<T, U> 的作用是从 T 中提取出 U。
type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a",没有逗号就是提出重复的
type T1 = Extract<string | number | (() => void), Function>; // () =>void
//提出Function,即是 () =>void
Omit
Omit<T, K extends keyof any> 的作用是使用 T 类型中除了 K 类型的所有属性,来构造一个新的类型。
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
NonNullable
NonNullable<T> 的作用是用来过滤类型中的 null 及 undefined 类型。
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
Parameters
Parameters<T> 的作用是用于获得函数的参数类型组成的元组类型。
type A = Parameters<() =>void>; // []
type B = Parameters<typeofArray.isArray>; // [any]
type C = Parameters<typeofparseInt>; // [string, (number | undefined)?]
type D = Parameters<typeofMath.max>; // number[]

https://juejin.cn/post/6844904184894980104?searchId=20240912213712A754520529CA3924B22B#heading-0

// keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。

interface Person4 {
  name: string;
  age: number;
}

type K1 = keyof Person4; // "name" | "age" //获取所有键的类型

type K2 = keyof Person4[]; //[{name:'',age:1},{name:'',age:1}]// "length" | "toString" | "pop" | "push" | "concat" | "join" //数组的方法
type K3 = keyof { [x: string]: Person4 };// string | number  TypeScript 中支持两种索引签名,数值索引和字符串索引则是数字
type K4 = Person4[keyof Person4];//string | number  TypeScript 中支持两种索引签名,数值索引和字符串索引数字
索引签名就是对象的key,数组下标
T, K extends keyof T, //keyof T,T 获取T所有的属性类型,
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}


  • TypeScript函数构造签名,直接返回一个 new class是不行的
定义构造器签名分为三种方法:
1、字面量定义
const myCalss3: new (name: string, age: number) => Teacher = Teacher;
2、接口字面量
const myCalss4: { new(name: string, age: number): Teacher } = Teacher;
3、接口定义构造器签名
// 接口中定义构造器(构造函数签名)
interface myCalss3 {
    new(name: string, age: number)
}
简单例子,类不能直接赋值,return,需要添加class类的类型才能验证
class Ctor {
    name: string
    constructor(s: string) {
        this.name = s
    }
}
type SomeContructor = {
    new (s: string): Ctor//构造器签名  
}
function fn(ctor: SomeContructor) {
    return new ctor('zhangsan.')
}
const f = fn(Ctor)
-------------------------------------------------------------------------------------
// 定义一个人的类
class Person {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    };
    run(): void {
        console.log('Person name => : ', this.name, this.age);
    }
}
// 定义一个老师的类
class Teacher extends Person {
    run(): void {
        console.log('Teacher name => ', this.name);
        super.run();
    }
}
// 定义一个学生的类
class Student extends Person {
    identity: string;
    constructor(name: string, age: number, identity: string) {
        super(name, age);
        this.identity = identity;
    };
    run(): void {
        console.log('Student name => :', this.name);
        super.run();
    };
    sayBoook(): void {
        console.log(`Student say => ${this.identity}: 床前明月光,疑是地上霜`);
    }
}

// 接口中定义构造器(构造函数签名) :人 和 老师
interface PersonConstructor {
    new(name: string, age: number):Person;
}
// 接口中定义构造器(构造函数签名) : 学生
interface StudentConstructor {
    new(name: string, age: number, identity: string):Student;
}
 // 接口中定义构造器(构造函数签名) : Teacher 
interface StudentConstructor {
    new(name: string, age: number):Teacher ;
}
// 使用构造器接口
function createPerson(ctor: PersonConstructor, name: string, age: number): Person {
    return new ctor(name, age);
}

function createStudent(ctor: StudentConstructor, name: string, age: number, identity: string): Student {
    return new ctor(name, age, identity);
}

// 使用示例
const teacher = createPerson(Teacher, "王老师", 35);
const student = createStudent(Student, "小明", 16, "班长");

// // 工厂函数加上泛型的使用。
// 接口字面量
function createPerson<T>(clazz: { new(name: string, age: number): T }, name: string, age: number): T {
    return new clazz(name, age);
}
//字面量 
function createPerson2<T>(clazz: new (name: string, age: number) => T, name: string, age: number): T {
    return new clazz(name, age);
}
// 接口
function createPerson3<T>(clazz: myInterface1, name: string = "defaultName", age: number = 18): T {
    return new clazz(name, age);
}
 
(
    function test() {
        const person1: Person = createPerson(Person, '张三', 25)
        const person2: Person = createPerson2(Person, '李四', 25)
        const person3: Person = createPerson3(Person, '王二')
        person1.run();// 为啥会报错啊
        person2.run();// 为啥会报错啊
        person3.run(); // 为啥会报错啊
 
        // 用接口interface定义构造器签名
        const myClass1: myInterface1 = Person;
        const myClass2: myInterface2 = Student;
        const tom = new myClass1('TOM', 20);
        const joker = new myClass2('Joker', 20, '学生');
        const joker2 = new myClass2('Joker2', 20, '学生');
        tom.run()
        joker.run();
        joker.sayBoook();
        joker2.run();
        joker2.sayBoook();
 
 
        //字面量 构造器签名
        const myCalss3: new (name: string, age: number) => Teacher = Teacher;
        // 接口字面量 构造器签名
        const myCalss4: { new(name: string, age: number): Teacher } = Teacher;
        const make = new myCalss4('Make', 30);
        const make2 = new myCalss3('make2', 30);
        make.run();
        make2.run();
 
    }
)()
 

// index.ts
export interface IFindPathwayLikeAllRes {
  createUserId: string;
  createUserName: string;
  createDate: string;
  updateUserId: string;
  updateUserName: string;
  updateDate: string;
  clinicalPathwayStateDTO: IClinicalPathwayStateDTO;
}

export interface IClinicalPathwayStateDTO {
  lastStatusId: string;
  lastStatusName: string;
  lastTime: string;
  isSubmit: string;
  submitTime: string;
  submitUserId: string;
  submitUserName: string;
  submitDesc: string;
  isCheckSubmit: string;
  checkSubmitTime: string;
  checkSubmitUserId: string;
  checkSubmitUserName: string;
  checkSubmitDesc: string;
}
使用
import { IClinicalPathwayInfoDTO, IFindPathwayLikeAllRes } from "@/api/ipCp/types";
const props = defineProps<{
   data?: IFindPathwayLikeAllRes;
}>;



//IResponse.ts
export interface IResponse<T> {
  rowCount: number;
  msg: string;
  code: string;
  data: T;
}
//IFindPathwayLikeAllRes .ts
export interface IFindPathwayLikeAllRes {
  createUserId: string;
  createUserName: string;
  createDate: string;
  updateUserId: string;
  updateUserName: string;
  updateDate: string;
  clinicalPathwayInfoDTO: IClinicalPathwayInfoDTO;
  clinicalPathwayStateDTO: IClinicalPathwayStateDTO;
}

//引用
import { IResponse } from "../index";
import { IFindPathwayLikeAllRes } from "./types";

//根据条件查询路径信息集合 (分页)
export const findPathwayList = (params: IFindPathwayInfoAllParams) => http.send<IResponse<IFindPathwayLikeAllRes[]>>(`${url.ipCp}/api/def/clinicalPathwayInfo/findPathwayList`, params, "POST");

//IFindPathwayLikeAllRes[]表示定义了一个数组,成员是IFindPathwayLikeAllRes,就是IResponse<T>的T

 details: {
 pid: string;
 }[];是啥意思
//定义了一个数组,数组中的每个元素都是一个对象,该对象有一个名为 pid 的属性,其类型为 string。

export interface IPEMRPageResponse<T> {
  code: string;
  currentPage: number;
  data: T;
  endIndex: number;
  error: Record<any, any>;
  indexCount: number;
  msg: string;
  nextPage: number;
  pageCount: number;
  pageSize: number;
  path: string;
  previousPage: number;
  requestId: string;
  rowCount: number;
  startIndex: number;
  status: string;
  success: boolean;
  timestamp: string;
}
定义了一个数组,数组中的每个元素都是一个对象,该对象有一个名为 pid 的属性,其类型为 string。

TypeScript 函数与 JavaScript 函数的区别
TTypeScript JavaScript
含有类型 无类型
函数重载 无函数重载


image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容