数据类型
- 简单声明定义
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类型表示的是那些永不存在的值的类型,值会永不存在的两种情况:
- 如果一个函数执行时抛出了异常,那么这个函数永远不存在返回值(因为抛出异常会直接中断程序运行,这使得程序运行不到返回值那一步,即具有不可达的终点,也就永不存在返回了
- 函数中执行无限循环的代码(死循环),使得程序永远无法运行到函数返回值那一步,永不存在返回。
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
含有类型 无类型
函数重载 无函数重载