13

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

传递的参数:

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

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

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"))
4

2 回答 2

16

您总是可以通过狡猾(例如,索引访问和假设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]};
}

(请注意,由于TypeScript中的错误,这仍然很难正确输入)现在该函数将只接受key属性为 type 的内容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,从而迫使您在调用站点指定键类型:

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

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

于 2017-07-23T00:29:20.257 回答
0

常量声明不能是泛型类型。

所以你的代码更改将是

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

或者

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

或者

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

return updateData;
}
于 2017-07-22T20:04:02.877 回答