Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего нужен Scoped Model
Scoped Model — это паттерн управления состоянием (State Management) для Flutter приложений. Его роль заключается в том, чтобы делать данные приложения доступными для виджетов без необходимости передавать их через конструкторы (избегаем prop drilling).
Проблема без Scoped Model
// ❌ Проблема: prop drilling
class MyApp extends StatelessWidget {
final String userName = 'John';
final int userScore = 100;
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Screen1(
userName: userName,
userScore: userScore,
),
);
}
}
class Screen1 extends StatelessWidget {
final String userName;
final int userScore;
Screen1({required this.userName, required this.userScore});
@override
Widget build(BuildContext context) {
return Screen2(
userName: userName,
userScore: userScore,
);
}
}
class Screen2 extends StatelessWidget {
final String userName;
final int userScore;
Screen2({required this.userName, required this.userScore});
@override
Widget build(BuildContext context) {
return Text('$userName: $userScore');
}
}
// Передаём параметры через все промежуточные слои!
// Это неудобно и ненадёжно
Решение со Scoped Model
// 1. Создаём модель данных
class UserModel extends Model {
String _userName = 'John';
int _userScore = 100;
String get userName => _userName;
int get userScore => _userScore;
void updateScore(int newScore) {
_userScore = newScore;
notifyListeners(); // уведомляем виджеты об изменениях
}
}
// 2. Оборачиваем приложение в ScopedModel
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScopedModel<UserModel>(
model: UserModel(),
child: MaterialApp(
home: Screen1(), // теперь не передаём параметры
),
);
}
}
// 3. В любом виджете получаем данные
class Screen1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Screen2(); // не передаём параметры
}
}
class Screen2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
// ScopedModelDescendant слушает изменения модели
return ScopedModelDescendant<UserModel>(
builder: (context, child, model) {
return Column(
children: [
Text(model.userName),
Text('Score: ${model.userScore}'),
ElevatedButton(
onPressed: () => model.updateScore(model.userScore + 10),
child: Text('Add 10 points'),
),
],
);
},
);
}
}
Как работает Scoped Model
// 1. Model — это Observer
class UserModel extends Model {
// notifyListeners() уведомляет всех слушателей об изменениях
List<VoidCallback> listeners = [];
void addListener(VoidCallback listener) => listeners.add(listener);
void notifyListeners() {
for (var listener in listeners) {
listener(); // вызываем listener, который запускает rebuild
}
}
}
// 2. ScopedModelDescendant слушает эти изменения
// Когда модель изменяется, builder перестраивается
Основные компоненты
Model — базовый класс для хранения состояния:
class CounterModel extends Model {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // важно!
}
}
ScopedModel — предоставляет модель всему поддереву:
ScopedModel<CounterModel>(
model: CounterModel(),
child: MyApp(),
)
ScopedModelDescendant — потребляет модель и перестраивается при изменениях:
ScopedModelDescendant<CounterModel>(
builder: (context, child, model) => Text('Count: ${model.count}'),
)
Сравнение со сторонними решениями
Scoped Model сейчас устарел и его вытеснили новые решения:
| Решение | Когда использовать | Сложность |
|---|---|---|
| Scoped Model | Простые приложения (2014-2019) | Низкая |
| Provider (обновленный Scoped Model) | Современный стандарт | Низкая |
| Riverpod | Высокая типизация, функциональное | Средняя |
| Bloc | Сложная логика, разделение слоёв | Высокая |
| Mobx | Реактивное программирование | Средняя |
| GetX | Быстрое прототипирование | Низкая |
Современная альтернатива: Provider
// Provider — это эволюция Scoped Model
// Более мощный и гибкий
final counterProvider = StateNotifierProvider<CounterNotifier, int>(
(ref) => CounterNotifier(),
);
class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);
void increment() => state++;
}
// В виджете
final count = ref.watch(counterProvider);
Зачем Scoped Model был нужен?
- Избежать prop drilling — не передавать параметры через всё дерево
- Реактивность — виджеты автоматически обновляются при изменении данных
- Чистота — разделение логики от UI
- Простота — минимум кода для управления состоянием
Практический пример: приложение с авторизацией
class AuthModel extends Model {
String? _token;
bool get isAuthenticated => _token != null;
Future<void> login(String username, String password) async {
_token = await apiService.authenticate(username, password);
notifyListeners();
}
void logout() {
_token = null;
notifyListeners();
}
}
// В приложении
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScopedModel<AuthModel>(
model: AuthModel(),
child: MaterialApp(
home: HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<AuthModel>(
builder: (context, child, authModel) {
if (authModel.isAuthenticated) {
return Dashboard();
} else {
return LoginScreen();
}
},
);
}
}
Недостатки Scoped Model
- Кипящий код (boilerplate) — нужно оборачивать в ScopedModelDescendant
- Перестройки — весь descendant перестраивается даже если нужно обновить одно значение
- Отсутствие типизации — на некоторых операциях
- Устаревание — Provider и Riverpod более современные
Когда использовать Scoped Model сейчас?
- Старые проекты (легаси код)
- Обучение основам управления состоянием
- Очень простые приложения
Для новых проектов рекомендуются: Provider, Riverpod или Bloc
Вывод: Scoped Model решал проблему управления состоянием в ранние дни Flutter. Сейчас он исторически важен, но на практике заменён на более мощные решения вроде Provider и Riverpod. Понимание его концепции важно для освоения современных решений управления состоянием.