TypeScript:如何处理泛型类型和 keyof 运算符

标签 typescript generics generic-programming keyof

我尝试编写一个通用函数来为数据库更新组装更新数据。

传递的参数:

  • 要更新的记录
  • 属性键
  • 一个新的数组项

即使我使用 keyof R 限制了 key 的类型,我无法将具有该键的新对象分配给 Partial<R>不变。我收到错误 Type '{ [x: string]: any[]; }' is not assignable to type 'Partial<R>'. 我该怎么做才能使以下代码正常工作?如果我替换通用类型 R通过非通用类型,它可以工作。但这不是我需要的。

Snippet on TypeScript Playground

interface BaseRecord {
    readonly a: ReadonlyArray<string>
}

function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) {
    const updateData: Partial<R> = { [key]: [...record[key], newItem] }
    return updateData
}

interface DerivedRecord extends BaseRecord {
    readonly b: ReadonlyArray<string>
    readonly c: ReadonlyArray<string>
}
const record: DerivedRecord = { a: [], b: [], c: ["first item in c"] }
console.log(getUpdateData<DerivedRecord>(record, "c", "second item in c"))

最佳答案

您总是可以通过巧妙的方式(例如,索引访问和编译器假设 R[key] 是可读写的)来按照您的意愿改变类型系统

function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) {
    var updateData: Partial<R> = {};
    updateData[key] = [...record[key], newItem]; 
    return updateData
}

或暴力破解(通过 any 类型):

function getUpdateData<R extends BaseRecord>(record: R, key: keyof R, newItem: string) {
    const updateData: Partial<R> = <any> { [key]: [...record[key], newItem] }
    return updateData
}

以上回答了你的问题,但要小心:这个函数不安全。它假设任何 record传入的将有一个 string[] key 的值属性,但类型 R也许不会。例如:

interface EvilRecord extends BaseRecord {
    e: number;
}
var evil: EvilRecord = { a: ['hey', 'you'], e: 42 };
getUpdateData(evil, 'e', 'kaboom');  // compiles okay but runtime error 

还有,返回值类型Partial<R>有点太宽了:你知道它会有 key键,但您需要检查它以使类型系统满意:

var updatedData = getUpdateData<DerivedRecord>(record, "c", "first item in c") // Partial<DerivedRecord>
updatedData.c[0] // warning, object is possibly undefined

我建议输入 getUpdateData()像这样:

type KeyedRecord<K extends string> = {
    readonly [P in K]: ReadonlyArray<string>
};

function getUpdateData<K extends string, R extends KeyedRecord<K>=KeyedRecord<K>>(record: R, key: K, newItem: string) {
    return <KeyedRecord<K>> <any> {[key as string]:  [...record[key], newItem]};
}

(注意由于 a bug in TypeScript 仍然很难正确输入)现在该函数将只接受 key 的内容属性类型为 ReadonlyArray<string> ,并保证 key属性存在于返回值中:

var evil: EvilRecord = { a: ['hey', 'you'], e: 42 };
getUpdateData(evil, 'e', 'kaboom'); // error, number is not a string array

var updatedData = getUpdateData(record, "c", "first item in c") // KeyedRecord<"c">
updatedData.c[0] // no error

希望对您有所帮助。


技术更新

我更改了建议的getUpdateData()上面的声明有两个通用参数,因为出于某种原因,TypeScript 为 key 推断了一个太宽的类型之前的参数,强制您在调用站点指定 key 类型:

declare function oldGetUpdateData<K extends string>(record: KeyedRecord<K>, key: K, newItem: string): KeyedRecord<K>;
oldGetUpdateData(record, "c", "first item in c"); // K inferred as 'a'|'b'|'c', despite the value of 'c'
oldGetUpdateData<'c'>(record, "c", "first item in c"); // okay now 

通过添加第二个泛型,我显然延迟了 TypeScript 在正确推断键类型后对记录类型的推断:

getUpdateData(record, "c", "hello"); // K inferred as 'c' now

请随意忽略这一点,但这就是用启发式类型推断制作香肠的方式。

关于TypeScript:如何处理泛型类型和 keyof 运算符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45258534/

相关文章:

java - 你能定义一个既有下界又有上限的通用界限吗?

c++ - 用作模板函数输入的函数的 void 返回值被视为参数

angularjs - 在不迭代数组的情况下从 Angular 生成标记

Angular2 - 测试调用内部http服务的函数

typescript - 我可以指定一个接口(interface)将共享另一个接口(interface)的 key 吗?

javascript - 在 TypeScript 中使用相同的 Update 方法添加用户时设置自动 id

java - 为什么要为 Collection 类引入泛型?

c# - 基类中的泛型可以在父类中指定吗

Rust 如何在某些结构的静态函数调用中解析类型注释

java - 计算二叉树中通用对象的频率