Комментарии (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 код более элегантным и удобным для использования.