Spread и Rest операторы

Введение

Spread (`...`) и Rest операторы - это мощные инструменты в Dart, которые значительно упрощают работу с коллекциями. Они позволяют "разворачивать" элементы одной коллекции в другую, создавать копии, объединять коллекции и многое другое.

1. Spread оператор (`...`)

Spread оператор позволяет "развернуть" элементы коллекции в другую коллекцию.

1.1 Базовый синтаксис

Dart - Объединение списков

Светлая тема Темная тема
List<int> numbers1 = [1, 2, 3];
List<int> numbers2 = [4, 5, 6];

// Объединение списков с помощью spread оператора
List<int> combined = [...numbers1, ...numbers2];
print(combined); // [1, 2, 3, 4, 5, 6]

1.2 Создание копий коллекций

Dart - Копирование списка

Светлая тема Темная тема
// Создание поверхностной копии списка
List<String> original = ['a', 'b', 'c'];
List<String> copy = [...original];

// Изменение копии не влияет на оригинал
copy.add('d');
print('Оригинал: $original'); // [a, b, c]
print('Копия: $copy');        // [a, b, c, d]

1.3 Добавление элементов при создании

Dart - Добавление элементов

Светлая тема Темная тема
List<int> base = [2, 3, 4];
List<int> extended = [1, ...base, 5, 6];
print(extended); // [1, 2, 3, 4, 5, 6]

// Можно добавлять элементы в любое место
List<String> fruits = ['яблоко', 'банан'];
List<String> menu = ['завтрак:', ...fruits, 'обед:', 'суп', 'салат'];
print(menu); // [завтрак:, яблоко, банан, обед:, суп, салат]

2. Null-aware Spread оператор (`...?`)

Этот оператор безопасно работает с nullable коллекциями.

2.1 Базовое использование

Dart - Null-aware Spread

Светлая тема Темная тема
List<int>? nullableList = null;
List<int> numbers = [1, 2];

// Без null-aware оператора было бы исключение
List<int> safe = [...numbers, ...?nullableList, 3];
print(safe); // [1, 2, 3]

// Если список не null, элементы добавляются
nullableList = [10, 20];
List<int> withValues = [...numbers, ...?nullableList, 3];
print(withValues); // [1, 2, 10, 20, 3]

2.2 Практический пример с API данными

Dart - API Response

Светлая тема Темная тема
class ApiResponse {
  List<String>? data;
  List<String>? errors;
  
  ApiResponse({this.data, this.errors});
}

void processApiResponse(ApiResponse response) {
  // Безопасное объединение данных и ошибок
  List<String> allMessages = [
    'Обработка запроса:',
    ...?response.data,
    'Ошибки:',
    ...?response.errors,
    'Обработка завершена'
  ];
  
  print(allMessages);
}

// Тестирование
var response1 = ApiResponse(data: ['Данные получены', 'Валидация пройдена']);
var response2 = ApiResponse(errors: ['Ошибка сети', 'Таймаут']);
var response3 = ApiResponse(); // null данные

processApiResponse(response1);
processApiResponse(response2);
processApiResponse(response3);

3. Spread с разными типами коллекций

3.1 Работа с Set

Dart - Spread с Set

Светлая тема Темная тема
Set<int> set1 = {1, 2, 3};
Set<int> set2 = {3, 4, 5};

// Объединение множеств (дубликаты автоматически удаляются)
Set<int> combinedSet = {...set1, ...set2};
print(combinedSet); // {1, 2, 3, 4, 5}

// Преобразование в список
List<int> listFromSets = [...set1, ...set2];
print(listFromSets); // [1, 2, 3, 3, 4, 5] - дубликаты сохраняются

3.2 Работа с Map

Dart - Spread с Map

Светлая тема Темная тема
Map<String, int> scores1 = {'Анна': 95, 'Борис': 87};
Map<String, int> scores2 = {'Вера': 92, 'Денис': 88};

// Объединение карт
Map<String, int> allScores = {...scores1, ...scores2};
print(allScores); // {Анна: 95, Борис: 87, Вера: 92, Денис: 88}

// При совпадении ключей последнее значение побеждает
Map<String, int> updated = {...scores1, 'Анна': 98, ...scores2};
print(updated['Анна']); // 98

4. Практические примеры использования

4.1 Создание конфигурационных объектов

Dart - Конфигурация БД

Светлая тема Темная тема
class DatabaseConfig {
  final Map<String, dynamic> config;
  
  DatabaseConfig({required this.config});
  
  // Создание конфигурации с базовыми настройками
  factory DatabaseConfig.withDefaults(Map<String, dynamic> userConfig) {
    const defaultConfig = {
      'host': 'localhost',
      'port': 5432,
      'timeout': 30,
      'ssl': false,
    };
    
    return DatabaseConfig(
      config: {...defaultConfig, ...userConfig}
    );
  }
}

// Использование
var dbConfig = DatabaseConfig.withDefaults({
  'host': 'production-db.com',
  'ssl': true,
  'username': 'admin'
});

print(dbConfig.config);
// {host: production-db.com, port: 5432, timeout: 30, ssl: true, username: admin}

4.2 Объединение результатов поиска

Dart - Сервис поиска

Светлая тема Темная тема
class SearchService {
  List<String> searchInDatabase(String query) {
    // Имитация поиска в БД
    return ['db_result_1', 'db_result_2'];
  }
  
  List<String> searchInCache(String query) {
    // Имитация поиска в кэше
    return ['cache_result_1'];
  }
  
  List<String>? searchInExternal(String query) {
    // Может вернуть null если внешний сервис недоступен
    return query.length > 5 ? ['external_result'] : null;
  }
  
  List<String> performSearch(String query) {
    return [
      'Результаты поиска для: $query',
      ...searchInCache(query),
      ...searchInDatabase(query),
      ...?searchInExternal(query), // Безопасное добавление
      'Поиск завершен'
    ];
  }
}

// Тестирование
var searchService = SearchService();
print(searchService.performSearch('dart'));
print(searchService.performSearch('programming'));

4.3 Построение меню приложения

Dart - Построитель меню

Светлая тема Темная тема
class MenuItem {
  final String title;
  final String? icon;
  
  MenuItem(this.title, {this.icon});
  
  @override
  String toString() => icon != null ? '$icon $title' : title;
}

class MenuBuilder {
  static List<MenuItem> buildMainMenu({
    required bool isAdmin,
    required bool isLoggedIn,
    List<MenuItem>? customItems
  }) {
    // Базовые элементы меню
    const baseItems = [
      MenuItem('Главная', icon: '🏠'),
      MenuItem('О нас', icon: 'ℹ️'),
    ];
    
    // Элементы для авторизованных пользователей
    const userItems = [
      MenuItem('Профиль', icon: '👤'),
      MenuItem('Настройки', icon: '⚙️'),
    ];
    
    // Элементы для администраторов
    const adminItems = [
      MenuItem('Панель администратора', icon: '🛠️'),
      MenuItem('Пользователи', icon: '👥'),
    ];
    
    return [
      ...baseItems,
      if (isLoggedIn) ...userItems,
      if (isAdmin) ...adminItems,
      ...?customItems, // Пользовательские элементы (могут быть null)
      MenuItem('Выход', icon: '🚪'),
    ];
  }
}

// Использование
print('Меню для гостя:');
var guestMenu = MenuBuilder.buildMainMenu(isAdmin: false, isLoggedIn: false);
guestMenu.forEach(print);

print('\nМеню для пользователя:');
var userMenu = MenuBuilder.buildMainMenu(isAdmin: false, isLoggedIn: true);
userMenu.forEach(print);

print('\nМеню для администратора:');
var adminMenu = MenuBuilder.buildMainMenu(
  isAdmin: true, 
  isLoggedIn: true,
  customItems: [MenuItem('Отчеты', icon: '📊'), MenuItem('Логи', icon: '📋')]
);
adminMenu.forEach(print);

5. Spread с условными выражениями

5.1 Условное добавление элементов

Dart - Список покупок

Светлая тема Темная тема
List<String> buildShoppingList({
  required bool needMilk,
  required bool needBread,
  required bool isWeekend,
  List<String>? specialItems
}) {
  return [
    'Список покупок:',
    '- Яйца',
    if (needMilk) '- Молоко',
    if (needBread) '- Хлеб',
    if (isWeekend) ...['- Пицца', '- Мороженое'], // Разворачиваем список
    ...?specialItems,
  ];
}

// Тестирование
print(buildShoppingList(
  needMilk: true,
  needBread: false,
  isWeekend: true,
  specialItems: ['- Торт', '- Свечи']
));

5.2 Фильтрация при объединении

Dart - Фильтрация чисел

Светлая тема Темная тема
List<int> combinePositiveNumbers(List<List<int>> numberLists) {
  List<int> result = [];
  
  for (var list in numberLists) {
    // Добавляем только положительные числа
    result.addAll([
      ...list.where((n) => n > 0)
    ]);
  }
  
  return result;
}

// Альтернативный способ с использованием expand
List<int> combinePositiveNumbersV2(List<List<int>> numberLists) {
  return [
    ...numberLists.expand((list) => list.where((n) => n > 0))
  ];
}

// Тестирование
var lists = [
  [1, -2, 3],
  [-4, 5, 6],
  [7, -8, -9, 10]
];

print(combinePositiveNumbers(lists)); // [1, 3, 5, 6, 7, 10]
print(combinePositiveNumbersV2(lists)); // [1, 3, 5, 6, 7, 10]