繁体   English   中英

Typescript:在union中指定多个回调类型

[英]Typescript: specifying multiple callback types in a union

我正在将我使用的一些东西转换为打字稿,而且我有点不知所措。 我创建了一个接受数组和对象的通用foreach函数,并采用几种不同类型的回调来处理迭代。 但是,如果它接受一些不同的回调,我不知道如何指定类型。 我需要能够从函数返回boolean或void,它应该能够传入(any),(any,int)或(string,any)。 这是我到目前为止所得到的。

function foreach(obj : Array<any> | Object, func : (
            ((any) => boolean) |
            ((any) => void) |
            ((any, int) => boolean) |
            ((any, int) => void) |
            ((string, any) => boolean) |
            ((string, any) => void)
))
{
    // if obj is an array ...
    if(Object.prototype.toString.call(obj) === '[object Array]') {
        // if callback def1
        if(func.length == 1) {  // error: property length does not exist on type 'any[] | Object'
            for(let i = 0; i < obj.length; i++)  {
                if(typeof func === "function") {
                    if(!func(obj[i])) break; // error: Cannot invoke an expression whose type lacks a call signature
                }
            }
        // if callback def2
        } else if(func.length == 2) { // error: property length does not exist on type 'any[] | Object'
            for(let i = 0; i < obj.length; i++) {
                if(!func(obj[i], i)) break; // error: Cannot invoke an expression whose type lacks a call signature
            }
        }
    // if obj is an object ...
    } else if(Object.prototype.toString.call(obj) == '[object Object]') {
        // if callback def1
        if(func.length == 1) {
            for(let key in obj) {
                if(!obj.hasOwnProperty(key)) continue;
                if(!func(obj[key])) break; // error: Cannot invoke an expression whose type lacks a call signature
            }
        // if callback def3
        } else if(func.length == 2) {
            for(let key in obj) {
                if(!obj.hasOwnProperty(key)) continue;
                if(!func(key, obj[key])) break; // error: Cannot invoke an expression whose type lacks a call signature
            }
        }
    }
};

你去的地方:

function foreach<T>(obj : T[], func: (item: T) => void)
function foreach<T>(obj : T[], func: (item: T) => boolean)
function foreach<T>(obj : T[], func: (item: T, index: number) => boolean)
function foreach<T>(obj : T[], func: (item: T, index: number) => void)
function foreach<T>(obj : {[key: string]: T}, func: (item: T) => boolean)
function foreach<T>(obj : {[key: string]: T}, func: (item: T) => void)
function foreach<T>(obj : {[key: string]: T}, func: (key: string, item: T) => boolean)
function foreach<T>(obj : {[key: string]: T}, func: (key: string, item: T) => void)
function foreach<T>(obj : T[] | {[key: string]: T}, func : (item : T | string, index ?: number | T) => (boolean | void))
{
    if(Object.prototype.toString.call(obj) === '[object Array]') {
        let arr = <any>obj as T[];
        if(func.length == 1) {
            let cb = <any>func as (item: T) => boolean; 
            for(let i = 0; i < arr.length; i++) if(!cb(arr[i])) break;
        } else if(func.length == 2) {
            let cb = <any>func as (item: T, index: number) => boolean;
            for(let i = 0; i < arr.length; i++) if(!cb(obj[i], i)) break;
        }
    } else if(Object.prototype.toString.call(obj) == '[object Object]') {
        let arr = obj as {[key: string]: T};
        if(func.length == 1) {
            let cb = <any>func as (item: T) => boolean;
            for(let key in obj) {
                if(!obj.hasOwnProperty(key)) continue;
                if(!cb(obj[key])) break;
            }
        } else if(func.length == 2) {
            let cb = func as (key: string, item: T) => boolean;
            for(let key in obj) {
                if(!obj.hasOwnProperty(key)) continue;
                if(!cb(key, obj[key])) break;
            }
        }
    }
};

用法示例: 在此输入图像描述 在此输入图像描述

你可以利用intellisense: 在此输入图像描述

您可以通过利用泛型函数(按类型参数化的函数,如function foo<T> ),可选参数(带? )和多个函数签名声明来简化代码。 这将让你避免所有这些工会和any工会。

首先,将您的功能分解为两个是有意义的。 原因是TypeScript在运行时调度到不同的实现意义上不支持多态。 你真的有两个不同的函数(一个用于数组,一个用于对象),所以你应该这样写。 我知道jQuery甚至是下划线都喜欢伪多态,但实际上这看似便利是一个糟糕的设计原则。 写下你的意思,并写下你写的东西。

您的代码最终将如下所示:

function forEachArray<T>(
  array: Array<T>, 
  func?: (value: T, index?: number): boolean
): void {
  for (let i = 0; i < obj.length; i++)  {
    if (func && !func(array[i], i)) break;
  }
}

function forEachObject<T>(
  object: {[index: string]: T}, 
  func?: (value: T, key?: string): boolean
): void {
  for (let key of Object.keys(object)) {
    if (func && !func(object[key], key)) break;
  }
}

在这里,我们使用了一些你似乎没有意识到的工具。 一个是问号 ? 指示可选参数,我们用既为func本身,也是第二个参数func (索引或键)。 第二个是泛型 ,我们在这里使用它来强制数组或对象中值的同质性。 这将强制执行以下概念:如果您的数组包含字符串,则传入的函数必须将字符串作为其第一个(值)参数。 如果你想放宽这个限制,你总是可以把它称为forEachObject<any>(...

设计方面,让回调返回布尔值或无效似乎是值得怀疑的。 因为你总是使用它,好像它返回一个布尔值,强制执行。

您不需要特殊情况回调执行或不执行第二个参数的情况。 你可以简单地使用? func回调参数的签名中,继续并传递它,如果需要,函数将忽略它。

此外,为了与Array#forEach语义保持一致,您可能还应该允许可选的thisArgs第三个参数,并且还将基础数组或对象的第三个参数传递给回调。 forEachArray的情况下,这将是这样的:

function forEachArray<T>(
  array: Array<T>, 
  func?: (value: T, index?: number, array?: Array<T>): boolean,
  thisArg?: Object
): void {
  for (let i = 0; i < obj.length; i++)  {
    if (func && !func.call(thisArg, array[i], i, array)) break;
  }
}

如果您真的想要一个带有数组或对象的foreach函数,请使用定义多个函数签名的技术,然后执行。 我为紧凑性定义了一个Hash类型:

type Hash<T> = {[index: string]: T};

function foreach<T>(
  array: Array<T>, 
  func?: (value: T, index?: number, array?: Array<T>): boolean,
  thisArg?: Object
): void;

function foreach<T>(
  object: Hash<T>,
  func?: (value: T, key?: string, object?: Hash<T>): boolean,
  thisArg?: Object
): void;

function foreach(thing, func?, thisArg?) {
  (thing instanceof Array ? forEachArray : forEachObject)(thing, func, thisArg);
}

请注意,在声明这样的多个函数签名时,您不需要在实现上指定任何类型,如果这样做,它们将被忽略。

警告:上面的代码都没有经过测试甚至编译过。

暂无
暂无

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

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