← Назад к вопросам

Для чего нужен Extension в Dart?

1.3 Junior🔥 201 комментариев
#Dart

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Extensions в Dart: назначение и практическое применение

Extensions (расширения) в Dart — это мощный инструмент, позволяющий добавлять новые методы и свойства к существующим типам без наследования. Это одна из самых полезных фич современного Dart.

Что такое Extension

Extension — это способ добавить новые методы и getter'ы к классу, который вы не контролируете (например, встроенные типы String, int, List) или просто расширить функциональность.

// Базовый синтаксис
extension StringExtension on String {
  // Добавляем новый метод к String
  String get capitalized {
    if (isEmpty) return this;
    return this[0].toUpperCase() + substring(1);
  }
}

// Использование
print('hello'.capitalized); // Hello

Основные назначения Extensions

1. Расширение встроенных типов

// Расширяем String
extension StringUtils on String {
  bool get isEmailValid {
    return contains('@') && contains('.');
  }

  String get reversed {
    return split('').reversed.join('');
  }

  int get wordCount {
    return split(' ').length;
  }
}

// Использование
print('user@example.com'.isEmailValid); // true
print('hello'.reversed); // olleh
print('hello world test'.wordCount); // 3

// Расширяем int
extension IntUtils on int {
  Duration get milliseconds => Duration(milliseconds: this);
  Duration get seconds => Duration(seconds: this);
  Duration get minutes => Duration(minutes: this);

  bool get isEven => this % 2 == 0;
  bool get isOdd => this % 2 != 0;

  List<int> get range => List.generate(this, (i) => i);
}

// Использование
await Future.delayed(500.milliseconds);
print(5.isEven); // false
print(5.isOdd); // true
print(3.range); // [0, 1, 2]

2. Упрощение работы с коллекциями

extension ListExtensions<T> on List<T> {
  /// Получить случайный элемент
  T? get random {
    if (isEmpty) return null;
    return this[Random().nextInt(length)];
  }

  /// Разбить список на подсписки размером n
  List<List<T>> chunked(int size) {
    if (size <= 0) throw ArgumentError('Size must be positive');
    final chunks = <List<T>>[];
    for (var i = 0; i < length; i += size) {
      chunks.add(sublist(i, min(i + size, length)));
    }
    return chunks;
  }

  /// Дублировать каждый элемент
  List<T> duplicated() {
    final result = <T>[];
    for (var item in this) {
      result.add(item);
      result.add(item);
    }
    return result;
  }
}

// Использование
var numbers = [1, 2, 3, 4, 5];
print(numbers.random); // случайное число
print(numbers.chunked(2)); // [[1, 2], [3, 4], [5]]
print([1, 2, 3].duplicated()); // [1, 1, 2, 2, 3, 3]

3. Работа с Map

extension MapExtensions<K, V> on Map<K, V> {
  /// Получить значение или дефолт
  V getOrDefault(K key, V defaultValue) {
    return containsKey(key) ? this[key]! : defaultValue;
  }

  /// Преобразовать Map<K, V> в Map<K, T>
  Map<K, T> mapValues<T>(T Function(V) transform) {
    return map((k, v) => MapEntry(k, transform(v)));
  }

  /// Отфильтровать по key
  Map<K, V> filterByKey(bool Function(K) predicate) {
    return {for (var entry in entries) if (predicate(entry.key)) entry.key: entry.value};
  }
}

// Использование
var config = {'timeout': 30, 'retries': 3};
print(config.getOrDefault('maxSize', 100)); // 100
print(config.mapValues((v) => v * 2)); // {timeout: 60, retries: 6}

4. Работа с DateTime

extension DateTimeUtils on DateTime {
  /// Проверить если это сегодня
  bool get isToday {
    final now = DateTime.now();
    return year == now.year && month == now.month && day == now.day;
  }

  /// Добавить дни
  DateTime addDays(int days) {
    return add(Duration(days: days));
  }

  /// Форматировать как "Jan 15, 2024"
  String get formatted {
    final months = [
      'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
      'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
    ];
    return '${months[month - 1]} $day, $year';
  }

  /// Начало дня
  DateTime get startOfDay {
    return DateTime(year, month, day);
  }

  /// Конец дня
  DateTime get endOfDay {
    return DateTime(year, month, day, 23, 59, 59, 999);
  }
}

// Использование
var date = DateTime.now();
print(date.isToday); // true
print(date.addDays(5)); // дата через 5 дней
print(date.formatted); // Mar 26, 2024

5. Расширения для Flutter UI

extension PaddingExtension on Widget {
  Widget padding(EdgeInsets padding) => Padding(
    padding: padding,
    child: this,
  );

  Widget paddingAll(double value) => padding(EdgeInsets.all(value));
  Widget paddingSymmetric({double h = 0, double v = 0}) =>
      padding(EdgeInsets.symmetric(horizontal: h, vertical: v));
}

extension SizedExtension on Widget {
  Widget sized({double? width, double? height}) => SizedBox(
    width: width,
    height: height,
    child: this,
  );

  Widget square(double size) => SizedBox.square(
    dimension: size,
    child: this,
  );
}

extension GestureExtension on Widget {
  Widget onTap(VoidCallback onTap) => GestureDetector(
    onTap: onTap,
    child: this,
  );

  Widget onLongPress(VoidCallback onLongPress) => GestureDetector(
    onLongPress: onLongPress,
    child: this,
  );
}

// Использование — это очень удобно!
Text('Hello')
  .paddingAll(16)
  .square(100)
  .onTap(() => print('Tapped'))

Практические примеры в реальных приложениях

Валидация и преобразование данных

extension ValidationExtensions on String {
  bool get isValidEmail {
    return RegExp(
      r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    ).hasMatch(this);
  }

  bool get isValidPassword {
    return length >= 8 && contains(RegExp(r'[A-Z]')) && contains(RegExp(r'[0-9]'));
  }

  bool get isValidPhone {
    return RegExp(r'^\+?[0-9]{10,15}$').hasMatch(this);
  }
}

// Использование
if (email.isValidEmail) {
  // процесс валидации пройден
}

Логирование с контекстом

extension Logger on Object {
  void log([String? prefix]) {
    print('[${prefix ?? runtimeType}] $this');
  }
}

// Использование
User(name: 'John', age: 30).log('USER'); // [USER] User(...)

Работа с цветами

extension ColorExtensions on Color {
  /// Осветлить цвет
  Color lighten([double amount = 0.1]) {
    assert(amount >= 0 && amount <= 1);
    final hsl = HSLColor.fromColor(this);
    return hsl.withLightness((hsl.lightness + amount).clamp(0.0, 1.0)).toColor();
  }

  /// Затемнить цвет
  Color darken([double amount = 0.1]) {
    assert(amount >= 0 && amount <= 1);
    final hsl = HSLColor.fromColor(this);
    return hsl.withLightness((hsl.lightness - amount).clamp(0.0, 1.0)).toColor();
  }

  /// Получить contrast цвет (белый или чёрный)
  Color get contrastColor {
    return computeLuminance() > 0.5 ? Colors.black : Colors.white;
  }
}

// Использование
var primaryColor = Colors.blue;
var darkVariant = primaryColor.darken(0.2);
var lightVariant = primaryColor.lighten(0.2);

Области видимости Extensions

// Файл: lib/extensions/string_extensions.dart
extension StringExtension on String {
  String get capitalized => isEmpty ? '' : '${this[0].toUpperCase()}${substring(1)}';
}

// Файл: lib/main.dart
import 'extensions/string_extensions.dart';

void main() {
  print('hello'.capitalized); // Hello
}

// Файл: lib/other_file.dart
// Если не импортировать, extension не будет доступен
print('hello'.capitalized); // ошибка — неизвестный метод

Ограничения Extensions

Нельзя переопределять существующие методы:

extension StringExtension on String {
  // ❌ Это вызовет ошибку при попытке использовать
  String toUpperCase() => toLowerCase(); // konflikt!
}

Нельзя добавлять поля (только методы и getter'ы):

extension StringExtension on String {
  // ❌ Ошибка — Extensions не могут иметь поля
  String extraData = 'test';

  // ✅ Правильно — используйте getter
  String get info => 'String: $this';
}

Best Practices

Именуйте Extensions понятно:

// ✅ Хорошо
extension StringValidation on String { ... }
extension DateTimeFormatting on DateTime { ... }
extension ListPagination on List { ... }

// ❌ Плохо
extension Ext1 on String { ... }
extension Helper on DateTime { ... }

Группируйте связанные Extensions:

// lib/extensions/string_extensions.dart
extension StringValidation on String {
  bool get isEmail => ...
  bool get isPhone => ...
  bool get isStrongPassword => ...
}

extension StringFormatting on String {
  String get capitalized => ...
  String get reversed => ...
}

Документируйте Extensions:

/// Extension для валидации email адреса
/// 
/// Пример:
/// ```dart
/// var email = 'test@example.com';
/// if (email.isValidEmail) { ... }
/// ```
extension EmailValidation on String {
  bool get isValidEmail => ...
}

Extensions vs Helper Functions

// Extensions — более читаемо
String result = '  hello  '.trim().capitalized;

// vs Helper функции — более многословно
String result = capitalize(trim('  hello  '));

// Extensions лучше для цепочек операций
var processed = text
    .trim()
    .capitalized
    .reversed
    .padLeft(10)
    .padRight(20);

Выводы

Extensions в Dart позволяют:

  • Делать код более читаемым через методы в цепочке
  • Расширять встроенные типы без наследования
  • Организовывать утилиты более структурированно
  • Уменьшать boilerplate код
  • Улучшать maintainability проекта

Это один из лучших способов сделать Flutter код более элегантным и удобным для использования.