Typescript入门手册函数

Posted 余光、

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Typescript入门手册函数相关的知识,希望对你有一定的参考价值。

🚀【TypeScript入门手册】记录了出场率较高的Ts概念,旨在帮助大家了解并熟悉Ts
🎉 本系列会持续更新并更正,重点关照大家感兴趣的点,欢迎同学留言交流,在进阶之路上,共勉!
star本项目给作者一点鼓励吧

一、函数声明

函数是一等公民,在javascript中,有两种常见的定义函数的方式——函数声明和函数表达式,让我们来学习一下如何书写描述函数的类型(types)。

// 函数声明(Function Declaration)
function sum(x, y) 
  return x + y;


// 函数表达式(Function Expression)
const mySum = function(x, y) 
  return x + y;
;

最简单描述一个函数的方式是使用**函数类型表达式(function type expression)。**它的写法有点类似于箭头函数:

function greeter(fn: (a: string) => void) 
  fn("Hello, World");

 
function printToConsole(s: string) 
  console.log(s);

 
greeter(printToConsole);

语法(a: string) => void表示一个函数有一个名为a,类型是字符串的参数,这个函数并没有返回任何值。

如果一个函数参数的类型并没有明确给出,它会被隐式设置为 any。

注意函数参数的名字是必须的,这种函数类型描述(string) => void,表示的其实是一个函数有一个类型是 any,名为 string 的参数。

当然了,我们也可以使用类型别名(type alias)定义一个函数类型:

type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) 
    // ...

1.1 函数声明

一个函数有输入和输出,要在 TypeScript 中对其进行约束,两者都要考虑到,其中函数声明的类型定义较简单:

function sum(x: number, y: number): number 
  return x + y;

输入多余的(或者少于要求的)参数,是不被允许的:

function sum(x: number, y: number): number 
  return x + y;


sum(1);
// 应有 2 个参数,但获得 1 个。ts(2554)
// index.ts(1, 25): 未提供 "y" 的自变量。

sum(1, 2, 3); // 应有 2 个参数,但获得 3 个

1.2 函数表达式

最简单的描述函数的方式就是函数类型表达式,应该是怎么样的呢?

let mySum1 = function(x: number, y: number): number 
  return x + y;
;

这段代码不会报错,也是正确的,而还有一种函数表达式的定义是这样的:

let mySum2: (x: number, y: number) => number = function(
  x: number,
  y: number
): number 
  return x + y;
;

注意:此箭头,并不是 ES6 中我们熟知的箭头函数哦~

两者的区别是 mySum1 的类型是推导出来的,而 mySum2 是直接定义的,但两者其实一样,不需要深究。

二、函数签名

在 JavaScript 中,函数除了可以被调用,自己也是可以有属性值的。然而上一节讲到的函数类型表达式并不能支持声明属性,如果我们想描述一个带有属性的函数,我们可以在一个对象类型中写一个调用签名(call signature)。

type DescribableFunction = 
    description: string;
    (someArg: number): boolean; // 参数列表和返回值类型
;
function doSomething(fn: DescribableFunction) 
    console.log(fn.description + " returned " + fn(6));


// 测试func
const func = function(someArg: number): boolean 
    return someArg > 5;
;
func.description = "携带的描述";

doSomething(func);

三、泛型函数

  1. 指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。记住
  2. 所谓泛型就是用一个相同类型来关联两个或者更多的值。

我们经常需要写这种函数,即函数的输出类型依赖函数的输入类型,或者两个输入的类型以某种形式相互关联。让我们考虑这样一个函数,它返回数组的第一个元素:

function firstElement(arr: any[]) 
    return arr[0];

注意此时函数返回值的类型是 any,如果能返回第一个元素的具体类型就更好了。

此时我们需要在函数签名里声明一个类型参数 (type parameter):

function firstElement<T>(arr: T[]): T 
    return arr[0];


const str = firstElement(['str']);
// const str: string
const bool = firstElement([true]);
// const bool: boolean
const num = firstElement([1]);
// const num: number

通过给函数添加一个类型参数 T,并且在两个地方使用它,我们就在函数的输入(即数组)和函数的输出(即返回值)之间创建了一个关联。现在当我们调用它,一个更具体的类型就会被判断出来;

3.1 推断(Inference)

注意在上面的例子中,我们没有明确指定T的类型,类型是被 TypeScript 自动推断出来的。

我们也可以使用多个类型参数,举个例子:

const res = map([1, 2, 3, 4], (val) => val.split(","));
// (parameter) val: number
// 类型“number”上不存在属性“split”。

注意在这个例子中,TypeScript 既可以推断出 Input 的类型是number ,此时自然不能使用split方法。

3.2 约束(Constraints)

有的时候,我们想关联两个值,但只能操作值的一些固定字段,这种情况,我们可以使用**约束(constraint)**对类型参数进行限制。

让我们写一个函数,函数返回两个值中更长的那个。为此,我们需要保证传入的值有一个number类型的length属性。我们使用extends语法来约束函数参数:

function longest<Type extends  length: number >(a: Type, b: Type) 
    if (a.length >= b.length) 
        return a;
     else 
        return b;
    


const res1 = longest([1, 2, 3], 1);
// ❌ 类型“number”的参数不能赋给类型“ length: number; ”的参数。
const res2 = longest([1, "2", 3], [1]);
// const res2: (string | number)[]
const res3 = longest('yuguang', '余光');
// const res3: "yuguang" | "余光"

正是因为我们对Type做了 length: number 限制,我们才可以被允许获取 a b参数的 .length 属性。没有这个类型约束,我们甚至不能获取这些属性,因为这些值也许是其他类型,并没有 length 属性。

基于传入的参数,longerArray和 longerString 中的类型都被推断出来了。

3.3 泛型约束实战(Working with Constrained Values)

这是一个使用泛型约束常出现的错误:

function minimumLength<Type extends  length: number >(
    obj: Type,
    minimum: number
): Type 
    if (obj.length >= minimum) 
        return obj;
     else 
        return  length: minimum ;
        // 不能将类型“ length: number; ”分配给类型“Type”。
        //  " length: number; " 可赋给 "Type" 类型的约束
        // 但可以使用约束 " length: number; " 的其他子类型实例化 "Type"。
    

这里很绕,我也理解了很久,欢迎大家讨论交流哦~

其中的问题就在于函数理应返回与传入参数相同类型的对象,而不仅仅是符合约束的对象。我们可以写出这样一段反例:

3.4 声明类型参数 (Specifying Type Arguments)

TypeScript 通常能自动推断泛型调用中传入的类型参数,但也并不能总是推断出。举个例子,有这样一个合并两个数组的函数:

function combine<Type>(arr1: Type[], arr2: Type[]): Type[] 
    return arr1.concat(arr2);

// 如果你像下面这样调用函数就会出现错误:

const arr1 = combine([1, 2, 3], ["hello"]);
// 不能将类型“string”分配给类型“number”

// 而如果你执意要这样做,你可以手动指定 Type:
const arr2 = combine<string | number>([1, 2, 3], ["hello"]);

四、写一个好的泛型函数的一些建议

尽管写泛型函数很有意思,但也容易翻车。如果你使用了太多的类型参数,或者使用了一些并不需要的约束,都可能会导致不正确的类型推断。

4.1 类型参数下移(Push Type Parameters Down)

下面这两个函数的写法很相似:

function firstElement1<Type>(arr: Type[]) 
    return arr[0];


function firstElement2<Type extends any[]>(arr: Type) 
    return arr[0];


// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);

分析:

  • 第一个函数可以推断出返回的类型是 number,但第二个函数推断的返回类型却是 any,因为 TypeScript 不得不用约束的类型来推断 arr[0] 表达式,而不是等到函数调用的时候再去推断这个元素。

4.2 使用更少的类型参数 (Use Fewer Type Parameters)

这是另一对看起来很相似的函数:

// good
function filter1<Type>(arr: Type[], func: (arg: Type) => boolean): Type[] 
  return arr.filter(func);

// bad
function filter2<Type, Func extends (arg: Type) => boolean>(
  arr: Type[],
  func: Func
): Type[] 
  return arr.filter(func);

我们创建了一个并没有关联两个值的类型参数 Func,这是一个危险信号,因为它意味着调用者不得不毫无理由的手动指定一个额外的类型参数。Func 什么也没做,却导致函数更难阅读和推断。

4.3 类型参数应该出现两次 (Type Parameters Should Appear Twice)

有的时候我们会忘记一个函数其实并不需要泛型

// bad
function greet<Str extends string>(s: Str) 
    console.log("Hello, " + s);

// good
function greet(s: string) 
    console.log("Hello, " + s);

记住:类型参数是用来关联多个值之间的类型。如果一个类型参数只在函数签名里出现了一次,那它就没有跟任何东西产生关联。如果一个类型参数仅仅出现在一个地方,强烈建议你重新考虑是否真的需要它。

五、函数参数

  1. 可选参数
  2. 参数默认值
  3. 剩余参数
  4. 参数解构

5.1 可选参数

如果你已经对函数的参数类型声明,那么输入多余的(或者少于要求的)参数,是不允许的。那么如何定义可选的参数呢?

与接口中的可选属性类似,我们用?表示可选的参数:

function hello(name1: string, name2: string): void 
    console.log(`hello! $name1 $name2 ? "and" + " " + name2 : ""`);


hello("余光");
// 应有 2 个参数,但获得 1 个
// 未提供 "name2" 的自变量。

function hello1(name1: string, name2?: string): void 
    console.log(`hello! $name1 $name2 ? "and" + " " + name2 : ""`);


hello1("余光");

需要注意的是,可选参数必须接在必需参数后面。换句话说,可选参数后面不允许再出现必需参数了:

5.2 参数默认值

在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数:

function hello(name1: string = "余光", name2: string = "yuguang"): void 
    console.log(`hello! $name1 $name2 ? "and" + " " + name2 : ""`);


hello(); // hello! 余光 and yuguang
hello("小明"); // hello! 小明 and yuguang

是不是又拉回到了我们熟悉的领域?

5.3 剩余参数

ES6 中,可以使用 …rest 的方式获取函数中的剩余参数(rest 参数):

function func(a, ...arg) 
  console.log(a);
  console.log(arg);


func(1, 2, 3, 4); // 1, [2, 3, 4]

**注意:**rest参数只能是最后一个参数

5.4 参数解构

你可以使用参数解构方便的将作为参数提供的对象解构为函数体内一个或者多个局部变量,在 JavaScript 中,是这样的:

type Man =  name: string; age: number ;
function sum( name, age : Man) 
    console.log(name + age);

sum( a: 10, b: 3, c: 9 );
// 类型“ a: number; b: number; c: number; ”的参数不能赋给类型“Man”的参数。
// 对象文字可以只指定已知属性,并且“a”不在类型“Man”中。ts

在解构语法后,要写上对象的类型注解。

六、其他类型

这里介绍一些也会经常出现的类型。像其他的类型一样,你也可以在任何地方使用它们,但它们经常与函数搭配使用。

6.1 void

void 表示一个函数并不会返回任何值,当函数并没有任何返回值,或者返回不了明确的值的时候,就应该用这种类型。

function noop() 
    return;

在 JavaScript 中,一个函数并不会返回任何值,会隐式返回 undefined,但是 void 和 undefined 在 TypeScript 中并不一样。

6.2 object

这个特殊的类型 object 可以表示任何不是原始类型(primitive)的值 (string、number、bigint、boolean、symbol、null、undefined)。object 不同于空对象类型 ,也不同于全局类型 Object。很有可能你也用不到 Object。

object不同于Object,总是使用object!

6.3 unknown

unknown类型可以表示任何值。有点类似于 any,但是更安全,因为对 unknown 类型的值做任何事情都是不合法的:

function f1(a: any) 
    a.b(); // OK

function f2(a: unknown) 
    a.b();
    // 类型“unknown”上不存在属性“b”

有的时候用来描述函数类型,还是蛮有用的。你可以描述一个函数可以接受传入任何值,但是在函数体内又不用到 any 类型的值。

你可以描述一个函数返回一个不知道什么类型的值,比如:

function safeParse(s: string): unknown 
    return JSON.parse(s);

6.4 never

一些函数从来不返回值:

function fail(msg: string): never 
  throw new Error(msg);

never 类型表示一个值不会再被观察到 (observed)。

作为一个返回类型时,它表示这个函数会丢一个异常,或者会结束程序的执行。

当 TypeScript 确定在联合类型中已经没有可能是其中的类型的时候,never类型也会出现:

function fn(x: string | number) 
    if (typeof x === "string") 
        // do something
     else if (typeof x === "number") 
        // do something else
     else 
        x; // has type 'never'!
    

写在最后

本篇文章是《Typescript基础入门》第三篇,到这里就结束了,主要带大家了解一下函数在Ts中的表现,其实到这里有关一门语言中基础部分——类型相关的知识已经聊得差不多了,一起加油~

参考:

关于我:

  • 花名:余光
  • 邮箱:webbj97@163.com
  • csdn:传送门

其他沉淀:

以上是关于Typescript入门手册函数的主要内容,如果未能解决你的问题,请参考以下文章

Typescript入门手册函数

Typescript入门手册函数

Typescript入门手册函数

Typescript入门手册类型收窄(Narrowing)

Typescript入门手册类型收窄(Narrowing)

Typescript入门手册类型收窄(Narrowing)