[英]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;
}
}
}
};
您可以通过利用泛型函数(按类型参数化的函数,如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.