При использовании Array.reduce()
для сложных операций, мы склонны прибегать к использованию побочных эффектов (например, определять переменную let
или объект вне области видимости. и изменять их во время итерации).
Вот простой способ сохранить чистоту ваших функций reduce, используя агрегатор в качестве состояния.
Рассмотрим функцию reduce, которая предназначена для суммирования всех чисел и добавления наибольшего числа дважды.
Перед этим мы можем объявить изменяемую переменную, использовать ее для отслеживания наибольшего числа, а затем добавить его после завершения уменьшения:
let biggestNum = Number.NEGATIVE_INFINITY;
const sum = arr.reduce(
(agg, val) => {
biggestNum = Math.max(val, biggestNum);
return agg + val;
},
0,
);
const sumAndBiggest = sum + biggestNum;
В качестве альтернативы мы можем инкапсулировать всю связанную логику ВНУТРИ функции reduce:
const sumAndBiggest = arr.reduce(
(agg, val, index, sourceArr) => {
if (index === sourceArr.length - 1) {
return agg.sum + val + agg.biggestNum;
}
return {
sum: agg.sum + val,
biggestNum: Math.max(val, agg.biggestNum),
};
},
{ sum: 0, biggestNum: Number.NEGATIVE_INFINITY },
);
Вы заметите две вещи:
- Мы используем агрегатор в качестве состояния — мы включаем фактическую сумму, которую мы уменьшаем, и самое большое число, которое мы нашли на данный момент.
- Мы используем 3-й и 4-й аргументы функции reduce — текущий индекс и ссылку на массив, над которым мы итерируем, — чтобы узнать, является ли текущая итерация последней. Если да, то мы прибавляем наибольшее число и возвращаем конечную сумму вместо нового состояния.
Этот способ добавляет немного больше логики в функцию reduce. Почему мы должны использовать его?
Потому что он позволяет нам инкапсулировать все данные, необходимые для операции уменьшения. Это означает:
- Вы не получаете остатков переменных после завершения редукции. Когда кто-то читает код или отлаживает его, ему не придется беспокоиться о том, что эти переменные —
biggestNum
в нашем первом примере — используются где-то еще. - Ваша функция reduce — это чистая функция. Все, на что вам нужно обратить внимание, происходит внутри функции и ее аргументов. Это также означает упрощение отладки.
- Все данные состояния собираются в мусор, когда они больше не используются.
К сожалению, TypeScript предлагает только возможность определить одно возвращаемое значение для reduce
, и не позволяет нам различать конечное и промежуточные возвращаемые значения.
Это означает, что нам придется определить функцию reduce как способную возвращать оба типа, и использовать ключевое слово as
для утверждения типа нашего агрегатора перед использованием:
interface IReduceState {
sum: number,
biggestNum: number;
}
const sumAndBiggest = arr.reduce<number | IReduceState>(
(agg, val, index, sourceArr) => {
const { sum, biggestNum } = agg as IReduceState
if (index === sourceArr.length - 1) {
return sum + val + biggestNum;
}
return {
sum: sum + val,
biggestNum: Math.max(val, biggestNum),
};
},
{ sum: 0, biggestNum: Number.NEGATIVE_INFINITY },
);
Если у вас есть более красивое решение, пожалуйста, дайте мне знать!
Счастливого кодинга!
Спасибо, Йонатан Кра, за ваш любезный отзыв.