如何将以下 TypeScript 逻辑转换为函数?

How to convert the following TypeScript logic into a function?

所以,我最初想做以下转换:

import { ComponentType } from 'react';
import { Component1, Component2 } from './components';

const components = {
  foo: Component1,
  bar: Component2,
};

// Into

const componentLookup = {
  foo: {
    name: 'foo',
    component: Component1
  },
  bar: {
    name: 'bar',
    component: Component2
  },
};

因此,我创建了以下 TS 逻辑:

const components = {
  foo: Component1,
  bar: Component2,
};

type ComponentName = keyof typeof components;
type ComponentLookup = {
  [key in ComponentName]: {
    name: ComponentName;
    component: ComponentType;
  };
};

let componentLookup: ComponentLookup = {} as ComponentLookup;
(Object.keys(components) as ComponentName[]).forEach((name) => {
  componentLookup[name] = { name: name, component: components[name] };
});

// Intellisense picks up keys
componentLookup.foo;
componentLookup.bar;

最后,我决定创建一个函数 createLookups 来获取组件对象并执行逻辑,但是,智能感知有问题。

const createLookups = (components: {
  [name: string]: ComponentType;
}): {
  [key in keyof typeof components]: {
    name: keyof typeof components;
    component: ComponentType;
  };
} => {
  type ComponentName = keyof typeof components;
  type ComponentLookup = {
    [key in ComponentName]: {
      name: ComponentName;
      component: ComponentType;
    };
  };

  let componentLookup: ComponentLookup = {} as ComponentLookup;
  (Object.keys(components) as ComponentName[]).forEach((name) => {
    componentLookup[name] = { name: name, component: components[name] };
  });

  return componentLookup;
};

如果在同一个文件中定义了 createLookups 并且我调用了 createLookups(components),intellisense 会在 foobar 上启动;但是,如果 createLookups 是在另一个文件中定义的,它不会轻易地选择 foo/bar 键。

这是 TypeScript 还是我的编辑器 (WebStorm) 的问题?

如果您以一般方式解决此问题(将对象的条目(key-value 对)映射到任意 属性 名称),那么您可以将其应用于您的特定需求(映射属性 名称和组件值),使用 currying and generic parameter constraint:

的组合

TS Playground

import {type ComponentType} from 'react';

type MappedEntries<
  ObjectType extends Record<string, unknown>,
  KeyProp extends string,
  ValueProp extends string,
> = {
  [K in keyof ObjectType]: (
    Record<KeyProp, K>
    & Record<ValueProp, ObjectType[K]>
  );
};

function mapEntriesToKeysInObjects <
  ObjectType extends Record<string, unknown>,
  KeyProp extends string,
  ValueProp extends string,
>(obj: ObjectType, keyProp: KeyProp, valueProp: ValueProp): MappedEntries<ObjectType, KeyProp, ValueProp> {
  return (Object.keys(obj) as (keyof ObjectType)[]).reduce((mapped, key) => {
    mapped[key] = {
      [keyProp]: key,
      [valueProp]: obj[key],
    } as Record<KeyProp, keyof ObjectType> & Record<ValueProp, ObjectType[keyof ObjectType]>;
    return mapped;
  }, {} as MappedEntries<ObjectType, KeyProp, ValueProp>);
}

// Let's test it:

const mapped = mapEntriesToKeysInObjects({first: 1, last: 2}, 'name', 'component');
mapped.first.name; // "first"
mapped.first.component; // number
mapped.last.name; // "last"
mapped.last.component; // number

// Looks good

// Now apply it to your function:
const createLookups = <T extends Record<string, ComponentType>>(components: T) => {
  return mapEntriesToKeysInObjects(components, 'name', 'component');
};