? 我想出了一种方法来做到这一点,结果的用法对我来说非常易读。我将another Stack Overflow answer 扩展为jcalz
类型
const NotNullSymbol = Symbol("not null");
export type NotNull = typeof NotNullSymbol;
type RemoveNotNullTypes<T> = T extends NotNull
? unknown
: T extends object
? { [K in keyof T]: RemoveNotNullTypes<T[K]> }
: T;
type _Overwrite<T, U> = U extends NotNull
? Exclude<T, null>
: U extends object
? {
[K in keyof T]: K extends keyof U ? _Overwrite<T[K], U[K]> : T[K];
} & RemoveNotNullTypes<U>
: U;
type ExpandRecursively<T> = T extends Function
? T
: T extends object
? T extends infer O
? { [K in keyof O]: ExpandRecursively<O[K]> }
: never
: T;
export type Overwrite<T, U> = ExpandRecursively<_Overwrite<T, U>>;
示例用法
type Person = {
name: string | null;
house: {
kitchen: {
stoveName: string | null;
stoveBrand: number | undefined;
otherThings: unknown;
};
};
};
type PersonWithNullsRemoved = Overwrite<
Person,
{
name: NotNull;
house: {
kitchen: {
stoveName: NotNull;
stoveBrand: string;
};
};
}
>;
function foo(person: PersonWithNullsRemoved) {
// no TS errors for the following lines
const name = person.name.toLowerCase();
const stoveName = person.house.kitchen.stoveName.toLowerCase();
const stoveBrand = person.house.kitchen.stoveBrand.toLowerCase();
}
function bar(person: Person) {
const name = person.name.toLowerCase(); // Error: Object is possibly 'null'
const stoveName = person.house.kitchen.stoveName.toLowerCase(); // Error: Object is possibly 'null'
const stoveBrand = person.house.kitchen.stoveBrand.toLowerCase(); // Error: Object is possibly 'undefined' and Error: Property 'toLowerCase' does not exist on 'number'.
}
解释
我不会深入探讨Overwrite 的一般工作原理,因为这已经在the SO answer I was inspired by 中完成。我用NotNull 类型扩展了它,以避免必须覆盖像这样的深层嵌套属性:Exclude<Person['house']['kitchen']['stoveName'], null>,当它更加嵌套时会变得非常忙碌。相反,简单的NotNull 对我来说更好读!
NotNull 只是特定 unique symbol 的类型。或者,一个唯一的字符串 const 可能就足够了,但可能会导致意外匹配。
当_Overwrite 评估传入的覆盖映射时,如果值为NotNull,那么它将只取原始类型的值并从中排除null。否则,如果它是一个对象,它将遵循正常路径。但是,在将对象与U 合并时,我们需要确保NotNull 类型不会以最终发出的类型结束。所以我们 RemoveNotNullTypes 来自 U 和任何 Us 嵌套属性。
这个实现在生产环境中运行良好,我通过删除nulls 来覆盖Prisma 返回的对象的类型,其中业务逻辑在给定情况下不允许null。有时你可以只添加! 来声明你不希望它是null 但在这种情况下,我们试图让发出的类型与生成的ResponseBody 类型匹配ResponseBody 类型@ 类型.
如果某些事情仍然没有意义,请告诉我,我很乐意尝试进一步解释。