Javascript 将函数转换为使用typescript类型

Javascript 将函数转换为使用typescript类型,javascript,typescript,Javascript,Typescript,我的职责如下: const selector = (definitions, query = {}) => { const select = {}; Object.keys(definitions).forEach((key) => { if (typeof query[key] !== 'undefined') { if (definitions[key].validation(query[key])) { if (typeof def

我的职责如下:

const selector = (definitions, query = {}) => {
  const select = {};

  Object.keys(definitions).forEach((key) => {
    if (typeof query[key] !== 'undefined') {
      if (definitions[key].validation(query[key])) {
        if (typeof definitions[key].convert !== 'undefined') {
          select[key] = definitions[key].convert(query[key], key);
        } else {
          select[key] = query[key];
        }
      } else if (typeof definitions[key].default !== 'undefined') {
        select[key] = definitions[key].default;
      }
    } else if (typeof definitions[key].default !== 'undefined') {
      select[key] = definitions[key].default;
    }
  });

  return select;
};
这个类型脚本报告我需要正确地键入
定义
查询
,但我真的不知道如何键入

错误消息。
元素隐式具有“any”类型,因为类型“{}”没有索引签名。

我怎么做


用法:

const-config={
姓名:{
验证:val=>val.length>=3
},
第页:{
验证:val=>Number.isInteger(parseInt(val,10)),
默认值:1,
},
限制:{
默认值:10,
}
}
常量myQuery={
姓名:‘da’,
第页:'不可解开',
另一个:"1",,
}
选择(配置,myQuery)
试验

const expected=JSON.stringify({
页码:1,
限额:10,
})
const output=JSON.stringify(选择器(config,myQuery))
如果(预期!==输出){
抛出新错误(`输出错误。\n预期:${expected}\n输出:${output}`)
}
使用情况:


注意:
验证
是必需的,但
转换
默认
是可选参数。

索引签名是指当您指定对象的各个键将对应于某一类型时,即使您事先不知道这些键将是(或不想枚举它们)哪些键

例如,您的definitions参数可能定义如下:

interface definition {
  validation?: (arg: any) => boolean;
  convert?: (arg0: any, arg1: any) => any;
  default?: number;
}

interface definitionsCollection {
  [key: string]: definition; // <--- this is an index signature
}

const selector = (definitions: definitionsCollection, query = {}) => {
  // ... etc
}

定义
参数提供了一个很好的解决方案。我们可以对其进行一些增强,使其更为通用和明确,并为
查询
参数提供一个类型:

type QueryValue = any;
type Query = { [key: string]: QueryValue };

type Definition<T1, T2=T1> = {
  validation?: (value: QueryValue) => boolean;
  convert   ?: (value: QueryValue, key: string) => T1;
  default   ?: T2;
};
我们可以使用一些可以在编辑器中评估的“测试类型”来验证
SelectorResult
类型。结果很好,但如果合并了交点类型,效果会更好:

type Test1 = SelectorResult<{ a: 'invalid' }>;
// Not a `Definition<T>`
// => Expected `{}`
// >> Received `{} & {}` : OK

type Test2 = SelectorResult<{ a: any }>;
// ~`Definition<unknown>`
// => Expected `{ a?: unknown }`
// >> Received `{ a: {} } & { a?: unknown }` : ~OK

type Test3 = SelectorResult<{ a: WithConvert<any> }>;
// `convert` alone is ignored.
// => Expected `{}`
// >> Received `{} & {}` : OK

type Test4 = SelectorResult<{ a: WithDefault<number> }>;
// `default` ensures the key is kept and provides the value type: `number`.
// => Expected `{ a: number }`
// >> Received `{ a: number } & {}` : OK

type Test5 = SelectorResult<{ a: WithDefault<number> & WithConvert<any> }>;
// Idem Test 4: `convert()` still ignored, only `default` matters.
// => Expected `{ a: number }`
// >> Received `{ a: number } & {}` : OK

type Test6 = SelectorResult<{ a: WithValidation }>;
// `validation()` alone makes the key optional and the value type `unknown`.
// => Expected `{ a?: unknown }`
// >> Received `{} & { a?: unknown }` : OK

type Test7 = SelectorResult<{ a: WithValidation & WithConvert<string> }>;
// Idem Test6 + `convert()` providing the value type: `string`
// => Expected `{ a?: string }`
// >> Received `{} & { a?: string | undefined }` : OK

type Test8 = SelectorResult<{ a: WithDefault<number>; b: 'invalid'; c: WithValidation }>;;
// Combining several keys from different cases: a (Test4), b (Test1), c (Test6)
// => Expected `{ a: number; c?: unknown }`
// >> Received `{ a: number } & { c?: unknown }` : OK
type Test1=SelectorResult;
//不是`定义`
//=>应为`{}`
//>>收到`{}&{}`:OK
类型Test2=SelectorResult;
//~`定义`
//=>应为{a?:未知}`
//>>收到`{a:{}}&{a?:未知}`:~确定

type Test3=SelectorResult
typescript typings
tag用于与第三方库的拉入类型定义相关的问题。不清楚,但这个问题似乎不需要这个标签。哦,谢谢你这么说,我将删除它。你能提供一个或多个用例示例plz吗<代码>常量结果=选择器({tbd},{tbd});预期(结果)。toEqual(待定)。因此,可以为输入参数
定义
查询
以及输出设置适当的类型。然后Nicholas的答案可以适合其他更通用的解决方案(不仅仅是字符串)。基于此,我添加了我自己的回答,虽然不完全准确,但我认为仍然很有趣。键入
(any)=>boolean
也将生成
隐式'any'
错误,它应该是
(arg:any)=>boolean
(或者更好的做法是给参数指定有意义的名称,而不是
arg
)。感谢您的更正。我已经将它更新为
(arg:any)
,尽管我同意他们应该给它更多有意义的名称和类型(我只是不知道这些名称和类型是什么)。我还有一个问题,请检查这个示例@NicholasTower@DavidCosta我的答案是在你们添加数据示例之前给出的。所以我不得不猜一些类型,我猜错了。我已经更新了答案,将默认设置为一个数字,并将所有属性设置为可选。
type WithValidation = { validation: (value: QueryValue) => boolean; }
type WithConvert<T> = { convert   : (value: QueryValue, key: string) => T; }
type WithDefault<T> = { default   : T; }

type SelectorResult<D> = {
  [P in {
    [K in keyof D]:
      D[K] extends WithDefault<any> ? K : never
  }[keyof D]]:
    D[P] extends WithDefault<infer T> ? T : never;
} & {
  [P in {
    [K in keyof D]:
      D[K] extends WithDefault<any> ? never :
      D[K] extends WithConvert<any> ?
      D[K] extends WithValidation   ? K : never :
      D[K] extends Definition<any>  ? K : never
  }[keyof D]]?:
    D[P] extends WithValidation & WithConvert<infer T> ? T :
    D[P] extends Definition<any> ? unknown : never;
};
type Test1 = SelectorResult<{ a: 'invalid' }>;
// Not a `Definition<T>`
// => Expected `{}`
// >> Received `{} & {}` : OK

type Test2 = SelectorResult<{ a: any }>;
// ~`Definition<unknown>`
// => Expected `{ a?: unknown }`
// >> Received `{ a: {} } & { a?: unknown }` : ~OK

type Test3 = SelectorResult<{ a: WithConvert<any> }>;
// `convert` alone is ignored.
// => Expected `{}`
// >> Received `{} & {}` : OK

type Test4 = SelectorResult<{ a: WithDefault<number> }>;
// `default` ensures the key is kept and provides the value type: `number`.
// => Expected `{ a: number }`
// >> Received `{ a: number } & {}` : OK

type Test5 = SelectorResult<{ a: WithDefault<number> & WithConvert<any> }>;
// Idem Test 4: `convert()` still ignored, only `default` matters.
// => Expected `{ a: number }`
// >> Received `{ a: number } & {}` : OK

type Test6 = SelectorResult<{ a: WithValidation }>;
// `validation()` alone makes the key optional and the value type `unknown`.
// => Expected `{ a?: unknown }`
// >> Received `{} & { a?: unknown }` : OK

type Test7 = SelectorResult<{ a: WithValidation & WithConvert<string> }>;
// Idem Test6 + `convert()` providing the value type: `string`
// => Expected `{ a?: string }`
// >> Received `{} & { a?: string | undefined }` : OK

type Test8 = SelectorResult<{ a: WithDefault<number>; b: 'invalid'; c: WithValidation }>;;
// Combining several keys from different cases: a (Test4), b (Test1), c (Test6)
// => Expected `{ a: number; c?: unknown }`
// >> Received `{ a: number } & { c?: unknown }` : OK
const keySelector = <D, Q>(definitions: D, query: Q, key: string) => {
  const val = query[key as keyof Q];
  const def = definitions[key as keyof D] as Definition<D[keyof D]>;

  if (typeof val !== 'undefined' &&
      typeof def.validation !== 'undefined' &&
      def.validation(val)) {
    return typeof def.convert !== 'undefined'
        ? { [key]: def.convert(val, key) }
        : { [key]: val };
  }

  return typeof def.default !== 'undefined'
      ? { [key]: def.default }
      : {};
};

const safeSelector = <D>(definitions: D, query: Query) =>
  Object.keys(definitions).reduce(
    (result, key) => Object.assign(result, keySelector(definitions, query, key)),
    {} as SelectorResult<D>);

const selector = <D>(definitions: D, query: Query = {}) =>
  safeSelector(definitions, query || {});
// Tests (with colors for Chrome/Firefox JavaScript Console)
test('Skip key not defined', {}, { a: 1 }, {});
test('Empty definition', { a: {} }, { a: 1 }, {});
test('Use default when key is missing', { a: { default: 10 } }, {}, { a: 10 });
test('Keep key with valid definitions', { a: { default: 10 }, b: { ko: 'def' } }, {}, { a: 10 });

test('Use default when value is not valid ', { a: { default: 10, validation: () => false } }, { a: 1 }, { a: 10 });
test('Use value when it is valid          ', { a: { default: 10, validation: () => true  } }, { a: 1 }, { a: 1 });
test('Use converted value when it is valid', { a: { convert: (a: number, k: string) => `${k}=${a * 2}`, validation: () => true } }, { a: 1 }, { a: 'a=2' });

function test<D>(message: string, definitions: D, query: Query, expected: SelectorResult<D>) {
  const actual = selector(definitions, query);
  const args = [expected, actual].map(x => JSON.stringify(x));
  if (args[0] === args[1]) {
    console.log(`%c ✔️ ${message}`, 'color: Green; background: #EFE;');
  } else {
    console.log(`%c ❌ ${message} KO : Expected ${args[0]}, Received ${args[1]}`, 'color: Red; background: #FEE;');
  }
}