如何在不省略类型注释的情况下,为TypeScript中具有许多属性的对象提供类型安全性



TypeScript存储库中我的问题AsValue实用程序类型功能请求已被拒绝,2020年11月。我期望以下API:

const MarkupPreprocessingDefaultSettings: AsValue = {
mustExecuteCodeQualityInspection: true,
waitingForOtherFilesWillBeSavedPeriod__milliseconds: 1000,
entryPointsGroupDependent: {
indentationSpacesCountInOutputCode: 2,
mustExecuteHTML5_Validation: true,
mustExecuteAccessibilityInspection: true
}
};
// Same as
const MarkupPreprocessingDefaultSettings: {
mustExecuteCodeQualityInspection: true,
waitingForOtherFilesWillBeSavedPeriod__milliseconds: 1000,
entryPointsGroupDependent: {
indentationSpacesCountInOutputCode: 2,
mustExecuteHTML5_Validation: true,
mustExecuteAccessibilityInspection: true
}
} = {
mustExecuteCodeQualityInspection: true,
waitingForOtherFilesWillBeSavedPeriod__milliseconds: 1000,
entryPointsGroupDependent: {
indentationSpacesCountInOutputCode: 2,
mustExecuteHTML5_Validation: true,
mustExecuteAccessibilityInspection: true
}
};

奥,但我们在现实中有什么?让我们考虑一下具体的问题。

目标

routing变量进行注释,例如:

  1. TypeScript必须知道哪些属性存在,哪些不存在。例如,routing.products必须工作,但routing.gibberish必须强制转换TypeScript错误
  2. 除了productsorders之外,密钥的数量可以是任意大的
  3. 不允许任何类型的注释省略、anyobject
type Route = {
URN: string;
}
const routing /* : ??? */ = {
products: {
URN: "/products"
},
orders: {
URN: "/orders"
}
}

选项1:手动注释所有内容(过度使用(

const routing : {
products: Route;
orders: Route;
// and several thousands of keys for real commercial application ...
} = {
products: {
URN: "/products"
},
orders: {
URN: "/orders"
},
// and several thousands of keys for real commercial application ...
}

结论:非技术性,难以扩展和维护。不可接受的

选项2:使用索引型(松散型安全型(

type Route = {
URN: string;
}
const Routing: { [routeName: string]: Route  } = {
products: {
URN: "/products"
},
orders: {
URN: "/orders"
}
}
console.log(Routing.products)
console.log(Routing.orders)
console.log(Routing.gibberish) // Undefined!

结论:它不会防止打字错误;没有可用的自动完成功能。不可接受的

选项3:使用通用辅助函数

const asRouting = <T extends Record<keyof T, Route>>(t: T) => t;
const routing = asRouting({
products: {
URN: "/products"
},
orders: {
URN: "/orders"
}
});

如果您对routing变量进行了注释,编译器将只将该变量视为已注释类型,并且不会得到任何类型推断。正如你所看到的,要么你必须用正确的类型进行注释,这是多余的,要么或者你注释的类型比你想要的更宽,你就会丢失你关心的类型信息。所以你真的不应该注释routing。让编译器为您推断它的类型。

但是,如果您错误地使用一个或多个属性不是有效Route的值初始化routing,该怎么办?那么,使用routing的代码将产生编译器错误。但这可能与routing的定义相去甚远。你希望编译器错误就在那里,这样你就可以修复它。

这就是上面的通用helper函数的作用。asRouting()在运行时是一个身份函数,只输出与输入相同的值。在编译时,它也是一个标识函数,输出类型将与输入类型相同。但由于这种类型T被约束为Record<keyof T, Route>,因此asRouting()将只接受其所有属性都是有效Route对象的输入。

让我们看看上面定义的routing会发生什么。其推断类型可以通过IntelliSense显示为:

/* const routing: {
products: {
URN: string;
};
orders: {
URN: string;
};
} */

这正是你想要的类型,而不需要你拼写出来:

console.log(routing.products.URN); // okay
console.log(routing.orders.URN); // okay
console.log(routing.somethingElse); // compiler error!
// ---------------> ~~~~~~~~~~~~~
// Property 'somethingElse' does not exist

但您仍然需要进行所有类型检查,以确保所有属性都是Route:

const badRouting = asRouting({
products: {
URL: "/product"; // error! 
// ~~~~~~~~~~~~~~~~ 
//  Object literal may only specify known properties, 
//  and 'URL' does not exist in type 'Route'
}
})

游乐场链接到代码

最新更新