自学内容网 自学内容网

前端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)!