声明了类型后TypeScript就会进行类型检测,声明的类型可以称之为类型注解
。
tslint --init,可以通过tslint检测ts代码的规范性;
变量的类型推导 - 在开发中,有时候为了方便起见我们并不会在声明每一个变量时都写上对应的数据类型,我们更希望可以通过Typescript本身特性帮助我们推断出对应的变量类型。
Typescript类型:
1. number
2. boolean
3. string
4. 数组类型
const name: string[] = ['a', 'b']
const num: number[] = [1, 2, 3]
const name2: Array\<string> = ['a', 'b'] 不推荐,在JSX中有冲突 其实是个泛型了
5. 元组类型Tuple
特殊的数组或者是不限定类型的数组,TS中的元祖类型其实就是数组类型的扩展,元祖用于保存定长定数据类型的数据。
let user : [string, number] = ['xiaoming', 20];
6. object类型:用interface或者type来描述
// 表示一个对象
let obj:object; // 定义了一个只能保存对象的变量
// obj = 1;
// obj = "123";
// obj = true;
obj = {name:'lnj', age:33};
console.log(obj);
7. any 和 void
- any表示任意类型, 当我们不清楚某个值的具体类型的时候我们就可以使用any,在TS中任何数据类型的值都可以负责给any类型。
- void与any正好相反, 表示没有任何类型, 一般用于函数返回值,在TS中只有null和undefined可以赋值给void类型
function test():void {
console.log("hello world");
}
test();
8. 函数
const add = function(x: number, y: number): number {
return x + y;
}
const add2: (x: number, y: number) => number = add;
9. 联合类型
10. Never类型
表示的是那些永不存在的值的类型,一般用于抛出异常或根本不可能有返回值的函数。
function demo():never {
throw new Error('报错了');
}
demo();
function demo2():never {
while (true){}
}
demo2();
11. 枚举类型
枚举类型是TS为JS扩展的一种类型, 在原生的JS中是没有枚举类型的
枚举用于表示固定的几个取值
enum Direction {
// 默认从0开始递增,也可以指定值,指定后从指定值递增
Up = 10,
Down,
Left,
Right
}
- Record类型
type RECORD< K extends string | number | symbol, V> = {
[P in K]: V
}
// {
// name: string | number,
// age: string | number
// }
type Persion = RECORD<'name' | 'age', string | number>
// *********************
type Persion2 = Record<string, string | number>;
const p: Persion = {
name: 'ww',
age: 30
}
const p2: Persion2 = {
name: 'ww',
age: 30,
sex: '男’'
}
类:
class Animal {
// private、protected、public 控制
readonly name: string;
// 类的静态属性和静态方法
static categoies: string[] = ['pig', 'bird'];
static isAnamal(a) {
return a instanceof Animal;
}
constructor(name: string) {
this.name = name;
}
run() {
return `${this.name} is running`;
}
}
// 继承
class Dog extends Animal {
bark() {
return `${this.name} is barking`;
}
}
const xiaobao = new Dog('xiaobao');
console.log(xiaobao.run());
console.log(xiaobao.bark());
class Cat extends Animal {
constructor(name: string) {
// 如果在子类重写构造函数,则必须调用父类
super(name);
}
// 重写父类方法
run() {
// 通过 super 调用父类的方法
return 'Meow, ' + super.run();
}
}
const maomao = new Cat('maomao');
console.log(maomao.run());
接口interface
举例:在车里可以打开音乐播放器,在手机上也可以打开音乐播放器,因此打开音乐播放器的功能是共用的。但是车和手机好像没有公共的父类,因此不能通过继承来实现,而通过接口可以实现。
interface Radio {
switchRadio(): void;
}
interface Battery {
checkBatteryStatus();
}
interface RadioWithBattery extends Radio {
checkBatteryStatus();
}
//car和phone都有switchRadio功能 但却不易于找到共有的父类
class Car implements Radio {
switchRadio() {}
}
class Phone implements Radio, Battery {
switchRadio() {}
checkBatteryStatus() {}
}
class Phone2 implements RadioWithBattery {
switchRadio() {}
checkBatteryStatus() {}
}
类型别名type:
类型别名就是给一个类型起个新名字, 但是它们都代表同一个类型
例如: 你的本名叫张三, 你的外号叫小三, 小三就是张三的别名, 张三和小三都表示同一个人。type用来定义类型别名。
// 给string类型起了一个别名叫做MyString, 那么将来无论是MyString还是string都表示string
type MyString = string;
let value:MyString;
value = 'abc';
// 类型别名也可以使用泛型
type MyType<T> = {x:T, y:T};
let value:MyType<number>;
value = {x:123, y:456};
// 可以在类型别名类型的属性中使用自己
type MyType = {
name:string,
// 一般用于定义一些树状结构或者嵌套结构
children?:MyType
}
let value:MyType = {
name:'one',
children:{
name:'one',
children:{
name:'one',
}
}
}
// 接口和类型别名是相互兼容的
type MyType = {
name:string
}
interface MyInterface {
name:string
}
let value1:MyType = {name:'zs'};
let value2:MyInterface = {name:'ww'};
value1 = value2;
value2 = value1;
类型注解
上面的type除了进行类目别名定义外,另一个作用就是类型注解。
import { type CSSObject} from '@ant-design/cssinjs'
通过使用type关键字进行类型注解,可以让TypeScript更好地理解和检查代码中使用该类型的地方。
类型断言 as;
类型断言就是告诉编译器, 你不要帮我们检查了, 相信我,我知道自己在干什么。断言有两种写法:
第一种:
//type assertion类型断言
function getLength(input: string | number): number {
// input.length 直接使用会报错
const str = input as String
if (str.length) {
return str.length
}else{
const number = input as Number
return number.toString().length
}
}
第二种
//type assertion类型断言
function getLength(input: string | number): number {
// input.length 直接使用会报错
if ((<string>input).length) {
return (<string>input).length;
} else {
return input.toString().length;
}
}
interface和type的区别:
我们发现interface和type都可以用来定义对象类型,那么在开发中定义对象类型时,到底选择哪一个呢?
如果是定义非对象类型,通常推荐使用type;
如果是定义对象类型,那么他们是有区别的:
- interface可以重复的对某个接口来定义属性和方法,它会自动合并;
- 而type定义的是别名,别名是不能重复的;
interface接口:
- 对对象的形状(shape)进行描述
- 对类进行抽象
- Duck Typing (鸭子类型)
// 注意:接口属性是分号结束
interface IPerson {
readonly id: number; // 只读属性
name: string;
age?: number; // 可选属性
}
let person: IPerson = {
id: 111,
name: 'xiaoming'
}
// 函数定义
interface IFunc {
(num1: number, num2: number): number
}
const addFunc: IFunc = (arg1, arg2) => arg1 + arg2
// 索引类型
interface IRole {
[id: number]: string
}
const role: IRole = ['super_admin', 'admin']; // 理解数组的本质也是对象,-> [{0: 'super_admin', 1: 'admin'}]
const role1: IRol = {
0: 'super_admin',
1: 'user'
}
如果传入的对象多一个或者少一个属性怎么处理?少一个用可选?: 处理;多一个或者多多个需要绕开TS检查,可以用断言或者索引签名处理
interface FullName{
firstName:string
lastName:string
middleName?:string
[propName:string]:any
}
// 方式一: 使用类型断言
say({firstName:'Jonathan', lastName:'Lee', middleName:"666", abc:'abc'} as FullName);
// 方式三: 使用索引签名
say({firstName:'Jonathan', lastName:'Lee', middleName:"666", abc:'abc', 123:123, def:"def"});
// 索引访问操作符注意点
// 不会返回null/undefined/never类型
interface TestInterface {
a:string,
b:number,
c:boolean,
d:symbol,
e:null,
f:undefined,
g:never
}
type MyType = TestInterface[keyof TestInterface];
接口的继承
// 接口的继承
// TS中的接口和JS中的类一样是可以继承的
interface LengthInterface {
length:number
}
interface WidthInterface {
width:number
}
interface HeightInterface {
height:number
}
interface RectInterface extends LengthInterface,WidthInterface,HeightInterface {
// length:number
// width:number
// height:number
color:string
}
let rect:RectInterface = {
length:10,
width:20,
height:30,
color:'red'
}
泛型
认识泛型:类型的参数化,之前写的是一个类型,现在以参数形式写类型。
泛型可以应用在函数中,也可以应用在类、接口中。
简单例子:
function echo<T>(arg: T): T {
return arg;
}
const res1 = echo('str');
const res2 = echo(123)
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
const result2 = swap(['string', 123]); //注意result2的类型
由于泛型只有在实际传入值的时候才能确定类型,因此会有如下问题,
解决方式1:传入的必须是个数组,但是字符就传不进去了
function echoWithArr<T>(arg: T[]): T[] {
console.log(arg.length);
return arg;
}
解决方式2:约束泛型
约束泛型:
要求指定的泛型类型必须有Length属性才可以
interface LengthInterface {
length: number;
}
function getLength<T extends LengthInterface>(args: T): T {
console.log(args.length);
return args;
}
const str = getLength('str');
const obj = getLength({ length: 10 });
const arr2 = getLength([1, 2, 3]);
在泛型约束中使用类型参数
/*
1.在泛型约束中使用类型参数?
一个泛型被另一个泛型约束, 就叫做泛型约束中使用类型参数
* */
// 需求: 定义一个函数用于根据指定的key获取对象的value
// interface KeyInterface{
// [key:string]:any
// }
let getProps = <T, K extends keyof T>(obj:T, key:K):any=>{
return obj[key];
}
let obj = {
a:'a',
b:'b'
}
// 代码不够健壮, 明明obj中没有c这个key但是却没有报错
// let res = getProps(obj, "c");
let res = getProps(obj, "a");
console.log(res);
经典案例
定义一个函数来获取对象上的属性,由于ts不知道要获取的属性在不在obj上,所以会报如下错误:
解决方式1:增加索引类型
解决方式2: 增加断言
解决方式3: 通过泛型+keyof
解决方式4:
typeof 和 keyof 在typescript中的使用
在ts中,typeof会获取到每个对象的详细类型
keyof tyepof persion => name|age
类的泛型
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item);
}
pop(): T {
return this.data.shift();
}
}
const queue = new Queue<number>();
queue.push(1);
const queue2 = new Queue<string>();
queue2.push('str');
console.log(queue2.pop);
interface KeyPair<T, U> {
key: T;
value: U;
}
let kp1: KeyPair<number, string> = { key: 123, value: 'str' };
let kp2: KeyPair<string, number> = { key: '123', value: 123 };
let arr1: number[] = [1, 2, 3];
let arr3: Array<number> = [1, 2, 3];
interface IPlus<T> {
(a: T, b: T): T;
}
function plus(a: number, b: number): number {
return a + b;
}
function connect(a: string, b: string): string {
return a + b;
}
const a: IPlus<number> = plus;
const b: IPlus<string> = connect;
去除null 或 undefined的检测
// 去除 null或 undefined检测
function getLength(value:(string | null | undefined)) {
value = 'abc';
return ()=>{
// return value.length; // 报错
// return (value || '').length;
// return (value as string).length;
// 我们可以使用!来去除null和undefined
// !的含义就是这个变量一定不是null和undefined
return value!.length;
}
}
let fn = getLength('www.baidu.com');
let res = fn();
console.log(res);
类型声明:https://microsoft.github.io/TypeSearch/
类型声明文件包括:
-
TS内置的类型声明文件
-
第三方库的类型声明文件 (注意:.d.ts文件只能写类型声明,不能写可运行的代码)
第三方库最终打包为js代码,但是js代码没有类型提示和校验,所以第三方库一般还都提供一个.d.ts文件用于类型声明,并在package.json中的typing字段标识。例如axios库
在企业开发中我们不可避免的需要引用第三方的 JS 的库,默认情况下TS是不认识我们引入的这些JS库的,但是TS还需要做类型校验,所以在使用这些JS库的时候, 我们就要告诉TS它是什么, 它怎么用。
如何告诉TS呢?那就是通过声明来告诉。
declare const $:(selector:string)=>{ width():number; height():number; ajax(url:string, config:{}):void; }; console.log($); console.log($('.main').width()); console.log($('.main').height());
对于常用的第三方库, 其实已经有大神帮我们编写好了对应的声明文件
所以在企业开发中,如果我们需要使用一些第三方JS库的时候我们只需要安装别人写好的声明文件即可。注意:类型声明文件只是开发依赖,安装到devDeependence中即可。TS声明文件的规范 @types/xxx,例如: 想要安装jQuery的声明文件, 那么只需要npm install @types/jquery 即可
-
自己提供的
- 如果多个.ts文件中都用到同一个类型,此时可以创建.d.ts文件来提供该类型,实现类型共享。
- 为已有的js文件提供类型声明
注意点:
- 在导入.js文件时,TS会自动加载与.js同名的.d.ts文件。
- declare关键字用于类型声明,在.d.ts中,如果是type、interface等这些明确是ts类型的,可以省略declare关键字,但是如果是let、const、function等既可以在js,又可以在ts中使用的,则必须前面加上declare关键字。
可辨识联合类型
什么是可辨识联合
具有共同的可辨识特征。
一个类型别名,包含了具有共同的可辨识特征的类型的联合。
interface Square {
kind: "square"; // 共同的可辨识特征
size: number;
}
interface Rectangle {
kind: "rectangle"; // 共同的可辨识特征
width: number;
height: number;
}
interface Circle {
kind: "circle"; // 共同的可辨识特征
radius: number;
}
/*
Shape就是一个可辨识联合
因为: 它的取值是一个联合
因为: 这个联合的每一个取值都有一个共同的可辨识特征
* */
type Shape = (Square | Rectangle | Circle);
function aera(s: Shape) {
switch (s.kind) {
case "square": return s.size * s.size;
case "rectangle": return s.width * s.height;
case "circle": return Math.PI * s.radius ** 2; // **是ES7中推出的幂运算符
}
}
类型映射
什么是映射类型?
根据旧的类型创建出新的类型,我们称之为映射类型
interface TestInterface1{
name:string,
age:number
}
将上面的映射为下面的:
interface TestInterface2 {
readonly name:string,
readonly age:number
}
// 实现
interface TestInterface1{
name:string,
age:number
}
interface TestInterface2{
readonly name?:string,
readonly age?:number
}
type ReadonlyTestInterface<T> = {
// [P in keyof T]作用: 遍历出指定类型所有的key, 添加到当前对象上
// readonly [P in keyof T]: T[P]
// readonly [P in keyof T]?: T[P]
-readonly [P in keyof T]-?: T[P]
}
type MyType = ReadonlyTestInterface<TestInterface2>
// 我们可以通过+/-来指定添加还是删除 只读和可选修饰符
// 由于生成只读属性和可选属性比较常用, 所以TS内部已经给我们提供了现成的实现
// Readonly / Partial
type MyType2 = Readonly<TestInterface1>
type MyType3 = Partial<TestInterface1>
type MyType4 = Partial<Readonly<TestInterface1>
Pick映射类型
将原有类型中的部分内容映射到新类型中
interface TestInterface {
name:string,
age:number
}
type MyType = Pick<TestInterface, 'name'>
Record映射类型
// 他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型
type Animal = 'person' | 'dog' | 'cat';
interface TestInterface {
name:string;
age:number;
}
type MyType = Record<Animal, TestInterface>
let res:MyType = {
person:{
name:'zs',
age:18
},
dog:{
name:'wc',
age:3
},
cat:{
name:'mm',
age:2
}
}
条件类型(三目运算)
判断前面一个类型是否是后面一个类型或者继承于后面一个类型,如果是就返回第一个结果,如果不是就返回第二个结果。
语法: T extends U ? X : Y;
type MyType<T> = T extends string ? string : any;
type res = MyType<boolean>
分布式条件类型
被检测类型是一个联合类型的时候,该条件类型就被称之为分布式条件类型。
type MyType<T> = T extends any ? T : never;
type res = MyType<string | number | boolean>;
// 从T中剔除可以赋值给U的类型。 Exclude
type MyType<T, U> = T extends U ? never : T;
// type res = MyType<string | number | boolean, number>
等价于:
// type res = Exclude<string | number | boolean, number>
// 提取T中可以赋值给U的类型。 Extract
type res = Extract<string | number | boolean, number | string>
// 从T中剔除null和undefined。 NonNullable
type res = NonNullable<string | null | boolean | undefined>
// 获得函数的参数类型组成的元组类型。 Parameters
function say(name:string, age:number, gender:boolean) {
}
type res = Parameters<typeof say>;
infer关键字
条件类型提供了一个infer关键字,可以让我们在条件类型中定义新的类型。
// 需求: 定义一个类型, 如果传入的是数组, 就返回数组的元素类型,
// 如果传入的是普通类型, 则直接返回这个类型
// type MyType<T> = T extends any[] ? T[number] : T;
// type res = MyType<string[]>;
// type res = MyType<number>;
// type MyType<T> = T extends Array<infer U> ? U : T;
// type res = MyType<string[]>;
// type res = MyType<number>;
命名空间
命名空间可以看做是一个微型模块,当我们想把相关的业务代码写在一起,又不想污染全局空间的时候,我们就可以使用命名空间,本质就是定义一个大对象, 把变量/方法/类/接口…的都放里面。
命名空间和模块区别
在程序内部使用的代码, 可以使用命名空间封装和防止全局污染
在程序内部外部使用的代码, 可以使用模块封装和防止全局污染
总结: 由于模块也能实现相同的功能, 所以大部分情况下用模块即可
// 56/test.ts
namespace Validation {
const lettersRegexp = /^[A-Za-z]+$/;
// 通过export关键字,可以在命名空间外部使用这个方法
export const LettersValidator = (value) =>{
return lettersRegexp.test(value);
}
}
// a.js --使用命名空间,需要引入命名空间,引入方式为/// <reference path="" />
/// <reference path="./56/test.ts" />
console.log(Validation.LettersValidator('abc'));
console.log(Validation.LettersValidator(123));
interface Person {
readonly id: number;
name: string;
age: number
}
声明合并
在ts当中接口和命名空间是可以重名的, ts会将多个同名的合并为一个。
interface TestInterface {
name:string;
}
interface TestInterface {
age:number;
}
// interface TestInterface {
// name:string;
// age:number;
// }
class Person implements TestInterface{
name:string;
age:number;
}
注意:
- 同名接口如果属性名相同, 那么属性类型必须一致
- 同名接口如果出现同名函数, 那么就会成为一个函数的重载
函数重载(Function Overloading)是一种面向对象编程语言中的一个特性,它允许一个函数拥有多个同名但参数不同的实现。当调用这个函数时,编译器或运行时会根据实际传入的参数来选择合适的实现版本来执行。
知识点:
- ?:
在 TypeScript 中,?: 是一个可选属性的语法,用于定义一个对象中的可选属性。它的语法如下:
interface Person {
name: string;
age?: number;
}
- 泛型:
在 TypeScript中,你可以使用泛型来创建可重用的模板。泛型是一种在编写代码时不指定具体类型的技术,它可以让你编写更通用的代码。下面是一个简单的例子:
function identity<T>(arg: T): T {
return arg;
}
在这个例子中,我们定义了一个 identity 函数,它接受一个泛型类型 T 的参数,并返回同样的类型。这意味着我们可以使用这个函数来返回任何类型的值:
const result1 = identity("hello"); // result1 的类型为 string
const result2 = identity(42); // result2 的类型为 number
除了函数
,你还可以在类
、接口
和类型别名
中使用泛型。下面是一个使用泛型的接口的例子:
interface Pair<T, U> {
first: T;
second: U;
}
const pair: Pair<number, string> = { first: 42, second: "hello" };
在这个例子中,我们定义了一个 Pair 接口,它有两个泛型类型 T 和 U,表示它包含两个不同类型的值。我们可以使用这个接口来创建一个包含数字和字符串的对象。
总之,泛型是 TypeScript 中非常有用的特性,它可以让你编写更通用、更灵活的代码。
- unknown类型
在 TypeScript 中,unknown 类型表示一个未知的值。它类似于 any 类型,但有一些重要的区别。与 any 类型不同,unknown 类型不能被随意赋值给其他类型,除非首先进行类型检查或类型断言。
下面是一个使用 unknown 类型的例子:
function printValue(value: unknown) {
if (typeof value === "string") {
console.log(value.toUpperCase());
} else if (typeof value === "number") {
console.log(value.toFixed(2));
} else {
console.log("Unknown value");
}
}
printValue("hello"); // 输出 "HELLO"
printValue(3.14159); // 输出 "3.14"
printValue(true); // 输出 "Unknown value"
在这个例子中,我们定义了一个 printValue 函数,它接受一个 unknown 类型的参数,并根据参数的类型进行不同的操作。如果参数是字符串类型,它将字符串转换为大写并输出;如果参数是数字类型,它将数字保留两位小数并输出;否则,它输出一个错误消息。
注意,我们在函数中使用了类型检查来确定参数的类型。这是因为 unknown 类型不能被随意赋值给其他类型。如果我们尝试在函数中将 unknown 类型的值赋值给其他类型的变量,TypeScript 将会报错。
总之,unknown 类型在 TypeScript 中非常有用,它可以帮助我们编写更安全、更可靠的代码。
组件增加子组件属性,统一导出:
import { FC } from 'react'
import Menu, { MenuProps } from './menu'
import SubMenu, { SubMenuProps } from './subMenu'
import MenuItem, { MenuItemProps } from './menuItem'
export type IMenuComponent = FC<MenuProps> & {
Item: FC<MenuItemProps>,
SubMenu: FC<SubMenuProps>
}
const TransMenu = Menu as IMenuComponent
TransMenu.Item = MenuItem
TransMenu.SubMenu = SubMenu
export default TransMenu
Element
和 HTMLElement
的区别:
引入:
在TypeScript中,const allCalendarHeight = Array.from(dropdownDom?.querySelectorAll(‘.date-picker-calendar’)).map(c => c?.offsetHeight); 报错:类型“Element”上不存在属性“offsetHeight”
在 TypeScript 中,如果你在使用 querySelectorAll 获取元素时,返回的是 Element 类型,而 offsetHeight 是 HTMLElement 类型特有的属性,因此 TypeScript 会报错。
Element
和 HTMLElement
是 DOM 中的两个不同类型,它们之间有一些关键的区别:
-
Element:
Element
是所有 DOM 元素的基类,包括所有类型的 HTML 元素和 SVG 元素。- 它提供了一些通用的方法和属性,如
getAttribute
、setAttribute
、classList
等。 - 由于它是一个更宽泛的类型,因此不包含特定于 HTML 的属性和方法。
-
HTMLElement:
HTMLElement
是Element
的子类,专门用于表示 HTML 元素。- 它包含与 HTML 元素相关的特定属性和方法,例如
innerHTML
、offsetHeight
、style
等。 HTMLElement
进一步细分为更具体的类型,如HTMLDivElement
、HTMLSpanElement
等,代表特定类型的 HTML 元素。
总结
- Element 是更通用的类型,适用于所有 DOM 元素。
- HTMLElement 是专门用于 HTML 元素的类型,包含特定的属性和方法。
在 TypeScript 中,使用 HTMLElement
可以让你访问与 HTML 元素相关的特定属性,而不会引发类型错误。