TypeScript学习(环境声明 接口 枚举)

环境声明

声明文件

你可以通过 declare 关键字来告诉 TypeScript,你正在试图表述一个其他地方已经存在的代码,如:写在 JavaScript、CoffeeScript 或者是像浏览器和 Node.js 运行环境里的代码:

foo = 123; // Error: 'foo' is not defined

declare var foo: any;
foo = 123; // allow

你可以选择把这些声明放入 .ts 或者 .d.ts 里。在你实际的项目里,建议你应该把声明放入独立的 .d.ts 里(可以从一个命名为 global.d.ts 或者 vendor.d.ts 文件开始)。

如果一个文件有扩展名 .d.ts,这意味着每个根级别的声明都必须以 declare 关键字作为前缀。这有利于让开发者清楚的知道,在这里 TypeScript 将不会把它编译成任何代码,同时开发者需要确保这些在编译时存在。

TIP

  • 环境声明就好像你与编译器之间的一个约定,如果在编译时它们不存在,但是你却使用了它们,程序将会在没有警告的情况下中断。
  • 环境声明就好像是一个文档。如果源文件更新了,你应该同步更新。所以,当你在运行时有新的行为时,如果没有去更新环境声明,编译器将会报错。

变量

当你想告诉 TypeScript 编辑器关于 process 变量时,你可以这么做:

declare let process: any;

TIP

你并不需要为 process 做这些,因为这已经存在于社区维护的 node.d.ts,这允许你使用 process,并能成功通过 TypeScript 的编译:

process.exit();

我们推荐尽可能的使用接口,例如:

interface Process {
    
    
  exit(code?: number): void;
}

declare let process: Process;

因为这允许其他人扩充这些全局变量,并且会告诉 TypeScript 有关于这些声明的修改。例如:考虑到以下情况,我们添加一个 exitWithLogging 函数至 process:

interface Process {
    
    
  exitWithLogging(code?: number): void;
}

process.exitWithLogging = function() {
    
    
  console.log('exiting');
  process.exit.apply(process, arguments);
};

接口(重要)

接口运行时的影响为 0。在 TypeScript 接口中有很多方式来声明变量的结构。下面两个是等效的声明, 示例 A 使用内联注解,示例 B 使用接口形式:

// 示例 A
declare const myPoint: {
    
     x: number; y: number };

// 示例 B
interface Point {
    
    
  x: number;
  y: number;
}
declare const myPoint: Point;

示例 B 的好处在于,如果有人创建了一个基于 myPoint 的库来添加新成员, 那么他可以轻松将此成员添加到 myPoint 的现有声明中:

// Lib a.d.ts
interface Point {
    
    
  x: number,
  y: number
}
declare const myPoint: Point

// Lib b.d.ts
interface Point {
    
    
  z: number
}

// Your code
myPoint.z // Allowed!

TypeScript 接口是开放式的,这是 TypeScript 的一个重要原则,它允许你使用接口来模仿 JavaScript 的可扩展性。

类可以实现接口

如果在类中使用必须要被遵循的接口(类)或别人定义的对象结构,可以使用 implements 关键字来确保其兼容性:

interface Point {
    
    
  x: number;
  y: number;
}

class MyPoint implements Point {
    
    
  x: number;
  y: number; // Same as Point
}

基本上,在 implements(实现) 存在的情况下,该外部 Point 接口的任何更改都将导致代码库中的编译错误,因此可以轻松地使其保持同步

interface Point {
    
    
  x: number;
  y: number;
  z: number; // New member
}

class MyPoint implements Point {
    
    
  // ERROR : missing member `z`
  x: number;
  y: number;
}

注意,implements 限制了类实例的结构,如下所示。但像 foo: Point = MyPoint 这样的代码,与其并不是一回事。

let foo: Point = new MyPoint();

#并非每个接口都是很容易实现的

typescript implements的作用和意义到底是什么?
接口旨在声明 JavaScript 中可能存在的任意结构。思考以下例子,可以使用 new 调用某些内容:

interface Crazy {
    
    
  new (): {
    
    
    hello: number;
  };
}

你可能会有下面这样的代码:

class CrazyClass implements Crazy {
    
    
  constructor() {
    
    
    return {
    
     hello: 123 };
  }
}

// Because
const crazy = new CrazyClass(); // crazy would be { hello:123 }

你可以使用接口声明所有“Crazy”的 JavaScript 代码,甚至可以安全地在 TypeScript 中使用它们。但这并不意味着你可以使用 TypeScript 类来实现它们。

枚举(定义枚举的时候,给赋值数字,默认从赋值的数字开始)

枚举是组织收集有关联变量的一种方式,许多程序语言(如:c/c#/Java)都有枚举数据类型。下面是定义一个 TypeScript 枚举类型的方式:

enum CardSuit {
    
    
  Clubs,
  Diamonds,
  Hearts,
  Spades
}

// 简单的使用枚举类型
let Card = CardSuit.Clubs;

// 类型安全
Card = 'not a member of card suit'; // Error: string 不能赋值给 `CardSuit` 类型

枚举常量与我们的经常使用的类常量和静态常量比较

  • 定义起来更简单
  • 枚举常量属于稳态型,使用常量接口,我们得对输入值进行检查,确定是否越界。简单理解:如果比较确定输入值的范围,使用枚举可以帮助我们做输入值的验证工作。
  • 枚举类型是不能有继承的,也就是说一个枚举常量定义完毕后,除非修改重构,否则无法做扩展。
  • 建议 在项目开发中,推荐使用枚举常量代替接口常量或类常量。

手动赋值

export enum EvidenceTypeEnum {
    
    
  UNKNOWN = '',
  PASSPORT_VISA = 'passport_visa',
  PASSPORT = 'passport',
  SIGHTED_STUDENT_CARD = 'sighted_tertiary_edu_id',
  SIGHTED_KEYPASS_CARD = 'sighted_keypass_card',
  SIGHTED_PROOF_OF_AGE_CARD = 'sighted_proof_of_age_card'
}

const枚举(即使用 const 修饰符来强调当前枚举类型,并且会影响编译结果: 首选)

  • 使用内联语法对性能有明显的提升作用。运行时没有 Tristate 变量的事实,是因为编译器帮助你把一些在运行时没有用到的不编译成 JavaScript。然而,你可能想让编译器仍然把枚举类型编译成 JavaScript,用于如上例子中从字符串到数字,或者是从数字到字符串的查找。在这种情景下,你可以使用编译选项 --preserveConstEnums,它会编译出 var Tristate 的定义,因此你在运行时,手动使用 Tristate[‘False’] 和 Tristate[0]。并且这不会以任何方式影响内联。
// 修饰符 + 关键词 + 枚举名称
const enum Tristate {
    
    
  False,
  True,
  Unknown
}

const lie = Tristate.False;
// 上述声明了一个常量枚举,并且内部的数据均为只读常量
// const 枚举不会生成 lookup table,并且运行时不可访问当前枚举对象,只允许访问枚举成员的值;
const OBJ = Tristate => Error,运行时不存在
function returnNumber (obj: {
    
     a: number }): number {
    
    
  return obj.a
}
returnNumber(Tristate) => Error,运行时不存在
const A = Tristate['0'] => Error,没有生成反向映射,所以不存在该属性
const B = Tristate['False'] => 0
// 基于 const 枚举的特点,如果你只是为了生成一组常量并且只需要获取某一个常量
// 从性能方面考虑,const 枚举是首选;
// 当然如果你在 tsconfig.json 中指定下面选项
"compilerOptions": {
    
    
  // 保留 const 和 enum 声明该项为 true
  "preserveConstEnums": true
}
// 或者在命令行中添加了 --preserveConstEnums 指令,均会让当前 const 枚举转变为普通枚举
// 即会生成反向映射表;在实际开发中,一般情况你可能需要禁止掉该项,除非想要用于调试;

外部枚举

即使用 declare 关键词来声明一个枚举,这种声明枚举的方式比较特别,使用的时候需要非常谨慎,该枚举类型不会生成反向映射

// 声明语 + 关键词 + 枚举名称
declare enum Tristate {
    
    
  False,
  True,
  Unknown
}
console.log(Tristate)
console.log(Tristate.Unknown)
// 你会发现,无论你是访问枚举本身还是内部成员,均会报错: Tristate is not defined
// 编译之后并没有生成该枚举,也就是说,声明的外部枚举是没有被编译的,导致在 runtime 的时候就会报错
// 这就让人很头疼,既然不能访问,那为何要能做出这个声明呢。

有静态方法的枚举

你可以使用 enum + namespace 的声明的方式向枚举类型添加静态方法。如下例所示,我们将静态成员 isBusinessDay 添加到枚举上:

enum Weekday {
    
    
  Monday,
  Tuesday,
  Wednesday,
  Thursday,
  Friday,
  Saturday,
  Sunday
}

namespace Weekday {
    
    
  export function isBusinessDay(day: Weekday) {
    
    
    switch (day) {
    
    
      case Weekday.Saturday:
      case Weekday.Sunday:
        return false;
      default:
        return true;
    }
  }
}

const mon = Weekday.Monday;
const sun = Weekday.Sunday;

console.log(Weekday.isBusinessDay(mon)); // true
console.log(Weekday.isBusinessDay(sun)); // false

开放式枚举

只有在不使用模块时,开放式的枚举才有意义,应该使用模块

  • 编译成 JavaScript 的枚举:
    var Tristate;
    (function(Tristate) {
          
          
      Tristate[(Tristate['False'] = 0)] = 'False';
      Tristate[(Tristate['True'] = 1)] = 'True';
      Tristate[(Tristate['Unknown'] = 2)] = 'Unknown';
    })(Tristate || (Tristate = {
          
          }));
    

我们来看看包裹函数 (function (Tristate) { /* code here */})(Tristate || (Tristate = {})),特别是 (Tristate || (Tristate = {})) 部分。这捕获了一个局部变量 TriState,它要么指向已经定义的TriState 值,要么使用一个新的空对象来初始化它。这意味着你可以跨多个文件拆分(和扩展)枚举定义,你可以把 Color 的定义拆分至两个块中:

enum Color {
    
    
  Red,
  Green,
  Blue
}

enum Color {
    
    
  DarkRed = 3,
  DarkGreen,
  DarkBlue
}

TIP

  • 枚举的延续块中,重新初始化第一个成员(此处为 DarkRed = 3),使生成的代码不破坏先前定义的值(即0、1…等值)。如果您仍然不这样做,TypeScript 将会发出警告(错误信息:In an enum with multiple declarations, only one declaration can omit an initializer for its first enum element.)简单的说不能重复。
  • ts中的枚举类型和普通的js对象本质上没有区别,只是对于开发者来说,相较于直接使用值类型去做判断,枚举类型更易读,能够提升代码的可读性和易维护性。

Guess you like

Origin blog.csdn.net/shadowfall/article/details/120973835