广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何使用TypeScript为IWAC封装乍得网络无障碍指南的非文本内容类型?

乍得网络无障碍指南对非文本内容的约束往往分散在替代文本、标题、描述性音频等多个条款中。要在IWAC这类规则检查框架中实现自动化验证,首先要解决的是内容类型的边界定义,否则不同规则可能对同一种媒体给出不一致的判断。本文基于TypeScript的联合类型与类型守卫,构建一套可复用的非文本内容封装,供IWAC规则调用。

如何使用TypeScript为IWAC封装乍得网络无障碍指南的非文本内容类型?

用可辨识联合描述非文本内容类型

乍得指南对非文本内容的分类并不只是简单罗列媒体格式。图片可能承担信息传递、链接触发、装饰排版三种职责;音频和视频则需要区分纯媒体与带替代信息的情况;图标在可交互组件中又必须与按钮、链接的可访问名称关联。如果只用字符串或布尔值表示内容类型,封装出来的规则接口会迅速失去约束力。

更稳妥的做法是使用TypeScript的可辨识联合。先定义一个kind字段作为判别属性,再为每种内容类型声明独立接口。图片类型需要携带替代文本、是否为装饰性图片、是否作为链接使用等信息;音频类型则关注是否有描述文字或同步字幕;视频类型还需要区分是否有音轨、是否需要音频描述。这样所有规则函数在入口处就能通过判别属性收窄类型,后续访问字段时不会出现不存在的属性。

相比使用枚举加泛型参数的方案,可辨识联合的优势在于字段可以随类型变化。开发者不需要在同一个接口里用可选属性硬凑,也不会因为先判断一个布尔值再去访问另一个属性而出现类型断层的尴尬。对于IWAC这种需要长期维护扩展的规则库,这种建模方式能显著降低后续新增媒体类型的改动成本。

type NonTextContent =
  | { kind: 'image'; alt: string | null; decorative: boolean; link: boolean }
  | { kind: 'audio'; transcript: string | null; description: string | null }
  | { kind: 'video'; captions: boolean; audioDescription: boolean; transcript: string | null }
  | { kind: 'icon'; label: string | null; interactive: boolean }
  | { kind: 'decorative' }; // 装饰性非文本内容独立为一类
function getIssue(content: NonTextContent): string | null {
  switch (content.kind) {
    case 'image':
      return content.decorative ? (content.alt === '' ? null : '装饰性图片应使用空替代文本') : (content.alt ? null : '信息图片缺少替代文本');
    case 'audio':
      return content.transcript || content.description ? null : '音频内容缺少文字描述';
    case 'video':
      return content.captions ? null : '视频缺少同步字幕';
    case 'icon':
      return content.interactive ? (content.label ? null : '可交互图标缺少可访问名称') : null;
    case 'decorative':
      return null;
  }
}

在IWAC规则中实现检查函数

IWAC规则的典型形态是接收一个DOM节点或元素信息,返回若干Issue对象。封装层的职责是把DOM结构映射为前面定义的NonTextContent联合类型,再交给对应的检查函数处理。这样IWAC的规则注册表可以只依赖一个稳定的入口函数,而不必关心具体媒体类型的判断细节。

以图片为例,映射逻辑需要读取元素的alt属性、role属性以及是否被<a>元素包裹。对于alt缺失的情况,不能直接判定违规,还要结合装饰性图片的例外规则。乍得指南对装饰性图片要求alt为空字符串而不是缺失,这个差异需要在封装时显式区分null和空字符串。对于可交互图标,则优先检测aria-label和title,再检测可见文本,最后才判定缺失。

传统上很多检测器只用正则表达式去扫描HTML源码,无法处理动态插入的节点、组件化渲染的结果以及Shadow DOM里的内容。通过DOM元素对象来映射,则能够拿到更完整的运行时属性,并且可以复用浏览器原生查询能力,避免正则匹配对属性顺序、引号风格、大小写的脆弱依赖。

interface Issue {
  code: string;
  message: string;
  elementId: string;
}
function mapImageElement(el: Element): NonTextContent {
  const alt = el.getAttribute('alt'); // 可能为null
  const role = el.getAttribute('role');
  const interactive = role === 'link' || role === 'button';
  const isLinked = el.closest('a') !== null;
  const decorative = role === 'presentation' || role === 'none';
  return { kind: 'image', alt, decorative, link: isLinked || interactive };
}
function runImageRule(el: Element): Issue[] {
  const content = mapImageElement(el);
  const result = getIssue(content);
  return result ? [{ code: 'NT_IMG_001', message: result, elementId: el.id || '' }] : [];
}

用类型守卫处理未知输入与扩展

实际页面中,非文本内容有时无法通过静态标签一眼判断。例如伪元素生成的图标、画布元素绘制的内容、以及由JavaScript动态创建的图形,它们没有被DOM属性明确标注。封装层不能假设外部输入总是符合预期,需要提供类型守卫来验证未知结构,同时给调用方一个安全的转换入口。

类型守卫的意义在于将unknown类型收窄为NonTextContent。可以写一个isNonTextContent函数,检查kind字段是否在允许的联合范围中,并逐个校验必要字段的类型。这样从页面采集来的原始数据在进入规则前就能被拦截,不符合要求的对象不会被误判为某种非文本内容。

对于未来新增的类型,只需扩展联合类型和守卫的匹配分支,编译器会提示所有switch需要补齐。比如后续加入VR场景或三维模型作为非文本内容,可以根据新的kind值定义专属字段,既不会被旧逻辑错误合并,也不会影响已有规则函数对既有字段的访问。

function isNonTextContent(value: unknown): value is NonTextContent {
  if (typeof value !== 'object' || value === null) return false;
  const obj = value as Record<string, unknown>;
  switch (obj.kind) {
    case 'image':
      return typeof obj.decorative === 'boolean' && typeof obj.link === 'boolean';
    case 'audio':
      return (typeof obj.transcript === 'string' || obj.transcript === null);
    case 'video':
      return typeof obj.captions === 'boolean';
    case 'icon':
      return typeof obj.interactive === 'boolean';
    case 'decorative':
      return true;
    default:
      return false;
  }
}

与IWAC规则注册表集成并做编译期校验

封装层的最终目标不是单独运行,而是成为IWAC规则体系中的一个稳定模块。IWAC通常要求每个规则实现Rule接口,包含id、title、test函数和scope字段。非文本内容规则可以按媒体类型拆分成多个规则,也可以合并为一个规则并返回不同code。这里选择按类型拆分,因为乍得指南中不同非文本内容条目的通过条件差异较大,拆开更容易维护。

在实现Rule接口时,TypeScript的泛型约束能保证封装层暴露的检查函数签名与IWAC期望保持一致。如果IWAC的规则上下文类型发生变化,编译期就会报错,不用等到运行时才发现。将前面的runImageRule等函数包装成规则对象,放入规则数组即可。同时可以利用as const和keyof保持code字符串的唯一性。

集成后还需要补充一组轻量测试,覆盖装饰性图片、缺失替代文本的图片、无字幕视频、不可交互图标等典型场景。测试用例可以直接构造DOM元素或者普通对象,通过isNonTextContent过滤后传入规则函数,这样不必启动完整浏览器即可完成大部分逻辑验证。

interface IWACRule {
  id: string;
  title: string;
  scope: 'html' | 'dom';
  test: (el: Element) => Issue[];
}
const imageNonTextRule: IWACRule = {
  id: 'NT_IMG_001',
  title: '非文本内容:图片替代文本合规',
  scope: 'dom',
  test: runImageRule
};
const rules: IWACRule[] = [imageNonTextRule];

当规则库继续扩展时,新的音频规则、视频规则都可以沿用同一套NonTextContent联合类型和映射策略。开发者在编写新规则时,会在类型定义、映射函数以及注册表三个位置得到编译期提示,从而避免把音频字段错误地用在图片检查逻辑里。整个封装层既保留了TypeScript的强约束,又没有把乍得指南的灵活性压平成只能处理图片的单一结构。

广告位 · 文章底部(后台-模块管理替换此区域)