HTML Diff
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&lt;CounterModel&gt;( model: new CounterModel(), child: new Column(children: [ new ScopedModelDescendant&lt;CounterModel&gt;( builder: (context, child, model) =&gt; new Text('${model.counter}'), ), new Text("Другой виджет, который не зависит от CounterModel") ]) ); } }<p>В противовес использованию подхода setState(), данное решение позволяет отделить логику отображения от бизнес-логики. Однако оно накладывает определенные ограничения:</p>
23 class CounterApp extends StatelessWidget { @override Widget build(BuildContext context) { return new ScopedModel&lt;CounterModel&gt;( model: new CounterModel(), child: new Column(children: [ new ScopedModelDescendant&lt;CounterModel&gt;( builder: (context, child, model) =&gt; 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