类型为R和K的Typescript扩展了R的类型,如何声明R[K]是string类型

类型为R和K的Typescript扩展了R的类型,如何声明R[K]是string类型,typescript,Typescript,假设我有接口记录,它看起来像 interface Record { id: string; createdBy: string; dateCreated: string; } 和界面结果,如下所示: interface Result<R extends Record> { records: R[]; referredRercords: { [ref:string]: Record; } } 有没有一种方法可以确保typescript类型R[K]确实是一个字符串

假设我有接口记录,它看起来像

interface Record {
  id: string;
  createdBy: string;
  dateCreated: string;
}
和界面结果,如下所示:

interface Result<R extends Record> {
  records: R[];
  referredRercords: { [ref:string]: Record; }
}
有没有一种方法可以确保typescript类型R[K]确实是一个字符串,而不用事先进行未知的转换?或者这就是处理这个问题的方法

编辑:

这同样有效,我更喜欢它,所以我会选择它,除非有人有更好的:

let key = record[refProp];
let val = (typeof key === 'string') ? result.referredRecords[key] : undefined;

请注意,在命名的
记录中已经有一个类型
,它代表一个对象类型,其键为
K
,值为
V
。我要将您的类型重命名为
MyRecord
,以区分它们。事实上,内置的
Record
类型非常有用,我可能会(巧合地)在回答这个问题时使用它

下面是新代码:

interface MyRecord {
    id: string;
    createdBy: string;
    dateCreated: string;
}

interface Result<R extends MyRecord> {
    records: R[];
    referredRecords: { [ref: string]: MyRecord; }
}

function mergeRecords<R extends MyRecord, K extends keyof R>(
    result: Result<R>, refs: [K, string][]
) {
    return result.records.map((record) => {

        let refsObj = refs.reduce((acc, [refProp, toProp]) => {
            let key: string = record[refProp]; // error, not a string 
            let val = result.referredRecords[key]; 
            return Object.assign(acc, { [toProp]: val });
        }, {});
        return Object.assign(record, refsObj);
    });
}
哎呀


理想情况下,您应该约束
R
(可能还有
K
)类型,以便让编译器知道发生了什么。这里有一种方法:

// return the keys from T whose property types match the type V
type KeysMatching<T, V> = { 
  [K in keyof T]-?: T[K] extends V ? K : never 
}[keyof T];

function mergeRecords<
    R extends MyRecord & Record<K, string>, 
    K extends KeysMatching<R, string>
>(
    result: Result<R>, refs: [K, string][]
) {
    return result.records.map((record) => {

        let refsObj = refs.reduce((acc, [refProp, toProp]) => {
            let key: string = record[refProp]; // no error now
            let val = result.referredRecords[key];
            return Object.assign(acc, { [toProp]: val });
        }, {});
        return Object.assign(record, refsObj);
    });
}
现在,第一次调用按需要失败,因为
“foo”
处的属性不是
字符串,而第二次调用仍然成功,因为
“bar”
处的属性没有问题


好的,希望能有帮助。祝你好运

回答得很好。非常感谢,非常彻底,将尽快测试并接受。如果这是可行的,我可能会问另一个关于键入返回值的问题,如果你认为你也能帮上忙的话。我得到了大部分信息,它肯定是可行的,你能再解释一下键匹配类型吗?具体来说,“-?”部分的意思是什么,以及结尾的[keyof T]是什么?
interface MyRecord {
    id: string;
    createdBy: string;
    dateCreated: string;
}

interface Result<R extends MyRecord> {
    records: R[];
    referredRecords: { [ref: string]: MyRecord; }
}

function mergeRecords<R extends MyRecord, K extends keyof R>(
    result: Result<R>, refs: [K, string][]
) {
    return result.records.map((record) => {

        let refsObj = refs.reduce((acc, [refProp, toProp]) => {
            let key: string = record[refProp]; // error, not a string 
            let val = result.referredRecords[key]; 
            return Object.assign(acc, { [toProp]: val });
        }, {});
        return Object.assign(record, refsObj);
    });
}
interface Oops extends MyRecord {
    foo: number;
    bar: string;
}
declare const result: Result<Oops>;
mergeRecords(result, [["foo", "bar"]]); // okay, but it shouldn't accept "foo"!
mergeRecords(result, [["bar", "baz"]]); // okay
// return the keys from T whose property types match the type V
type KeysMatching<T, V> = { 
  [K in keyof T]-?: T[K] extends V ? K : never 
}[keyof T];

function mergeRecords<
    R extends MyRecord & Record<K, string>, 
    K extends KeysMatching<R, string>
>(
    result: Result<R>, refs: [K, string][]
) {
    return result.records.map((record) => {

        let refsObj = refs.reduce((acc, [refProp, toProp]) => {
            let key: string = record[refProp]; // no error now
            let val = result.referredRecords[key];
            return Object.assign(acc, { [toProp]: val });
        }, {});
        return Object.assign(record, refsObj);
    });
}
interface Oops extends MyRecord {
    foo: number;
    bar: string;
}
declare const result: Result<Oops>;
mergeRecords(result, [["foo", "bar"]]); // error on "foo"
mergeRecords(result, [["bar", "baz"]]); // okay