以色列网络无障碍指南通常被简称为 IWAC,非文本内容相关条款并不只是要求给图片写替代文本。IWAC 将非文本内容进一步划分为信息性图片、功能性图标、音频、视频、验证码、图表和纯装饰元素。每一类内容对应的无障碍处理方式差别很大:信息图片需要等价文本,图标需要可访问名称,音频需要完整文字稿,视频需要字幕和口述影像,验证码需要替代验证方式,装饰元素则需要从辅助技术树中彻底隐藏。若在项目里用一串布尔值或可选字符串表示这些要求,很快就会出现枚举值与含义不一致的问题。

对于 TypeScript 项目,规范落地的第一步不是写工具函数,而是建立稳定的领域类型。类型定义能把 IWAC 的约束从文档变成编译期可见的结构。比如一个音频对象如果缺少 transcript 字段,类型检查阶段就能报错,不用等到无障碍审计时才发现。接下来会从判别联合、类型守卫、组件集成三个角度说明如何封装。
用判别联合划分七类非文本内容
先定义一个公共基础接口,只保留所有非文本内容都需要的字段。id 用于溯源,purpose 表示用途,lang 表示界面语言。IWAC 常见项目语言有希伯来语、英语和阿拉伯语,可以用字面量联合限制语言代码。然后每个具体类型通过 kind 字段区分,这个字段就是判别联合的判别式。这样在 switch 或 if 中收窄类型时,TypeScript 能自动推断出具体类型。
type LangCode = 'he' | 'en' | 'ar';
interface BaseNonTextContent {
id: string;
lang?: LangCode;
purpose: 'informative' | 'decorative' | 'functional';
}
interface ImageContent extends BaseNonTextContent {
kind: 'image';
alt: string | null;
longdescUrl?: string;
isDecorative: boolean;
}
interface IconContent extends BaseNonTextContent {
kind: 'icon';
label: string;
iconType: 'standalone' | 'accompanying';
}
interface AudioContent extends BaseNonTextContent {
kind: 'audio';
transcript: string;
audioUrl: string;
}
interface VideoContent extends BaseNonTextContent {
kind: 'video';
captionsUrl: string;
audioDescriptionUrl?: string;
posterAlt?: string;
}
interface CaptchaContent extends BaseNonTextContent {
kind: 'captcha';
alternative: 'audio' | 'logical' | 'contact';
instructions: string;
}
interface ChartContent extends BaseNonTextContent {
kind: 'chart';
summary: string;
dataTableUrl?: string;
}
interface DecorativeContent extends BaseNonTextContent {
kind: 'decorative';
ariaHidden: true;
}
type NonTextContent =
| ImageContent
| IconContent
| AudioContent
| VideoContent
| CaptchaContent
| ChartContent
| DecorativeContent;
注意 ImageContent 的 alt 设计成了 string | null。这里不是为了省事,而是为了区分未填写和空字符串。信息性图片要求 alt 非空,装饰性图片要求 alt 为空字符串。如果只定义为 string,空字符串和漏填无法在类型层面表达,后续校验就要额外处理 undefined 与 null 的边界。把可空性放进类型后,校验函数可以更明确地判断。
IconContent 单独定义 label,是因为图标经常使用 SVG 或矢量字体,HTML 中 aria-label 和 title 的行为不同,IWAC 对功能性图标的可访问名称有专门要求。把图标从图片中拆出来,能够避免误用 alt 属性去描述一个按钮里的放大镜图标。
用类型守卫把规则变成可执行的校验函数
封装类型只是第一步,真正的无障碍合规需要运行时校验。内容可能来自后端接口、运营配置或用户生成数据,类型断言只能提供静态保证,无法确认对象真的符合 IWAC 要求。此时可以写一组类型守卫和校验函数,先用 kind 字段判断类别,再按规则逐项检查。
function isImageContent(content: NonTextContent): content is ImageContent {
return content.kind === 'image';
}
function validateImageContent(image: ImageContent): string[] {
const errors: string[] = [];
if (!image.isDecorative && !image.alt) {
errors.push('信息性图片必须提供 alt 文本');
}
if (image.isDecorative && image.alt !== null && image.alt !== '') {
errors.push('装饰性图片的 alt 应为空字符串');
}
return errors;
}
function validateNonTextContent(content: NonTextContent): string[] {
const baseErrors: string[] = [];
if (!content.id) baseErrors.push('缺少 id');
switch (content.kind) {
case 'image':
return [...baseErrors, ...validateImageContent(content)];
case 'icon':
if (!content.label) return [...baseErrors, '图标必须提供可访问名称'];
return baseErrors;
case 'audio':
if (!content.transcript.trim()) return [...baseErrors, '音频必须提供文字稿'];
return baseErrors;
case 'video':
if (!content.captionsUrl) return [...baseErrors, '视频必须提供字幕地址'];
return baseErrors;
case 'captcha':
if (!content.instructions) return [...baseErrors, '验证码必须提供操作说明'];
return baseErrors;
case 'chart':
if (!content.summary) return [...baseErrors, '图表必须提供结构化摘要'];
return baseErrors;
case 'decorative':
if (!content.ariaHidden) return [...baseErrors, '装饰内容必须设置 aria-hidden'];
return baseErrors;
default:
return baseErrors;
}
}
validateImageContent 里把装饰性图片的规则直接写成逻辑:如果 isDecorative 为 true,alt 必须是空字符串;如果为 false,alt 至少不能为 null。这个函数可以被单元测试覆盖,也可以直接接在数据进入组件之前。其他类型同理,音频检查 transcript,视频检查 captionsUrl,验证码检查 instructions。把规则表达为函数后,规范就不再是模糊的文档,而是可以落地的测试断言。
这里容易遇到一个坑:有人会把类型守卫命名为 isContent,但内部只判断 id 是否存在。类型守卫必须符合真实的收窄语义,否则会让 TypeScript 在后续代码中误判类型。比较好的做法是让 isImageContent 只依赖 kind 字段,具体的字段有效性交给 validate 函数处理。这样收窄逻辑简单,校验逻辑聚焦,职责清晰。
在组件和测试中让类型定义发挥实际作用
类型定义最终要作用在组件属性上。假设有一个无障碍内容渲染器,入口只接收 NonTextContent,内部根据 kind 分支渲染。这样在组件层就不可能传入一个不属于已知分类的对象。对图片分支,创建 DOM 元素时直接把已验证的 ImageContent 传入,强制调用前先校验。
type RenderProps = {
content: NonTextContent;
};
function assertRenderable(content: NonTextContent): void {
const errors = validateNonTextContent(content);
if (errors.length > 0) {
throw new Error(`Invalid IWAC non-text content: ${errors.join('; ')}`);
}
}
function createImageElement(image: ImageContent): HTMLElement {
assertRenderable(image);
const img = document.createElement('img');
img.alt = image.isDecorative ? '' : image.alt ?? '';
if (image.longdescUrl) img.longDesc = image.longdescUrl;
return img;
}
上述 createImageElement 先调用 assertRenderable,未通过就直接抛错。这个函数可以在开发环境中作为组件边界,也可以被测试用例复用。结合 Vitest 或 Jest,可以为每一种非文本内容写参数化测试,传入缺少 transcript 的音频对象,断言抛出错误;传入合规对象,断言返回 HTMLElement。这样类型与运行时行为保持一致。
还可以利用 TypeScript 的条件类型做更细粒度的约束。比如装饰性图片要求 alt 为空字符串,信息性图片要求非空字符串。可以定义 AltFor<T> 这样的条件类型,根据布尔参数推导不同的 alt 类型。不过不建议把类型写得过于复杂,团队维护成本会上升。优先保证判别联合清晰、校验函数完整,再按需引入条件类型。
TypeScriptIWAC非文本内容类型定义修改时间:2026-10-05 18:50:58