我试图让 Typescript 警告我关于我们所拥有的 API 的不正确用法(我希望看到的错误被标记):
interface Icon { src: string; }
interface IconSet {
[iconName: string]: Icon;
}
type IconRegistry<T> = {
[K in keyof T]: Icon;
};
function asIconRegistry<T extends IconSet>(iconSet: T): IconRegistry<T> {
return iconSet as any;
}
type UiBuilder = <P, S extends IconRegistry<P>>(iconRegistry: S) => {
withIcon<K extends keyof S>(iconName: K): null,
};
const listIcons = asIconRegistry({
sort: { src: 'sort.svg' }, // Expected to be OK
email: { source: 'email.svg' }, // Expected to ERR
});
const profileIcons = asIconRegistry({
security: { src: 'security.svg' }, // Expected to be OK
email: { src: 'email.svg' }, // Expected to be OK
});
function buildUi(builder: UiBuilder) {
builder(profileIcons).withIcon('security'); // Expected to be OK
builder(profileIcons).withIcon('bold'); // Expected to ERR
}
它工作得很好,正如可以在这里看到的,除了一个小细节 - 该asIconRegistry
函数是一个实际的运行时工件,这是不理想的。
手动输入变量:
const listIcons2: IconRegistry<{ sort: Icon, email: Icon }> = {
sort: { src: 'sort.svg' }, // Expected to be OK
email: { source: 'email.svg' }, // Expected to ERR
};
这有效,但太冗长了 - 泛型的用处有点丢失。除此之外,随着列表的大小和复杂性的增加,这变得更难维护,并且更难说服此 API 的消费者以这种方式键入他们的内容。
如何在没有运行时工件和上述冗长的情况下实现相同级别的类型安全?
好吧,让我们清理一下。你的泛型比必要的要宽一些;大多数情况下,当您真正关心它们的键名时,您正在使用完整的对象类型。这是我的更改:
// Icon is the same
interface Icon { src: string; }
// you just need key names here (K), not a full object type (T)
type IconRegistry<K extends string> = Record<K, Icon>
// you don't really need IconSet, but for convenience, here it is
type IconSet = IconRegistry<string>
// the old P and S didn't do anything except determine keys
// let's just use those keys (K) directly
type UiBuilder = <K extends string>(iconRegistry: IconRegistry<K>) => {
withIcon(iconName: K): null,
};
所以现在您已准备好创建和使用一些IconSet
s。
我真的不明白为什么您认为类型检查对运行时的影响为零很重要(身份函数调用的影响可能相当低,尤其是对于现代 JavaScript 引擎),但我喜欢挑战。您想在编译时验证listIcons
和profileIcons
是否有效IconSet
,而不会将任何内容发送到 JavaScript 中。
这个怎么样:
type VerifyIconRegistry<T extends IconSet> = any
您需要将一个 validIconSet
作为参数传递给VerifyIconRegistry<>
. 让我们看看它与 invalid 的作用listIcons
:
const listIcons = {
sort: { src: 'sort.svg' },
email: { source: 'email.svg' }
};
declare var witness:
VerifyIconRegistry<typeof listIcons> // ERROR
// Property 'src' is missing in type '{ source: string; }'
有错误。typeof listIcons
不是有效的IconSet
. Notedeclare var witness
不会发出任何 JavaScript。它确实witness
在编译时在此处添加了一个名为环境作用域的变量......给它任何你不需要的名字,你可以重用它,因为var
s 可以重新声明。
现在为有效profileIcons
:
const profileIcons = {
security: { src: 'security.svg' },
email: { src: 'email.svg' }
};
declare var witness:
VerifyIconRegistry<typeof profileIcons> // OKAY
那个有效(我们重用了这个witness
名字)。
最后,让我们确保我们没有中断buildUi()
:
function buildUi(builder: UiBuilder) {
builder(profileIcons).withIcon('security'); // OKAY
builder(profileIcons).withIcon('bold'); // ERROR
// Argument of type '"bold"' is not assignable
// to parameter of type '"security" | "email"'.
}
看起来不错。希望有所帮助;祝你好运!
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句