Typescript:作为对象键的并集的类型不能用作该对象的键

时间:2019-11-07 07:09:49

标签: typescript

我有一个用一个参数调用的函数。此参数是对象。此函数返回另一个对象,其中一个属性是传递给函数对象的值。当我尝试使用obj [key]表示法检索对象的值时遇到问题。

我确实知道,密钥必须是正确的类型,才能像这样使用。我不能使用简单的键作为字符串,如果在接口中我没有[key:string]:any。但这不是问题。我的键是作为传递对象中的键的字符串的并集:'user'| '名称'等

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],
        }));
    }

我希望使用类型为'name'|的paramId。 “性别” | 'appName'| ... 作为具有此键的对象的键不会引发错误。但不幸的是我有错误:

TS7053:元素隐式地具有“ any”类型,因为类型为“ name”的表达式| “性别” | 'appName'| ...不能用于索引Twitter |脸书 类型“ Twitter”上不存在属性“名称” Facebook

我正在和它战斗几个小时。知道我该如何解决吗?

3 个答案:

答案 0 :(得分:3)

最好将函数参数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],
    }));
}

Playground

说明

通过使用泛型,您可以保留queryParameters的传入参数的类型,并确保它是Twitter<string> | Facebook<string>的子类型。类型为paramId的{​​{1}}现在可以访问keyof T的属性。

使用并集类型queryParameters时,Twitter<string> | Facebook<string>仍未确定,并且在函数体中可能仍为queryParametersTwitter<string>。在您的情况下,这会导致Facebook<string>运算符和属性访问出现问题,因为您只能访问给定联合类型的所有成分的 common 属性键。并且keyofTwitter<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>

Playground

借助通用类型,我们可以使用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键。

答案 1 :(得分: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"

答案 2 :(得分:1)

(假设您的意思是type TwFbObjKeys = keyof Twitter<string> | keyof Facebook<string>

Typescript引发错误,因为paramId的类型包含两个接口的所有键:

“名称” | '用户'| “性别” | 'appName'| “随便什么” | 'imjustanexapmle'

但是queryParameters只能是任一接口的类型:

“名称” | '用户'| “性别”

“ appName” | “随便什么” | '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!")
  }
}