Управление состоянием Flutter — предварительное исследование и краткое изложение

Flutter
Управление состоянием Flutter — предварительное исследование и краткое изложение

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

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

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

Во флаттере есть два типа управления состоянием: локальное состояние и глобальное состояние.

Локальное состояние: Flutter изначально предоставляет элементы управления InheritWidget для реализации локального управления состоянием. При изменении InheritedWidget все виджеты в его поддереве, которые зависят от его данных, будут перестроены. Типичные сценарии применения: интернационализированный копирайтинг, ночной режим и т.д.

Глобальное состояние: Flutter не обеспечивает встроенного управления глобальным состоянием и в основном должен полагаться на сторонние библиотеки для реализации. Хотя использование InheritedWidget на корневом элементе управления также может быть достигнуто, это кажется немного сложным... Это похоже на использование React состояния на корневом узле, что принесет те же проблемы, такие как слишком глубокая передача состояния.

InheritedWidget

преимущество:

  1. автоматическая подписка

InheritedWidget будет поддерживать карту виджета внутри. Когда подвиджет вызывает Context#inheritFromWidgetOfExactType, он автоматически сохраняет подвиджет на карте и возвращает InheritedWidget подвиджету.

  1. автоматическое уведомление

Сожаление о перестроении InheritedWidget автоматически запускает метод Update InheritElement.

недостаток:

  1. Невозможно разделить логику представления и бизнес-логику.
  2. Невозможно направить уведомления/уведомления о директивах.

InheritedWidget не различает, нужно ли обновлять виджет или нет, и будет уведомлять все дочерние виджеты о каждом обновлении. Поэтому необходимо сотрудничать со StreamBuilder для решения проблемы.

StreamBuilder — это виджет, инкапсулированный Flutter для отслеживания изменений в данных Stream.StatefulWidget, через внутр.Stream.listen()отслеживать входящиеstreamизменения, вызов при обнаружении измененияsetState()способ обновления виджета.

оstreamСтатьи введения есть везде, да и другие тоже очень подробные, поэтому я не буду их здесь повторять.

имеютStreamBuilder, мы можем передать его в дочерний виджетStreamBuilderдля мониторинга InheritedWidgetStreamДанные изменяются, а затем решается, нужно ли обновлять текущий подвиджет, тем самым завершая направленное уведомление о данных.

ScopedModel

Адрес склада:Universal.fever sirius.org/packages/generates…

Метод написания немного похож на текущий популярный React.@rematchГосударственная библиотека управления после изменения данных модели в каждом способе требуется только один разnotifyListeners()Все статусы могут быть обновлены.

class CounterModel extends Model {
  int _counter = 0;

  int get counter => _counter;

  void increment() {
    // First, increment the counter
    _counter++;
    
    // Then notify all the listeners.
    notifyListeners();
  }
}

Также нужно провести корневой компонент на входеScopedModel, он будет работать нормально.

class CounterApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // First, create a `ScopedModel` widget. This will provide 
    // the `model` to the children that request it. 
    return new ScopedModel<CounterModel>(
      model: new CounterModel(),
      child: new Column(children: [
        // Create a ScopedModelDescendant. This widget will get the
        // CounterModel from the nearest ScopedModel<CounterModel>. 
        // It will hand that model to our builder method, and rebuild 
        // any time the CounterModel changes (i.e. after we 
        // `notifyListeners` in the Model). 
        new ScopedModelDescendant<CounterModel>(
          builder: (context, child, model) => new Text('${model.counter}'),
        ),
        new Text("Another widget that doesn't depend on the CounterModel")
      ])
    );
  }
}

(Код здесь скопирован из официальной демоверсии)

преимущество:

  1. автоматическая подписка
  2. автоматическое уведомление
  3. Простой и удобный в использовании, стоимость обучения для фронтенд-разработчиков практически нулевая.

недостаток:

  1. Невозможность разделить логику представления и бизнес-логику
  2. Невозможно настроить таргетинг на уведомления/уведомления о директивах

ScopedModelНа самом деле InheritedWidget просто инкапсулирован, поэтому он наследует преимущества и недостатки InheritedWidget.

Redux

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

Redux разделяет данные и представления, а рендеринг представлений на основе данных решает проблему разделения представлений и сервисов в ScopedModel.

  • Store — это класс модели, который хранит состояние внутри.
  • StoreProvider — это InheritedWidget, который хранит Store внутри. (Дата центр)
  • StoreConnector предоставляет StoreStreamListener, который по сути является StreamBuilder, внутри которого находится Stream.Этот поток преобразуется с помощью changeController в Store, который является потоком SteamController, путем вызова метода карты. .
  • StoreStreamListener завершает реконструкцию представления, прослушивая собственный поток.

Проще говоряStoreConnectorответственный за дата-центрStream<State>сталиStream<ViewModel>, то StoreStreamListener отвечает за прослушиваниеStream<ViewModel>изменения для обновления дочернего виджета.

обработать:

  1. Просмотр проблем слоя Действие
  2. Отправка в Магазине преобразует это действие вStream<State>, и добавить вchangeControllerизStream<State>ждет казни.
  3. StoreStreamListener прослушивает новыеStream<State>Inflow, преобразовать входящее состояние в ViewModel в соответствии с заранее согласованным с контрагентом скрытым методом, а затем передать ViewModel вStream<ViewModel>середина.
  4. Слой View прослушивает приток нового потока и перестраивает весь View.

преимущество:

  1. автоматическая подписка
  2. автоматическое уведомление
  3. Целевые уведомления
  4. Разделение представления и бизнес-логики

Я прочитал много статей в Интернете и чувствую, что использование Redux во Flutter кажется возможным решением, однако после просмотра статей с анализом Redux во Flutter в компании все еще много проблем.

Из-за прямой разницы между Dart и JavaScript возникает множество проблем, которые трудно решить при использовании Redux во Flutter.

Например, сравниваяStoreконструктор иcombineReducersфункция:

// dart
Store(
  this.reducer, {
  State initialState,
  List<Middleware<State>> middleware = const [],
  bool syncStream: false,
);
    
Reducer<State> combineReducers<State>(Iterable<Reducer<State>> reducers);
// ts
function createStore(reducer: Reducer);
function combineReducers(reducers: ReducersMapObject): Reducer;

В сочетании с прототипом функции и плоским использованием можно увидеть различия между ними. JScombineReducersПередаваемое значение являетсяReducerВ процессе выполнения функции скрытое дерево состояний каждой части интегрируется вместе, чтобы стать полным деревом.

В Dart такой динамической структуры нет, только при созданииStoreПри отображении всех переданных начальных деревьев состояний это противоречит концепции «развязки». Если разные части состояния будут храниться в разных хранилищах, обмен между этими состояниями станет очень затруднительным, что не согласуется с концепцией дизайна самого Redux.

А в Dart создание неизменяемых данных тоже очень хлопотно.В Dart нет оператора "..." для деструктуризации объектов в js.

const newState = {
    ...oldState,
    count: count + 1
};

Основываясь на вышеуказанных причинах, хотя поначалу я был более оптимистичен в отношении Redux, в конце концов я отказался от Redux...

BloC

Основная идея BloC — разделение данных и представления, а рендеринг управляется изменениями данных. (да, точно так же, как редукс)

В некотором смысле Redux можно рассматривать как особый вид BloC.

Вводный документ был опубликован большим парнем в Nuggets:nuggets.capable/post/684490…Я также прочитал эту статью, чтобы узнать соответствующие знания о BloC.

Разница между BloC и Redux заключается в том, что у Redux есть хранилище в центре обработки данных для хранения всех данных.Когда данные изменяются, центр обработки данных вызывает скрытый метод для преобразования состояния в соответствующую ViewModel, а затем уведомляет дочерний виджет о его изменении. .

В BloC нет понятия store, есть только один StreamController, но этот Controller не хранит данные, а только обрабатывает данные, а в BloC нет метода convert, Viwe будет напрямую преобразовывать State во ViewModel.

Основываясь на преимуществах Redux, BloC более тщательно разделяет бизнес и решает проблему, связанную с тем, что Redux сложно разделить состояния каждой части Приложение может иметь несколько источников данных, и их можно обрабатывать и объединять с помощью потоковых операций. Сильная масштабируемость плюс встроенная поддержка DartStreamКлассы также удобнее писать.

При использовании решения BloC в бизнесе нам не нужно повторно внедрять это решение с помощью Stream, и мы можем использовать его напрямуюflutter_blocБиблиотека может:GitHub.com/Felang El/ был бы…

reBloC

адрес:GitHub.com/red br OG Don/…rebloc — это реализация redux+bloc.

Rebloc is an attempt to smoosh together two popular Flutter state management approaches: Redux and BLoC. It defines a Redux-y single direction data flow that involves actions, middleware, reducers, and a store. Rather than using functional programming techniques to compose reducers and middleware from parts and wire everything up, however, it uses BLoCs.

The store defines a dispatch stream that accepts new actions and produces state objects in response. In between, BLoCs are wired into the stream to function as middleware, reducers, and afterware. Afterware is essentially a second chance for Blocs to perform middleware-like tasks after the reducers have had their chance to update the app state.

Официальное описание состоит в том, чтобы объединить схему потока данных Redux (решение направленного уведомления) и адаптивное программирование блока с меньшим количеством кода. Но я чувствую, что эта схема не только имеет сложность избыточности, но также вводит замкнутый поток потока блока, что в конечном итоге делает всю схему более сложной.

fish_redux

fish_redux — это набор решений для проектирования флаттера с открытым исходным кодом Али Сянью.zhuanlan.zhihu.com/p/55062930Он также основан на редуксе для немного улучшенной упаковки с несколькими новыми концепциями: адаптер и компонент.

Сам Redux предоставляет только глобальное решение для управления состоянием и не заботится о конкретном бизнесе. fish_redux — это улучшение redux для бизнеса.

Каждый компонент (Component) должен определять данные (Struct) и Reducer. В то же время зависимости между компонентами решают противоречие между централизацией и разделяй и властвуй.

Компонент Component的概念有点类似我们rematch中的model,含有View、Effect、Reducer三部分。 View负责展示 Effect负责非state修改的函数 Reducer负责修改state的函数

Адаптер Поскольку частота, используемая в ListView Flutter, fish_redux ListView сделана для оптимизации производительности, тем самым возникает адаптер.

Его цель — решить 3 проблемы модели Component в сценарии flutter-ListView:

  1. Размещение "Big-Cell" в компоненте не может обеспечить оптимизацию производительности кода ListView.
  2. Компонент не может различать появления|исчезновения и инициализации|распоряжаться.
  3. Связь между жизненным циклом Effect и представлением не соответствует интуитивным ожиданиям в контексте ListView. Короче говоря, нам нужен логический ScrollView, производительный ListView, абстракция частичного представления и функциональная инкапсуляция. Чтобы сделать такой независимый уровень абстракции, мы смотрим на фактический эффект, мы не используем фреймворк для страницы, мы используем компонент фреймворка и сравнение базовой производительности компонента фреймворка + адаптер.

Структура каталогов fish_redux:

  • page
  • --sample_page
  • ---- action.dart
  • ---- page.dart
  • ---- view.dart
  • ---- effect.dart
  • ---- reducer.dart
  • ---- state.dart
  • components
  • --sample_component
  • ---- action.dart
  • ---- component.dart
  • ---- view.dart
  • ---- effect.dart
  • ---- reducer.dart
  • ---- state.dart

преимущество:

  1. Данные управляются централизованно, и фреймворк автоматически выполняет слияние редюсеров.
  2. Управление компонентами по принципу «разделяй и властвуй», изолируйте компоненты и контейнеры друг от друга.
  3. Просмотр, Редуктор, Изоляция эффекта. Легко написать и использовать повторно.
  4. Декларативная сборка конфигурации.
  5. Хорошая масштабируемость.

Лично я считаю, что дизайн fish_redux подходит для сложных бизнес-сценариев в сочетании со сложной структурой каталогов и связанными с ними концепциями, он не подходит для обычных данных и менее сложного бизнеса.

Mobx

адрес:Общие .DEV / Пакеты / МО ...

Подобно BloC, MobX также является режимом наблюдателя. Но MobX скрывает все обновления и отправку сообщений в геттеры и сеттеры, поэтому разработчикам не нужно заботиться о времени отправки сообщения и ответа при его использовании, и компонент будет повторно отображать, когда любой объект, от которого он зависит, обновляется.

Версия MobX для Dart очень похожа на js. Поскольку в Dart нет декоратора, MobX используетmobx_codegenСгенерируйте часть кода вместо этой части работы:GitHub.com/Не дайте себя обмануть деталями/Поклонение…

import 'package:mobx/mobx.dart';

// Include generated file
part 'todos.g.dart';

// This is the class used by rest of your codebase
class Todo = TodoBase with _$Todo;

// The store-class
abstract class TodoBase implements Store {
  TodoBase(this.description);

  @observable
  String description = '';

  @observable
  bool done = false;
}

(Приведенный выше код скопирован из официальной демо-версии)

Вышеупомянутое создает класс, который содержит реактивное состояние и соответствующие методы. использоватьmobx_codegenСгенерировано_$Todoунаследовано отdescriptionа такжеdoneсвойства и добавить к ним дополнительные операции, чтобы можно было зафиксировать состояние.

Вот демо официального счетчика:

// package:mobx_examples/counter/counter.dart
import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter; // 这里是mobx_codegen生成的

abstract class CounterBase implements Store {
  @observable
  int value = 0;

  @action
  void increment() {
    value++;
  }
}
// counter_widget.dart
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx_examples/counter/counter.dart';

class CounterExample extends StatefulWidget {
  const CounterExample();

  @override
  CounterExampleState createState() => CounterExampleState();
}

class CounterExampleState extends State<CounterExample> {
  final Counter counter = Counter();

  @override
  Widget build(BuildContext context) {
    return Column(children: <Widget>[
      Observer(builder: (_) => Text('${counter.value}')),
      RaisedButton(
        child: Text('inc'),
        onPressed: counter.increment,
      )
    ]);
  }
}

Когда кнопка нажата,incrementметод будет запущен, тем самым изменивvalueзначение и сообщить об изменении.ObserverКомпонент повторно визуализируется после получения сообщения об обновлении. По принципу MobX можно обнаружить, что поднятые выше проблемы решаемы. Обновление компонента не имеет ничего общего с тем, изменилась ли ссылка на данные, его волнует только, изменилось ли используемое значение, поэтому нет необходимости рассматривать неизменяемую проблему вообще.

Преимущество MobX также в том, что другие решения труднее реализовать, поскольку он может создавать множество состояний с одинаковой структурой и соответствующими операциями с низкими затратами. Например, при ведении списка можно создать объект MobX в компоненте каждого элемента списка, а затем позволить вложенным в него компонентам реагировать на операцию обновления этого объекта. Взяв за пример вышеприведенный компонент, сколько бы объектов CounterExample не создавалось, в них будут соответствующие Counters, и нет необходимости неестественным образом комбинировать эти состояния. Кроме того, если есть другие типы компонентов с похожими состояниями и операциями, этот класс также можно использовать для уменьшения повторяющейся разработки. Это именно та проблема, которую хочет решить React Hooks.До этого не было подходящего способа справиться с этим сценарием в нативном React (для достижения аналогичного эффекта можно использовать миксины и в Dart). Redux должен иметь дело с массивами состояний или словарями состояний, что является сложной операцией, особенно в среде Dart. Метод BloC использует InheritWidget для получения контекста.Если в дереве компонентов есть несколько объектов состояния одного типа, легко вызвать конфликты, и для решения этой проблемы необходимо использовать дополнительные методы.

Суммировать

Подобно React, Flutter может использовать setState для управления локальным состоянием компонентов, но сложно управлять всем сложным приложением, используя только setState.

Среди упомянутых выше решений управления состоянием проще в использовании только BloC и MobX.Если вы привыкли к vue и MobX, рекомендуется использовать MobX напрямую.Отклик данных MobX почти прозрачен, и разработчики могут более свободно организовывать то, что они хотят состояние.

Flutter все еще чувствует себя слишком молодым, и также изучаются различные решения для управления состоянием.Как и fish_redux, созданный Xianyu, в сообществе нет идеального решения для управления состоянием.Лучше всего выбрать подходящее решение для управления состоянием в соответствии с ваше собственное дело Хороший ответ.

(Если в статье есть поток, паром подобран, пожалуйста, игнорируйте QAQ)