0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p><strong>Управление состоянием во Flutter</strong>- горячая тема. Возможных вариантов решения задачи много и запутаться в них, выбирая наиболее подходящий под ваши потребности, - крайне просто. Я сам путался, но нашел подходящее решение. Позвольте поделиться им с вами.</p>
1
<p><strong>Управление состоянием во Flutter</strong>- горячая тема. Возможных вариантов решения задачи много и запутаться в них, выбирая наиболее подходящий под ваши потребности, - крайне просто. Я сам путался, но нашел подходящее решение. Позвольте поделиться им с вами.</p>
2
<p>Чтобы найти решение, подходящее к вашим потребностям, необходимо определить сами потребности. В моем случае это:</p>
2
<p>Чтобы найти решение, подходящее к вашим потребностям, необходимо определить сами потребности. В моем случае это:</p>
3
<ol><li>Иметь возможность развития проекта без ущерба качеству кода.</li>
3
<ol><li>Иметь возможность развития проекта без ущерба качеству кода.</li>
4
<li>Разделить логику отображения от бизнес-логики.</li>
4
<li>Разделить логику отображения от бизнес-логики.</li>
5
<li>Иметь понятный код, который сложно поломать.</li>
5
<li>Иметь понятный код, который сложно поломать.</li>
6
<li>Предсказуемость и понятность кода.</li>
6
<li>Предсказуемость и понятность кода.</li>
7
</ol><p>Учитывая эти требования, подходящими вариантами остаются:</p>
7
</ol><p>Учитывая эти требования, подходящими вариантами остаются:</p>
8
<ol><li>Использование метода setState() и Stateful-виджетов.</li>
8
<ol><li>Использование метода setState() и Stateful-виджетов.</li>
9
<li>Библиотека ScopedModel.</li>
9
<li>Библиотека ScopedModel.</li>
10
<li>Применение паттерна BLoC (Компоненты бизнес-логики).</li>
10
<li>Применение паттерна BLoC (Компоненты бизнес-логики).</li>
11
<li>Redux.</li>
11
<li>Redux.</li>
12
</ol><h2>Разница между локальным и глобальным состоянием</h2>
12
</ol><h2>Разница между локальным и глобальным состоянием</h2>
13
<p>Перед тем, как погрузиться в анализ отобранных решений, необходимо понять разницу между локальным и глобальным состоянием. Для этого подойдет практический пример.</p>
13
<p>Перед тем, как погрузиться в анализ отобранных решений, необходимо понять разницу между локальным и глобальным состоянием. Для этого подойдет практический пример.</p>
14
<p>Представим форму авторизации, где пользователю предлагается ввести логин и пароль и получить объект "личности пользователя" после отправки формы. В этом примере любая проверка данных, вводимых в поля формы, будет являться частью локального состояния виджета формы авторизации, и остальная часть приложения не должна знать об этом. А возвращаемый сервером авторизации объект "личности" - частью глобального состояния. Так как от этого объекта зависят другие компоненты, меняющие поведение в зависимости от того, авторизован ли пользователь.</p>
14
<p>Представим форму авторизации, где пользователю предлагается ввести логин и пароль и получить объект "личности пользователя" после отправки формы. В этом примере любая проверка данных, вводимых в поля формы, будет являться частью локального состояния виджета формы авторизации, и остальная часть приложения не должна знать об этом. А возвращаемый сервером авторизации объект "личности" - частью глобального состояния. Так как от этого объекта зависят другие компоненты, меняющие поведение в зависимости от того, авторизован ли пользователь.</p>
15
<h4>Краткие выводы для тех, кто устал ждать</h4>
15
<h4>Краткие выводы для тех, кто устал ждать</h4>
16
<p>Если вы не хотите ждать, или не заинтересованы в моих исследованиях, то вот краткий обзор полученных результатов:</p>
16
<p>Если вы не хотите ждать, или не заинтересованы в моих исследованиях, то вот краткий обзор полученных результатов:</p>
17
<p>Моя рекомендация - использовать BLoC для управления локальным состоянием и Redux для глобального состояния, особенно если вы создаете сложное приложение, которое будет расти со временем.</p>
17
<p>Моя рекомендация - использовать BLoC для управления локальным состоянием и Redux для глобального состояния, особенно если вы создаете сложное приложение, которое будет расти со временем.</p>
18
<h2>Почему не стоит использовать setState()</h2>
18
<h2>Почему не стоит использовать setState()</h2>
19
<p>Использование setState() внутри ваших виджетов отлично подходит для быстрого создания прототипов и получения обратной связи на эти вносимые изменения, но данный путь не помогает нам достичь поставленных целей, потому логика отображения смешивается с бизнес-логикой, чем нарушается принцип чистоты и качества кода. Сопровождение подобного кода будет сложным в будущем, поэтому, кроме как для создания прототипов, данный подход не рекомендуется.</p>
19
<p>Использование setState() внутри ваших виджетов отлично подходит для быстрого создания прототипов и получения обратной связи на эти вносимые изменения, но данный путь не помогает нам достичь поставленных целей, потому логика отображения смешивается с бизнес-логикой, чем нарушается принцип чистоты и качества кода. Сопровождение подобного кода будет сложным в будущем, поэтому, кроме как для создания прототипов, данный подход не рекомендуется.</p>
20
<h2>ScopedModel - шаг в верном направлении</h2>
20
<h2>ScopedModel - шаг в верном направлении</h2>
21
<p>ScopedModel - библиотека стороннего разработчика Brian Egan. Она дает возможность создавать специальные объекты Models, а также использовать метод notifyListeners() тогда, когда это необходимо. Например, для отслеживания любого изменения свойства объекта модели:</p>
21
<p>ScopedModel - библиотека стороннего разработчика Brian Egan. Она дает возможность создавать специальные объекты Models, а также использовать метод notifyListeners() тогда, когда это необходимо. Например, для отслеживания любого изменения свойства объекта модели:</p>
22
class CounterModel extends Model { int _counter = 0; int get counter = _counter; void increment() { _counter++; notifyListeners(); } }<p>В наших виджетах мы сможем реагировать на изменения в модели с помощью предоставляемого данной библиотекой виджета ScopedModelDescendant:</p>
22
class CounterModel extends Model { int _counter = 0; int get counter = _counter; void increment() { _counter++; notifyListeners(); } }<p>В наших виджетах мы сможем реагировать на изменения в модели с помощью предоставляемого данной библиотекой виджета ScopedModelDescendant:</p>
23
class CounterApp extends StatelessWidget { @override Widget build(BuildContext context) { return new ScopedModel<CounterModel>( model: new CounterModel(), child: new Column(children: [ new ScopedModelDescendant<CounterModel>( builder: (context, child, model) => new Text('${model.counter}'), ), new Text("Другой виджет, который не зависит от CounterModel") ]) ); } }<p>В противовес использованию подхода setState(), данное решение позволяет отделить логику отображения от бизнес-логики. Однако оно накладывает определенные ограничения:</p>
23
class CounterApp extends StatelessWidget { @override Widget build(BuildContext context) { return new ScopedModel<CounterModel>( model: new CounterModel(), child: new Column(children: [ new ScopedModelDescendant<CounterModel>( builder: (context, child, model) => new Text('${model.counter}'), ), new Text("Другой виджет, который не зависит от CounterModel") ]) ); } }<p>В противовес использованию подхода setState(), данное решение позволяет отделить логику отображения от бизнес-логики. Однако оно накладывает определенные ограничения:</p>
24
<ol><li>Если Model становится сложной, то сложным становится и определение того, когда нужно использовать метод notifyListeners(), а когда нет - чтобы избежать лишнего обновления интерфейса.</li>
24
<ol><li>Если Model становится сложной, то сложным становится и определение того, когда нужно использовать метод notifyListeners(), а когда нет - чтобы избежать лишнего обновления интерфейса.</li>
25
<li>API, предоставляемый Model, в общем не точно описывает асинхронную природу интерфейса приложений.</li>
25
<li>API, предоставляемый Model, в общем не точно описывает асинхронную природу интерфейса приложений.</li>
26
</ol><p>Учитывая все это, если состояние вашего приложения не легкое для управления, я не рекомендую использовать данных подход. Я просто не верю, что он способен продуктивно обеспечить рост и сложность приложений.</p>
26
</ol><p>Учитывая все это, если состояние вашего приложения не легкое для управления, я не рекомендую использовать данных подход. Я просто не верю, что он способен продуктивно обеспечить рост и сложность приложений.</p>
27
<h2>Мощное решение - BLoC</h2>
27
<h2>Мощное решение - BLoC</h2>
28
<p>Данный паттерн был придуман в Google и там же его используют. Он поможет нам достичь следующих целей:</p>
28
<p>Данный паттерн был придуман в Google и там же его используют. Он поможет нам достичь следующих целей:</p>
29
<ol><li>Разделение логики отображения от бизнес-логики.</li>
29
<ol><li>Разделение логики отображения от бизнес-логики.</li>
30
<li>Использование асинхронной природы для отображения интерфейса.</li>
30
<li>Использование асинхронной природы для отображения интерфейса.</li>
31
<li>Возможность переиспользования в разных Dart-приложениях, таких как Flutter или AngularDart.</li>
31
<li>Возможность переиспользования в разных Dart-приложениях, таких как Flutter или AngularDart.</li>
32
</ol><p>Идея данного подхода очень проста:</p>
32
</ol><p>Идея данного подхода очень проста:</p>
33
<ul><li>BLoC использует</li>
33
<ul><li>BLoC использует</li>
34
</ul><p>Api для описания асинхронно поступающих в наши компоненты данных;</p>
34
</ul><p>Api для описания асинхронно поступающих в наши компоненты данных;</p>
35
<ul><li>BLoC использует</li>
35
<ul><li>BLoC использует</li>
36
</ul><p>Api для описания асинхронно возвращаемых нашими компонентами данных;</p>
36
</ul><p>Api для описания асинхронно возвращаемых нашими компонентами данных;</p>
37
<ul><li>наконец, мы можем использовать виджет StreamBuilder для управления потоками данных без приложения усилий с нашей стороны к подпискам на обновления данных и перерисовку виджетов.</li>
37
<ul><li>наконец, мы можем использовать виджет StreamBuilder для управления потоками данных без приложения усилий с нашей стороны к подпискам на обновления данных и перерисовку виджетов.</li>
38
</ul><p>У Google есть хорошие примеры использования данного паттерна управления состоянием, потому что он широко используется и очень рекомендуется компанией.</p>
38
</ul><p>У Google есть хорошие примеры использования данного паттерна управления состоянием, потому что он широко используется и очень рекомендуется компанией.</p>
39
<p>Я и сам очень рекомендую использовать данный подход для управления локальным состоянием, однако он подойдет даже и для управления глобальным состоянием. Однако в последнем случае вы столкнетесь с проблемой - где и как правильно внедрять BLoC, чтобы к нему имели доступ разные компоненты, и тут на сцену выходит<strong>Redux</strong>.</p>
39
<p>Я и сам очень рекомендую использовать данный подход для управления локальным состоянием, однако он подойдет даже и для управления глобальным состоянием. Однако в последнем случае вы столкнетесь с проблемой - где и как правильно внедрять BLoC, чтобы к нему имели доступ разные компоненты, и тут на сцену выходит<strong>Redux</strong>.</p>
40
<h2>Redux и BLoC - идеальный микс для меня</h2>
40
<h2>Redux и BLoC - идеальный микс для меня</h2>
41
<p>Одной из целей, которые я описывал в начале статьи, был поиск чего-то, широко используемого и предсказуемого, и это Redux - паттерн и набор инструментов, которые вместе помогают нам управлять глобальным состоянием. Он имеет три базовых принципа в основе:</p>
41
<p>Одной из целей, которые я описывал в начале статьи, был поиск чего-то, широко используемого и предсказуемого, и это Redux - паттерн и набор инструментов, которые вместе помогают нам управлять глобальным состоянием. Он имеет три базовых принципа в основе:</p>
42
<ol><li>Единственный источник истины - все состояние state вашего приложения хранится в древовидном объекте в единственном хранилище store.</li>
42
<ol><li>Единственный источник истины - все состояние state вашего приложения хранится в древовидном объекте в единственном хранилище store.</li>
43
<li>Состояние доступно только для чтения - единственный способ изменить состояние - это вызвать специальный объект action, описывающий, что должно произойти с состоянием.</li>
43
<li>Состояние доступно только для чтения - единственный способ изменить состояние - это вызвать специальный объект action, описывающий, что должно произойти с состоянием.</li>
44
<li>Изменения производятся с помощью чистых функций - для определения того, что изменяется в состоянии вы пишите чистую функцию reducer, которая не должна вызывать никаких побочных эффектов. Ссылка на пример кода:<em>https://pub.dartlang.org/packages/redux</em>.</li>
44
<li>Изменения производятся с помощью чистых функций - для определения того, что изменяется в состоянии вы пишите чистую функцию reducer, которая не должна вызывать никаких побочных эффектов. Ссылка на пример кода:<em>https://pub.dartlang.org/packages/redux</em>.</li>
45
</ol><p>Данный подход к управлению состоянием широко принят web-разработчиками, и его появление на мобильных устройствах поможет получить преимущества web и mobile-application разработчикам.</p>
45
</ol><p>Данный подход к управлению состоянием широко принят web-разработчиками, и его появление на мобильных устройствах поможет получить преимущества web и mobile-application разработчикам.</p>
46
<p>Brian Egan разрабатывает как оригинальный Redux, так и flutter_redux, а также он сделал потрясающее Todo приложение, в котором он применил множество архитектурных паттернов, включая Redux.</p>
46
<p>Brian Egan разрабатывает как оригинальный Redux, так и flutter_redux, а также он сделал потрясающее Todo приложение, в котором он применил множество архитектурных паттернов, включая Redux.</p>
47
<p>Учитывая все качества Redux, я очень сильно советую использовать его для управления глобальным состоянием, но вы должны быть уверены, что не используете его для управления локальным состоянием, если хотите масштабировать свое приложение.</p>
47
<p>Учитывая все качества Redux, я очень сильно советую использовать его для управления глобальным состоянием, но вы должны быть уверены, что не используете его для управления локальным состоянием, если хотите масштабировать свое приложение.</p>
48
<h2>Последние слова</h2>
48
<h2>Последние слова</h2>
49
<p>В данной статье нет полностью правильного или неправильного решения. Чтобы определиться с тем, какой подход применять в вашем проекте, вам необходимо определиться с вашими потребностями. Для меня и моих целей комбинация Redux и BLoC позволяет моим проектам быстро и безопасно расти, а также облегчает вход сторонних разработчиков в эти проекты, благодаря доступным и понятным инструментам. Однако не у всех одинаковые потребности и с течением времени можно находить как проблемы в текущих инструментах, так и еще лучшие решения. Очень важно всегда оставаться любопытным, учиться и думать, подходит ли вам тот или иной инструмент.</p>
49
<p>В данной статье нет полностью правильного или неправильного решения. Чтобы определиться с тем, какой подход применять в вашем проекте, вам необходимо определиться с вашими потребностями. Для меня и моих целей комбинация Redux и BLoC позволяет моим проектам быстро и безопасно расти, а также облегчает вход сторонних разработчиков в эти проекты, благодаря доступным и понятным инструментам. Однако не у всех одинаковые потребности и с течением времени можно находить как проблемы в текущих инструментах, так и еще лучшие решения. Очень важно всегда оставаться любопытным, учиться и думать, подходит ли вам тот или иной инструмент.</p>
50
<p><em>Первоисточник: https://medium.com/flutter-community/let-me-help-you-to-understand-and-choose-a-state-management-solution-for-your-app-9ffeac834ee3.</em></p>
50
<p><em>Первоисточник: https://medium.com/flutter-community/let-me-help-you-to-understand-and-choose-a-state-management-solution-for-your-app-9ffeac834ee3.</em></p>
51
51