我试图创建一个函数,它接受泛型类型并保留其内容的文字值,但不允许任何额外的键.
typescriptsatisfies
关键字做了我所需要的—但从我所看到的来看,它只能在定义文字类型时使用,而不是作为函数参数使用.
这里有bare-bones example个我的意思:
type Foo = { a: number };
const foo = <T extends Foo>(x: T) => x;
foo({ a: 1, wrong: 2 }) // typechecks, but i don't want it to
foo({ a: 1, wrong: 2} satisfies Foo) // causes the type error i want
我试过各种各样的杂技,都没有用.然而,在发布了feature request on the Typescript repo之后,我被指出了Typescribe的一个新特性,它允许我得到一些工作(见comment).
我最终得到的解决方案是这个怪物:
type OptionalKeys<T extends Record<string, unknown>> = {
// eslint-disable-next-line
[P in keyof T]: {} extends Pick<T, P> ? P : never;
}[keyof T];
type RequiredKeys<T extends Record<string, unknown>> = {
// eslint-disable-next-line
[P in keyof T]: {} extends Pick<T, P> ? never : P;
}[keyof T];
type Satisfies<T, Base> =
// recur if both the generic type and its base are records
T extends Record<string, unknown>
? Base extends Record<string, unknown>
// this check is to make sure i don't intersect with {}, allowing any keys
? (keyof T & RequiredKeys<Base> extends never
? unknown
: {
[K in keyof T & RequiredKeys<Base>]: Satisfies<
T[K],
Base[K]
>;
}) &
// this check is to make sure i don't intersect with {}, allowing any keys
(keyof T & OptionalKeys<Base> extends never
? unknown
: {
[K in keyof T & OptionalKeys<Base>]?: Satisfies<
T[K],
Base[K]
>;
})
// if the generic type is a record but the base type isn't, something has gone wrong
: never
: T extends (infer TE)[]
? Base extends (infer BE)[]
? Satisfies<TE, BE>[]
// if the generic type is an array but the base type isn't, something has gone wrong
: never
// the type is a scalar so no need to recur
: T;
这里是一个ts playground that demonstrates this solution in action,通过引起以下示例的正确错误:
type Foo = { a: { b?: number }[] };
const foo = <T extends Foo>(x: Satisfies<T, Foo>) => x;
foo({ a: [{}] }); // typechecks
foo({ a: [{ b: 3 }] }); // typechecks
foo({ x: 2 }); // error
foo({ a: [{}], x: 2 }); // error
foo({ a: [{ b: 2, x: 3 }] }); // error
foo({ a: [{ x: 3 }] }); // error
任何关于如何清理或以其他方式改善它的建议将不胜感激!我对这件事已经超出了我的舒适区.