简体   繁体   English

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

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

I am converting some stuff I use to typescript, and I'm at a bit of a loss. 我正在将我使用的一些东西转换为打字稿,而且我有点不知所措。 I made a generic foreach function that accepts arrays and objects, and takes a few different types of callbacks to handle the iteration. 我创建了一个接受数组和对象的通用foreach函数,并采用几种不同类型的回调来处理迭代。 However, I don't know how to specify the type if it accepts a few different callbacks. 但是,如果它接受一些不同的回调,我不知道如何指定类型。 I need to be able to return boolean or void from the function, and it should be able to pass in (any), (any, int), or (string, any). 我需要能够从函数返回boolean或void,它应该能够传入(any),(any,int)或(string,any)。 This is what I've got so far. 这是我到目前为止所得到的。

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
            }
        }
    }
};

there you go: 你去的地方:

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;
            }
        }
    }
};

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

you can take advantage of intellisense: 你可以利用intellisense: 在此输入图像描述

You can simplify your code by taking advantage of generic functions (functions parameterized by types, as in function foo<T> ), optional arguments (with ? ), and multiple function signature declarations. 您可以通过利用泛型函数(按类型参数化的函数,如function foo<T> ),可选参数(带? )和多个函数签名声明来简化代码。 This will let you avoid all these unions and any 's. 这将让你避免所有这些工会和any工会。

First, it makes sense to break your function into two. 首先,将您的功能分解为两个是有意义的。 The reason is that TypeScript does not support polymorphism in the sense of dispatching to different implementations at run time. 原因是TypeScript在运行时调度到不同的实现意义上不支持多态。 You really have two different functions here (one for arrays and one for objects), and so you should write them as such. 你真的有两个不同的函数(一个用于数组,一个用于对象),所以你应该这样写。 I know that jQuery and even underscore love pseudo-polymorphism, but actually this seeming convenience is a poor design principle. 我知道jQuery甚至是下划线都喜欢伪多态,但实际上这看似便利是一个糟糕的设计原则。 Write what you mean, and mean what you write. 写下你的意思,并写下你写的东西。

Your code will end up looking like: 您的代码最终将如下所示:

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;
  }
}

Here we've used a couple of tools you seem perhaps not to have been aware of. 在这里,我们使用了一些你似乎没有意识到的工具。 One is the question mark ? 一个是问号 ? to indicate optional arguments, which we use both for func itself, and also the second argument to func (the index or key). 指示可选参数,我们用既为func本身,也是第二个参数func (索引或键)。 The second is generics , which we use here to enforce the homogeneity of the values in the array or object. 第二个是泛型 ,我们在这里使用它来强制数组或对象中值的同质性。 This will enforce the notion that if your array contains strings, for example, the function you pass in must take a string as its first (value) parameter. 这将强制执行以下概念:如果您的数组包含字符串,则传入的函数必须将字符串作为其第一个(值)参数。 if you want to relax this restriction, you can always call it as forEachObject<any>(... . 如果你想放宽这个限制,你总是可以把它称为forEachObject<any>(...

Design-wise, having the callback return either boolean or void seems questionable. 设计方面,让回调返回布尔值或无效似乎是值得怀疑的。 Since you always use it as if it is returning a boolean, enforce that. 因为你总是使用它,好像它返回一个布尔值,强制执行。

You do not need to special case the situation where the callback does or does not take a second parameter. 您不需要特殊情况回调执行或不执行第二个参数的情况。 You can simply make that parameter optional using ? 你可以简单地使用? in the signature for the func callback parameter, and go ahead and pass it, and the function will ignore it if it wants. func回调参数的签名中,继续并传递它,如果需要,函数将忽略它。

Also, for consistency with Array#forEach semantics, you should probably also allow an optional thisArgs third parameter, and also pass to your callback a third argument for the underlying array or object. 此外,为了与Array#forEach语义保持一致,您可能还应该允许可选的thisArgs第三个参数,并且还将基础数组或对象的第三个参数传递给回调。 In the case of forEachArray , that would look like this: 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;
  }
}

If you really want to have a single foreach function that takes either an array or an object, use the technique of defining multiple function signatures, followed by an implementation. 如果您真的想要一个带有数组或对象的foreach函数,请使用定义多个函数签名的技术,然后执行。 I define a Hash type for compactness: 我为紧凑性定义了一个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);
}

Note that when declaring multiple function signatures like this, you don't need to specify any types on the implementation, and they would be ignored if you did. 请注意,在声明这样的多个函数签名时,您不需要在实现上指定任何类型,如果这样做,它们将被忽略。

Caveat: none of the code above has been tested or even compiled. 警告:上面的代码都没有经过测试甚至编译过。

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

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