类型与组件怎样帮助维护界面
把外部数据验证、联合状态和组件所有权连接起来,理解 TypeScript 与 React、Vue 各自能检查哪些问题。
定义资料与界面状态契约,拒绝错误响应,并验证组件输入、事件和稳定身份。
资料卡片最初只有标题,后来增加下载状态、错误提示和重试。若每个页面各自拼字段、改布尔值,很快会出现“加载中却显示旧错误”或“标题字段缺失仍进入成功页”。类型和组件帮助表达约束,但运行时的接口数据仍需要单独检查。
教学案例是一个资料列表组件:接收经过验证的记录,显示加载、成功或错误,并通过事件提出收藏请求。目标是让合法状态容易表达,让错误数据在进入界面前被拒绝。本课不给出未运行的框架项目,代码只验证合成状态契约。
静态类型不能替网络验证
TypeScript 在运行前检查程序中的类型关系,生成 JavaScript 时类型标记通常会被擦除。把接口结果断言为某个类型,只是告诉编译器按该类型理解它,不会自动检查 JSON。服务端返回 null、旧字段或错误数字时,浏览器仍会拿到这些真实值。
在数据边界先把外部值视为未知,检查对象结构、必需字段、字段类型与允许范围,再转换成内部记录。资料 ID 应是非空字符串,标题有明确长度规则;日期要约定格式与时区,不能只检查“它是字符串”。验证失败时提供明确错误,而不是用空标题掩盖契约问题。
类型检查与运行时验证可以共享契约设计,但不能互相冒充。编译通过说明静态关系满足当前编译器检查;合成响应通过验证说明这份输入符合检查条件。两者都不能证明真实服务永远不会返回新的或无效数据。
用状态标签减少不可能组合
多个布尔值可能同时出现 loading=true、hasError=true、hasData=true,迫使每个渲染分支猜优先级。带 kind 标签的联合状态可以明确每个分支有哪些字段:loading 不含结果,success 有列表,error 有错误信息。
如果产品需要边加载边展示旧结果,就把 refreshing 设计成独立状态并定义保留哪些数据,而不是偷偷违反 loading 的约束。类型应表达实际产品行为,不能为了简化代码取消用户需要的反馈。
TypeScript 可以根据标签缩小联合类型范围,帮助在不同分支访问正确字段。新增状态时检查所有处理分支,避免遗漏。错误本身也要区分可重试的连接失败和不可重试的契约错误,不把所有失败都压成一个无意义字符串。
组件输入与修改责任要明确
资料卡片接收记录、是否已收藏和操作状态,展示这些值,并通过事件或回调告诉上层用户想收藏哪条资料。持有列表的上层决定更新与持久化。多个组件直接改同一份可变全局对象,会让数据从哪里变化变得难以追踪。
显示的过滤结果、记录数量和按钮文案通常可以从现有状态推导。再保存一份独立数量就需要每次同步,容易留下旧值。为外部系统同步使用合适的副作用机制;纯计算不应每次都用副作用把结果写回状态。
组件划分按变化责任,而不是每个 HTML 元素都建一个文件。搜索输入、请求结果、单条卡片可以有清楚边界;只有一个属性的包装组件若没有复用或规则价值,可能只是增加跳转成本。先让接口清楚,再决定目录数量。
React 与 Vue 的状态机制各有约束
React 渲染时读取的 state 是本次渲染的快照,setter 安排后续更新,不会把当前函数已经读取的值立刻改成新值。组件在树中的位置与 key 影响身份和状态保留。列表使用稳定资料 ID,避免插入或排序后把某张卡片的局部状态错配给另一张。
Vue 提供响应式依赖与组件更新机制,状态变化会通过框架调度触发相关更新。也需要遵守其响应式用法和组件输入约定。不能因为对象能变化,就让任何模块随意修改它;响应式追踪不自动决定业务所有权。
选择框架时比较团队经验、生态依赖、现有项目和交付要求,不凭几句语法宣布谁永远更快。迁移框架会改变构建与组件方式,但不会替你修好接口权限、状态契约或重复数据来源。先定位维护问题,再判断迁移是否必要。
离线检查每种状态允许的字段
函数按 kind 检查合成状态:成功必须有列表,错误必须有字符串,加载不能携带结果或错误。断言包含一个非法组合,明确显示契约能拒绝什么。它不是 TypeScript 编译器,也不模拟 React 或 Vue 的更新调度。
def valid(state):
kind = state.get("kind")
if kind == "loading":
return "data" not in state and "error" not in state
if kind == "success":
return isinstance(state.get("data"), list) and "error" not in state
if kind == "error":
return isinstance(state.get("error"), str) and "data" not in state
return False
assert valid({"kind": "success", "data": [1]})
assert not valid({"kind": "loading", "data": [1]})
print(valid({"kind": "error", "error": "offline"}))
输出为 True,表示 {'kind': 'error', 'error': 'offline'} 满足这个简化规则。成功列表中的元素尚未校验,空错误字符串也会被接受;如果业务不允许,必须加上相应规则。通过检查不等于完整资料对象已可信。
模型将加载与旧结果分离,因此不能直接用它表达 refreshing。扩展时先定义该状态字段和转换条件,再增加断言。不要为一个实际需要的新状态直接放松所有分支,否则原有约束就失去作用。
组件错误边界、请求错误和字段验证失败也需要分开处理。界面渲染异常可能需要备用视图;请求失败需要保留查询与重试;契约失败需要记录安全的字段信息并停止把数据当成功结果使用。把这三种情况都捕获后返回空列表,会让维护者失去定位依据,也让用户误以为确实没有资料。
新增字段时也要保留旧响应测试,明确兼容边界,而不是只更新类型声明。
练习:从契约到组件验收
为资料定义 ID、标题、分类和下载状态。写出外部 JSON 验证规则、内部状态标签,以及卡片输入和收藏事件。给五份合成响应:正常、缺标题、错误类型、空列表、未知状态。每份都记录是否接受及界面反馈。
验收要求:错误数据不会以成功记录进入组件;加载、成功与错误不能组成非法混合状态;筛选后的数量由记录推导;插入和重新排序不把卡片状态交给别的 ID。若使用框架,应在真实项目验证更新与身份行为,并说明实际运行的检查;离线状态函数只证明自己的契约。
资料来源与延伸阅读
资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。