前端TS基础
文章目录
TypeScript 可以看成是 JavaScript 的超集(superset),即它继承了后者的全部语法,添加了一个独立的类型系统
一、类型
1、定义类型
- js的类型是动态的,变量定义以后,可以赋值另一个类型的值,这导致了不确定性;
// 报错
// 不能将类型“number”分配给类型“string”。
let bar:string = 123;
// 类型推断为string; 鼠标放在sayHello函数上,查看函数定义,可以看到
// 返回值类型被推断;
// (local function) sayHello(name: string | number): string
function sayHello(name:string | number){
return name + "";
}
sayHello('zhangsan')
2、any、unknown、never
// any类型
// 加了any类型,感觉是回到了js,当然一般的项目会限制使用any类型
let anyVar: any = 123;
let anyVarUndefined: any;
anyVar = "hello world";
let anyVarbar = anyVar;
console.log(anyVar); // 输出hello world
console.log(anyVarUndefined); //不会报错,any类型可以赋值undefined
console.log(anyVarbar + 123); // any类型变量赋值给其他变量,会导致其他变量也变成any类型
// 不给变量定义类型,那么等于any类型
// (local function) getNumber(count1: any, count2: any): any
function getNumber(count1, count2){
return count1 + count2;
}
// unknown
// 类型感觉只是对值的类型规定,而不能再后续正常使用
let num: unknown = 1;
num = num + 1; // 报错:“num”的类型为“未知”。
// 在判断中限制unknown的类型为具体类型,就可以使用了
if(typeof num === 'number'){
num = num + 1
}
// never类型
let neverVar: never;
// 空类型,不能在任何地方使用
neverVar = neverVar + 1; // 报错:不能将类型“number”分配给类型“never”。
// 一般用于函数永不返回的情况,比如抛出异常
function f(): never {
throw new Error('Error');
// 注意当你返回值类型为never,依旧return语句会报错
// 需要使用as 类型推定
return undefined as never;
}
// never 是其他类型子集,所以可以赋值给其他类型
// never是底层类型,在这之上可以再次定义类型
let v1:number = f(); // 不报错
let v2:string = f(); // 不报错
3、基础类型
// 基础类型
// string\number\boolean\null\undefined\symbol
let str:string = "hello world";
let num1:number = 123;
let bool:boolean = true;
let nullVar:null = null;
let undefinedVar:undefined = undefined;
let symbolVar:symbol = Symbol();
// 对象类型
const x:object = { foo: 123 };
const y:object = [1, 2, 3];
const z:object = (n:number) => n + 1;
// 包装类型
// 大写String包含两种:一个是字面量类型,一个是对象类型;
// 小写string只有字面量类型
const s1:String = 'hello'; // 正确
const s2:String = new String('hello'); // 正确
const s3:string = 'hello'; // 正确
const s4:string = new String('hello'); // 错误,不能将类型“String”分配给类型“string”。
// 大写Object,除了null和undefined外,Object包含其他所有类型
let obj:Object;
obj = true;
obj = 'hi';
// 小写object只有引用数据类型
let obj1:object;
obj1 = [];
// 值类型
// ts中任何变量都是有类型的,注意很多时候被默认推导了
// let 会给 strValue1 推导类型为 string
let strValue1 = 'hello';
// const 会给 strValue2 推导类型为 hello (普通类型的值)
const strValue2 = 'hello';
// const 会给 objValue 推导类型为 object (引用数据的值)
const objValue: object = { foo: 1 };
// 注意:引用数据会被推导
// const objValueNoType: {
// foo: number;
// }
const objValueNoType = { foo: 1 };
// 主动设置值类型(单个值)
let numValue1: 1 = 123; // 不能将类型“123”分配给类型“1”。
let numValue2: 123 = 123; // 不能将类型“123”分配给类型“1”。
let numValue3: number = 123;
// 注意 number 类型 包含 123 类型
numValue2 = numValue3; // 错误:不能将类型“number”分配给类型“123”。
numValue3 = numValue2; // 正确
4、联合类型
// 联合类型
// 多个类型组合在一起的类型,可以是不同类型的值,也可以是同一类型的值
// 可以给变量增加类型 null | undefined
let unionValue1: string | number | null | undefined = 123;
let unionValue2: 'hello' | 'world' | number = 'hello';
// 联合类型可以被看成是 类型范围的扩大
// 使用的时候需要类型缩小,否则会报错;也就是说类型范围的变化不会减少工作量
if(typeof unionValue1 === 'number'){
console.log(unionValue1.toFixed(2));
}
5、交叉类型
// 交叉类型
// let intersectionValue1: number | "hello"
// string类型包含hello类型,交叉类型就是'hello'类型
// 最终交叉类型依旧是联合类型:number | "hello"
let intersectionValue1: (string | number) & (number | boolean | 'hello') = 'hello';
6、type、typeof
// type 类型别名
// 给一个类型起一个别名,方便使用;类型可以是一个变量
// ts增加了类型系统,那么需要搭配类型变量,更加灵活使用
type MyType = string | number | boolean;
let myVar: MyType = true;
// typeof
// typeof 在js里返回最终值的类型
// typeof 在ts里获取类型,两者是不一样的
console.log('typeof myVar', typeof myVar); // boolean js中的 typeof 运算符
let myTypeofVar1: MyType;
// 使用其他变量的类型
// let myTypeofVar2: MyType
let myTypeofVar2: typeof myTypeofVar1 = true;
7、作用域
// 类型变量具有作用域
try {
type MyType = string | number;
} catch (error) {
type MyType = boolean;
}
二、数据结构
1、数组
// 数组类型
// 规定了数组中每个元素的类型,但不规定元素的个数
// 成员数组类型
// number[] 等价于 (number)[]
let arr0: number[] = [1, 2, 3];
let arr1: (string | number)[] = [1, 2, '3'];
// 泛型数组类型
let arr2: Array<string | number> = [1, 2, '3'];
// 获取数组类型其中的一个元素类型
type Names = (string | number)[];
type Name = Names[0]; // string | number
type Name1 = typeof arr1[0]; // string | number
// 注意类型数据不能打印出来,只能在编译期间使用
// console.log('数组类型', Name); // 报错
// 数组类型推断
// 自动推断为:let arr3: (string | number)[] (前面讲过,默认是any类型)
let arr3 = [1, 2, '3'];
// arr3.push(true); // 推断过一次,不会继续推断;类型不对会报错
// 只读数组
// 数组的元素不能被修改,只能读取
let readonlyArr1: readonly string[] = ['hello', 'world'];
// readonly string[] 相当于简化版,没有push、pop等属性
// 所以readonly string[] 是父类型,string[]是他的子类型,子类型添加了更多方法
arr3 = readonlyArr1; // 错误:父类型不能赋值给子类型
readonlyArr1 = ['hello', 'world']; // 正确
// 简单说,属性多\功能全的类型可以赋值给属性少的类型(如果他们有父子关系),但是反过来不行
// 另外三种只读数组
// ReadonlyArray\Readonly\const
const a1:ReadonlyArray<number> = [0, 1];
const a2:Readonly<number[]> = [0, 1];
const arr = [0, 1] as const;
// 多维度数组;
// 从左往右读取,最左边是数组最深层的元素,最右边是数组最外层的元素
const arr4: string[][] = [['hello', 'world'], ['foo', 'bar']];
2、元组
// 元组类型
// 规定了数组中每个元素的类型,且元素的个数也做规定;可选元素可以用?表示,只能放在最后面
let tuple1: [string, number?] = ['hello', 123];
// ... 扩展运算符可以增加无限多个元素
let tuple2: [string, ...number[],] = ['hello'];
// 元组中元素的名称, 没有实际意义,只是语义化的作用
let tuple3: [message: string, count: number] = ['hello', 123];
// 获取元组类型的元素类型,通过数字索引
type TupleType = [string, number?];
// 注意类型变量也只能赋值给类型变量;不能当成值变量使用
type TupleType1 = TupleType[1]; // number | undefined
// const tuple4 = TupleType[0]; // 报错
// 只读元组
// readonly 关键词;或者使用泛型 Readonly<[string, number]>
let readonlyTuple1: readonly [string, number] = ['hello', 123];
let readonlyTuple2: Readonly<[string, number]> = ['hello', 123];
// 元组长度length
let tupleLength1: [string, number] = ['hello', 123];
let tupleLength2: [string, number, boolean?] = ['hello', 123, true];
let tupleLength3: [string, ...number[], ] = ['hello', 123, ];
// 当元组长度固定,length属性为定值,使用其他值会报错
// if(tupleLength1.length === 3){ // 报错
// console.log('tupleLength1.length', tupleLength1.length);
// }
// 当元组长度固定几个选项 1、2、3
if(tupleLength2.length === 3){
console.log('tupleLength2.length', tupleLength2.length);
}
// 当元组长度不固定,可以随意判断长度
if(tupleLength3.length === 3){
console.log('tupleLength2.length', tupleLength3.length);
}
// 元组在函数参数中使用
// 注意函数参数一般有固定的元素数量,这个时候使用元组确定数量
function tupleSayHello(name:string, age:number):void{
console.log(`hello ${name}, your age is ${age}`);
}
const tupleArr1: [string, number] = ['hello', 123];
const tupleArr2 = ['hello', 123]; // (string | number)[] 会被认为数量无限
tupleSayHello(...tupleArr1); // 正确
// sayHello(...tupleArr2); // 报错 sayHello只接受有限的2个参数
const tupleArr3 = ['hello', 123] as [string, number]; // 推断类型
// 只读的值数组,可以用as const来定义成元组类型
const tupleArr4 = ['hello', 123] as const;
tupleSayHello(...tupleArr4)
3、函数类型
// 函数类型
// 默认是有返回值类型推断的,可以省略(除非条件不充足,无法推断)
type funType = (x: number, y: number) => void;
// 方式1;注意不写参数类型会被推断为any类型
const funAdd1 = (x: number, y: number, z) => x + y;
// 方式2
const funAdd2: (x: number, y: number) => number = (x, y) => x + y;
// 参数数量
let funAdd3: (x: number, y: number) => number = (x, y) => x + y;
funAdd3 = (x: number) => x + 1; // 正确;可以少于初始化定义的参数数量
// funAdd3 = (x: number, y: number, z: number) => x + y + z; // 错误:参数大于定义的数量
// keyof 关键字获取函数的类型(注意得到的是type名)
const funAdd4: funType = (x: number, y: number) => x + y; // 正确
// keyof 关键字获取函数的类型(注意得到的是类型)
// const funAdd4 = (x: number, y: number) => x + y; // 正确
const funAdd5: typeof funAdd4 = (x, y) => x + y; // 正确;
// 对象方式定义函数类型
let fooObj: {
(x:number): void;
} = (x) => {
return x + 1;
};
// 等同于
interface FooObj {
(x:number): void;
}
let fooObj1: FooObj = (x) => {
return x + 1;
};
// 特殊类型 Function
let fooObj2 = (cb: Function, x: number) => {
return cb(x + 1);
};
// 箭头函数; 注意ts中参数区域要用括号包裹起来;返回值写在括号后面
const funAdd6 = (x: number): number => x;
// 参数可选; 注意 y的类型是 number | undefined
// 可选的?的位置只能在最后面追加参数;前面的参数想要使用undefined得单独加
// void 返回值表示不能使用 return 返回内容
const funAdd7 = (x: number | undefined = 0, y?: number): number => {
// 注意在代码中写的typeof是值的类型判断,而不是类型变量
if(typeof x === 'number' && y !== undefined){
return x + y;
}
x?.toFixed(2); // 注意?.运算符,可以安全的使用undefined
y?.toFixed(2);
// 如果不判断就使用x、y,会报错
// return x + y
// 注意必须有默认值,因为定义了返回值
return 0;
};
funAdd7(undefined); // 正确
funAdd7(1); // 正确
funAdd7(1, undefined); // 正确
funAdd7(1, 2); // 正确
funAdd7(undefined, 2); // 正确
// 函数参数默认值
// 不可以可选时使用默认值:y?: number = 0
const funAdd8 = (x: number = 0, y: number = 0) => x + y;
// undefined 不会覆盖默认值
funAdd8(undefined); // 0
// 参数结构
type FunTypeAdd9 = {x: number, y: number}
const funAdd9 = ({ x, y }: FunTypeAdd9) => x + y;
funAdd9({ x: 1, y: 2 }); // 3
// rest参数
// 剩余参数,可以接收任意数量的参数,会被存入一个数组中
// args 这个名字可以自定义, 但是必须放在最后面
const funAdd10 = (type: string, ...args: number[]) => {
let sum = 0;
for(let i = 0; i < args.length; i++){
sum += args[i];
}
return sum;
};
funAdd10('add', 1, 2, 3); // 6
// readonly 关键字
// const funAdd11 = (x: readonly number[], y: number) => x[0]++;
// funAdd11( [1, 2, 3], 1); // 错误:不能修改只读数组
// void 关键字
// 一般用于函数没有返回值,或者返回值没有意义的情况
const funAdd12 = (x: number): void => {
// 没有return,或者return undefined || null
// 或者 throw new Error('something wrong');
// 当然函数默认返回的就是undefined
// return 1; // 错误
// return ''; // 错误
return; // 正确
return undefined; // 正确
};
funAdd12(1); // 正确
// 函数重载
// 注意不能写const let定义,使用function定义
// 同样不能写箭头函数了
// 当你传入两种模式,代码层面做好判断即可;不同的参数返回不同的数据类型
function funAdd13 (x: number): number;
function funAdd13 (x: string): string;
function funAdd13 (x: number | string): number | string {
// number => number
if(typeof x === 'number'){
return x + 1;
}
// string => string
return x + '1';
};
funAdd13(1); // 2
funAdd13('hello'); // hello
// 构造函数
// 函数默认是不能当成构造函数使用的
// const funAdd13_child = new funAdd13('hello'); // 报错
// type 构造函数, new 关键字 开头
type FunAdd14_new = new (x: number) => object;
type funAdd_type = {
new (x: number): object;
}
// funAdd_type 只能和 Class 结合使用
class FunAdd14_class {
constructor(public x: number) {
this.x = x + 1;
}
}
const funAdd14: funAdd_type = FunAdd14_class;
const funAdd14_child = new funAdd14(123); // 正确
4、对象类型
// 对象类型
// 字面量对象,注意可以使用, 或者;
// 属性个数固定,必须包含所有属性(有点类似于元组强制数量)
const objValue1: {
name: string,
age: number,
sayHello: () => void; // 函数方式 1
sayHello2(): void; // 函数方式 2
count?: number // 可选属性
} = {
name: 'vbj',
age: 18,
sayHello: () => {
console.log(`hello ${this.name}`);
}
};
console.log(`objValue1`, objValue1);
// 删除必选属性,不再被允许
// delete objValue1.name; // 错误
// 删除可选属性,可以被删除
delete objValue1.count; // 正确
// type 定义对象类型
type ObjValue2 = {
age: number[],
name?: string,
}
// 获取某一个元素的类型;注意类型变量不是纯对象,不能使用 ObjValue2.name
type ObjValue2Name = ObjValue2['name']; // string
// interface 定义对象类型
interface ObjValue3 {
toString(): string;
name: string;
age?: number;
}
// 如果属性是构造函数自带的,可以省略(如 toString)
// 因为对象默认自带 toString 方法(Object.prototype.toString)
const objValue3: ObjValue3 = {
name: 'vbj',
age: 18,
};
// const objValue3Age = objValue3.age * 1; // 错误:不能使用 undefined 类型
if (objValue3.age) { // 正确; 可选值需要经过判断再使用;有点类似函数参数的可选
const objValue3Age = objValue3.age * 1; // 正确
}
// age1、age2这两个是不一样的
// 前面必须定义属性age1;age2可以有可以没有,只不过他们的值类型相似
type ObjValue4 = {
age1: number | undefined,
age2?: number,
}
// readonly 关键字
type ObjValue5 = {
readonly name: string,
readonly age: number,
readonly points: number[];
readonly count: {
count1: number,
count2: number,
}
}
const objValue4: ObjValue5 = {
name: 'vbj',
age: 18,
points: [1, 2, 3],
count: {
count1: 100,
count2: 200,
}
};
// 浅层修改属性报错
// objValue4.name = 'hello'; // 错误
// 深层属性修改; 正确
objValue4.points[0] = 100; // 正确
objValue4.count.count1 = 100; // 正确
// 引用数据会突破readonly限制
// 内存数据优先于关键字
const objValue5 = {
name: 'vbj',
age: 18,
points: [1, 2, 3],
count: {
count1: 100,
count2: 200,
}
}
const objValue6:ObjValue5 = objValue5
objValue5.name = 'hello'; // 正确
console.log('objValue6', objValue6.name); // hello
// 属性名索引(索引也具有类型)
// 注意索引类型只能写一个;比如 propName: string只能写一个
type ObjValue7 = {
[propName: number]: number,
// [propName: string]: string, // 报错。索引代表的值类型冲突
// string索引优先级更高,只要他的容错度高就可以
[propName: string]: string | number,
// [propName: string]: string | number | undefined,
// 索引类型和普通类型,很容易冲突
// name: boolean // 错误:[propName: string] 和 boolean 类型冲突
}
// 解构赋值
// 注意单独的变量不能使用类型注解;只能集体使用;因为:号会被识别成变量别名
const { name: vbjName, age, points, count }: {
name: string,
age: number,
} = {
name: 'vbj',
age: 18,
}
// 结构类型; 父类型,基础;子类型比父类性更加严格(包含父类型的属性)
const objPointA = {
x: 1,
y: 1
};
const objPointB:{ x: number } = objPointA; // 正确
for (const key in Object.keys(objPointB)) {
// 报错;objPointB的值中可能出现除x以外的属性,默认为any
// const item = objPointB[key];
// 所以只能使用objPointB中确定的属性
const item = objPointB.x;
}
// 字面量和变量区别
function sayHelloObj({name: string}): void {}
const sayHelloObjParam = {name: 'vbj', age: 18};
sayHelloObj(sayHelloObjParam); // 正确;结构类型满足即可(参数可多不能少)
sayHelloObj({name: 123, age: 18}); // 错误:age类型不匹配; 字面量会匹配参数数量
// 空对象
// 写空对象,会认为后续无法添加属性
const emptyObj: {} = {}; // 类型被锁定 const emptyObj: {}
// emptyObj.name = 'vbj'; // 报错
const emptyObj2: Object = { name: 'vbj' };
emptyObj2.name = 'vbj'; // 报错
// 初始化对象
// useState后的泛型区域内就是 countObject的类型;如果默认空对象,要给每个属性写可选类型
// const [countObject, setCountObject] =
// useState<{ count?: number, age?: number }>({}); // 方式 1
// 或者给默认值
const defaultCountObject = { count: 0, age: 0 };
const [countObject, setCountObject] =
useState<{ count: number, age: number }>(defaultCountObject); // 方式 2
const countObjectValue = countObject?.count;
useEffect(() => {
setCountObject({ count: 0, age: 18 }); // useState类型
}, []);
三、接口
// interface基础用法
interface Person {
firstName?: string;
lastName?: string; // 可选属性
readonly age?: number; // 只读属性
[key: string]: any; // 注意每一种key类型,只能有一种值类型
[key: number]: string; // key number索引
}
const persons: Person = {
firstName: 'John',
lastName: 'Doe',
age: 30,
};
// interface的数字索引可以定义数组
const personKeys: Person = ['firstName', 'lastName', 'age'];
// interface的函数
interface Person2 {
// interface 函数重载;注意只能写function,不能写箭头函数
fun(name: string): string;
// fun(name: number): number;
fun1?: (age: number) => number;
fun2?: { (name: string): void };
}
// 具体的重载,只能在具体函数体使用,interface重载只是定义
const person2: Person2 = {
fun: function (name: string): string {
return `hello ${name}`;
},
};
// 独立函数; (type可以更方便完成)
interface Person3 {
(name: string, age: number): void;
}
const person3: Person3 = function (name: string, age: number): void {
console.log(`hello ${name}, your age is ${age}`);
};
// 构造函数
interface Person4 {
new (name: string, age: number): void;
}
class person4Class {
name: any;
age: any;
constructor(name: any, age: any) {
this.name = name;
this.age = age;
}
}
// 注意这里的Person4是接口,person4Class是类
const person4: Person4 = person4Class;
// 继承
interface Animal {
name: string;
}
interface AnimalColor {
color: string;
}
// 当type是对象时,可以被继承
type AnimalType = {
type: string;
};
// 继承Animal,AnimalColor; 注意属性不能重重复,否则报类型不兼容
interface Dog extends Animal, AnimalColor, AnimalType {
bark(): void;
// color: number; // 属性“color”的类型不兼容。
}
// 同名的interface会合并属性
// 当一个interface没有你想要的属性,又不想新增,可以直接合并
interface Dog {
age: number; // 新增属性
}
// type 和 interface 的区别
// 相同点
// 1. type都命名为对象的时候,基本一致
// 2. 可以定义属性类型
// 不同点
// 1. interface 可以继承,type 不可以
// 2. interface 可以合并,type 不可以
// 3. interface 只能写对象,type 可以写任意类型(类型更灵活)
// 4. type 有联合类型、交叉类型,interface 没有
// 5. type 不能重复声明,interface 可以重复声明
// 6. interface 中使用 this 关键字,type 中不能使用
// 个人总结
// 1. interface 用于组件参数列表,API接口参数定义,函数返回值,更多属于业务层面
// 2. type 用于复杂的类型定义,更多属于类型层面
四、泛型
// 泛型
// 当参数和返回值有关系的时候,无法明显看出来关系
// 即便是指定类型为一致,也只是表明他们类型恰好一致,而不是恒定一致性
const getArrayFirst1 = (num: any[]): any => {
return num[0];
};
// 写法 1 function
function getArrayFirst2<T>(num: T[]): T {
return num[0];
}
// 写法 2 箭头函数
// 注意 T后面有逗号
const getArrayFirst3 = <T,>(num: T[]): T => {
return num[0];
};
// 前面两种是根据值推到处类型,也可以主动设置类型
// 写法 3 interface
interface GetArrayFirst4 {
<T>(num: T[]): T;
}
const getArrayFirst5: GetArrayFirst4 = (num) => {
return num[0];
};
// 泛型可以不写,默认根据实参推导
getArrayFirst2([1, 2, "3"]); // <string | number>
// 也可以指定类型;如果复杂的类型,尽可能主动传入泛型变量
getArrayFirst2<string | number>([1, 2, "3"]);
// interface + 泛型
// interface 可以 动态设置类型
interface GetArrayFirst6<T> {
(num: T[]): T;
}
// 注意 interface有泛型时,必须使用,不能省略
const getArrayFirst7: GetArrayFirst6<string | number> = (num) => {
return num[0];
};
getArrayFirst7([1, 2, "3"]);
// class + 泛型
// 类也可以使用泛型
class MyClassGetNum1<T> {
// static num: T[] = []; // 报错;静态属性不能使用泛型;只能使用实例属性
// constructor 相当于函数的参数入口
constructor(public num: T[], ...args: any[]) {}
getFirst(): T {
return this.num[0];
}
}
// T === string | number 默认根据实参推导
const myClassNum1 = new MyClassGetNum1([1, 2, "3"]);
// class 和构造函数的 关系
// class 改造成 函数
function MyClassGetNum1Func<U>(ClassCore: typeof MyClassGetNum1, num: U[], ...args: any[]): MyClassGetNum1<U> {
return new ClassCore<U>(num, ...args);
}
// 注意 U 如果不传,会根据 存在的U推导;
// 比如参数有U,传了参数但函数没有传U,会根据参数确定U
const myClassNum2 = MyClassGetNum1Func(MyClassGetNum1, [1, 2, "3"]);
console.log("myClassNum2", myClassNum2.getFirst());
// type + 泛型
type GetArrayFirst8<T> = (num: T[]) => T;
const getArrayFirst9: GetArrayFirst8<string | number> = (num) => {
return num[0];
};
getArrayFirst9([1, 2, "3"]);
// 树形结构的 type type 可以使用自己
type Tree<T> = {
value: T;
left?: Tree<T>;
right?: Tree<T>;
};
const treeType: Tree<number> = {
value: 1,
left: {
value: 2
},
right: {
value: 9
}
};
// 泛型默认值
function getArrayFirst10<T = string>(num: T[]): T {
return num[0];
}
// 函数没传泛型,根据参数推导,并且覆盖默认值
getArrayFirst10([1, 2, "3"]);
class MyClassGetNum2<T = string> {
constructor(public num: T[]) {}
// 可以设置在参数位置
add(num: T): void {
this.num.push(num);
}
}
const myClassNum3 = new MyClassGetNum2([1, 2, "3"]);
// 推导的泛型,会覆盖默认值
// console.log("myClassNum3", myClassNum3.add(true)); // 报错
// 数组泛型
// Array 相当于一个 interface
const arrInterface1: Array<number> = [1, 2, 3];
// 只读数组 ReadonlyArray
const arrInterface2: ReadonlyArray<number> = [1, 2, 3];
// arrInterface2.push(1); // 报错
// arrInterface2 = []; // 报错
// 泛型条件约束
// 也就是说和默认类型不同,约束类型是不会被覆盖的,会和T一起构成联合类型
// 简单说 A extends B 就是 A 必须是 B 的子类型;
// A 范围小一些,B 范围大(条件宽松)
// 注意 = 10 指的是T的默认值,不是string
function getArrayFirst11<T extends number | string = 10>(num: T[]): T {
return num[0];
}
// 使用了 extends 约束,最好是主动传入泛型,而不是依赖默认推导
getArrayFirst11<number | string>([1, 2, 3, 4, "5"]);
// 1、泛型使用会增加复杂度,尽量不要滥用,超过 3 个重复的类型考虑使用泛型
五、enum
// enum 枚举类型
// 类似于js对象,编译后也是对象;enum这里的作用是提供 类型 + 枚举
// 枚举值的特点是名称很重要(语义化),值可以变化程度大
enum Color {
// 枚举值可以不用从 0 开始,可以设置其他值
Red,
Yellow, // 默认自增 + 1
Green = "Green",
// 注意数字索引中断以后,不会默认自增,需要手动设置
Blue = 0
// obj = {
// name: "obj"
// }
}
const color: Color = Color.Red;
console.log("color", color);
// 同名枚举值,会合并
// 注意数字索引为0的位置,只有一个;其他的同名enum要手动设置
enum Color {
// Pink, // 0,但是报错,因为已经存在了
Pink = "Pink"
}
// const enum
// 编译后会直接替换成 值,不会生成对象; 提高了效率
// 编译后
// const color = 0;
// 函数和 enum 类型
// 当参数使用 enum,不能再传值,而是只能穿 enum 类型
function getColor(color: Color): string {
switch (color) {
case Color.Red:
return "红色";
case Color.Yellow:
return "黄色";
default:
return "未知";
}
}
// console.log("getColor", getColor('Green')); // 报错,类型不匹配
console.log("getColor", getColor(Color.Green)); // 正确
// keyof 索引类型
// 得到所有 key 的联合类型;typeof 是为了获取类型,keyof 获取 key
type Color_key = keyof typeof Color;
// 获取了所有值 key 表示枚举的值
type Color_value = { [key in Color]: any };
// enum 反向映射
// 枚举值可以反向映射到枚举名; 注意枚举值相同,反向映射得到最后一个
// 注意使用[]反向映射也可以获取枚举值,获取枚举值尽量使用Color.Blue规范
const color_reverse = Color[0]; // 0 映射到 Blue 而不是 Red
// Color["123"] // 123 映射到 undefined
// Color["Blue"] // Blue 枚举值 0
// Color.Blue; // 0
console.log("Color_key", color_reverse, );
六、断言
// 断言
type Person_As1 = {
name: string;
age: number;
count: number;
}
const person_assert = {
name: 'vbj',
}
// 断言 即便不满足该类型,可以认为指定断言,这样可以绕过检查(可能导致异常)
const person_assert_type1: Person_As1 = person_assert as Person_As1;
// 当你定义数据初始化时,可能没有那么多数据,可以指定断言以后,逐步添加属性
person_assert_type1.age = 18;
console.log('person_assert_type1', person_assert_type1.count);
// 使用断言的场景,很有可能你所使用的数据,是很模糊的
// 不确定有哪些属性,或者暂时没有; 这个时候很容易把父类性赋值给子类型
// 可以看到不满足条件也被定义了
const person_assert_type2: Person_As1 = {
name: 'vbj',
age: 18,
count: 100,
num: 100
} as Person_As1
// 断言条件
// 只要as右边是目标类型的子类型即可(子类型 = 父类性 + 额外属性)
// 比如 const value: T1 = valueDefault as T2;
// T2 必须是 T1 的子类型;
// valueDefault是T2的子类型 或者 T2是valueDefault的子类型
const person_assert_type3: Person_As1 = {
name: 'vbj',
} as { name: string, age: number, count: number, num: number }; // 正确
// 最终把数字赋值给字符串
// 注意 any unknown 是所有类型的父类性
// const person_assert_type4: string = 100 as string; // 错误 as 两边的值类型不匹配
const person_assert_type4: string = 100 as unknown as string; // 正确 中转 unknown 类型
// as const 关键字
// let const 关键词会导致变量的类型不一致(简单说const会让变量的类型固定)
// let string1: string
let string1 = 'JavaScript';
// const string2: "JavaScript"
const string2 = 'JavaScript';
// let string3 = string1 as const; // 报错 as const 前面只能出现基本数据字面量,不能是变量
// let string3: "JavaScript"
let string3 = 'JavaScript' as const;
// 对象字面量 + as const 关键字;
// 会导致对象属性变成只读readonly, 不能再添加属性;
// 属性的值也被改成值类型,比如name类型不是string,而是'vbj'
const person_as_type5 = { name: 'vbj', } as const; // 正确,空对象类型
// person_as_type5.age = 1 // 错误,不能添加属性
// 枚举值类型锁定
enum Person_enum1 {
name,
age = 'age',
}
let person_enum1 = Person_enum1.name; // Person_enum1
let person_enum2 = Person_enum1.name as const; // Person_enum1.name
// 非空断言
function AsFun1(value: string | null): string {
// 断言 value 非空,否则会报错; 这个例子中其实就是排除 null
// 非空表示这里一定有值
return value!.toUpperCase();
}
// 断言函数(断言逻辑的复用)
// 这里断言只要是string类型,就抛出异常
// 注意 asserts value is string 只是语义化;具体逻辑需要自己设定
function isStringAs(value:unknown):asserts value is string {
if (typeof value !== 'string')
throw new Error('Not a string');
}
function AsFun2(value: string | number | null): string {
isStringAs(value); // 调用断言函数
// 如果断言函数使用得当,那么错误会出现在断言函数,不会在这里报错
return value.toUpperCase();
}
AsFun2(null);
七、工具
1、模块
// 模块
// type 和 interface 区分
// 类型定义文件:module_type.d.ts
export interface interfaceName {
name: string;
}
export type typeName = {
age: number;
}
export let num = 123;
// 引入类型定义文件: const.ts
// 方式 1; 单个确定为type类型
import { type interfaceName, typeName } from './module_type';
// 方式 2; 批量确定为type类型
import type { interfaceName, typeName, num } from './module_type';
const numCurrent = num; // 报错,num是类型,而不是值
2、namespace
// namespace
// 命名空间可以将类型定义文件拆分为多个文件,方便管理
// 命名空间,可以导出和引入;
// 如果未导出,外部无法访问;
// 可以和函数重名,重名的时候,相当与给函数增加属性;
function module_namespace1(){
}
namespace module_namespace1 {
export interface interfaceName {
name: string;
}
}
// 同名的命名空间,会进行合并;
// 注意,只会合并 export 的成员;
namespace module_namespace1 {
export let num = 123;
let nullVar = null;
}
namespace module_namespace2 {
import interfaceName = module_namespace1.interfaceName;
import num = module_namespace1.num;
// import nullVar = module_namespace1.nullVar; // 报错
}
3、装饰器
// 装饰器
// 装饰器可以替换类中原有的方法、属性等
function simpleDecorator() {
console.log('hi');
}
function simpleDecoratorConstructor() {
return function(target: any) {
console.log('simpleDecoratorConstructor');
}
}
@simpleDecorator
// simpleDecorator 会附在A身上,当A加载就会调用 simpleDecorator
// 注意有多个装饰器,靠近 A 的装饰器先触发
class A {
// 注意装饰器后的代码返回一个函数即可
// simpleDecoratorConstructor 返回的函数会替换 getTarget 函数体
@simpleDecoratorConstructor()
getTarget(params: any) {
console.log('A getTarget');
}
} // "hi"
console.log('simpleDecorator', new A().getTarget(1));
4、declare
// declare 关键字
// 当外部模块的变量没有类型,可以使用 declare 关键字声明变量类型
// 不能赋值, 只能定义类型
// declare let module_declare: string = 'hello'; // 报错
declare let module_declare: string;
module_declare = 'hello'; // 正确
// declare module NAME
declare module 'antd' {
export function message(content: string): void;
}
declare module '@ant-design/icons' {
export function message(content: string): void;
}
// 强制模块
export {};
// 全局对象扩展
// 注意,只能扩展属性,不能新增顶层对象
declare global {
interface String {
toRaw(): string;
}
}
String.prototype.toRaw = ():string => {
return 'toRaw';
};
5、运算符
// 运算符
type T5 = {
name: string;
age: number;
};
// 返回所有的属性名(可能找到原型链上)
type T6 = keyof T5; // T6 是联合类型 "name" | "age"
const t6: T6 = 'name'; // 正确,keyof T5 返回 T5 的属性名类型
// 一般需要使用对象属性的时候使用,keyof 返回 属性名的联合类型
const T7_Obj = {
name: 'vbj',
age: 18,
};
const T7_Key: string = 'name';
function T7_Func(obj: T5, key: string) {
return obj[key as keyof T5];
}
T7_Func(T7_Obj, T7_Key)
// in 运算符 (类型中,而不是js的in运算符)
type Type8 = {
name: string;
age: number;
};
// 类似于复制所有目标对象的属性到一个新对象中
type Type9 = {
[key in keyof Type8]: any;
};
// 方括号
// 获取类型的属性值
type Type10 = {
name: string;
age: number;
[key:number]: boolean
};
// 因为是类型括号,所以括号中也可以穿入类型(类型括号不能变量值)
// type Type = Type10['na' + 'me'] // 报错
type Type10_number = Type10[number] // boolean
type Type11 = Type10['name'] // string
// 联合类型,也会被识别成联合类型
type Type12 = Type10['name'|'age'] // string | number
// extends 三元运算符
// 条件判断,根据类型判断返回不同类型;公式:T extends U ? X : Y
// T 是 U 的子类型,返回 X;否则,返回 Y
type Type13 = string extends string | number? string : number; // string
// infer 关键字
// infer 关键字可以帮助我们推导类型变量;这样可以得到一个主动推导的变量类型(element)
type type14<Type> = Type extends Array<infer element> ? element : string;
type type15 = type14<string[]>; // string
// is 关键字
// is 关键字可以判断一个变量是否是某个类型;相当于语义化,具体判断需要自行定义
function isType10(a:string): a is string {
console.log('isType10 Function');
return typeof a ==='string';
}
if(isType10('isType10_result')){
console.log('isType10_result');
}
// 类型中的字符串模板
type Type16 = `hello ${string} world`; // "hello " + string + " world"
// satisfies 关键字
// 手动类型检测, 帮助你进行类型报错提示
type Type17 = {
name: string;
age: number;
};
// 这里并不想指定类型,而使用默认推导;但是又希望推导的时候,能够进行属性校验
// 主要是为了获取开发时的提示
const Type17_Obj = {
name: 'vbj',
aeg: 18,
count: 100,
} satisfies Type17;
const Type17_count = Type17_Obj.count;
6、映射类型
// 映射类型
// 映射类型可以帮助我们快速创建类型;
// 语法:{ [K in keyof T]: X }
// 其中,K 是 T 的属性名,X 是任意类型
type Type18 = {
name: string;
readonly age: number;
};
// 注意前面的+、-、readonly,表示修改关键字,增加或去除只读性
// +、-、?,表示增加或去除可选性
type Type19 = {
// count: number; // 如果映射,不能添加新属性
// A as B 可以在之前的键名A基础上转换属性名B
+readonly[P in keyof Type18 as `newkey ${P}`]+?: Type18[P];
};
7、类型工具
// 类型工具
type Type20 = {
name: string;
age: number;
}
// 工具类型类似于语法糖
// type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
type TypeTool1 = Partial<Type20>; // 所有属性可选
type TypeTool2 = Required<Type20>; // 所有属性必填
type TypeTool3 = Readonly<Type20>; // 所有属性只读
type TypeTool4 = Pick<Type20, 'name' | 'age'>; // 选择性的属性
type TypeTool5 = Omit<Type20, 'age'>; // 排除的属性 返回剩下的属性
type TypeTool9 = Parameters<() => void>; // 参数类型 可以传入函数类型
type TypeTool10 = ReturnType<() => string>; // 返回值类型 可以传入函数类型
type TypeString1 = 'hello'
type TypeString2 = Uppercase<TypeString1> // HELLO 字符串类类型大小写转换
// 使用频率较高的类型工具
// 快速创建未知数量属性的对象类型
type Type21 = Record<string, any>; // 索引类型,可以指定索引类型
8、语法注释
// 语法注释
// @ts-nocheck 忽略检查
const element = document.getElementById(123);
// JSDoc 声明
// JSDoc 注释可以帮助我们生成文档,同时也会帮助我们进行类型检查
// 注意类型变量放在{}中
/**
* @param {string} name 这里的类型只是说明参数类型,不是实际类型;
* @typedef {(number | string)} NumberLike 相当于创建type别名
* @typed {NumberLike} 描述类型是什么,使用刚才定义的别名
* @returns {void} 这里的类型只是说明返回值类型,不是实际类型;
* @extends {HTMLElement} 继承自HTMLElement
*/
function NoteFun(name: number): string {
console.log('Hello ' + name);
return 'Hello';
}
9、tsconfig.json
// tsconfig.json
//npm install -g typescript
// tsc --init 生成tsconfig.json文件
// 打包编译时会读取tsconfig.json配置
{
"compilerOptions": {
"outDir": "./dist", // 编译输出目录
"allowJs": true, // 允许编译js文件
"target": "es5", // 编译js的目标版本
"jsx": "react", // 使用jsx, 规定输出的jsx文件名
"lib": ["dom", "es2021"], // 编译时需要引入的内置的类型库
"sourceMap": true, // 生成sourceMap文件
"strict": true, // 开启严格模式
"paths": {
"@/*": ["src/*"] // 路径映射, 类似于webpack的resolve.alias
}
},
"include": ["./src/**/*"] // 需要编译的文件列表
}
原文地址:https://blog.csdn.net/hole_diss/article/details/144168924
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!