如何根据Map提供的按键定义界面?

时间:2019-07-14 06:48:55

标签: typescript

我是Typescript的新手。

我想提取一个名为Mapper的类。
该映射器采用两个参数:
1.我们要映射的对象数据 2.定义地图后数据形状的地图

它具有一些方法,这些方法将根据我们要执行的映射类型返回MappedData。

class Mapper {
  constructor(data: Data, dataMap: Map) {

  }

  mapKey(): MappedKeyData {
    // return a mappedKeyData
  }
}

// Usage
const source = {
  Name: "Dudi"
}
const map = new Map([
  ['name', 'Name']
])
const mapper = new Mapper(source, map)
console.log(mapper.mapKey()) // {name: 'Dudi'}

问题是我想确保MappedKeyData的类型是一个接口,该接口具有在键映射之后定义的所有键。 (根据地图中定义的键)

例如:

interface MappedKeyData {
  name: 'string'
}

这是使用Typescript的正确方法吗?如果是,该如何实施?谢谢...

1 个答案:

答案 0 :(得分:1)

如果您确实想使用类和Map对象,则可以,但是使用函数和普通的旧javascript对象会更简单。
我将展示一种执行此操作的方法,并且您可以根据需要进行调整(但请注意,TypeScript中的Map并不是您所希望的类型,因此请避免使用它。)

这是mapKeys()函数:

function mapKeys<D extends object, M extends Record<keyof M, keyof D>>(
  data: D,
  dataMap: M
) {
  const ret = {} as { [K in keyof M]: D[M[K]] };
  (Object.keys(dataMap) as Array<keyof M>).forEach(
    <K extends keyof M>(k: K) => {
      ret[k] = data[dataMap[k]];
    }
  );
  return ret;
}

这是它的基本用法:

const mapped = mapKeys(
  {
    Name: "Dudi",
    Age: 123
  },
  { name: "Name", age: "Age" }
);
// const mapped: {
//    name: string;
//    age: number;
// }
console.log(mapped); // {name: 'Dudi', age: 123}

mapKeys()在运行时的工作方式只是循环访问k的键dataMap并将返回对象中的类似属性设置为来自data的数据按键dataMap[k]。这就是ret[k] = data[dataMap[k]]行。

其余大部分是类型注释和断言,以帮助编译器理解和验证我们在做什么。假设data的类型为D,而dataMap的类型为M,则返回类型将为mapped type {[K in keyof M]: D[M[K]] }。这就是说它将具有与M相同的键,并且每个键K的值都将是D[M[K]]类型(即我们look up { K的1}}属性,然后查找M that 属性。

因此,这应该适合您,具体取决于您的用例。


当然有很多警告。在某些情况下,我试图问您想看到什么,我可以肯定的是,如果不是{{的键,则您不希望在D中输入一个值1}}。 constraint dataMap应该保证。但是还有其他可能的边缘情况:

严格键入data

首先,必须相当强地键入M extends Record<keyof M, keyof D>参数才能使其起作用。如果您只是这样做:

dataMap

然后,dataMap最终将被推断为类型const source = { Name: "Didu", Age: 321 }; const map = { name: "Name", age: "Age" }; ,而不是map。而且您会得到一个错误:

{name: string, age: string}

解决此问题的方法是确保{name: "Name", age: "Age"}具有string literal属性值,例如使用const assertion时得到的值:

const badInfer = mapKeys(source, map); // error!
// ┌───────────────────────────> ~~~
// └─ Argument of type '{ name: string; age: string; }' is not assignable
//    to parameter of type 'Record<"name" | "age", "Name" | "Age">'.

请注意,如果我们使用dataMap代替对象,则default TypeScript type declarations for it不够坚固。 const fixedMap = { name: "Name", age: "Age" } as const; // // inferred to be {readonly name: "Name"; readonly age: "Age"; } const fixedInfer = mapKeys(source, fixedMap); // okay, right type now 意味着Map中的任何键都可能映射到Map<K, V>中的任何值。因此,K充其量只能与类型V等效,并且您的输出类型中会有不幸的new Map([["name","Name"],["age","Age"]])

出租物业

没有什么可以阻止您将{name: "Name" | "Age", age: "Name" | "Age"}中的某些键留在string | number中:

data

复制的属性

没有什么能阻止您在dataMap中多次映射const fewerProps = mapKeys({ Name: "Dodi", Age: 132 }, { name: "Name" }); // const fewerProps: { name: string }; // no age console.log(fewerProps); // {name: "Dodi"} 中的相同键:

data

索引签名很奇怪

如果您的dataMap对象具有index signature,则类型系统的行为会很奇怪,因为它将假定const duplicateProps = mapKeys( { Name: "Dido", Age: 231 }, { name: "Name", alsoName: "Name", age: "Age", alsoAge: "Age" } ); // const duplicateProps: {name: string; alsoName: string; age: number; alsoAge: number;} console.log(duplicateProps); // {name: "Dido", alsoName: "Dido", age: 231, alsoAge: 231} 具有与索引匹配的任何可能的键:

data

这不是一个完美的结果,因为类型系统说data具有类型const dictionary: { [k: string]: string | number } = { Name: "Didi", Age: 312 }; const confused = mapKeys(dictionary, { dogs: "Dogs", cats: "Cats", age: "Age" }); // const confused: { dogs: string | number; cats: string | number; age: string | number;} console.log(confused); // {dogs: undefined, cats: undefined, age: 312 } // note that "undefined" can sneak into index signatures 的{​​{1}}属性,但实际上在运行时是confused。这是索引签名的陷阱之一,我们的映射器也陷入其中。


如果您决定要查看的内容并放入更复杂的类型断言和注释,则可以解决其中一些警告,但是我想我会在这里停留,并给您补充任何补丁。希望这足以给您一些指导。祝你好运!

Link to code