likes
comments
collection
share

Typescript: 函数类型、函数重载及泛型函数

作者站长头像
站长
· 阅读数 61

Typescript 中的函数类型

函数类型包含两部分:参数类型和返回值类型,一个完整的函数类型,这两部分都是需要的。

  • 参数类型:以参数列表的形式写出参数类型,并为每个参数指定一个名字和类型
let myAdd: (baseValue: number, increment: number) => number = 
function(x: number, y: number): number {
  return x + y
}

只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否一样,比如这里的x对应着baseValuebaseValue只是为了增加可读性。

  • 返回值类型:在函数和返回值类型之前使用=>符号,使之清晰明了

返回值类型是函数类型的必要部分,如果函数没有返回任何值,也必须指定返回值类型为 void 而不能留空。

函数的可选参数、默认参数、剩余参数

原则:传递给一个函数的参数个数必须与函数期望的参数个数一致

function buildName(firstName: string, lastName: string) {
    return firstName + ' ' + lastName;
}

let result1 = buildName('Bob')                   // Error, 参数过少
let result2 = buildName('Bob', 'Adams', 'Sr.');  // Error, 参数过多
let result3 = buildName('Bob', 'Adams');         // OK

1. 可选参数

JavaScript 里,每个参数都是可选的,可传可不传,不传参的时候,它的值就是 undefined

TypeScript 里,可以在参数名旁使用 ? 实现可选参数的功能。 比如,我们想让 lastName 是可选的:

function buildName(firstName: string, lastName?: string): string {
  if (lastName)
    return firstName + ' ' + lastName
  else
    return firstName
}

可选参数必须跟在必须参数后面。 如果想让 firstName 是可选的,那么就必须调整它们的位置,把 firstName 放在后面。

2. 默认值

function buildName(firstName: string, lastName = 'Smith'): string {
  return firstName + ' ' + lastName
}

与普通可选参数不同的是,带默认值的参数不需要放在必须参数的后面。

如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined 值来获得默认值。

function buildName(firstName = 'Will', lastName: string): string {
  return firstName + ' ' + lastName
}

let result1 = buildName('Bob')                  // Error, 参数过少
let result2 = buildName('Bob', 'Adams', "Sr.")  // Error, 参数过多
let result3 = buildName('Bob', 'Adams')         // OK, 返回 "Bob Adams"
let result4 = buildName(undefined, 'Adams')     // OK,  返回 "Will Adams"

3. 剩余参数

默认参数和可选参数有个共同点:它们表示某一个参数。有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。在 JavaScript 里,你可以使用 arguments 来访问所有传入的参数。

在 TypeScript 里,你可以把所有参数收集到一个变量里,然后给它定义一个类型:

function buildName(firstName: string, ...restOfName: string[]): string {
  return firstName + ' ' + restOfName.join(' ')
}

let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MacKinzie')

剩余参数会被当做个数不限的可选参数。可以一个都没有,同样也可以有任意个。

编译器创建参数数组,名字是你在省略号...后面给定的名字,然后在函数体内使用这个数组。

Typescript 中函数的this类型

学习如何在 JavaScript 里正确使用 this 就好比一场成年礼。

JavaScript里,this 的值在函数被调用的时候才会指定。 这是个既强大又灵活的特点,但是你需要花点时间弄清楚函数调用的上下文是什么。但众所周知,这不是一件很简单的事,尤其是在返回一个函数或将函数当做参数传递的时候。

下面就来回顾下this的使用。

let deck = {
  suits: ['hearts', 'spades', 'clubs', 'diamonds'],
  cards: Array(52),
  createCardPicker: function() {
    return function() {
      let pickedCard = Math.floor(Math.random() * 52)
      let pickedSuit = Math.floor(pickedCard / 13)

      return {suit: this.suits[pickedSuit], card: pickedCard % 13}
    }
  }
}

let cardPicker = deck.createCardPicker()
let pickedCard = cardPicker()

console.log('card: ' + pickedCard.card + ' of ' + pickedCard.suit)

运行这个程序,会发现它并没有输出而是报错了。 因为 createCardPicker 执行完后返回一个函数,这个函数直接调用时,this 是指向 window 而不是 deck 对象。

为了解决这个问题,可以在函数被返回时就绑好正确的this,这样的话,无论之后怎么使用它,都会引用绑定的deck 对象。

还有一种方式是使用箭头函数,因为箭头函数的this继承于父级函数的this,父级函数的this指向deck

// 第一种方式 var that = this
let deck = {
  ...
  createCardPicker: function() {
    let that = this
    return function() {
      ...
      return {suit: that.suits[pickedSuit], card: pickedCard % 13}
    }
  }
}

// 第二种方式 使用箭头函数,箭头函数能保存函数创建时的 `this` 值,而不是调用时的值。
let deck = {
  ...
  createCardPicker: function() {
    return () => {
      ...
      return {suit: this.suits[pickedSuit], card: pickedCard % 13}
    }
  }
}

在上述的例子中 this 的类型为 any,这在 typescrit 中是不被允许的。那如何让this也有类型提示呢?

修改的方法是,提供一个显式的 this 参数,它只是个假的参数,它出现在参数列表的最前面

让我们往例子里添加一些接口Card 和 Deck,让类型重用能够变得清晰简单些:

interface Card {
  suit: string
  card: number
}

interface Deck {
  suits: string[]
  cards: number[]

  createCardPicker (this: Deck): () => Card
}

let deck: Deck = {
  suits: ['hearts', 'spades', 'clubs', 'diamonds'],
  cards: Array(52),
  // 函数现在显式指定其被调用方必须是 deck 类型
  createCardPicker: function (this: Deck) {
    return () => {
      let pickedCard = Math.floor(Math.random() * 52)
      let pickedSuit = Math.floor(pickedCard / 13)

      return {suit: this.suits[pickedSuit], card: pickedCard % 13}
    }
  }
}

现在 TypeScript 知道 createCardPicker 期望在某个 Deck 对象上调用。也就是说 this 是 Deck 类型的,而非 any

函数重载

函数重载是 TypeScript 的一项非常强大的特性。它最大优点在于它可以提高代码的可读性和健壮性

通过为一个函数定义多个函数类型,我们可以清晰地表达函数所能处理的不同参数类型和参数数量,使得代码更加易于理解和维护。

此外,函数重载也帮助我们避免一常见的错误,例如传递错误的参数类型或参数数量过多。

在什么情况下应该使用函数重载呢?

函数重载通常适用于处理多个有相似逻辑,但类型和参数数量不同的函数,例如,axios 库中的 axios 函数可以接受多不同类型的参数用于向服务器发送请求。

// 参数是多样的
axios('url', { ... })
axios({...})

通过函数重载,我们可以将这些相似但参数不同的函数逻辑合并在一起,使得代码更加简洁和易于维护。

先从一个简单的例子看看重载的使用:

function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
  return x + y;
}

add(1, 2); // 3
add("hello", "world"); // "helloworld"

第一行和第二行是函数签名,声明了函数的输入和输出:

  • add(x: number, y: number): number; 表示接收两个数字,返回一个数字。

  • add(x: string, y: string): string; 表示接收两个字符串,返回一个字符串。

第三行代码块是根据参数类型和返回类型的不同,处理不同输入情况的函数实现。使用参数类型最宽泛的 any 来处理不属于前两个签名的调用情况。

为了让编译器能够选择正确的检查类型,它与 JavaScript 里的处理流程相似,它查找重载列表,尝试使用第一个重载定义,如果匹配的话就使用这个,因此,在定义重载的时候,一定要把最精确的定义放在最前面

讲完了函数,最后来看看泛型是如何函数紧密结合在一起的。

泛型函数

这里只是简单介绍下泛型在函数上的应用,后面会详细介绍泛型。

下面这段代码,你肯定会奇怪result的类型是为什么是any类型? 因为我传了一个字符串类型的参数进去了,函数体直接返回了这个参数,按道理来说result应该是字符串类型啊。

function echo(arg) {
    return arg
}
const result = echo('str')

这是因为类型推断无法进入到函数体里面去推断返回值的类型,所以我们需要有个类型显示的声明返回值的类型,这个类型是由根据传入的参数类型决定的,即如果传入的是一个数字类型,那么返回值就是数字类型。

那这个时候泛型就登场了,看下面这个例子:

function identity<T>(value: T): T {
    return value
}

let output = identity<string>('mystring')
// 还可以省略<string>, ts帮我们做了类型推论,推断T是字符串,推荐使用这种方式,这样代码比较精简。
let output1 = identity('string')

此时,output的类型就字符串类型。

总结

首先介绍了如何定义一个函数的类型,主要包括参数类型和返回值类型。其中,参数可以是可选的,也可以赋一个默认值,对于多个参数可以使用...收敛到一个数组中,然后对数组进行类型定义。

接着讲了函数的重载,利用函数的重载可以大大提高函数的灵活性和可维护性,特别适合运用在参数类型和参数个数比较灵活的场景中。

最后讲解了泛型在函数中的应用,泛型是一个非常重要的概念,后面的文章会详细分析它的使用。

转载自:https://juejin.cn/post/7246660018275696695
评论
请登录