1

我正在尝试将数组方法 filter 和 map 组合成一个名为 malter 的函数。我到目前为止:

type mapFn<T, S> = (value: T, index: number, originalArray: Readonly<T[]>) => S;

interface Array<T> {
  malter<S = any>(mapFn: mapFn<T, S>): S[];
}

function notUndefined<T>(v: T | undefined): v is T {
  return typeof v !== "undefined"
}

Array.prototype.malter = function malter<T, S = any>(mapFn: mapFn<T, S>): S[] {
  return this.reduce(function(acc: S[], val: T, index: number, orgArr: T[]) {
    const el = mapFn(val, index, orgArr);
    if (notUndefined(el)) {
      acc.push(el);
    }
    return acc;
  }, []);
};

它基本上有效。但是在使用它时,它会在第 5-7 行抛出一个 TypeError。另一个隐式返回 undefined 的测试函数也抛出了这个错误。

Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'."
const test = [2, 3, 4, 5, 3];

function test1(): string[] {
   return test.malter<number, string>(num => 
      num > 3 
       ? num.toFixed(2) 
       : undefined
   );
}

一个有效的解决方案是为 malter 提供 2 个参数。一个过滤器和一个映射函数,并分别调用它们。这将确保类型保存,但也会使其不那么简单。

当然,我可以在第 5-7 行简单地做到这一点:

(num > 3 ? num.toFixed(2) : undefined) as string

这可能是最好的妥协?你怎么看?有没有我没有想到的解决方案,或者你会妥协吗?

4

2 回答 2

2

@TitianCernicova-Dragomir 的回答是正确的,但我也想给出一个稍微不同的解决方案。主要区别在于,我们不使用条件类型S(可能包括undefined)转换为Exclude<S, undefined>(不包括),而是将S其作为返回类型(不包括undefined)并将输入类型用作S | undefined。从调用者的角度来看,它们的行为相同(或几乎如此)(编译器将对Exclude输入类型进行类似的分析),但编译器可能能够malter更好地推断实现中的类型后一种情况:

type mapFn<T, S> = (value: T, index: number, originalArray: Readonly<T[]>) => S;

interface Array<T> {
  // S will not include undefined when inferred from mapFn
  malter<S>(mapFn: mapFn<T, S | undefined>): S[];
}

// hey, look, this works the same way, with T|undefined => T
// instead of T => Exclude<T, undefined>
function notUndefined<T>(v: T | undefined): v is T {
  return typeof v !== "undefined";
}

Array.prototype.malter = function malter<T, S>(
  this: Array<T>, // inform the compiler that this is an array
  mapFn: mapFn<T, S | undefined> 
): S[] {
  return this.reduce(function(acc: S[], val: T, index: number, orgArr: T[]) {
    const el = mapFn(val, index, orgArr);
    if (notUndefined(el)) {
      acc.push(el);
    }
    return acc;
  }, []);
}; // inference in implementation works


const test = [2, 3, 4, 5, 3];

// don't need any type parameters 
function test1(): string[] {
  return test.malter(num => (num > 3 ? num.toFixed(2) : undefined));
}

好的,希望有帮助。祝你好运!

链接到代码

于 2019-05-28T14:03:36.427 回答
1

您正在undefined用作要过滤的值。我们可以允许内部函数返回undefined,然后使用 过滤掉它Exclude,就像实现一样:

type mapFn<T, S> = (value: T, index: number, originalArray: Readonly<T[]>) => S;

interface Array<T> {
    malter<S>(mapFn: mapFn<T, S>): Exclude<S, undefined>[];
}

function notUndefined<T>(v: T | undefined): v is T {
    return typeof v !== "undefined"
}

Array.prototype.malter = function malter<T, S>(mapFn: mapFn<T, S>): Exclude<S, undefined>[] {
    return this.reduce(function (acc: S[], val: T, index: number, orgArr: T[]) {
        const el = mapFn(val, index, orgArr);
        if (notUndefined(el)) {
            acc.push(el);
        }
        return acc;
    }, []);
};

const test = [2, 3, 4, 5, 3];

function test1(): string[] {
    return test.malter(num =>
        num > 3
            ? num.toFixed(2)
            : undefined
    );
}
于 2019-05-28T13:12:20.740 回答