强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Deno 入门教程 / 第 04 章:TypeScript 深入

第 04 章:TypeScript 深入

4.1 Deno 与 TypeScript 的关系

Deno 是第一个将 TypeScript 作为一等公民的 JavaScript 运行时。这意味着:

特性 Deno Node.js
.ts 文件运行 直接运行 需要 ts-node / tsx
类型检查 内置(可选) 需要 tsc
编译配置 deno.json 中配置 tsconfig.json
.d.ts 声明 自动生成 需要手动创建
模块解析 Deno 自有规则 TypeScript 默认规则

内部工作原理

.ts 源码 → SWC 转译(快速) → JavaScript → V8 执行
                 ↓
        类型检查(TSC,可选,异步)

Deno 使用 SWC(Rust 编写的超快编译器)进行转译,使用 TSC(TypeScript Compiler)进行类型检查。两者分离意味着:

  • 类型错误不会阻止运行(除非显式使用 --check
  • 转译速度极快
# 默认行为:转译但不检查类型
deno run script.ts

# 显式类型检查
deno run --check script.ts

4.2 deno.json 中的 TypeScript 配置

基本配置

// deno.json
{
  "compilerOptions": {
    "strict": true,
    "lib": ["deno.window"],
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

常用编译选项

选项 类型 默认值 说明
strict boolean true 启用严格模式
lib string[] ["deno.window"] 类型库
target string "esnext" 编译目标
jsx string JSX 转换模式
jsxImportSource string JSX 导入源
experimentalDecorators boolean false 装饰器支持
emitDecoratorMetadata boolean false 装饰器元数据
noUnusedLocals boolean false 未使用变量报错
noUnusedParameters boolean false 未使用参数报错
noFallthroughCasesInSwitch boolean false switch 穿透报错

lib 类型库

Deno 提供了多种类型库:

{
  "compilerOptions": {
    // 窗口环境(浏览器-like,最常用)
    "lib": ["deno.window"]
    
    // Worker 环境
    // "lib": ["deno.worker"]
    
    // 自定义组合
    // "lib": ["esnext", "dom", "dom.iterable"]
  }
}
lib 值 说明
deno.window 完整 Deno 运行时环境(含 window 类型)
deno.worker Web Worker 环境
esnext 最新 ECMAScript 特性
dom DOM API 类型
dom.iterable DOM 可迭代类型

4.3 类型导入与导出

基本类型导入

// 使用 type 关键字导入类型(推荐)
import type { User } from "./types.ts";

// 这只在编译时存在,运行时完全消除
const user: User = { name: "Alice", age: 30 };

为什么使用 import type

// ❌ 普通导入 —— 运行时仍然会加载模块
import { User } from "./types.ts";

// ✅ 类型导入 —— 运行时不加载,减小开销
import type { User } from "./types.ts";

// ✅ 内联类型导入
import { type User, createUser } from "./user.ts";

💡 最佳实践:始终使用 import type 导入纯类型,Deno lint 会自动检查。

.d.ts 声明文件

// types/global.d.ts
declare global {
  interface Window {
    MY_APP_VERSION: string;
  }
  
  // 扩展全局类型
  interface String {
    toCamelCase(): string;
  }
}

export {};

deno.json 中引用:

{
  "compilerOptions": {
    // Deno 自动包含工作区中的 .d.ts 文件
  }
}

@deno-types 注释

当模块没有 TypeScript 类型时,手动指定类型声明:

// 指定远程模块的类型声明
// @deno-types="https://deno.land/x/[email protected]/mod.d.ts"
import { someFunc } from "https://deno.land/x/[email protected]/mod.js";

someFunc(); // 有类型提示

4.4 模块解析规则

Deno 的模块解析与 TypeScript 传统方式有所不同:

URL 导入

// 远程 URL 导入
import { serve } from "https://deno.land/[email protected]/http/server.ts";

// npm 包导入(Deno 2.0+)
import express from "npm:[email protected]";

// JSR 包导入
import { encodeBase64 } from "@std/encoding/base64";

相对路径导入

// 导入本地文件(必须带扩展名)
import { helper } from "./utils.ts";
import { Config } from "../types/config.ts";

// ❌ 不带扩展名会报错(与 Node.js 不同)
// import { helper } from "./utils";

导入映射(Import Map)

通过 deno.jsonimports 字段管理依赖别名:

// deno.json
{
  "imports": {
    "std/": "https://deno.land/[email protected]/",
    "oak": "jsr:@oak/oak@^16.0",
    "lodash": "npm:lodash-es@^4.17",
    "@utils/": "./src/utils/",
    "react": "npm:react@^18.0",
    "react-dom": "npm:react-dom@^18.0"
  }
}
// 使用导入映射
import { serve } from "std/http/server.ts";       // 映射到远程 URL
import { Router } from "oak";                      // 映射到 JSR 包
import _ from "lodash";                            // 映射到 npm 包
import { formatDate } from "@utils/date.ts";       // 映射到本地路径

4.5 JSX/TSX 支持

React JSX

// deno.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react"
  },
  "imports": {
    "react": "npm:react@^18.0",
    "react-dom": "npm:react-dom@^18.0"
  }
}
// app.tsx
function App() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <h1>Count: {count}</h1>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

export default App;

Preact JSX

// deno.json
{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "preact"
  },
  "imports": {
    "preact": "npm:preact@^10.0",
    "preact/": "npm:preact@^10.0/"
  }
}

JSX 模式对比

模式 说明 适用场景
react-jsx 自动导入 jsx runtime React 18+ / Preact 10+
react-jsxdev 开发模式 runtime 开发环境
react React.createElement 调用 旧版 React
preserve 保留 JSX 不转换 交给其他工具处理

4.6 类型声明与第三方包

为 JavaScript 包提供类型

// third-party.d.ts
declare module "https://deno.land/x/some_js_lib/mod.js" {
  export function doSomething(input: string): number;
  export interface Config {
    timeout: number;
    retries: number;
  }
}

Shimming Node.js 类型

当使用 npm 包时,Deno 2.0 自动提供 Node.js 类型:

// 直接使用 Node.js 类型,Deno 自动处理
import fs from "node:fs";
import path from "node:path";

const files: string[] = fs.readdirSync(".");
console.log(path.join("/home", "user"));

triple-slash 指令

/// <reference lib="deno.window" />
/// <reference types="npm:@types/node" />

// 这些指令告诉 TypeScript 编译器包含特定的类型库

4.7 高级类型技巧

条件类型与模板字面量

// 条件类型
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>;  // "yes"
type B = IsString<number>;  // "no"

// 模板字面量类型
type EventName = `on${Capitalize<"click" | "focus" | "blur">}`;
// "onClick" | "onFocus" | "onBlur"

const 类型参数

// Deno 2.0 支持 const 类型参数
function defineRoutes<const T extends readonly { path: string; method: string }[]>(
  routes: T
): T {
  return routes;
}

const routes = defineRoutes([
  { path: "/api/users", method: "GET" },
  { path: "/api/users", method: "POST" },
]);

// routes 的类型被精确推断为字面量类型

satisfies 操作符

type Theme = {
  primary: string;
  secondary: string;
  fontSize: number;
};

// 使用 satisfies 保持类型推断的同时验证结构
const theme = {
  primary: "#007bff",
  secondary: "#6c757d",
  fontSize: 16,
  custom: "value", // 不报错,因为 satisfies 不改变推断
} satisfies Theme;

theme.custom; // OK!类型推断保留了 custom 属性

4.8 常见 TypeScript 问题

Q1:类型检查太慢?

# 跳过类型检查,只转译
deno run --no-check script.ts

# 仅在 CI 中检查
deno check --all src/**/*.ts

Q2:如何使用第三方 @types 包?

// deno.json
{
  "compilerOptions": {
    "types": ["npm:@types/node", "npm:@types/lodash"]
  }
}

Q3:strict 模式下的常见错误

// Error: Object is possibly 'undefined'
const arr: number[] = [];
const first = arr[0]; // number | undefined

// 解决方案 1:非空断言(谨慎使用)
const first1 = arr[0]!;

// 解决方案 2:空值检查
const first2 = arr[0] ?? 0;

// 解决方案 3:类型守卫
function getFirst(arr: number[]): number {
  if (arr.length === 0) throw new Error("Empty array");
  return arr[0];
}

Q4:模块找不到?

# 确保扩展名正确
import { x } from "./mod.ts";  # ✅ 必须带 .ts

# 刷新依赖缓存
deno cache --reload mod.ts

4.9 本章小结

要点 说明
原生支持 Deno 直接运行 .ts,无需额外工具
配置位置 deno.json 中的 compilerOptions
类型导入 使用 import type 导入纯类型
模块解析 URL、相对路径、导入映射
JSX 支持 react-jsx、preserve 等多种模式
类型检查 默认不阻塞运行,--check 显式检查

📖 扩展阅读


下一章第 05 章:权限系统 → 深入理解 Deno 的安全沙箱。