Управление состоянием Flutter: руководство по началу работы с Provider4 (1)

Flutter

задний план

давно, в нашейQQ группаМой друг пытался вытащить меня[Provider](https://github.com/rrousselGit/provider)учебники, но я никогда не был предан этому. Потому что я думаю, что если вы пишете туториал на уровне ворот, то уже есть официальные документы, и кто-то уже это написал, если вы хотите углубляться, я не буду. Но в последнее время все не так, из-за гидрологии.

государственное управление

Говоря оFlutter, мы вряд ли можем избежать управления состоянием. дляReactУправление состоянием знакомо разработчикам , но все еще несколько незнакомо таким разработчикам, как мы.Flutterявляется декларативным, что означаетFlutterЯвляется отражением текущего состояния приложения путем обновления пользовательского интерфейса:

Проще говоря, вFlutter, если мы хотим обновить наши элементы управления, самый простой способ должен бытьsetState().如果说我们一个页面里的组件不多,直接使用setState()Это не проблема, но на практике наш макет страницы все еще достаточно сложен.

Один случай, когда мы находимся на странице:

Если мы возьмем всеWidgetОни все прописаны в класс, этот класс точно будет толстяком больше 200 фунтов, и в него легко попасть{{{{}}}}вихрь. тогда будем думатьWidgetраскол, но в это время, если только опираясь наsetState(), вы обнаружите, что это будет очень болезненно, потому чтоsetState()ограничивается только какWidget, то есть если вы только на днеWidgetвызыватьsetStateне обновляет верхний уровеньWidget, что означает, что вы делаете это с помощью обратных вызовов, и в процессе вы обнаружите некоторыеWidgetПеременные в классе должны быть неизменяемыми, что вызовет другие неприятности, не говоря уже о них.

Вообще говоря, в реальной разработке очень возможно обмениваться данными между страницами:

На приведенном выше рисунке показана функция корзины, когда пользователь нажимаетAdd, продукт будет добавлен в корзину, и когда мы нажмем на корзину, мы сразу увидим продукт. Подумайте, как мы должны реализовать это, не используя управление состоянием?

Сказав так много, я просто хочу сказать, что использование управления состоянием более важно. Проще говоря, как легко и быстроWidgetДелитесь данными между собой и отображайте данные на странице.

FlutterМетоды управления состоянием включают, но не ограничиваютсяProvider,Bloc,Reduxтак же какFish-Redux.

  • BlocЕсли быть точным, то это концепция, и это также первое управление состоянием, которое я использовал.Сейчас есть соответствующие библиотеки реализации, которые в основном основаны на реактивном программировании.
  • ReduxдляReactВ конце концов, это знакомо разработчикам.FlutterЭто также заимствовано изReact.
  • Fish-ReduxРодился изRedux, выпускаемый Али, в целом более сложен и подходит для средних и крупных проектов. Общество теперь рождаетсяFish-ReduxИнструменты для кода шаблона
  • ProviderдаGoogleРекомендуемое управление состоянием также является вторым используемым мной управлением состоянием, которое является относительно простым и беззаботным.

Далее я кратко представлюProviderиспользование.

Первое знакомство с провайдером

ProviderНа самом деле даInheritedWidgetупаковка. по сравнению с прямым использованиемInheritedWidget,использоватьProviderЕсть много преимуществ, таких как упрощение выделения и удаления ресурсов, поддержка отложенной загрузки и т. д.

Providerпредоставляет нам различные видыProvider. Для просмотра всех видовproviderможно нажатьздесь

name description
Provider Самый простой провайдер. Он принимает значение и предоставляет это значение, независимо от того, что это за значение.
ListenableProvider дляListenableобъект созданprovider.ListenableProviderбудет прослушивать изменения объекта, покаListenableProviderслушатель называется,ListenableProviderУправления, которые зависят от провайдера, будут восстановлены.
ChangeNotifierProvider ChangeNotifierProviderэто особыйListenableProvider, который основан наChangeNotifier, и при необходимости он автоматически вызоветChangeNotifier.dispose.
ValueListenableProvider мониторValueListenableи выставит толькоValueListenable.value.
StreamProvider монитор одинStreamИ выставьте последнее отправленное значение внешнему миру.
FutureProvider нести одинFuture,когдаFutureКогда это сделано, он обновляет элементы управления, которые зависят от него.

Ввиду отсутствия у меня таланта и знаний в этой статье не будет описываться как использовать различныеProvider, поэтому в этой статье выбран тот, который я использую чаще всегоChangeNotifierProviderОбъяснить, я надеюсь, что смогу бросить кирпич для привлечения нефрита.

Создать провайдера

Обычно создаютProviderЕсть два способа:

  • конструктор по умолчанию
  • .valueМетод строительства

Когда мы хотим создать новый объект, мы хотим использовать конструктор по умолчанию вместо использования.valueконструктор, потому что если мы передаем.valueСоздание объекта может вызвать утечку памяти или некоторые неожиданные проблемы. Вот краткое объяснение, почему его нельзя использоватьvalueСоздайте объект, хороший английский видноОригинальный StackOverflow. потому чтоFlutterсерединаbuildМетод должен быть чистым и без побочных эффектов, многие внешние факторы спровоцируютrebuild, Например:

  • Маршрутизация pop/push
  • Изменение размера экрана, обычно из-за смены клавиатуры или изменения ориентации экрана.
  • Родительский контроль перерисовывает дочерний контроль
  • зависит отInheritedWidgetЭлемент управления (раздел Class.of(context)) изменился

Итак, используя.valueПроблема с созданием объекта заключается в том, что он делаетbuildСтать чистым или иметь побочные эффекты, вызываемые извнеbuildЗвонок стал очень неприятным. Этот вопрос до сих пор, друзья, которые любят учиться, могут исследовать себя.

  • хочуиспользоватьProviderизcreateобъект создан в .
Provider(
  create: (_) => MyModel(),
  child: ...
)
  • не хочуиспользоватьProvider.valueСоздавайте объекты.
ChangeNotifierProvider.value(
 value: MyModel(),
 child: ...
)
  • не хочуСоздавайте объекты из переменных, которые могут меняться со временем. Потому что в этом случае созданный нами объект не будет обновляться, даже если ссылочная переменная изменится.
int count;

Provider(
  create: (_) => MyModel(count),
  child: ...
)

Если вы хотите передать в наш объект переменные, которые со временем изменяются, рассмотрите возможность использованияProxyProvider:

int count;

ProxyProvider0(
  update: (_, __) => MyModel(count),
  child: ...
)

Примечания: При использованииProviderизcreate/updateПри обратном звонке следует учитывать, что по умолчаниюcreate/updateЗвонок вызывается лениво. Это означает, что только мыProviderДанные запрашиваются хотя бы один раз,create/updateбудет называться. Если мы хотим выполнить предварительную обработку, мы можем использоватьlazyпараметр для отключения этой функции:

MyProvider(
  create: (_) => Something(),
  lazy: false,
)

Чтение данных от провайдера

Самый простой способ прочитать данные — использоватьBuildContextметод расширения:

  • context.watch(), этот метод будет использовать соответствующий элемент управления для отслеживания изменений T.
  • context.read(), этот метод возвращает T напрямую и не отслеживает изменения.
  • context.select(R cb(T value)), этот метод заставит соответствующий контроль контролировать только небольшую часть изменений T. Из названия мы знаем, что это фильтр.

Конечно, мы также можем использовать статические методы.Provider.of<T>(context), это иwatch/readПоведение очень похожее, и таким же образом мы получаем данные перед методом расширения, описанным выше.

Эти методы ищут в управлении деревом и от прохожденияBuildContextСвязанные элементы управления для запуска, в конечном итоге возвращаются, чтобы найти и вернуть самую последнюю переменную типа T (если не найдено, выбросить).

Стоит отметить, что сложность этой операции составляет O(1). На самом деле, это не ходит по дереву управления.

Кстати говоря, это не более чем перевод документа, теперь приступим к работе~~~

Show me the code

История еще изFlutterсчетчиков, так как вновь созданныйFlutterШаблон проекта - это счетчик, теперь мы используемChangeNotifierProviderСделаем простую трансформацию этого проекта.

  • БудуMyHomePageЗависит отStatefulWidgetизменить наStatelessWidget.
  • использоватьChangeNotifierProviderобновить страницу

первая версия

Во-первых, мы собираемся создатьChangeNotifier:

class MyChangeNotifier extends ChangeNotifier {
  int _counter = 0;

  int get counter => _counter;

  incrementCounter() {
    _counter++;
    notifyListeners();//要更新UI记得调用这个方法
  }
}

В настоящее время мы нажимаемFloatingActionButtonбудет вызван, когдаMyChangeNotifierизincrementCounterметод, следует отметить, что когда мы закончим обработку бизнеса, если нам нужно обновить пользовательский интерфейс, нам нужно вызватьnotifyListenersставить в известностьProviderОбновить пользовательский интерфейс.

Далее мы реализуем наш пользовательский интерфейс.

Во-первых, мы собираемся создатьMyHomePage, Макет пользовательского интерфейса напрямую использует макет в примере, разница в том, что мы используемStatelessWidget. Затем мы проходимBuildContextвыигратьMyChangeNotifierпример. Обратите внимание, что когда мы нажимаемFloatingActionButtonМы не звонилиsetState(ерунда,StatelessWidgetтоже не могуsetState), но наш пользовательский интерфейс все равно будет обновляться. код показывает, как показано ниже:

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    MyChangeNotifier notifier =
        Provider.of(context); //通过Provider.of(context)获取MyChangeNotifier
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${context.watch<MyChangeNotifier>().counter}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: notifier.incrementCounter,//点击时我们期望输出点击次数
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Теперь мы будем использоватьChangeNotifierProviderпакетMyHomePage, что гарантирует, чтоMyHomePageсквозьBuildContextполучатьMyChangeNotifierпример.

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return MaterialApp(
     title: 'Flutter Demo',
     theme: ThemeData(
       primarySwatch: Colors.blue,
       visualDensity: VisualDensity.adaptivePlatformDensity,
     ),
     home: ChangeNotifierProvider(
         create: (_) => MyChangeNotifier(),
         child: MyHomePage(title: 'Flutter Demo Home Page')),
   );
 }
}

Пока что код написан, запускаем, эффект точно такой же, как в примере?

Конечно, мы можем напрямуюMyChangeNotifierОпределите поле непосредственно вoutputMessage, то непосредственно вMyHomePageдавать напрямуюTextНазначение.

Text(
    context.watch<MyChangeNotifier>().outputMessage,
    style: Theme.of(context).textTheme.headline4,
    ),

вторая версия

Теперь кажется, что мы научилисьChangeNotifierProviderОсновное использование, теперь мы собираемся сделать простое преобразование приведенного выше кода.

  • БудуChangeNotifierProviderперейти кMyHomePage.

Очень просто, код выглядит следующим образом:


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;
  final MyChangeNotifier notifier = MyChangeNotifier();

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => notifier,
      child: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '${context.watch<MyChangeNotifier>().counter}',
                style: Theme.of(context).textTheme.headline4,
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: notifier.incrementCounter,//点击时我们期望输出点击次数
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Когда мы успешно запустили приведенный выше код, мы столкнулись с некоторыми проблемами:

Когда я впервые столкнулся с этой ошибкой, я не мог не сказать что-то, что начинается с F и заканчивается на U. Но мы не можем не решить проблему. В это время нам может понадобитьсяConsumer.

Consumerиспользование

ConsumerВ нем нет магии самой по себе и нет причудливой реализации. Просто используйте его в новом элементе управленияProvider.of, затем поместите элементы управленияbuildМетод делегирован параметрамbuilder. этоbuilderбудет вызываться несколько раз. Это так просто.

ConsumerДизайн имеет два первоначальных намерения

  • когда нашBuildContextУказанный не существует вProviderчас,Consumerпозвольте намProviderПолучить данные в .
  • Оптимизация производительности достигается за счет более мелкозернистых перерисовок.

Сейчас мы находимся в первом случае, что касается второго случая, читатели могут исследовать его сами. Итак, мы можем добавитьConsumerрешить вышеуказанноеProviderNotFoundExceptionвопрос:


class MyHomePage extends StatelessWidget {
  final String title;
  final MyChangeNotifier notifier = MyChangeNotifier();

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (_) => notifier,
      child: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: Consumer<MyChangeNotifier>(
          builder: (_, localNotifier, __) => Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                Text(
                  'You have pushed the button this many times:',
                ),
                Text(
                  '${localNotifier.counter}',
                  style: Theme.of(context).textTheme.headline4,
                ),
              ],
            ),
          ),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: notifier.incrementCounter,
          tooltip: 'Increment',
          child: Icon(Icons.add),
        ),
      ),
    );
  }
}

Запускаем еще раз, все идеально?

предварительное резюме

Время ограничено, хотелось закончить на одном дыхании, но в век интернета, как можно стыдиться сказать, что я был в интернете? . .

так какProviderПервая статья о начале работы, эта статья еще очень проста, ведь я просто изменил пример Flutter. В следующей статье я расскажу большеProviderИспользование и проблемы, также включают более сложные демонстрации.

Продолжение следует. . . Ожидать или не ожидать, что последнее слово будет за вами.