繁体   English   中英

typescript:根据回调返回类型验证函数参数

[英]typescript: validate function argument based on callback return type

我正在尝试键入一个函数,该函数为每个数组项运行回调并使用新属性扩展该项:

interface Row {
    a: number
}
export function extender<
  Data extends Row[],
  T extends object,
  Props extends keyof T
  >(data: Data, extend: (row: Row) => T, newProp?: Props): (T & Row)[] {
      return data.map(row => ({
          ...row,
          ...extend(row)
      }))
}

当我打电话时,这份草稿似乎正在发挥作用

extender([{ a: 1 }, { a: 2 }], () => ({ once: 1, more: 2 }), 'once')

但是,一旦我尝试在extend回调中使用row参数,我就会得到一个错误Argument of type '"once"' is not assignable to parameter of type 'undefined'.

// TS_ERROR: Argument of type '"once"' is not assignable to parameter of type 'undefined'.
extender([{ a: 1 }, { a: 2 }], (row) => ({ once: 1, more: row.a * 2 }), 'once')

我错过了什么?

概括

在您的示例中, Tobject (实际上代表所有 non-primitives )的约束。 “任何普通对象”的类型可以表示为Record<PropertyKey, any> (它使用类型实用程序Record<Keys, Type> )。 PropertyKeystring | number | symbol string | number | symbol string | number | symbol ,所以这种类型的意思是“任何具有字符串、数字和/或符号键的对象,这些属性的值可以是任何类型”。

通过简单地替换该约束,您的代码应该可以在没有诊断错误的情况下编译。


更多的

如果输入数组的成员不完全Row类型(相反,它们扩展了它),则函数的当前返回类型会丢失一些类型信息,并且它不承认T可以覆盖类型的概念在map操作中对象合并过程中的属性a 映射类型可以帮助您保留此类类型信息,甚至可以保留诸如具有明确定义的元素数量(元组)的输入数组的长度之类的内容。 考虑以下示例:

TS游乐场

type PlainObject = Record<PropertyKey, any>;
type Mutable<T extends PlainObject> = { -readonly [K in keyof T]: T[K] };

type ExtendedTuple<A extends readonly PlainObject[], T extends PlainObject> = {
  -readonly [Index in keyof A]: Mutable<Omit<A[Index], keyof T>> & Mutable<T>;
}

type Row = { a: number };

export function extender <
  Data extends readonly Row[],
  T extends PlainObject,
  Prop extends keyof T,
>(data: Data, extend: (row: Data[number]) => T, newProp?: Prop): ExtendedTuple<Data, T> {
  return data.map(row => ({ ...row, ...extend(row) })) as unknown as ExtendedTuple<Data, T>;
}

const extended1 = extender(
  [{ a: 1 }, { a: 2 }],
  () => ({ once: 1, more: 2 }),
  'once',
); // Record<'a' | 'more' | 'once', number>[]

const extended2 = extender(
  [{ a: 1 }, { a: 2 }],
  (row) => ({ once: 1, more: row.a * 2 }),
  'once',
); // Record<'a' | 'more' | 'once', number>[]

const [
  e2o0, // Record<'a' | 'more' | 'once', number> | undefined
  e2o1, // Record<'a' | 'more' | 'once', number> | undefined
  e2o2, // Record<'a' | 'more' | 'once', number> | undefined
  e2o3, // Record<'a' | 'more' | 'once', number> | undefined
  e2o4, // Record<'a' | 'more' | 'once', number> | undefined
  // etc...
] = extended2;


// Tuple correctness:

const extended3 = extender(
  [{ a: 1, b: 1 }, { a: 2, b: 2 }, { a: 3, b: 3 }] as [Record<'a' | 'b', number>, Record<'a' | 'b', number>, Record<'a' | 'b', number>],
  (row) => ({ a: 'hi', once: 1, more: row.a * 2 }),
  'once',
); /* [
  { a: string } & Record<'b' | 'more' | 'once', number>,
  { a: string } & Record<'b' | 'more' | 'once', number>,
  { a: string } & Record<'b' | 'more' | 'once', number>,
] */

// Note the values at the tuple indexes are not potentially undefined:
const [
  e3o0, // { a: string } & Record<'b' | 'more' | 'once', number>
  e3o1, // { a: string } & Record<'b' | 'more' | 'once', number>
  e3o2, // { a: string } & Record<'b' | 'more' | 'once', number>
  e3o3, /* This error is expected
  ~~
  Tuple type '[...]' of length '3' has no element at index '3'.(2493) */
] = extended3;

暂无
暂无

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

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