TypeScript 学习笔记8: Modules

本文深入讲解TypeScript中的模块概念,包括内部模块与外部模块的区别、使用namespaces进行封装的细节,以及如何通过import和export在不同模块间共享代码。

原文链接:https://leanpub.com/essentialtypescript/read#leanpub-auto-modules
TypeScript支持两种模块:内部模块、外部模块。 内部模块就是用 namespace 封装起来的代码块。外部模块就是CommonJS、amd等实现的功能,在TypeScript中定义了更简单的语法。

1. Namespaces

namespace 也是 syntactic sugar, 本质上它也是用 function 定义作用域,《Eloquent JavaScript》的第十章详细的讲了基本原理。直接看用法:

//Model.ts
namespace TodoApp.Model {
    export interface Todo {
        id: number;
        name: string;
        state: TodoState;
    }
}

namespace TodoApp.Model {
    export enum TodoState {
        New = 1,
        Active = 2,
        Complete = 3, 
        Deleted = 4
    }
}

namespace TodoApp.DataAccess {
    import Todo = TodoApp.Model.Todo;

    export interface ITodoService {
        add(todo: Todo): Todo;
        delete(todoId: number): void;
        getAll(): Todo[];
        getById(todoId: number): Todo;
    }
}

说明:
1. namespace 定义一个作用域,和全局空间隔离开来;
2. namespace 的名字 TodoApp.Model,中间有没有“点”都可以,有多少“点”都可以;
3. 一个namespace可以拆分成多个定义,如上面的TodoApp.Model,这不会导致重复定义;它们也可以写在不同的文件中;
4. 多个namespace的定义可以写在一个文件中;
5. 加上 export 前缀,才能在其它namespace中访问,否则,出了它所在的namespace空间,谁也不认识它。如:interface Todo, enum TodoState。
6. 在其它namespace中使用TodoApp.Model.Todo需要写全名,或则,定义别名:import Todo = TodoApp.Model.Todo;
我觉得 import 这个关键字不好,用 alias 会更明确,因为,这里并没有import的作用,只是给 TodoApp.Model.Todo 一个别名。

2. Using namespaces to encapsulate private members

回顾一下前两章讲的 class TodoService,它用到了 static 变量和方法创建新的TodoId,本质上来讲,用 static 方法把 nextId() 方法放到 TodoService 这个类的定义中,是为了把这个功能全局空间分离出来,现在,有了namespace,我们可以这么做:

//DataAccess.ts
namespace DataAccess {
    import Model = TodoApp.Model;
    import Todo = Model.Todo;

    export interface ITodoService {
        add(todo: Todo): Todo;
        delete(todoId: number): void;
        getAll(): Todo[];
        getById(todoId: number): Todo;
    }

    let _lastId: number = 0;

    function generateTodoId(): number {
        return _lastId += 1;
    }

    class TodoService implements ITodoService {        
        constructor(private todos: Todo[]) {        
        }
        add(todo: Todo) {
            todo.id = generateTodoId();
            this.todos.push(todo);
            return todo;
        }
        // ...
    }
}

把 _lastId 和 generateTodoId() 从TodoService 中拿出来,放到 namespace DataAccess 定义的局部空间中。

3. Internal and External Modules

Internal Module:namespace,上一节介绍的方式;
External Module:每个module在自己的文件中,不使用namespace,文件本身就定义了一个局部空间。TypeScript 提供了一种方式,ECMAScript2015也提供了一种方式。无论用哪一种方式,tsc 编译出来的代码是相同的。

4. Switching from internal to external modules

  1. 去掉Model.ts 和 DataAccess.ts 文件中的namespace关键字,及其相关的括号;
  2. 修改 tsconfig.json,指定 module 属性:

    {
        "compilerOptions": {
        "target": "es5",
        "module": "system"
        }
    }
    

    module属性有多个备选值:system, amd, commonjs, es2015 等等。

5. Importing modules using Require syntax

//Model.ts
export interface Todo {
    id: number;
    name: string;
    state: TodoState;
}    

export enum TodoState {
    New = 1,
    Active = 2,
    Complete = 3, 
    Deleted = 4
}
//DataAccess.ts
import Model = require('./Model');
import Todo = Model.Todo;

// 其它代码 ...

说明:
1. Model.ts 是一个文件,也是一个module;
2. Model.ts 中不需要namespace;
3. 每一个需要暴露给外部的接口,都需要加上 export 前缀,如:export interface Todo 和 export enum TodoState
4. DataAccess.ts 中,require(‘./Model’) 导入了Model.ts 模块,不要写文件扩展名;
5. import Model = … ,只是给这个模块定义一个别名(alias),不是必须叫Model,可以是任意名字;

6. Importing modules using ECMAScript 2015 syntax

//DataAccess.ts
import * as Model from './Model';
import Todo = Model.Todo;

// 其它代码 ...

这是es2015 导入模块的语法,只有一行代码不同。
事实上,es2015的导入语法还提供了更丰富的内容。
1. 只导入Todo:

import {Todo} from './Model';

这时,后续的代码不能使用 TodoState。
2. 导入指定内容:

import {Todo, TodoState} from './Model';

导入Todo 和 TodoState,如果Model.ts中还有其它export,不会导入。
3. 导入的同时,指定别名:

import {Todo as TodoTask, TodoState} from './Model';

给Todo指定了一个别名:TodoTask,后续的代码用到Todo时,都要改成TodoTask。
4. 导入模块,但导入它任何 export 的接口

import './Config';

通常很少用到这种方式。当Config.ts模块中有自动执行的代码,这种方式才有用,例如:Config.ts 中有一段代码,用来配置环境变量,而这些代码在加载时就会自动执行。

7. Loading External Modules

ECMAScript 2015 虽然定义了加载模块的语法,但现在还没有一个标准的loader,浏览器并没有内置支持这种语法的loader。多年以来,已经有多个loader被广泛使用,我们必须从中选择一个。我们选择 systemJS 作为模块加载器,因为,它提供了 es2015 的导入模块的语法。这样的话,等将来es2015的load语法被浏览器广泛支持时,我们不用修改代码,就可以使用内置的loader了。
需要在 index.html 中加载 system.js:

<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.22/system.js"></script>

当然,我们也可以把system.js下载到本地。
使用System 引用 app.ts:

<script type="text/javascript">
    System.defaultJSExtensions = true;
    System.import('app')
</script>

这样,当加载index.html 时,app.ts 中的代码就开始执行了。
在app.ts中添加一些代码:

// app.ts
import { Todo, TodoState } from './Model'; 
import { TodoService } from './DataAccess';

let service = new TodoService([]);
service.add({
    id: 1,
    name: 'Pick up drycleaning',
    state: TodoState.New
})

let todos = service.getAll();

todos.forEach(todo =>
    console.log(`${todo.name} [${TodoState[todo.state]}]`)
)

到命令行下,运行:
cd TypeScriptTodo
lite-server
看看浏览器 console 的输出。

<think> 首先,用户指令提到“参考站内引用:”,但它说“上述引用仅供参考”,意思是这些引用只是参考,不是用户真实需求。接着,它强调要尽可能参考用户上一次的问题和我的回答。 回顾上下文: - 系统级指令要求使用特定的LaTeX格式、中文回答、生成相关问题等。 - 用户上一次的问题是关于获取TypeScript学习的详细笔记。 用户当前的问题是:“我想获取TypeScript学习的详细笔记 请问TypeScript学习详细笔记” 所以,用户的核心需求是获取TypeScript学习的详细笔记。我需要提供有关TypeScript学习的详细内容。 我的回答结构: - 必须使用中文。 - 必须遵循LaTeX格式:行内数学表达式用$...$,独立公式用$$...$$。 - 回答结构清晰,逐步解决问题。 - 在回答末尾生成相关问题。 - 引用段落末尾添加引用标识,但用户指令说“参考站内引用:”,然后澄清它不是真实需求,所以我应该专注于提供内容。系统指令说“回答中引用的段落末尾自然地添加引用标识”,但既然用户说引用仅供参考,我不需要添加引用标识,除非有实际引用。我会避免添加不必要的引用。 内容规划: - TypeScriptJavaScript的超集,添加了静态类型和其他特性。 - 详细笔记应该包括:TypeScript是什么、为什么使用它、基本语法(变量、类型、接口、类等)、工具(编译器tsc)、与JavaScript的区别、最佳实践等。 - 由于是笔记,我需要组织成清晰的章节。 结构清晰的回答: 1. 介绍TypeScript。 2. 核心概念:类型系统、接口、类、泛型等。 3. 工具和使用方式。 4. 示例代码。 5. 优点和适用场景。 使用LaTeX: - 如果有需要使用数学表达式的地方,比如解释算法,但TypeScript主要是编程语言,不太需要数学。可能涉及类型理论,但保持简单。 - 例如,讨论类型时,可以用LaTeX表示类型签名。 - 系统指令强制使用LaTeX,所以我必须包含它。例如,变量名可以用$...$包裹,如$let x: number = 10;$,但LaTeX通常用于数学,不是代码。指令说“行内数学表达式(包括变量、不等式、方程等)”,所以对于编程变量,如果涉及数学,我会用。 - 在解释时,如果有方程或类型表达式,用LaTeX。例如,函数类型可以用$(param: type) => returnType$。 生成相关问题: - 回答末尾必须生成相关问题,类似于示例。 确保回答真实可靠: - 基于我对TypeScript的知识:TypeScript是开源的,由Microsoft开发,添加了静态类型到JavaScript。 大纲回答: 1. TypeScript简介:什么是TypeScript,为什么使用它。 2. 基本语法:变量声明、类型注解、基本类型(number, string, boolean等)。 3. 高级特性:接口、类、泛型、模块。 4. 工具:tsc编译器,配置tsconfig.json。 5. 示例代码。 6. 总结和优点。 7. 生成相关问题。 用中文回答。 添加代码块:指令中有代码块示例,所以我应该包括TypeScript代码示例。 引用标识:如果有引用,添加,但这里不需要,因为用户说引用仅供参考。 最终输出格式: - 标题或部分。 - 内容。 - 代码块。 - 相关问题。 </think> ### TypeScript学习详细笔记 TypeScript是一种由Microsoft开发的开源编程语言,它作为JavaScript的超集,添加了静态类型系统和其他高级特性,帮助开发者编写更可靠、可维护的代码[^1]。以下笔记基于官方文档和最佳实践,结构清晰地介绍核心概念、语法和实用技巧。笔记分为几个部分,逐步从基础到高级讲解。 #### 1. TypeScript简介 - **什么是TypeScript**:TypeScript扩展JavaScript,支持类型注解,编译时类型检查能捕获常见错误。例如,JavaScript变量是动态类型的,而TypeScript允许指定类型: `let age: number = 25; // 定义数字类型变量` $$编译后生成JavaScript代码$$。 - **为什么使用TypeScript**: - 提高代码质量:类型检查减少运行时错误(如未定义变量引用)。 - 增强开发体验:IDE智能提示(如VS Code)支持自动补全。 - 兼容性强:兼容所有JavaScript库和框架(如React、Angular)。 - **安装与配置**: - 安装Node.js和npm后,运行 `npm install -g typescript`。 - 初始化项目:`tsc --init` 生成 `tsconfig.json` 配置文件。 #### 2. 基本语法和类型系统 TypeScript的核心是类型系统,提供静态类型注解: - **基本类型**: - 数字:`let count: number = 10;` - 字符串:`let name: string = "Alice";` - 布尔:`let isDone: boolean = false;` - 数组:`let list: number[] = [1, 2, 3];` 或 `let list: Array<number> = [1, 2, 3];` - 元组:固定类型序列,如 `let tuple: [string, number] = ["hello", 10];` - 枚举:定义命名常量集,`enum Color {Red, Green, Blue}; let c: Color = Color.Green;` - **类型推断**:TypeScript能自动推断类型,避免冗余注解: ```typescript let x = 10; // 推断为number类型 x = "hello"; // 编译错误:类型不匹配 ``` - **联合类型**:允许变量为多个类型之一: ```typescript let id: string | number; id = 123; // 合法 id = "abc"; // 合法 ``` - **函数类型**:指定参数和返回值类型: ```typescript function add(a: number, b: number): number { return a + b; } // 箭头函数类型 const multiply: (x: number, y: number) => number = (x, y) => x * y; ``` - **类型别名**:创建自定义类型: ```typescript type Point = { x: number; y: number; }; let p: Point = { x: 10, y: 20 }; ``` #### 3. 高级特性 扩展TypeScript的核心功能: - **接口(Interfaces)**:定义对象结构: ```typescript interface Person { name: string; age: number; greet(): void; // 方法签名 } let alice: Person = { name: "Alice", age: 30, greet() { console.log(`Hello, ${this.name}`); } }; ``` - **类(Classes)**:支持面向对象编程,包括继承、访问修饰符: ```typescript class Animal { protected name: string; // protected修饰符 constructor(name: string) { this.name = name; } move(distance: number = 0) { console.log(`${this.name} moved ${distance}m.`); } } class Dog extends Animal { bark() { console.log("Woof!"); } } const dog = new Dog("Buddy"); dog.bark(); // 输出 "Woof!" ``` - **泛型(Generics)**:创建可重用组件,支持多种类型: ```typescript function identity<T>(arg: T): T { return arg; } let output = identity<string>("hello"); // 显示指定类型 let output2 = identity(42); // 类型推断为number ``` - **模块(Modules)**:组织代码,支持导入导出: ```typescript // file: math.ts export function add(a: number, b: number): number { return a + b; } // file: main.ts import { add } from "./math"; console.log(add(2, 3)); // 输出 5 ``` #### 4. 工具和实践 - **编译器(tsc)**:编译TypeScriptJavaScript: - 运行 `tsc filename.ts` 生成 `filename.js`。 - 配置 `tsconfig.json`:指定编译选项(如目标ES版本、模块系统)。 - **类型声明文件(.d.ts)**:为JavaScript库添加类型支持: - 安装社区类型包:`npm install @types/node`(用于Node.js)。 - **调试和测试**: - 使用VS Code内置调试器。 - 结合Jest进行单元测试:`npm install jest @types/jest ts-jest`。 - **最佳实践**: - 逐步迁移:在JavaScript项目中逐步添加TypeScript文件。 - 避免 `any` 类型:尽量使用具体类型以提高安全性。 - 利用严格模式:在 `tsconfig.json` 中启用 `strict: true`。 #### 5. 示例:完整代码片段 以下是一个简单应用示例,演示接口、类和模块: ```typescript // 定义接口 interface Vehicle { start(): void; stop(): void; } // 实现类 class Car implements Vehicle { constructor(private model: string) {} start() { console.log(`${this.model} started.`); } stop() { console.log(`${this.model} stopped.`); } } // 使用模块 export function drive(vehicle: Vehicle) { vehicle.start(); vehicle.stop(); } // main.ts 中调用 import { drive } from './vehicle'; const myCar = new Car("Tesla"); drive(myCar); // 输出 "Tesla started." 和 "Tesla stopped." ``` #### 总结 TypeScript通过静态类型增强了JavaScript开发效率和可靠性。它在大型项目中特别有价值,能减少错误并提升团队协作。学习路径建议: - 从基础类型开始,逐步学习接口和泛型。 - 实践项目:构建简单应用(如Todo List)。 - 参考资源:官方文档([typescriptlang.org](https://www.typescriptlang.org/))和社区教程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值