如何用TypeScript定义含指定终止类型的递归对象?
多语言嵌套文本对象的TypeScript类型定义问题
我需要定义一个支持灵活嵌套结构的多语言字符串存储对象,要求最后一层必须是特定的Language类型。最初的类型写法在调用getCurrLang(text['paragraph']['subText'])时触发类型错误:
Argument of type 'string | Text | Language' is not assignable to parameter of type 'Text | Language'. Type 'string' is not assignable to type 'Text | Language'
后续调整类型定义后仍未解决问题,求正确的类型定义方式或更优实现方案。
初始尝试代码
interface Text { [key: string | number]: ComponentText | Language; } interface Language { [key: string]: string; } const text: Text = { title: { en: "English text", sv: "Swedish text", fr: "French Text", es: "Spanish Text", }, paragraph: { subText: { en: "English text", sv: "Swedish text", fr: "French Text", es: "Spanish Text", } } } // getCurrLang(s: any) 负责返回对应语言的字符串,若传入非Language类型则抛出错误
后续尝试代码
type LangCode = 'en' | 'sv' | 'fr' | 'es'; interface Text { [key: string]: ComponentText | Array<ComponentText>; language?: Language; } type Language = { [key in LangCode]: string; };
正确的类型定义方案
核心思路是用递归类型区分嵌套节点和最终的语言节点,确保只有最底层是Language类型,上层嵌套节点只能包含其他嵌套节点或Language:
type LangCode = 'en' | 'sv' | 'fr' | 'es'; // 最终语言包类型:固定语言键值对 type Language = { [K in LangCode]: string; }; // 递归类型:嵌套节点可以是其他NestedText或Language type NestedText = { [key: string]: NestedText | Language; }; // 使用示例 const text: NestedText = { title: { en: "English text", sv: "Swedish text", fr: "French Text", es: "Spanish Text", }, paragraph: { subText: { en: "English text", sv: "Swedish text", fr: "French Text", es: "Spanish Text", }, // 支持任意多层嵌套 anotherSection: { subSubText: { en: "Deep nested English", sv: "Djupnästlad svenska" } } } }; // 给getCurrLang添加类型约束,只接受Language类型参数 function getCurrLang(s: Language): string { // 示例:根据当前环境获取对应语言字符串 const currLang: LangCode = 'en'; return s[currLang]; } // 此时调用不会报错,text.paragraph.subText被正确推断为Language类型 const subText = getCurrLang(text.paragraph.subText as Language);
额外优化建议
- 类型守卫:如果不想每次手动断言类型,可以添加类型守卫函数自动判断是否为Language类型:
function isLanguage(obj: NestedText | Language): obj is Language { return typeof obj === 'object' && !Array.isArray(obj) && ('en' in obj || 'sv' in obj || 'fr' in obj || 'es' in obj); } // 安全封装的语言获取函数 function safeGetCurrLang(s: NestedText | Language): string { if (isLanguage(s)) { return getCurrLang(s); } throw new Error("传入的不是有效的Language对象"); } // 无需手动断言,自动推导类型 const safeSubText = safeGetCurrLang(text.paragraph.subText);
- 从类型层面杜绝错误:原问题的错误根源是初始类型允许节点直接存储string,递归类型
NestedText只允许值为NestedText或Language,彻底避免了非法值的出现。
内容的提问来源于stack exchange,提问作者Swesen
相关产品推荐
相关产品推荐

