5.1、什么是TypeScript的函数类型
//1、声明的写法
// 参数类型 返回值类型
function foo(arg: number): number {
return 123
}
//2、表达式写法
// foo本身也是一个标识符(对象), 也应该有自己的类型,可以写成any(不太好、可以做任何操作、没限制她的范围、我们可以明确指定函数的)
const bar: any = (arg: number): number => {
return 123
}
在JavaScript开发中、函数是重要的组成部分、并且函数可以作为一等公民(可以作为参数、也可以作为返回值进行传递)
// 定义一个方法、fn 参数是一个方法(调用这个方法的时候传入的参数是一个函数)
function delayExecFn(fn) {
}
那么在使用的过程中、函数是否也可以有自己的类型呢?
我们可以编写函数类型表达式(Function Type Expressions),来表示函数类型
5.2、函数类型表达式
//格式:(参数列表)=>返回值
type BarType = (num1: number,num2:number) => number
const bar1: BarType = (arg1: number,arg2: number): number => {
return 123
}
export {}
在上面的语法中 (num1: number,num2:number) => number 代表的就是一个函数类型
- 接收两个参数的函数:num1和num2,并且都是number类型
- 并且这个函数是有返回值的,返回值为number类型的数字
注意:在某些语言当中,可能参数名称num1和num2是可以省略的,但是TypeScript不可以
你会发现为啥要有这种东西、开局的时候我们曾经提到过函数也是一个对象、一JS或者Ts编程当中的一等公民,是可以作为参数进行传递的、这便是函数表达式的作用、当函数作为参数时,限定传入函数的类型
5.3、函数类型表达式练习
type CalcType = (num1: number, num2: number) => number
// 1.函数的定义
function calc(calcFn: CalcType) {
const num1 = 10
const num2 = 20
const res = calcFn(num1, num2)
console.log(res)
}
// 2.函数的调用 满足 CalcType类型
function sum(num1: number, num2: number) {
return num1 + num2
}
// 3.函数的类型 并不满足CalcType类型
function foo(num1: number) {
return num1
}
calc(sum) // 不报错----完全满足函数的类型
calc(foo) // 不报错----为啥不报错?我foo函数只定义了一个参数啊????个数限定不了?
// 4、函数的调用 乘法计算
function mul(num1: number, num2: number) {
return num1 * num2
}
calc(mul)
// 5.直接使用匿名函数
calc(function(num1, num2) {
return num1 - num2
})
export {}
5.4、函数类型的参数个数
5.3中的代码发现很神奇、foo函数并不满足所谓的函数表达式定义的规则、但是为啥就是不报错、看下边的代码
// 1、定义类型别名
type CalcType = (num1: number, num2: number) => number
// 2.函数的定义
function calc(calcFn: CalcType) {
const num1 = 10
const num2 = 20
const res = calcFn(num1, num2)
console.log(res)
}
// 3.函数的类型 并不满足CalcType类型
function foo(num1: number) {
return num1
}
// 4、以foo作为参数调用calc报错嘛?
calc(foo) //并不报错
为什么不报错?
TypeScript对于传入的函数类型的参数个数不进行检测,但是当你把number变成String发现还是会报错,其实没检测个数,检测了类型
//TypeScript对于传入的函数类型的多余的参数会被忽略掉(the extra arguments are simply ignored.)
type CalcType = (num1: number, num2: number) => number
function calc(calcFn: CalcType) {
calcFn(10, 20) //这里的类型不能漏--实际参数校验
}
// 这个num 可传可不传都不会报错
calc(function(num) { 这是形参传递、 calcFn才是实际调用、假如形参定义了仨参数、在调用执行的时候传递了俩,也是会被校验的
return 123
})
注意:实际传入的参数比你定义的形参参数个数多,额外的实际参数就被忽略掉了
为什么不校验?
原因很简单、因为我们在开发中用到的匿名函数太多了(就是把函数作为参数传递的太多了)
比如forEach()方法,大多数情况下我们并不需要把参数全部补齐(item,index,arr),通常情况下只写一个item,如果Ts有检验的化,意味这
我必须每次都写全,有点浪费时间了,大家就觉得比较繁琐、觉得Ts不好用了
const names = ["abc", "cba", "nba"]
names.forEach(function(item) {
console.log(item.length)
})
思考下如下代码报错嘛?
// TS对于很多类型的检测报不报错, 取决于它的内部规则
// TS版本在不断更新: 在进行合理的类型检测的情况, 让ts同时更好用(好用和类型检测之间找到一个平衡)
// 举一个栗子:
interface IPerson {
name: string
age: number
}
// typescript github issue, 成员
const p = {
name: "why",
age: 18,
height: 1.88,
address: "广州市"
}
const info: IPerson = p //并不报错---isuu上说了,取决于这个对象是否第一测定义,老对象并不进行检测(主要取决于她内部的规则)
export {}
总之Ts很抽象
冯巩牛琴的相声、说你行你就行、不行也行、说你不行,你就不行、行也不行
理解某些东西的时候从语言的设计角度来理解某些东西,那么你看很多问题的时候,绝对是不一样的,你就不会陷入到整天学这个语法学那个语法、学这个规则学那个规则,这报错,这不报错、乱起八糟的。总之要知其然,知其所以然
5.5、调用签名(Call Signatures)
我们刚刚学习了、一种函数类型、叫做函数类型表达式,还有另外一种给我们函数指定类型的方法,叫做函数调用签名
函数类型表达式的局限性
// 1.函数类型表达式
type BarType = (num1: number) => number
// 2.bar本身来讲她也是个对象,也可以拥有属性,但是如果你按照函数类型表达式的写法,就没有办法给他指定属性
// 因为函数类型表达式只能表示她是一个函数,没有表达出来她具有其他属性---当然可以用调用签名解决这种问题
const bar: BarType = (num1: number): number => {
return 123
}
bar.name="aaa"//报红
bar.age=18 //报红
调用签名
// 2.函数的调用签名(从对象的角度来看待这个函数, 也可以有其他属性)
interface IBar {
name: string
age: number
// 函数可以调用: 函数调用签名 格式:参数列表:返回值类型
(num1: number): number
}
const bar: IBar = (num1: number): number => {
return 123
}
bar.name = "aaa"
bar.age = 18
bar(123)
一定要注意:调用签名的语法跟函数类型表达式的语法是不同的,调用签名是:函数类型表达式是=>
开发中如何选择
1、如果只是描述函数类型本身(函数可以被调用), 使用函数类型表达式(Function Type Expressions)
2、如果在描述函数作为对象可以被调用, 同时也有其他属性时, 使用函数调用签名(Call Signatures)
5.6、构造签名(Construct Signatures)
JavaScript 函数也可以使用 new 操作符调用,当被调用的时候,TypeScript 会认为这是一个构造函数(constructors),因为 他们会产生一个新对象。
你可以写一个构造签名( Construct Signatures ),方法是在调用签名前面加一个 new 关键词;
interface IPerson{
new (name:string):Person
}
function factory(ctor:IPerson){
return new ctor("why")
}
class Person{
name:string
constructor(name:string){
this.name=name
}
}
let aa=factory(Person) //传如一个类返回的是一个对象
console.log(aa.name);
5.7、函数的可选参数
// y就是一个可选参数
// 可选参数类型是什么? number | undefined 联合类型
function foo(x: number, y?: number) {
if (y !== undefined) { //可选参数必须经过类型缩小才能使用
console.log(y + 10)
}
}
foo(10)
foo(10, 20)
export {}
注意:另外可选类型需要在必传参数的后面:
5.8、默认参数
从ES6开始,JavaScript是支持默认参数的,TypeScript也是支持默认参数的:
// 函数的参数可以有默认值
// 1.有默认值的情况下, 参数的类型注解可以省略
// 2.有默认值的参数, 是可以接收一个undefined的值
function foo(x: number, y = 100) {
console.log(y + 10)
}
foo(10)
foo(10, undefined)
foo(10, 55)
export {}
function foo(x: number, y:number = 100) {
console.log(y + 10)
}
//这个时候的y其实是undefined和number类型的联合
5.9、剩余参数
从ES6开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中。
// 这个时候y的类型其实是 undefined 和 number 类型的联合
function foo(...args: (string | number)[]) {
}
foo(123, 321)
foo("abc", 111, "cba")
5.1、函数重载(了解)
在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?
我们可能会这样来编写,但是其实是错误的:
那么这个代码应该如何去编写呢?
在TypeScript中,我们可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用;
一般是编写两个或者以上的重载签名,再去编写一个通用的函数以及实现;
function add(arg1,arg2){
return arg1+arg2;
}
add(10,20)
add("abc","cba")
add({"name":'李白'},"123") //不会报错?但是不好
//需求:只能将两个数字/两个字符串进行相加
//1、实现两个函数----不合适冗余浪费
function add1(num1: number, num2: number) {
return num1 + num2
}
function add2(str1: string, str2: string) {
return str1 + str2
}
add1(10, 20)
add2("abc", "cba")
//2、错误的做法: 联合类型是不可以(写判断进行类型缩小、也可以完成、但是比较冗余)
function add(arg1: number|string, arg2: number|string) {
return arg1 + arg2
}
//3、TypeScript中函数的重载写法
// 3.1.先编写重载签名
function add(arg1: number, arg2: number): number
function add(arg1: string, arg2: string): string
// 3.2.编写通用的函数实现
function add(arg1: any, arg2: any): any {
return arg1 + arg2
}
add(10, 20)
add("aaa", "bbb")
// 通用函数不能被调用
add({name: "why"}, "aaa")
add("aaa", 111)
export {}
5.2、函数重载练习
比如我们对sum函数进行重构:
在我们调用sum的时候,它会根据我们传入的参数类型来决定执行函数体时,到底执行哪一个函数的重载签名;
function sum(a1:number,a2:number):number
function sum(a1:string,a2:string):string
function sum(a1:any,a2:any):any{
return a1+a2
}
console.log(sum(20,30))
console.log(sum("aaa","bbbb"))
但是注意,有实现体的函数,是不能直接被调用的:
函数重载这种东西在开发中,如果你想编写一些通用的函数的时候还是有点用的,但是在你平常做业务开发的时候,不是封装一个通用的工具,或者你不是在封装一个库、封装一个框架的时候,自己去编写重载的这种函数是比较少的。一般比较少,一般这种东西都是在框架里面、或者一些通用库,一些通用的工具里边可能去编写这种东西,从长远角度去看,也许有一点你也会去写这种通用的库的,来共享一些你自己的比较好用的一些工具,在实际的业务当中确实比较少些,所以说函数的重载了解即可、看到别人写了,一眼就能看出来就好。
5.3、联合类型和函数重载的区别
结论:尽量采用联合类型来实现功能
// 1.普通的实现
// function getLength(arg) {
// return arg.length
// }
// 2.函数的重载
// function getLength(arg: string): number
// function getLength(arg: any[]): number
// function getLength(arg) {
// return arg.length
// }
// 3.联合类型实现(可以使用联合类型实现的情况, 尽量使用联合类型)
// function getLength(arg: string | any[]) {
// return arg.length
// }
// 4.对象类型实现
function getLength(arg: {name?:String,length: number }) {
return arg.length
}
getLength("aaaaa")
getLength(["abc", "cba", "nba"])
getLength({name: "why", length: 100 })
export{}
5.4、可推导的this类型
this是JavaScript中一个比较难以理解和把握的知识点:
当然在目前的Vue3和React开发中你不一定会使用到this:
Vue3的Composition API中很少见到this,React的Hooks开发中也很少见到this了;
但是我们还是简单掌握一些TypeScript中的this,TypeScript是如何处理this呢?我们先来看两个例子:
上面的代码默认情况下是可以正常运行的,也就是TypeScript在编译时,认为我们的this是可以正确去使用的:
这是因为在没有指定this的情况,this默认情况下是any类型的;
1、对象中的函数中的This
// 1.对象中的函数中的this
const obj = {
name: "why",
studying: function() {
// 默认情况下, this是any类型
console.log(this.name.length, "studying")
}
}
obj.studying()
obj.studying.call({})//假如我这种调用方式传入的是一个空对象、那么this.name.length就非常的危险
2、普通的函数中的This
function foo() {
console.log(this)
}
3、生成tsconfig.js文件
tsc --init
这个文件的作用、可以对我们当前目录下面所有ts文件进行配置,那么之后VsCode在加载这个ts文件的时候,就会根据我们这个配置里面的一些配置选项来决定你这里的这些地方的语法到底是正确的语法还是错误的语法?
4、tsconfig的this配置项
翻译为:不能存在有模糊的this,当为true时,this类型必须时明确的
在设置了noImplicitThis为true时, TypeScript会根据上下文推导this,但是在不能正确推导时,就会报错,需要我们明确 的指定this。
如果你整个项目当中你都不打算指定this的类型、那么就可以把noImplictThis=false,如果改为true的话,会让你的代码变的更加严谨、但是会增加你的工作量
5、如何明确指定this的类型
在开启noImplicitThis的情况下,我们必须指定this的类型
如何指定呢?函数的第一个参数类型:
函数的第一个参数我们可以根据该函数之后被调用的情况,用于声明this的类型(名词必须叫this);
在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除;
function foo(this: { name: string }, info: {name: string}) {
console.log(this, info)
}
foo.call({ name: "why" }, { name: "kobe" })
5.5、this的内置工具
Typescript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用。
1、ThisParameterType
用于提取一个函数类型Type的this (opens new window)参数类型;
如果这个函数类型没有this参数返回unknown;
function foo(this: { name: string }, info: {name: string}) {
console.log(this, info)
}
//1.ThisParameterType: 获取FooType类型中this的类型
type FnType=ThisParameterType<typeof foo>
2、OmitThisParameter
用于移除一个函数类型Type的this参数类型,并且返回当前的函数类型
function foo(this: { name: string }, info: {name: string}) {
console.log(this, info)
}
//2.OmitOmitThisParameter: 删除this参数类型, 剩余的函数类型
type PureFooType=OmiThisParameter<typeOf foo>
3、ThisType
这个类型不返回一个转换过的类型,它被用作标记一个上下文的this类型。(官方文档)
事实上官方文档的不管是解释,还是案例都没有说明出来ThisType类型的作用;
我这里用另外一个例子来给大家进行说明:
// 3.ThisType: 用于绑定一个上下文的this
//state接口一个接口
interface IState {
name: string
age: number
}
//接口
interface IStore {
state: IState
eating: () => void
running: () => void
}
//标记上下文中this的类型
const store: IStore & ThisType<IState> = {
state: {
name: "why",
age: 18
},
eating: function(this:IState) {
//这个this经过类型推到是store,取name的话得this.state.name 解决方法this:IState
console.log(this.name)
},
running: function(this:IState) {
console.log(this.name)
}
}
store.eating.call(store.state)
export {}
其实vue当中的状态管理工具中的getters和actions就是这么实现的。