网络无障碍建设中,链接是最容易被忽视又最影响体验的元素。屏幕阅读器用户浏览页面时,往往依靠链接列表快速跳转,如果一连串链接都显示为点这里、了解更多这类模糊文字,用户根本无从判断每个链接指向何处。IWAC(Internet Web Accessibility Guidelines 的法语区实践版本)在参照 WCAG 的基础上,为喀麦隆等法语区国家提供了本地化的无障碍实施建议,其中对链接目的(link purpose)提出了可编程判定、可类型化管理的要求。本文将介绍如何用 TypeScript 把这些规则封装为一套强类型的链接目的模型,让无障碍约束从人工评审提前到编译期。

一、理解IWAC指南中的链接目的要求
IWAC 在链接层面主要继承 WCAG 2.1 的成功标准 2.4.4(链接目的在上下文中可判定)与 2.4.9(链接目的可仅由链接文本判定),同时结合喀麦隆本地政务与教育站点的实际情况,把常见链接用途归纳为几个大类:站内导航链接、站外引用链接、文件下载链接、锚点跳转链接、触发动作的伪链接以及在新窗口打开的链接。每一类对无障碍实现的要求不同,例如下载链接必须标明文件格式与体积,新窗口链接必须提前告知用户,动作型链接则要求具备按钮语义而非链接语义。
这些规则如果只停留在文档层面,落地效果往往取决于开发者的自觉。一旦用 TypeScript 把每一类链接建模为独立的类型,并要求业务代码必须显式声明链接目的,就能在编译阶段拦截大量不规范用法。这种思路的本质,是把无障碍指南翻译成类型系统可以理解的约束条件,让 编译器 承担一部分评审工作。
二、用判别联合类型建模链接目的
Discriminated Union(判别联合)是封装多种链接形态的最佳选择。我们用一个共同的 kind 字段作为判别属性,每种链接目的对应一个独立的结构体,各自携带必需的无障碍元数据。下面是一个基础实现:
// 定义链接目的的判别联合类型
export type IwacLinkPurpose =
| { kind: 'internal'; href: string; label: string }
| { kind: 'external'; href: string; label: string; opensInNewWindow: boolean }
| { kind: 'download'; href: string; label: string; fileType: string; fileSizeKb: number }
| { kind: 'anchor'; targetId: string; label: string }
| { kind: 'action'; actionName: string; label: string };这样建模的好处非常直接:编译器会强制下载链接必须提供 fileType 和 fileSizeKb,外部链接必须显式声明是否在新窗口打开。如果开发者漏填任何一个字段,TypeScript 会立即报错,而无障碍评审时才发现问题的时间成本要高得多。这正对应 IWAC 指南中“下载资源需提前告知格式与体积”“新窗口行为需可预期”的具体条款。
进一步,可以用字面量联合类型约束 fileType 的取值范围,例如限定为 'pdf' | 'docx' | 'xlsx' | 'zip',避免出现大小写不一或拼写错误的格式描述。这种细节约束在多语言站点中尤其重要,因为法语区站点常出现 PDF、pdf、Pdf 混用的情况,统一成小写字面量后,渲染层的无障碍提示文案也能保持一致。
三、编写类型守卫与运行时校验
类型系统只在编译期生效,当链接数据来自 CMS 接口或 JSON 配置时,还需要运行时校验来保证数据符合类型定义。类型守卫(Type Guard)是连接两个世界的桥梁:
// 类型守卫:判断链接是否为新窗口打开的外部链接
export function isOpenExternalLink(
link: IwacLinkPurpose
): link is Extract<IwacLinkPurpose, { kind: 'external' > {
return link.kind === 'external' && link.opensInNewWindow === true;
}
// 根据链接目的生成无障碍提示后缀
export function buildAccessibleLabel(link: IwacLinkPurpose): string {
switch (link.kind) {
case 'external':
return link.opensInNewWindow
? `${link.label}(新窗口打开)`
: link.label;
case 'download':
return `${link.label}(${link.fileType.toUpperCase()} 文件,约 ${link.fileSizeKb} KB)`;
default:
return link.label;
}
}借助 Extract 工具类型,守卫函数无需重复书写联合成员的完整结构,维护成本更低。当指南条款更新、链接分类需要扩充时,只需修改联合定义一处,所有 switch 分支会因穷尽性检查而提示遗漏,这比手工维护多份文档要可靠得多。
如果项目已经引入了 zod 之类的校验库,也可以把上面的类型定义反向生成为 Schema,让 IwacLinkPurpose 从推断类型中获得,实现编译期与运行时的单一数据源。对于喀麦隆这类多语言(法语、英语并存)站点,Schema 中还可以加入标签非空、不包含点击这里等禁用词的校验规则,把 IWAC 对链接文本质量的要求也纳入同一套管线。
四、在组件层落地无障碍渲染
类型封装的最终价值要体现在渲染层。设计一个统一的 IwacLink 组件,接收 IwacLinkPurpose 类型的 props,内部根据 kind 决定渲染为 <a> 还是 <button>,并自动附加无障碍属性:
export function IwacLink({ link }: { link: IwacLinkPurpose }) {
// 动作型链接必须渲染为按钮语义,符合 IWAC 对交互元素的判定
if (link.kind === 'action') {
return <button type="button" onClick={() => actions[link.actionName]}>
{link.label}
</button>;
}
const rel = link.kind === 'external'
? 'noopener noreferrer external'
: undefined;
return (
<a
href={link.kind === 'anchor' ? `#${link.targetId}` : link.href}
target={isOpenExternalLink(link) ? '_blank' : undefined}
rel={rel}
aria-label={buildAccessibleLabel(link)}
>
{link.label}
</a>
);
}这套封装的收益在于把无障碍知识集中到了一处:业务开发者不需要记住 IWAC 的每条细则,只要正确选择 kind 并填写对应字段,组件就会输出符合规范的标记。新窗口自动带上 rel="noopener noreferrer" 和可感知的提示文案,下载链接自动标注格式与体积,动作链接自动改用按钮语义——这些都是指南要求但又容易遗漏的细节。
总结来看,用 TypeScript 封装 IWAC 链接目的类型的核心思路是三步:先用判别联合描述链接分类与各自的必需元数据,再通过类型守卫和运行时校验覆盖动态数据来源,最后在统一组件中完成规范化的无障碍渲染。类型系统不能替代真实用户的可用性测试,但它能以近乎零成本的方式,让团队产出的链接在最基本的语义层面站得住脚,这对喀麦隆及整个法语区的无障碍合规工作来说,是一条性价比极高的路径。
TypeScript网络无障碍IWAC修改时间:2026-09-01 10:20:36