解锁新技能:Java 开发者如何丝滑过渡到 TypeScript -- 2
TypeScript 正在经历一场由 AI 驱动的“爆发式”增长,甚至在活跃度上已经超越了 Python,成为 GitHub 上最活跃的编程语言。 作为JAVA 开发者,有必要了解并熟悉一下typescriptTypeScript 的设计者有意借鉴了 Java、C# 等语言的一些语法元素,降低了学习曲线,但底层机制与JAVA、C#有着本质的不同在 AI Agent 的“应用层”和“工具层”开发中,
文章目录
TypeScript 正在经历一场由 AI 驱动的“爆发式”增长,甚至在活跃度上已经超越了 Python,成为 GitHub 上最活跃的编程语言。 作为JAVA 开发者,有必要了解并熟悉一下typescript
TypeScript 的设计者有意借鉴了 Java、C# 等语言的一些语法元素,降低了学习曲线,但底层机制与JAVA、C#有着本质的不同
- 是 JavaScript 的超集,底层基于原型链 (Prototype-based Inheritance)
- 倾向于函数式编程 (Functional Programming) 和声明式编程 (Declarative Programming)
- 对象创建时更倾向于用对象字面量、工厂函数等方式
- 结构化类型系统、联合类型、类型守卫、
any等特性,使其类型系统比 Java 的更加灵活和动态。但 TypeScript 的类型系统在编译成 JavaScript 后会完全消失(Type Erasure)
控制流分析CFA
类型收窄
- 控制流分析的核心机制:类型收窄,本质是一个缩小类型范围的过程
- 收窄手段:
- 类型守卫函数:自定义收窄逻辑
- type-guard functions (for anything)
- Assertion Functions【断言函数】
- 与原生逻辑的融合:直接利用 JavaScript 原生的布尔逻辑(typeof、instanceof、in)
- typeof (for primitives)
- instanceof(for classes)
- “property” in object (for objects)
- 类型守卫函数:自定义收窄逻辑
| 特性 | 原生布尔逻辑判断 | 用户自定义类型守卫 |
|---|---|---|
| 触发方式 | 使用 if、switch 等原生逻辑 |
调用你定义的特定函数 |
| 适用场景 | 简单、一次性的类型判断 | 复杂逻辑、需要复用的判断 |
| 优点 | 开箱即用,无需额外代码 | 提高代码复用性,封装复杂判断 |
| 本质 | 编译器自动推断 | 开发者显式告知编译器 |
// 1. 定义类型
type Fish = { swim: () => void };
type Bird = { fly: () => void };
// 2. 创建具体的实例 (关键:这里初始化了变量)
const myFish: any = {
swim: () => {
console.log("鱼在水里游来游去 🐟");
}
};
// 定义一个类型守卫函数
function isFish(pet: Fish | Bird): pet is Fish {
// 这里的逻辑可以非常复杂
return (pet as Fish).swim !== undefined;
}
// 使用自定义的守卫函数
if (isFish(myFish)) {
// TypeScript 知道 pet 是 Fish myFish.swim();
}
//使用javascript原生布尔逻辑判断
if ("swim" in myFish) {
myFish.swim();
}
Assertion Functions
class SuccessResponse {
constructor(public data: any) {
this.data = data;
}
}
function assertResponse(obj: unknown): asserts obj is SuccessResponse {
if (!(obj instanceof SuccessResponse)) {
throw new Error("fail");
}
}
try {
const validObj = new SuccessResponse({message: "成功"});
assertResponse(validObj);
console.log("检查通过:", validObj.data);
} catch (error) {
console.error((error as Error).message);
}
// 情况 B:传入错误的类型
try {
const invalidObj = {wrong: "data"};
assertResponse(invalidObj);
console.log(invalidObj.data);
} catch (error) {
console.error("捕获到错误:", (error as Error).message); // 输出: 捕获到错误: fail
}
短路求值
- 使用
&&(逻辑与) 进行缩小
const input: string | number = "Hello World";
// 返回11,但是如果input=42,则返回42
const inputLength = (typeof input === "string" && input.length) || input;
固定对象中的子字段的类型
const data = {
name: "you"
} as const;
console.log(data.name)
interfaces: 类型即行为
Almost everything in JavaScript is an object and interface is built to match their runtime behavior
- 用来描述对象的形状(属性和方法),能被其它对象继承
- TypeScript 的
interface是一种静态的契约,它不改变 JavaScript 的运行时本质,而是通过“观察”和“描述”对象在运行时实际拥有的属性和方法,来为动态的 JavaScript 提供类型安全。
类型系统
基础类型
JavaScript基本数据类型
| Type | Description | Example |
|---|---|---|
string |
Represents textual data. | let name: string = "Alice"; |
number |
Represents both integers and floating-point numbers. | let age: number = 30; |
boolean |
Represents logical values: true or false. |
let isActive: boolean = true; |
null |
Represents an intentional absence of value. | let data: null = null; |
undefined |
Represents a variable that has not been initialized. | let notDefined: undefined = undefined; |
The Special “Big” Primitive
Introduced in ES2020, this is used for very large numbers that exceed the safe integer limit.
bigint: Represents whole numbers larger thanNumber.MAX_SAFE_INTEGER.- Note: It is distinct from
numberand cannot be mixed with numbers in operations without conversion. - Example:
let bigNum: bigint = 100n;
- Note: It is distinct from
The Unique Identifier Primitive
symbol: Represents a unique, immutable value, often used as object property keys to avoid naming collisions.- Example:
let sym: symbol = Symbol("description");
- Example:
TypeScript特有原始类型
| Type | Description | Usage Context |
|---|---|---|
void |
Represents the absence of any value. | Used as the return type for functions that don’t return a value. |
never |
Represents values that never occur. | Used for functions that always throw an exception or have infinite loops. |
unknown |
Represents a value whose type is not known. | A safer alternative to any. You must narrow the type before using it. |
any |
Represents any JavaScript value. | Disables type checking. Use sparingly to avoid losing type safety. |
💡 Key Distinction: any vs unknown
any: Turns off TypeScript’s type checking. You can do anything with ananyvalue (e.g.,any.toFixed(),any.prop).unknown: Is type-safe. You must prove the type to TypeScript (via type guards or assertions) before you can perform operations on it.
javascript常用内置对象
- Date
- Error
- Array
- Map
- Set
- Regexp
- Promise
Type Literals
- Object:
{field:string} - Function:
(arg:number)=>string - Arrays:
string[] or Array<String> - Tuple:
[string,number]
调用签名 (): ReturnType
描述一个对象(通常是函数)被直接调用时的行为。
interface RandomNumberGenerator {
seed: number; // 它还可以有其他属性
(): number; // 调用它就返回一个随机数
}
const randomGen: RandomNumberGenerator = Object.assign(
function (): number {
return Math.random();
},
{seed: Date.now()}
);
// randomGen.seed = 12345; // 可以访问属性
const num = randomGen(); // 可以调用
console.log(num, randomGen.seed);
构造签名 new(s: string): InstanceType
描述一个类(或构造函数)被 new 关键字调用时的行为
构造签名示例
const UserFactory: ClassFactory<User> = User;
这行代码巧妙地利用了 TypeScript 的类型兼容性规则,将一个类(User)本身作为值,赋给了一个描述构造函数类型(ClassFactory<User>)的变量。这使得 UserFactory 变量可以像 User 类一样被 new 调用,从而实现了一种“类工厂”的效果。
interface ClassFactory<T> {
new(...args: any[]): T; // 可以接收任意参数,返回类型 T 的实例
}
// 使用它来约束一个类
class User {
constructor(public name: string, public age: number) {
}
}
// 定义一个工厂,它符合 ClassFactory<User> 的要求
const UserFactory: ClassFactory<User> = User;
// 使用工厂创建实例
const user = new UserFactory("Alice", 30);
console.log(user); // { name: 'Alice', age: 30 }
调用签名和构造签名示例
// 定义一个接口,同时包含调用签名和构造签名
interface MixedObject {
// 构造签名:new 时接收一个字符串,返回一个实例
new(s: string): MixedObjectInstance;
// 调用签名:直接调用时,也接收一个字符串,返回一个实例
(s: string): MixedObjectInstance;
}
// 定义实例的类型
class MixedObjectInstance {
constructor(public message: string) {
}
}
// 实现这个接口:通常用一个函数来实现,因为它既可以被 new,也可以被直接调用
function MixedObjectImpl(s: string): MixedObjectInstance {
// 检查是被 new 调用还是直接调用
if (new.target) {
// 如果是 new 调用,new.target 会是 MixedObjectImpl console.log("Called with new");
return new MixedObjectInstance(s);
} else {
// 如果是直接调用
console.log("Called without new");
return new MixedObjectInstance(s);
}
}
// 将函数类型断言为接口类型
const MixedObject: MixedObject = MixedObjectImpl as MixedObject;
// 设置原型链,使 new 能正确工作
MixedObject.prototype = MixedObjectInstance.prototype;
// 使用
const instance1 = new MixedObject("via new"); // 输出 "Called with new"console.log(instance1.message); // via new
const instance2 = MixedObject("via call"); // 输出 "Called without new"console.log(instance2.message); // via call
索引签名[key: string]: Type
TypeScript 支持 [key: string]: number 这种 索引签名(Index Signature) 语法,其核心目的非常明确: 为了填补“静态类型检查”与“JavaScript 动态特性”之间的鸿沟
为了让你能够安全地拥抱 JavaScript 的动态性。它让你在面对“未知”时,依然能划定 “类型边界”,防止代码变得不可控。
interface MyObj {
// 明确声明的属性
knownProp: string;
// 索引签名:允许任意其他字符串属性,但值必须是 string
[key: string]: string;
}
const obj: MyObj = {
knownProp: "张三", // 明确声明的属性,OK
dynamicA: "2", // 未声明的动态属性,OK (因为值是 number) dynamicB: "3" // 未声明的动态属性,OK (因为值是 number)};
console.log(obj)
声明合并(Declaration Merging)
当你在同一个作用域内,多次使用 interface 关键字定义同一个名字的接口时,TypeScript 会自动将这些定义合并成一个单一的接口。这个最终的接口会包含所有声明中定义的成员。
TypeScript 允许你通过多次声明同一个 interface 来逐步构建它的形状,所有声明会被自动合并成一个完整的接口。这是一种安全、灵活的类型扩展机制,尤其在增强第三方库类型时非常有用。
// 第一次声明
interface User {
name: string;
}
// 第二次声明(同名)
interface User {
age: number;
}
// 第三次声明(同名)
interface User {
email: string;
}
// 最终效果:User 接口包含了所有属性
const user: User = {
name: "Alice",
age: 30,
email: "alice@example.com"
}; // ✅ 完全合法
console.log(user)
declare global {
namespace Express {
interface Request {
user?: { id: string; name: string };
}
}
}
实际应用场景
这个特性在以下场景中非常有用:
1. 扩展现有库的类型定义
- 当你使用第三方库(如 jQuery、Express)时,它的类型定义文件(
.d.ts)可能没有包含某个插件添加的方法。 - 你可以通过再次声明同名接口来安全地扩展它,而无需修改原库的代码。
2. 模块化定义大型接口
- 对于非常复杂的接口(比如一个大型配置对象),你可以把它拆分成多个小的
interface声明,放在不同的文件或逻辑块中,最后它们会自动合并。
3. 条件性地添加属性(配合条件类型)
- 虽然不常用,但可以在不同条件下声明同名接口来动态构建类型。
注意事项
- 只适用于
interface:type、class、enum、function都不能通过这种方式合并(函数和命名空间有自己特殊的合并规则)。 - 作用域必须相同:只有在同一个作用域(如同一个文件或通过
declare global在全局作用域)内的同名interface才会合并。 - 方法和属性会累加:如果多次声明中有同名的属性或方法,它们的类型必须兼容,否则会报错。
Types
type 与 interface 针对对象形状定义:两者几乎等价, 但type 的表达能力更强大
- 原始类型别名
type ID = string; - Object Literal Type:
type Location = { x: number, y: number } - 联合类型(Union):
type Status = "success" | "error" | "loading"; - 元组Tuple:
type Point = [number, number];type Data = [location: Location, timestamp: string]
- 交叉类型
&:type Dog = Animal & { bark(): void }; - Type from Func Return:
type xx=ReturnType<typeof xxFunc>; - Type from module:
type data:import("./data").data - Type from indexing:
type Response = {data:{...}};type Data=Response["data"]; - Type from Value:
const data={};type Data=typeof data - 条件类型:
type IsString<T> = T extends string ? true : false; - 映射类型(Mapped Types):
type Readonly<T> = { readonly [K in keyof T]: T[K] }; - Template Union Type:
| 特性 | 作用 | 示例用途 |
|---|---|---|
| 条件类型 | 根据类型关系选择类型 | 提取返回值、过滤联合类型、类型守卫增强 |
| 映射类型 | 遍历键生成新类型 | 将属性设为可选/只读/可变、深度 Partial、Immutable 工具 |
条件类型示例
// 条件类型:如果是函数类型,则返回其返回值类型;否则返回 never
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
// 测试函数
function fetchData(): string {
return "Hello";
}
function add(a: number, b: number): number {
return a + b;
}
const value1: GetReturnType<typeof fetchData> = "Hello"; // ✅ stringconst value2: GetReturnType<typeof add> = 42; // ✅ number
console.log(value1, value2); // 运行时输出:Hello 42
映射类型示例
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
//以下写法也正确
//-readonly [K in keyof T]: (a: T[K]) => void;
};
type Point = {
readonly x: number;
readonly y: number;
};
const p: Mutable<Point> = {x: 10, y: 20};
p.x = 15; // ✅ 可修改
console.log(p);
Template Union Type
模板联合类型让 字符串字面量的组合变得类型安全且可预测,是构建健壮前端/后端类型系统的重要工具。
type EventName = 'click' | 'hover' | 'focus';
type EventType = 'on' | 'off';
type HandlerName = `${EventType}${Capitalize<EventName>}`;
// 使用
const handler: HandlerName = "onClick"; // ✅
console.log(handler);
type Size = 'small' | 'medium' | 'large';
type Variant = 'primary' | 'secondary';
type ClassName = `btn--${Size}-${Variant}`;
const className: ClassName = "btn--large-secondary"; // ✅
console.log(className)
何时用 interface?何时用 type?
尽可能使用 interface,直到你需要 type 的功能。interface 是“可扩展的对象契约”,type 是“任意类型的别名”
用 interface 描述“是什么”,用 type 表达“可以是哪些”
| 场景 | 推荐 |
|---|---|
| 定义对象/函数的公共 API(如组件 props、API 响应) | ✅ interface(可扩展、更面向对象) |
| 需要声明合并(如扩展现有库类型) | ✅ interface |
| 定义联合类型、元组、字符串字面量类型 | ✅ type |
| 使用映射类型、条件类型等高级特性 | ✅ type |
| 团队约定统一风格 | 保持一致即可 |
更多推荐
所有评论(0)