gpt4 book ai didi

typescript - 省略类型往返不适用于泛型

转载 作者:搜寻专家 更新时间:2023-10-30 21:33:40 26 4
gpt4 key购买 nike

考虑到广泛共享的类型省略,定义如下:

type Omit<ObjectType, KeysType extends keyof ObjectType> =
Pick<ObjectType, Exclude<keyof ObjectType, KeysType>>;

它用于减去类型(与相交相反)或换句话说,从类型中删除某些属性。
我试图利用这种类型来编写一个函数,该函数将使用一个类型的对象 T其中一个属性丢失,然后设置缺失的属性并返回一个类型 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: "" }
}

g定义的错误是:
类型“pick>&{x:string;}”不能分配给类型“t”。
但我确信这个错误信息是错误的。类型 Pick<T, Exclude<keyof T, "x">> & { x: string; }可分配给 T
我哪里做错了?
对于更多的上下文,我正在编写一个反应高阶组件,它将接受一个组件并自动提供一些已知的道具,返回一个新组件并移除那些已知的道具。

最佳答案

警告,请回答。总结:
潜在的问题是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">>;

但在您的案例中,您试图使用generic 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属性,要么持有 b属性,这取决于 x属性的 string literal值:
// 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()窄的类型。如果调用 TA将被推断为 g({a: 1})。如果 T总是被推断为 A,那么您甚至可能没有泛型函数。
由于编译器可能无法查看 T足够理解它如何与 A一起形成 Omit<T, 'x'>,所以它不能进入类型 Pick<T, 'x'>的值,并找出 T应该是什么。那么我们能做些什么呢?
编译器更容易推断传递给它的实际值的类型,因此让我们尝试:
function g<T>(arg: T) {
return { ...arg, x: "" };
}

现在 Omit<T, 'x'>将获取 T类型的值并返回 g()类型的值。这将始终可以分配给 T,因此您应该可以使用它:
const okay = g({a: 1, b: "two"}); // {a: number, b: string, x: string}
const works: A = okay; // fine

如果你想阻止参数 T & {a: string}具有 A属性,那还没有发生:
const stillWorks = g({x: 1}); 

但我们可以通过对
function g<T extends { [K in keyof T]: K extends 'x' ? never : T[K] }>(arg: T) {
return { ...arg, x: "" };
}

const breaksNow = g({x: 1}); // error, string is not assignable to never.

这是相当安全的类型,不需要类型断言,并且对于类型推断更好。所以我可能会把它放在那里。
好吧,希望这部中篇小说能帮到你。祝你好运!

关于typescript - 省略类型往返不适用于泛型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55299469/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com