Typescript:由对象键组成的类型无法用作此对象的键

6
我有一个函数,它接收一个参数,这个参数是一个对象。该函数将返回另一个对象,其中一个属性是传递给函数的对象的值。当我使用obj[key]符号表示法获取对象的值时,出现问题。
我明白,key必须是适当的类型才能像这样使用。如果在接口中没有[key:string]:any,我不能使用简单的字符串作为键。但这不是问题所在。我的键是传递对象中键的字符串联合:'user' | 'name'等。
interface Twitter<T> {
  name: T;
  user: T;
  sex: T extends string ? boolean : string;
}
interface Facebook<T> {
  appName: T;
  whatever: T;
  imjustanexapmle: T;
}

type TwFbObjKeys = keyof Twitter<string> | keyof Facebook<string>

我的翻译如下所示:

我的函数长这个样子:

public static getArrayOfObjects(
        queryParameters: Twitter<string> | Facebook<string>,
    ) {
        const keys = Object.keys(queryParameters) as TwFbObjKeys[];
        return keys.map((paramId) => ({
            paramId,
            value: queryParameters[paramId],
        }));
    }

我期望使用参数id作为对象的键,该参数类型为'name' | 'sex' | 'appName' | ...,不会引发错误。但是不幸的是,我遇到了以下错误:

TS7053:由于'type'类型的表达式'name' | 'sex' | 'appName' | ...不能用于索引Twitter | Facebook类型,因此元素隐式具有任意类型。 Twitter | Facebook上不存在“name”属性。

我已经花费几个小时在解决这个问题。您有什么解决方法吗?


你如何调用这个方法?是 getArrayOfObjects('appName') 还是 getArrayOfObjects({appName: 'asd', .... }) - John
1
getArrayOfObject({name: 'Jon', sex: 'male', ...}) 这个问题被 @ford04 回答了。 - Zenek Wiaderko
3个回答

9
更好地定义函数参数queryParameters,将其作为泛型类型T进行约束,约束条件为<T extends Twitter<string> | Facebook<string>>,而不是联合类型Twitter<string> | Facebook<string>
function getArrayOfObjects<T extends Twitter<string> | Facebook<string>>(
    queryParameters: T
) {
    const keys = Object.keys(queryParameters) as (keyof T)[];
    return keys.map((paramId) => ({
        paramId,
        value: queryParameters[paramId],
    }));
}

游乐场

说明

使用泛型可以保持传入参数 queryParameters 的类型,并确保它是 Twitter<string> | Facebook<string> 的子类型。现在,类型为 keyof TparamId 可以访问 queryParameters 属性。

如果使用联合类型 Twitter<string> | Facebook<string>queryParameters 仍然未确定,并且在函数体中仍可能是 Twitter<string>Facebook<string>。这会导致 keyof 运算符和属性访问在您的情况下存在问题,因为您只能访问联合类型给定的所有成分的 公共 属性键。而对于 Twitter<string>Facebook<string>,不存在公共属性。

为了更好地说明问题,您可能想将 TwFbObjKeys 定义为

// "name" | "user" | "sex" | "appName" | "whatever" | "imjustanexapmle"
type TwFbObjKeys = keyof Twitter<string> | keyof Facebook<string>

// not this: keyof only applies to Twitter<string> here: "name" | "user" | "sex" | Facebook<string>
type TwFbObjKeys_not = keyof Twitter<string> | Facebook<string>

但仅此还不足以解决问题。例如:
declare const queryParameters: Twitter<string> | Facebook<string>

// type keysOfQueryParameters = never
type keysOfQueryParameters = keyof typeof queryParameters

// ok, let's try to define all union keys manually
type TwFbObjKeys = "name" | "user" | "sex" | "appName" | "whatever" | "imjustanexapmle"
declare const unionKeys: TwFbObjKeys

// error: doesn't work either
queryParameters[unionKeys]

// this would work, if both Twitter and Facebook have "common" prop
queryParameters["common"]

游乐场

通过使用通用类型,我们可以使用 keyof T 安全地访问传入的 queryParameters 键。

2
谢谢!在这里“复制”代码时,缺少keyof是我的一个笔误,但它仍然无法正常工作(正如您所解释的那样)。当我使用您的改进时,错误就不再存在了。感谢您详尽的回答。不仅提供了解决方案,而且真正回答了问题。现在不仅我的代码没有错误,而且我还学到了新东西。我非常感激。 - Zenek Wiaderko

1

我认为你的问题来自于那一行

type TwFbObjKeys = keyof Twitter<string> | Facebook<string>
// is of type type TwFbObjKeys = "name" | "user" | "sex" | Facebook<string>

你可能想要。
type TwFbObjKeys = keyof Twitter<string> | keyof Facebook<string>
// of type type TwFbObjKeys = "name" | "user" | "sex" | "appName" | "whatever" | "imjustanexapmle"

谢谢你发现了这个问题!在我的代码中,我使用了 keyof Facebook<string>。但问题出在其他地方。我会更新我的问题。 - Zenek Wiaderko

1
假设你的意思是type TwFbObjKeys = keyof Twitter<string> | keyof Facebook<string>
Typescript会报错,因为paramId的类型包含两个接口的所有键: 'name' | 'user'| 'sex' | 'appName' | 'whatever' | 'imjustanexapmle'queryParameters只能是一个接口的类型: 'name' | 'user'| 'sex' 或者 'appName' | 'whatever' | 'imjustanexapmle' 为了解决这个问题,你应该分别使用这两种类型。
interface Twitter<T> {
  name: T;
  user: T;
  sex: T extends string ? boolean : string;
}
interface Facebook<T> {
  appName: T;
  whatever: T;
  imjustanexapmle: T;
}

function isTwitter<T>(val: Twitter<T> | Facebook<T>): val is Twitter<T> {
  return (
    'name' in (val as Twitter<T>) &&
    'user' in (val as Twitter<T>) &&
    'sex' in (val as Twitter<T>) 
  )
}

function isFacebook<T>(val: Twitter<T> | Facebook<T>): val is Facebook<T> {
  return (
    'appName' in (val as Twitter<T>) &&
    'whatever' in (val as Twitter<T>) &&
    'imjustanexapmle' in (val as Twitter<T>) 
  )
}

function getValue<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

function getArrayOfObjects(
    queryParameters: Twitter<string> | Facebook<string>,
) {
  if (isTwitter(queryParameters)) { // if queryParameters is of type Twitter<string>
    const keys = Object.keys(queryParameters);
    return keys.map((paramId) => ({
      paramId,
      value: getValue(queryParameters, paramId as keyof Twitter<string>)
    }));
  } else if (isFacebook(queryParameters)) { // if queryParameters is of type Facebook<string>
    const keys = Object.keys(queryParameters);
    return keys.map((paramId) => ({ 
      paramId,
      value: getValue(queryParameters, paramId as keyof Facebook<string>)
    }));
  } else {  // if queryParameters is invalid
    throw new Error("invalid queryParameters!")
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接