强曰为道

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

第 04 章:TypeScript 深入

第 04 章:TypeScript 深入

4.1 Deno 与 TypeScript 的关系

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

特性DenoNode.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
  }
}

常用编译选项

选项类型默认值说明
strictbooleantrue启用严格模式
libstring[]["deno.window"]类型库
targetstring"esnext"编译目标
jsxstringJSX 转换模式
jsxImportSourcestringJSX 导入源
experimentalDecoratorsbooleanfalse装饰器支持
emitDecoratorMetadatabooleanfalse装饰器元数据
noUnusedLocalsbooleanfalse未使用变量报错
noUnusedParametersbooleanfalse未使用参数报错
noFallthroughCasesInSwitchbooleanfalseswitch 穿透报错

lib 类型库

Deno 提供了多种类型库:

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

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 runtimeReact 18+ / Preact 10+
react-jsxdev开发模式 runtime开发环境
reactReact.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 的安全沙箱。