2025_TypeScript
目录
- typeScript特性
- typescript演练场
- 数据类型
- js基础数据类型
- number+string+boolean+symbol
- null+undefined
- let + 隐士声明 undefiend+null类型
- any+unknown+void+never
- any
- unknown
- void
- never
- 数组
- type[](最常用)
- 泛型数组
- 元组
- 多维数组
- 函数
- 基本函数声明
- 带可选参数的函数
- 带默认值的函数
- 带剩余参数的函数
- 函数表达式
- 使用接口声明函数-interface关键字
- 使用别名声明函数-type关键字
- 函数重载
- 异步函数-Promise接口
- 对象
- interface
- 基础语法
- 可选属性
- 只读属性
- 函数类型的接口
- 可索引类型的接口
- 示例:响应数据接口
- 高级类型
- 联合类型
- 语法
- 字面量联合
- 基本类型联合 - 一个变量可以是数字或字符串
- 复杂对象类型联合
- 注意问题-类型收窄
- typeof 类型守卫
- instanceof 类型守卫
- 可辨识联合
- 交叉类型
- 语法
- 基本类型交叉
- 对象类型的交叉(最常用)
- 联合类型 与交叉类型 对比
- 泛型
- 定义
- 语法
- 在函数中使用
- 接口中使用泛型
- 别名中使用泛型
- 泛型约束 - 接口+ extends继承
- 泛型约束- keyof
- 条件判断
- 常用的内置接口
typeScript特性
-
静态类型检查
:TypeScript是静态类型检查,在编译时
就会检查数据的类型是否匹配,能够发现很多潜在的错误=> js是动态检查,只有当代码运行时才会发现数据类型的问题; 比如:fn is not a function
=> ts是静态检查,即使该代码不会运行也会检查其类型匹配是不是存在问题;
let bol = false if(bol){fn() } - javascript: 运行没有任何问题,因为fn()由于条件判断没有被运行到所以不会报错; - typescript: 直接报错, 因为静态检查在检查时没有发现声明fn =>Cannot find name 'fn'
-
类型推断:TypeScript 能够自动推断变量的类型。比如当你声明一个变量并赋值时,TypeScript 会根据赋值来推断这个变量的类型,不需要每次都显式声明类型。
-
接口和类型定义:TypeScript 提供了
interface
和type
关键字,允许你定义复杂的数据结构。这对于项目中不同部分的代码协作和数据交互来说非常重要; -
类和模块支持:TypeScript 支持面向对象编程中的类(class)概念,增加了构造函数、继承、访问控制修饰符(如 public、private、protected),并且支持 ES 模块化规范。
-
兼容 JavaScript:TypeScript 是 JavaScript 的超集,这意味着所有合法的 JavaScript 代码都是合法的 TypeScript 代码。这使得 JavaScript 项目可以逐步迁移到 TypeScript,而无需完全重写
typescript演练场
ts不可以直接在浏览器运行,需要先将ts编译为js再在浏览器运行~
typescript演练场 => 在这里不需要搭建ts环境就可以尝试使用ts进行开发;
数据类型
ts中 提供了所有 JavaScript 的基础类型,并增加了枚举等类型;
若是变量声明但没有被赋值可以显示声明一个变量的数据类型;
若是变量在声明的时候被赋值 => 不需要显式声明变量的数据类型,因为ts的类型推断会将数据的类型声明为当前赋值的数据类型;
let num1: number // 显示声明num1为number类型
let num2 = 10 // 通过类型推断 num2是number类型
js基础数据类型
number+string+boolean+symbol
TS提供了类型检测,只有类型兼容
的值才可以相互赋值(注意这里是类型兼容不是相同类型哦~)
let num1 = 111 // num1是number类型
let num2: number = '111' // error: 显示声明num2为number类型,但是赋值的是string类型 =>Type 'string' is not assignable to type 'number'let str = '111' // str是string类型
num1 = str // error: 类型不相同不能相互赋值 Type 'string' is not assignable to type 'number'.let bol: boolean // bol是boolean类型let symbol = Symbol() // symbol是symbol类型
null+undefined
TypeScript 有一个重要的编译选项 strictNullChecks,它决定了 undefined 和 null 的行为;
- 若是strictNullChecks: false; 非严格模式 => 在非严格模式下 null+undefiend是所有类型的子类型,可以被赋值给其他任意数据类型;
let num:number = undefined let str:string = null
- 若是strictNullChecks: true; 严格模式 => 在严格模式下undefined 和 null 只能赋值给明确声明包含它们的类型
let num: number = undefined // Type 'undefined' is not assignable to type 'number'
而在开发环境中 基本使用的都是严格模式,这样看起来好像 undefined和null是没有什么用处的。 其实undefined+null一般是在联合声明
中使用的;
let value: number | null = null
- 后续value既可以赋值number类型也可以赋值 null类型
- 若是后续value变量存在运算如下也不会存在问题 =>ts会判断当前数据类型是null还是number
value = 222; console.log(value / 2); value = null; console.log(value / 2); // error: “value”可能为 “null”。
在React中使用ref存储dom时也会使用
import { useRef } from 'react'
const container = useRef<HTMLDivElement | null>(null); // 在这里表明container.current的值是HTMLDivElement或者null类型
let + 隐士声明 undefiend+null类型
由于开发环境都是在严格模式下,所以后面没有说明都是在严格模式下进行编译~
这里发现一个非常奇怪的现象:
const nul1: null = null; // null类型
const unde1: undefined = undefined; // undefined类型const nul2 = null; // null类型
const unde2 = undefined; // undefiend类型let num3: null = null; // null类型
let unde3:undefined = undefined; // undefined类型let nul4 = null; // any类型
let unde4 = undefined; // any类型
nul4 = 111 // 不会报错;
若是使用 const声明 或者是使用 let+显示声明类型 的方式声明类型为undefiend+null的变量时 变量的类型是正确的;
若是使用 let +隐士声明 的方式声明undefined+null类型的变量 改变量的类型为any,后续可以赋值为任意类型。
很多 JavaScript 模式会先初始化变量为 null/undefined,然后根据条件赋值为其他类型;
Typescript为了保证兼容性,考虑到 let 声明的变量后续可以被重新赋值,将其推断为 any;
any+unknown+void+never
any
any类型相当于关闭了ts对该变量以及该变量相关的变量的类型检验 => 该变量可以被赋予任意值任意类型 该变量的值同样可以赋值给其他任意变量;
let every: any;
let num = 111;
const str = 'string';every = str;
num = every;
console.log(str, every); // string string
上述示例中 every是any类型不受ts类型检验的控制,但是num是number类型,将every赋值给num类型时 也不会报错!!! => 相当于也破坏了num的类型检验。
不推荐使用any;
unknown
那若是在开发过程中, 存在一个变量最初声明的时候并不知道它的数据类型时应该如何声明呢?
此时可以使用unknown类型。
简单讲: any霍霍自己也霍霍别人; unknown是只霍霍自己但是不霍霍别人~
unknown是一个安全类型的any => unknown是一个未知类型,所以可以被任意赋值,但是该变量的值在赋值给其他变量的时候不被允许;
let every: unknown = 111;
let num = 111;
every = '111';
num = every; // 不能将类型“unknown”分配给类型“number”
如果 unknown 类型的变量的值恰好就是目标变量类型的值,为什么 TypeScript 会被允许赋值吗?答案是不会 => TypeScript 不允许 unknown 直接赋值给其他类型,即使"恰好"类型匹配!
const every: unknown = 111;
let num = 111;
num = every; // 不能将类型“unknown”分配给类型“number”
在进行赋值时可以使用类型判断
或者断言
;
- 类型判断: unknown不确定变量是什么类型 => 先判断变量值的类型确定是目标类型再赋值
const every: unknown = 111; let num = 111; if (typeof (every) === 'number') {num = every; }
- 断言(不安全):类型断言就是告诉编译器这是什么类型 => 即使告诉编译器的是错误信息编译器也不会去检验;
const every: unknown = 111; let num = 222; num = every as number; // 这里是没有问题的,因为此时every的值确实是number类型 console.log(every, num); // 111 111
const every: unknown = '111'; let num = 222; num = every as number; // 但是这里就是存在问题的 => 此时every是string类型,但是在类型断言时告诉编译器这是number类型 编译器不会去进行检验直接将其赋值给num了 导致num即使声明的number类型但是现在赋值的是stirng类型的值! console.log(every, num); // '111' '111'
void
void 类型表示"没有类型",通常用于函数没有返回值的情况。
// 函数不返回任何值
function logMessage(message: string): void {console.log(message);// 没有 return 语句,或者只有 return;
}// 显式返回 undefined
function doNothing(): void {return undefined; // ✅ 允许
}// 错误:返回其他值
function badFunction(): void {return "hello"; // ❌ Error: Type 'string' is not assignable to type 'void'
}
void类型还存在一个重要的特性: 回调函数的返回值若是声明为void类型 即使存在返回值也会被忽略
; 这是为了兼容js语法特异设计的,比如在forEach中存在使用return来终止本次循环的情况;
// 场景:数组的 forEach 方法
const arr = [1, 2, 3];// 常见的误用(但 TypeScript 允许)
arr.forEach(item => {if (item === 2) {return true; // 开发者可能想"中断",但实际上无效}console.log(item);
});
// 输出: 1, 3(return true 被忽略,不会中断循环)// 如果 TypeScript 禁止这种写法,会破坏很多现有代码
never
never 类型表示永远不可能发生的类型,是 TypeScript 中最底层的类型
never类型永远不可能有值
let nev:never // nev不能被赋予任何值
nev = 111 // error: Type '111' is not assignable to type 'never'
never类型经常用于函数中抛出异常,不能正常返回值的情况
function throwError(): never{throw new Error('error')
}
在做交集的时候 不可能发生的类型条件 类型就是never
let nev: string & number // nev的类型为never类型
在 联合类型中 never类型常被忽略
type str = string | never // str是string类型
数组
数组的声明有两种方式:type[]
或者Array<type>
type[](最常用)
数字数组
let numArr: number[]; // numArr数组中的元素必须全部都是number类型
numArr = [1,2,3,4,5] // 正确
numArr = ['1'] // Type 'string' is not assignable to type 'number'.(2322)
字符串数组
let strArr:string[];
strArr = ['1'] // 正确
strArr = ['1', 2] // Type 'number' is not assignable to type 'string'
对象数组
let objArr: {name: string,age:numberarea? : string
}[] // objArr数组中的元素必须是一个对象 而且对象必须包含name,age属性 可以包含area属性
objArr = [{name: 'chaochao', age: 18, area: 'china'},{name: 'niuniu', age: 18}
] // 正确
objArr = [{name: 'chaochao'} // Property 'age' is missing
]
想要该数组只读 使用readonly
关键字声明;需要注意的是 readonly关键字防止的是原地修改,而不是重新赋值通过push等方法修改会报错,但是若是重新赋值是没有问题的。
let numArr2: readonly number[] = [1,2]; numArr2.push(2) // Property 'push' does not exist on type 'readonly number[]'
numArr2 = [1,2,2] // 正确
也就是说
泛型数组
使用泛型来声明数组,需要使用Array
或ReadonlyArray
接口 =>这两个接口都是来声明数组的接口,只是一个可修改一个是只读的;
ReadonlyArray 接口移除了所有会修改原数组的方法(如 push, pop, splice),只保留读取方法(如 slice, map, filter 等返回新数组的方法)
let arr: Array<number> // arr为一个数组且数组元素必须为number类型
arr = [1,2,3,'4'] // error:不能将类型“string”分配给类型“number”
arr.push(2) // 正确let arr2: ReadonlyArray<number> = [1,2,3]
arr2 = [1,2,3,4]
arr2.push(1) // Property 'push' does not exist on type 'readonly number[]'
arr2 = [1,2,3,4,5] // 正确
元组
- 元组可以用来设置已知数组长度的数组
let 变量名:[type1, type2, ...]let arr: [number, number] // 目标数组只有2个元素且元素类型都是数字类型let arr:[number, string] // 目标数组只有2个元素且第一个元素是数字第二个是字符串
- 可选元素的元组
optionalTuple中的第一个元素必须存在且类型为stirng类型,第二个元素可选若是存在必须为number类型let optionalTuple: [string, number?] = ["hello"]; // 第二个元素可选
- 带剩余元素的元组:用于声明
只知道元素类型但不知道元素个数
的场景
上述声明的restTuple数组第一个元素必须是 string 类型,后面可以跟任意多个 number 类型的元素(0个或多个);let restTuple: [string, ...number[]] = ["hello", 1, 2, 3, 4];
restTuple = ['1']restTuple = ['1', 2, 3]restTuple = ['1', '2'] // error
多维数组
// 二维数组
let matrix: number[][] = [[1, 2, 3],[4, 5, 6],[7, 8, 9]
];// 三维数组
let cube: number[][][] = [[[1, 2], [3, 4]],[[5, 6], [7, 8]]
];
函数
在js声明函数中声明函数
function 函数名(参数...){// 函数体
}const 函数名 = function(参数...){// 函数体
}const 函数名 = (参数...) => {// 函数体
}
ts中也提供了多种方式来定义函数,本质就是声明函数的同时声明参数类型与返回值类型
基本函数声明
- 语法
若是没有声明函数的返回值类型,那么ts会使用类型推断,推断出函数的返回值类型。function(参数: 参数值类型): 返回值类型{// 函数体 }
- 示例
上述接收的参数是一个number类型的数组,返回值为number类型function getSum(arr: number[]): number{return arr.reduce((pre,current)=> pre+current, 0) }
这样写也是不会报错的,并且得到的返回值验证num也是number类型function getSum(arr: number[]){return arr.reduce((pre,current)=> pre+current, 0) } const num = getSum([1,2,3,4,5]) // num为number类型
带可选参数的函数
- 同对象一样,可以在参数后面添加
?
表示该参数可传可不传function(参数?: 参数值类型): 返回值类型{// 函数体 }
值得注意的是可选参数要放在函数入参的最后面,不然会导致编译错误function getPersionInfo(name: string, age?: number,): {name: string, age: number}{return{name,age: age || 18} } const p1 = getPersionInfo('chaochao') // { "name": "chaochao", "age": 18} const p2 = getPersionInfo('niuniu', 24) // { "name": "chaochao", "age": 24}
上述代码会error,交换一下 age与number参数的传递顺序就没有问题了;function getPersionInfo(name: string, age?: number, area: string): {name: string, age: number}{return{name,age: age || 18,} } // error: A required parameter cannot follow an optional parameter
带默认值的函数
- 语法: 默认传参与js相同,在声明函数的时候传递默认值同时也隐士声明了该参数值的类型
function 函数名(参数名=默认值){ // 函数体 }
- 示例
// 这个函数中的area是一个可选参数,但是在函数体中使用的时候会判断 没有传递age就返回18 => 那么此处使用默认值传参会更好一点 function getPersionInfo(name: string, age=18): {name: string, age: number}{return{name,age} }
带剩余参数的函数
现在回顾一下,刚才声明了一个如下函数
function getSum(arr: number[]): number{return arr.reduce((pre,current)=> pre+current, 0)}
现在封装一个函数传递的参数是n个数字
// ...arr 表示将所有传递的数据放在这个arr里面function getSum(...arr: number[]): number{return arr.reduce((pre,current)=> pre+current, 0)}console.log(getSum(1,2,3,4,5)) // 15
函数表达式
函数表达式也是一样的
const 函数名 = function(参数名: 参数值类型,参数名=参数值, 参数名?: 参数值类型 ):返回值类型{- 参数名: 参数值类型 - 参数名?: 参数值类型 // 可选参数- 参数名=参数值 // 带默认值的参数- ...rest: 类型[] // 剩余参数
}
const 函数名 = (参数名: 参数值类型,参数名=参数值, 参数名?: 参数值类型 ):返回值类型 => {- 参数名: 参数值类型 - 参数名?: 参数值类型 // 可选参数- 参数名=参数值 // 带默认值的参数- ...rest: 类型[] // 剩余参数
}
使用接口声明函数-interface关键字
- 语法
interface 接口名{(参数名: 参数值类型): 返回值类型 }
- 示例
/*const getSum = function(...arr: number[]): number{return arr.reduce((pre,current)=> pre+current, 0)}*/// 上面这个函数使用接口声明类型如下interface Option{(...arr: number[]): number}const getSum: Option = (...arr)=>{return arr.reduce((pre,current)=> pre+current, 0)}console.log(getSum(1,2,3,4,5)) // 15
使用别名声明函数-type关键字
- 语法
// 使用别名声明函数有一种箭头函数的感觉 type 别名 = (参数名:参数值类型)=> 返回值类型
- 举例说明
/*const getSum = function(...arr: number[]): number{return arr.reduce((pre,current)=> pre+current, 0)} */ type Option = (...arr: number[]) => number const getSum: Option = (...arr)=>{return arr.reduce((pre,current)=> pre+current, 0) } console.log(getSum(1,2,3,4,5)) // 15
函数重载
函数重载允许一个函数接受不同数量或类型的参数
,并返回不同的类型结果;
函数重载由两部分组成:
-
重载签名:定义函数的各种调用方式(只有类型声明,没有实现)
-
实现签名:包含实际的函数实现
举例说明
// 重载签名(Overload Signatures)
function greet(name: string): string;
function greet(age: number): string;
function greet(isFormal: boolean): string;// 实现签名(Implementation Signature)
function greet(value: string | number | boolean): string {if (typeof value === 'string') {return `Hello, ${value}!`;} else if (typeof value === 'number') {return `You are ${value} years old!`;} else {return value ? 'Formal greeting!' : 'Casual greeting!';}
}
函数重载本质上就是一种函数复用机制,它将相关的不同行为封装在同一个函数名下,根据不同的调用情景(参数类型、数量)来处理不同的事情。
异步函数-Promise接口
TypeScript 中的异步函数(async/await)完全基于 Promise 接口
async fn(): Promise<T>{// 函数体
}
在 TypeScript 中,为 async 函数标注类型时,其返回类型必须是 Promise,其中 T 是 await 表达式最终会得到的值的类型;
interface ApiResopnse<T>{status: number,message: string,suucess: boolean,data: T
}type Product = {id: number,name: string,price: number
}
// 获取的data是产品列表
async function getProducts(): Promise<ApiResopnse<Product[]>>{const res = await fetch('/api/products')return res.json()
}
至于这里的data为什么使用泛形就是为了实现复用 => 接口的返回类型相似只是data中的数据不一样;
对象
TypeScript 提供了多种方式来定义和操作对象类型;
interface
interface(接口) 是 TS 设计出来用于定义对象的形状,主要用于类型检查。
定义 interface 一般首字母大写。
- 语法
readonly 属性名: 类型, // 只读属性, 熟悉必须存在且匹配
属性名?: 类型, // 属性可选,存在必须与该类型匹配
[key: 类型]: 类型 // 自定义属性, 可以定义属性名/属性名类型/属性值类型
基础语法
interface 接口名 {属性名: 类型, // 该属性必须存在且匹配
}
属性必须和类型定义的时候完全一致; 属性数量也必须完全一致(多了少了都不行);
示例
interface Person{name: string,age: number
}const p1: Person = {name: 'chaochao',age: 18
} // 正确const p2: Person = {name: 'chaochao'
} // error: Property 'age' is missing ...const p3:Person = {name: 'chaochao',age: 18,area: 'china'
} // error: Object literal may only specify known properties, and 'area' does not exist in type 'Person'.
可选属性
若是某属性可能存在在属性后面添加?
修饰符,表示该属性是可选的;
interface Person{name: string,age: number,area?: string // area属性可以存在也可以不存在,若是存在属性值类型必须为string类型
}const p1: Person = {name: 'chaochao',age: 18
} // 正确const p3:Person = {name: 'chaochao',age: 18,area: 'china'
} // 正确
只读属性
若是某属性仅在声明的时候赋值 ,不能修改需要使用readonly
关键字修饰
interface Person{readonly name: string,age: number,area?: string // area属性可以存在也可以不存在,若是存在属性值类型必须为string类型
}const p1: Person = {name: 'chaochao',age: 18
}p1.name = 'niuniu' // error: Cannot assign to 'name' because it is a read-only property
函数类型的接口
- 语法
interface 接口名 {(参数名:参数值类型,...): 返回值类型 }
- 示例
interface Fn{(arr: number[], isTry: boolean): number }let sum = 0 const getSum: Fn = function(arr, isTry){if(!isTry){sum = 0}return arr.reduce((pre, current)=> pre+current, sum) }console.log(getSum([1,2,3,4,5], false))
可索引类型的接口
经过上述的讲解,看一下下面这个接口是什么意思呢?
-
接口1: 数组
interface LikeArray{[index: number]: string }
上述接口 使用自定义属性 属性名的类型为number类型,属性值的类型为string类型 => 这就是定义的元素为stirng类型的数组
interface LikeArray{[index: number]: string }const arr1: LikeArray = ['1','2'] const arr2: LikeArray = [1] // error: Type 'number' is not assignable to type 'string'
-
接口2: 自定义对象
interface LikeObj{[key: string]: string }
上述接口 使用自定义属性 属性名的类型为string类型,属性值也是string类型 => 这就是定义的一个属性名数量不固定但是属性值为string类型的对象
interface LikeObj{[key: string]: string}const obj1: LikeObj = {name: 'chaochao'}const obj2: LikeObj = [age: 18 ] // error: Type 'number' is not assignable to type 'string'
示例:响应数据接口
interface Apiresopnse<T>{status: number,message: string,suucess: boolean,data: T
}
初学者看到这里就会想 为什么在这里使用泛形呢? 直接在这里声明data的类型效果不是一样的吗?
这里是为了复用的 => 不同的接口返回的data数据不同但是status、success等数据的数据类型都是相同的!
interface Apiresopnse<T>{status: number,message: string,suucess: boolean,data: T
}interface User {id: number;username: string;email: string;createdAt: Date;
}// 使用泛型接口
async function getUser(id: number): Promise<ApiResponse<User>> {const response = await fetch(`/api/users/${id}`);return response.json();
}
高级类型
联合类型
联合类型允许您将一个值定义为多种类型中的一种 => 使用竖线|
将多个类型连接起来
语法
Type1 | Type2 | Type3 ...
一个值可以是 Type1,也可以是 Type2,也可以是 Type3,等等
字面量联合
以限制一个值只能从一组预定义的选项中选择,类似于枚举
type Direction = "north" | "south" | "east" | "west";let move: Direction;move = "north"; // 正确
move = "up"; // 错误!"up" 不是有效的选项
基本类型联合 - 一个变量可以是数字或字符串
let id: number | string;id = 101; // 正确
id = "ABC101"; // 正确
id = true; // 错误!Boolean 类型不在联合类型中
复杂对象类型联合
interface Circle {kind: "circle";radius: number;
}interface Square {kind: "square";sideLength: number;
}type Shape = Circle | Square;function getArea(shape: Shape) {// 处理 shape 的逻辑...
}getArea({ kind: "circle", radius: 5 }); // 正确
getArea({ kind: "square", sideLength: 10 }); // 正确
注意问题-类型收窄
value = 222;
console.log(value / 2);
value = null;
console.log(value / 2); // error: “value”可能为 “null”。
TypeScript 在编译时会进行严格的类型检查, 此处会判断value的类型检查,但在某些情况下 => 当一个值是联合类型时,TypeScript 只知道它是这些类型的其中之一,但不知道具体是哪一个,在使用之前需要判断
function printId(id: number | string) {console.log(id.toUpperCase()); // 错误!
}
如上代码 在这里直接调用toUpperCase方法会报错,因为toUpperCase是string类型的方法在number类型上并不存在,此时需要结合类型判断
或者断言
使用
typeof 类型守卫
适用于基本类型(string, number, boolean, symbol)
instanceof 类型守卫
适用于类(class)
可辨识联合
使用switch进行判断
交叉类型
交叉类型使用 &
符号将多个类型连接起来。
语法
TypeA & TypeB & TypeC ...
新类型将拥有 所有 参与类型的属性和方法。可以理解为类型的"合并"或"叠加"。
基本类型交叉
基本类型的交叉通常会产生 never 类型(即不可能存在的类型)
type Impossible = string & number; // 类型为 never
// 因为一个值不可能同时是 string 和 number
对象类型的交叉(最常用)
type type1 = {name: string,age: number
}type type2 = {color: string
}// 交叉类型: 同时拥有 type1 与type2 的所有属性
type type = type1 & type2 // an存在3个属性const an1: type = {name: 'dog',age: 2,color: 'yellow'
}
对象交叉类型若是存在类型有重叠时,交叉会合并属性
;
type cat = {name: string,age: number,color: string
}type dog = {name: string,age: number,color: string[],
}type an = cat & dog const an1: an = {name: 'dog',age: 2,color: // 这里无论写什么值都会报错 因为color的类型为 string & string[] 类型 即never类型
}
上述an类型存在类型冲突不能声明任何对象了;
联合类型 与交叉类型 对比
泛型
定义
泛型使用类型变量来表示类型,在定义时不指定具体类型,在使用时再指定。
语法
<T>、<K>、<V> // 通常使用单个大写字母
在函数中使用
-
语法
function 函数名<T>(){... }
-
示例:举一个最简单的例子, 希望函数接收一个参数并返回 => 并不知道参数值的类型
function getParam<T>(params: T): T{return params }console.log(getParam(111)) // 111 console.log(getParam('string')) // string
接口中使用泛型
- 语法
interface 接口名<T>{... }
- 示例:Array 是一个在接口中使用泛型的完美例子( Array是Typescript内置的接口)
let arr: Array<number> // arr就是一个元素为number类型的数组
别名中使用泛型
- 语法
type 别名<T> = ...
泛型约束 - 接口+ extends继承
有时候需要给泛型添加一定的约束, 可以使用 接口 + extends继承
=> 比如该类型必须存在length属性
interface HasLength{length: number
}function getParams<T extends HasLength>(params: T): T{return params
}console.log(getParams(111)) // error: Argument of type 'number' is not assignable to parameter of type 'HasLength'.
console.log(getParams('string')) // string
console.log(getParams([111])) // [111]
泛型约束- keyof
keyof它用于限制泛型参数必须是某个类型
的键。
-
语法
T extends keyof 类型
-
举例说明: 安全的属性访问函数 => 现在创建一个函数,返回person对象的属性(必须是存在的属性才能返回)
const person = {name: 'chaochao',age: 18,area: 'china' }function getPersonInfo<K extends keyof person>(key:K){ // errorreturn person[key] }
上述代码是标红的,能看出为什么吗?
原因是 K extends keyof person中 person是一个值并不是一个类型,所以我们需要获取该值的类型进行声明
这样就没有问题了 -
用例扩展: 若是需要传入某个对象并返回该对象的值应该如何处理呢?
const person = {name: 'chaochao',age: 18,area: 'china' }function getPersonInfo<T, K extends keyof T>(obj: T, key:K){return obj[key] }getPersonInfo(person, 'name') // 'chaochao' getPersonInfo(person, 'sex') // error: Argument of type '"sex"' is not assignable to parameter of type '"name" | "age" | "area"'.
条件判断
type IsString<T> = T extends string ? true : false
let bol1: IsString<111> //bol1是一个常量 false
let bol2: IsString<'222'> //bol1是一个常量 true
上述在编译时判断类型 T 是否是 string 类型,基于以上认知,也可以判断T是否是函数/数组/对象…类型
// 检测是否是函数类型
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;// 检测是否是数组类型
type IsArray<T> = T extends any[] ? true : false;// 检测是否是对象类型(排除原始类型)
type IsObject<T> = T extends object ? T extends Function | any[] ? false : true : false;
常用的内置接口
Array/ReadonlyArray/Promise
interface Array<T>{[index: number]: T,... 一些内置方法}