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

Для чего нужен Scoped Model?

1.6 Junior🔥 11 комментариев
#Другое

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

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

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

Для чего нужен 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 был нужен?

  1. Избежать prop drilling — не передавать параметры через всё дерево
  2. Реактивность — виджеты автоматически обновляются при изменении данных
  3. Чистота — разделение логики от UI
  4. Простота — минимум кода для управления состоянием

Практический пример: приложение с авторизацией

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

  1. Кипящий код (boilerplate) — нужно оборачивать в ScopedModelDescendant
  2. Перестройки — весь descendant перестраивается даже если нужно обновить одно значение
  3. Отсутствие типизации — на некоторых операциях
  4. Устаревание — Provider и Riverpod более современные

Когда использовать Scoped Model сейчас?

  • Старые проекты (легаси код)
  • Обучение основам управления состоянием
  • Очень простые приложения

Для новых проектов рекомендуются: Provider, Riverpod или Bloc

Вывод: Scoped Model решал проблему управления состоянием в ранние дни Flutter. Сейчас он исторически важен, но на практике заменён на более мощные решения вроде Provider и Riverpod. Понимание его концепции важно для освоения современных решений управления состоянием.

Для чего нужен Scoped Model? | PrepBro