我正在将我使用的一些东西转换为打字稿,而且我有点亏本。我创建了一个接受数组和对象的通用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
}
}
}
};
答案 0 :(得分:1)
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;
}
}
}
};
答案 1 :(得分:0)
您可以通过利用泛型函数(按类型参数化的函数,如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>(...
。
设计方面,让回调返回boolean或void似乎有问题。因为你总是使用它就像它返回一个布尔值一样,强制执行。
在回调执行或不执行第二个参数的情况下,您不需要特殊情况。您可以使用?
回调参数的签名中的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);
}
请注意,在声明这样的多个函数签名时,您不需要在实现上指定任何类型,如果您这样做,它们将被忽略。
警告:上面的代码都没有经过测试甚至编译过。