如何在实用程序类型上定义 属性?

How to define a property on a utility type?


试图通过任何可用的方式进行类型推断


所以我有一个将数组作为参数的函数。该数组包含未知对象。我们确实知道对象都是一样的,所以换句话说,数组只能接受一种类型的对象,但该对象类型是动态的,它几乎可以是任何类型。我说几乎是因为我们知道更多信息:对象都有一个名称 属性.


总结:


该函数的数组参数接受一个对象数组,这些对象都是相同类型,但类型未知。我们所知道的是该类型包含一个名称 属性,因此;我们知道每个对象都有一个名字 属性.



好的,现在让我们看看函数:


type ObjType = {
  name: string;
  [key: string]: any;
}

type NewObj = {
  [key: string]: {}
}

export function nameArray(arrParam: ObjType[]){
  let obj: NewObj = {};

  arrParam.forEach((item, index)=>{
    obj[item.name] = item;
  });

  return obj;
}


关于函数和类型


因为我们知道对象都有一个名字属性,我们可以用它为一个键分配一个合适的名字,然后将对象分配给那个键。这样我们就有了可枚举数组,以及一个我们可以用来按名称引用对象的对象。


功能非常棒!


好吧,我撒谎了,老实说我不是故意的。该功能确实有效,但我认为 wonderwood 是一个糟糕的措辞选择。类型推断没有在函数中流动,所以,即使它有效,我在使用它时也得不到类型信息,这很糟糕,因为 TS 需要很多额外的工作,如果我不这样做,那又有什么意义在脚本的复杂方面结束类型。


我正在实际项目中使用它。这是我测试它的文档。

import { nameArray } from './util-fn.mjs';

type StrGenArg = {
    // INDEX @00
    name: string,
    arg: string,
    code: string | number;
  }

type StrGenArgs = StrGenArg[];

export const strGenArg: StrGenArgs = [
  { // INDEX @00 | "escOpen"
    name: 'escOpen',
    arg:  '%%',
    code: '\x1b'
  },
  { // INDEX @01 | "escClose"
    name: 'escClose',
    arg:  '%%',
    code: '\x1b'
  },
  { // INDEX @02 | "reset"
    name: 'reset',
    arg:  '0',
    code: 0
  },
  { // INDEX @03 | "red"
    name: 'red',
    arg:  '%r',
    code: 31
  },
  { // INDEX @04 | "green"
    name: 'green',
    arg:  'g',
    code: 32
  },
  { // INDEX @05 | "yellow"
    name: 'yellow',
    arg:  'y',
    code: 33
  }
];

export const args = nameArray(strGenArg);

正如我所说的那样有效;例如,我会执行以下操作。

console.log(args.yellow)

它打印黄色,红色和绿色以及其他对象也一样,但是,正如我已经说过的:没有类型!

我试过使用这样的实用程序类型。


function nameArray<T>(arrParam: T[]){
  let obj: NewObj = {};

  arrParam.forEach((item, index)=>{
    obj[item.name] = item;
  });
}

但是当我以这种方式编写函数时,它不知道 T 有一个命名的 属性,因此,TSC 显示错误,并且不会编译。

有没有写这个函数的地方我可以得到类型推断?


您需要限制 T 始终有一个 属性 name。这将强制传递给函数的数组的任何元素必须具有 name 属性.

function nameArray<T extends { name: string }>(arrParam: T[]){ /* ... */ }

但这并不能完全解决您的问题。例如,您声明您想要访问 args.yellow。那我们该怎么做呢?

这有点复杂。

首先我们必须将as const添加到strGenArgs,这样我们就不会在这里丢失类型信息:

export const strGenArg = [
  { // INDEX @00 | "escOpen"
    name: 'escOpen',
    arg:  '%%',
    code: '\x1b'
  },
  { // INDEX @01 | "escClose"
    name: 'escClose',
    arg:  '%%',
    code: '\x1b'
  },
  { // INDEX @02 | "reset"
    name: 'reset',
    arg:  '0',
    code: 0
  },
  { // INDEX @03 | "red"
    name: 'red',
    arg:  '%r',
    code: 31
  },
  { // INDEX @04 | "green"
    name: 'green',
    arg:  'g',
    code: 32
  },
  { // INDEX @05 | "yellow"
    name: 'yellow',
    arg:  'y',
    code: 33
  }
] as const;

我们还引入了泛型类型 K,它将包含所有可能的名称值。之后我们return一个Record<K, StrGenArg>.

type ExtractName<T extends { name: string }[]> = T[number]["name"]

function nameArray<
  T extends { name: string }[], 
  K extends ExtractName<T>
>(arrParam: readonly [...T]): Record<K, StrGenArg> {
  let obj: Record<string, any> = {};

  arrParam.forEach((item, index)=>{
    obj[item.name] = item;
  });

  return obj
}

现在可以输入了:

export const args = nameArray(strGenArg);

args.yellow // works with typing

Playground