简体   繁体   English

通过对象嵌套键的动态字符串文字访问嵌套属性值

[英]Access nested property value via dynamic string literals of object's nested keys

I would like to access a value (object) via the keys of a nested object.我想通过嵌套的 object 的键访问一个值(对象)。

Here is an example.这是一个例子。 I have an object that holds a dynamic number of car brands.我有一个拥有动态数量的汽车品牌的 object。 Each brand has a dynamic number of models.每个品牌都有动态数量的模型。 Each model has an object with information about the car.每个 model 都有一个 object 与有关汽车的信息。

Please see this TS playground for a full example . 请参阅此 TS 操场以获取完整示例

type CarInfo = {
  size: number;
  color: string;
};

type Cars = {
  // brand
  [brandKey: string]: {
    // model
    [modelKey: string]: {
      size: number;
      color: string;
    };
  };
};

const exampleCars: Cars = {
  mercedes: {
    c: { size: 123, color: "green" },
  },
};

In reality, I can't give this object a type, because I want to use the keys as string literals, which would be overwritten by the string union.实际上,我不能给这个 object 一个类型,因为我想将键用作字符串文字,这将被string联合覆盖。 So my cars look like this:所以我的车是这样的:

const CARS = {
  mercedes: {
    c: {
      size: 333,
      color: "red",
    },
    ...
  },
  ...
} as const;

type Cars = typeof CARS;

With that, I can "extract" the string literals and use them in a function to get one specific car info via the brand key and the model key:这样,我可以“提取”字符串文字并在 function 中使用它们,以通过品牌密钥和 model 密钥获取特定的汽车信息:

function getCarInfo<
  TBrandKey extends keyof Cars,
  TModelKey extends keyof Cars[TBrandKey]
>(brandKey: TBrandKey, modelKey: TModelKey) {
  const brandEntry = CARS[brandKey];
  const carInfo = brandEntry[modelKey];

  return carInfo;
}

Please note that the there is no explicit return, but only an implicit return - more on that further down below.请注意,没有显式返回,而只有隐式返回 - 更多内容请参见下文。

This now works when calling the function with explicit string literals:这现在在使用显式字符串文字调用 function 时有效:

// works
const car1 = getCarInfo("mercedes", "c");
// works (fails as expected)
const car2 = getCarInfo("audi", "wrong-on-purpose");

// works
const color = car1.color

But instead of using fixed string literals, I want to give them through as props of a React component:但是我不想使用固定的字符串文字,而是想将它们作为 React 组件的道具提供:

function Car<
  TBrandKey extends keyof Cars,
  TModelKey extends keyof Cars[TBrandKey]
>(props: { brand: TBrandKey; model: TModelKey }): JSX.Element {
  // works
  const carInfo = getCarInfo(props.brand, props.model);
  // does not work
  const carInfo2 = CARS[brand][model];

  return (
    <>
      {/* does not work */}
      <p>{carInfo.color}</p>
    </>
  );
}

// works
<Car brand="mercedes" model="c" />;

Here, the props of the component work as expected.在这里,组件的 props 按预期工作。 They eg give autocomplete for the "model" when providing the "brand".例如,他们在提供“品牌”时为“模型”提供自动完成功能。

But I cannot get the car info.但我无法获取汽车信息。

  • When having an implicit return of getCarInfo , TS doesn't know that color exists on carInfo .当隐式返回getCarInfo时,TS 不知道carInfo上存在color
  • When giving getCarInfo an explicit return ( CarInfo ), the function itself does not work, as I whatever is found via brandEntry[modelKey] is not of type CarInfo in TypeScript's world.当给getCarInfo显式返回 ( CarInfo ) 时, function 本身不起作用,因为我通过brandEntry[modelKey]找到的任何东西都不是 TypeScript 世界中的CarInfo类型。

What am I missing?我错过了什么?

Implementing generic functions in a way that TypeScript can check can be difficult.以 TypeScript 可以检查的方式实现通用功能可能很困难。 I generally recommend creating wider types for use in the implementation.我通常建议创建更广泛的类型以在实现中使用。

Here are a few options:这里有几个选项:

function Car<
  TBrandKey extends keyof Cars,
  TModelKey extends keyof Cars[TBrandKey] & string
>(props: { brand: TBrandKey; model: TModelKey }): JSX.Element {
  let CARS2: Record<string, Record<string, CarInfo>> = CARS;
  const car = CARS2[props.brand][props.model];
  return <p>{car.color}</p>;
}

This requires TModelKey to extend string in addition to the keyof because TS doesn't track that there's no symbol key on the object through the indirection of the first generic.除了keyof之外,这还需要TModelKey扩展string ,因为 TS 不会通过第一个泛型的间接跟踪来跟踪 object 上没有符号键。

function Car<
  TBrandKey extends keyof Cars,
  TModelKey extends keyof Cars[TBrandKey]
>(props: { brand: TBrandKey; model: TModelKey }): JSX.Element {
  let CARS2: Record<string, Record<PropertyKey, CarInfo>> = CARS;
  const car = CARS2[props.brand][props.model];
  return <p>{car.color}</p>;
}

Instead of & string , you could also widen the CARS2 type even more.除了& string之外,您还可以进一步扩大CARS2类型。


TypeScript also supports overloaded functions, and a really useful trick occasionally is to define an overloaded function which only has one visible signature, with an easier to work with implementation signature. TypeScript 也支持重载函数,有时一个非常有用的技巧是定义一个重载的 function,它只有一个可见的签名,更容易使用实现签名。

This doesn't work great here, but is a useful trick to know:这在这里效果不佳,但知道一个有用的技巧:

function Car<
  TBrandKey extends keyof Cars,
  TModelKey extends keyof Cars[TBrandKey] & string
>(props: { brand: TBrandKey; model: TModelKey }): JSX.Element
function Car(props: { brand: keyof Cars, model: keyof Cars[keyof Cars] }): JSX.Element {
  const car = CARS[props.brand][props.model];
  //    ^? const car: never - because keyof on an intersection requires common keys
}
``

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM