Flutter: крючки для виджетов

Созданный на 12 дек. 2018  ·  100Комментарии  ·  Источник: flutter/flutter

Команда React недавно анонсировала хуки: https://medium.com/@dan_abramov/making -sense-of-react-hooks-fdbde8803889. Из-за того, насколько Flutter похож на React, вероятно, было бы интересно посмотреть, подходят ли они и для Flutter.

Определение:

Хуки очень похожи на State из StatefulWidget с одним основным отличием: мы можем иметь столько хуков, сколько захотим на виджете.
Хуки имеют доступ ко всем жизненным циклам, которые есть у State (или модифицированной версии).

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

Хуки отличаются от суперпримесей тем, что они не могут создавать конфликты. Хуки _полностью_ независимы и не связаны с виджетом.
Это означает, что хук можно использовать для хранения значений и их публичного раскрытия, не опасаясь конфликтов. Это также означает, что мы можем повторно использовать один и тот же хук несколько раз, в отличие от примесей.

Принцип:

Хуки в основном хранятся в массиве Element . Они доступны только из метода виджета build . И хуки должны быть доступны безоговорочно, например:

ДЕЛАТЬ:

Widget build(BuildContext context) {
  Hook.use(MyHook());
}

НЕ:

Widget build(BuildContext context) {
  if (condition) {
    Hook.use(MyHook());
  }
}

Это ограничение может показаться очень ограничивающим, но оно связано с тем, что хуки хранятся по их индексу. Не их тип и не имя.
Это позволяет повторно использовать один и тот же хук сколько угодно раз без каких-либо столкновений.

Вариант использования

Самая полезная часть хуков заключается в том, что они позволяют извлекать логику жизненного цикла в повторно используемый компонент.

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

Это приводит к общему фрагменту кода: stanim

class Example extends StatefulWidget {
  <strong i="7">@override</strong>
  ExampleState createState() => ExampleState();
}

class ExampleState extends State<Example>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  <strong i="8">@override</strong>
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
  }

  <strong i="9">@override</strong>
  void dispose() {
    super.dispose();
    _controller.dispose();
  }

  <strong i="10">@override</strong>
  Widget build(BuildContext context) {
    return Container(

    );
  }
}

Хуки решают эту проблему, извлекая логику жизненного цикла. Это приводит к потенциально _намного_ меньшему коду:

class Example extends StatelessWidget {
  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    AnimationController controller = useAnimationController(duration: const Duration(seconds: 1));
    return Container(

    );
  }
}

Наивной реализацией такого хука может быть следующая функция:

AnimationController useAnimationController({Duration duration}) {
  // use another hook to obtain a TickerProvider
  final tickerProvider = useTickerProvider();

  // create an AnimationController once
  final animationController = useMemoized<AnimationController>(
    () => AnimationController(vsync: tickerProvider, duration: duration)
  );
  // register `dispose` method to be closed on widget removal
  useEffect(() => animationController.dispose, [animationController]), 

   // synchronize the arguments
  useValueChanged(duration, (_, __) {
    animationController.duration = duration;
  });

  return animationController;
}

useAnimationController — один из таких «крючков».

Где наивная реализация такого хука будет следующей:

Этот код должен выглядеть так же, как кто-то, кто использовал класс State . Но тут есть несколько интересных моментов:

  • Хук полностью заботится о AnimationController , от создания до удаления. Но и обновления.
  • Его можно легко извлечь в многоразовую упаковку.
  • Он также заботится об обновлении duration при горячей перезагрузке, в отличие от создания AnimationController в initState .
  • Хуки могут использовать другие хуки для создания более сложной логики.

Недостатки

Крючки платные. Доступ к значению имеет накладные расходы, аналогичные InheritedElement . И они также требуют создания нового набора недолговечных объектов, таких как замыкания или «виджеты».

Мне еще предстоит запустить тест, чтобы увидеть реальную разницу, хотя


Другая проблема связана с горячей перезагрузкой.

Добавление хуков в конец списка, если все в порядке. Но поскольку хуки работают в порядке их следования, добавление хуков в середине существующих хуков приведет к частичному сбросу состояния. Пример:

Переход от A, B к A, C, B приведет к сбросу состояния B (повторный вызов dispose и initHook ).

Вывод

Хуки _намного_ упрощают мир виджетов, позволяя повторно использовать больший объем кода. Особенно в очень распространенных сценариях, таких как удаление, запоминание и просмотр значения.

Их можно использовать для полной замены StatefulWidget.

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

Можно извлекать хуки вне Flutter, создавая собственные элементы. На данный момент нет необходимости изменять исходники.

Но из-за специфики хуков _много_ выгоды от пользовательского линтера. Какие внешние пакеты не могут предоставить на данный момент.

Бонус

~ Незавершенная реализация доступна здесь: https://github.com/rrousselGit/flutter_hooks (последние функции находятся в ветке prod-ready ).~

~Выпуск планируется в ближайшее время в виде альфа-версии. Но текущая реализация работает до некоторой степени.~

Доступно здесь https://pub.dartlang.org/packages/flutter_hooks# -readme-tab-

framework new feature

Самый полезный комментарий

Просто хотел сказать — если кто-то из команды Flutter захочет поговорить один на один о хуках, я буду рад объяснить исторический и технический контекст, лежащий в основе того, почему мы внедряем их в React. Этот комментарий также может быть интересен: https://github.com/reactjs/rfcs/pull/68#issuecomment -439314884.

Все 100 Комментарий

@Hixie @eseidelGoogle

Вот продолжение https://twitter.com/ericmander/status/1070024779015479302 с некоторыми подробностями того, что я имел в виду. На всякий случай, если команда Flutter захочет продолжить инвестировать в эту тему.

Привет @rrousselGit На днях я просматривал твой пакет flutter_hooks. Я не уверен, какие варианты у нас есть для их реализации во Flutter, но я заметил, что вы передаете методы хуков через HookContext, тогда как в React хуки можно создавать отдельно и использовать как своего рода «примесь состояния». Ключевое отличие состоит в том, что flutter_hooks требует, чтобы все методы ловушек были встроены в пакет, в то время как React позволяет полностью разделять методы ловушек.

Казалось бы, это будет иметь решающее значение для соответствия спецификации React. Есть ли у вас мысли о том, как этого можно достичь?

РЕДАКТИРОВАТЬ :

HookContext был удален, и все хуки теперь являются статическими методами. Итак, следующее уже устарело


Мой пакет делает это для автоматического завершения, но это не обязательно.

Вместо

context.useSomeHook()

Мы можем иметь

useSomeHook(context)

Это строго ничего не меняет, кроме возможности обнаружения.

Ключевой особенностью является

T HookContext.use<T>(Hook);

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

Но из-за специфики хуков было бы много пользы от пользовательского линтера. Какие внешние пакеты не могут предоставить на данный момент.

Я бы порекомендовал вам проголосовать (и добавить свой вариант использования)
https://github.com/dart-lang/linter/issues/697. Было бы здорово, если бы пакеты могли предоставлять свои собственные пользовательские линты.

Другая проблема связана с горячей перезагрузкой.

Меня это беспокоит. Было бы грустно, если бы мы ухудшили работу с горячей перезагрузкой.

Доступно здесь https://pub.dartlang.org/packages/flutter_hooks

Можете ли вы уточнить, в чем преимущества включения этого в структуру Flutter по сравнению с наличием внешнего пакета?

Что касается горячей перезагрузки, я интегрировал ее, и она удобна в использовании.

Единственная реальная проблема - это # ​​26503, для устранения которой, похоже, требуется изменение инструментов. И у меня нет пропускной способности, чтобы сделать это.


Есть несколько небольших улучшений, которые мы можем внести в хуки, интегрировав их во Flutter:

  • Некоторое увеличение производительности, не зависящее от StatefulElement
  • Рефакторинг/анализ, который в настоящее время невозможен или очень ограничен с использованием Analyzer_plugin

Но я думаю, что главная причина в том, что хуки — это часть более масштабной работы над React, то есть асинхронного рендеринга.

Если Flutter когда-либо захочет пойти по маршруту асинхронного рендеринга (а он должен), для этого потребуются две вещи:

  • Множественные изменения фреймворка
  • Что-то похожее на крючки

Есть также очевидный аспект сообщества. С 270 звездами в месяц и местом на https://github.com/trending/dart?since=monthly , хотя я их особо не рекламировал, хуки определенно вызывают интерес сообщества.

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

С официальной поддержкой хуков это экспоненциально увеличит усилия сообщества.

Любопытно: не могли бы вы объяснить, как вы заставили его работать с горячей перезагрузкой?

Я расширил StatefulElement вместо ComponentElement, чтобы иметь доступ к reassemble .
Это позволяет следующему вызову build изменить список ловушек, не допуская при этом обычной перестройки.

По сути, если previousHook.runtimeType != newHook.runtimeType по заданному индексу, этот хук и все последующие будут удалены.

Часть «все последующие» связана с тем, что хук может использовать другие хуки; поэтому просто удалить один недостаточно.
После месяца постоянного использования это редко проблема.

На самом деле все наоборот, горячая перезагрузка кажется улучшенной. Поскольку все находится в рамках метода build , горячая перезагрузка всегда применяет изменения (в отличие от initState ). Типичный пример AnimationController :

final animationController = useAnimationController(duration: const Duration(second: 1));

Этот хук обеспечивает плавную горячую перезагрузку длительности; что обычно требует горячего перезапуска.

Я начал переводить часть своего основного приложения, чтобы удалить некоторые шаблоны. Я не рассматриваю хуки как функцию, а как полноценный паттерн. Есть много тем, которые только и ждут, чтобы их реализовали, и они значительно улучшат производительность, а также обслуживание. Я начал с анимации, удаления контроллеров и хотел бы увидеть хуки для Firebase или другие распространенные случаи.

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

👍 Я тоже осознал это через несколько недель после того, как впервые увидел предложение Себастьяна.

Часть «все последующие» связана с тем, что хук может использовать другие хуки; поэтому просто удалить один недостаточно.

Я также подумал, что в JS может быть полезна эвристика: если горячая перезагрузка приводит к броску render ( build Flutter) (например, из-за несоответствия типов), мы сбрасываем все состояния хуков и повторяем попытку. Если он снова потерпит неудачу, то мы потерпим неудачу.

Я думаю, у вас нет этой проблемы из-за строгой типизации? Я не уверен. Что произойдет, если вы начнете редактирование со строкового состояния Hook, а затем измените код так, чтобы он воспринимался как число?

Что произойдет, если вы начнете редактирование со строкового состояния Hook, а затем измените код так, чтобы он воспринимался как число?

Вы имеете в виду переход от:

final counter = useState(0)

к:

final name = useState('foo');

?

Если это так, система типизации правильно определяет, что тип изменился. Это потому, что тип useState на самом деле useState<int> против useState<String> .

JS, скорее всего, придется здесь тяжелее.

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

(Тем не менее, https://pub.dartlang.org/packages/flutter_hooks кажется, что здесь уже все реализовано, поэтому я предполагаю, что нам не нужно ничего добавлять на платформу?)

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

Я довольно плохо разбираюсь в маркетинге, поэтому настоятельно рекомендую посмотреть выступление React Team об этом. Их введение начинается здесь https://youtu.be/dpw9EHDh2bM.

Далее последовало сравнение до и после от Дэна Абрамова (который ответил здесь на несколько комментариев выше).

Дело в том, что все, что они говорят в этом разговоре, относится и к Flutter (включая их блог о вреде миксинов https://reactjs.org/blog/2016/07/13/mixins-considered-harmful.html).


Тем не менее, хуки можно охарактеризовать следующим броским предложением:

С помощью хуков вы можете «публиковать» еще больший процент вашего приложения.

Это связано с тем, что крючки могут быть полностью извлечены из виджета и опубликованы в пабе для использования другими. В настоящее время это невозможно с StatefulWidget , потому что наша логика привязана к жизненным циклам State .

Другой эффект хуков заключается в том, что они изменяют способ группировки кода. В настоящее время у нас есть следующее:

initState() {
  a = A(widget.foo);
  b = B(widget.bar);
}

didUpdateWidget(SomeWidget old) {
  if (old.foo != widget.foo) {
    a.foo = widget.foo;
  }
  if (old.bar != widget.bar) {
    b.bar = widget.bar;
  }
}

dispose() {
  a.dispose();
  b.dispose();
}

Все смешано и разделено на несколько жизненных циклов. Становится очень трудно увидеть, забыли ли мы удалить/обновить переменную.

С хуками получается следующее:

final a = useMemoized(() => A(foo));
useValueChanged(foo, () => a.foo = foo});
useEffect(() => a.dispose, [a]);

final b = useMemoized(() => B(bar));
useValueChanged(bar, () => b.bar = bar});
useEffect(() => b.dispose, [b]);

Теперь все перегруппировано. Становится гораздо более очевидным, забыли ли мы удалить/обновить переменную или нет, и мы можем легко извлечь эту логику в статическую функцию, которая делает все внутри.

Крючки соответствуют DRY. Мы пишем один раз логику для создания/обновления/удаления переменной. И мы повторно используем его везде вместо того, чтобы копировать и вставлять.

Наглядный пример (в React):


В целом, хотя невероятно, что можно реализовать хуки вне репозитория Flutter; Я не думаю, что это должно оставаться как есть.

Это как если бы Flutter предоставил только базовый класс Widget и сделал бы StatefulWidget внешней зависимостью: это не имеет смысла.

Flutter — это фреймворк, мы не хотим предлагать только основы

Это означает, что мы также хотим:

  • предоставить крючки для всех аспектов фреймворка, которые выиграют от них
  • взаимодействовать с инспектором виджетов (текущий пользовательский интерфейс не оптимизирован для такого варианта использования)
  • рефакторинг/линтер
  • предоставить утилиту для тестирования

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

Хуки являются СУХИМИ и представляют собой миксины бизнес-логики/состояния, которые способствуют чистому совместному размещению кода. Альтернативой является фрагментированное повторение в кодовой базе.

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

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

Мой наивный вопрос заключается в том, имеет ли смысл поддерживать функциональный виджет во флаттере. Я понимаю, что в настоящее время функциональный виджет не рекомендуется из-за отсутствия ссылки на собственный элемент и отсутствия собственного BuildContext. Было бы полезно ввести новый функциональный механизм, который может создавать те же самые объекты под ним, что и виджет класса?

@ivenxu Это то, что я тоже создал в другом пакете: https://github.com/rrousselGit/functional_widget

Это генератор кода, который генерирует класс из функции (с ключами и всем остальным) и совместим с хуками для создания HookWidget

Сумма обоих:

<strong i="11">@hwidget</strong>
Widget foo(BuildContext context, {Duration duration}) {
  final controller = useAninationController(duration: duration);
  useEffect(controller.forward, [controller]);

  final value = useAnination(controller);
  return Text(value.toString());
}

Это делает хороший текст, который идет от 0 до 1 постепенно.
В 6 строках кода...
Что потребовало бы более 30 строк с обычными виджетами.

И я не считаю автоматически сгенерированные переопределения debugFillProperties и operator== .

@rrousselGit Я знал о вашей работе над функциональным виджетом, хороший материал и спасибо! Однако я говорю о нативной поддержке от флаттера.

Кстати, как проходит приложенная отладка для code-gen? Возможно ли установить точку останова в foo() и проверить локальную переменную? Как выглядит стек вызовов?

Иногда функциональная парадигма для компонента/виджета намного короче, чем аналог класса. Поэтому я ищу идеи, будет ли какое-либо значение поддерживаемого родным функциональным виджетом.

С хуками получается следующее

Я действительно думаю, что картина «до» здесь намного яснее и понятнее, чем картина «после». Хотя, конечно, это субъективное мнение.

Это как если бы Flutter предоставил только базовый класс Widget и сделал бы StatefulWidget внешней зависимостью: это не имеет смысла.

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

Я думаю, что имеет смысл иметь это как пакет для тех людей, которые хотят его использовать. Если есть что-то, что мы можем сделать в основной структуре, чтобы сделать ее лучше, эффективнее и т. д., то мы определенно должны это сделать.

Я думаю, что это отличный проект, и я с интересом слежу за пакетом.

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

@rrousselGit Я подозреваю, что вы добьетесь большого успеха, продолжая разрабатывать это как отдельный пакет и открывая проблемы / PR для основной структуры, которые помогут добиться большего успеха. Если есть что-то, что вам нужно выставить во фреймворке, чего в настоящее время нет, чтобы заставить эту работу работать, давайте займемся этим. Если во фреймворке есть ошибка, которая не позволяет этому работать, давайте заставим это работать!

И FWIW, я говорю это, имея аналогичный опыт с flutter_svg, который привел к некоторому вкладу в структуру и смог быть полезен многим людям в качестве пакета.

Я только что понял:
Если хуки реализованы во Flutter, хорошим кандидатом является ComponentElement.

Это означает, что вместо нового типа Widget, StatelessWidget и StatefulWidget будут совместимы с хуками.

Такой подход может позволить сосуществовать обоим синтаксисам. Это может быть полезно, учитывая, что оба синтаксиса имеют свои плюсы и минусы.


Меня пока устраивает, что крючки находятся за пределами Flutter.

Меня беспокоит только то, что, будучи неофициальным, это сократит общее использование хуков. И, следовательно, уменьшите количество хуков, доступных на пабе.

Учитывая, что самым большим преимуществом хуков является влияние на сообщество (пользовательские хуки можно публиковать в пабах), это снижает их полезность.

Просто хотел сказать — если кто-то из команды Flutter захочет поговорить один на один о хуках, я буду рад объяснить исторический и технический контекст, лежащий в основе того, почему мы внедряем их в React. Этот комментарий также может быть интересен: https://github.com/reactjs/rfcs/pull/68#issuecomment -439314884.

@Hixie или @dnfield кто-нибудь из вас когда-нибудь связывался с @gaearon?

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

Я думаю, было бы здорово, если бы была попытка оценить, подходит ли эта парадигма для Flutter ❤️

Я пропустил этот комментарий! Я только что обратился к ним.

Должен признаться, я не совсем понимаю, как хуки применимы к флаттеру. Есть несколько пакетов Hooks, если вы хотите поэкспериментировать с ними (некоторые упомянуты в этой ошибке выше). По сути, я не очень понимаю, какую проблему решает концепция.

Для получения более подробной информации о моей точке зрения см. Мои более ранние комментарии: https://github.com/flutter/flutter/issues/25280#issuecomment -455846788, https://github.com/flutter/flutter/issues/25280#issuecomment - 455847134, https://github.com/flutter/flutter/issues/25280#issuecomment-456272076 .

По сути, я не очень понимаю, какую проблему решает концепция.

Хуки похожи на примеси состояния, но без присущих примесям проблем: нет конфликта переменных и их можно использовать более одного раза.

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

Каждому контроллеру нужны как initState, didUpdateWidget, так и dispose, которые _всегда_ реализуются одинаково.

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


Настоящая проблема с хуками заключается в том, что для их реализации требуется много работы.

Основная структура проста. Но из-за своего уникального поведения они требуют глубокой интеграции с инструментами линтера и рефакторинга.

И для оптимальной реализации могут потребоваться некоторые языковые функции, такие как кортеж/деструктуризация.

Я не думаю, что это действительно касается комментариев, которые я имел выше (https://github.com/flutter/flutter/issues/25280#issuecomment-455846788 и https://github.com/flutter/flutter/issues/25280#). в частности, issuecomment-456272076).

Хм, @gaearon , вероятно, объяснит это лучше меня, но с моей точки зрения:

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

  • меньше багов. Их декларативный API затрудняет забывание обновить/очистить состояние.

  • насчет «почему они должны быть основными?» мне нравится метафора Дэна Абрамова.
    Крючки для виджетов — то же, что электроны для атомов.
    Нет смысла исключать их, когда они являются примитивами, используемыми для создания больших вещей.
    Дело не только в реализации хуков. Вся структура может извлечь из них пользу.
    Их можно использовать для улучшения таких вещей, как анимация, формы и многое другое.

@Hixie , ты проводил время, используя хуки, или ты сводишь их в таблицу, чтобы принять решение? Я не уверен, какой % времени команда Flutter тратит на написание фреймворка по сравнению с написанием приложений для клиентов. Я подозреваю, что люди, которые пишут приложения для клиентов, подойдут к этому вопросу немного с другой точки зрения, чем разработчики фреймворков. То есть, как это сделает меня более эффективным и принесет больше пользы моим клиентам, если оно не соответствует тем же критериям, что и существующее решение.

Недавно я представил flutter_hooks вместе с functions_widget в одном из наших новых горячих приложений.

Это значительно сократило шаблонный код, и после рефакторинга всех этих беспорядочных виджетов с отслеживанием состояния в крошечные функции с хуками кодовая база сократилась примерно на 70%.

С точки зрения более высокого уровня функциональный виджет с набором хуков похож на императивное упрощение объединения нескольких наблюдаемых с использованием combLatest в RX. Но вместо того, чтобы явно указывать все наблюдаемые в преамбуле и иметь функцию, работающую с ними, хуки гораздо более гибкие, потому что вы вплетаете их в код, где вам нужно значение.

Но, в конце концов, каждый такой виджет похож на этап в RX-потоке.

Я определенно не думаю, что нам следует удалять flutter_hooks. Я просто не вижу, что мы (команда Flutter) можем предложить более ценного.

Крючки для виджетов — то же, что электроны для атомов.

Я думаю, если мы следуем этой метафоре, вы должны рассматривать Флаттер как кварки или, может быть, адроны. Электроны Крюков находятся слоем выше.

@Hixie , ты проводил время, используя хуки, или ты сводишь их в таблицу, чтобы принять решение? Я не уверен, какой % времени команда Flutter тратит на написание фреймворка по сравнению с написанием приложений для клиентов.

Я сам не использовал хуки. Я пишу приложения с помощью Flutter. При этом я не обнаружил необходимости в чем-то вроде хуков. Однако, как отмечалось выше, я не думаю, что хуки соответствуют моему стилю разработки (я предпочитаю видеть шаблон). Я определенно не хотел бы удалять хуки от тех, кто хочет их использовать.

Я просто не вижу, что мы (команда Flutter) можем предложить более ценного.

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

Хуки позволяют проводить очень сильный статический анализ, который невозможен с помощью обычного StatefulWidget.
Это делает управление локальным состоянием виджета более безопасным/более масштабируемым. Для меня сокращение стандарта — это просто бонус.

Но:

  • _много работы_ над очень низкоуровневыми API.
  • интерфейс плагина анализатора нестабилен, отсутствует, почти не используется и не рекомендуется командой (поскольку для каждого плагина выполняется один анализ)
  • Сам флаттер не использует API плагина, а вместо этого реализует его непосредственно на сервере.

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

Я думаю, если мы следуем этой метафоре, вы должны рассматривать Флаттер как кварки или, может быть, адроны. Электроны Крюков находятся слоем выше.

Если флаттер — это кварки, то это еще и галактика.

У нас действительно есть очень низкоуровневые классы, такие как Layer .
Но также существует огромное количество высокоуровневых классов, таких как Switch .

Все библиотеки Материалов и Купертино можно было извлечь как пакеты, затем Навигатор и сформировать связанные объекты. Но это не так.
А хуки находятся на несколько слоев ниже.

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

Хуки на самом деле не столько уменьшают шаблоны. (Мы также не возражаем против шаблонного кода.) Основная ценность для меня заключается в возможности инкапсулировать и составлять логику с отслеживанием состояния.

const [value, setValue] = useState(0)
const debouncedValue = useDebounced(value, 1000)
const interpolatedValue = useSpring(debouncedValue, {
  friction: 10,
  mass: 20
})

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

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

Я согласен, что мы должны поддержать это. Однако это должна быть ошибка, зарегистрированная в проекте Dart GitHub. Я бы хотел, чтобы мы убрали линты, специфичные для Flutter, из кодовой базы Dart.

Все библиотеки Материалов и Купертино можно было извлечь как пакеты, затем Навигатор и сформировать связанные объекты. Но это не так.

Да, наверное, это тоже верно. Есть практические причины, по которым мы этого не делаем (например, это усложнит «Hello World», что затруднит начало использования Flutter), но, тем не менее, мы можем это рассмотреть. Опять же, это отдельный вопрос, который мы должны зарегистрировать отдельно, если мы хотим его рассмотреть.

Главная ценность для меня — это возможность инкапсулировать и составлять логику с отслеживанием состояния.

Это действительно очень ценно. Однако неясно, должен ли он быть в основной структуре.

Это действительно очень ценно. Однако неясно, должен ли он быть в основной структуре.

Это позволило бы отказаться от некоторых основных миксинов State.

Например, AutomaticKeepAliveClientMixin находится в странной позиции.
Мы должны вызвать super.build , что очень нелогично. И этот super.build возвращает null , который не будет работать с ненулевыми типами.
Но в качестве крючка оба эти вопроса решаются.

Точно так же нам не нужны и SingleTickerProviderStateMixin , и TickerProviderStateMixin , поскольку хуки можно использовать повторно столько раз, сколько нужно.

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

На самом деле мы бы не стали использовать хуки в основной структуре, поскольку, ИМХО, они делают вещи менее читабельными. Поэтому наличие их в основной структуре не поможет удалить другой код.

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

Если хуки хороши, то люди будут их использовать независимо от того, из проекта с открытым исходным кодом Flutter или, скажем, из проекта с открытым исходным кодом Hooks.

На самом деле мы бы не стали использовать хуки в основной структуре, поскольку, ИМХО, они делают вещи менее читабельными. Поэтому наличие их в основной структуре не поможет удалить другой код.

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

Если хуки хороши, то люди будут их использовать независимо от того, из проекта с открытым исходным кодом Flutter или, скажем, из проекта с открытым исходным кодом Hooks.

Звучит неплохая идея. Хуки могут работать как отдельный проект с открытым исходным кодом. Будет ли основная команда иметь приоритет в отношении основных требований к изменению проекта? ИМХО Это очень важно для успеха хукового проекта.

Что такое «требование к основным изменениям»?

Что такое «требование к основным изменениям»?

Инструменты для разработчиков.
В основном о подключаемых модулях анализатора, но также, возможно, о системе подключаемых модулей на flutter/devtools.

Там много работы, которая не может быть сделана сообществом.

На самом деле мы бы не стали использовать хуки в основной структуре, поскольку, ИМХО, они делают вещи менее читабельными. Поэтому наличие их в основной структуре не поможет удалить другой код.

При всем уважении, но это звучит немного невежественно. Вы действительно должны дать им попробовать! Они гораздо более лаконичны, чем пляски с initState/dispose/setState/build для каждого отдельного аспекта состояния.

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

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

Тем не менее, я не понимаю, почему это не могут сделать люди, не являющиеся сотрудниками Google. Flutter и Dart — это проекты с открытым исходным кодом, и многие вещи реализуются людьми, не являющимися сотрудниками Google.

Тем не менее, я не понимаю, почему это не могут сделать люди, не являющиеся сотрудниками Google. Flutter и Dart — это проекты с открытым исходным кодом, и многие вещи реализуются людьми, не являющимися сотрудниками Google.

Мы говорим о месяцах работы над несколькими проектами, над вещами, которые команда считает «функциями с низким приоритетом», со многими серьезными критическими изменениями. И затронутые проекты очень низкого уровня и почти без общедоступной документации.

Требуемые инвестиции и риски отказа / застревания на полпути слишком высоки. Тем более, что мы не получаем от этого никаких денег.

Так что, хотя я понимаю вашу точку зрения, это крайне маловероятно

Есть ли какая-то дорожная карта по этому поводу? Есть ли у вас какие-либо идеи, если и когда крючки могут приземляться в трепетании?

Если Flutter будет успешным (на что я надеюсь), через 1 год каждый разработчик React, пытающийся подключиться к Flutter, будет кричать, когда узнает, что здесь нет первоклассной поддержки хуков.

Команда Flutter приняла правильное решение, скопировав модель React на основе классов, но теперь это в прошлом, функциональные компоненты и хуки — это будущее, команда Flutter должна осознать эту новую реальность, если они хотят оставаться на высоте. .

Точно, как заявил cgarciae, сообщество React переходит к хуку и функциональному компоненту в будущем. И это заставляет флаттер выглядеть устаревшим, поскольку модель программирования и производительность не соответствуют другим решениям для кроссплатформенной разработки.

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

Я полностью понимаю, когда кто-то создает набор инструментов, низкоуровневую реализацию фреймворка, шаблонный код может не показаться большой проблемой. Но если разработчику приложения нужно написать много шаблонного «ненужного», чтобы заставить что-то работать, эффект будет нежелательным.

В зависимости от того, под каким углом люди смотрят на конкретную проблему, компромисс/баланс может выглядеть по-разному. Непредоставление надлежащего инструментария или определенной функции может сделать внутреннюю реализацию фреймворка более чистой, но это просто снимает ответственность с потребителя фреймворка, делает реализацию на стороне клиента более сложной и неуклюжей, чем она должна быть, что, в свою очередь, не помогает адаптации предоставления рамки. Для разработчика приложений производительность и согласованность имеют значение, унифицированный подход/стандарт важен для командной работы и сообщества. Целостный подход всегда должен учитывать людей, живущих по другую сторону баррикад.

Хуки должны быть просты в реализации, но функциональные виджеты сложнее. Для этого в Dart потребуются типы объединения, поскольку вам нужно выразить что-то вроде:

'Виджет | Функция виджета (BuildContext)».

функциональные виджеты сложнее. Для этого потребуются типы объединения в Dart

Это далеко не достаточно.
Есть еще вопрос:

  • как передать параметры в функции? Карри?
  • в дереве виджетов два функциональных виджета должны иметь разные runtimeType
  • как подключить виджеты с помощью devtools? (метод виджетов debugFillProperties )

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

На самом деле, часть генерации кода заставила нас отказаться от вашего пакета functions_widget. Это просто создает много прыжков при просмотре кода. Так что это не полетит для меня ...

Это просто создает много прыжков при просмотре кода.

Поэтому я упомянул плагин-анализатор.

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

  • проверьте, что функция не используется напрямую, и класс является предпочтительным
  • иметь правильный "перейти к определению"/"заглянуть"/..., чтобы щелчок по классу перенаправлял на функцию

Это должно исправить все проблемы.
Но мы возвращаемся к тому, что «хукам нужна правильная система подключаемых модулей анализатора».

Возможно, введение виджетов functional не обязательно:

Допустим, использование хуков разрешено только внутри метода StatelessWidget build .

С технической точки зрения - не имеет большого значения (насколько я могу сейчас думать), класс это или функция. Важная часть заключается в том, что вы не смешиваете классы с отслеживанием состояния и хуки. Под капотом флаттер-рендерер по-прежнему может считать хуки и их порядок, если они вызываются внутри метода build , и правильно им управлять.

В будущем, если будет возможно создание функциональных виджетов, это не нанесет серьезных изменений. Кроме того, он будет обратно совместим, так как не нарушит работу существующих приложений.

Я не вижу никаких очевидных проблем с использованием хуков внутри StatelessWidget , которые лишили бы преимуществ использования таких хуков, как композиция.

Однако я не уверен, как это будет сравниваться с такими случаями, как useCallback и т. д., реагировать по сравнению с возможностью использования методов экземпляра в любом случае.

Аргумент «мы должны добавить это, потому что разработчики React рассердятся» недействителен. (см. обсуждение 500+ комментариев JSX).

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

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

У меня, как у разработчика Android, была такая проблема: я пытаюсь разработать отличную внутреннюю структуру для своих приложений, не имея ни малейшего представления о том, что пользователи моего приложения будут испытывать, когда увидят мой уродливый пользовательский интерфейс и трудный для понимания UX.

Как уже было сказано ранее, крючок великолепен. неважно, кто его внедряет, злой FB или Apple.
это просто работает. это разделяет логику состояния и их жизненные циклы. мы можем сделать некоторые полезные другие хуки поверх других, публиковать независимые виджеты, API, инструменты, ... из состояний приложения, повторно использовать наши предварительно созданные в других проектах, ....

И я думаю, что если предварительно добавить строку в pubspec.yaml для импорта материала или купертино сложно создать простое приветственное приложение!!!, то здесь нечего обсуждать.
Но поскольку StatefullWidget очень важен для экосистемы флаттера, хуки и их актуальность имеют большее значение для будущего флаттера, его экосистемы и сообщества разработчиков флаттера.

Только представьте, как разработка приложений на флаттере станет проще, защищеннее от ошибок, легкой для понимания, расширяемой, читабельной, отлаживаемой, переносимой, ... (любой хороший тонкий и способный здесь, пожалуйста 😁) с хуками, напрямую поддерживаемыми ядром (или чем-то еще). посередине между ядром и некоторыми сложными виджетами, такими как материал, ...), особенно официальным.

Чтобы лучше понять мою точку зрения, посмотрите, какой ужасной была разработка под Android с помощью java и вспомогательных библиотек, и как прикольно и мило было с Kotlin.
Это также открывает двери для других хороших вещей, таких как Coroutines, Flow, поддержка AndroidX + Kotlin, новая система пользовательского интерфейса (Compose).
Даже это подтолкнуло java-сообщество к запуску и внедрению фьючерсов kotlin в свои эко.

Поэтому, пожалуйста, бросьте любой буфер в хуки, отреагируйте и просто сделайте это.

Отсутствие официальной поддержки хуков — единственная причина, по которой я не перешел с React Native.

Хуки настолько упрощают жизнь, что я больше не хочу писать по-старому.

Удивительная часть «родных хуков» заключается в том, что мы можем портировать многие внутренние StatefulWidget или ... в эту простую и понятную форму.
Кроме того, команда Flutter заявляет, что Flutter и Dart созданы для простоты использования, понимания и быстрого обучения. Все это может быть правдой и лучше с Native Hooks.
И путь (initState/dispose/setState/build) не находится на этом пути. (они могут понадобиться нам в бэкенде платформы, но не для новых разработчиков или даже дизайнеров, которые хотят использовать код только для описания своих идей (а не сложной логики))

Я читал в этой теме, что многие люди в восторге от использования хуков во Flutter, и есть несколько открытых вопросов относительно хуков:

  • Влияние/измерение производительности — в частности, эффективность постоянного вызова функций по сравнению с возможностью повторного использования состояния без вызова новой функции.
  • Умение (неумение?) красиво играть с глобальными клавишами и/или эффективно перемещаться по дереву.
  • Анализатор поддержки/линтов, специфичных для хуков.
  • Возможность работать с горячей перезагрузкой так же хорошо, как и с обычными старыми виджетами с состоянием/без состояния.
  • Потенциальная потребность в дополнительном API фреймворка.

Я уверен, что все эти вопросы можно решить, но решить их не так уж и просто. Проблема не столько в том, «должны ли хуки быть в фреймворке или должны жить в отдельном пакете». В какой-то степени это решение уже принято — flutter_hooks доступен на pub.dev уже больше года и является популярным пакетом. Дело в том, что для того, чтобы сделать flutter_hooks по-настоящему безупречным, потребуется значительная работа и инвестиции, помимо того, что уже сделано.

Большая часть этой работы уже была проделана для базовых классов фреймворка, и потребовались годы нескольких инженерных групп, а также нескольких участников с открытым исходным кодом, чтобы довести ее до того состояния, в котором она находится. Иногда кажется, что существует иллюзия «если мы просто объединим его с репозиторием X, все неразрешенные проблемы будут решены!» Но эти вещи происходят так, что люди, которые в восторге от них, выполняют работу по их внедрению. @rrousselGit уже проделал большую работу над этим, и похоже, что несколько других участников также сделали это в репозитории хуков.

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

Что ж, на мой взгляд, хуки должны быть языковой функцией, похожей на генераторы синхронизации / асинхронности .
Это вообще не должно быть связано с флаттером.

Проблемы, которые решают хуки:

  • декларативное и реактивное управление состоянием.
    State обязательно
  • госсостав для фиксации СУХОЙ.
    Все эти XXController должны быть созданы+обновлены+утилизированы, и нет никакого способа разложить эту логику на множители.
  • удобочитаемость, поскольку для достижения этого результата не требуется бесконечное вложение функций/виджетов

Но альтернативный синтаксис может быть:

class MyWidget extends HookWidget {
  const MyWidget({Key key, this.title}): super(key: key);

  final String title;

  Hook<Widget> build() hook* {
    final (flag, setFlag) = yield false;
    final (animationController) = yield* useAnimationController(duration: const Duration(seconds: 2));

    // TODO: do something with animationController
    return CheckBox(
      value: flag,
      onChanged: setFlag,
    );  
  }
}

Hook<AnimationController> useAnimationController({required Duration duration}) hook* {
  final (tickerProvider) = yield* useTickerProvider();
  final (animationController) = yield AnimationController(duration: duration, vsync: tickerProvider);
  animationController.duration = duration;

  return animationController;
}

Это дает тот же эффект, что и flutter_hook, но не зависит от флаттера и с несколькими факторами оптимизации, которые не может сделать flutter_hook.

И это в основном позволяет _всему_ использовать хуки, а не только виджеты.

Это интересная идея. Похоже, там есть запросы на несколько языковых функций (что-то вроде defer из Go, что-то вроде детерминированного деструктора из C++, некоторая возможность неявно компоновать функции в объекты) — но это не совсем то место. чтобы отслеживать это.

Я думаю, что было бы полезно определить болевые точки, с которыми сталкиваются люди, не имея чего-то вроде крючков во Flutter, и зарегистрировать проблемы конкретно по этой проблеме, не выбирая решение для начала. Однако вполне возможно, что некоторые из этих болевых точек могут быть просто вещами, которые инфраструктура навязывает для достижения целей, связанных с компромиссами между производительностью, качеством или удобством использования (например, мы могли бы улучшить болевые точки X в стоимость значительного ухудшения болевой точки Y).

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

Хуки @dnfield хорошо работают «как семья», только когда у вас есть все основные области требований.

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

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

Такие усилия не стоят того только из-за одной зацепки (болевой точки), поэтому я бы сказал, что решить эту проблему как набор более мелких проблем невозможно. Единственный хук не стоит того, чтобы переключаться с компонентов с состоянием и уж точно не стоит вносить существенные изменения в движок рендеринга.

Если эта проблема предназначена для того, чтобы сказать: «Я хочу, чтобы Flutter был больше похож на React, но также и на Flutter», я не уверен, насколько это действенно, и проблема GitHub, вероятно, в любом случае не лучший способ отслеживать такие вещи, даже если мы допустим такую ​​вещь имеет смысл отслеживать.

Другими словами: если это запрос на переписывание ядра Flutter, это неправильный подход к нему. Я не уверен, какой правильный подход к этому был бы, но, вероятно, это началось бы с простого разветвления репозитория и предпринятия огромных усилий.

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

Кажется, ответ часто звучит примерно так: «Если вы хотите, чтобы X был похож на React, используйте реакцию».

Я считаю, что хуки — это универсальное решение многих проблем с интерфейсом, когда вам нужно повторно использовать сложную логику данных. Это больше похоже на идею, которую можно использовать в любой технологии.

Хуки, однако, требуют некоторого переключения в представлении о потоке данных, прежде чем их значение станет полностью видимым. Я думаю, что это может быть большой проблемой при «запросе» хуков в других фреймворках.

Я думаю, что команда React проделала отличную работу, пытаясь объяснить хуки, но, возможно, это вызывает некоторое сопротивление, потому что хуки кажутся тесно связанными с React.

Я думаю, что React так часто упоминают следующие хуки просто потому, что именно здесь они были изобретены. React также является лучшим источником информации о хуках (пока что).

В общем, я бы описал «болевую точку» как невозможность использовать шаблон композиции для логики данных непосредственно во Flutter. Крючки — это просто пример того, как это можно сделать.

Я также понимаю, что введение хуков — это огромная задача, и я должен сказать, что не хочу браться за нее.

Я лично не понимаю аргумент «сообщество пыталось решить эту проблему, поэтому нам не нужно ничего делать».

Исправление сообщества не означает, что Google не может сделать его лучше.
Возьмите выражения внутри коллекций в качестве примера.
Сообщество может «исправить» это, разветвив Row/Column/ListView/... для поддержки null .
Но Google исправил это, изменив язык.

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

Хотя это не означает, что Flutter должен соответствовать React, я ожидаю, что команда Flutter, по крайней мере, будет внимательно следить за обновлениями React и его экосистемой.
По крайней мере, это должно дать лучший ответ на вопрос, почему «нам не нужны хуки в фреймворке», кроме аргумента сообщества.

@rrousselGit @pie6k

Лично я нахожу Hooks API в том виде, в каком он есть в React, довольно сложным для понимания. Я предпочитаю методы жизненного цикла и их описательные имена.

Я знаю, что это «проблема со мной», но я поддерживаю комментарий @dnfield , в котором говорится, что нам, возможно, следует решать проблемы и реализовывать решения по-другому.

Также я редко сталкивался с ситуацией, когда два отдельных виджета должны использовать одну и ту же логику (это то, что я понял как главное преимущество хуков?)

И насчет того, что Flutter вдохновлен React, это нормально, но это не означает, что он будет следовать всем шаблонам и архитектуре React навсегда.
Одна из основных проблем, с которыми я столкнулся при работе с React, заключалась в том, что существует множество разных способов что-то сделать, а «рекомендуемые практики» меняются ежедневно. Я понимаю, что такова природа разработки программного обеспечения, но я все еще думаю, что если есть что-то плохое в существующих способах, их следует попытаться улучшить… вместо того, чтобы накапливать разные способы для достижения одной и той же цели.

Я надеюсь, что это предлагает вам немного другую точку зрения и никого не оскорбляет.

Огромное уважение за ваш вклад в сообщество Flutter.

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

@cgarciae никто не невежествен, @dnfield ясно сказал - вместо того, чтобы пытаться втиснуть существующие решения, откройте задачи для соответствующих проблем, которые у вас есть, команда Flutter оценит их и, возможно, предложит решение, которое подходит Flutter еще лучше.

Это функция, о которой сообщество настоятельно просит и будет просить. Если проблема не в том месте, у вас, ребята, есть запрос на регистрацию требования от сообщества?

@SpajicM Я полностью согласен с ответом «нет».
С чем я не согласен, так это с текущим обоснованием.

Некоторые примечания:

  • Хуки — важная особенность React, отца Flutter.
  • эта проблема является одной из самых популярных на Flutter
  • flutter_hooks довольно популярен
  • Дэн Абрамов из команды React предложил обсудить с командой https://github.com/flutter/flutter/issues/25280#issuecomment -456404333

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

В конце концов, это сводится к проблеме общения.

  • Связалась ли команда Flutter с командой React, как предложил Дэн Абрамов?
  • Экспериментировала ли команда с хуком в React?
  • Рассматривалась ли альтернатива проблемам, которые решают крючки?

Мы, как сообщество, ничего об этом не знаем.

Сделав еще один шаг, я думаю, это иронично, что сообществу было предложено разделить этот вопрос на «какие проблемы решают хуки», когда сама команда React предложила объяснить команде Flutter, что это за проблемы.

@rrousselGit Я думаю, что идея заключается в том, чтобы услышать о проблемах со стороны, специфичной для Flutter, непосредственно от пользователей, и принять это в качестве отправной точки, а не принимать рассуждения React, это не значит, что их вклад не будет ценным.

Мое мнение, что было бы хорошо иметь способ четкого разделения между логикой виджета и дизайном , поэтому я согласен с тем, что есть проблема, которую нужно решить , но то, как это делает React, меня несколько сбивает с толку, и я хочу, чтобы будущее решение Flutter имело менее резкое изменение парадигмы/кривая обучения. У меня нет идеи, но я уверен, что сообщество вместе с командой могли бы что-то придумать, если бы они были непредубежденными и не зацикливались на существующей реализации хуков.

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

_"Flutter нужен лучший способ изолировать логику виджета"_

Я думаю, что https://svelte.dev/ имеет другой и лучший подход к решению этих проблем. В качестве первого шага команда Flutter должна осознать, что есть проблема и нам нужно решение.

Я новичок в флаттере, и я чувствую, что API имеет много шаблонов. Хотя у Dart есть общий язык, похоже, что я программирую на Go, где более распространено/проще выполнять копирование и вставку. Я надеюсь, что после выхода NNDB будет проведен масштабный рефакторинг, особенно с использованием метода расширения в полной мере. Возможно, стоит изучить постепенную абстракцию API с помощью чего-то вроде метода расширения.

ИМХО, команда Flutter слишком увлечена идеей «все есть виджет». Виджеты отлично подходят для визуальных элементов и макетов, но не для получения/обработки данных. Вместо громоздких FutureBuilder , StreamBuilder , TweenAnimationBuilder и т. д. я бы предпочел функциональный API:

Widget build(BuildContext context) {
    final a = useFuture(someFuture);
    final b = useStream(someStream);
    if (a.value == null || b.value == null) {
        return CircularProgressIndicator();
    }

    final value = a.value + b.value;
    final smoothedValue = animate(value, duration: Duration(milliseconds: 100), curve: Curves.easeInOut);

    return Slider(
        value: smoothedValue
    );
}

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

MediaQueryGetter {
    builder: (BuildContext context, MediaQueryData data) {
        ...
    }
}

ты можешь использовать
final data = MediaQuery.of(context);

К сожалению, этот механизм (подписка при получении) работает только в связке с InheritedWidget.

Я не уверен, насколько еще мы можем продвинуться в этом, но я хотел бы остановиться на нескольких моментах, которые я вижу здесь:

  1. Гораздо сложнее оценить _решение_, чем оценить _проблему_ в выпуске GitHub. Когда вы представляете решение, вам нужно достичь гораздо более высокой планки, чтобы получить признание. Вы должны показать, что решение хорошо сформировано, стоит усилий, которые оно влечет за собой, и что оно решает некоторый набор реальных проблем, соизмеримых с уровнем усилий, которые оно требует (включая не только начальное прототипирование и реализацию, но также текущее обслуживание и качество). . Другими словами, проблема, в которой мы обсуждаем конкретную болевую точку, связанную с управлением данными и состоянием виджетов, вероятно, приведет к ряду решений, некоторые из которых могут быть приняты во фреймворк, некоторые из них могут стать сторонними пакетами, а некоторые из которых было бы хорошо, но просто слишком дорого.

  2. Инженеры Google, работающие над Flutter и Dart, всегда стремятся сделать их лучше и, как правило, всегда заняты. То же самое относится и ко многим инженерам, не работающим в Google, которые вносят свой вклад во Flutter. Закрытие этой ошибки не означает, что ни один инженер Google никогда не будет работать над улучшением работы хуков с Flutter. Тот факт, что пакет hooks принадлежит члену сообщества, никоим образом не снижает качество пакета или его ценность, а также не уменьшает возможности устранения некоторых недостатков, выявленных в этой ошибке, например, улучшенной поддержки анализа/анализа.

  3. Есть разные причины принять или отклонить функцию. Иногда дело в том, что функция действительно крутая, но не совсем соответствует архитектуре. Иногда функция выглядит хорошо на первый взгляд, но имеет некоторые серьезные недостатки, которые она не устранила. Иногда функция великолепна, она требует достаточного количества усилий, но количество усилий, которые она стоит, перевешивает ценность, которую она дает пользователям, или отнимает время, необходимое для других, еще более ценных функций. В частности, для Flutter иногда бывает просто, что у нас есть многоуровневая архитектура, которая позволяет расширять сторонние пакеты, и во многих случаях лучше сохранить укладку и позволить третьим сторонам делать отличную работу, чем включать что-то новое во фреймворк. Мы делаем это сами в таких пакетах, как новый пакет анимаций, который находится по адресу https://github.com/flutter/packages/tree/master/packages/animations.

Наконец, я был на другом конце этого. Я сам занимаюсь сопровождением пакетов, и мне приходилось отклонять предложения по функциям, которые упростили бы поддержку или разработку моего пакета. Мой единственный совет: если у вас есть отличный пакет, который вам нравится, продолжайте над ним работать. Другие люди узнают его и тоже помогут, независимо от того, включен он в этот репозиторий или нет.

Новый ComposeUI для Android имеет хуки, похожие на состояния: (preview2)

val state = remember { CardDesignerState() } // react: let state = useState(CardDesignerState())
val thing = stateFor<T?> { null } // react: let thing = useState()

также у нового SwiftUI для iOS есть похожие вещи (но внутри):

// from https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions
Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                        .animation(.easeInOut)

представьте, что SwiftUI в iOS использует onCreate, onInit, onUpdate, onExit, ... 🤢

но лучший фреймворк (конечно, это Flutter) по-прежнему сопротивляется идее использования хуков, почему?
Потому что это похоже на React/ReactNative!!! или несколько замечательных цитат от команды сопровождающих:

Я предпочитаю видеть шаблон

очевидно против идеи флаттера писать пользовательский интерфейс с декларативным синтаксисом

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

Флаттер мог бы быть красивее:

// this is just scratch, not a complete and true use-case
build(context) {
    final angle = useAnimation(2*PI, 0, 5 /*seconds*/);
    return Image.from(myAwesomeImage)
        .padding(8)
        .scale(2.5)
        .rotate(angle);
}

@HKhademian полностью согласен. Отсутствие первоклассной поддержки чего-либо похожего на хуки (или, другими словами, всего, что позволяет правильно компоновать многоразовую бизнес-логику) на самом деле единственная причина, по которой я не выбрал Flutter для своего последнего проекта.

2. Тот факт, что пакет хуков принадлежит члену сообщества, никоим образом не снижает качество пакета или его ценность, а также не уменьшает возможности устранения некоторых недостатков, выявленных в этой ошибке, например, лучшей поддержки анализа. /линтинг

Есть ли у членов сообщества призрачные пакеты? да. Собирается ли Google создать призрачное ядро ​​Flutter? Что ж, судя по вашей позиции, это вполне возможно. Утверждение, что ядро ​​Flutter столь же жизнеспособно для потребления, как и пакеты сообщества, в лучшем случае неверно, а в худшем — ложь.

Кроме того, снова и снова было продемонстрировано, что, если @Hixie не поддержит инициативу, которая продемонстрировала склонность использовать личные предпочтения для преодоления запросов сообщества, не имеет значения, чего хочет или в чем нуждается сообщество. Без бай-ина этого не бывает. А если это не так, то процесс принятия решения слишком непрозрачен, чтобы увидеть обратное.

Что меня интересует, так это то, что люди, которые решают, будут ли функции жить или умрут, по-видимому, имеют очень мало опыта в бизнесе по созданию мобильных приложений. Эта точка зрения всегда будет давать наибольшую информацию о том, какой сахар смешивать с Flutter.

Я думаю, нам нужен формализованный процесс принятия решения о том, какие функции войдут во Flutter, что придает больше веса сообществу, чтобы оно могло внести свой вклад в процесс принятия решений. Очевидно, не на 100%, но сейчас кажется, что это где-то 5%, и притом супер шаткие 5%. @timsneath

Гораздо сложнее оценить решение, чем оценить проблему в выпуске GitHub.

Команда React предложила объяснить свои аргументы в пользу хуков в самом начале этого выпуска.
Это должно полностью покрыть необходимость объяснить, почему нужны хуки, поскольку никто не может объяснить хуки лучше, чем сама команда React.

Связанная проблема (мета) https://github.com/flutter/flutter/issues/28884

Люк, я уважаю и ценю твою страсть, но, пожалуйста, прекрати с нападками на лицо. Мы все работаем над созданием лучшей платформы, на которую способны, идем на всевозможные компромиссы в зависимости от ресурсов, исследований пользователей, вклада заинтересованных сторон, потребностей клиентов и вклада сообщества.

Будьте добры, пожалуйста. https://github.com/flutter/flutter/blob/master/CODE_OF_CONDUCT.md

Никаких нападок ad hominem, спасибо за отзыв. Согласно CoC, возможно, нам следует собраться вместе, чтобы обсудить то, что я пытаюсь сообщить.

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

Были ли какие-либо обновления по этому поводу или где мы можем согласовать решение? Я сталкиваюсь с этим в своих проектах Flutter, особенно с анимацией и переходами. Хуки помогают инкапсулировать это состояние initState и dispose , среди прочего, в одну функцию и просто использовать эту функцию вместо того, чтобы настраивать все подряд.

По крайней мере стороннему наблюдателю/пользователю Flutter, а не специалисту по поддержке плагина кажется, что были законные опасения, которые не чувствовали, что к ним прислушиваются, например, другая проблема с Flutter Overlays (#50961). ), и кажется, что они не представляют реальной проблемы для пользователей, а не для сопровождающих. Опять же, никакого неуважения к сопровождающим, это всего лишь мое мнение, как человека, случайно читающего эту тему, я не слишком знаком с какими-либо другими проблемами, которые могут отличаться.

С учетом сказанного, как мы можем действовать в более общем плане, думая о решениях проблем, которые, по-видимому, у пользователей возникают на законных основаниях? Я знаю, что в Rust есть интересная система RFC, которая, кажется, хорошо работает для разработки новых решений.

@satvikpendem Процесс RFC здесь заключается в том, чтобы зарегистрировать ошибку с описанием проблемы, а затем, когда проблема будет четко описана, обсудить возможные решения.

Кажется, что проблемы были объяснены до сих пор, поэтому теперь можно обсудить решения, исходя из вашего комментария. Что мы можем сделать дальше, есть ли другие решения, которые работают лучше, чем хуки или что-то подобное?

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

@hixie https://github.com/flutter/flutter/issues/51752

Я также думаю, что у нас должно быть продолжение комментария Дэна: https://github.com/flutter/flutter/issues/25280#issuecomment -456404333
Каков был результат этого обсуждения?

Привет, Реми, большое спасибо за ошибку № 51752. Я знаю, что вы потратили массу времени на это пространство и предоставили здесь очень ценные пакеты. Спасибо х1000 за это!

Повторяя комментарий @dnfield , кажется, что мы еще не пришли к единому мнению относительно проблемного пространства и его значимости. Вышеупомянутая ошибка является полезным шагом к этой цели и/или обсуждению возможных подходов к проблеме. Ваш дополнительный вопрос здесь предполагает, что разговор с командой React — это первый шаг отсюда, но если у нас еще нет согласования в проблемной области, это кажется преждевременным.

Возможно, мы сможем показать полные примеры приложений, в которых есть такие проблемы, которые можно исправить с помощью хуков. Мы не обязательно будем переписывать их с помощью хуков, а просто покажем, как много копипаста. Будет ли это предметом обсуждения, @timsneath? Или вы бы подумали о чем-то другом? Я пытаюсь понять, как показать проблемное пространство как можно яснее.

:волна: @timsneath
Честно говоря, я не совсем понимаю, почему обсуждение с командой React было бы преждевременным.

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

Обсуждение с ними может принести только пользу обеим сторонам.

Например:
После создания flutter_hooks со мной связался Дэн, чтобы обсудить, как я справился с горячей перезагрузкой для хуков.
Мой ответ был: «Было почти нечего делать, потому что Flutter использует типизированный язык».
Через несколько месяцев они улучшили горячую перезагрузку React, сгенерировав что-то похожее на типы с помощью Babel.

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

Некоторые открытые вопросы, которые можно задать без конкретной проблемы:

  • Почему крючки?
  • Почему приостановка/параллельный режим?
  • Почему порталы?

@satvikpendem Процесс RFC здесь заключается в том, чтобы зарегистрировать ошибку с описанием проблемы, а затем, когда проблема будет четко описана, обсудить возможные решения.

Означает ли это, что любые обсуждения, которые не являются ошибкой, игнорируются? Я думал, что целью RFC были вещи, которые не являются ошибками, но обычно для расширения более субъективных вещей, таких как опыт разработчиков или семантика инструментов.

Если кто-то говорит: «Я думаю, что у нас должны быть хуки, потому что они становятся идиоматичными в разных фреймворках, и люди сообщают об улучшении опыта и производительности», это допустимое обсуждение, но это не воспроизводимая ошибка.

Я абсолютно уверен, что никто не говорил, что «обсуждения, не являющиеся ошибками, будут игнорироваться». Давай, давай работать вместе добросовестно, относясь друг к другу с уважением и вежливостью. Мы все делаем все возможное, но за наше внимание борется множество других конкурирующих ошибок, дизайнов и идей :)

@lukepighetti , по иронии судьбы, «у нас должны быть хуки, потому что они есть в других фреймворках» — это именно тот разговор, которого мы на самом деле пытаемся избежать, потому что он приводит к дизайну, который по определению оптимизирован для нужд других фреймворков. В процессе очень полезно описать проблему, которую мы пытаемся решить, в контексте Flutter, поскольку это помогает нам всем прийти к согласию относительно того, является ли проблема одной и той же или нет, должно ли решение быть одинаковым и т. д.

@rrousselGit - конечно, есть несколько полезных общих разговоров, которые мы могли бы провести с командой React. Я приветствую это предложение, и, возможно, нам следует сделать это в какой-то момент. Конечно, у них много ума в этой команде, и их предложение очень любезное и доброе. Прямо сейчас мы будем говорить с ними в первую очередь потому, что вы сказали нам, а не потому, что у нас есть конкретные вопросы, которые мы достаточно информированы, чтобы обсудить :)

И еще, просто напоминание: «команда Flutter» — это просто люди, которые вносят свой вклад во Flutter. Некоторые участники работают в Google, а некоторые участники играют более важную роль в управлении архитектурой проекта, но вы также входите в команду Flutter, как и все, кто вносит свой вклад в проект :)

Как всегда — спасибо — за ваше терпение в обсуждении этого, за ваш вклад в сообщество, за то, что продолжаете продвигать проект вперед с творческими идеями в этом пространстве!

Чтобы уточнить, «ошибка», как мы используем это, является общим термином, который относится ко всему, что может привести к изменению. Это может быть вариант использования, может быть идея, может быть логическая ошибка, может быть опечатка в документации, что-то непонятное на веб-сайте, что угодно.

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

@satvikpendem Если проблема в том, что «в моем приложении слишком много шаблонов», то это, безусловно, ошибка, которую вы должны зарегистрировать, и мы можем обсудить, как ее исправить. Упомяните об ошибке # здесь, чтобы мы знали, что нужно продолжить обсуждение вашей ошибки.

Спасибо за комментарий @Hixie. Моя проблема в целом связана с той же ошибкой, о которой упоминал @rrousselGit (# 51752), поэтому я не думаю, что мне есть что добавить, основываясь на том, что я прочитал в этом выпуске.

@timsneath Я не уверен, что понимаю ваш комментарий к @lukepighetti , как будто мы описывали проблему в контексте Flutter несколько раз, например, в этой проблеме, № 51752 и других. Что еще нам нужно включить? Как мы можем помочь вам быть более информированными об этой проблемной области, чтобы, если мы поговорим с командой React или другими, у вас было бы достаточно знаний, чтобы задавать обоснованные вопросы, как вы говорите?

Я согласен с тем, что мы не должны копировать что-то из других фреймворков только потому, что они есть, например, в React, поэтому было бы полезно увидеть другие решения этой проблемы дублирования кода. Основатель Vue @yyx990803 опубликовал некоторые из своих мыслей в RFC Vue (https://www.github.com/vuejs/rfcs/tree/function-apis/active-rfcs%2F0000-function-api.md), которые могут быть полезны. пройти через. Полезно внимательно прочитать разделы о том, какие проблемы решаются и почему они не согласны с API на основе классов.

Спасибо за разъяснение @Hixie , я неправильно понял это более широкое (возможно, внутреннее?) определение «ошибки».

@timsneath Я не уверен, что следую. Другая группа людей, разработчики ядра React, уже определила и сообщила о наборе проблем, создала решение в архитектурно похожей среде, и многие команды переднего плана в нескольких средах сообщают об успехе. Я не вижу никаких признаков того, что это проблема GitHub «решение перед проблемой». Похоже, что @Hixie не согласен с тем, что есть проблема, которую необходимо решить, и похоже, что это основано на выборе стиля или обслуживания, который не отражает преимущества опыта разработчика. Я говорю это с величайшим уважением, пытаясь понять, откуда берется нежелание использовать этот RFC.

Обычно я не рекомендую повторять функции, но RFC для хуков не лишен предшествующего уровня техники с хорошим обоснованием. Предшествующий уровень техники доступен у основной группы реагирования, и любое обоснование, которое мы придумаем, будет повторять то, что они могут и сообщили. @rrousselGit , кажется, призывает вас, ребята, организовать встречу с ними для обсуждения этой темы, поскольку они могут дать гораздо больше информации, чем мы можем в GitHub.

Что касается мета-вопроса, лично для меня было бы полезно, если бы существовал конкретный процесс включения широких RFC в дорожную карту flutter/flutter , предложенную сообществом. У нас есть поговорка, что внешние заинтересованные стороны — это те, кто хочет сложной и необходимой работы, поэтому их нужно вовлекать и воспринимать всерьез. Внешним в контексте flutter/flutter будет не-команда, не-Google, не-GDE.

Я задокументировал наш процесс RFC на нашей вики.

Внешний в контексте флаттера/флаттера будет некомандным, негугловым, не-GDE.

По определению, если вы отправляете RFC, вы являетесь членом команды.

Моя проблема в целом связана с той же ошибкой, о которой упоминал @rrousselGit (# 51752)

В таком случае я рекомендую принять участие в этой дискуссии; этот конкретный вопрос закрыт, но тот открыт. В этом выпуске было несколько описаний предложений, но, похоже, ни одно из них не работает хорошо. Особо четкого описания "крючков" там пока не было, только упоминалось вскользь.

Я до сих пор не понимаю, почему мы должны объяснять проблему, когда и проблемы, и предлагаемые решения очень четко задокументированы React и Vue.

RFC хуков содержит около 1400 комментариев, ознакомительных видеороликов, документации и статей от многих умных людей.

Мы можем не согласиться с решением этих проблем. Но не должно быть необходимости объяснять проблемы

Проблема была объяснена в № 51752, не так ли? Разве это не проблема?

(Относительно того, почему: потому что указание команде разработчиков на RFC с 1400 комментариями для другого продукта не является эффективным способом общения с этой командой разработчиков. Извините, если это кажется тупым.)

Извините, что пингую мегатред. Просто хотел сказать всем, кто следил за этим, что я оставил еще несколько мыслей с точки зрения React в https://github.com/flutter/flutter/issues/51752#issuecomment -665380355 и был бы рад ответить больше вопросов, если это будет полезно.

Я также хотел выразить надежду, что эта (и связанные с ней темы) сможет оставаться вежливой и не будет давить на сопровождающих аргументами типа «это работает для React». React и Flutter имеют существенные различия в модели программирования, и React Hooks особенно сильно зависят от некоторых наших нюансов. В результате у них также есть некоторые причуды, которые многие могут не принять.

Я изменил свое мнение о крючках. Я думаю, что это фантастический шаблон, но я только что закончил контракт React Native, и хуки (на мой взгляд) ужасно фрагментировали систему разработки с очень небольшой выгодой. В настоящее время Flutter использует шаблон, очень похожий на компоненты класса React, и вокруг него построено множество инструментов. Если бы фреймворк переключился на хуки в качестве основного решения для управления состоянием, это разрушило бы всю существующую работу и мыслительные шаблоны, которые разработчики Flutter используют для очень небольшой выгоды.

Я думаю, что есть аргумент, что хуки — лучший шаблон для продуктивной разработки, но я думаю, что есть более убедительный аргумент (например, основной аргумент dartfmt), что согласованность во времени лучше, чем «лучше».

Я также должен отметить, что при работе с новыми разработчиками React мы часто сталкиваемся с блокпостами с хуками, создающими неожиданные результаты, и должны сначала научить их использовать компоненты класса. (Хорошим сравнением является то, что новым разработчикам легче использовать циклы for по сравнению с методами сбора, такими как map/reduce/filter/fold). Крючки — это продвинутый паттерн, и мы иногда принимаем это как должное. Разочаровывает то, что сообщество React быстро отказывается от документации и поддержки шаблонов компонентов класса, что затрудняет предоставление этого обучения или возможности для новых разработчиков.

В другом выпуске #51752 я упомянул, что, возможно, нам следует поработать над созданием версии хуков, более специфичной для Flutter, поскольку сами хуки, похоже, имеют некоторые недостатки, такие как шаблон useEffect(() => ..., [] ) для однократного рендеринга. @Hixie сделал интересную версию с шаблоном Property and PropertyManager, который, похоже, делает что-то похожее на хуки, но может не иметь этих недостатков. Нам следует больше искать альтернативы хукам, потому что, по крайней мере, для Flutter кажется, что есть что-то, что работает лучше, чем хуки в стиле React, но при этом решает те же проблемы.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги