02_TypeScript与应用架构
判别联合、运行时数据边界、DTO 映射与按功能组织前端代码。
TypeScript 与前端应用架构
学习目标:把已有 TypeScript 语法用于真实应用的数据边界、模块组织和错误建模。
1. 类型不能代替运行时校验
TypeScript 类型在编译后消失。fetch 返回的 JSON、浏览器存储、URL 参数都属于不可信输入;需要运行时检查或受约束的 schema,不能直接 as User 就认定安全。
type LoadState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; message: string };
function titleOf(state: LoadState<{ title: string }>): string {
if (state.status === 'success') return state.data.title;
if (state.status === 'error') return state.message;
return '加载中';
}
判别联合把互斥状态写进类型,避免 data、loading、error 三个布尔/可空字段出现矛盾组合。
2. 把服务端 DTO 转成界面模型
HTTP 客户端 → 运行时解析 → DTO → 映射函数 → 页面模型 → 组件
接口字段可能使用字符串时间、可空值或后端命名规则;组件不应到处重复转换。集中封装 api/ 和映射函数,让 UI 面对稳定的数据模型。API 契约仍以服务端文档与契约测试为准。
3. 按功能组织代码
src/
app/ # 路由、全局提供者、入口
features/notes/
api/ # 请求与 DTO
components/ # 笔记界面
model/ # 类型与业务映射
shared/ # 经多个功能验证确实共用的组件
先按业务功能聚合,再提炼共用层;不要一开始把所有代码分散进巨大的 components/、utils/。跨层引用保持单向,避免组件反过来直接修改全局数据源。
4. 严格模式与错误处理
开启 TypeScript strict,少用 any;边界值优先用 unknown,检查后再使用。网络错误、超时、取消和业务错误要有不同处理;不要把所有失败转成空数组,否则页面会把故障误显示为“没有数据”。
自测
const user = json as User会验证服务端响应吗?不会。- 为什么按功能聚合代码?相关页面、API、模型更容易一起定位和修改。
5. 在网络边界从 unknown 开始
response.json() 得到的数据可能缺字段、字段类型错误或来自旧版本服务。定义一个小的解析函数,比在组件里到处写 as Note 更可靠:
type Note = { id: number; title: string };
function parseNote(input: unknown): Note {
if (typeof input !== 'object' || input === null) throw new Error('Invalid note');
const value = input as Record<string, unknown>;
if (typeof value.id !== 'number' || !Number.isInteger(value.id))
throw new Error('Invalid note id');
if (typeof value.title !== 'string') throw new Error('Invalid note title');
return { id: value.id, title: value.title };
}
这里断言成 Record<string, unknown> 只是为了安全读取字段,每个字段仍被检查。真实项目可使用受维护的 schema 库,规则集中放在 features/notes/api。若 API 的 ID 可能超过 JavaScript 安全整数范围,应在契约中决定是否以字符串传输。
6. 用类型表达失败分支
业务错误不是单一 Error 字符串。可将 unauthorized、forbidden、conflict、network 与 invalid_response 建模为判别联合,组件根据 kind 给出不同处理。每新增错误种类,switch 的穷尽检查会提示尚未处理的页面分支。
练习:写 parseNotes(input: unknown): Note[],先确认是数组,再逐项调用 parseNote;分别测试空数组、缺失 id、字符串 id 和正常响应。然后把列表组件中的类型断言删掉。