gpt4 book ai didi

typescript - 禁止显式类型参数或约束联合类型参数以提高类型安全性

转载 作者:行者123 更新时间:2023-12-02 18:59:48 30 4
gpt4 key购买 nike

getValues() 时,以下 Typescript 代码可以正常工作。调用函数时无需显式类型参数。但是,当使用显式类型参数调用它时,可能会导致错误(请参阅下面的调用getValues<'a' | 'b' | 'c' >(store, 'a', 'b'))。

type Store = Record<string, string>;

function getValues<T extends string>(
store: Store, ...keys: T[]
): {[P in T]: string} {
const values = {} as {[P in T]: string};

keys.forEach((p) => {
const value = store[p];
if (value) {
values[p] = value;
} else {
values[p] = 'no value';
}
});

return values;
}


// Usage / requirements:

const store: Store = {}; // assume it is filled with arbitrary values


// in the following line values1 is inferred to be of type
// {a: string, b: string}, which is the required behavior
const values1 = getValues(store, 'a', 'b');


// the following line does not compile, which is the required behavior
// Argument of type 'b' is not assignable to parameter of type 'a' | 'c'
const values2 = getValues<'a' | 'c'>(store, 'a', 'b');


// in the following line values3 is inferred to be of type
// {a: string, b: string, c: string}, which is NOT the required behavior
// the required behavior would be to forbid this situation, similar to
// how values2 declaration was forbidden by the compiler.
const values3 = getValues<'a' | 'b' | 'c' >(store, 'a', 'b');

// now values3.c can be accessed
// values3.c cannot have a value and will cause an error when used

有没有办法阻止调用者提供显式类型参数或任何其他方式使此代码更加类型安全,而不会使 getValues 函数的返回类型部分化或使其返回类型的属性可选。

我想到了以下想法,但我不知道它们在 Typescript 中是否可行或如何可行:

想法 1:防止提供显式类型参数

想法 2:删除 getValues 的值参数并在运行时访问提供的类型参数

想法 3:强制所有提供的类型参数也用作值参数。也就是说,上面声明values3常量会导致编译错误。

我知道问题是由不安全类型断言 as {[P in T]: string} 引起的。我正在寻找一种方法来使其类型安全。

最佳答案

这可能带来的麻烦大于其值(value);不幸的是,尝试从类型系统获得此类保证所涉及的复杂性很高,并且应该权衡人们手动指定太宽的类型参数的可能性。也许您可以只记录该函数( /** hey, let the compiler infer T, thanks **/ ),而不是尝试让编译器为您强制执行此操作。


首先,无法阻止指定显式类型参数。即使您可以,它也可能对您没有多大帮助,因为类型推断仍然可能导致您试图避免的更广泛的类型:

const aOrB = Math.random() < 0.5 ? "a" : "b";
getValues(store, aOrB); // {a: string, b: string} again

您也无法在运行时访问类型参数,因为它们与静态类型系统的其余部分一样,是 erased转译为 JavaScript 后。


环顾四周,我认为 TS 人员在尝试处理 computed property keys 时遇到了这个问题。 ,也有类似的问题。提出的解决方案(但未在语言中实现,请参阅 microsoft/TypeScript#21030 和链接问题进行讨论)似乎称为 Unionize你会得到 {a: string} | {b: string}如果您不知道 key 是否为 "a""b" .


我想我可以想象使用像 Unionize 这样的类型配合采取programmatic intersection传入的每个键的这些类型。这意味着您希望通用参数不是键类型的联合,而是 rest tuple关键类型:

type UnionizeRecord<K, V> = K extends PropertyKey ? { [P in K]: V } : never;
type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never;
type UnionizeRecordTupleKeys<K extends PropertyKey[], V> = Expand<
{ [I in keyof K]: (x: UnionizeRecord<K[I], V>) => void }[number] extends
((x: infer R) => void) ? R : never
>;

UnionizeRecord<K,V>类型就像 Record<K, V>但会生成类型的联合,一个类型对应 K 中的每个键。 Expand<T> type 仅有助于 IntelliSense 输出;它变成了一个十字路口,如 {a: string} & {bar: number}转换为等效的单个对象类型,如 {a: string, bar: number} 。和UnionizeRecordTupleKeys<K, V>是,嗯,丑陋。它的作用是遍历每个索引 I K的元组并生成 UnionizeRecord<K[I], V> 。然后将它们相交并展开。有关所有工作原理的具体信息,请通过 distributive conditional types 进行解释。和 inference in conditional types .

您的getValues()函数看起来像这样:

function getValues<K extends (number extends K['length'] ? [string] : string[])>(
store: Store, ...keys: [...K]
): UnionizeRecordTupleKeys<K, string> {
const values = {} as any; // not worried about impl safety here

keys.forEach((p) => {
const value = store[p];
if (value) {
values[p] = value;
} else {
values[p] = 'no value';
}
});

return values;
}

我不想让编译器理解它可以将东西分配给 UnionizeRecordTupleKeys<K, string> 类型的值。当K是一个未指定的泛型类型参数,所以我使用 any内部实现。另一个问题是我constrained K类型为可分配给 string[]元组 ;我想防止像 Array<"a" | "b"> 这样的非元组。确实没有必要阻止这种情况,因为输出类型只会比您想要的更宽,但我想阻止有人手动指定一些奇怪的东西。您可以使用类似的技巧来防止其他特定的事情。


让我们看看它是如何工作的。这仍然是您想要的行为:

const values1 = getValues(store, 'a', 'b');
/*const values1: {
a: string;
b: string;
} */

如果手动指定类型参数,它需要是一个元组:

const values2 = getValues<["a", "b"]>(store, 'a', 'b');
/* const values2: {
a: string;
b: string;
} */

如果指定了错误的元组,则会出现错误:

const values3 = getValues<["a", "b", "c"]>(store, 'a', 'b'); // error!
// -----------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Expected 4 arguments, but got 3.

如果为元组元素之一指定或传入联合,则会得到对象类型的联合:

const aOrB = Math.random() < 0.5 ? "a" : "b";
const values4 = getValues(store, aOrB);
/* const values4: {
a: string;
} | {
b: string;
} */

它可以成为很多东西的有趣结合:

const cOrD = Math.random() < 0.5 ? "c" : "d";
const values5 = getValues(store, aOrB, cOrD);
/* const values5: {
a: string;
c: string;
} | {
a: string;
d: string;
} | {
b: string;
c: string;
} | {
b: string;
d: string;
} */

这样就一切正常了!不过,可能有很多我没有考虑到的边缘情况......即使它有效,它也很笨重和复杂。再说一遍,我可能只是建议记录您的原始函数,以便调用者了解编译器的限制。但很高兴看到您能多么接近“正确”的输出类型!

Playground link to code

关于typescript - 禁止显式类型参数或约束联合类型参数以提高类型安全性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65724066/

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