TypeScript:让JavaScript喝上一杯严谨的茶
TypeScript 就像给 JavaScript 泡了一杯严谨的茶,既保留了原有的灵活风味,又添了几分静态类型的醇厚。它让代码在运行前就能暴露潜在问题,同时提供了更智能的工具支持,像是给狂野的 JavaScript 系上了一条安全带。
TypeScript 的类型系统:从"随心所欲"到"约法三章"
JavaScript 开发者都熟悉这种场景:
javascript
function add(a, b) {
return a + b;
}
console.log(add(1, 2)); // 3
console.log(add('1', '2')); // '12'
同样的函数,输入数字返回求和,输入字符串却变成拼接。TypeScript 通过类型注解解决了这个问题:
typescript
function add(a: number, b: number): number {
return a + b;
}
add(1, 2); // 正确
add('1', '2'); // 编译时报错
类型系统不仅限于基本类型,还能定义复杂结构:
typescript
interface User {
id: number;
name: string;
email?: string; // 可选属性
}
function registerUser(user: User) {
// 实现逻辑
}
registerUser({ id: 1, name: 'Alice' }); // 正确
registerUser({ name: 'Bob' }); // 错误:缺少必需的id属性
高级类型:类型系统的瑞士军刀
TypeScript 的真正威力在于其高级类型系统。比如这个处理API响应的例子:
typescript
type ApiResponse<T> = {
data: T;
status: number;
error?: string;
};
type UserProfile = {
id: string;
username: string;
avatar: string;
};
async function fetchUserProfile(userId: string): Promise<ApiResponse<UserProfile>> {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}
// 使用时能获得完整的类型提示
const result = await fetchUserProfile('123');
if (result.error) {
console.error(result.error);
} else {
console.log(result.data.username); // 类型安全访问
}
联合类型和类型守卫让代码更健壮:
typescript
type Shape =
| { kind: 'circle'; radius: number }
| { kind: 'square'; size: number }
| { kind: 'rectangle'; width: number; height: number };
function area(shape: Shape): number {
switch (shape.kind) {
case 'circle':
return Math.PI * shape.radius ** 2;
case 'square':
return shape.size ** 2;
case 'rectangle':
return shape.width * shape.height;
default:
// TypeScript会确保所有情况都被处理
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}
泛型:编写可复用的类型安全代码
泛型让组件既能保持灵活性又不失类型安全。看看这个React组件示例:
typescript
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{renderItem(item)}</li>
))}
</ul>
);
}
// 使用时
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
<UserList
items={users}
renderItem={(user) => <span>{user.name}</span>}
/>;
// 自动推断user类型为{ id: number; name: string }
装饰器:给代码加点"语法糖"
TypeScript 装饰器为类和类成员添加了声明式语法:
typescript
function logExecutionTime(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const end = performance.now();
console.log(`${propertyKey} executed in ${(end - start).toFixed(2)}ms`);
return result;
};
return descriptor;
}
class Calculator {
@logExecutionTime
static fibonacci(n: number): number {
if (n <= 1) return n;
return Calculator.fibonacci(n - 1) + Calculator.fibonacci(n - 2);
}
}
Calculator.fibonacci(10); // 控制台会输出执行时间
类型推断与类型体操
TypeScript 的类型推断能力常常令人惊喜。看看这个条件类型示例:
typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<123>; // false
// 更实用的例子:提取Promise的返回值类型
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
type C = UnpackPromise<Promise<string>>; // string
type D = UnpackPromise<number>; // number
映射类型可以批量转换接口属性:
typescript
interface Person {
name: string;
age: number;
email: string;
}
type ReadonlyPerson = Readonly<Person>;
// 等同于:
// {
// readonly name: string;
// readonly age: number;
// readonly email: string;
// }
type PartialPerson = Partial<Person>;
// 等同于:
// {
// name?: string;
// age?: number;
// email?: string;
// }
与现代JavaScript生态的无缝集成
TypeScript 对现代JavaScript特性的支持非常全面。比如这个使用可选链和空值合并的例子:
typescript
interface Company {
name: string;
address?: {
street?: string;
city?: string;
};
}
function getCompanyCity(company: Company): string {
return company.address?.city ?? '未知城市';
}
const company1 = { name: 'Acme' };
console.log(getCompanyCity(company1)); // "未知城市"
const company2 = {
name: 'Globex',
address: { city: 'New York' }
};
console.log(getCompanyCity(company2)); // "New York"
工程化优势:不只是类型检查
TypeScript 的工程化特性让大型项目维护更轻松。比如路径别名:
json
// tsconfig.json
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}
然后在代码中可以这样使用:
typescript
import { Button } from '@components/Button';
import { formatDate } from '@utils/date';
项目引用(Project References)让monorepo管理更轻松:
json
// tsconfig.json
{
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/ui" }
]
}
类型声明文件:与JavaScript世界和平共处
即使不使用TypeScript编写的库,也能通过声明文件获得类型支持。比如为旧版jQuery添加类型:
typescript
// @types/jquery/index.d.ts
declare interface JQuery {
modal(action: 'show' | 'hide'): void;
carousel(options?: any): void;
}
declare var $: {
(selector: string): JQuery;
ajax(url: string, settings?: any): Promise<any>;
};
对于现代npm包,可以直接安装@types包:
bash
npm install --save-dev @types/lodash
然后就能享受完整的类型提示:
typescript
import { debounce, throttle } from 'lodash';
const debouncedFn = debounce(() => {
console.log('Resized!');
}, 200);
window.addEventListener('resize', debouncedFn);
从JavaScript迁移的渐进式路径
TypeScript 允许逐步迁移现有JavaScript项目。可以从重命名文件开始:
// 把 .js 文件改为 .ts 或 .tsx
// 先设置 allowJs: true 和 checkJs: false
然后逐步添加类型注解。对于复杂的现有代码,可以使用any类型暂时绕过类型检查:
typescript
// 迁移初期可以这样
const legacyData: any = getLegacyData();
// 后期再逐步替换为具体类型
JSDoc注释也能被TypeScript识别,实现渐进增强:
javascript
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
function add(a, b) {
return a + b;
}
TypeScript的未来:持续演进的类型系统
每个TypeScript版本都会带来新特性。比如4.9引入的satisfies操作符:
typescript
const colors = {
red: '#ff0000',
green: '#00ff00',
blue: '#0000ff'
} satisfies Record<string, `#${string}`>;
// 既验证了值格式,又保留了字面量类型
5.0引入的装饰器新标准:
typescript
@registerComponent
class MyComponent {
@observable
count = 0;
@action
increment() {
this.count++;
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn