第 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.json 的 imports 字段管理依赖别名:
// 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 的安全沙箱。