跳到主要内容

2.4 TypeScript 与 C# 类型系统

本章目标: 给任务数据和函数加类型,知道 TypeScript 能保证什么、不能保证什么。

类型是开发时约束​

src/types/task.ts
export interface TaskItem {
id: number;
title: string;
isDone: boolean;
}

export type TaskFilter = 'all' | 'pending' | 'done';

interface 描述对象结构;type 可以定义联合类型等别名。编译为 JavaScript 后这些类型声明会被擦除。

TypeScript 主要采用结构类型:对象有符合要求的结构即可兼容,不必像 C# 那样显式声明实现接口。类的私有成员等情况还有额外规则。

interface Named { name: string; }
const employee = { name: '小林', department: '研发' };
const person: Named = employee;

推断、可选与空值​

const count = 0; // 通常无需显式写 : number
let selected: number | null = null;

interface User {
name: string;
email?: string;
}

function displayEmail(user: User): string {
return user.email ?? '未填写';
}

可选属性可能是 undefined。开启严格检查后,访问空值之前需要判断。不要靠到处写 ! 消除提示;非空断言不会产生运行时检查。

联合类型与收窄​

type LoadState =
| { status: 'loading' }
| { status: 'success'; total: number }
| { status: 'error'; message: string };

function describe(state: LoadState): string {
switch (state.status) {
case 'loading': return '加载中';
case 'success': return `共 ${state.total} 条`;
case 'error': return state.message;
}
}

通过 status 判断后,编译器知道当前分支有哪些属性。这比随意组合多个布尔变量更容易表达互斥状态。

泛型保留输入输出关系​

interface PageResult<T> {
items: T[];
total: number;
}

function first<T>(items: T[]): T | undefined {
return items[0];
}

可以类比 C# 泛型,但 TS 泛型不会在运行时产生不同的 CLR 类型,也不能用 typeof T 做服务端响应校验。

unknown 比 any 更适合边界​

function errorMessage(error: unknown): string {
return error instanceof Error ? error.message : '发生未知错误';
}

any 会绕过类型检查;unknown 要求先检查再使用。HTTP、浏览器存储等外部输入都应视为边界。

类型断言不等于反序列化验证

await response.json() as TaskItem[] 只是告诉编译器你相信这个结构。服务器若返回错误字段,浏览器仍可能出错。应按风险使用类型守卫或 Zod 等运行时验证库。

以下是最小的单项验证例子:

function isTask(value: unknown): value is { id: number; title: string; isDone: boolean } {
if (typeof value !== 'object' || value === null) return false;
return 'id' in value && typeof value.id === 'number'
&& 'title' in value && typeof value.title === 'string'
&& 'isDone' in value && typeof value.isDone === 'boolean';
}

实战源码会把验证函数和模型放在同一处,避免前端静态类型与运行时判断各写一套不同的规则。

Vue 中的类型​

组件脚本片段
import { ref } from 'vue';
import type { TaskItem } from './types/task';

const tasks = ref<TaskItem[]>([]);
const selected = ref<TaskItem | null>(null);

空数组应明确元素类型,否则可能得到过窄的推断。import type 表示仅导入类型。Vue 模板类型检查使用 vue-tsc,普通 tsc 不足以检查 .vue 模板。

练习​

定义 CreateTaskRequest,只包含 title。再定义 TaskFilter,限制为三个筛选值。解释 as CreateTaskRequest 为什么不能阻止外部传入数字标题。

参考答案
interface CreateTaskRequest { title: string; }
type TaskFilter = 'all' | 'pending' | 'done';

类型断言只影响编译器。真正接收外部数据时需要检查值;服务端还需要按业务规则再次验证。

参考:TypeScript 手册、Vue 与 TypeScript。