Обработка коллекций: map, where, и итерируемые
Вместо ручных циклов Dart даёт цепочки методов, которые превращают одну коллекцию в другую коротко и наглядно.
Суть:
mapпреобразует каждый элемент,whereфильтрует,reduce/foldсворачивают коллекцию в одно значение. Это функциональный стиль — меньше кода, меньше ошибок.
Помимо трёх главных методов, в арсенале есть множество удобных помощников. firstWhere находит первый подходящий элемент, any и every проверяют, выполняется ли условие хотя бы для одного или для всех элементов, sort упорядочивает, а expand разворачивает вложенные списки в один плоский. Все они следуют той же философии: вы описываете, что хотите получить, а не как это перебрать вручную.
Допустим, у вас список цен, и нужно оставить только дорогие товары и прибавить к каждому налог. Можно написать цикл с временным списком и add, а можно одной строкой: prices.where((p) => p > 1000).map((p) => p * 1.2). Второй вариант читается как предложение и не оставляет места для опечаток в индексах.
final numbers = [1, 2, 3, 4, 5, 6];
// map — преобразовать каждый элемент
final doubled = numbers.map((n) => n * 2).toList();
// [2, 4, 6, 8, 10, 12]
// where — оставить подходящие
final evens = numbers.where((n) => n % 2 == 0).toList();
// [2, 4, 6]
// цепочка: чётные, затем удвоить
final result = numbers.where((n) => n % 2 == 0)
.map((n) => n * 10)
.toList();
// [20, 40, 60]
// reduce / fold — свернуть в одно значение
final sum = numbers.reduce((a, b) => a + b); // 21
Как это работает под капотом
Методы map и where ленивы: они не создают новый список сразу, а возвращают Iterable — описание будущих вычислений. Реальная работа происходит, только когда вы вызываете .toList() или перебираете результат. Это экономит память при длинных цепочках. Поэтому в коде вы почти всегда видите .toList() в конце — он материализует результат.
[1,2,3,4,5,6]
|
v .where((n) => n % 2 == 0) фильтр
[2, 4, 6] (пока Iterable, лениво)
|
v .map((n) => n * 10) преобразование
[20, 40, 60] (всё ещё Iterable)
|
v .toList() материализация
готовый List [20, 40, 60]
Во Flutter это критично: чтобы из списка данных получить список виджетов, вы пишете items.map((item) => Text(item)).toList(). Так данные превращаются в интерфейс одной строкой.
# Те же операции на Python: фильтр + преобразование + свёртка
numbers = [1, 2, 3, 4, 5, 6]
doubled = [n * 2 for n in numbers] # map
evens = [n for n in numbers if n % 2 == 0] # where
chained = [n * 10 for n in numbers if n % 2 == 0] # where + map
print('doubled:', doubled)
print('evens: ', evens)
print('chained:', chained)
total = 0
for n in numbers: # аналог reduce/fold
total += n
print('sum:', total)
Частые ошибки
- Забыть
.toList()там, где Flutter ждёт список (например, вchildren) — получите ошибку типа Iterable. - Менять исходную коллекцию внутри
map. Эти методы должны быть «чистыми» — только возвращать новое значение, не трогая внешнее. reduceна пустой коллекции бросает исключение; для безопасности используйтеfoldс начальным значением.
Best practices
- Предпочитайте
map/whereручным циклам — код становится декларативным и читается сверху вниз. - Используйте spread-оператор
...и...?для слияния списков:[...a, ...b]. - Длинные цепочки разбивайте по строкам с одним методом на строку — так видно каждый шаг.
Особенно важна чистота преобразований. Функция внутри map или where не должна менять внешние переменные или исходную коллекцию — она лишь вычисляет новое значение по входному. Такой стиль называют функциональным, и он резко снижает число коварных багов, потому что данные не меняются исподтишка. Во Flutter это особенно ценно: один и тот же список данных порождает предсказуемый список виджетов на каждой перерисовке.
Итог: функциональная обработка коллекций — главный инструмент превращения данных в интерфейс. Запомните связку where().map().toList() — вы будете писать её в каждом втором экране Flutter.