TS简介
TS是JS的静态类型检查器,也成为静态编译;添加了可选的静态类型、和基于类的面向对象编程,不会影响程序的基本运行
JS、ES、TS的关系
ES是JS的标准,TS是JS的超集

TS的竞品
-
TypeScript:核心是静态类型系统,提供编译时类型检查
-
ESLint/TSLint:专注于代码风格和质量检查(如命名规范、潜在错误),不直接提供类型系统。其中ESLint支持 JS 和 TS,架构更适合增量检查
-
CoffeeScript:改进 JS 语法结果被JS反超
-
Flow:Facebook 的静态类型检查器,类似 TS,但更强调与现有 JS 代码的兼容;但维护力度下降
ts的特点
静态类型代码:预测代码
安装ts=>npm install typescript -g =>运行代码=>tsc 文件名.ts=>生成对应js文件,运行该文件=>node 文件名.js
类型别名:类型名字=类型
因为ts生成js文件后会产生两个相同的函数,所以命名重复,并且每次修正ts都要重新tsc该文件

所以我们进行了优化编译:
优化编译
解决ts和js的命名冲突问题:tsc --init,生产配置文件
自动编译:tsc --watch
发出错误:tsc -noEmitOnError hello.ts
显式类型:形如function 函数名(参数名:参数类型。参数名:参数类型)
function greet(person, data) {
console.log("hello ".concat(person, " ").concat(data));
}
greet('august',new Date());
ts会根据类型初始定义推断变量的类型:
降级编译
不适用es6语法的文件可以通过修改tsconfig.json的target属性来降级
严格模式
"strict": true,//注释掉该属性,取消严格模式
等效开启这些设置项:
{
"compilerOptions": {
// 禁止隐式any类型(必须显式声明any类型)
"noImplicitAny": true,
// 严格的null/undefined检查(必须明确处理null和undefined)
"strictNullChecks": true,
// 严格的函数类型检查(函数参数类型必须严格匹配)
"strictFunctionTypes": true,
// 严格的bind/call/apply检查(确保调用方法时参数类型正确)
"strictBindCallApply": true,
// 严格的类属性初始化检查(类属性必须初始化)
"strictPropertyInitialization": true,
// 禁止隐式this类型(必须明确this的类型)
"noImplicitThis": true,
// 生成的JS代码中使用严格模式(自动添加"use strict")
"alwaysStrict": true
}
}
数据类型
基元类型
string、number、boolean
//ts
let str: string = 'hello world'
let number:number=100
let bool:boolean=true
经过tsc --watch以后生成对应的js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
let str = 'hello world';
let number = 100;
let bool = true;
//# sourceMappingURL=01-hello-world.js.map
数组类型
数组的定义:type[]和Array<type>,type为任意合法的类型
let arr: number[] = [1, 2, 3, 4]
let arr2: Array<number> = [1, 1, 1, ]
any类型
当你不希望某个特定值导致类型检查错误,可以使用any:
let obj: any = {
x: 0,
}
obj.foo()
obj()
obj.bar = 100
obj = 'hello'
const n:number=obj
这么写ts是不会报错的,但生成的js无法运行
函数类型
在ts里使用函数,运行我们指定函数的输入和输出值的类型

function greet(name: string) {
console.log('Hello,'+name.toUpperCase()+'!!')
}
greet('august')
//Hello,AUGUST!!
function getFavoriteNumber(num:number):number {
return num
}
//100
console.log(getFavoriteNumber(100))
const names = ['august', 'hello', 'world']
names.forEach(name=>{
console.log(name.toUpperCase())
})
//AUGUST
//HELLO
//WORLD
对象类型

function printCoord(pt:{x:number, y:number}){
console.log('x坐标为:'+pt.x, 'y坐标为:'+pt.y)
}
printCoord({x:100, y:200})
function printName(obj:{first:string, last?:string}){//last为可选
// if(obj.last!==undefined){
// console.log(obj.first+obj.last)
// }
console.log(obj.last?.toUpperCase())//如果last存在,则转换为大写
}
//printName({ first: 'august', last: 'hello', xxx: 'aaaa' })不能额外传递属性
printName({ first: 'august', last: 'hello' })
联合类型
允许我们从现有类型中构建新类型,表示可能是这些类型中的一个

function printId(id: number | string | number[]) {
//console.log(id.toUpperCase())类型“string | number”上不存在属性“toUpperCase”。
//类型“number”上不存在属性“toUpperCase”。
if (typeof id === 'string') {
console.log(id.toUpperCase())
} else {
console.log(id)
}
}
printId(101)//101
printId('aaa')//AAA
// printId({
// id:101
// })类型“{ id: number; }”的参数不能赋给类型“string | number”的参数。
function welcomePeople(x: string[] | string) {
if (Array.isArray(x)) {
console.log('hello' + x.join(' and '))
} else {
console.log('hello' + x)
}
}
welcomePeople(['august', 'hello world'])//helloaugust and hello world
function getFirstThree(x: number[] | string) {
return x.slice(0, 3)
}
const arr = getFirstThree([1, 2, 3, 4, 5])
console.log(arr)//[ 1, 2, 3 ]
为了方便,我们可以给重新组合起来的元素起别名来多次使用
类型别名
可以定义对象类型

type Point = {
x: number
y: number
}
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x)
console.log("The coordinate's y value is " + pt.y)
}
printCoord({
x: 100,
y: 100,
})//The coordinate's x value is 100
//The coordinate's y value is 100
type ID = number | string
function printId(id: ID) {
console.log(id)
}
printId(101)//101
printId('aaa')//aaa
//type定义变量类型
type UserInputSanitized = string | number
function sanitizeInput(str: string):UserInputSanitized {
return str.trim()
}
let userInput = ' Hello, world! '
let sanitized = sanitizeInput(userInput)
console.log(sanitized)//Hello, world!
接口类型
是一种定义对象的方式,通过关键字interface来定义
interface Point{
x: number
y: number
}
function printCoord(pt:Point) {
console.log(`The coordinate's x value is ${pt.x}`)
console.log(`The coordinate's y value is ${pt.y}`)
}
printCoord({
x: 100,
y: 100,
})
几乎所有interface定义的类型都可以通过type类型别名来定义
interface Animal{
name: string
}
interface Bear extends Animal{
honey: boolean
}//继承
const bear:Bear={
name: 'bear',
honey: true,
}
console.log(bear.name)//bear
console.log(bear.honey)//true
如上的代码等效于如下代码:
type Animal = {
name:string
}
//使用&号合并类型
type Bear = Animal & {
honey: boolean
}
const bear:Bear={
name: 'bear',
honey: true,
}
但是type不能合并同字段的属性,interface可以
//向现有的类型添加字段
interface MyWindow{
count:number
}
interface MyWindow{
title:string
}
const w: MyWindow = {
count: 100,
title: 'hello world',
}//两次同名的接口合并属性成功
type不能向已有字段添加,只能用前面说过的&继承
//类型创建以后不能更改
type MyWindow = {
title:string
}
type Mywindow = {
count: number
}
//只能通过&符号合并
type MyWindowAndMywindow = MyWindow & Mywindow
const w: MyWindowAndMywindow = {
title: 'hello world',
count: 100,
}
console.log(w.count)//100
类型断言
断言指的是开发者手动指定值的类型的方式,可以理解为告诉编译器“我知道这个值的具体类型,不需要你检查”,但是不会真的转换数据类型,不过你要承担断言错误的风险
两种定义方式

与类型注释相同,不影响代码的运行时行为,在编译后被删除
const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement
const myCanvas2 = <HTMLCanvasElement>document.getElementById('main_canvas')
//告诉编译器document.getElementById('main_canvas')返回的元素是HTMLCanvasElement 类型(而不是默认的 HTMLElement)
ts可以防止不可能的强制的发生
断言本身是一种强制告诉编译器我作为开发者已知该类型,“防止不可能的强制的发生”的意思是:当你想把一个值断言成一个它 根本不可能属于的类型 时,TypeScript 会报错,防止这种不安全的操作。
const x = ('hello' as any) as number//双重类型断言,强制将hello断言为一个number
也可以写为unknown,在语义上更明确
const x = ('hello' as unknown) as number//双重类型断言,强制将hello断言为一个number
文字类型
文字类型表示一个固定的值,类似于let和const的区别

例如下面这种情况,const是不能变的
let testString = 'hello'
testString = 'world'
const testString2 = 'hello'
//testString2 = 'world'//error
等效于我给x定为类型是“hello”的字符串,那么它就不能是别的类型,x必须是字符串且为“hello”
let x: 'hello' = 'hello'
//x='world'//不能将类型“"world"”分配给类型“"hello"”。
文字类型是死板的,例如这里的aligement必须是left、right、center
function printText(s: string, aligement: 'left' | 'right' | 'center') {
console.log(s + " " + aligement)
}
printText('hello', 'left')//hello left
printText('hello', 'right') //hello right
printText('hello', 'center')//hello center
应用举例:
比大小返回对应的结果
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1
}
进行类型收窄:
interface Options {
width: number
}
function config(x: Options | 'auto') { console.log('Auto mode')
}
config({ width: 100 })
config('auto')
布尔文字类型:
let b1: true = true
let b2: false = false
文字推理
里面也提到了断言的用法,限制类型放宽
文字推理决定了 TypeScript 是将一个值视为具体的字面量类型,还是更宽泛的原始类型。在这里我们限制req.method必须是‘GET’字面量而不是字符串,这就是文字推理
function handleRequest(url: string, method: 'GET' | 'POST') {
console.log(url, method)
}
const req = {
url: 'https://www.bilibili.com',
method: 'GET' as const, //告诉 TypeScript 不要放宽类型推断,保持 'GET' 的字面量类型
}
handleRequest(req.url,req.method)
//另一种写法
function handleRequest(url: string, method: 'GET' | 'POST') {
console.log(url, method)
}
const req = {
url: 'https://www.bilibili.com',
method: 'GET'
}
handleRequest(req.url, req.method as 'GET')
null和undefined类型
和联合类型一起用
let x = undefined //自动推断
let y = null //自动推断
let x // 类型推断为 any,值为 undefined
//let z: string=undefined 严格模式下错误
function doSomething(x: string | null) {
if (x === null) {
console.log('x is null')
} else {
console.log(x.toUpperCase())
}
}
function liveDangerously(x?:(number | null)) {
console.log(x!.toFixed(2))
//“!”断言 x 不是 null/undefined,强制允许调用 toFixed
}
liveDangerously(100)//100.00
liveDangerously(null)//运行时错误
liveDangerously(undefined)//运行时错误
枚举
确定需要枚举的时候使用,否则也没必要使用

enum Direction {
Up=1,
Down,
Left,
Right,
}
console.log(Direction.Up)//1
console.log(Direction.Down)//2
console.log(Direction.Left)//3
console.log(Direction.Right)//4
不常用的原语
bigint:非常大的整数
symbol:全局唯一的引用
const oneHundred: bigint = BigInt(100)
const anotherHundred: bigint = 100n
const firstSymbol = Symbol('name')
const secondSymbol = Symbol('name')
//console.log(firstSymbol === secondSymbol) 始终返回false
类型缩小
宽类型->窄类型,一般用在联合类型里
前面我们用断言处理过,也可以加个条件判断

详细介绍类型缩小
类型守卫
收窄变量的类型范围

例如这样:
function printAll(str: string | string[] | null) {
if (typeof str === 'object') {//是数组就遍历
for (const s of str) {
console.log(s) //“str”可能为 “null”,因为null也是object
}
}else if(typeof str==='string'){//是字符串就打印
console.log(str)
} else {
//...
}
}
但是上面的null也是object,这样判断是有问题的,后面会讲
真值缩小
使用条件、&&、||、if语句、布尔否定(!)

使用&&修改前面的代码
function printAll(str: string | string[] | null) {
if (str && typeof str === 'object') {
//是数组就遍历
for (const s of str) {
console.log(s) //&&进行非空检查,确保str不为null,再进行typeof判断
}
} else if (typeof str === 'string') {
//是字符串就打印
console.log(str)
} else {
//...
}
}
示例:
function multiplyAll(
values: number[] | undefined,
factor:number
) {
if(values){
return values.map(x=>x*factor)
}
return []
}
console.log(multiplyAll([1, 2, 3], 2))//[ 2, 4, 6 ]
console.log(multiplyAll(undefined, 2))//[]
等值缩小
使用===、!==、==、!=
function example(x: string | number, y: string | boolean) {
if (x === y) {
//检查值和类型
x.toUpperCase()
y.toLowerCase()
} else {
console.log(x)
console.log(y)
}
}
还是修改前面的printAll函数:
function printAll(str: string | string[] | null) {
if (str !== null) {
if (typeof str === 'object') {
//是数组就遍历
for (const s of str) {
console.log(s) //“str”可能为 “null”,因为null也是object
}
} else if (typeof str === 'string') {
//是字符串就打印
console.log(str)
} else {
//...
}
}
}
同时屏蔽null和undefined
interface Container{
value:number|null|undefined
}
function multiplyValue(container: Container, factor: number) {
if(container.value != null ){//!null是过滤undefined和null的常用写法
console.log((container.value *= factor))
}
}
multiplyValue({value:10},2)//10
multiplyValue({ value: undefined }, 6)//未进入分支
multiplyValue({ value: null }, 6)//未进入分支
in操作符缩小
因为有了联合类型,联合类型实际是由多个基础类型组成的,所以可以通过in操作符看组合类型是否含有某个基本类型

举例
type Fish = {
swim: () => void
}
type Bird = {
fly: () => void
}
function move(animal: Fish | Bird) {
if ('swim' in animal) {
console.log('这是🐟')
} else if ('fly' in animal) {
console.log('这是鸟')
} else {
console.log('这是未知动物')
}
}
const fish:Fish={
swim:()=>{}
}
move(fish)
如果我们再添加一个人类组合类型,人类的游泳类型是可选的,这在判断的时候会出错,所以我们在这里使用了断言,当然断言并不是最好的解决办法
type Fish = {
swim: () => void
}
type Bird = {
fly: () => void
}
type Human = {
swim?: () => void //人类可能会游泳
walk: () => void
}
function move(animal: Fish | Bird | Human) {
if ('swim' in animal) {
return (animal as Fish).swim()
} else if ('fly' in animal) {
return (animal as Bird).fly()
} else {
console.log('这是未知动物')
}
}
const XiaoMing: Human = {
swim: () => { },
walk: () => { },
}
move(XiaoMing)
instanceof操作符缩小
检查一个值是否为另一个值的实例
![]()
判断x是否为Date对象
function logValue(x:Date|string) {
if (x instanceof Date) {
console.log(x.toUTCString())//日期对象转换为 UTC 时间字符串
} else {
console.log(x.toUpperCase())//字符串对象转换为大写字符串
}
}
logValue(new Date())//Tue, 28 Nov 2023 03:00:00 GMT
logValue('hello')//HELLO
分配缩小

以这个代码为例子可以看出x会根据我们一开始给遍历赋的值,ts会自动根据所赋的值来收窄该遍历的类型范围
我们限制了数字和字符串,就无法再赋值布尔值
//x为联合类型,包括number和string
let x = Math.random() < 0.5 ? 10 : 'hello'
x = 1
console.log(x)//1
x = `xixixi`
console.log(x)//xixixi
x=true//不能将类型“boolean”分配给类型“string | number”。
控制流分析
通过分析代码的执行路径(条件分支、循环、返回语句等)来 自动推断和收窄变量的类型,也是一种静态分析,在编译时进行,不影响运行

可以看出控制流一直在控制x的类型
function example() {
let x: string | number | boolean
x = Math.random() < 0.5
//x:=boolean
console.log(x)
if (Math.random() < 0.5) {
x = 'hello'
//x:=string
console.log(x)
} else {
x = 100
//x:=number
console.log(x)
}
return x
}
let x = example()
初始: string | number | boolean
↓
赋值: boolean (50% true, 50% false)
↓
条件分支:
├─ 50%: 赋值为 string ("hello")
└─ 50%: 赋值为 number (100)
↓
最终: string | number
使用类型谓词
在这个函数里,如果pet是Fish,就返回true
其中用到了断言,就是我们假设pet一定有swim这个方法,只不过可能为undefined

一个类型守卫和类型谓词结合的例子
function getSmallPet(): Fish | Bird {
let fish: Fish = {
name: 'sharkey',
swim: () => {},
}
let bird: Bird = {
name: 'sparrow',
fly: () => {},
}
return true ? bird : fish //返回一个Bird的宠物
}
let pet = getSmallPet()
if (isFish(pet)) {
pet.swim()
} else {
console.log(pet.name) //sparrow
pet.fly()
}
这里的fliter用法的优势在于ts的过滤可以通过isFish里的类型谓词,自动过滤后的确切类型为Fish[]
function getSmallPet(): Fish | Bird {
let fish: Fish = {
name: 'sharkey',
swim: () => {},
}
let bird: Bird = {
name: 'sparrow',
fly: () => {},
}
return true ? bird : fish //返回一个Bird的宠物
}
let pet = getSmallPet()
if (isFish(pet)) {
pet.swim()
} else {
console.log(pet.name) //sparrow
pet.fly()
}
let zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()]
const underWater1: Fish[] = zoo.filter(isFish)//使用类型谓词过滤数组
const underWater2: Fish[] = zoo.filter(isFish) as Fish[]//使用类型断言,可能会冗余
const underWater3: Fish[] = zoo.filter((pet): pet is Fish => {
if (pet.name === 'frog') {
return false
}
return isFish(pet)
})//灵活的过滤方式,自定义类型谓词
//三个都返回空数组
unions

举个例子,在下面的代码getArea方法里,很明显我们的shape.kind不一定是radius,所以不一定可以求圆的面积,这里我们用了强制断言,但这并不是一种最好的处理方法
interface Shape{
kind:'circle'|'square'
radius?: number
sideLength?: number
}
function handleShape(shape: Shape) {
if (shape.kind === 'square') {
}
}
function getArea(shape:Shape){
if(shape.kind==='circle'){
return Math.PI*shape.radius!**2//平方求圆面积,使用断言强制确定该值不为null和undefined
}
}
经过以下的修改就不会涉嫌危险断言的问题了
这使用了 Discriminated Union 模式
每个接口都有一个共同的判别字段,例如kind
每个接口都有自己特定的必须属性
ts可以根据判别的字段自动收窄类型
可以应用在状态管理上
interface Circle {
kind: 'circle'
radius: number
}
interface Square {
kind: 'square'
sideLength: number
}
type Shape = Circle | Square
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2
case 'square':
return shape.sideLength ** 2
}
}
never类型与穷尽性检查
never类型标识永远不肯发生的值的类型
用于表示以下情况:
- 函数永远不会正常返回
- 类型收窄后不可能存在的分支
- 不可能发生的情况

never大概是这样用的
interface Circle {
kind: 'circle'
radius: number
}
interface Square {
kind: 'square'
sideLength: number
}
interface Triangle {
kind: 'triangle'
sideLength: number
}
type Shape = Circle | Square | Triangle
function getArea(shape: Shape) {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2
case 'square':
return shape.sideLength ** 2
default:
// 类型“Shape”上不存在属性“kind”时走此分支
const _exhaustiveCheck: never = shape //不能将类型“Triangle”分配给类型“never”
//因为在定义Triangle之前,不是圆和方形的结果走到default必然为never
//现在定义了Triangle,所以走到default的结果为Triangle,不能赋值给never
return _exhaustiveCheck
}
}
在ts上如何使用函数
函数类型表达式
长得像箭头函数

大概就是这样
function greet(fn: (a: string) => void) {
fn('hello')
//fn的接收值为空
}
function printToConsole(s: string) {
console.log(s)
//printToConsole的返回值为空
}
greet(printToConsole)//打印hello
调用签名
调用签名是一种描述函数类型的方法
如果我们想用属性来描述可调用的东西,可以在对象里写一个调用签名
// 1. 普通的函数声明
function add(a: number, b: number): number {
return a + b;
}
// 2. 调用签名(描述函数类型)
type AddFunction = (a: number, b: number) => number;
注意只有函数表达式赋值给变量后才能添加属性
type DescribableFunction = {
description: string //属性:函数有一个 description 字符串属性
(someArg: number): boolean // 调用签名:函数本身接受 number 参数,返回 boolean
} //同时具有属性和调用签名的函数类型定义
function doSomething(fn: DescribableFunction) {
console.log(fn.description + '的结果是' + fn(100))
} //doSomething的参数fn的类型是DescribableFunction
//function fn(someArg: number) {
//return someArg > 100
//}这样是不行的,要写成函数表达式赋值给变量的形式,例如下面这个
const fn: DescribableFunction = (someArg: number): boolean => {
return someArg > 100
} //正好fn的类型就是一个DescribableFunction
fn.description = '判断是否大于100'
doSomething(fn) //判断是否大于100的结果是false
构造签名
ts里描述构造函数类型的方式,类似于调用签名,但是用于构造函数

与调用签名的区别其实就是一个用作普通函数,一个用作构造函数
// 调用签名(普通函数)
interface Callable {
(x: number): string;
}
// 构造签名(构造函数)
interface Constructable {
new (x: number): MyClass;
}
举个例子展现构造签名的使用
class Ctor {
s: string
constructor(s: string) {
this.s = s
}
}
type SomeConstructor = {
new (s: string): Ctor
}//构造签名
function fn(ctor: SomeConstructor) {
return new ctor('hello') //ctor相当于一个构造函数,返回这个构造函数的实例
}//接收任何符合SomeConstructor签名的构造函数
console.log(fn(Ctor).s)//hello
结合使用调用签名和构造签名
interface CallOrConstructor {
new (s: string): Date
(n: number): number
}
function fn(date: CallOrConstructor) {
let d = new date('2023-01-01')
let n = date(100)
}
泛型函数
泛型函数允许我们创建可重用、类型安全的函数,能够处理多种类型

在这里用any就会有问题,因为返回的类型不准确,不安全
function firstElement(arr: any[]) {
return arr[0]
}
console.log(firstElement(['a', 'b', 'c']))//a,但是a的类型是any而不是string
console.log(typeof firstElement(['a', 'b', 'c']))//在js里是object,在ts里是any
在调用时由ts根据传入的参数自动推断类型
//使用泛型
//此时的T代表这一个类型变量,意为“某种类型”,而函数在调用时会自己推断T的具体类型
function firstElement<T>(arr: T[]) :T|undefined{
return arr[0]
}
console.log(firstElement(['a', 'b', 'c']))//a,保证了传入和传出的类型匹配
console.log(firstElement([1, 2, 3]))//1,保证了传入和传出的类型匹配
console.log(firstElement([true, false, true]))//true,保证了传入和传出的类型匹配
泛型是推测你的返回值,不过你也可以指定返回类型
//使用泛型
//此时的T代表这一个类型变量,意为“某种类型”,而函数在调用时会自己推断T的具体类型
function firstElement<T>(arr: T[]) :T|undefined{
return arr[0]
}
console.log(firstElement<string>(['a', 'b', 'c']))//a,保证了传入和传出的类型匹配
console.log(firstElement<number>([1, 2, 3]))//1,保证了传入和传出的类型匹配
console.log(firstElement<boolean>([true, false, true]))//true,保证了传入和传出的类型匹配
泛型不只可以定义一个,还可以定义多个
在这里我们模拟map,设定输入的泛型和输出的泛型的参数,在这里<I>就是string,<O>就是number和string
//模拟map
function map<I, O>(arr: I[], fn: (item: I) => O): O[] {
return arr.map(fn)
}
console.log(map([1, 2, 3], (item) => item * 2)) //[2, 4, 6]
console.log(map(['a', 'b', 'c'], (item) => item.toUpperCase())) //['A', 'B', 'C']
限制条件
限制条件就是给泛型添加限制,本来是什么类型都可以匹配返回,现在必须保证有限制的那个类型才能返回
function longest<T extends { length: number }>(a: T, b: T) {
if (a.length >= b.length) {
return a
} else {
return b
}
}
console.log(longest([1, 2, 3], [4, 5]))//[1, 2, 3]
console.log(longest(['a', 'b', 'c'], ['d', 'e']))//['a', 'b', 'c']
console.log(longest('123', '456'))//'123'
console.log(longest('123', '4567'))//'4567'
使用受限值
当我们对泛型参数使用了约束(extends)后,在函数体内只能访问约束中定义的属性和方法,而不能访问类型参数可能具有的其他属性。
这么写是有问题的,ts这么做是为了防止类型欺骗
function minimumLENGTH<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj
} else {
return { length: minimum }//不能将类型“{ length: number; }”分配给类型“Type”。
//Type确实含有length属性,但是length:number并不是Type类型
}
}
也可以使用断言,但是也是不建议真正去这样做
function minimumLENGTH<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj
} else {
return { length: minimum } as Type //不能将类型“{ length: number; }”分配给类型“Type”。
//但是可以使用类型断言将其转换为Type类型
}
}
const arr = minimumLENGTH([1, 2, 3], 6)
console.log(arr.length) //4
更正确的做法得看是对于什么数据来处理,如果是单纯的数组可以用展开运算符,如果是别的对象就要复制对象了
function minimumArrayLength<T>(arr: T[], minimum: number): T[] {
if (arr.length >= minimum) {
return arr;
} else {
// 创建新数组,填充 undefined 或默认值
const newArray = [...arr];
newArray.length = minimum;
return newArray;
}
}
// 使用
const arr = minimumArrayLength([1, 2, 3], 6);
console.log(arr.length); // 6
console.log(Array.isArray(arr)); // true
console.log(arr); // [1, 2, 3, undefined, undefined, undefined]
指定类型参数
在泛型的基础上,指定函数参数的类型
function combine<Type>(arr1: Type[], arr2: Type[]): Type[] {
return arr1.concat(arr2)
}
//const arr=combine([1,2,3],['string'])这么写是有问题的,因为这里的Type只能容许第一个传入的数组的类型,
//而第二个数组的类型是不能被推断出来的,所以这里需要指定类型参数
const arr = combine<number | string>([1, 2, 3], ['string'])
console.log(arr)//[ 1, 2, 3, 'string' ]
如何编写优秀的通用函数

准则1:
function firstElement<Type>(arr: Type[]): Type | undefined {
return arr[0]
}//一般建议用类型参数
function lastElement<Type extends any[]>(arr: Type[]): Type | undefined {
return arr[arr.length - 1]
}//而不是使用extends对其进行约束
const arr = [1, 2, 3]
const first = firstElement(arr)
const last = lastElement(arr)//类型“number[]”的参数不能赋给类型“any[][]”的参数。
//不能将类型“number”分配给类型“any[]”。
准则2:
function filter1<Type>(arr: Type[], callback: (arg: Type) => boolean) {
return arr.filter(callback)
}
function filter2<Type, Callback extends (arg: Type) => boolean>(//没必要在此处单独定义一个类型参数
arr: Type[],
callback: Callback
) {
return arr.filter(callback)
}
准则3:
function greet(str: string) {
console.log(str)
}
greet('hello')//更简洁
function greet2<Str extends string>(str: Str) {
console.log(str)
}
greet2('hello')
总的来说我感觉就是避免使用无谓的限制条件
可选参数
某个参数是可选的,填不填都行的,用问号标明
function f(a: number, b?: number) {//b为可选
if (b) {
return a + b
}
return a
}
和!的区别是:!通常用于断言某个值一定存在
interface User {
name?: string; // 可选属性
}
function getUserName(user: User): string {
return user.name!; // 断言 name 一定存在
}
回调函数的可选参数

回调参数的可选性应该由调用方(你的 myForEach 函数)的行为决定,而不是由回调函数的类型声明随意决定。
function myForEach(arr: any[], callback: (item: any, index?: number) => void) {//不要在回调函数中使用可选参数
//forEach的回调函数中,不能使用可选参数
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i)
}
}
myForEach([1, 2, 3], (item) => {
console.log(item)
})
myForEach([1, 2, 3], (item, index) => {
console.log(item, index)
})
函数重载
重载签名:只有类型声明,没有函数体
实现签名:只有具体的函数代码,实现签名只有一个,并且兼容所有的重载签名
function makeDate(timestamp: number): Date //重载签名
function makeDate(m: number, d: number, y: number): Date //重载签名
function makeDate(m: number, d?: number, y?: number): Date {//实现签名
if (d !== undefined && y !== undefined) {
return new Date(y, m, d)
} else {
return new Date(m)
}
}
const d1 = makeDate(1000000000000)
const d2 = makeDate(1000000000000, 10, 10)
//const d3 = makeDate(1000000000000, 10)不能这么写,因为并没有重载签名与之对应
重载签名和实现签名
研究重载签名和实现签名的三部分:参数不正确、参数类型不正确、返回类型不正确
我们只能根据重载签名传入参数,不能通过实现签名
function fn(x: string): void
function fn() {}
fn(hello)
实现签名的参数类型要兼容
function fn(x: boolean): void
function fn(x: string): void
function fn(x: boolean | string) {
}
实现前面的返回值类型要与函数重载对应
function fn(x: boolean): boolean
function fn(x: string): string
function fn(x: boolean | string) {
if (typeof x === 'boolean') {
return x//返回值满足第一个重载签名的要求
} else {
return x//返回值满足第二个重载签名的要求
}
}
编写优秀的重载
一般建议使用联合类型的参数,而不是重载函数
function len(s: string): number
function len(arr: any[]): number
function len(x: any) {
return x.length
} //可以使用字符串和数组调用,但是不可以用可能是数组和字符串的类型调用
len('hello')
len([1, 2, 3])
len(Math.random() > 0.5 ? 'hello' : [1, 2, 3])//报错
拯救方法:使用联合类型
function len(x: string | any[]) {
return x.length
}
len('hello')
len([1, 2, 3])
len(Math.random() > 0.5 ? 'hello' : [1, 2, 3]) //报错
函数内的this声明
和js一样,TS不允许为箭头函数使用this参数
interface User {
admin: boolean
}
interface DB {
filterUsers(filter: (this: User) => boolean): User[]
}
const db: DB = {
filterUsers(filter: (this: User) => boolean): User[] {
let user1: User = {
admin: true,
}
let user2: User = {
admin: false,
}
return [user1, user2]
},
}
const admins = db.filterUsers(function (this: User) {
return this.admin
})//普通函数可以使用this参数
//onst admins2=db.filterUsers((this:User)=>{
// return this.admin
//})箭头函数不能使用this参数,因为箭头函数没有自己的this,只能通过上下文捕获
console.log(admins)
补充额外类型
在js里如果不写函数的返回值,默认返回undefined,但是在ts里void和undefined是不一样的




参数展开运算符--形参展开
其实就是es6的剩余参数
function multiply(n: number, ...m: number[]) {
return m.map((x) => x * n)
}
const a = multiply(2, 1, 2, 3)//[1,2,3]归为接收剩余参数的部分
console.log(a)//[ 2, 4, 6 ]
参数展开运算符--实参展开
展开运算符
const arr1 = [1, 2, 3]
const arr2 = [4, 5, 6]
arr1.push(...arr2)
console.log(arr1)//[ 1, 2, 3, 4, 5, 6 ]
//const args = [8, 5]
//const angle = Math.atan2(...args)//错误使用,因为参数不是数组
const args = [8, 5] as const
const angle = Math.atan2(...args); // ✅ 正确
参数解构
其实还是展开运算符的用法
function sum({ a, b, c }: { a: number; b: number; c: number }) {
console.log(a + b + c)
}
sum({
a: 10,
b: 20,
c: 30,
})//60
把组合类型提出来
//写的简单一点就是这样
type sumType = {
a: number,
b: number,
c: number
}
function sum({ a, b, c }: sumType) {
console.log(a + b + c)
}
sum({
a: 10,
b: 20,
c: 30,
})//60
返回void类型

type voidFunc = () => void
const f1: voidFunc = () => {
return true
}
const f2: voidFunc = () => true
const f3: voidFunc = function () {
return true
}
//这种写法没问题
const v1: void = f1()
const v2: void = f2()
const v3: void = f3()
console.log(v3)//true,即使我们要求返回值为void,也会返回true,只不过我们常常会忽略
// 不能将类型“boolean”分配给类型“void”。
// const v4: boolean = f1()
// const v5: boolean = f2()
// const v6: boolean = f3()
// 这是因为void 类型的变量只能赋值 undefined 和 null,而 boolean 类型的变量可以赋值 true 和 false。
// 所以,当我们将一个 boolean 类型的变量赋值给一个 void 类型的变量时,会报错。

1573

被折叠的 条评论
为什么被折叠?



