《Go 后端开发者学 TypeScript:export关键字大全》

TypeScript 学习笔记:全面理解 export、import、declare 和模块导出

在 TypeScript 中,export 用来将当前模块中的函数、变量、类、接口、类型等暴露给其他模块使用。

对于 Go 后端开发者,可以先这样理解:

  • Go:通过标识符首字母大写,决定能否被其他包访问。
  • TypeScript :通过 export 显式指定哪些内容可以被其他模块导入。
  • import:从其他模块中导入需要使用的内容。

一、最基本的 export 用法

假设项目结构如下:

css 复制代码
project/
├── math.ts
└── main.ts

1. 导出函数

math.ts:

typescript 复制代码
export function add(a: number, b: number): number {
    return a + b;
}

export function sub(a: number, b: number): number {
    return a - b;
}

// 没有 export,只能在当前模块内部使用
function multiply(a: number, b: number): number {
    return a * b;
}

main.ts:

perl 复制代码
import { add, sub } from "./math";

console.log(add(1, 2)); // 3
console.log(sub(5, 3)); // 2

这里的 import { add, sub } 表示按照导出名称获取相应函数。

需要注意,multiply 没有导出,因此无法通过命名导入直接访问。

2. 导出变量

typescript 复制代码
// config.ts
export const PORT: number = 8080;

export let environment: string = "development";

使用:

javascript 复制代码
import { PORT, environment } from "./config";

console.log(PORT);        // 8080
console.log(environment); // development

3. 导出类

typescript 复制代码
// user.ts
export class User {
    constructor(public name: string) {}

    sayHello(): void {
        console.log(`Hello, ${this.name}`);
    }
}

使用:

sql 复制代码
import { User } from "./user";

const user = new User("张三");
user.sayHello();

这与 Go 中导出一个结构体类型有些相似,不过 TypeScript 的 class 还包括构造函数和方法等运行时行为。

二、export default:默认导出

export default 表示将某个值设置为当前模块的默认导出。

一个模块可以有多个命名导出,但只能有一个默认导出。

例如:

typescript 复制代码
// math.ts
export default function add(a: number, b: number): number {
    return a + b;
}

导入时:

javascript 复制代码
import calculate from "./math";

console.log(calculate(1, 2)); // 3

为什么明明定义的是 add,却可以使用 calculate?

因为:

javascript 复制代码
import calculate from "./math";

不是根据 calculate 这个名称搜索函数,而是从 math.ts 中获取它的默认导出,并在当前文件将其命名为 calculate。

也可以拆开写:

typescript 复制代码
// math.ts
function add(a: number, b: number): number {
    return a + b;
}

export default add;

效果相同。

命名导出和默认导出的区别

特性 export export default
导出数量 可以多个 每个模块只能一个
导入语法 import { add } import add
导入时改名 import { add as sum } import sum
是否依赖导出名称 是 不依赖原名称
常见用途 工具函数、类型、常量 模块的主要对象

混合导出

typescript 复制代码
// math.ts
export const PI = 3.14;

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

export default function multiply(a: number, b: number): number {
    return a * b;
}

导入:

javascript 复制代码
import multiply, { PI, add } from "./math";

console.log(PI);             // 3.14
console.log(add(1, 2));      // 3
console.log(multiply(2, 3)); // 6

三、export declare:声明外部已经存在的内容

这一部分比较重要,也是阅读 TypeScript 工程源码时经常遇到的写法。

先介绍 declare。

1. declare 的含义

declare 表示:

告诉 TypeScript 编译器,某个变量、函数或其他声明已经存在,只需要知道它的类型,不需要在这里创建运行时代码。

例如:

php 复制代码
declare const APP_VERSION: string;

它的意思是:

假设运行环境中已经存在一个叫 APP_VERSION 的字符串变量,请在类型检查时认可它。

但 declare 不会真正创建这个变量。

比如:

typescript 复制代码
declare const APP_VERSION: string;

console.log(APP_VERSION);

TypeScript 类型检查可以通过,但如果运行环境没有定义 APP_VERSION,执行时就会报错:

vbnet 复制代码
ReferenceError: APP_VERSION is not defined

因此,declare 只负责类型声明,不负责创建实际的变量或函数。

2. export declare function

例如:

typescript 复制代码
// math.d.ts
export declare function add(
    a: number,
    b: number
): number;

拆开理解:

  • export:允许其他模块导入这个声明。
  • declare:仅声明函数的类型,不提供函数实现。
  • function add:声明一个名为 add 的函数。
  • : number:函数返回数字。

其他文件可以这样写:

csharp 复制代码
import { add } from "./math";

const result = add(1, 2);

但要特别注意:math.d.ts 只提供类型声明,并不包含可执行的 add 实现。

真实运行时必须有对应模块提供 add 函数,否则无法正常运行。

3. 为什么需要 export declare?

假设你有一个 JavaScript 文件:

math.js:

css 复制代码
function add(a, b) {
    return a + b;
}

export { add };

TypeScript 项目想使用这个 JavaScript 模块,同时希望获得类型检查,就可以添加声明文件:

math.d.ts:

typescript 复制代码
export declare function add(
    a: number,
    b: number
): number;

TypeScript 就知道:

csharp 复制代码
import { add } from "./math";

add(1, 2);       // 正确
add("1", "2");   // 类型错误

实际的函数由 math.js 提供,类型由 math.d.ts 描述。

4. .ts 和 .d.ts 有什么区别?

文件类型 作用 通常包含实现吗?
.ts TypeScript 源代码 是
.d.ts TypeScript 类型声明文件 否
.js JavaScript 运行时代码 是

你可以类比 Go 开发时的两个概念:

  • 函数签名:描述输入参数和返回类型。
  • 函数实现:提供真正的执行逻辑。

TypeScript 的声明文件主要负责前者。

不过 Go 并没有完全对应 .d.ts 的常规模块机制,所以这只是帮助理解的类比。

5. export declare class

声明类也是一样:

typescript 复制代码
// user.d.ts
export declare class User {
    name: string;

    constructor(name: string);

    sayHello(): void;
}

这里只描述:

  • 存在一个 User 类。
  • 有 name 属性。
  • 构造函数接收一个字符串。
  • 有 sayHello() 方法。

不会生成 User 类的实际实现。

6. export declare const

typescript 复制代码
// config.d.ts
export declare const PORT: number;

export declare const API_URL: string;

表示对应模块在运行时会提供这些变量,但声明文件本身不会创建它们。

四、export interface:导出接口

TypeScript 的 interface 用来描述对象的结构。

例如:

typescript 复制代码
// user.ts
export interface User {
    id: number;
    name: string;
    age: number;
}

其他文件:

python 复制代码
import type { User } from "./user";

const user: User = {
    id: 1001,
    name: "张三",
    age: 18
};

你可以将这种接口理解为对数据结构的一种约束。

类似 Go 的:

go 复制代码
type User struct {
    ID   int64
    Name string
    Age  int
}

但两者不完全相同:TypeScript 的 interface 只参与类型检查,不会生成运行时的结构定义;Go 的 struct 则是具体的数据类型。

注意: interface 本身就属于类型声明,因此通常不需要写 export declare interface 。

五、export type:导出类型

type 用来定义类型别名,也可以描述联合类型、对象类型等。

例如:

ini 复制代码
// types.ts
export type UserId = number;

export type Status = "pending" | "running" | "success";

export type User = {
    id: UserId;
    name: string;
};

其他文件:

ini 复制代码
import type { UserId, Status, User } from "./types";

const id: UserId = 1001;

const status: Status = "running";

const user: User = {
    id: 1001,
    name: "张三"
};

Status 这里表示一个联合类型,只允许指定的三个字符串。

例如:

ini 复制代码
const status: Status = "failed";

会发生类型错误,因为 "failed" 不属于定义的联合类型。

export type 与 import type

除了:

ini 复制代码
export type UserId = number;

还可以写:

ini 复制代码
// types.ts
type UserId = number;
type UserName = string;

export type { UserId, UserName };

使用:

python 复制代码
import type { UserId, UserName } from "./types";

import type 表示只导入类型,不会生成对应的运行时导入。

这在大型 TypeScript 项目中非常常见。

六、export { }:集中导出

前面都是在声明函数时直接加 export:

typescript 复制代码
export function add(a: number, b: number) {
    return a + b;
}

其实还可以在文件最后集中导出。

perl 复制代码
// math.ts
function add(a: number, b: number) {
    return a + b;
}

function sub(a: number, b: number) {
    return a - b;
}

const PI = 3.14;

export { add, sub, PI };

导入:

perl 复制代码
import { add, sub, PI } from "./math";

这两种方式的效果基本一致。

导出时重新命名

typescript 复制代码
function add(a: number, b: number) {
    return a + b;
}

export { add as sum };

其他文件:

javascript 复制代码
import { sum } from "./math";

console.log(sum(1, 2)); // 3

注意这里对外暴露的是 sum,而不是 add。

七、export * from:批量重新导出

在大型项目中,经常有很多文件。

例如:

lua 复制代码
project/
├── utils/
│   ├── math.ts
│   ├── string.ts
│   └── index.ts
└── main.ts

math.ts:

typescript 复制代码
export function add(a: number, b: number) {
    return a + b;
}

string.ts:

javascript 复制代码
export function upper(value: string) {
    return value.toUpperCase();
}

如果其他文件分别导入:

javascript 复制代码
import { add } from "./utils/math";
import { upper } from "./utils/string";

文件多了以后,导入路径会比较分散。

可以在 utils/index.ts 中统一导出:

javascript 复制代码
export * from "./math";
export * from "./string";

然后:

javascript 复制代码
import { add, upper } from "./utils";

console.log(add(1, 2));   // 3
console.log(upper("go")); // GO

这叫作 重新导出(Re-export) 。

需要注意:

export * from "./math" 会重新导出目标模块的命名导出,但不会自动重新导出它的默认导出。

八、export { xxx } from:选择性重新导出

如果只希望重新导出某个函数:

csharp 复制代码
export { add } from "./math";

还可以修改导出名称:

csharp 复制代码
export { add as sum } from "./math";

如果要重新导出其他模块的默认导出:

javascript 复制代码
export { default as calculate } from "./math";

它的含义是:

从 math.ts 取出默认导出,并在当前模块中以 calculate 作为命名导出重新暴露。

对应导入:

javascript 复制代码
import { calculate } from "./utils";

还可以直接转发默认导出:

javascript 复制代码
export { default } from "./math";

九、export * as:将整个模块作为一个对象导出

例如:

typescript 复制代码
// math.ts
export function add(a: number, b: number) {
    return a + b;
}

export function sub(a: number, b: number) {
    return a - b;
}

在 index.ts 中:

javascript 复制代码
export * as MathUtils from "./math";

导入:

javascript 复制代码
import { MathUtils } from "./index";

console.log(MathUtils.add(1, 2)); // 3
console.log(MathUtils.sub(5, 2)); // 3

这种方式很像 Go 中通过包名访问函数:

scss 复制代码
mathutil.Add(1, 2)

只是 TypeScript 的 MathUtils 是由模块命名空间导出形成的对象,两者并非完全相同的机制。

十、export enum:导出枚举

TypeScript 还支持枚举。

ini 复制代码
// status.ts
export enum TaskStatus {
    Pending = "pending",
    Running = "running",
    Success = "success"
}

导入:

javascript 复制代码
import { TaskStatus } from "./status";

const status = TaskStatus.Running;

console.log(status); // running

和 Go 中使用常量定义状态值的方式有一定相似之处。

例如:

ini 复制代码
type TaskStatus string

const (
    Pending TaskStatus = "pending"
    Running TaskStatus = "running"
    Success TaskStatus = "success"
)

需要注意:普通 TypeScript enum 通常会生成运行时 JavaScript 对象,而 type 和 interface 不会。

十一、export =:兼容 CommonJS 模块

有时查看一些较早的 TypeScript 或 Node.js 项目,会看到:

ini 复制代码
export = Logger;

例如:

typescript 复制代码
// logger.ts
class Logger {
    log(message: string) {
        console.log(message);
    }
}

export = Logger;

这是 TypeScript 为兼容 CommonJS 等模块模式提供的语法。

在兼容的 TypeScript 模块配置下,可以使用:

ini 复制代码
import Logger = require("./logger");

const logger = new Logger();
logger.log("hello");

它与现代 ES 模块中的 export default 不是同一个语法机制。

新项目通常优先选择标准的 export 和 export default,除非需要兼容已有的 CommonJS 接口。

十二、export as namespace:声明全局命名空间

在第三方库的 .d.ts 类型声明文件中,偶尔会看到:

typescript 复制代码
export as namespace MyLib;

例如:

typescript 复制代码
// mylib.d.ts
export function add(a: number, b: number): number;

export as namespace MyLib;

它表示这个库除模块导入方式外,还可以按特定的全局命名空间形式提供类型声明。

这常用于支持既能通过模块导入、又能通过浏览器全局变量使用的库。

但是:

export as namespace 不会创建实际的全局变量。 是否存在全局对象仍然取决于库的运行时实现。

一般开发业务代码时很少需要自己编写这种语法。

十三、export {}:把文件标记为模块

还有一种看起来很奇怪的写法:

arduino 复制代码
export {};

它没有导出任何函数或变量。

那它有什么用?

作用是:告诉 TypeScript,这个文件是一个模块,而不是一个普通的全局脚本文件。

例如:

ini 复制代码
// demo.ts
const name = "张三";

export {};

这里的 name 属于当前模块内部,不会因为它是在文件顶层定义的,就被当作全局脚本声明。

在某些需要扩展全局类型的声明文件中,也会看到这种写法:

typescript 复制代码
export {};

declare global {
    interface Window {
        appVersion: string;
    }
}

declare global 用于扩展全局类型声明,而 export {} 确保这个文件被视为外部模块。

注意:这里仍然只是类型扩展,并没有实际给 window 对象赋值。

十四、declare 与 export 的本质区别

前面介绍了 export declare,这里把两者放到一起对比。

1. export function:导出实际实现

typescript 复制代码
export function add(a: number, b: number): number {
    return a + b;
}

这个文件提供了真实的函数实现。

2. export declare function:只导出类型声明

typescript 复制代码
export declare function add(
    a: number,
    b: number
): number;

它不提供函数实现,只告诉 TypeScript:

这个模块应该存在一个叫 add 的函数,接收两个数字,返回一个数字。

对比 export function export declare function
导出函数信息 是 是
是否包含函数实现 是 否
是否生成函数代码 是 否
常见文件 .ts .d.ts
常见用途 编写业务逻辑 描述库的 API 类型

可以记住:

  • export 解决的是:这个模块对外提供什么?
  • declare 解决的是:告诉 TypeScript 某个东西存在,它是什么类型?
  • export declare 解决的是:把某个已经存在或预期存在的 API 类型声明提供给其他模块。

十五、常见 export 语法速查表

语法 作用
export const a = 1 导出变量
export function foo() {} 导出函数
export class User {} 导出类
export interface User {} 导出接口
export type ID = number 导出类型别名
export enum Status {} 导出枚举
export default foo 默认导出
export { foo, bar } 集中命名导出
export { foo as bar } 重命名导出
export type { User } 只导出类型
export * from "./utils" 批量重新导出命名成员
export { foo } from "./utils" 选择性重新导出
export * as Utils from "./utils" 以命名空间形式重新导出
export declare function foo(): void 导出函数类型声明,不提供实现
export declare const VERSION: string 导出变量类型声明,不创建变量
export = foo TypeScript 的 CommonJS 兼容导出
export as namespace Foo 声明全局命名空间用法
export {} 将文件标记为模块

十六、实际项目中应该怎么选择?

对于刚从 Go 转到 TypeScript 的开发者,可以按照下面的原则来使用:

第一种:编写普通业务函数

优先使用命名导出:

javascript 复制代码
export function createTask() {}
export function deleteTask() {}
export function queryTask() {}

第二种:编写主要对外提供一个对象的文件

可以使用默认导出:

javascript 复制代码
export default class TaskService {
    createTask() {}
    deleteTask() {}
}

第三种:定义数据结构或类型

使用 export interface 或 export type:

typescript 复制代码
export interface Task {
    id: number;
    status: string;
}

export type TaskId = number;

第四种:为外部 JavaScript 库编写类型声明

使用 .d.ts 声明文件:

typescript 复制代码
export declare function createTask(
    name: string
): number;

第五种:大型项目统一管理导出

可以通过 index.ts 重新导出:

javascript 复制代码
export * from "./task";
export * from "./user";
export * from "./config";

十七、总结

从 Go 后端转向 TypeScript,最重要的是理解以下几个概念:

  1. export:把当前模块的成员暴露给其他模块。
  2. import:从其他模块中导入成员。
  3. export default:提供模块的默认导出,导入时可以自由命名。
  4. declare:只提供类型声明,不创建实际运行时代码。
  5. export declare:将某个 API 的类型声明导出给其他模块。
  6. export type:导出类型,不产生对应的运行时值。
  7. export * from:将其他模块的命名导出统一重新导出。
  8. export {}:将文件标记为模块。

最终记住一句话: export 管模块对外暴露什么, import 管模块之间怎么引用, declare 管类型系统如何认识已有的外部代码。

掌握这些以后,再阅读 TypeScript 工程里的 index.ts、types.ts、*.d.ts 等文件,就容易理解得多了。

相关推荐
Penge66614 分钟前
《Go 后端开发者学 TypeScript:从零理解 async、await 与 Promise》
javascript
Penge66635 分钟前
typescript 中const用法
javascript
程序员大阳11 小时前
HTML+CSS+JS对话式实战(3)-活动管理页实现查询、新增、删除API后端接口
javascript·css·html·实战·删除·查询·新增
炫酷的大高个12 小时前
6 亿播放的闪身步,我用前端算出 15 倍重心差
前端·javascript
林小帅12 小时前
【笔记】Prisma v7 + SQLite TS2305 及连锁 TS7006 问题
前端·javascript·后端
Dumbness12 小时前
订阅消息弹窗必须在用户点击里调:联调实验室里 timing 和 Mock 支付通知踩的坑
javascript·uni-app
黄连木笛13 小时前
不改一行 Postman 源码,把它汉化了:Electron preload 注入 + fetch 拦截实战
javascript·electron
叼烟扛炮14 小时前
[LangChain] 学习笔记(03):打破失忆症——对话机制与上下文管理
java·前端·javascript·笔记·学习·langchain
卋罖75015 小时前
Razor 视图 + JavaScript + Vue/iView 组件实现的表格自定义列
javascript·vue·iview·razor视图