本文深入探讨 TypeScript 类型系统的图灵完备性,涵盖从高级泛型、条件类型、模板字面量类型到元编程(装饰器)和 React 高级模式。
内容分为几个核心模块,每个模块都包含详细的理论解释和生产级代码示例。
模块一:高级泛型与类型体操基础
在高级开发中,泛型不仅仅是 <T>,它涉及到约束(Constraints)、默认值和递归。
1.1 深度递归类型与对象属性修饰
我们需要处理复杂的不可变数据结构或深度可选配置。
ts
/**
* 模块一:高级泛型工具类型
* 场景:复杂状态管理、配置对象处理
*/
// 1. DeepPartial: 递归地将对象所有属性变为可选
// 这在处理大型配置对象(如 Webpack 配置或图表库配置)时非常有用
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
// 2. DeepReadonly: 递归地将对象所有属性变为只读
// 用于 Redux 的 State 或任何不可变数据结构
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
// 3. DeepRequired: 递归移除可选属性
type DeepRequired<T> = {
[P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};
// 4. Mutable: 移除只读属性
type Mutable<T> = {
-readonly [P in keyof T]: T[P];
};
// --- 测试案例 ---
interface UserSettings {
theme: {
mode: 'dark' | 'light';
palette: {
primary: string;
secondary?: string;
};
};
notifications: {
email: boolean;
sms?: boolean;
};
}
// 使用 DeepPartial,允许只配置部分层级
const initialConfig: DeepPartial<UserSettings> = {
theme: {
palette: {
primary: '#007bff'
}
}
};
// 使用 DeepReadonly 保护状态
const state: DeepReadonly<UserSettings> = {
theme: {
mode: 'dark',
palette: { primary: '#000', secondary: '#fff' }
},
notifications: { email: true, sms: false }
};
// state.theme.mode = 'light'; // 报错:无法分配到 "mode" ,因为它是只读属性。
// --- 进阶:复杂键值过滤 ---
// 获取类型中所有非函数属性的 Key
type NonFunctionKeys<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
// 获取类型中所有函数属性的 Key
type FunctionKeys<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
// 仅提取对象中的数据部分
type PickData<T> = Pick<T, NonFunctionKeys<T>>;
// 仅提取对象中的方法部分
type PickMethods<T> = Pick<T, FunctionKeys<T>>;
class UserService {
id: number = 1;
name: string = "Admin";
login(): void { console.log("Logging in..."); }
logout(): void { console.log("Logging out..."); }
updateProfile(data: PickData<UserService>): void {
this.name = data.name;
}
}
const service = new UserService();
const dataOnly: PickData<UserService> = { id: 2, name: "User" };
// const methodOnly: PickMethods<UserService> = { login: () => {} }; // 正确
模块二:条件类型(Conditional Types)与 infer 关键字
这是 TypeScript 类型编程的核心。通过 infer,我们可以"解包"类型,例如获取 Promise 的返回值、数组的元素类型或函数的参数类型。
2.1 类型推断与解包工具
ts
/**
* 模块二:条件类型与 infer
* 场景:API 响应处理、函数包装器、Vue/React 类型推导
*/
// 1. Unpacked: 解包数组、Promise 或基本类型
type Unpacked<T> = T extends (infer U)[]
? U
: T extends Promise<infer U>
? U
: T;
// 测试 Unpacked
type T0 = Unpacked<string>; // string
type T1 = Unpacked<string[]>; // string
type T2 = Unpacked<Promise<string>>; // string
type T3 = Unpacked<Promise<string>[]>; // Promise<string>
// 2. GetReturnType: 获取函数返回类型 (内置 ReturnType 的实现原理)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
// 3. GetParameters: 获取函数参数类型 (内置 Parameters 的实现原理)
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
// --- 实战:类型安全的 API 请求封装 ---
interface ApiResponse<TData> {
code: number;
message: string;
data: TData;
}
// 模拟后端 API 函数
function getUserInfo(userId: string): Promise<ApiResponse<{ name: string; age: number }>> {
return Promise.resolve({
code: 200,
message: "success",
data: { name: "Alice", age: 30 }
});
}
function getPosts(): Promise<ApiResponse<{ title: string }[]>> {
return Promise.resolve({
code: 200,
message: "success",
data: [{ title: "TS is awesome" }]
});
}
// 高级工具:提取 API 函数 Promise 解析后的 Data 部分
// 逻辑:
// 1. T 必须是一个函数
// 2. 函数返回 Promise<ApiResponse<Data>>
// 3. 提取 Data
type ExtractApiData<T extends (...args: any) => any> =
ReturnType<T> extends Promise<ApiResponse<infer Data>> ? Data : never;
// 自动推导出的类型
type UserData = ExtractApiData<typeof getUserInfo>; // { name: string; age: number }
type PostsData = ExtractApiData<typeof getPosts>; // { title: string }[]
// 通用处理函数,自动保持类型
async function apiHandler<TFunc extends (...args: any) => any>(
apiFunc: TFunc,
...args: Parameters<TFunc>
): Promise<ExtractApiData<TFunc>> {
const response = await apiFunc(...args);
// 这里假设 response 结构符合 ApiResponse
// 在实际项目中通常会有拦截器处理
if ((response as any).code === 200) {
return (response as any).data;
}
throw new Error((response as any).message);
}
// 使用示例
async function main() {
// user 自动被推断为 { name: string; age: number }
const user = await apiHandler(getUserInfo, "123");
console.log(user.name);
// posts 自动被推断为 { title: string }[]
const posts = await apiHandler(getPosts);
console.log(posts[0].title);
}
模块三:模板字面量类型(Template Literal Types)
TS 4.1 引入的特性,允许对字符串进行模式匹配和操作。这在处理 CSS 类名、事件名称或路由路径时非常强大。
ts
/**
* 模块三:模板字面量类型
* 场景:CSS-in-JS、事件系统、国际化键值生成
*/
// 1. 简单的字符串拼接
type Color = "red" | "blue";
type Quantity = "100" | "200";
type ItemCssClass = `bg-${Color}-${Quantity}`;
// "bg-red-100" | "bg-red-200" | "bg-blue-100" | "bg-blue-200"
// 2. 事件名称生成器
type Entity = "User" | "Product" | "Order";
type Operation = "Create" | "Update" | "Delete";
// 自动生成所有可能的事件名
type EventName = `${Entity}${Operation}`;
// "UserCreate" | "UserUpdate" | ... | "OrderDelete"
// --- 实战:类型安全的事件总线 (Event Bus) ---
// 定义事件负载映射
type EventPayloads = {
"user:login": { userId: string; time: number };
"user:logout": void;
"modal:open": { title: string; content: string };
"modal:close": void;
};
// 字符串操作工具类型
type Split<S extends string, D extends string> =
string extends S ? string[] :
S extends '' ? [] :
S extends `${infer T}${D}${infer U}` ? [T, ...Split<U, D>] : [S];
// 测试 Split
type Parts = Split<"user:login:success", ":">; // ["user", "login", "success"]
class TypedEventBus {
private listeners: { [K in keyof EventPayloads]?: ((payload: EventPayloads[K]) => void)[] } = {};
// on 方法:EventName 必须是 EventPayloads 的键,cb 参数自动推导
on<K extends keyof EventPayloads>(eventName: K, cb: (payload: EventPayloads[K]) => void) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName]!.push(cb);
}
// emit 方法:payload 类型必须匹配
emit<K extends keyof EventPayloads>(eventName: K, payload: EventPayloads[K]) {
const callbacks = this.listeners[eventName];
if (callbacks) {
callbacks.forEach(cb => cb(payload));
}
}
}
const bus = new TypedEventBus();
// 类型安全:payload 自动推断为 { userId: string; time: number }
bus.on("user:login", (payload) => {
console.log(`User ${payload.userId} logged in at ${payload.time}`);
});
// 类型报错演示
// bus.emit("user:login", { userId: 123 }); // Error: Type 'number' is not assignable to type 'string'.
bus.emit("user:login", { userId: "u_001", time: Date.now() }); // OK
// --- 进阶:CSS Padding/Margin 简写类型检查 ---
type SizeUnit = "px" | "em" | "rem" | "%";
type SizeValue = `${number}${SizeUnit}` | "0" | "auto";
// 允许 1 到 4 个值
type CSSPadding =
| SizeValue
| `${SizeValue} ${SizeValue}`
| `${SizeValue} ${SizeValue} ${SizeValue}`
| `${SizeValue} ${SizeValue} ${SizeValue} ${SizeValue}`;
function setPadding(el: HTMLElement, padding: CSSPadding) {
el.style.padding = padding;
}
// setPadding(document.body, "10px"); // OK
// setPadding(document.body, "10px 20px"); // OK
// setPadding(document.body, "10px 20px 0 5%"); // OK
// setPadding(document.body, "10"); // Error: 缺少单位
// setPadding(document.body, "red"); // Error
模块四:装饰器(Decorators)与元编程
虽然装饰器在 ECMAScript 中仍处于演进阶段,但在 TypeScript(尤其是 Angular 和 NestJS 生态)中应用广泛。这里展示基于 TypeScript 实验性装饰器的高级用法。
ts
/**
* 模块四:装饰器与 AOP (面向切面编程)
* 注意:需要在 tsconfig.json 中开启 "experimentalDecorators": true
*/
// 1. 方法装饰器:日志记录与性能监控
function LogPerformance(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const start = performance.now();
console.log(`[Starting] ${propertyKey} with args:`, JSON.stringify(args));
try {
const result = await originalMethod.apply(this, args);
const end = performance.now();
console.log(`[Finished] ${propertyKey} in ${(end - start).toFixed(2)}ms`);
return result;
} catch (error) {
console.error(`[Error] ${propertyKey} failed:`, error);
throw error;
}
};
return descriptor;
}
// 2. 参数装饰器与方法装饰器配合:参数校验
import "reflect-metadata"; // 需要 npm install reflect-metadata
const REQUIRED_METADATA_KEY = Symbol("required");
// 参数装饰器:标记哪个参数是必填的
function Required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
let existingRequiredParameters: number[] = Reflect.getOwnMetadata(REQUIRED_METADATA_KEY, target, propertyKey) || [];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata(REQUIRED_METADATA_KEY, existingRequiredParameters, target, propertyKey);
}
// 方法装饰器:执行校验逻辑
function Validate(target: any, propertyName: string, descriptor: PropertyDescriptor) {
let method = descriptor.value;
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(REQUIRED_METADATA_KEY, target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] === undefined || arguments[parameterIndex] === null) {
throw new Error(`Missing required argument at index ${parameterIndex} for method ${propertyName}`);
}
}
}
return method.apply(this, arguments);
}
}
// 3. 类装饰器:依赖注入 (简易版 IOC 容器)
type Constructor<T = any> = new (...args: any[]) => T;
const ServiceMap = new Map<string, any>();
function Service(name: string) {
return function(constructor: Constructor) {
console.log(`Registering service: ${name}`);
ServiceMap.set(name, new constructor());
}
}
function Inject(serviceName: string) {
return function(target: any, propertyKey: string) {
// 在属性被访问时懒加载注入
Object.defineProperty(target, propertyKey, {
get: () => {
const service = ServiceMap.get(serviceName);
if (!service) throw new Error(`Service ${serviceName} not found`);
return service;
},
enumerable: true,
configurable: true
});
}
}
// --- 综合应用 ---
@Service("Logger")
class LoggerService {
log(msg: string) { console.log(`[LOG SERVICE]: ${msg}`); }
}
class UserController {
@Inject("Logger")
private logger!: LoggerService;
@Validate
@LogPerformance
async createUser(@Required name: string, @Required email: string, age?: number) {
// 模拟异步操作
await new Promise(resolve => setTimeout(resolve, 100));
this.logger.log(`Creating user: ${name} (${email})`);
return { id: Math.floor(Math.random() * 1000), name, email };
}
}
// 测试运行
async function runDecorators() {
const controller = new UserController();
try {
await controller.createUser("Bob", "bob@example.com"); // 成功
// await controller.createUser("Alice", null as any); // 抛出 Error: Missing required argument
} catch (e) {
console.error(e);
}
}
// runDecorators();
模块五:React 高级模式与多态组件
在前端开发中,React 的类型定义尤为重要。我们将构建一个多态组件(Polymorphic Component),它可以根据 as 属性改变渲染的 HTML 标签,同时保持类型安全。
ts
/**
* 模块五:React 高级类型模式
* 场景:组件库开发、高阶组件 (HOC)
*/
import React from 'react';
// 1. 多态组件 (Polymorphic Components)
// 这是一个非常高级的模式,允许 <Text as="h1"> 或 <Text as="a" href="...">
// 获取元素自身的 Props,排除我们要重写的 'as'
type AsProp<C extends React.ElementType> = {
as?: C;
};
type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);
// 这是一个复杂的类型定义:
// 1. 接收泛型 C (组件类型) 和 Props
// 2. 合并传入的 Props 和 as 属性
// 3. 合并 HTML 原生属性 (Omit 掉重复的)
type PolymorphicComponentProps<C extends React.ElementType, Props = {}> =
React.PropsWithChildren<Props & AsProp<C>> &
Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;
// 定义组件类型
type PolymorphicRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>["ref"];
type PolymorphicComponentPropWithRef<C extends React.ElementType, Props = {}> =
PolymorphicComponentProps<C, Props> & { ref?: PolymorphicRef<C> };
// 实现组件
type TextProps = {
color?: 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
};
// 使用 React.forwardRef 实现
// 注意:由于泛型组件和 forwardRef 的结合在 TS 中比较棘手,通常需要类型断言
export const Text = React.forwardRef(
<C extends React.ElementType = "span">(
{ as, color = 'primary', size = 'md', children, ...rest }: PolymorphicComponentProps<C, TextProps>,
ref: PolymorphicRef<C>
) => {
const Component = as || "span";
const style = {
color: color === 'danger' ? 'red' : 'black',
fontSize: size === 'lg' ? '20px' : '14px'
};
return (
<Component ref={ref} style={style} {...rest}>
{children}
</Component>
);
}
) as <C extends React.ElementType = "span">(
props: PolymorphicComponentPropWithRef<C, TextProps>
) => React.ReactElement | null;
// --- 使用示例 ---
function App() {
return (
<div>
{/* 渲染为 span (默认) */}
<Text>Hello World</Text>
{/* 渲染为 h1 */}
<Text as="h1" size="lg">Title</Text>
{/* 渲染为 a 标签,TS 会自动提示 href 属性是必须的/可用的 */}
<Text as="a" href="https://google.com" color="danger">
Link
</Text>
{/* 错误演示:div 没有 href 属性 */}
{/* <Text as="div" href="..." /> // Error: Property 'href' does not exist on type... */}
</div>
);
}
// 2. 类型安全的 Render Props
interface ListProps<T> {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
}
// 使用泛型组件定义
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item, index)}</li>
))}
</ul>
);
}
// 使用
// <List
// items={[{ id: 1, name: 'A' }, { id: 2, name: 'B' }]}
// renderItem={(item) => <span>{item.name}</span>} // item 自动推断为对象
// />
模块六:混合(Mixins)与类的组合
TypeScript 支持 Mixin 模式,允许我们将多个类的功能组合到一个类中。这在构建复杂的 UI 组件库(如 Material CDK)时非常常见。
ts
/**
* 模块六:Mixins
* 场景:多重继承模拟、功能组合
*/
// 定义构造函数类型
type Constructor<T = {}> = new (...args: any[]) => T;
// 1. Activatable Mixin: 添加激活/未激活状态
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActivated: boolean = false;
activate() {
this.isActivated = true;
console.log("Activated!");
}
deactivate() {
this.isActivated = false;
console.log("Deactivated!");
}
};
}
// 2. Disposable Mixin: 添加资源清理功能
function Disposable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
private disposables: (() => void)[] = [];
addDisposable(cb: () => void) {
this.disposables.push(cb);
}
dispose() {
this.disposables.forEach(cb => cb());
this.disposables = [];
console.log("Resources disposed.");
}
};
}
// 3. Timestamped Mixin: 记录创建时间
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
timestamp = new Date();
};
}
// --- 组合使用 ---
class BaseComponent {
name: string;
constructor(name: string) {
this.name = name;
}
render() {
console.log(`Rendering ${this.name}`);
}
}
// 创建一个拥有所有能力的类
// 注意:Mixin 的应用顺序会影响类的层级
const SmartComponent = Timestamped(Disposable(Activatable(BaseComponent)));
// 演示
const comp = new SmartComponent("MyButton");
comp.render(); // 来自 BaseComponent
comp.activate(); // 来自 Activatable
console.log(`Created at: ${comp.timestamp}`); // 来自 Timestamped
comp.addDisposable(() => console.log("Cleaning up event listeners..."));
comp.dispose(); // 来自 Disposable
模块七:高级类型守卫与断言
运行时类型检查对于前端应用的健壮性至关重要。
ts
/**
* 模块七:类型守卫 (Type Guards) 与断言
* 场景:处理联合类型、运行时数据验证
*/
// 1. 区分联合类型 (Discriminated Unions)
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
type Shape = Square | Rectangle | Circle;
// 计算面积:利用 switch 的类型收窄
function area(s: Shape): number {
switch (s.kind) {
case "square":
// 在这里 s 被收窄为 Square
return s.size * s.size;
case "rectangle":
// 在这里 s 被收窄为 Rectangle
return s.width * s.height;
case "circle":
return Math.PI * s.radius ** 2;
default:
// 穷尽性检查 (Exhaustiveness checking)
// 如果未来添加了新形状但没处理,这里会报错
const _exhaustiveCheck: never = s;
return _exhaustiveCheck;
}
}
// 2. 用户自定义类型守卫 (User-Defined Type Guards)
// 返回值类型必须是 `arg is Type`
function isString(value: unknown): value is string {
return typeof value === "string";
}
function isDate(value: unknown): value is Date {
return value instanceof Date;
}
// 复杂对象的类型守卫
interface ApiError {
error: true;
code: number;
details: string;
}
interface ApiSuccess<T> {
error: false;
data: T;
}
type ApiResult<T> = ApiSuccess<T> | ApiError;
function isSuccess<T>(result: ApiResult<T>): result is ApiSuccess<T> {
return result.error === false;
}
// 使用
function handleApiResult(result: ApiResult<string>) {
if (isSuccess(result)) {
// TS 知道这里是 ApiSuccess,可以安全访问 .data
console.log(result.data.toUpperCase());
} else {
// TS 知道这里是 ApiError
console.error(`Error ${result.code}: ${result.details}`);
}
}
// 3. 断言签名 (Assertion Signatures) - TS 3.7+
// 类似于 Node.js 的 assert 模块
function assertIsNumber(val: any): asserts val is number {
if (typeof val !== "number") {
throw new Error("Not a number!");
}
}
function double(input: any) {
assertIsNumber(input);
// 此行之后,input 被视为 number
return input * 2;
}
模块八:类型体操挑战(Type Gymnastics)
为了展示 TS 的极限能力,我们实现一些类似 Lodash 函数的类型版本。
ts
/**
* 模块八:类型体操
* 场景:库作者、极度复杂的类型推导
*/
// 1. 实现 Join 类型 (Array.join 的类型版)
type Join<T extends any[], U extends string | number> =
T extends [infer F, ...infer R]
? R['length'] extends 0
? `${F & string}`
: `${F & string}${U}${Join<R, U>}`
: "";
type Path = Join<['users', 'id', 'posts'], '/'>; // "users/id/posts"
// 2. 实现 DeepValue (根据路径获取深度属性值)
// 类似于 lodash.get(obj, "a.b.c")
type GetPropType<T, P extends string> =
P extends keyof T
? T[P]
: P extends `${infer K}.${infer R}`
? K extends keyof T
? GetPropType<T[K], R>
: never
: never;
// 测试 DeepValue
interface Config {
app: {
database: {
host: string;
port: number;
}
}
}
type DBHost = GetPropType<Config, "app.database.host">; // string
type DBPort = GetPropType<Config, "app.database.port">; // number
type Invalid = GetPropType<Config, "app.database.password">; // never
// 3. 元组转对象
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const;
type TupleToObject<T extends readonly any[]> = {
[P in T[number]]: P;
};
type CarModelObj = TupleToObject<typeof tuple>;
// { tesla: "tesla", "model 3": "model 3", ... }
// 4. 柯里化函数的类型推导 (Currying)
type Curry<P extends any[], R> =
(arg: Head<P>) => HasTail<P> extends true ? Curry<Tail<P>, R> : R;
type Head<T extends any[]> = T extends [any, ...any[]] ? T[0] : never;
type Tail<T extends any[]> = ((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
// 这是一个简化的定义,实际柯里化类型非常复杂
declare function curry<P extends any[], R>(f: (...args: P) => R): Curry<P, R>;
const add = (a: number, b: number, c: number) => a + b + c;
const curriedAdd = curry(add);
// const sum = curriedAdd(1)(2)(3); // 理想情况下推导出 number
模块九:声明合并与模块扩展 (Module Augmentation)
在扩展第三方库(如给 Window 对象添加属性,或扩展 React 的 Theme)时必不可少。
ts
/**
* 模块九:模块扩展
* 场景:全局变量、第三方库补丁
*/
// 1. 扩展全局 Window 对象
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION__: any;
Analytics: {
track: (event: string) => void;
};
}
// 扩展 String 原型
interface String {
toTitleCase(): string;
}
}
// 实现扩展的方法
String.prototype.toTitleCase = function() {
return this.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
};
// 使用
// window.Analytics.track("Page View");
// const title = "hello world".toTitleCase();
// 2. 扩展第三方库 (以 styled-components 为例)
// 假设这是一个名为 'my-theme-lib' 的库
// import 'my-theme-lib';
// declare module 'my-theme-lib' {
// export interface DefaultTheme {
// borderRadius: string;
// colors: {
// main: string;
// secondary: string;
// };
// }
// }
总结与最佳实践
建议:
- 不要过度设计:虽然泛型和条件类型很强大,但如果一个类型定义超过了 10 行且难以阅读,考虑简化它或添加详细注释。
- 利用
unknown代替any:在不确定类型时,unknown强制你在使用前进行类型检查,比any安全得多。 - 优先使用接口 (Interface) 定义对象:接口支持声明合并,对库的扩展性更好;类型别名 (Type Alias) 更适合联合类型和元组。
- 严格模式 :始终开启
strict: true,特别是strictNullChecks,这能避免 80% 的运行时错误。