

本文面向有 Go 经验的开发者,从设计哲学、类型系统、运行时生态、工程化四个维度,深度剖析 TypeScript 的「不可替代性」,并试图回答那个困扰很多后端程序员的问题——为什么 AI 开源世界几乎被 TypeScript 统治了?
我写 Go 快五年了。从微服务到 CLI 工具,从 Kubernetes operator 到各种中间件胶水代码,Go 一直是我最趁手的锤子。简洁、编译快、部署简单、并发模型优雅——这些优点被说烂了,但确实好用。
大约半年前我开始系统学 TypeScript。起因很简单:几乎每一周,我都会在 GitHub Trending 上看到一个新的 AI 项目,点进去一看——TypeScript。Vercel AI SDK,TypeScript。LangChain.js,TypeScript。Dify,TypeScript。各种 LLM 编排框架、Agent 框架、RAG 脚手架,清一色的 TypeScript。
作为一个 Go 程序员,我的第一反应是不服气:Go 的并发不是更强吗?Go 的部署不是更简单吗?Go 的性能不是更好吗?凭什么 AI 生态不选 Go?
这篇文章,就是我花了半年时间找到的答案。
Go 的类型系统哲学是八个字:极简、显式、编译即文档。
一个典型的 Go 数据模型长这样:
type Student struct {
ID string `json:"id"`
Name string `json:"name"`
Gender string `json:"gender"` // "male" | "female" | "other"
Scores map[string]float64 `json:"scores"`
Status string `json:"status"` // "active" | "graduated" | "suspended"
}写得挺清楚,但有一点 Go 程序员心照不宣:Gender 和 Status 字段的类型定义其实是有缺陷的。它们是 string,但合法的值只有几个。这个约束写在注释里,编译器完全看不见。
在 Go 里我们习惯的补救方式:定义常量、写 validate 函数、加单元测试、靠 code review 兜底。这在单体服务里还行,但当你要对接一个返回 JSON 的 AI API、处理嵌套十几层的响应体时,这种手工纠错的方式就捉襟见肘了。
同样的模型,TypeScript 这样写:
type Gender = 'male' | 'female' | 'other';
type StudentStatus = 'active' | 'graduated' | 'suspended';
interface Student {
id: string;
name: string;
gender: Gender;
scores: Record<string, number>;
status: StudentStatus;
}这里的 Gender 不是一个 string 加上注释,它就是一个只有三个合法值的类型。你在代码里写成 gender: 'unknown',编辑器立刻标红,编译直接报错。这不是 lint 规则,不是运行时校验,是类型系统层面的保障。
这就是 TypeScript 最核心的武器——联合类型(Union Types)和字符串字面量类型(String Literal Types)。Go 的类型系统做不到这一点,因为 Go 没有 sum types,也没有 literal types。
Go 程序员处理多态靠接口(interface)。一个经典的场景:
type Event struct {
Type string
// 不同事件类型,Payload 结构完全不同
ClickData *ClickPayload
KeypressData *KeypressPayload
}使用者必须手动判断 Type,然后解引用对应的 Payload 字段。选错了就 nil pointer panic。TypeScript 提供了更优雅的方案——判别联合(Discriminated Union):
type Event =
| { type: 'click'; x: number; y: number }
| { type: 'keypress'; key: string; modifiers: string[] }
| { type: 'scroll'; deltaX: number; deltaY: number };
function handleEvent(event: Event) {
switch (event.type) {
case 'click':
// 这里 event 自动收窄为 Click 类型,x、y 有智能提示
console.log(event.x, event.y);
break;
case 'keypress':
console.log(event.key, event.modifiers);
break;
case 'scroll':
console.log(event.deltaX, event.deltaY);
break;
// 如果漏了一种 case,TypeScript 的 strict 模式会报错:
// TS7030: Not all code paths return a value.
}
}编译器强制你处理所有可能的分支。少写一个 case,编译直接挂。这是 Go 的 switch-case 加 default 做不到的——Go 的 default 是兜底逃逸口,TypeScript 的 never 检查是不留死角。
Go 的接口是结构化类型(Structural Typing),但 Go 的 struct 是名义类型(Nominal Typing)——两个字段完全相同的 struct 不能互相赋值,除非显式转换。
TypeScript 走得更彻底:它的一切类型都是结构化的。两个 interface 只要形状一样,就是兼容的,不需要显式声明实现关系。这一点和 Go 的 interface 精神相通,但 TS 把它用到了极致:
interface Point2D {
x: number;
y: number;
}
interface Point3D {
x: number;
y: number;
z: number;
}
const p3: Point3D = { x: 1, y: 2, z: 3 };
const p2: Point2D = p3; // OK!Point3D 是 Point2D 的子类型反过来就不行——Point2D 缺了 z。这种鸭子类型的灵活性,在处理 AI API 返回的多变 JSON 结构时极其有用。AI 模型可能返回标准格式,也可能多塞几个字段(比如 token 用量、finish reason 之类的元数据),TypeScript 的类型系统天然容忍「多一些字段」,Go 的 struct 严格匹配 JSON 会直接反序列化失败或丢掉数据。
这部分是 TypeScript 真正让 Go 程序员感到「降维打击」的能力。Go 的泛型只支持非常基础的类型参数化(函数、方法、struct),而 TypeScript 的泛型可以做类型层面的计算。
举个例子——从一个对象类型中提取所有值为函数的 key:
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends (...args: any[]) => any ? K : never;
}[keyof T];
interface API {
name: string;
fetch: (url: string) => Promise<Response>;
cancel: () => void;
}
type APIHandlers = FunctionKeys<API>; // "fetch" | "cancel"这是在编译时完成的。Go 做不到——Go 的泛型是最近几个版本才勉强支持的,而且能力极其受限:不支持方法上的类型参数(直到 Go 1.22 依然有限制),不支持对 struct 字段做类型级遍历。
TypeScript 还能用模板字面量类型做字符串层面的类型体操:
type EventName = 'click' | 'hover' | 'focus';
type HandlerName = `on${Capitalize<EventName>}`;
// "onClick" | "onHover" | "onFocus"这些能力单拎出来看都有点炫技的味道,但合在一起,它为 AI 应用场景提供了一种几乎无法替代的 DX(开发体验):调用任何一个 AI SDK 的函数,你在编码阶段就能知道参数类型、返回值结构、错误分支,不需要翻文档。
Go 程序员初看 Node.js,通常会发出一个灵魂拷问:"单线程怎么搞高并发?"
这个问题反映了一个常见的误解。Node.js 的 JavaScript 主线程确实是单线程的,但它的 I/O 操作(网络请求、文件读写、数据库查询)全部走的是底层的事件驱动 + 线程池(libuv)。一个 Node.js 进程可以同时处理成千上万个并发连接,只是你的业务逻辑跑在单线程上不会互相打断。
对于 AI 应用——尤其是 LLM 应用——这个模型出奇地合适:
for await...of、ReadableStream API、fetch 的 streaming body)经过多年打磨,比 Go 的 io.Reader 生态更适合处理「逐块到达的文本」。下面这段代码演示了 Node.js 如何用「单线程 + 事件循环」并发发起多个 AI 请求——无需 goroutine、无需 channel,写法就是线性的 await:
// 并发调用 3 个模型,总耗时 ≈ 最慢的那一个,而不是三者之和
async function queryModels(models: string[]): Promise<unknown[]> {
return Promise.all(
models.map(async (model) => {
const res = await fetch('https://api.example.com/v1/chat', {
method: 'POST',
body: JSON.stringify({ model, prompt: 'hello' }),
});
return res.json();
})
);
}
// 调用:三个请求同时发出,事件循环在等待网络 I/O 时去处理别的任务
const results = await queryModels(['gpt-4o', 'claude-3', 'llama-3']);Go 的 goroutine 是真正的并发——多个函数可以同时执行、利用多核。这对于 CPU 密集型任务(编解码、加密、图像处理)是碾压级优势。但 LLM 应用几乎不碰这些:
这些全是 I/O 密集操作。在 I/O 密集场景下,Node.js 的事件循环模型和 Go 的 goroutine 在吞吐量上差异不大,但 Node.js 的代码写起来更「线性」——没有 channel 通信的认知负担,没有 goroutine 泄漏的坑,没有 select 的超时处理。
同样是「并发取 10 个网页」,两种语言的写法对比一目了然:
// Go:需要显式起 goroutine + channel 收集结果
func fetchAll(urls []string) []string {
ch := make(chan string, len(urls))
for _, u := range urls {
go func(u string) {
resp, _ := http.Get(u)
body, _ := io.ReadAll(resp.Body)
ch <- string(body)
}(u)
}
var out []string
for range urls {
out = append(out, <-ch)
}
return out
}// TypeScript:同样是并发,但写法就是普通的 async/await
async function fetchAll(urls: string[]): Promise<string[]> {
return Promise.all(
urls.map(async (u) => (await (await fetch(u)).text()))
);
}2024-2025 年,TypeScript 的运行时格局发生了剧变:
运行时 | 特点 |
|---|---|
Node.js | 生态最成熟,npm 包最全,22.x LTS 稳定可靠 |
Deno | 原生 TypeScript 支持,零配置运行 .ts 文件,内置格式化/lint/测试工具,默认安全沙箱 |
Bun | 性能怪物,启动比 Node.js 快 4 倍,内置打包器/转译器/测试运行器/包管理器,兼容 npm |
Deno 最让我心动的一点是:你不再需要 tsconfig.json、package.json、各种 webpack/vite/eslint/prettier 的配置。一个 .ts 文件,deno run 直接跑。这对 Go 程序员的审美是致命的——Go 就是 go run main.go,Deno 终于让 TypeScript 拥有了同等体验。
Bun 的性能表现也值得关注。它用 Zig 重写了 JavaScriptCore 引擎,对 I/O 密集型场景做了极致优化。如果你用 Bun 做 AI API 编排,它能比 Node.js 处理更多并发请求,同时启动时间接近 Go 编译后的二进制。
对 Go 程序员最友好的,是这两个新运行时几乎复刻了 go run 的体验:
# Deno:零配置直接跑 .ts,无需 tsconfig / package.json
deno run main.ts
# Bun:同样零配置,且自带包管理(无需先 npm install)
bun run main.ts
bun add @ai-sdk/openai # 顺手装依赖,等价于 npm installGo 编译产出一个静态链接的二进制文件,丢到服务器上就能跑。这曾经是 Go 对 Node.js 的最大优势——Node.js 需要安装 runtime、管理 node_modules、处理原生模块编译。
但情况在变化:
node:22-alpine 基础镜像也就 50MB,和 Go 的 scratch 镜像差距不大把 TypeScript 应用打包成「类 Go 二进制」也越来越现实:
# Deno:编译成单个独立可执行文件(无需目标机装 runtime)
deno compile --output ai-server main.ts
# Bun:同理,且产物体积更小
bun build --compile --outfile ai-server main.ts配合多阶段 Docker 构建,部署复杂度已经和 Go 拉平:
# 多阶段构建:最终镜像只有 ~50MB
FROM oven/bun:1 AS build
WORKDIR /app
COPY . .
RUN bun build --compile --outfile /ai-server main.ts
FROM gcr.io/distroless/base-debian12
COPY --from=build /ai-server /ai-server
CMD ["/ai-server"]这是 TypeScript 在工程上最大的杀手锏。一个典型的 Go 全栈项目:
backend/ (Go)
├── model/user.go -- 定义 User struct
├── handler/api.go -- 返回 JSON
frontend/ (React + TypeScript)
├── types/user.ts -- 手写一份 User interface
├── api/user.ts -- 调用后端问题来了:后端改了 User 的一个字段,前端不知道。编译全部通过,上线后炸了——前端拿到的 JSON 多了一个字段或少了一个字段。团队规模越大,这个问题越痛。
TypeScript 全栈的解决方案是 monorepo + 共享类型包:
packages/
├── shared-types/ (前后端共享)
│ └── user.ts -- 唯一真相来源
├── backend/ (Node.js + Express/Fastify/Hono)
└── frontend/ (React/Next.js)后端改类型 → 类型包更新 → 前端编译报错 → 前端开发者立刻知道要改什么。编译器变成了前后端的契约执行者。
Go 做不到这一点,因为 Go 的 struct 编译后类型信息全部擦除——你没办法在运行时把一个 Go struct 的定义「发送」给前端让它做类型检查。也许可以用 OpenAPI / Protobuf 做中间层,但那是一条额外的工具链,不是语言原生能力。
共享类型包的实际用法,前后端引用同一个 User 类型,编译器自动同步契约:
// packages/shared-types/user.ts —— 唯一真相来源
export interface User {
id: string;
name: string;
email: string;
role: 'admin' | 'member';
}
// packages/backend/api.ts —— 后端直接复用
import { User } from '@shared/types';
app.get('/user/:id', (req, res) => {
const user: User = db.find(req.params.id); // 类型约束来自共享包
res.json(user);
});
// packages/frontend/api.ts —— 前端同样复用,字段改了这里立刻报错
import { User } from '@shared/types';
async function loadUser(id: string): Promise<User> {
return (await fetch(`/user/${id}`)).json();
}全栈 TypeScript 的另一个隐性收益:团队里所有人用同一种语言,review 成本直线下降。后端工程师能看懂前端代码,前端工程师能参与 API 设计讨论。招聘也更容易——你只需要优秀的 TypeScript 工程师,不需要分别招 Go 后端和 React 前端。
对于 AI 产品团队来说这一点尤其重要。AI 产品的迭代节奏极快——今天加一个 RAG 功能,明天改 prompt 模板,后天接一个新的 embedding 模型。后端和前端的边界是模糊的,很多逻辑(比如 streaming 处理、token 计数、prompt 模板渲染)前后端都会用到。TypeScript 让这些逻辑可以写一次、两端共享。
例如「估算一段文本的 token 数」这种前后端都要用的小工具,写一次就能两端共享:
// packages/shared/token.ts —— 前后端共用
export function estimateTokens(text: string): number {
// 粗略估算:英文约 4 字符/token,中文约 1.5 字符/token
const cjk = (text.match(/[一-龥]/g) || []).length;
const others = text.length - cjk;
return Math.ceil(cjk / 1.5 + others / 4);
}
// 前端:限制输入框不超过模型上下文窗口
const remaining = 128000 - estimateTokens(input);
// 后端:决定是否需要截断历史消息
if (estimateTokens(history) > limit) history = truncate(history);这是本文的核心命题。我在研究过程中梳理了以下几个关键原因:
OpenAI 的 API 返回一个巨大的 JSON。GPT-4 的 chat completion 响应体有嵌套的 choices、message、tool_calls、function 等几十个字段,且不同模型、不同 mode 下结构略有不同。
Go SDK 的做法:定义一堆 struct,用 json:"..." tag 映射,字段全是 *string(因为很多字段可能为 null)。拿到数据后你得手动判空、类型断言、处理嵌套。
TypeScript SDK 的做法:用泛型 + 条件类型 + 判别联合,把每一个 API 版本、每一种调用模式的返回值都精确建模。你在 VSCode 里写 .choices[0].message.,自动补全直接列出所有可用字段,且类型精确到字面量级别。写 Function Calling 时,tool_calls 的 function.arguments 能根据你定义的 schema 做类型收窄。
这不是「好用一点」,这是代际差距。Go 的类型系统没有条件类型和 mapped types,无论是 SDK 作者还是使用者,都只能用 interface{} + 类型断言或者生成大量重复代码来模拟这个行为。
TypeScript SDK 把「版本 + 模式」编码进类型参数,使用者写代码时就能拿到精确补全:
import OpenAI from 'openai';
const client = new OpenAI();
// 返回类型被精确建模:choices[0].message.content 是 string | null
const completion = await client.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: '你好' }],
});
const reply = completion.choices[0].message.content; // 类型已知,无需断言
// Function Calling:tool_calls 的 arguments 类型由你定义的 schema 推导
const tools = [{
type: 'function' as const,
function: {
name: 'get_weather',
parameters: { type: 'object' as const, properties: { city: { type: 'string' } } },
},
}];
// client 会自动把工具定义约束到调用与返回值上LLM 的 streaming 响应是 AI 应用的核心交互模式。一个 streaming 处理流程:
发起请求 → 获取 ReadableStream → 逐 chunk 解析 SSE → 提取 delta text → 推送给前端这个流程在 TypeScript 里几乎不需要第三方库:
const response = await fetch('https://api.openai.com/v1/chat/completions', { ... });
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const text = decoder.decode(value);
// 解析 SSE 事件,提取 delta
}同样的逻辑在 Go 里,你需要手动管理 bufio.Scanner、处理 SSE 协议的换行符边界、协调 context.Context 的超时——代码量至少翻倍。
更重要的是,前端也需要消费 streaming。从后端收到 streaming 响应后,前端要通过 ReadableStream 或 EventSource 逐 chunk 渲染到 UI 上。如果你是 Go 后端 + React 前端的架构,streaming 的消费逻辑要在两种语言里各写一遍。全栈 TypeScript 可以直接复用 streaming 处理代码。
作为一名 Go 程序员,我起初对 npm 是不屑的。node_modules 黑洞、left-pad 事件、动辄几百个依赖——这些槽点我都认同。
但在 AI 领域,npm 的「海量」变成了绝对优势:
pdf-parse、pdfjs-dist、unstructured。@xenova/transformers 让你在 Node.js 里直接跑 HuggingFace 模型。@langchain/core 的 ChatPromptTemplate、handlebars、mustache。@ai-sdk/vue、@ai-sdk/react,10 分钟出原型。Go 的生态讲究「标准库优先,第三方尽量少」。这在基础设施领域是美德,但在需要快速集成各种新 API、新模型的 AI 应用层,是拖累。你不可能等每一个 AI 服务商都出官方 Go SDK——他们大概率只出 Python 和 TypeScript。
npm 上「装一个包就能用」的体验,对 AI 原型开发是巨大的加速器:
npm install @xenova/transformers pdf-parse @langchain/core// 几行代码在 Node.js 里本地跑一个 embedding 模型,无需 Python 环境
import { pipeline } from '@xenova/transformers';
const extractor = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
const output = await extractor('Hello world', { pooling: 'mean', normalize: true });
console.log(output.tolist()); // 向量数组,可直接写入向量库做 RAGVercel AI SDK(ai 和 @ai-sdk/openai 等包)是目前最活跃的 AI 应用层框架之一。它的设计哲学和 TypeScript 深度绑定:
LanguageModel 接口:不管你接的是 OpenAI、Anthropic、Google AI 还是开源模型,上层代码完全一致。streamText / generateText / streamObject:三个函数覆盖了 90% 的 LLM 调用场景,且 return type 精确到流/非流的每一个字段。tool() 函数:定义 function calling 的工具,类型检查贯穿调用全链路——你定义的 tool 的 input schema 类型,会传导到 LLM 返回的 tool call 的参数类型,再传导到你的 handler 函数。这种「端到端的类型安全」在 Go 生态里没有任何等价物。Go 的 AI 框架(比如 langchaingo)只能用 any / interface{} 在各个环节传数据,类型信息全部丢失。
Vercel AI SDK 把「模型无关 + 流/非流 + 工具调用」全部类型化,一个 streamText 就能覆盖主流场景:
import { openai } from '@ai-sdk/openai';
import { streamText, tool } from 'ai';
import { z } from 'zod';
const result = streamText({
model: openai('gpt-4o'),
messages: [{ role: 'user', content: '北京今天天气如何?' }],
tools: {
getWeather: tool({
description: '查询城市天气',
parameters: z.object({ city: z.string() }), // schema 即类型
execute: async ({ city }) => ({ city, temp: 26 }),
}),
},
});
// result.textStream 是一个 AsyncIterable<string>,类型贯穿全链路
for await (const delta of result.textStream) {
process.stdout.write(delta); // 逐 token 输出,类型安全
}这个原因听起来很俗,但它是无可辩驳的事实:AI 产品的用户界面,一定跑在浏览器里。浏览器只认 JavaScript(或编译到 JavaScript 的语言)。
Go + HTMX 可以做一些简单的交互,但一个需要 streaming 渲染、复杂状态管理、富文本编辑的 AI 应用(比如聊天界面、文档协作、代码生成器),前端只能是 React / Vue / Svelte。前端是 TypeScript 的地盘,那后端如果也用 TypeScript,整个团队的语言栈就是对齐的。
Python 在这个逻辑里有一个死穴:Python 能写 AI 后端,但写不了前端。所以 Python + TypeScript 的组合很常见。但 Go 同样写不了前端,而且 Go 的 AI 生态远不如 Python 成熟。这就把 Go 放在了 AI 应用开发最尴尬的位置——前端不行,后端不是首选。
前端消费 streaming 响应,用 React 的 useChat 钩子几乎是「开箱即用」,这正是 TypeScript 前后端统一红利的体现:
'use client';
import { useChat } from '@ai-sdk/react';
export function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat();
return (
<div>
{messages.map((m) => (
<div key={m.id}>{m.role === 'user' ? '你: ' : 'AI: '}{m.content}</div>
))}
<form onSubmit={handleSubmit}>
<input value={input} onChange={handleInputChange} />
</form>
</div>
);
}
// 后端只要返回 AI SDK 约定的流式协议,前端零额外解析这篇文章不是为了踩 Go 捧 TypeScript。两种语言各自有最适合的场景。TypeScript 在以下方面明显不如 Go:
视频转码、图像处理、加密计算、大规模数据聚合——这些场景 Go 的 goroutine + 多核利用是货真价实的优势。Node.js 的 worker_threads 可以用,但体验远不如 goroutine 自然。
同样是「用 4 个核并行算斐波那契」,Go 的写法天然简洁,Node.js 则要显式搬出 worker:
// Go:goroutine 开箱即用,自动利用多核
func main() {
ch := make(chan int, 4)
for i := 0; i < 4; i++ {
go func(n int) { ch <- fib(40 + n) }(i)
}
for i := 0; i < 4; i++ { <-ch }
}// TypeScript:CPU 密集任务需手动开 worker_threads,心智负担更重
import { Worker } from 'worker_threads';
function fibInWorker(n: number): Promise<number> {
return new Promise((resolve) => {
const w = new Worker(`
const { parentPort } = require('worker_threads');
parentPort.on('message', (n) => parentPort.postMessage(fib(n)));
function fib(x){return x<2?x:fib(x-1)+fib(x-2);}
`, { eval: true });
w.on('message', resolve);
w.postMessage(n);
});
}数据库、消息队列、服务网格、容器运行时、Kubernetes 生态——Go 在这里的地位无可撼动。你不可能用 TypeScript 写一个 etcd 或者 Docker。Go 的静态编译、无运行时依赖、极小内存占用,是系统软件的硬需求。
TypeScript 项目半年不维护,node_modules 可能已经有一堆安全漏洞,依赖版本锁可能已经解不开了。Go 的项目,go mod tidy + go build,大概率还能跑。Go 的向后兼容承诺和标准库的稳定性,是 TypeScript 目前还做不到的。
如果你的团队只做后端微服务、只处理 gRPC 协议、没有任何浏览器端的需求,那 TypeScript 的全栈优势对你来说没有意义。Go 在这个领域依然是极好的选择。
回到最初的问题:为什么要学 TypeScript?AI 开源项目为什么偏爱它?
我的答案是三句话:
第一,TypeScript 的类型系统是表达 API 契约的最佳载体。 联合类型、条件类型、字面量类型、模板字面量类型——这些能力让 AI SDK 的作者能够用代码精确描述「这个 API 接受什么、返回什么、有哪些分支」,而使用者能在编码时获得完整的类型护航。Go 的类型系统做不到这一点,不是因为 Go 不好,而是因为 Go 的设计目标不在这里。
第二,浏览器是 AI 产品无法绕过的终端,TypeScript 是浏览器和后端的唯一交汇点。 当你的团队用同一种语言写前端和后端,类型定义共享、streaming 代码复用、团队成员互相 review——这些工程收益是组织层面的,不是语言特性层面的。
第三,npm 的海量生态 + Vercel 等平台的生态锚定,形成了正反馈循环。 AI 服务商出 TS SDK → 开发者用 TS 写 AI 应用 → AI 框架在 TS 生态里最活跃 → 更多 AI 服务商优先出 TS SDK。这个循环已经很难打破。
如果你的技术栈像我一样以 Go 为主,我不建议你「转」TypeScript。我建议你「加」TypeScript:
type-challenges 这个开源项目是非常好的练习场。@ai-sdk/openai,写一个小的 chat 程序。亲眼看看 TypeScript 的类型系统是如何让 AI API 调用变得「丝滑」的。这种体验是看再多文章也感受不到的。落地到代码,这里给你两个「上手即见效」的练习入口:
// 练习一:type-challenges 风格的「提取函数类型的参数」
// 题目:实现类型 MyParameters<T>,拿到函数 T 的参数元组类型
type MyParameters<T extends (...args: any[]) => any> =
T extends (...args: infer P) => any ? P : never;
const fn = (a: string, b: number) => a.repeat(b);
type P = MyParameters<typeof fn>; // [string, number]
// 练习二:用 @ai-sdk/openai 写一个最小 chat,亲眼看类型护航
import { openai } from '@ai-sdk/openai';
import { generateText } from 'ai';
const { text } = await generateText({
model: openai('gpt-4o'),
prompt: '用一句话解释 TypeScript 的联合类型',
});
console.log(text); // text 的类型是 string,无需任何断言本文写于 TypeScript 5.x + Node.js 22 / Deno 2 / Bun 1 时代。技术迭代很快,文中观点可能随时间变化,但底层逻辑——类型系统的表达能力、前后端语言统一的工程价值、生态的正反馈循环——应该会是长期的趋势。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。