作者选择了 COVID-19 救援基金作为 Write for Donations计划的一部分接受捐款。
介绍
创建和使用函数是任何编程语言的基本方面,而 TypeScript没有什么不同。 TypeScript 完全支持现有的 JavaScript 函数语法,同时也将 类型信息和 函数过载作为新功能添加。
在本教程中,您将开始使用类型信息创建最基本的函数,然后转向更复杂的场景,例如使用 休息参数和功能过载。
前提条件
要遵循本教程,您将需要:
- 联合国 您可以执行 TypeScript 程序的环境与示例一起执行 。 要在您的本地机上设置此功能, 您需要安装如下:
- 既 [Node] (https://nodejs.org/en/about/) (又 [npm] (https://docs.npmjs.com/about-npm (或 [yarn] (https://yarnpkg.com/getting-started )) , 以便运行一个处理 TypeScript相关包的开发环境. 这个教程用Node.js版本14.3.0和npm版本6.14.5进行了测试. 要安装在 macOS 或 Ubuntu 18.04 上,请遵循 [如何在 macOS (https://andsky.com/tech/tutorials/how-to-install-node-js-and-create-a-local-development-environment-on-macos) 上安装节点并创建本地开发环境 或 ** 使用 [如何在 Ubuntu 18.04 (https://andsky.com/tech/tutorials/how-to-install-node-js-on-ubuntu-18-04 上安装节点.js 的PPA** 部分。 如果您正在使用 Linux (WSL) (https://docs.microsoft.com/en-us/windows/wsl/install-win10) 的 Windows 子系统,此功能也会有效 。
- 此外,您需要安装在您的机器上的脚本编译器。 要做到这一点,请参考官方TypeScript网站.
- 如果您不想在本地机器上创建类型脚本环境, 您可以使用官方 [TypeScript Playground] (https://www.typescriptlang.org/play) 来跟踪 。
- 您需要足够的JavaScript知识,特别是ES6+语法,例如解构,休息操作员,以及进出口. 如果您需要更多有关这些主题的信息,请读取我们的[JavaScript series How To Code (https://www.digitalocean.com/community/tutorial_series/how-to-code-in-javascript).
- 此教程会引用文本编辑器中支持TypeScript并显示行内错误的方面. 这对使用TypeScript是不必要的,但确实更多地利用了TypeScript的特性. 为了获得这些好处,您可以使用像[Visual Studio Code (https://code.visualstudio.com/)这样的文本编辑器,该编辑器对出框的TypeScript有完全的支持. 您可以在 [TypeScript Playground] (https://www.typescriptlang.org/play ) 中尝试这些好处. (英语)
本教程中的所有示例都是使用TypeScript版本 4.2.2创建的。
创建编写函数
在本节中,您将在 TypeScript 中创建函数,然后向它们添加类型信息。
在JavaScript中,函数可以以多种方式声明,其中最流行的是使用函数
关键字,如下所示:
1function sum(a, b) {
2 return a + b;
3}
在本示例中,‘总’是函数的名称,‘(a, b)’是参数,‘{返回 a + b;}’是函数体。
在 TypeScript 中创建函数的语法相同,除了一个主要的补充:您可以让编译器知道每个参数或参数应该有哪些类型。
1function functionName(param1: Param1Type, param2: Param2Type): ReturnType {
2 // ... body of the function
3}
使用此语法,您可以将类型添加到前面显示的总
函数的参数中:
1function sum(a: number, b: number) {
2 return a + b;
3}
这确保a
和b
是数字
值。
您也可以添加返回值的类型:
1function sum(a: number, b: number): number {
2 return a + b;
3}
现在,TypeScript 将预期总
函数将返回一个数字值. 如果您用某些参数调用函数并将结果值存储在名为结果
的变量中:
1const result = sum(1, 2);
如果您正在使用TypeScript播放场或正在使用完全支持TypeScript的文本编辑器,在结果
上浮动的方针将显示const结果:数字
,表明TypeScript从函数声明中暗示了它的类型。
如果您将函数称为具有不同于函数预期类型的值,则TypeScript 编译器(‘tsc’)会为您发出错误 `2345’。
1sum('shark', 'whale');
这将给出如下:
1[secondary_label Output]
2Argument of type 'string' is not assignable to parameter of type 'number'. (2345)
您可以在函数中使用任何类型,而不仅仅是 基本类型。
1type User = {
2 firstName: string;
3 lastName: string;
4};
您可以创建一个函数返回用户的完整名称,如下:
1function getUserFullName(user: User): string {
2 return `${user.firstName} ${user.lastName}`;
3}
大多数情况下,TypeScript 是足够聪明的来推断函数的返回类型,所以在这种情况下,您可以从函数声明中放下返回类型:
1function getUserFullName(user: User) {
2 return `${user.firstName} ${user.lastName}`;
3}
请注意,您删除了 : string
部分,该部分是您的函数的返回类型. 当您返回函数体内的一个字符串时,TypeScript 正确地假定您的函数具有一个字符串返回类型。
要现在调用函数,您必须通过与用户
类型相同的形状的对象:
1type User = {
2 firstName: string;
3 lastName: string;
4};
5
6function getUserFullName(user: User) {
7 return `${user.firstName} ${user.lastName}`;
8}
9
10const user: User = {
11 firstName: "Jon",
12 lastName: "Doe"
13};
14
15const userFullName = getUserFullName(user);
此代码将成功通过TypeScript类型检查器. 如果您在编辑器中浮过userFullName
常数,编辑器将其类型识别为字符串
。
TypeScript 中的可选函数参数
在创建函数时,并不总是需要有所有参数,在本节中,您将学习如何在TypeScript中将函数参数标记为可选。
要将函数参数转换为可选参数,请在参数名称后直接添加 ?
修改器. 由于函数参数 param1
与类型 T
,您可以通过添加 ?
来使 param1
成为可选参数,如下所示:
1param1?: T
例如,您可以将可选的前缀
参数添加到您的getUserFullName
函数中,该函数是一个可选的字符串,可以作为用户的全名前缀添加:
1type User = {
2 firstName: string;
3 lastName: string;
4};
5
6function getUserFullName(user: User, prefix?: string) {
7 return `${prefix ?? ''}${user.firstName} ${user.lastName}`;
8}
在这个代码块的第一个突出部分中,您正在为您的函数添加一个可选的前缀
参数,而在第二个突出部分中,您正在用该函数前缀用户的完整名称。
现在,您可以用或没有前缀参数调用您的函数,如下所示:
1type User = {
2 firstName: string;
3 lastName: string;
4};
5
6function getUserFullName(user: User, prefix?: string) {
7 return `${prefix ?? ''} ${user.firstName} ${user.lastName}`;
8}
9
10const user: User = {
11 firstName: "Jon",
12 lastName: "Doe"
13};
14
15const userFullName = getUserFullName(user);
16const mrUserFullName = getUserFullName(user, 'Mr. ');
在这种情况下,userFullName
的值将是Jon Doe
,而mrUserFullName
的值将是Jon Doe先生
。
请注意,您不能在所需参数之前添加一个可选参数;它必须列出序列中的最后一个参数,就像在 (用户:用户,前缀?:字符串)
中所做的那样。
1[secondary_label Output]
2A required parameter cannot follow an optional parameter. (1016)
射箭函数表达式
到目前为止,本教程已经展示了如何在TypeScript中键入函数
关键字定义的正常函数,但在JavaScript中,您可以以多种方式定义函数,例如使用 箭头函数。
将类型添加到箭头函数的语法几乎与将类型添加到正常函数相同。
1const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
如果你想明确你的函数的返回类型,你会添加它之后的 ()
,如下块中的突出代码所示:
1const getUserFullName = (user: User, prefix?: string): string => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
现在您可以像以前那样使用您的功能:
1type User = {
2 firstName: string;
3 lastName: string;
4};
5
6const getUserFullName = (user: User, prefix?: string) => `${prefix ?? ''}${user.firstName} ${user.lastName}`;
7
8const user: User = {
9 firstName: "Jon",
10 lastName: "Doe"
11};
12
13const userFullName = getUserFullName(user);
这将通过TypeScript打字检查器没有错误。
<$>[注] 注: 请记住,所有适用于JavaScript中的函数都适用于TypeScript中的函数。
功能类型
在之前的部分中,您已将类型添加到参数中,并在TypeScript中返回函数的值。在本节中,您将学习如何创建函数类型,即代表特定函数签名的类型。
创建函数类型的语法类似于创建箭头函数,有两个不同:
- 您删除函数体.
- 您使函数声明返回
返回
类型本身。
以下是如何创建一个匹配您正在使用的getUserFullName
函数的类型:
1type User = {
2 firstName: string;
3 lastName: string;
4};
5
6type PrintUserNameFunction = (user: User, prefix?: string) => string;
在本示例中,您使用类型
关键字来声明一个新类型,然后在窗口中提供两种参数的类型和箭头后返回值的类型。
对于一个更具体的例子,想象一下,您正在创建一个名为onEvent
的事件收听函数(https://andsky.com/tech/tutorials/understanding-events-in-javascript),该函数作为第一个参数接收事件名称,而作为第二个参数接收事件回调。
1type EventContext = {
2 value: string;
3};
然后,您可以这样写onEvent
函数:
1type EventContext = {
2 value: string;
3};
4
5function onEvent(eventName: string, eventCallback: (target: EventContext) => void) {
6 // ... implementation
7}
请注意,eventCallback
参数的类型是函数类型:
1eventCallback: (target: EventTarget) => void
这意味着您的onEvent
函数预计在eventCallback
参数中将另一个函数传输。 该函数应该接受一个EventTarget
类型的单个参数。 该函数的返回类型被您的onEvent
函数忽略,因此您正在使用空
(https://andsky.com/tech/tutorials/how-to-use-basic-types-in-typescript)作为类型。
使用编写的非同步函数
在使用 JavaScript 工作时,相对常见的是 非同步函数。TypeScript 有一个特定的方法来处理此问题。
创建非同步函数的语法与用于JavaScript的语法相同,加上允许类型:
1async function asyncFunction(param1: number) {
2 // ... function implementation ...
3}
将类型添加到正常函数和添加类型添加到非同步函数之间存在一个主要的区别:在非同步函数中,返回类型必须始终是Promise<T>
通用。
假设你有一个用户
类型:
1type User = {
2 id: number;
3 firstName: string;
4};
另外,假设您在数据存储中有几个用户对象,这些数据可以存储在任何地方,例如在文件、数据库或 API 请求后面。
1type User = {
2 id: number;
3 firstName: string;
4};
5
6const users: User[] = [
7 { id: 1, firstName: "Jane" },
8 { id: 2, firstName: "Jon" }
9];
如果您想要创建一个按 ID 以无同步的方式检索用户的类型安全函数,则可以这样做:
1async function getUserById(userId: number): Promise<User | null> {
2 const foundUser = users.find(user => user.id === userId);
3
4 if (!foundUser) {
5 return null;
6 }
7
8 return foundUser;
9}
在此函数中,您首先将函数宣布为非同步:
1async function getUserById(userId: number): Promise<User | null> {
然后,您指定它接受用户 ID 作为第一个参数,该参数必须是号码
:
1async function getUserById(userId: number): Promise<User | null> {
返回类型 getUserById
是一个 Promise,它解决为 either User
或 null
. 您正在使用 union type User Átha null
作为类型参数的 Promise
通用。
User null
是Promise<T>
中的T
:
1async function getUserById(userId: number): Promise<User | null> {
使用等待
调用函数,并将结果存储在名为用户
的变量中:
1type User = {
2 id: number;
3 firstName: string;
4};
5
6const users: User[] = [
7 { id: 1, firstName: "Jane" },
8 { id: 2, firstName: "Jon" }
9];
10
11async function getUserById(userId: number): Promise<User | null> {
12 const foundUser = users.find(user => user.id === userId);
13
14 if (!foundUser) {
15 return null;
16 }
17
18 return foundUser;
19}
20
21async function runProgram() {
22 const user = await getUserById(1);
23}
<$>[注]
**注:**您正在使用一个名为 RunProgram
的包装函数,因为您不能在文件的顶层使用等待
。
1[secondary_label Output]
2'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module. (1375)
美元
如果您在编辑器中或TypeScript Playground中翻过用户
,您会发现用户
的类型为用户
null,这正是getUserById
函数返回的承诺的类型。
如果删除等待
并直接调用函数,则返回承诺
对象:
1async function runProgram() {
2 const userPromise = getUserById(1);
3}
如果你跳过userPromise
,你会发现它有类型的Promise<User███ null>
。
大多数时候,TypeScript 可以推断你的 async 函数的返回类型,就像非 async 函数一样。
1async function getUserById(userId: number) {
2 const foundUser = users.find(user => user.id === userId);
3
4 if (!foundUser) {
5 return null;
6 }
7
8 return foundUser;
9}
添加类型来休息参数
Rest parameters是JavaScript中的一个功能,允许函数作为单个数组接收许多参数。
使用休息参数以安全的方式是完全可能的,使用剩余参数,然后是结果数组的类型. 举个例子,下面的代码,你有一个称为总
的函数,该函数接受一个可变数量的数字,并返回其总数:
1function sum(...args: number[]) {
2 return args.reduce((accumulator, currentValue) => {
3 return accumulator + currentValue;
4 }, 0);
5}
此函数使用 .reduce
Array 方法来重复数组,并将元素添加在一起. 注意在这里突出的剩余参数 args
。
呼叫您的函数正常工作:
1function sum(...args: number[]) {
2 return args.reduce((accumulator, currentValue) => {
3 return accumulator + currentValue;
4 }, 0);
5}
6
7const sumResult = sum(2, 4, 6, 8);
如果您使用数字以外的任何东西来调用函数,例如:
1const sumResult = sum(2, "b", 6, 8);
TypeScript 编译器会发出错误 2345
:
1[secondary_label Output]
2Argument of type 'string' is not assignable to parameter of type 'number'. (2345)
使用过载函数
程序员有时需要一个函数来接受不同的参数,取决于函数的命名方式。在JavaScript中,这通常是通过有一个参数来完成的,该参数可以假定不同类型的值,如字符串或数字。
使用TypeScript,您可以创建函数过载,明确描述它们所解决的不同案例,从而提高开发人员的体验,每一个实现过载函数的文件单独。
假设你有一个用户
类型:
1type User = {
2 id: number;
3 email: string;
4 fullName: string;
5 age: number;
6};
您要创建一个函数,可以使用下列任何信息搜索用户:
id
email
age
andfullName
您可以创建这样的函数:
1function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
2 // ... code
3}
此函数使用``运算符来组成idOrEmailOrAge
和返回值的类型联盟。
接下来,为您想要使用函数的每个方式添加函数过载,如下所示的突出代码所示:
1type User = {
2 id: number;
3 email: string;
4 fullName: string;
5 age: number;
6};
7
8function getUser(id: number): User | undefined;
9function getUser(email: string): User | undefined;
10function getUser(age: number, fullName: string): User | undefined;
11
12function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
13 // ... code
14}
这个函数有三个过载,一个为每种方式获取用户。当创建函数过载时,您在函数实现本身之前添加函数过载。
接下来,您将实现函数本身,该函数应该具有与所有函数过载兼容的参数列表. 在上一个示例中,您的第一个参数可以是数字或字符串,因为它可以是id
,email
或年龄
:
1function getUser(id: number): User | undefined;
2function getUser(email: string): User | undefined;
3function getUser(age: number, fullName: string): User | undefined;
4
5function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
6 // ... code
7}
因此,您在函数实现中将 idOrEmailorAge
参数的类型设置为 number Átha string
. 这样,它可以兼容您的 `getUser' 函数的所有过载。
您还在为您的函数添加一个可选的参数,因为当用户通过一个fullName
时:
1function getUser(id: number): User | undefined;
2function getUser(email: string): User | undefined;
3function getUser(age: number, fullName: string): User | undefined;
4
5function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
6 // ... code
7}
实现您的功能可能如下,您正在使用一个用户
数组作为用户的数据存储:
1type User = {
2 id: number;
3 email: string;
4 fullName: string;
5 age: number;
6};
7
8const users: User[] = [
9 { id: 1, email: "[email protected]", fullName: "Jane Doe" , age: 35 },
10 { id: 2, email: "[email protected]", fullName: "Jon Doe", age: 35 }
11];
12
13function getUser(id: number): User | undefined;
14function getUser(email: string): User | undefined;
15function getUser(age: number, fullName: string): User | undefined;
16
17function getUser(idOrEmailOrAge: number | string, fullName?: string): User | undefined {
18 if (typeof idOrEmailOrAge === "string") {
19 return users.find(user => user.email === idOrEmailOrAge);
20 }
21
22 if (typeof fullName === "string") {
23 return users.find(user => user.age === idOrEmailOrAge && user.fullName === fullName);
24 } else {
25 return users.find(user => user.id === idOrEmailOrAge);
26 }
27}
28
29const userById = getUser(1);
30const userByEmail = getUser("[email protected]");
31const userByAgeAndFullName = getUser(35, "Jon Doe");
在这个代码中,如果idOrEmailOrAge
是一个字符串,那么你可以用电子邮件
密钥搜索用户。下面的条件假定idOrEmailOrAge
是一个数字,所以它是id
或年龄
,取决于fullName
是否定义。
函数过载的一个有趣的方面是,在大多数编辑器中,包括VS Code和TypeScript Playground,一旦您输入函数名称并打开第一个关节来调用函数,就会出现一个出现窗口,包含所有可用的过载,如下图所示:
如果您在每个函数过载中添加了评论,则评论也将作为文档来源显示在 pop-up 中,例如,将以下突出评论添加到示例过载中:
1...
2/**
3 * Get a user by their ID.
4 */
5function getUser(id: number): User | undefined;
6/**
7 * Get a user by their email.
8 */
9function getUser(email: string): User | undefined;
10/**
11 * Get a user by their age and full name.
12 */
13function getUser(age: number, fullName: string): User | undefined;
14...
现在,当您绕过这些函数时,评论将出现在每个过载时,如下动画所示:
用户定义类型警卫
本教程将审查的TypeScript中的最后一个函数功能是 user-defined type guards,这些函数是允许TypeScript更好地推断某些值的类型。
将条件值添加到数组时,一个常见的任务是检查某些条件,然后只添加值,如果条件是真的. 如果值不是真的,则代码将添加一个 false
Boolean 到数组。
当与值进行调用时,布尔构造器会返回真
或假
,取决于该值是否为真
或假
。
例如,假设您有一个字符串的数组,并且您只希望将字符串生产
包含在该数组中,如果另一个字符串是真实的:
1const isProduction = false
2
3const valuesArray = ['some-string', isProduction && 'production']
4
5function processArray(array: string[]) {
6 // do something with array
7}
8
9processArray(valuesArray.filter(Boolean))
虽然这在运行时是完全有效的代码,但TypeScript 编译器在编译过程中会给出错误 2345
:
1[secondary_label Output]
2Argument of type '(string | boolean)[]' is not assignable to parameter of type 'string[]'.
3 Type 'string | boolean' is not assignable to type 'string'.
4 Type 'boolean' is not assignable to type 'string'. (2345)
此错误表明,在编译时,传递给processArray
的值被解释为falseuiñ string
值的数组,这不是processArray
预期的。
这是一个TypeScript不够聪明的案例,可以推断使用 .filter(Boolean)
将所有 falsy
值从您的数组中删除,但是,有一个方法可以给 TypeScript 提供这个提示:使用用户定义的类型保护程序。
创建一个用户定义的类型守护函数,名为isString
:
1function isString(value: any): value is string {
2 return typeof value === "string"
3}
注意 isString
函数的返回类型. 创建用户定义的类型保护程序的方法是使用下面的语法作为函数的返回类型:
1parameterName is Type
在parameterName
是您正在测试的参数的名称,而Type
是该参数的值如果该函数返回true
的预期类型。
在这种情况下,如果isString
返回true
,你会说值
是字符串
,你还会将值
参数的类型设置为任何
,因此它可以与任何
值类型工作。
现在,更改您的.filter
调用以使用您的新函数,而不是通过Boolean
构建器:
1const isProduction = false
2
3const valuesArray = ['some-string', isProduction && 'production']
4
5function processArray(array: string[]) {
6 // do something with array
7}
8
9function isString(value: any): value is string {
10 return typeof value === "string"
11}
12
13processArray(valuesArray.filter(isString))
现在,TypeScript 编译器正确地推断到processArray
传递的数组仅包含字符串,并且您的代码编译正确。
结论
函数是TypeScript中的应用程序的构建块,在本教程中,您了解如何在TypeScript中构建类型安全函数,以及如何利用函数过载来更好地记录单个函数的所有变体。
有关TypeScript的更多教程,请参阅我们的 How To Code in TypeScript 系列页面。