1.1 开始使用 Typescript

在安装 Typescript 之前,需要先安装 npm,然后可以使用命令行来安装 Typescript,如下所示:

npm install typescript@next

npm install typescript@next命令中,@next表示安装 TypeScript 的下一个版本,也就是最新的开发版本或预发布版本。

通常情况下,npm install typescript会安装最新的稳定版本的 TypeScript。但是,如果我们想尝试最新的功能或修复了一些 bug 的预发布版本,可以使用@next标记来安装这个版本。

预发布版本通常是在正式发布之前进行测试和反馈的版本。它们可能包含一些新的功能、改进或修复了一些问题,但也可能存在一些不稳定性或兼容性问题。因此,使用@next标记安装预发布版本时,需要注意这些潜在的问题,并确保我们的代码和依赖项与该版本兼容。

现在局部安装的 Typescript 将是最新版本,而且各种IDE也支持它。例如,我们可以使用 VS Code 通过创建 .vscode/setting.json 来使用这个版本,如下所示:

{
	"typescript.tsdk":"./node_modules/typescript/lib"
}

.vscode/settings.json 文件中设置 "typescript.tsdk":"./node_modules/typescript/lib" 的作用是告诉 Visual Studio Code(VS Code) 使用指定的 TypeScript SDK(软件开发工具包) 路径。

TypeScript SDK 是一组用于开发和构建 TypeScript 项目的工具和库。当你在 VS Code 中使用 TypeScript 时,它需要知道 TypeScript SDK 的位置,以便提供语法检查、代码补全、类型检查等功能。

通过在 .vscode/settings.json 文件中设置 "typescript.tsdk" 属性,你可以指定 TypeScript SDK 的路径。在这个例子中,"./node_modules/typescript/lib" 表示 TypeScript SDK 的路径是当前项目node_modules 目录下的 typescript/lib 文件夹。

这样设置后,VS Code 将使用指定路径下的 TypeScript SDK 来提供相关的功能和工具。

需要注意的是,这个设置是针对特定项目的,因此它会覆盖全局的 TypeScript SDK 设置。这样可以确保每个项目都使用其自己的 TypeScript SDK 版本,而不会受到全局设置的影响。

1.2 选择TypeScript的理由

1.2.1 类型是出色的文档形式之一,函数签名是一个定理,函数体是具体的实现。

  • 类型是出色的文档形式之一:

    类型信息可以帮助我们了解变量、函数参数和返回值的预期数据结构。在阅读和理解代码时,类型信息可以作为一种隐式文档,使我们更容易地推断代码的功能和行为。此外,类型信息还可以在编译时提供类型检查,帮助我们捕获潜在的错误。

    例如,考虑以下函数:

    function add(a: number, b: number): number {
    	 return a + b;
    }
    

    通过查看类型信息,我们可以快速地了解这个函数的功能:它接受两个数字参数 ab,并返回它们的和。这种类型信息可以作为一种简洁、易于理解的文档形式。

  • 函数签名是一个定理:

    函数签名 定义了函数的 输入参数类型返回值类型 以及 可能抛出的异常 。它描述了函数的约束和预期行为,就像数学定理一样。函数签名可以帮助我们确保函数的实现和调用遵循预期的类型约束,从而提高代码的可读性和可维护性。

    例如,考虑以下函数签名:

    type MyFunctionType = (param1: string, param2: number) => boolean;
    

    这个函数签名定义了一个定理:存在一个函数,它接受一个字符串参数 param1 和一个数字参数 param2,并返回一个 布尔值。这个定理描述了函数的约束和预期行为。

  • 函数是具体的实现:

    函数体是函数签名(定理)的具体实现。它描述了如何根据输入参数计算返回值。函数体需要遵循函数签名的约束,以确保代码的正确性和一致性。

    例如,考虑以下函数实现:

    const myFunction: MyFunctionType = (param1, param2) => {
    	 return param1.length > param2;
    };
    

    这个函数体实现了之前定义的函数签名(定理)。它根据输入参数 param1param2 计算了一个 布尔值 ,即 param1 的长度是否大于 param2

总之,类型、函数签名和函数体在编程中起着重要作用。类型信息可以作为一种出色的文档形式,函数签名定义了函数的约束和预期行为(定理),而函数体提供了具体的实现。这些概念有助于提高代码的可读性、可维护性和健壮性。

1.2.2 鸭子类型是一流的语言结构

鸭子类型(Duck Typing) 是一种动态类型语言的编程概念,它关注对象的行为,而不是对象的实际类型。换句话说,如果一个对象像鸭子一样走路、叫声,那么我们就可以把它当作鸭子来对待,而不关心它是否真的是鸭子。在鸭子类型的语言中,函数和方法的参数不需要声明具体的类型,而是通过对象的属性和方法来判断它是否符合预期的行为。

鸭子类型的优势在于它提供了很高的灵活性和可扩展性。由于鸭子类型关注对象的行为,而不是类型本身,这使得我们可以编写更通用、更易于重用的代码。这也是为什么鸭子类型被认为是一流的语言结构。

以下是一个简单的例子,说明了鸭子类型的概念:

function makeSound(animal) {
  console.log(animal.sound());
}

const duck = {
  sound: () => "Quack!",
};

const dog = {
  sound: () => "Woof!",
};

makeSound(duck); // 输出 "Quack!"
makeSound(dog); // 输出 "Woof!"

在这个例子中,makeSound 函数接受一个 animal 参数,但我们没有声明它的具体类型。相反,我们依赖于 animal 对象是否具有 sound 方法来判断它是否符合预期的行为。这使得我们可以将不同类型的对象传递给 makeSound 函数,只要它们具有 sound 方法即可。

需要注意的是,鸭子类型主要适用于动态类型语言(如 JavaScriptPython 等)。在静态类型语言(如 TypeScriptJava 等)中,鸭子类型的概念仍然存在,但通常需要通过接口或其他类型抽象机制来实现。例如,在 TypeScript 中,我们可以使用 接口 来实现类似的效果:

interface Animal {
  sound(): string;
}

function makeSound(animal: Animal) {
  console.log(animal.sound());
}

const duck: Animal = {
  sound: () => "Quack!",
};

const dog: Animal = {
  sound: () => "Woof!",
};

makeSound(duck); // 输出 "Quack!"
makeSound(dog); // 输出 "Woof!"

在这个例子中,我们定义了一个 Animal 接口,它要求实现 sound 方法。makeSound 函数接受一个 Animal 类型的参数,这使得我们可以传递任何实现了 Animal 接口的对象。尽管 TypeScript 是静态类型的,但通过接口,我们仍然可以实现鸭子类型的灵活性。

1.2.3 类型可以由环境来定义

Typescript 的主要目标之一是让人们能够安全、轻松地在 Typescript 中使用现有的 Javascript 库。它可以通过声明来做到这一点。Typescript 提供了一个浮动的标准来衡量你希望在声明中投入多少努力;投入的越多,你获得的类型安全和代码智能提示就越多。

请注意,大多数流行的 Javascript 库的声明定义已经由 DefinedTyped 社区编写过了,因此,在大多数情况下,声明文件已经存在;或者,至少已经拥有了大量经过深思熟虑的可用的 Typescript 声明目标。

可以把 jQuery 作为一个编写声明文件的简单示例。在默认情况下,Typescript 要求(正如良好的 Javascript 代码所要求的一样)在使用变量(即在某处使用 var )之前先进行声明。

Typescript 中,直接编写下面代码会报错:

$(".awesome").show();  // 错误,找不到$

$(".awesome").show(); 是使用 jQuery 库中的 选择器 来选取所有具有 awesome 类名的元素,并将它们显示出来。

具体作用如下:

  • $ 符号是 jQuery 库的入口函数,它用于选取元素或创建 jQuery 对象。
  • $(".awesome") 使用 CSS 选择器 .awesome 来选取所有具有 awesome 类名的元素。这个选择器会匹配文档中所有类名为 awesome 的元素。
  • .show()jQuery 提供的一个方法,用于显示被选元素。它会将选中的元素设置为可见状态,如果元素之前被隐藏了。

因此,$(".awesome").show(); 的作用是将所有具有 awesome 类名的元素显示出来,使它们在页面上可见。

TypeScript 中,$(".awesome").show(); 会报错,因为 TypeScript 是一种强类型语言,它需要知道变量和对象的类型。在这个例子中,$ 是一个全局变量,它代表 jQuery 库。TypeScript 编译器不知道 $ 的类型,因此会抛出一个错误。

要修复这个错误,需要在 TypeScript 代码中声明 $ 的类型。这可以通过添加 declare var \$: any; 这一行代码来实现。这告诉 TypeScript 编译器,$ 是一个任意类型的变量,因此编译器不会对其进行类型检查。如下所示:

declare var $: any;
$(".awesome").show();

这样修改之后,TypeScript 编译器就可以正确地识别 $ 变量,不再报错。但是,使用 any 类型会导致失去类型安全性,因此在实际项目中,建议使用 @types/jquery 这样的类型定义库,而不是直接使用 any 类型。安装 @types/jquery 后,可以直接使用 $ 变量,而不需要 declare var \$: any; 这一行代码。

如果你愿意,可以基于此来定义结构,并提供更多信息以保护你免收错误的影响,如下所示:

declare var $: {
	(selector: string): any;
}
$(".awesome").show();  // 正确;
$(123).show();         // 错误,selector 必须为 string 类型的

在这段代码中,我们声明了 $ 变量的类型为一个对象,这个对象包含一个函数签名,表示这个函数接受一个 string 类型的参数 selector,并返回 any 类型的值。

declare var $: {
  (selector: string): any;
};

接下来,我们调用了 $ 函数两次:

$(".awesome").show();
$(123).show();

第一次调用是正确的,因为我们传递了一个字符串参数 ".awesome"。然而,第二次调用是错误的,因为我们传递了一个数字参数 123。根据我们之前声明的类型,$ 函数只接受一个 string 类型的参数。所以,当我们尝试传递一个 number 类型的参数时,TypeScript 编译器会报错,因为这不符合我们声明的类型约束。

要修复这个错误,我们需要确保传递给 $ 函数的参数是一个字符串。例如,我们可以将数字转换为字符串,如下所示:

$(String(123)).show();

或者,我们可以修改 $ 函数的类型声明,使其接受 number 类型的参数。但是,在实际的 jQuery 用法中,通常不会使用数字作为选择器,因此这种修改可能并不合适。

1.2.4 函数签名

函数签名是一种定义函数类型的语法,它描述了函数的输入参数类型、返回值类型以及可能抛出的异常。在 TypeScript 中,函数签名通常用于定义接口、类型别名或对象类型中的函数属性。函数签名可以帮助我们确保函数的实现和调用遵循预期的类型约束,从而提高代码的可读性和可维护性。

函数签名的基本语法如下:

(param1: Type1, param2: Type2, ...): ReturnType;
  • param1param2 等是函数的参数名;
  • Type1Type2 等是函数参数的类型;
  • ReturnType 是函数的返回值类型;

以下是一些函数签名的用法示例:

  1. 在接口中定义函数签名:

    interface MyInterface {
      myFunction(param1: string, param2: number): boolean;
    }
    

    这个接口 MyInterface 包含一个名为 myFunction 的函数,它接受一个 string 类型的参数 param1 和一个 number 类型的参数 param2,并返回一个 boolean 类型的值。

  2. 在类型别名中定义函数签名:

    type MyFunctionType = (param1: string, param2: number) => boolean;
    

    这个类型别名 MyFunctionType 描述了一个函数,它接受一个 string 类型的参数 param1 和一个 number 类型的参数 param2,并返回一个 boolean 类型的值。

  3. 在对象类型中定义函数签名:

    type MyObjectType = {
      myFunction(param1: string, param2: number): boolean;
    };
    

    这个对象类型 MyObjectType 包含一个名为 myFunction 的函数,它接受一个 string 类型的参数 param1 和一个 number 类型的参数 param2,并返回一个 boolean 类型的值。

  4. 定义可选参数和默认参数:

    type MyFunctionType = (param1: string, param2?: number, param3: boolean = true) => boolean;
    

    在这个函数签名中,param2 是一个可选参数,它的类型为 number | undefinedparam3 是一个具有默认值 true 的参数,它的类型为 boolean

  5. 定义剩余参数

    type MyFunctionType = (param1: string, ...restParams: number[]) => boolean;
    

    在这个函数签名中,...restParams 表示一个剩余参数,它是一个 number 类型的数组。这意味着该函数可以接受一个 string 类型的参数 param1 和任意数量的 number 类型的参数。

1.2.5 箭头函数

TypeScript 的箭头函数 (Arrow Function)ES6(ECMAScript 2015) 引入的一种新的函数语法,它使用 => 符号表示。箭头函数具有更简洁的语法,同时它自动绑定了上层作用域的 this 值,这使得在某些场景下编写函数更加方便。箭头函数在 TypeScript 中的使用和在 JavaScript 中的使用基本相同,只是 TypeScript 会对参数和返回值进行类型检查。

以下是箭头函数的一些特点和用法:

  1. 简洁的语法:

    箭头函数的语法比传统的函数表达式更简洁。例如,一个简单的箭头函数如下所示:

    const add = (a: number, b: number): number => a + b;
    

    这个箭头函数接受两个 number 类型的参数 ab,并返回它们的和。等效的传统函数表达式如下所示:

    const add = function (a: number, b: number): number {
    return a + b;
    };
    
  2. 自动绑定 this 值:

    箭头函数会自动捕获它所在上下文的 this 值。这使得在某些场景下编写函数更加方便,例如在类方法中处理事件回调。例如:

    class MyClass {
      value: number;
    
      constructor(value: number) {
        this.value = value;
      }
    
      onClick() {
        // 使用箭头函数自动捕获 `this` 值
        document.addEventListener("click", () => {
          console.log(this.value);
        });
      }
    }
    

    在这个例子中,onClick 方法中的箭头函数自动捕获了 this 值,使得我们可以在事件回调中访问 MyClass 实例的 value 属性。如果使用传统的函数表达式,我们需要手动绑定 this 值,例如通过 bind 方法。

    class MyClass {
      value: number;
    
      constructor(value: number) {
        this.value = value;
      }
    
      onClick() {
        // 使用传统的函数表达式,并通过 `bind` 方法手动绑定 `this` 值
        document.addEventListener("click", function () {
          console.log(this.value);
        }.bind(this));
      }
    }
    

    在这个例子中,onClick 方法中的传统函数表达式使用了 bind 方法将 this 值绑定到 MyClass 实例。这使得我们可以在事件回调中访问 MyClass 实例的 value 属性。这种方法在 ES6 引入箭头函数之前,是 JavaScript 中常用的处理 this 值的方法。

  3. 没有 arguments 对象:

    箭头函数没有自己的 arguments 对象。它会访问其外部作用域的 arguments 对象。例如:

    function outerFunction(a: number, b: number) {
      const add = (): number => {
        // 访问外部作用域的 `arguments` 对象
        return arguments[0] + arguments[1];
      };
      return add();
    }
    

    在这个例子中,箭头函数 add 访问了 outerFunctionarguments 对象。请注意,在 TypeScript 中,arguments 对象的类型安全性较差,因此在实际项目中,建议避免使用 arguments 对象,而是使用具名参数或剩余参数 (...rest)

总之,TypeScript 的箭头函数提供了更简洁的语法和自动绑定 this 值的特性,使得编写函数更加方便。但请注意,箭头函数并不适用于所有场景,例如在定义类方法或需要使用 prototype 的情况下,仍然需要使用传统的函数表达式或方法定义。

11-22 02:59