给出广泛共享的类型Omit,其定义为:
type Omit<ObjectType, KeysType extends keyof ObjectType> =
Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>;
用于减去类型(相交的相反)或换句话说,从类型中删除某些属性。
我正在尝试使用此类型来编写一个函数,该函数将使用类型为T
的对象,但其中一个属性丢失,然后设置该缺失的属性并返回类型为{{ 1}}。
使用以下使用特定类型的示例,一切都很好:
T
但是通用的完全相同的函数无法编译:
type A = { x: string }
function f(arg: Omit<A, 'x'>): A {
return { ...arg, x: "" }
}
function g<T extends A>(arg: Omit<T, 'x'>): T {
return { ...arg, x: "" }
}
的定义错误是:
输入'Pick>&{x:string; }”不能分配给类型“ T”。
但是我确定此错误消息是错误的。类型g
是可分配给Pick<T, Exclude<keyof T, "x">> & { x: string; }
的。
我要去哪里错了?
更多情况下,我正在编写一个React高阶组件,该组件将接受一个组件并自动提供一些已知的道具,并返回一个删除了那些已知道具的新组件。
答案 0 :(得分:2)
警告,请提前回答。摘要:
根本的问题是known,但牵引力可能不太大
简单的解决方法是使用类型断言return { ...arg, x: "" } as T;
简单的修复方法并不完全安全,在某些情况下效果不佳
在任何情况下,g()
均无法正确推断T
重构的g()
函数可能对您来说更好
我需要停止写很多东西
这里的主要问题是,编译器根本不够聪明,无法验证泛型的某些等效性。
// If you use CompilerKnowsTheseAreTheSame<T, U> and it compiles,
// then T and U are known to be mutually assignable by the compiler
// If you use CompilerKnowsTheseAreTheSame<T, U> and it gives an error,
// then T and U are NOT KNOWN to be mutually assignable by the compiler,
// even though they might be known to be so by a clever human being
type CompilerKnowsTheseAreTheSame<T extends U, U extends V, V=T> = T;
// The compiler knows that Picking all keys of T gives you T
type PickEverything<T extends object> =
CompilerKnowsTheseAreTheSame<T, Pick<T, keyof T>>; // okay
// The compiler *doesn't* know that Omitting no keys of T gives you T
type OmitNothing<T extends object> =
CompilerKnowsTheseAreTheSame<T, Omit<T, never>>; // nope!
// And the compiler *definitely* doesn't know that you can
// join the results of Pick and Omit on the same keys to get T
type PickAndOmit<T extends object, K extends keyof T> =
CompilerKnowsTheseAreTheSame<T, Pick<T, K> & Omit<T, K>>; // nope!
为什么它不够聪明?通常,有两种大致的答案:
有问题的类型分析取决于某些人类的聪明才智,而这些才智很难或不可能在编译器代码中捕获。在奇异性发生并且TypeScript编译器变得完全智能之前,您可能会想到一些事情,使编译器无法做到。
所涉及的类型分析相对来说由编译器执行比较简单。但是这样做会花费一些时间,并且可能会对性能产生负面影响。它是否足以改善开发人员体验,值得付出成本?不幸的是,答案通常是不。
在这种情况下,可能是后者。有an issue in Github about it,但除非有很多人开始为它喝彩,否则我不希望看到有很多工作要做。
现在,对于任何具体类型,编译器通常都可以检查并评估所涉及的具体类型并验证等效性:
interface Concrete {
a: string,
b: number,
c: boolean
}
// okay now
type OmitNothingConcrete =
CompilerKnowsTheseAreTheSame<Concrete, Omit<Concrete, never>>;
// nope, still too generic
type PickAndOmitConcrete<K extends keyof Concrete> =
CompilerKnowsTheseAreTheSame<Concrete, Pick<Concrete, K> & Omit<Concrete, K>>;
// okay now
type PickAndOmitConcreteKeys =
CompilerKnowsTheseAreTheSame<Concrete, Pick<Concrete, "a"|"b"> & Omit<Concrete, "a"|"b">>;
但是,在您的情况下,您尝试通过通用T
使其实现,但这不会自动发生。
当您比编译器更了解所涉及的类型时,很可能需要明智地使用type assertion,这是这种情况下语言的一部分:
function g<T extends A>(arg: Omit<T, 'x'>): T {
return { ...arg, x: "" } as T; // no error now
}
在那里,它现在可以编译,您完成了,对不对?
好吧,我们不要太草率。使用类型断言的陷阱之一是,当您确定自己在做的事情是安全的时,您告诉编译器不要担心验证某些事情。但是你知道吗?这取决于您是否希望看到一些极端情况。这是我最担心您的示例代码的地方。
假设我有一个disciminated union类型的U
,这意味着 拥有一个a
属性或一个{ {1}}属性,具体取决于b
属性的string literal值:
x
没问题吧?但是,// discriminated union U
type U = { x: "a", a: number } | { x: "b", b: string };
declare const u: U;
// check discriminant
if (u.x === "a") {
console.log(u.a); // okay
} else {
console.log(u.b); // okay
}
扩展了U
,因为类型A
的任何值也应该是类型U
的值。这意味着我可以这样叫A
:
g
值// notice that the following compiles with no error
const oops = g<U>({ a: 1 });
// oops is supposed to be a U, but it's not!
oops.x; // is "a" | "b" at compile time but "" at runtime!
可分配给{a: 1}
,因此编译器认为它产生了类型为Omit<U, 'x'>
的值oops
。但是不是吗?您知道U
在运行时既不是oops.x
也不是"a"
,而是"b"
。我们已经对编译器撒谎了,现在当我们开始使用""
时会遇到麻烦。
现在也许这种情况不会发生在您身上,如果是这样,您就不必担心太多了……毕竟,打字应该使维护代码更容易,而不是更难。
最后,我想提一句,键入的oops
函数将永远无法推断出比g()
狭窄的T
类型。如果您呼叫A
,则g({a: 1})
将被推断为T
。如果A
总是被推断为T
,那么您甚至也可能没有泛型函数。
出于相同的原因,编译器无法充分窥视A
来理解如何与Omit<T, 'x'>
结合形成Pick<T, 'x'>
,因此它无法窥视类型的值T
并找出Omit<T, 'x'>
应该是什么。那该怎么办?
对于编译器而言,推断您传递给它的实际值的类型要容易得多,所以让我们尝试一下:
T
现在function g<T>(arg: T) {
return { ...arg, x: "" };
}
将使用类型g()
的值并返回类型T
的值。最终总是可以将其分配给T & {a: string}
,因此可以使用它:
A
如果您想以某种方式阻止const okay = g({a: 1, b: "two"}); // {a: number, b: string, x: string}
const works: A = okay; // fine
的参数具有g()
属性,那就没有发生:
x
但是我们可以在const stillWorks = g({x: 1});
上设置约束来实现:
T
这是相当类型安全的,不需要类型断言,并且对于类型推断更好。所以这可能就是我要离开的地方。
好的,希望这本中篇小说对您有所帮助。祝你好运!