在面向乍得地区发布Web产品时,IWAC(乍得网络无障碍指南)对文档结构给出了比WCAG更贴合本地语种排版的细则。例如要求每个内容页必须包含明确的页眉、主导航、主区域与页脚,且标题不能跳过层级。直接用HTML写容易遗漏,用TypeScript把页面结构类型化,可以在编码时强制这些约束。
理解IWAC页面结构的核心约束
IWAC将页面划分为若干个必须出现的区块,并且对区块顺序与嵌套关系做了规定。与通用无障碍规范不同,它强调法语与阿拉伯语混排时,导航区域需要带明确的语种切换入口,且主区域之前不能插入广告性质的aside。我们在TypeScript里首先要把这些区块抽象成枚举式的联合类型,让编译器知道一个合规页面由哪些部分构成。
除了区块 presence 之外,标题层级也是重点。IWAC禁止从 h1 直接跳到 h4,这种跳跃在屏幕阅读器里会让乍得用户丢失上下文。因此类型系统不仅要描述“有哪些区块”,还要描述“区块内的标题序列是否连续”。下面我们先定义最基础的区块类型,后续再叠加标题校验。
很多团队刚开始用 any 绕过检查,结果上线后被读屏软件报出结构错乱。把约束前置到类型层,是成本最低的预防手段。类型即文档,新成员看完类型定义就能明白IWAC的结构底线。
// 区块角色,对应IWAC规定的必备结构
type IwacRegion =
| 'header'
| 'navigation'
| 'main'
| 'footer'
| 'language_switch';
// 单个结构节点的描述
interface IwacStructureNode {
region: IwacRegion;
titleLevel?: 1 | 2 | 3 | 4 | 5 | 6;
children?: IwacStructureNode[];
}
// 一个页面必须满足的最小结构
type IwacPage = [
{ region: 'header' },
{ region: 'navigation' },
{ region: 'language_switch' },
{ region: 'main' },
{ region: 'footer' }
];
用泛型与条件类型封装标题连续性
仅有区块不够,标题跳跃问题需要更精细的类型表达。我们可以写一个条件类型,接收标题层级数组,如果相邻差值大于1就推导出错误类型。这样在写页面配置时,一旦写了 [1, 4] 编译器就标红。这种写法比写校验函数更早发现错误,也避免无效渲染。
实现上可以用模板字面量类型配合数字运算模拟差值,但TypeScript原生不支持数字相减推导,所以通常用查找表方式枚举合法跳变。下面的代码展示如何用映射类型把“上一级标题”映射到“允许的下一步标题”,从而约束嵌套标题。
这种封装还能结合IDE自动补全。开发者输入 region 时只能选IWAC规定的五种,输入 titleLevel 时只能选1到6,且子节点受父节点限制。相比口头规范,类型把乍得指南变成了敲代码时的硬阻力。
// 允许的标题后继:key为当前层级,value为可接的层级集合
type NextTitle = {
1: 2;
2: 1 | 3;
3: 2 | 4;
4: 3 | 5;
5: 4 | 6;
6: 5;
};
// 校验标题序列是否连续
type CheckHeading<T extends number[]> =
T extends [infer F, infer S, ...infer R]
? S extends NextTitle[F & keyof NextTitle]
? R extends number[]
? CheckHeading<[S, ...R]>
: true
: false
: true;
// 用法示例
type Good = CheckHeading<[1, 2, 3]>; // true
type Bad = CheckHeading<[1, 4]>; // false
在组件层消费页面结构类型
定义好底层类型后,要在React或Vue组件里实际使用才有价值。以函数组件为例,可以把页面配置对象声明为 IwacPage 类型,组件内部遍历生成对应的 <header>、<nav> 等语义标签。如果外部传入的结构少了 language_switch,TypeScript在调用处就报错,根本走不到运行阶段。
为了适配乍得双语环境,组件还可利用类型守卫动态插入语种切换按钮。类型系统保证这个按钮一定出现在 navigation 之后、main 之前,符合IWAC顺序。下面示例展示一个接收强类型配置的组件骨架,以及如何使用类型断言收窄区块。
当项目规模变大,建议把这些类型独立成 @types/iwac 包,配合ESLint规则做团队统一。这样即便有人想用 div 伪装 header,类型与lint也会双重拦截,保障乍得用户拿到结构正确的页面。
import { IwacPage, IwacStructureNode } from './iwac-types';
function renderRegion(node: IwacStructureNode): string {
switch (node.region) {
case 'header':
return '<header>' + (node.children?.map(renderRegion).join('') ?? '') + '</header>';
case 'navigation':
return '<nav>' + (node.children?.map(renderRegion).join('') ?? '') + '</nav>';
case 'language_switch':
return '<div class="lang">FR | AR</div>';
case 'main':
return '<main>' + (node.children?.map(renderRegion).join('') ?? '') + '</main>';
case 'footer':
return '<footer>' + (node.children?.map(renderRegion).join('') ?? '') + '</footer>';
}
}
export function IwacLayout(page: IwacPage) {
return page.map(renderRegion).join('');
}
类型封装带来的维护与协作收益
把IWAC结构做成TypeScript类型,不只是为了编译通过。它让异地协作的同事无需通读指南原文,靠类型提示就能写出合规页面。尤其在乍得外包团队与国内团队对接时,类型成为无歧义的中间语言,减少评审中的来回沟通。
另外,当指南修订时,只需改类型定义与少量映射,组件逻辑不动。比如新增“政务声明区块”,在 IwacRegion 联合类型加一项,所有漏写的地方立即暴露。这种中心化约束比在文档里加粗“必须包含”有效得多,也方便做增量迁移。
从工程角度看,类型层校验零运行时开销,打包体积不涨,却换来了结构安全。长期维护乍得站点时,这套封装会显著降低无障碍回归缺陷的数量。
TypeScriptIWAC页面结构类型修改时间:2026-08-16 17:10:42