我有这个打字脚本代码.本质上,我想传递一个keys name(K,K1),并将其转换为一个保留key和key值的新对象,并添加一个额外的对象.以下是目前为止我的代码

type KOutput<T, K extends keyof T> = {
    [P in K]: T[P];
} & {
    assignment: string[];
};

const transform =
    <Obj, K extends keyof Obj, K1 extends keyof Obj>(key1: K, key2: K1, obj: Obj): KOutput<Obj, K | K1> => {
        const output: KOutput<Obj, K | K1> = {
            [key1]: obj[key1],
            [key2]: obj[key2],
            assignment: ['test'],
        }

        return output;
    };

transform('key1', 'key2', { key1: 'value1', key2: 'value2' });

我仍然得到这个错误,虽然输出,

Type '{ [x: string]: Obj[K] | Obj[K1] | string[]; assignment: string[]; }' is not assignable to type 'KOutput<Obj, K | K1>'.
  Type '{ [x: string]: Obj[K] | Obj[K1] | string[]; assignment: string[]; }' is not assignable to type '{ [P in K | K1]: Obj[P]; }'.

如何使Typescript 工作?

推荐答案

如果计算的键不是单个literal type,则TypeScript不能精确地用computed property表示对象的类型.如果键是generic类型,如KK1,则键被加宽为string并产生字符串index signature.这种类型不是wrong,但比你想要的要宽.microsoft/TypeScript#13948更多信息

到目前为止,如果你知道一些编译器不知道的类型,最简单的方法是使用type assertion:

const output = {
    [key1]: obj[key1],
    [key2]: obj[key2],
    assignment: ['test'],
} as KOutput<Obj, K | K1>

断言很容易,但你现在负责把类型正确.如果你的断言被证明是不正确的,那么你可能会在运行时不高兴.


如果您想要更好的东西,那么您就需要开始负责告诉编译器它可以期望的计算(computed)属性是什么样子.事实证明这有点困难.{[k]: v}的类型是not,通常是Record<typeof k, typeof v>.毕竟,如果k是类型"a" | "b",那么输出将类似于{a: any} | {b: any}not {a: any; b: any}.

这里有一个函数,我有时会使用它来获得更精确的类型,但代价是更复杂的(在实现中仍然需要使用类型断言):

function kv<K extends PropertyKey, V>(
  k: K, v: V
): { [P in K]: { [Q in P]: V } }[K] {
  return { [k]: v } as any
}

然后你可以重构transform()来使用它:

const transform =
  <Obj, K extends keyof Obj, K1 extends keyof Obj>(
    key1: K, key2: K1, obj: Obj) => {
    const output = {
      ...kv(key1, obj[key1]),
      ...kv(key2, obj[key2]),
      assignment: ['test'],
    };    
    return output 
  };

如果你判断一下,你会发现输出类型非常复杂.但是当你用合理的输入调用它时,你会得到你期望的输出类型:

const ret = transform('key1', 'key2', { key1: 'value1', key2: 'value2' });
// const ret: { key1: string; } & { key2: string; } & { assignment: string[]; } 
console.log(ret.assignment.map(x => x.toUpperCase())) // ["TEST"]
console.log(ret.key1.toUpperCase()) // "VALUE1"
console.log(ret.key2.toUpperCase()) // "VALUE2"

{ key1: string; } & { key2: string; } & { assignment: string[]; }型相当于KOutput<{key1: string, key2: string}, "key1" | "key2">型. 但是如果你try 用KOutput类型实际上是annotate the return type,你会得到一个错误:

/* Type '{ [P in K]: { [Q in P]: Obj[K]; }; }[K] & 
         { [P in K1]: { [Q in P]: Obj[K1]; }; }[K1] &
         { assignment: string[]; }' is not 
  assignable to type 'KOutput<Obj, K | K1>' */

那是因为它是not那种类型的.只有当KK1是单字符串文字类型时,它们才是等价的. 如果你把这个叫做:

const x = transform(Math.random() < 0.5 ? "a" : "b", "c", { a: 0, b: 0, c: 0 });

KOutput的版本会声称xabc作为键,而实际上它有abc,这就是上面疯狂的东西给你的:

/* const x: ({ a: number; } | { b: number; }) & { c: number; } & { assignment: string[]; } */

就我个人而言,我不知道是否值得担心这些边缘案件.但是它们是存在的,TypeScript根本不担心它们,而是 Select 索引签名.如果你想要简单的方法,请使用类型断言.如果你想走一条艰难的路,那就开始经历一个折磨人的逻辑,确定一个计算的属性对一个类型实际意味着什么.

Playground link to code

Typescript相关问答推荐

在TypScript手册中可以视为接口类型是什么意思?

带有微前端的动态React路由问题

接口的额外属性作为同一接口的方法的参数类型'

从typescript中的对象数组中获取对象的值作为类型'

在键和值为ARGS的泛型函数中未正确推断类型

使Typescribe强制所有对象具有相同的 struct ,从两个选项

有条件地删除区分的联合类型中的属性的可选属性

通过字符串索引访问对象';S的值时出现文字脚本错误

如何使用类型谓词将类型限制为不可赋值的类型?

是否可以针对联合类型验证类型属性名称?

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

如何在省略一个参数的情况下从函数类型中提取参数类型?

确保对象列表在编译时在类型脚本中具有唯一键

将带有样式的Reaction组件导入到Pages文件夹时不起作用

为什么特定的字符串不符合包括该字符串的枚举?

如何从接口中省略接口

三重融合视角与正交阵

重写返回任何

在TS泛型和记录类型映射方面有问题

元组解释