在实现无障碍表单时,错误信息如果只是随意拼接的字符串,后续维护和测试都会非常痛苦。安哥拉网络无障碍指南(IWAC)对表单错误提示提出了结构化要求:错误必须能被程序识别、必须与具体字段关联、必须给出可执行的修复建议。用TypeScript把这类规则封装成类型定义,可以让开发者在编译阶段就规避大量低级错误。

IWAC对表单错误提示的约束拆解
IWAC并不是简单规定错误文本的措辞,而是从用户感知和辅助技术解析两个角度提出约束。一个合规的错误提示至少包含错误编码、关联字段、错误级别、用户可读消息和修复建议。这样屏幕阅读器才能通过ARIA属性把错误信息准确播报给用户,同时自动化测试也能依据错误编码断言校验逻辑。
如果只用字符串表示错误,例如return '邮箱格式不正确',代码里无法判断这是格式错误还是必填错误,也无法知道它对应哪个输入项。更麻烦的是,当指南要求为不同错误类型提供不同的ARIA处理时,只能靠字符串匹配这种脆弱的方式。因此需要把错误从字符串升级为结构化对象。
根据常见表单校验场景,IWAC相关错误可以归纳为以下几类:必填缺失、格式非法、长度不足、长度超限、正则不匹配、重复冲突。这些类别通过一个code字段互相区分,同时保留fieldId指向具体的表单控件,确保错误和字段可关联。
用TypeScript定义错误类型的完整模型
TypeScript的可辨识联合非常适合为错误分类建模。先定义一个所有错误共有的基础字段,再让每个具体错误类型携带自己特有的信息。例如格式错误需要包含期望的格式描述,长度错误需要包含最小长度或最大长度,而重复冲突则需要包含重复值。这样每种错误都能提供足够的上下文,避免渲染时再做二次判断。
基础接口可以设计为包含code、fieldId、message、suggestion和severity。其中severity使用'error' | 'warning'联合类型,确保不会出现无效级别。code使用字面量联合'required' | 'format' | 'minLength' | 'maxLength' | 'pattern' | 'duplicate',这就是可辨识联合的判别字段。
下面是一个完整的类型定义示例,包含了基础接口、具体错误接口以及最终的联合类型。
/**
* IWAC 表单错误基础结构
*/
interface IWACFormErrorBase {
/** 关联的表单控件ID,必须与页面元素id一致 */
fieldId: string;
/** 用户可读的错误消息,应使用无障碍友好语言 */
message: string;
/** 修复建议,辅助用户理解如何更正 */
suggestion: string;
/** 错误级别,warning 不会阻止提交 */
severity: 'error' | 'warning';
/** 对应的IWAC规则编号,便于审计 */
ruleId: string;
}
/**
* 必填缺失错误
*/
interface RequiredFieldError extends IWACFormErrorBase {
code: 'required';
}
/**
* 格式非法错误,例如邮箱、手机号
*/
interface FormatFieldError extends IWACFormErrorBase {
code: 'format';
/** 期望的格式描述,如 example@domain.com */
expectedFormat: string;
}
/**
* 长度不足错误
*/
interface MinLengthFieldError extends IWACFormErrorBase {
code: 'minLength';
/** 当前实际长度 */
actualLength: number;
/** 要求的最小长度 */
minLength: number;
}
/**
* 长度超限错误
*/
interface MaxLengthFieldError extends IWACFormErrorBase {
code: 'maxLength';
actualLength: number;
maxLength: number;
}
/**
* 正则模式不匹配错误
*/
interface PatternFieldError extends IWACFormErrorBase {
code: 'pattern';
/** IWAC建议使用的规则名称,如 strong_password */
patternName: string;
}
/**
* 重复冲突错误,用于用户名、编号等唯一性校验
*/
interface DuplicateFieldError extends IWACFormErrorBase {
code: 'duplicate';
/** 重复的值,注意不要记录敏感信息 */
duplicatedValue: string;
}
/**
* IWAC 表单错误联合类型
*/
type IWACFormError =
| RequiredFieldError
| FormatFieldError
| MinLengthFieldError
| MaxLengthFieldError
| PatternFieldError
| DuplicateFieldError;
这段代码把IWAC的抽象要求变成了可检查的类型。当开发者构造错误对象时,如果遗漏了fieldId或者给code赋了不存在的值,TypeScript编译器会立即报错。同时不同错误类型的特有字段也受到约束,比如FormatFieldError必须具备expectedFormat,这样后续渲染格式错误的修复提示时就不用担心字段缺失。
类型守卫与错误渲染的联动
有了联合类型,下一步就是在渲染错误时根据code进行分流。如果直接使用if (error.code === 'format'),TypeScript可以自动收窄类型,但在复杂组件中往往需要把判断逻辑抽成类型守卫函数,这样既能复用也能提高可读性。
类型守卫函数的返回类型使用error is FormatFieldError这种形式,明确告诉TypeScript在条件成立后变量的具体类型。比如可以写一个isFormatFieldError函数,内部判断error.code === 'format'。类似的守卫函数可以覆盖所有错误类型,也可以只针对需要特殊处理的几类。
下面示例展示如何结合类型守卫和ARIA属性进行渲染。ARIA属性如aria-invalid和aria-describedby需要动态设置到对应的表单控件上,而错误消息则放入一个带有role="alert"或aria-live的容器中。注意在HTML里使用这些属性时不要拼错。
/**
* 类型守卫:判断是否为格式错误
*/
function isFormatFieldError(
error: IWACFormError
): error is FormatFieldError {
return error.code === 'format';
}
/**
* 类型守卫:判断是否为长度相关错误
*/
function isLengthFieldError(
error: IWACFormError
): error is MinLengthFieldError | MaxLengthFieldError {
return error.code === 'minLength' || error.code === 'maxLength';
}
/**
* 根据错误类型生成ARIA描述文本
*/
function buildAriaDescription(error: IWACFormError): string {
if (isFormatFieldError(error)) {
return `${error.message} ${error.suggestion} 期望格式:${error.expectedFormat}`;
}
if (isLengthFieldError(error)) {
if (error.code === 'minLength') {
return `当前长度${error.actualLength},至少需要${error.minLength}个字符`;
}
return `当前长度${error.actualLength},最多允许${error.maxLength}个字符`;
}
return `${error.message} ${error.suggestion}`;
}
/**
* 将错误渲染函数与DOM操作结合(示意)
*/
function renderIWACFormError(
error: IWACFormError,
fieldContainer: HTMLElement
): void {
const field = document.getElementById(error.fieldId);
if (!field) return;
field.setAttribute('aria-invalid', error.severity === 'error' ? 'true' : 'false');
const errorBox = document.createElement('div');
errorBox.setAttribute('role', 'alert');
errorBox.textContent = buildAriaDescription(error);
fieldContainer.appendChild(errorBox);
field.setAttribute('aria-describedby', errorBox.id);
}
这段代码体现了类型守卫的价值:在isLengthFieldError条件成立后,error被收窄为两个长度错误类型之一,再通过内部的error.code === 'minLength'进一步区分。这样访问actualLength和minLength等字段就是类型安全的,不会出现联合类型上属性不存在的编译错误。
对于更复杂的无障碍要求,比如错误发生时焦点管理或错误摘要区自动更新,可以在同一套类型基础上扩展渲染策略。因为错误结构已经统一,无论使用原生DOM、React还是Vue,都能方便地把IWACFormError转换成对应的无障碍组件。
错误类型定义的可扩展与维护策略
表单错误类型不可能一成不变。IWAC指南更新或者业务增加自定义校验时,需要在不破坏现有代码的前提下扩展联合类型。如果直接修改IWACFormError联合类型,所有使用该类型的函数都会收到影响,这其实是一种好事,可以迫使开发者处理新增类型。
为了降低扩展成本,可以使用工具类型单独管理错误码。例如定义一个IWACErrorCode = 'required' | 'format' | ...,然后让基础接口的code字段引用它。新增错误时先扩展IWACErrorCode,再增加对应的具体接口,最后把接口加入联合类型。这样错误码和联合类型保持同步,不容易遗漏。
还可以使用Extract工具类型从联合类型中筛选出某个具体错误类型,例如Extract<IWACFormError, {code: 'required'}>会得到RequiredFieldError。这在编写泛型函数或错误注册表时非常有用。下面演示一个基于Map的错误模板注册表,通过错误码快速获取默认消息和修复建议。
/**
* 错误码字面量联合
*/
type IWACErrorCode =
| 'required'
| 'format'
| 'minLength'
| 'maxLength'
| 'pattern'
| 'duplicate';
/**
* 错误模板注册表
*/
const errorTemplateRegistry: Record<IWACErrorCode, {
defaultMessage: string;
defaultSuggestion: string;
}> = {
required: {
defaultMessage: '此字段为必填项',
defaultSuggestion: '请填写该字段后重新提交'
},
format: {
defaultMessage: '输入格式不正确',
defaultSuggestion: '请按照提示的格式重新输入'
},
minLength: {
defaultMessage: '输入内容过短',
defaultSuggestion: '请增加输入字符数'
},
maxLength: {
defaultMessage: '输入内容过长',
defaultSuggestion: '请减少输入字符数'
},
pattern: {
defaultMessage: '输入内容不符合规则',
defaultSuggestion: '请检查是否包含不允许的字符'
},
duplicate: {
defaultMessage: '该值已存在',
defaultSuggestion: '请换一个不同的值'
}
};
/**
* 工厂函数:根据错误码创建基础错误对象
*/
function createIWACFormError<T extends IWACErrorCode>(
code: T,
fieldId: string,
ruleId: string
): Extract<IWACFormError, { code: T }> {
const template = errorTemplateRegistry[code];
const base = {
fieldId,
message: template.defaultMessage,
suggestion: template.defaultSuggestion,
severity: 'error' as const,
ruleId
};
if (code === 'required') {
return { ...base, code } as Extract<IWACFormError, { code: T }>;
}
if (code === 'format') {
return { ...base, code, expectedFormat: '请参考输入框提示' } as Extract<IWACFormError, { code: T }>;
}
// 其他类型的构造逻辑类似,此处省略以保持示例简洁
throw new Error('尚未实现的错误类型构造');
}
这种注册表模式把消息文案和类型构造分离,便于统一维护无障碍用语。需要注意的是,工厂函数中的as断言要谨慎使用,最好在实际项目中为每个分支编写完整的对象字面量,让TypeScript自动推断。上面的示例为了简洁省略了部分分支,完整实现应为每个code分支返回对应的具体错误对象。
除了类型层面,还应该考虑错误对象的序列化与反序列化。如果错误信息需要从服务端返回或存入日志,可以使用JSON.stringify序列化,但要注意fieldId和ruleId不能包含敏感信息。反序列化时则需要写一个类型守卫来验证数据结构,避免外部数据破坏类型假设。
最终,这套TypeScript类型定义把IWAC的抽象无障碍要求落到了具体的编译器约束上。错误编码、字段关联、修复建议不再依赖开发者的记忆力,而是由类型系统强制保证。后续无论是接入无障碍测试工具还是扩展新的错误类别,都能在较低成本下保持结构一致和语义清晰。
TypeScriptIWAC表单错误类型修改时间:2026-09-18 21:52:44