Я пытаюсь объединить методы массива 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;
  }, []);
};

Это в основном работает. Но при использовании он выдает ошибку TypeError в строке 5-7. Другая тестовая функция, которая неявно возвращала 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
   );
}

Решение, которое будет работать с типом экономно, состоит в том, чтобы предоставить 2 аргумента для malter. Фильтр и функция карты и вызывать их отдельно. Это обеспечит сохранность типа, но также сделает его менее простым.

Конечно, я мог бы просто сделать это в строке 5-7:

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

Это может быть лучшим компромиссом? Что вы думаете? Есть ли решение, о котором я не думал, или вы идете с компромиссом?

1
Reijo 28 Май 2019 в 15:59

2 ответа

Лучший ответ

Вы используете 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
    );
}
1
Titian Cernicova-Dragomir 28 Май 2019 в 13:12

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

Хорошо, надеюсь, это поможет. Удачи!

Ссылка на код

2
jcalz 28 Май 2019 в 14:03