Flutter: Reutilizar la lógica de estado es demasiado detallado o demasiado difícil

Creado en 2 mar. 2020  ·  420Comentarios  ·  Fuente: flutter/flutter

.

Relacionado con la discusión sobre los ganchos # 25280

TL; DR: Es difícil reutilizar la lógica State . Terminamos con un método build complejo y profundamente anidado o tenemos que copiar y pegar la lógica en varios widgets.

No es posible reutilizar dicha lógica a través de mixins ni funciones.

Problema

Reutilizar una lógica State en múltiples StatefulWidget es muy difícil, tan pronto como esa lógica se basa en múltiples ciclos de vida.

Un ejemplo típico sería la lógica de crear un TextEditingController (pero también AnimationController , animaciones implícitas y muchos más). Esa lógica consta de varios pasos:

  • definir una variable en State .
    dart TextEditingController controller;
  • creando el controlador (generalmente dentro de initState), con potencialmente un valor predeterminado:
    dart <strong i="25">@override</strong> void initState() { super.initState(); controller = TextEditingController(text: 'Hello world'); }
  • desecha el controlador cuando se desecha el State :
    dart <strong i="30">@override</strong> void dispose() { controller.dispose(); super.dispose(); }
  • haciendo lo que queramos con esa variable dentro de build .
  • (opcional) exponga esa propiedad en debugFillProperties :
    dart void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('controller', controller)); }

Esto, en sí mismo, no es complejo. El problema comienza cuando queremos escalar ese enfoque.
Una aplicación típica de Flutter puede tener docenas de campos de texto, lo que significa que esta lógica se duplica varias veces.

Copiar y pegar esta lógica en todas partes "funciona", pero crea una debilidad en nuestro código:

  • puede ser fácil olvidarse de reescribir uno de los pasos (como olvidar llamar a dispose )
  • agrega mucho ruido en el código

El problema de Mixin

El primer intento de factorizar esta lógica sería utilizar un mixin:

mixin TextEditingControllerMixin<T extends StatefulWidget> on State<T> {
  TextEditingController get textEditingController => _textEditingController;
  TextEditingController _textEditingController;

  <strong i="11">@override</strong>
  void initState() {
    super.initState();
    _textEditingController = TextEditingController();
  }

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

  <strong i="13">@override</strong>
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty('textEditingController', textEditingController));
  }
}

Luego se usa de esta manera:

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

class _ExampleState extends State<Example>
    with TextEditingControllerMixin<Example> {
  <strong i="18">@override</strong>
  Widget build(BuildContext context) {
    return TextField(
      controller: textEditingController,
    );
  }
}

Pero esto tiene diferentes defectos:

  • Un mixin se puede usar solo una vez por clase. Si nuestro StatefulWidget necesita varios TextEditingController , entonces ya no podemos usar el enfoque de mezcla.

  • El "estado" declarado por el mixin puede entrar en conflicto con otro mixin o con el State mismo.
    Más específicamente, si dos mixins declaran que un miembro usa el mismo nombre, habrá un conflicto.
    En el peor de los casos, si los miembros en conflicto tienen el mismo tipo, esto fallará silenciosamente.

Esto hace que los mixins no sean ideales y sean demasiado peligrosos para ser una verdadera solución.

Usando el patrón "constructor"

Otra solución puede ser utilizar el mismo patrón que StreamBuilder & co.

Podemos hacer un widget TextEditingControllerBuilder , que administra ese controlador. Entonces nuestro método build puede usarlo libremente.

Un widget de este tipo se implementaría normalmente de esta manera:

class TextEditingControllerBuilder extends StatefulWidget {
  const TextEditingControllerBuilder({Key key, this.builder}) : super(key: key);

  final Widget Function(BuildContext, TextEditingController) builder;

  <strong i="12">@override</strong>
  _TextEditingControllerBuilderState createState() =>
      _TextEditingControllerBuilderState();
}

class _TextEditingControllerBuilderState
    extends State<TextEditingControllerBuilder> {
  TextEditingController textEditingController;

  <strong i="13">@override</strong>
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(
        DiagnosticsProperty('textEditingController', textEditingController));
  }

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

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    return widget.builder(context, textEditingController);
  }
}

Luego se usa como tal:

class Example extends StatelessWidget {
  <strong i="19">@override</strong>
  Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller) {
        return TextField(
          controller: controller,
        );
      },
    );
  }
}

Esto resuelve los problemas encontrados con mixins. Pero crea otros problemas.

  • El uso es muy detallado. Eso es efectivamente 4 líneas de código + dos niveles de sangría para una sola declaración de variable.
    Esto es aún peor si queremos usarlo varias veces. Si bien podemos crear un TextEditingControllerBuilder dentro de otro una vez, esto reduce drásticamente la legibilidad del código:

    <strong i="28">@override</strong>
    Widget build(BuildContext context) {
    return TextEditingControllerBuilder(
      builder: (context, controller1) {
        return TextEditingControllerBuilder(
          builder: (context, controller2) {
            return Column(
              children: <Widget>[
                TextField(controller: controller1),
                TextField(controller: controller2),
              ],
            );
          },
        );
      },
    );
    }
    

    Ese es un código muy sangrado solo para declarar dos variables.

  • Esto agrega algo de sobrecarga ya que tenemos una instancia adicional de State y Element .

  • Es difícil usar TextEditingController fuera de build .
    Si queremos que State ciclos de vida realicen alguna operación en esos controladores, entonces necesitaremos GlobalKey para acceder a ellos. Por ejemplo:

    class Example extends StatefulWidget {
    <strong i="43">@override</strong>
    _ExampleState createState() => _ExampleState();
    }
    
    class _ExampleState extends State<Example> {
    final textEditingControllerKey =
        GlobalKey<_TextEditingControllerBuilderState>();
    
    <strong i="44">@override</strong>
    void didUpdateWidget(Example oldWidget) {
      super.didUpdateWidget(oldWidget);
    
      if (something) {
        textEditingControllerKey.currentState.textEditingController.clear();
      }
    }
    
    <strong i="45">@override</strong>
    Widget build(BuildContext context) {
      return TextEditingControllerBuilder(
        key: textEditingControllerKey,
        builder: (context, controller) {
          return TextField(controller: controller);
        },
      );
    }
    }
    
P5 crowd framework passed first triage proposal new feature

Comentario más útil

Agregaré algunos pensamientos desde la perspectiva de React.
Disculpe si no son relevantes, pero quería explicar brevemente cómo pensamos sobre Hooks.

Los ganchos definitivamente "esconden" cosas. O, dependiendo de cómo se mire, encapsúlelos. En particular, encapsulan el estado y los efectos locales (creo que nuestros "efectos" son lo mismo que los "desechables"). El "carácter implícito" es que adjuntan automáticamente la vida útil al componente dentro del cual se les llama.

Esta implícita no es inherente al modelo. Puede imaginar un argumento que se enhebra explícitamente a través de todas las llamadas, desde el propio Componente a través de Hooks personalizados, hasta cada Hook primitivo. Pero en la práctica, encontramos que es ruidoso y no es realmente útil. Así que hicimos el estado global implícito del Componente actualmente en ejecución. Esto es similar a cómo throw en una máquina virtual busca hacia arriba el bloque catch más cercano en lugar de pasar alrededor de errorHandlerFrame en el código.

Bien, entonces son funciones con un estado oculto implícito dentro de ellas, ¿eso parece malo? Pero en React, también lo son los componentes en general. Ese es el objetivo de Componentes. Son funciones que tienen una vida útil asociada (que corresponde a una posición en el árbol de la interfaz de usuario). La razón por la que los componentes en sí mismos no son un arma de fuego con respecto al estado es que no los llama simplemente desde un código aleatorio. Los llama desde otros componentes. Entonces, su duración tiene sentido porque permanece en el contexto del código de la interfaz de usuario.

Sin embargo, no todos los problemas tienen forma de componentes. Los componentes combinan dos habilidades: estado + efectos y una vida ligada a la posición del árbol. Pero hemos descubierto que la primera habilidad es útil por sí sola. Al igual que las funciones son útiles en general porque le permiten encapsular código, nos faltaba una primitiva que nos permitiera encapsular (y reutilizar) paquetes de estado + efectos sin crear necesariamente un nuevo nodo en el árbol. Eso es lo que son los Hooks. Componentes = Hooks + UI devuelta.

Como mencioné, una función arbitraria que oculta el estado contextual da miedo. Es por eso que aplicamos una convención a través de un linter. Los ganchos tienen "color" : si usa un gancho, su función también es un gancho. Y el linter hace cumplir que solo los Componentes u otros Hooks pueden usar Hooks. Esto elimina el problema de las funciones arbitrarias que ocultan el estado de la interfaz de usuario contextual porque ahora no son más implícitas que los propios componentes.

Conceptualmente, no vemos las llamadas de Hook como llamadas a funciones simples. Como useState() es más use State() si tuviéramos la sintaxis. Sería una característica del idioma. Puede modelar algo como Hooks con efectos algebraicos en idiomas que tienen seguimiento de efectos. Entonces, en ese sentido, serían funciones regulares, pero el hecho de que "usen" State sería parte de su firma de tipo. Entonces puede pensar en React como un "controlador" para este efecto. De todos modos, esto es muy teórico, pero quería señalar el estado de la técnica en términos del modelo de programación.

En términos prácticos, aquí hay algunas cosas. Primero, vale la pena señalar que los Hooks no son una API "adicional" para React. Son la API de React para escribir componentes en este momento. Creo que estaría de acuerdo en que, como característica adicional, no serían muy convincentes. Así que no sé si realmente tienen sentido para Flutter, que tiene un paradigma general posiblemente diferente.

En cuanto a lo que permiten, creo que la característica clave es la capacidad de encapsular estado + lógica efectiva, y luego encadenarlo como lo haría con la composición de funciones regular. Debido a que las primitivas están diseñadas para componer, puede tomar alguna salida de Hook como useState() , pasarla como entrada a un cusom useGesture(state) , luego pasarla como entrada a varios useSpring(gesture) llamadas que le dan valores escalonados, etc. Cada una de esas piezas desconoce por completo a las demás y pueden estar escritas por diferentes personas, pero se componen bien juntas porque el estado y los efectos se encapsulan y se "adjuntan" al Componente adjunto. Aquí hay una pequeña demostración de algo como esto y un artículo en el que recapitulo brevemente qué son los Hooks.

Quiero enfatizar que esto no se trata de reducir el texto estándar, sino de la capacidad de componer dinámicamente tuberías de lógica encapsulada con estado. Tenga en cuenta que es completamente reactivo, es decir, no se ejecuta una vez, pero reacciona a todos los cambios en las propiedades a lo largo del tiempo. Una forma de pensar en ellos es que son como complementos en una tubería de señal de audio. Si bien, en la práctica, percibo la desconfianza sobre las "funciones que tienen memoria", no hemos encontrado que eso sea un problema porque están completamente aisladas. De hecho, ese aislamiento es su característica principal. De lo contrario, se derrumbaría. Por lo tanto, cualquier codependencia debe expresarse explícitamente devolviendo y pasando valores al siguiente elemento de la cadena. Y el hecho de que cualquier Hook personalizado pueda agregar o eliminar estados o efectos sin romper (o incluso afectar) a sus consumidores es otra característica importante desde la perspectiva de la biblioteca de terceros.

No sé si esto fue útil en absoluto, pero espero que arroje algo de perspectiva sobre el modelo de programación.
Feliz de responder otras preguntas.

Todos 420 comentarios

cc @dnfield @Hixie
Como se solicitó, esos son los detalles completos sobre cuáles son los problemas resueltos por los ganchos.

Me preocupa que cualquier intento de hacer esto más fácil dentro del marco oculte la complejidad en la que los usuarios deberían estar pensando.

Parece que algo de esto podría mejorarse para los autores de bibliotecas si escribiéramos clases fuertemente que deben eliminarse con algún tipo de abstract class Disposable . En tal caso, debería poder escribir más fácilmente una clase más simple como esta si así lo deseara:

class AutomaticDisposingState<T> extends State<T> {
  List<Disposable> _disposables;

  void addDisposable(Disposable disposable) {
    assert(!_disposables.contains(disposable));
    _disposables.add(disposable);
  }

  <strong i="8">@override</strong>
  void dispose() {
    for (final Disposable disposable in _disposables)
      disposable.dispose();
    super.dispose();
  }
}

Lo que elimina algunas líneas repetidas de código. Podría escribir una clase abstracta similar para las propiedades de depuración, e incluso una que combine ambas. Su estado de inicio podría terminar pareciéndose a algo como:

<strong i="12">@override</strong>
void initState() {
  super.initState();
  controller = TextEditingController(text: 'Hello world');
  addDisposable(controller);
  addProperty('controller', controller);
}

¿Nos falta proporcionar tal información de mecanografía para las clases desechables?

Me preocupa que cualquier intento de hacer esto más fácil dentro del marco oculte la complejidad en la que los usuarios deberían estar pensando.

Los widgets ocultan la complejidad en la que los usuarios tienen que pensar.
No estoy seguro de que eso sea realmente un problema.

Al final, depende de los usuarios factorizarlo como quieran.


El problema no se trata solo de los desechables.

Esto olvida la parte de actualización del problema. La lógica de la etapa también podría depender de ciclos de vida como didChangeDependencies y didUpdateWidget.

Algunos ejemplos concretos:

  • SingleTickerProviderStateMixin que tiene lógica dentro de didChangeDependencies .
  • AutomaticKeepAliveClientMixin, que se basa en super.build(context)

Hay muchos ejemplos en el marco en los que queremos reutilizar la lógica de estado:

  • StreamBuilder
  • TweenAnimaciónConstructor
    ...

Estos no son más que una forma de reutilizar el estado con un mecanismo de actualización.

Pero sufren el mismo problema que los mencionados en la parte del "constructor".

Eso causa muchos problemas.
Por ejemplo, uno de los problemas más comunes en Stackoverflow son las personas que intentan usar StreamBuilder para efectos secundarios, como "empujar una ruta en el cambio".

Y, en última instancia, su única solución es "expulsar" StreamBuilder.
Esto involucra:

  • convertir el widget a estado
  • escuchar manualmente la transmisión en initState + didUpdateWidget + didChangeDependencies
  • cancelar la suscripción anterior en didChangeDependencies / didUpdateWidget cuando cambie la transmisión
  • cancelar la suscripción al disponer

Eso es _mucho trabajo_ y, de hecho, no es reutilizable.

Problema

Reutilizar una lógica State en múltiples StatefulWidget es muy difícil, tan pronto como esa lógica se basa en múltiples ciclos de vida.

Un ejemplo típico sería la lógica de crear un TextEditingController (pero también AnimationController , animaciones implícitas y muchos más). Esa lógica consta de varios pasos:

  • definir una variable en State .
    dart TextEditingController controller;
  • creando el controlador (generalmente dentro de initState), con potencialmente un valor predeterminado:
    dart <strong i="19">@override</strong> void initState() { super.initState(); controller = TextEditingController(text: 'Hello world'); }
  • eliminó el controlador cuando se eliminó el State :
    dart <strong i="24">@override</strong> void dispose() { controller.dispose(); super.dispose(); }
  • haciendo lo que queramos con esa variable dentro de build .
  • (opcional) exponga esa propiedad en debugFillProperties :
    dart void debugFillProperties(DiagnosticPropertiesBuilder properties) { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('controller', controller)); }

Esto, en sí mismo, no es complejo. El problema comienza cuando queremos escalar ese enfoque.
Una aplicación típica de Flutter puede tener docenas de campos de texto, lo que significa que esta lógica se duplica varias veces.

Copiar y pegar esta lógica en todas partes "funciona", pero crea una debilidad en nuestro código:

  • puede ser fácil olvidarse de reescribir uno de los pasos (como olvidar llamar a dispose )
  • agrega mucho ruido en el código

Realmente tengo problemas para entender por qué esto es un problema. He escrito muchas aplicaciones de Flutter, pero ¿realmente no parece un gran problema? Incluso en el peor de los casos, son cuatro líneas para declarar una propiedad, inicializarla, desecharla e informarla a los datos de depuración (y en realidad suele ser menos, porque normalmente puede declararla en la misma línea en la que la inicializa, las aplicaciones en general no necesita preocuparse por agregar estado a las propiedades de depuración, y muchos de estos objetos no tienen un estado que deba eliminarse).

Estoy de acuerdo en que una combinación por tipo de propiedad no funciona. Estoy de acuerdo en que el patrón del constructor no es bueno (literalmente usa el mismo número de líneas que el peor escenario descrito anteriormente).

Con NNBD (específicamente con late final para que los iniciadores puedan hacer referencia a this ) podremos hacer algo como esto:

typedef Initializer<T> = T Function();
typedef Disposer<T> = void Function(T value);

mixin StateHelper<T extends StatefulWidget> on State<T> {
  bool _active = false;
  List<Property<Object>> _properties = <Property<Object>>[];

  <strong i="8">@protected</strong>
  void registerProperty<T>(Property<T> property) {
    assert(T != Object);
    assert(T != dynamic);
    assert(!_properties.contains(property));
    _properties.add(property);
    if (_active)
      property._initState();
  }

  <strong i="9">@override</strong>
  void initState() {
    _active = true;
    super.initState();
    for (Property<Object> property in _properties)
      property._initState();
  }

  <strong i="10">@override</strong>
  void dispose() {
    for (Property<Object> property in _properties)
      property._dispose();
    super.dispose();
    _active = false;
  }

  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    for (Property<Object> property in _properties)
      property._debugFillProperties(properties);
  }
}

class Property<T> {
  Property(this.owner, this.initializer, this.disposer, [ this.debugName ]) {
    owner.registerProperty(this);
  }

  final StateHelper<StatefulWidget> owner;
  final Initializer<T> initializer;
  final Disposer<T> disposer;
  final String debugName;

  T value;

  void _initState() {
    if (initializer != null)
      value = initializer();
  }

  void _dispose() {
    if (disposer != null)
      disposer(value);
    value = null;
  }

  void _debugFillProperties(DiagnosticPropertiesBuilder properties) {
    properties.add(DiagnosticsProperty(debugName ?? '$T property', value));
  }
}

Lo usarías así:

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

  final String title;

  <strong i="14">@override</strong>
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage> {
  late final Property<int> _counter = Property<int>(this, null, null);
  late final Property<TextEditingController> _text = Property<TextEditingController>(this,
    () => TextEditingController(text: 'button'),
    (TextEditingController value) => value.dispose(),
  );

  void _incrementCounter() {
    setState(() {
      _counter.value += 1;
    });
  }

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the ${_text.value.text} this many times:',
            ),
            Text(
              '${_counter.value}',
              style: Theme.of(context).textTheme.headline4,
            ),
            TextField(
              controller: _text.value,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

Realmente no parece mejorar las cosas. Todavía son cuatro líneas.

Los widgets ocultan la complejidad en la que los usuarios tienen que pensar.

¿Qué esconden?

El problema no es el número de líneas, sino cuáles son estas líneas.

StreamBuilder puede tener tantas líneas como stream.listen + setState + subscription.close .
Pero escribir un StreamBuilder se puede hacer sin ninguna reflexión involucrada, por así decirlo.
No hay error posible en el proceso. Es simplemente "pasar la transmisión y crear widgets a partir de ella".

Mientras que escribir el código manualmente implica muchas más ideas:

  • ¿Puede la corriente cambiar con el tiempo? Si nos olvidamos de manejar eso, tenemos un error.
  • ¿Olvidamos cerrar la suscripción? Otro error
  • ¿Qué nombre de variable utilizo para la suscripción? Ese nombre puede no estar disponible
  • ¿Qué pasa con las pruebas? ¿Tengo que duplicar la prueba? Con StreamBuilder , no es necesario escribir pruebas unitarias para escuchar la transmisión, eso sería redundante. Pero si lo escribimos manualmente todo el tiempo, es completamente factible cometer un error.
  • Si escuchamos dos transmisiones a la vez, ahora tenemos múltiples variables con nombres muy similares que contaminan nuestro código, puede causar cierta confusión.

¿Qué esconden?

  • FutureBuilder / StreamBuilder oculta el mecanismo de escucha y realiza un seguimiento de cuál es la instantánea actual.
    La lógica de cambiar entre dos Future es bastante compleja, considerando que no tiene un subscription.close() .
  • AnimatedContainer oculta la lógica de hacer una interpolación entre los valores nuevos y anteriores.
  • Listview oculta la lógica de "montar un widget tal como aparece"

las aplicaciones generalmente no necesitan preocuparse por agregar estado a las propiedades de depuración

No lo hacen, porque no quieren lidiar con la complejidad de mantener el método debugFillProperties.
Pero si dijéramos a los desarrolladores "¿Le gustaría que todos sus parámetros y propiedades de estado estuvieran disponibles en la herramienta de desarrollo de Flutter?" Estoy seguro de que dirían que si

Mucha gente me ha expresado su deseo de un verdadero equivalente a la herramienta de desarrollo de React. La herramienta de desarrollo de Flutter aún no está allí.
En React, podemos ver todo el estado de un widget + sus parámetros, y editarlo, sin hacer nada.

De manera similar, la gente se sorprendió bastante cuando les dije que cuando usaban provider + algunos otros paquetes míos, por defecto el estado completo de su aplicación es visible para ellos, sin tener que hacer nada ( módulo este molesto error de devtool )

Tengo que admitir que no soy un gran admirador de FutureBuilder, causa muchos errores porque la gente no piensa en cuándo activar el Future. Creo que no sería descabellado que dejáramos de apoyarlo. StreamBuilder está bien, supongo, pero creo que las transmisiones en sí son demasiado complicadas (como mencionas en tu comentario anterior), así que ...

¿Por qué alguien tiene que pensar en la complejidad de crear preadolescentes?

ListView realmente no oculta la lógica de montar un widget tal como aparece; es una gran parte de la API.

El problema no es el número de líneas, sino cuáles son estas líneas.

Realmente no entiendo la preocupación aquí. Las líneas se parecen mucho a una simple repetición. Declare la cosa, inicialice la cosa, elimine la cosa. Si no es el número de líneas, ¿cuál es el problema?

Estoy de acuerdo contigo en que FutureBuilder es problemático.

Está un poco fuera de tema, pero sugeriría que en desarrollo, Flutter debería activar una recarga en caliente falsa cada pocos segundos. Esto destacaría el uso indebido de FutureBuilder, claves y muchos más.

¿Por qué alguien tiene que pensar en la complejidad de crear preadolescentes?

ListView realmente no oculta la lógica de montar un widget tal como aparece; es una gran parte de la API.

Estamos de acuerdo en eso. Mi punto fue que no podemos criticar algo como los ganchos con "oculta la lógica", ya que lo que hacen los ganchos es estrictamente equivalente a lo que hace un TweenAnimationBuilder / AnimatedContainer / ....
La lógica no se esconde

Al final, creo que las animaciones son una buena comparación. Las animaciones tienen este concepto de implícito vs explícito.
Las animaciones implícitas son amadas por su simplicidad, componibilidad y legibilidad.
Las animaciones explícitas son más flexibles, pero más complejas.

Cuando traducimos este concepto a escuchar transmisiones, StreamBuilder es una _ escucha implícita_, mientras que stream.listen es _explicit_.

Más específicamente, con StreamBuilder , _no_ puede_ olvidarse de manejar el escenario en el que cambia la transmisión u olvidarse de cerrar la suscripción.
También puede combinar varios StreamBuilder juntos

stream.listen es un poco más avanzado y más propenso a errores.

Los constructores son poderosos para simplificar la aplicación.
Pero como acordamos anteriormente, el patrón Builder no es ideal. Es tanto de escribir como de usar.
Este problema, y ​​lo que resuelven los ganchos, se trata de una sintaxis alternativa para * Builders

Por ejemplo, flutter_hooks tiene un equivalente estricto a FutureBuilder y StreamBuilder :

Widget build(context) {
  final AsyncSnapshot<T> snapshot = useStream(stream);
}

En la continuación, AnimatedContainer & iguales podrían estar representados por useAnimatedSize / useAnimatedDecoractedBox / ... de modo que tengamos:

double opacity;

Widget build(context) {
  final double animatedOpacity = useAnimatedDouble(opacity, duration: Duration(milliseconds: 200));
  return Opacity(
    opacity: animatedOpacity,
    child: ...,
  );
}

Mi punto fue que no podemos criticar algo como los ganchos con "esconde la lógica",

Ese no es el argumento. El argumento es "oculta la lógica en la que los desarrolladores deberían estar pensando ".

¿Tiene un ejemplo de esa lógica en la que los desarrolladores deberían estar pensando?

Por ejemplo, quién es el propietario del TextEditingController (quién lo crea, quién lo desecha).

¿Te gusta este código?

Widget build(context) {
  final controller = useTextEditingController();
  final focusNode = useFocusNode();
}

El anzuelo lo crea y lo desecha.

No estoy seguro de qué es lo que no está claro sobre esto.

Sí exactamente. No tengo idea de cuál es el ciclo de vida del controlador con ese código. ¿Dura hasta el final del ámbito léxico? ¿La vida del Estado? ¿Algo más? ¿A quién pertenece? Si se lo paso a otra persona, ¿pueden asumir la propiedad? Nada de esto es obvio en el código en sí.

Parece que su argumento se debe más a una falta de comprensión de lo que hacen los ganchos que a un problema real.
Estas preguntas tienen una respuesta claramente definida que es consistente con todos los ganchos:

No tengo idea de cuál es el ciclo de vida del controlador con ese código

Tampoco tienes que pensar en ello. Ya no es responsabilidad del desarrollador.

¿Dura hasta el final del ámbito léxico? La vida del Estado

La vida del Estado

¿A quién pertenece?

El gancho es dueño del controlador. Es parte de la API de useTextEditingController que posee el controlador.
Esto se aplica a useFocusNode , useScrollController , useAnimationController , ...

En cierto modo, estas preguntas se aplican a StreamBuilder :

  • No tenemos que pensar en los ciclos de vida de StreamSubscription
  • La suscripción tiene una duración de por vida del Estado.
  • StreamBuilder es propietario de StreamSubscription

En general, puedes pensar en:

final value = useX(argument);

como un estricto equivalente a:

XBuilder(
  argument: argument,
  builder: (context, value) {

  },
);

Tienen las mismas reglas y el mismo comportamiento.

Ya no es responsabilidad del desarrollador.

Creo que fundamentalmente ese es el desacuerdo aquí. Tener una API similar a una función que devuelve un valor que tiene un tiempo de vida definido que no está claro es, en mi humilde opinión, fundamentalmente muy diferente a una API basada en pasar ese valor a un cierre.

No tengo ningún problema con que alguien cree un paquete que use este estilo, pero es un estilo contrario al que me gustaría incluir en la API principal de flutter.

@Hixie
No creo que lo que @rrousselGit estaba diciendo fuera que son lo mismo, sino que tienen "las mismas reglas y el mismo comportamiento" con respecto al ciclo de vida. ¿Correcto?

Sin embargo, no resuelven los mismos problemas.

Tal vez me equivoque aquí, pero el otoño pasado, cuando probé flutter, creo que si hubiera necesitado tres de esos constructores en un widget, habría sido mucho anidamiento. Comparado con tres anzuelos (tres líneas).
También. Los ganchos son componibles, por lo que si necesita compartir la lógica de estado compuesta por múltiples ganchos, puede crear un nuevo gancho que use otros ganchos y algo de lógica adicional y solo use un nuevo gancho.

Cosas como compartir la lógica de estado fácilmente entre widgets era algo que me faltaba cuando probé Flutter Fall de 2019.

Por supuesto, podría haber muchas otras posibles soluciones. Tal vez ya se haya resuelto y simplemente no lo encontré en los documentos.
Pero si no, hay mucho que podría hacerse para acelerar el desarrollo en gran medida si algo como ganchos u otra solución para los mismos problemas estuviera disponible como ciudadano de primera clase.

Definitivamente no estoy sugiriendo usar el enfoque del constructor, como menciona el OP, que tiene todo tipo de problemas. Lo que sugeriría es usar initState / dispose. Realmente no entiendo por qué eso es un problema.

Tengo curiosidad por saber cómo se siente la gente sobre el código en https://github.com/flutter/flutter/issues/51752#issuecomment -664787791. No creo que sea mejor que initState / dispose, pero si a la gente le gustan los hooks, ¿les gusta eso también? ¿Son mejores los ganchos? ¿Peor?

@Hixie Hooks es agradable de usar porque compartimentan el ciclo de vida en una sola llamada de función. Si uso un gancho, digo useAnimationController , ya no tengo que pensar en initState y desecharlo. Elimina la responsabilidad del desarrollador. No tengo que preocuparme si eliminé cada controlador de animación que creé.

initState y dispose están bien para una sola cosa, pero imagina tener que realizar un seguimiento de múltiples y dispares tipos de estado. Los ganchos se componen en función de la unidad lógica de abstracción en lugar de distribuirlos en el ciclo de vida de la clase.

Creo que lo que estás preguntando es el equivalente a preguntar por qué tenemos funciones cuando podemos ocuparnos manualmente de los efectos en todo momento. Estoy de acuerdo en que no es exactamente lo mismo, pero en general se siente similar. Parece que no ha usado ganchos antes, por lo que los problemas no le parecen demasiado evidentes, por lo que le animo a que haga un proyecto pequeño o mediano usando ganchos, con el paquete flutter_hooks quizás, y vea como se siente. Lo digo con todo respeto, como usuario de Flutter me he encontrado con estos problemas a los que los ganchos brindan soluciones, al igual que otros. No estoy seguro de cómo convencerlo de que estos problemas realmente existen para nosotros, háganos saber si hay una mejor manera.

Agregaré algunos pensamientos desde la perspectiva de React.
Disculpe si no son relevantes, pero quería explicar brevemente cómo pensamos sobre Hooks.

Los ganchos definitivamente "esconden" cosas. O, dependiendo de cómo se mire, encapsúlelos. En particular, encapsulan el estado y los efectos locales (creo que nuestros "efectos" son lo mismo que los "desechables"). El "carácter implícito" es que adjuntan automáticamente la vida útil al componente dentro del cual se les llama.

Esta implícita no es inherente al modelo. Puede imaginar un argumento que se enhebra explícitamente a través de todas las llamadas, desde el propio Componente a través de Hooks personalizados, hasta cada Hook primitivo. Pero en la práctica, encontramos que es ruidoso y no es realmente útil. Así que hicimos el estado global implícito del Componente actualmente en ejecución. Esto es similar a cómo throw en una máquina virtual busca hacia arriba el bloque catch más cercano en lugar de pasar alrededor de errorHandlerFrame en el código.

Bien, entonces son funciones con un estado oculto implícito dentro de ellas, ¿eso parece malo? Pero en React, también lo son los componentes en general. Ese es el objetivo de Componentes. Son funciones que tienen una vida útil asociada (que corresponde a una posición en el árbol de la interfaz de usuario). La razón por la que los componentes en sí mismos no son un arma de fuego con respecto al estado es que no los llama simplemente desde un código aleatorio. Los llama desde otros componentes. Entonces, su duración tiene sentido porque permanece en el contexto del código de la interfaz de usuario.

Sin embargo, no todos los problemas tienen forma de componentes. Los componentes combinan dos habilidades: estado + efectos y una vida ligada a la posición del árbol. Pero hemos descubierto que la primera habilidad es útil por sí sola. Al igual que las funciones son útiles en general porque le permiten encapsular código, nos faltaba una primitiva que nos permitiera encapsular (y reutilizar) paquetes de estado + efectos sin crear necesariamente un nuevo nodo en el árbol. Eso es lo que son los Hooks. Componentes = Hooks + UI devuelta.

Como mencioné, una función arbitraria que oculta el estado contextual da miedo. Es por eso que aplicamos una convención a través de un linter. Los ganchos tienen "color" : si usa un gancho, su función también es un gancho. Y el linter hace cumplir que solo los Componentes u otros Hooks pueden usar Hooks. Esto elimina el problema de las funciones arbitrarias que ocultan el estado de la interfaz de usuario contextual porque ahora no son más implícitas que los propios componentes.

Conceptualmente, no vemos las llamadas de Hook como llamadas a funciones simples. Como useState() es más use State() si tuviéramos la sintaxis. Sería una característica del idioma. Puede modelar algo como Hooks con efectos algebraicos en idiomas que tienen seguimiento de efectos. Entonces, en ese sentido, serían funciones regulares, pero el hecho de que "usen" State sería parte de su firma de tipo. Entonces puede pensar en React como un "controlador" para este efecto. De todos modos, esto es muy teórico, pero quería señalar el estado de la técnica en términos del modelo de programación.

En términos prácticos, aquí hay algunas cosas. Primero, vale la pena señalar que los Hooks no son una API "adicional" para React. Son la API de React para escribir componentes en este momento. Creo que estaría de acuerdo en que, como característica adicional, no serían muy convincentes. Así que no sé si realmente tienen sentido para Flutter, que tiene un paradigma general posiblemente diferente.

En cuanto a lo que permiten, creo que la característica clave es la capacidad de encapsular estado + lógica efectiva, y luego encadenarlo como lo haría con la composición de funciones regular. Debido a que las primitivas están diseñadas para componer, puede tomar alguna salida de Hook como useState() , pasarla como entrada a un cusom useGesture(state) , luego pasarla como entrada a varios useSpring(gesture) llamadas que le dan valores escalonados, etc. Cada una de esas piezas desconoce por completo a las demás y pueden estar escritas por diferentes personas, pero se componen bien juntas porque el estado y los efectos se encapsulan y se "adjuntan" al Componente adjunto. Aquí hay una pequeña demostración de algo como esto y un artículo en el que recapitulo brevemente qué son los Hooks.

Quiero enfatizar que esto no se trata de reducir el texto estándar, sino de la capacidad de componer dinámicamente tuberías de lógica encapsulada con estado. Tenga en cuenta que es completamente reactivo, es decir, no se ejecuta una vez, pero reacciona a todos los cambios en las propiedades a lo largo del tiempo. Una forma de pensar en ellos es que son como complementos en una tubería de señal de audio. Si bien, en la práctica, percibo la desconfianza sobre las "funciones que tienen memoria", no hemos encontrado que eso sea un problema porque están completamente aisladas. De hecho, ese aislamiento es su característica principal. De lo contrario, se derrumbaría. Por lo tanto, cualquier codependencia debe expresarse explícitamente devolviendo y pasando valores al siguiente elemento de la cadena. Y el hecho de que cualquier Hook personalizado pueda agregar o eliminar estados o efectos sin romper (o incluso afectar) a sus consumidores es otra característica importante desde la perspectiva de la biblioteca de terceros.

No sé si esto fue útil en absoluto, pero espero que arroje algo de perspectiva sobre el modelo de programación.
Feliz de responder otras preguntas.

Definitivamente no estoy sugiriendo usar el enfoque del constructor, como menciona el OP, que tiene todo tipo de problemas. Lo que sugeriría es usar initState / dispose. Realmente no entiendo por qué eso es un problema.

Tengo curiosidad por saber cómo se siente la gente sobre el código en # 51752 (comentario) . No creo que sea mejor que initState / dispose, pero si a la gente le gustan los hooks, ¿les gusta eso también? ¿Son mejores los ganchos? ¿Peor?

La palabra clave late mejora las cosas, pero aún tiene algunos problemas:

Tales Property pueden ser útiles para estados que son autónomos o que no dependen de parámetros que pueden cambiar con el tiempo. Pero puede resultar difícil de usar cuando se encuentra en una situación diferente.
Más precisamente, carece de la parte de "actualización".

Por ejemplo, con StreamBuilder la transmisión escuchada puede cambiar con el tiempo. Pero no hay una solución fácil para implementar tal cosa aquí, ya que el objeto se inicializa solo una vez.

De manera similar, los ganchos tienen un equivalente al Key del widget, lo que puede hacer que una parte del estado se destruya y se vuelva a crear cuando esa clave cambia.

Un ejemplo de eso es useMemo , que es un gancho que almacena en caché una instancia de objeto.
Combinado con claves, podemos usar useMemo para obtener datos implícitos.
Por ejemplo, nuestro widget puede recibir un ID de mensaje, que luego usamos para buscar los detalles del mensaje. Pero ese ID de mensaje puede cambiar con el tiempo, por lo que es posible que debamos recuperar los detalles.

Con useMemo , esto puede verse así:

String messageId;

Widget build(context) {
  final Future<Message> message = useMemo(() => fetchMessage(messageId), [messageId]);

}

En esta situación, incluso si se vuelve a llamar al método de compilación 10 veces, siempre que messageId no cambie, la recuperación de datos no se vuelve a realizar.
Pero cuando los messageId nuevos cambios, un Future se crea.


Vale la pena señalar que no creo que flutter_hooks en su estado actual esté refinado para Dart. Mi implementación es más un POC que una arquitectura completa.
Pero creo que tenemos un problema con la reutilización del código de StatefulWidgets.

No recordaba dónde, pero recuerdo haber sugerido que los ganchos en el mundo ideal serían un generador de funciones personalizadas, junto a async* & sync* , que puede ser similar a lo que Dan sugiere con use State lugar de useState

@gaearon

Quiero enfatizar que no se trata de reducir el texto estándar, sino de la capacidad de componer dinámicamente tuberías de lógica encapsulada con estado.

Ese no es el problema que se está discutiendo aquí. Recomiendo presentar un error por separado para hablar sobre la incapacidad de hacer lo que describe. (Eso suena como un problema muy diferente y, honestamente, uno más convincente que el que se describe aquí). Este error trata específicamente sobre cómo parte de la lógica es demasiado detallada.

No, tiene razón, es mi redacción la que puede resultar confusa.
Como mencioné anteriormente, no se trata de la cantidad de líneas de código, sino de las líneas de código en sí.

Se trata de factorizar el estado.

Este error es extremadamente claro sobre el problema de que "Reutilizar la lógica de estado es demasiado detallado / difícil" y se trata de que hay demasiado código en un estado cuando tiene una propiedad que necesita tener código para declararlo, en initState, en dispose y en debugFillProperties. Si el problema que le preocupa es diferente, le recomiendo que presente un nuevo error que describa ese problema.

Realmente recomiendo encarecidamente que se olvide de los ganchos (o cualquier solución) hasta que comprenda completamente el problema que desea resolver. Solo al tener una comprensión clara del problema podrá articular un argumento convincente a favor de una nueva característica, porque debemos evaluar las características frente a los problemas que resuelven.

Creo que entonces estás malinterpretando lo que dije en ese número.

El problema no es un estándar, sino la reutilización.

El texto estándar es una consecuencia de un problema con la reutilización, no la causa

Lo que describe este problema es:

Es posible que deseemos reutilizar / componer la lógica de estado. Pero las opciones disponibles son mixins, Builders o no reutilizarlo, todos los cuales tienen sus propios problemas.

Los problemas de las opciones existentes pueden estar relacionados con el texto estándar, pero el problema que estamos tratando de resolver no lo es.
Si bien reducir el texto estándar de los constructores es un camino (que es lo que hacen los ganchos), puede haber uno diferente.

Por ejemplo, algo que quería sugerir por un tiempo era agregar métodos como:

context.onDidChangeDependencies(() {

});
context.onDispose(() {

});

Pero estos tienen sus propios problemas y no resuelven el problema por completo, así que no lo hice.

@rrousselGit , siéntase libre de editar la declaración del problema original en la parte superior aquí para reflejar mejor el problema. También siéntase libre de crear un documento de diseño: https://flutter.dev/docs/resources/design-docs que podemos iterar juntos (nuevamente, como sugiere @Hixie , enfocándose por ahora en la exposición más ajustada posible del enunciado del problema ). Me encantaría que te sintieras tan empoderado como cualquier otro ingeniero de Flutter; eres parte del equipo, ¡así que iteremos juntos!

He vuelto a examinar el problema varias veces. Honestamente, no entiendo de dónde viene el malentendido, así que no estoy seguro de qué mejorar.
El comentario original menciona repetidamente el deseo de reutilización / factorización. Las menciones sobre el texto estándar no son "Flutter es detallado" sino "Algunas lógicas no son reutilizables".

No creo que la sugerencia del documento de diseño sea justa. Se necesita una cantidad significativa de tiempo para escribir un documento de este tipo, y lo hago en mi tiempo libre.
Personalmente estoy satisfecho con los ganchos. No estoy escribiendo estos números en mi interés, sino para crear conciencia sobre un problema que afecta a un número significativo de personas.

Hace unas semanas, me contrataron para hablar de arquitectura sobre una aplicación Flutter existente. Probablemente fue exactamente lo que se menciona aquí:

  • Tienen cierta lógica que debe reutilizarse en múltiples widgets (manejar estados de carga / marcar "mensajes" como leídos cuando algunos widgets se vuelven visibles / ...)
  • Intentaron usar mixins, lo que causó importantes fallas en la arquitectura.
  • También intentaron manejar manualmente "crear / actualizar / eliminar" reescribiendo esa lógica en múltiples ubicaciones, pero causó errores.
    En algunos lugares, se olvidaron de cerrar las suscripciones. En otros, no manejaron el escenario donde su instancia stream cambia
  • marcar "mensajes" como leídos cuando algunos widgets se vuelven visibles

Ese es un caso interesante porque es similar a los problemas que he tenido en una de mis propias aplicaciones, así que miré cómo había implementado el código allí y realmente no veo muchos de los problemas que describe este error, que pueden por eso me cuesta entender el problema. Este es el código en cuestión:

https://github.com/jocosocial/rainbowmonkey/blob/master/lib/src/views/forums.dart

¿Tiene ejemplos de aplicaciones reales que podría estudiar para ver el problema en acción?

(Por cierto, en general, recomendaría encarecidamente no usar Streams en absoluto. Creo que generalmente empeoran las cosas).

(Por cierto, en general, recomendaría encarecidamente no usar Streams en absoluto. Creo que generalmente empeoran las cosas).

(Estoy totalmente de acuerdo. Pero la comunidad actualmente tiene la reacción opuesta. Quizás extraer ChangeNotifier / Listenable / ValueNotifier de Flutter en un paquete oficial ayudaría)

¿Tiene ejemplos de aplicaciones reales que podría estudiar para ver el problema en acción?

Tristemente no. Solo puedo compartir la experiencia que tuve mientras ayudaba a otros. No tengo una aplicación a mano.

Ese es un caso interesante porque es similar a los problemas que he tenido en una de mis propias aplicaciones, así que miré cómo había implementado el código allí y realmente no veo muchos de los problemas que describe este error, que pueden por eso me cuesta entender el problema. Este es el código en cuestión:

En su implementación, la lógica no está ligada a ningún ciclo de vida y se coloca dentro de _build_, por lo que soluciona el problema.
Puede tener sentido en ese caso específico. No estoy seguro de si ese ejemplo fue bueno.

Un mejor ejemplo puede ser extraer para actualizar.

En un típico pull-to-refresh, querremos:

  • en la primera compilación, maneja los estados de carga / error
  • al actualizar:

    • si la pantalla estaba en estado de error, muestre la pantalla de carga una vez más

    • si la actualización se realizó durante la carga, cancele las solicitudes HTTP pendientes

    • si la pantalla mostraba algunos datos:

    • seguir mostrando los datos mientras se carga el nuevo estado

    • si falla la actualización, siga mostrando los datos obtenidos anteriormente y muestre un snackbar con el error

    • si el usuario aparece y vuelve a entrar en la pantalla mientras la actualización está pendiente, muestra la pantalla de carga

    • asegúrese de que RefreshIndicator diga visible mientras la actualización está pendiente

Y queremos implementar dicha función para todos los recursos y múltiples pantallas. Además, algunas pantallas pueden querer actualizar varios recursos a la vez.

ChangeNotifier + provider + StatefulWidget tendrá bastantes dificultades para factorizar esta lógica.

Mientras que mis últimos experimentos (que se basan en la inmutabilidad y se basan en flutter_hooks ) admiten todo el espectro listo para usar:

final productsProvider = FutureProvider<List<Product>>.autoDispose((ref) async {
  final cancelToken = CancelToken();
  ref.onDispose(cancelToken.cancel);

  return await repository.fetchProducts(cancelToken: cancelToken);
});

// ...

Widget build(context) {
  // Listens to the Future created by productsProvider and handles all the refresh logic
  AsyncValue<List<Product>> products = useRefreshProvider(
    productsProvider,
    // TODO consider making a custom hook to encaplusate the snackbar logic
    onErrorAfterRefresh: (err, stack) => Scaffold.of(context).showSnackBar(...),
  );

  return RefreshIndicator(
    onRefresh: () => context.refresh(productsProvider),
    child: products.when(
      loading: () {
        return const SingleChildScrollView(
          physics: AlwaysScrollableScrollPhysics(),
          child: CircularProgressIndicator(),
        );
      },
      error: (err, stack) {
        return SingleChildScrollView(
          physics: const AlwaysScrollableScrollPhysics(),
          child: Text('Oops, something unexpected happened\n$err'),
        );
      },
      data: (products) {
        return ListView.builder(
          itemCount: products.length,
          itemBuilder: (context, index) {
            return ProductItem(products[index]);
          },
        );
      },
    ),
  );
}

Esta lógica es completamente autónoma. Se puede reutilizar con cualquier recurso dentro de cualquier pantalla.

Y si una pantalla quiere actualizar varios recursos a la vez, podemos hacer:

AsyncValue<First> first = useRefreshProvider(
  firstProvider,
  onErrorAfterRefresh: ...
);
AsyncValue<Second> second = useRefreshProvider(
  secondProvider,
  onErrorAfterRefresh: ...
);

return RefreshIndicator(
  onRefresh: () {
     return Future.wait([context.refesh(firstProvider), context.refresh(secondProvider)]);
  }
  ...
)

Recomendaría poner toda esa lógica en el estado de la aplicación, fuera de los widgets, y hacer que el estado de la aplicación refleje el estado actual de la aplicación. Tirar para actualizar no necesita ningún estado dentro del widget, solo tiene que decirle al estado ambiental que hay una actualización pendiente y luego esperar a que se complete su futuro.

No es responsabilidad del estado ambiental determinar cómo representar un error frente a carga frente a datos

Tener esta lógica en el estado ambiental no elimina todas las lógicas de la interfaz de usuario
La interfaz de usuario aún debe determinar si muestra el error en pantalla completa o en un snack-bar.
Todavía es necesario forzar la actualización de los errores cuando se vuelve a cargar la página.

Y esto es menos reutilizable.
Si la lógica de renderizado está completamente definida en los widgets en lugar del estado ambiental, entonces funcionará con _any_ Futuree incluso se puede incluir directamente dentro de Flutter.

Realmente no entiendo lo que está defendiendo en su último comentario. Mi punto es que no necesita cambios en el marco para hacer algo tan simple como el código indicador de actualización anterior, como lo demuestra el código que cité anteriormente.

Si tenemos muchos de estos tipos de interacciones, no solo para indicadores de actualización, sino también para animaciones y otros, es mejor encapsularlos donde estén más cerca de ser necesarios en lugar de ponerlos en el estado de la aplicación, porque el estado de la aplicación no necesita conocer los detalles de cada interacción en la aplicación si no es necesario en varios lugares de la aplicación.

No creo que estemos de acuerdo sobre la complejidad de la función y su reutilización.
¿Tiene un ejemplo que muestre que tal característica es fácil?

Me vinculé a la fuente de una aplicación que escribí arriba. Ciertamente no es un código perfecto, y planeo reescribir partes de él para la próxima versión, pero no experimenté los problemas que describe en este número.

Pero eres uno de los líderes tecnológicos de Flutter.
Incluso cuando se enfrente a un problema, tendrá la habilidad suficiente para encontrar una solución de inmediato.

Sin embargo, por otro lado, un número significativo de personas no comprende qué está mal con el siguiente código:

FutureBuilder<User>(
  future: fetchUser(),
  builder: ...,
)

Este hecho se demuestra por lo popular que es una Q / AI hecha en StackOverflow .

El problema no es que sea imposible abstraer la lógica del estado de una manera robusta y reutilizable (de lo contrario, no tiene sentido plantear este problema).
El problema es que se requiere tiempo y experiencia para hacerlo.

Al proporcionar una solución oficial, esto reduce la probabilidad de que una aplicación no se pueda mantener, lo que aumenta la productividad general y la experiencia del desarrollador.
No a todo el mundo se le ocurrió su sugerencia de propiedad . Si algo así se construyera dentro de Flutter, se documentaría, obtendría visibilidad y, en última instancia, ayudaría a personas que nunca habrían pensado en ello para empezar.

El problema es que realmente depende de cuál sea tu aplicación, cómo se ve tu estado, etc. Si la pregunta aquí es simplemente "cómo se administra el estado de la aplicación", entonces la respuesta no se parece en nada a los ganchos, hay mucha documentación que habla sobre diferentes formas de hacerlo y recomienda diferentes técnicas para diferentes situaciones ... básicamente, este conjunto de documentos: https://flutter.dev/docs/development/data-and-backend/state-mgmt

Hay un estado efímero y de aplicación, pero parece que también hay otro caso de uso: el estado que solo se refiere a un único tipo de widget pero que, sin embargo, desea compartir entre ese tipo de widget.

Por ejemplo, un ScrollController puede invocar algún tipo de animación, pero no es necesariamente apropiado ponerlo en el estado global de la aplicación, porque no son datos los que deben usarse en toda la aplicación. Sin embargo, varios ScrollController s pueden tener la misma lógica y desea compartir esa lógica de ciclo de vida entre cada uno de ellos. El estado sigue siendo por solo ScrollController s, por lo que no es un estado global de la aplicación, pero copiar y pegar la lógica es propenso a errores.

Además, es posible que desee empaquetar esta lógica para que sea más componible para sus proyectos futuros, pero también para otros. Si observa el sitio useHooks , verá muchas piezas de lógica que compartimentan ciertas acciones comunes. Si usa useAuth lo escribe una vez y nunca tendrá que preocuparse por si perdió una llamada initState o dispose , o si la función asíncrona tiene un then y catch . La función se escribe solo una vez, por lo que el margen de error básicamente desaparece. Por lo tanto, este tipo de solución no solo es más componible para múltiples partes de la misma aplicación y entre múltiples aplicaciones, sino que también es más segura para el programador final.

No tengo ninguna objeción a que la gente use ganchos. Por lo que puedo decir, nada lo impide. (Si algo _ está_ impidiendo eso, por favor presente un error al respecto).

Este error no se trata de ganchos, se trata de "Reutilizar la lógica de estado es demasiado detallado / difícil", y todavía estoy luchando por entender por qué esto requiere cambios en Flutter. Ha habido muchos ejemplos (incluidos los ganchos) que muestran cómo es posible evitar la verbosidad estructurando la aplicación de una forma u otra, y ya hay mucha documentación al respecto.

Ya veo, entonces estás preguntando por qué, si existe algo como un paquete de ganchos que ya se construyó sin cambios en Flutter, es necesario que haya una solución de primera mano para los ganchos. Supongo que @rrousselGit puede responder mejor a esto, pero la respuesta probablemente implica un mejor soporte, un soporte más integrado y más personas usándolos.

Puedo estar de acuerdo con usted en que además de eso, también estoy confundido por qué es necesario realizar cambios fundamentales en Flutter para admitir ganchos, ya que aparentemente el paquete flutter_hooks ya existe.

Todavía estoy luchando por entender por qué esto requiere cambios en Flutter.

Decir que este problema está resuelto porque la comunidad creó un paquete es como decir que Dart no necesita clases de datos + tipos de unión porque hice Freezed .
Freezed puede ser del agrado de la comunidad como una solución a estos dos problemas, pero aún podemos hacerlo mejor.

El equipo de Flutter tiene mucha más influencia que la que tendrá la comunidad. Tiene la capacidad de modificar toda la pila; personas que son expertas en cada parte individual; y un salario para patrocinar el trabajo necesario.

Este problema lo necesita.
Recuerde: uno de los objetivos del equipo de React es que los hooks formen parte del lenguaje, algo así como JSX.

Incluso sin soporte de idiomas, todavía necesitamos trabajar en el analizador; dardos flutter / devtools; y muchos ganchos para simplificar todas las diferentes cosas que hace Flutter (como animaciones implícitas, formas y más).

Ese es un buen argumento, estoy de acuerdo, a pesar de que la filosofía general de Flutter es tener un núcleo pequeño. Por esa razón, hemos estado agregando cada vez más nuevas funcionalidades como paquetes, incluso cuando se trata de personajes y animaciones de Google, cf. Eso nos da una mayor flexibilidad para aprender y cambiar con el tiempo. Haríamos lo mismo para este espacio, a menos que haya una razón técnica convincente por la que un paquete sea insuficiente (y con los métodos de extensión, eso es incluso menos probable que nunca).

Poner las cosas en el núcleo de Flutter es complicado. Un desafío es, como bien sabe por experiencia de primera mano, que el estado es un área que está evolucionando a medida que todos aprendemos más sobre lo que funciona bien en una arquitectura de interfaz de usuario reactiva. Hace dos años, si nos hubiéramos visto obligados a elegir un ganador, podríamos haber seleccionado BLoC, pero luego, por supuesto, su paquete de proveedor se hizo cargo y ahora es nuestra recomendación predeterminada.

Podría concebir cómodamente a colaboradores empleados por Google que respalden flutter_hooks o un paquete de ganchos similar que tuviera tracción (aunque obviamente tenemos muchos otros trabajos que compiten por nuestra atención ). En particular, deberíamos. Si está buscando que lo hagamos cargo de usted, obviamente esa es una pregunta diferente.

Interesante argumento, @timsneath. La comunidad de Rust también hace algo similar, porque una vez introducido en la biblioteca central o estándar de un lenguaje o marco, es muy difícil sacarlo. En el caso de Rust, es imposible ya que quieren mantener la compatibilidad con versiones anteriores para siempre. Por lo tanto, esperan hasta que los paquetes hayan llegado y compitan entre sí hasta que solo surjan unos pocos ganadores, luego lo incorporan al lenguaje.

Este podría ser un caso similar con Flutter. Puede que haya algo mejor que los ganchos más adelante, al igual que React tuvo que pasar de las clases a los ganchos, pero aún así tuvo que mantener las clases y la gente tuvo que migrar. Entonces, podría ser mejor tener soluciones de administración de estado competidoras antes de agregarse al núcleo. Y quizás nosotros, la comunidad, deberíamos innovar por encima de los ganchos o intentar encontrar soluciones aún mejores.

Entiendo esa preocupación, pero no se trata de una solución de gestión estatal.

Esta característica está más cerca de Inheritedwidget y StatefulWidget. Es una primitiva de bajo nivel, que podría ser tan baja como una característica del lenguaje.

Los Hooks pueden ser independientes del marco, pero eso es solo por suerte.
Como mencioné antes, otro camino a este problema puede ser:

context.onDispose(() {

});

Y oyentes de eventos similares.
Pero eso es imposible de implementar fuera del marco.

No sé qué se le ocurriría al equipo.
Pero no podemos excluir la posibilidad de que dicha solución tenga que estar directamente al lado de Element

¿Las extensiones ayudan con eso?

(Sin embargo, tal vez deberíamos hablar de eso en un tema diferente. Está un poco fuera de tema aquí. Realmente preferiría que tuviéramos un problema por problema que la gente está viendo, para que podamos discutir las soluciones en el lugar correcto. No es claro cómo context.onDispose ayudaría con la verbosidad).

Sospecho firmemente que hay algunas propuestas lingüísticas realmente buenas que podríamos hacer en relación con esto.

Creo que sería útil hablar sobre ellos de manera más específica que sobre cómo podrían habilitar un lenguaje específico de gestión estatal. Entonces podríamos considerar más seriamente qué permitirían y qué compensaciones podrían implicar.

En particular, podríamos considerar cómo y si podrían funcionar en los tiempos de ejecución de VM y JS.

No está claro cómo context.onDispose ayudaría con la verbosidad).

Como mencioné antes, este problema tiene más que ver con la reutilización del código que con la verbosidad. Pero si podemos reutilizar más código, esto debería reducir implícitamente la verbosidad.

La forma en que context.onDispose está relacionado con este problema es, con la sintaxis actual que tenemos:

AnimationController controller;

<strong i="11">@override</strong>
void initState() {
  controller = AnimationController(...);
}

<strong i="12">@override</strong>
void dispose() {
  controller.dispose();
}

El problema es:

  • esto está estrechamente acoplado a la definición de clase, por lo que no se puede reutilizar
  • a medida que el widget crece, la relación entre la inicialización y el desecho se vuelve más difícil de leer ya que hay cientos de líneas de código en el medio.

Con context.onDispose , podríamos hacer:

<strong i="21">@override</strong>
void initState() {
  controller = AnimationController(...);
  context.onDispose(controller.dispose);
}

La parte interesante es:

  • esto ya no está estrechamente acoplado con la definición de clase, por lo que se puede extraer en una función.
    Teóricamente podríamos tener una lógica semicompleja:
    dardo
    AnimationController someReusableLogic (contexto BuildContext) {
    controlador final = AnimationController (...);
    controller.onDispose (controller.dispose);
    controller.forward ();
    oyente vacío () {}
    controller.addListener (escucha);
    context.onDispose (() => controller.removeListener (oyente));
    }
    ...

@anular
void initState () {
controlador = someReusableLogic (contexto);
}
''

  • toda la lógica está agrupada. Incluso si el widget llega a tener una longitud de 300, la lógica de controller sigue siendo fácilmente legible.

El problema con este enfoque es:

  • context.myLifecycle(() {...}) no es recargable en caliente
  • No está claro cómo hacer que someReusableLogic lea las propiedades de StatefulWidget sin acoplar estrechamente la función a la definición del widget.
    Por ejemplo, los AnimationController de Duration pueden pasarse como un parámetro del widget. Por lo tanto, debemos manejar el escenario en el que cambia la duración.
  • no está claro cómo implementar una función que devuelve un objeto que puede cambiar con el tiempo, sin tener que recurrir a un ValueNotifier y tratar con los oyentes

    • Esto es especialmente importante para los estados calculados.


Pensaré en una propuesta de idioma. Tengo algunas ideas, pero nada de lo que valga la pena hablar en este momento.

Como mencioné antes, este problema tiene más que ver con la reutilización del código que con la verbosidad

Está bien. ¿Puede presentar un nuevo error que hable de eso específicamente? Este error se denomina literalmente "Reutilizar la lógica de estado es demasiado detallado / difícil". Si la verbosidad no es el problema, entonces _este_ no es el problema.

Con context.onDispose , podríamos hacer:

<strong i="11">@override</strong>
void initState() {
  controller = AnimationController(...);
  context.onDispose(controller.dispose);
}

No estoy seguro de por qué context es relevante en esto (y onDispose viola nuestras convenciones de nomenclatura). Sin embargo, si solo desea una forma de registrar las cosas que se ejecutarán durante la eliminación, puede hacerlo fácilmente hoy:

mixin StateHelper<T extends StatefulWidget> on State<T> {
  List<VoidCallback> _disposeQueue;

  void queueDispose(VoidCallback callback) {
    _disposeQueue ??= <VoidCallback>[];
    _disposeQueue.add(callback);
  }

  <strong i="17">@override</strong>
  void dispose() {
    if (_disposeQueue != null) {
      for (VoidCallback callback in _disposeQueue)
        callback();
    }
    super.dispose();
  }
}

Llámalo así:

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage> {
  TextEditingController controller;

  <strong i="21">@override</strong>
  void initState() {
    super.initState();
    controller = TextEditingController(text: 'button');
    queueDispose(controller.dispose);
  }

  ...
AnimationController someReusableLogic(BuildContext context) {
  final controller = AnimationController(...);
  controller.onDispose(controller.dispose);
  controller.forward();
  void listener() {}
  controller.addListener(listener);
  context.onDispose(() => controller.removeListener(listener));
}
...

<strong i="25">@override</strong>
void initState() {
  controller = someReusableLogic(context);
}

Usted puede hacer eso también:

AnimationController someReusableLogic<T extends StatefulWidget>(StateHelper<T> state) {
  final controller = AnimationController(...);
  state.queueDispose(controller.dispose);
  controller.forward();
  void listener() {}
  controller.addListener(listener);
  state.queueDispose(() => controller.removeListener(listener));
  return controller;
}
...

<strong i="6">@override</strong>
void initState() {
  controller = someReusableLogic(this);
}

El problema con este enfoque es:

  • context.myLifecycle(() {...}) no es recargable en caliente

En este contexto, no parece importar, ya que es solo para las cosas llamadas en initState. ¿Me estoy perdiendo de algo?

  • no está claro cómo hacer que someReusableLogic lea las propiedades de StatefulWidget sin acoplar estrechamente la función a la definición del widget.
    Por ejemplo, el AnimationController de Duration puede pasarse como un parámetro del widget. Por lo tanto, debemos manejar el escenario en el que cambia la duración.

Es bastante simple agregar una cola didChangeWidget al igual que la cola dispose:

mixin StateHelper<T extends StatefulWidget> on State<T> {
  List<VoidCallback> _disposeQueue;
  List<VoidCallback> _didUpdateWidgetQueue;

  void queueDispose(VoidCallback callback) {
    _disposeQueue ??= <VoidCallback>[];
    _disposeQueue.add(callback);
  }

  void queueDidUpdateWidget(VoidCallback callback) {
    _didUpdateWidgetQueue ??= <VoidCallback>[];
    _didUpdateWidgetQueue.add(callback);
  }

  <strong i="24">@override</strong>
  void didUpdateWidget(T oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (_didUpdateWidgetQueue != null) {
      for (VoidCallback callback in _didUpdateWidgetQueue)
        callback();
    }
  }

  <strong i="25">@override</strong>
  void dispose() {
    if (_disposeQueue != null) {
      for (VoidCallback callback in _disposeQueue)
        callback();
    }
    super.dispose();
  }
}

AnimationController conditionalAnimator(StateHelper state, ValueGetter<bool> isAnimating, VoidCallback listener) {
  final controller = AnimationController(vsync: state as TickerProvider, duration: const Duration(seconds: 1));
  state.queueDispose(controller.dispose);
  controller.addListener(listener);
  state.queueDispose(() => controller.removeListener(listener));
  if (isAnimating())
    controller.repeat();
  state.queueDidUpdateWidget(() {
    if (isAnimating()) {
      controller.repeat();
    } else {
      controller.stop();
    }
  });
  return controller;
}

Usado así:

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  <strong i="6">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(animating: false),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.animating}) : super(key: key);

  final bool animating;

  <strong i="7">@override</strong>
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> with StateHelper<MyHomePage>, SingleTickerProviderStateMixin {
  AnimationController controller;

  <strong i="8">@override</strong>
  void initState() {
    super.initState();
    controller = conditionalAnimator(this, () => widget.animating, () { print(controller.value); });
  }

  <strong i="9">@override</strong>
  Widget build(BuildContext context) {
    return Center(
      child: FadeTransition(
        opacity: controller,
        child: Text('Hello', style: TextStyle(fontSize: 100.0, color: Colors.white)),
      ),
    );
  }
}
  • no está claro cómo implementar una función que devuelve un objeto que puede cambiar con el tiempo, sin tener que recurrir a un ValueNotifier y tratar con los oyentes

    • Esto es especialmente importante para los estados calculados.

No estoy seguro de lo que esto significa aquí, ¿qué pasa con ValueNotifier y, digamos, ValueListenableBuilder?

Como mencioné antes, este problema tiene más que ver con la reutilización del código que con la verbosidad

Está bien. ¿Puede presentar un nuevo error que hable de eso específicamente? Este error se denomina literalmente "Reutilizar la lógica de estado es demasiado detallado / difícil". Si la verbosidad no es el problema, entonces este no es el problema.

Estoy empezando a sentirme bastante incómodo con esta discusión. Ya he respondido a este punto antes:
El tema de este problema es la reutilización, y la verbosidad se discute como consecuencia de un problema de reutilización; no como el tema principal.

Solo hay una viñeta en el comentario superior que menciona la verbosidad, y eso es con StreamBuilder, dirigido principalmente a los 2 niveles de sangría.

No estoy seguro de por qué el contexto es relevante en esto [...]. Sin embargo, si solo desea una forma de registrar las cosas que se ejecutarán durante la eliminación, puede hacerlo fácilmente hoy:

Cuando mencioné context.onDispose , mencioné explícitamente que no creo que sea una buena solución.
Lo expliqué porque preguntaste cómo se relaciona con la discusión.

En cuanto a por qué context lugar de StateHelper , es porque es más flexible (como trabajar con StatelessWidget)

context.myLifecycle (() {...}) no es recargable en caliente

En este contexto, no parece importar, ya que es solo para las cosas llamadas en initState. ¿Me estoy perdiendo de algo?

Podemos cambiar:

initState() {
  context.myLifecycle(() => print('hello'));
}

dentro:

initState() {
  context.myLifecycle(() => print('world'));
}

Esto no aplicará los cambios a la devolución myLifecycle llamada

Pero si usamos:

myLifecycle() {
  super.myLifecycle();
  print('hello');
}

entonces funcionaría la recarga en caliente.

No estoy seguro de lo que esto significa aquí, ¿qué pasa con ValueNotifier y, digamos, ValueListenableBuilder?

Esta sintaxis fue diseñada para evitar tener que usar Builders, por lo que volvimos al problema original.

Además, si realmente queremos que nuestra función sea componible, en lugar de su sugerencia ValueGetter + queueDidUpdateWidget , las funciones tendrán que tomar un ValueNotifier como parámetro:

AnimationController conditionalAnimator(StateHelper state, ValueListenable<bool> isAnimating, VoidCallback listener) {
...
}

ya que es posible que deseemos obtener isAnimating de algún lugar que no sea didUpdateWidget dependiendo de qué widget esté usando esta función.
En un lugar, puede ser didUpdateWidget; en otro puede ser didChangeDependencies; y en otro lugar puede estar dentro de la devolución de llamada de un stream.listen .

Pero luego necesitamos una forma de convertir estos escenarios en un ValueNotifier fácilmente y hacer que nuestra función escuche dicho notificador.
Así que estamos haciendo nuestra vida mucho más difícil.
Creo que es más confiable y más fácil usar un ConditionalAnimatorBuilder que este patrón.

En cuanto a por qué context lugar de StateHelper , es porque es más flexible (como trabajar con StatelessWidget)

StatelessWidget es para, bueno, widgets sin estado. El punto es que no crearían estados, eliminarían cosas, reaccionarían en didUpdateWidget, etc.

Re la cosa de recarga en caliente, sí. Es por eso que usamos métodos en lugar de poner cierres en initState.

Lo siento, sigo diciendo esto, y entiendo que debe ser frustrante, pero todavía no entiendo cuál es el problema que estamos tratando de resolver aquí. Pensé que era verbosidad, según el resumen del error original y una gran parte de la descripción original, pero entiendo que no es así. Entonces, ¿cuál es el problema? Parece que hay muchos deseos mutuamente excluyentes aquí, repartidos entre los muchos comentarios de este error:

  • Declarar cómo desechar algo debe hacerse en el mismo lugar que lo asigna ...
  • ... y el lugar que lo asigna debe ejecutarse solo una vez, ya que lo está asignando ...
  • ... y necesita funcionar con recarga en caliente (que por definición no vuelve a ejecutar el código que solo se ejecuta una vez) ...
  • ... y necesita poder crear un estado que funcione con widgets sin estado (que, por definición, no tienen estado) ...
  • ... y necesita habilitar el enganche en cosas como didUpdateWidget y didChangeDependencies ...

Este baile iterativo en el que estamos involucrados aquí no es una forma productiva de hacer las cosas. Como intenté decir antes, la mejor manera de obtener algo aquí es describir el problema al que se enfrenta de una manera que podamos entender, con todas las necesidades descritas en un solo lugar y explicadas con casos de uso. Recomiendo no enumerar las soluciones, especialmente las soluciones que sepa que no satisfacen sus necesidades. Solo asegúrese de que la necesidad que hace que esas soluciones sean inapropiadas se enumere en la descripción.

Para ser honesto, fundamentalmente me parece que estás pidiendo un diseño de marco completamente diferente. Eso está perfectamente bien, pero no es Flutter. Si tuviéramos que hacer un marco diferente, sería, bueno, un marco diferente, y todavía tenemos mucho trabajo por hacer en este marco. En realidad, mucho de lo que describe es muy similar a cómo está diseñado Jetpack Compose. No soy un gran admirador de ese diseño porque requiere la magia del compilador, por lo que depurar lo que está sucediendo es realmente difícil, pero ¿tal vez sea más conveniente para ti?

Parece que hay muchos deseos mutuamente excluyentes aquí, repartidos entre los muchos comentarios de este error:

No son mutuamente exclusivos. Los Hooks hacen todos y cada uno de estos. No entraré en detalles ya que no queremos centrarnos en soluciones, pero ellos marcan todas las casillas.

Como intenté decir antes, la mejor manera de obtener algo aquí es describir el problema al que se enfrenta de una manera que podamos entender, con todas las necesidades descritas en un solo lugar y explicadas con casos de uso.

Sigo sin entender cómo ese comentario principal no logra hacer eso.
No me queda claro lo que no está claro para los demás.

En realidad, mucho de lo que describe es muy similar a cómo está diseñado Jetpack Compose. No soy un gran admirador de ese diseño porque requiere la magia del compilador, por lo que depurar lo que está sucediendo es realmente difícil, pero ¿tal vez sea más conveniente para ti?

No estoy familiarizado con él, pero con una búsqueda rápida, diría que sí.

No son mutuamente exclusivos.

¿Son todas las viñetas que enumeré arriba parte del problema que estamos tratando de resolver aquí?

pero marcan todas las casillas

¿Puedes enumerar las casillas?

Sigo sin entender cómo ese comentario principal no logra hacer eso.

Por ejemplo, el OP dice explícitamente que el problema es sobre StatefulWidgets, pero uno de los comentarios recientes sobre este tema dijo que una sugerencia particular no era buena porque no funcionaba con StatelessWidgets.

En el OP dices:

Es difícil reutilizar la lógica State . Terminamos con un método build complejo y profundamente anidado o tenemos que copiar y pegar la lógica en varios widgets.

Entonces, a partir de esto, asumo que los requisitos incluyen:

  • La solución no debe estar profundamente anidada.
  • La solución no debe requerir muchos códigos similares en lugares que intentan agregar un estado.

El primer punto (sobre el anidamiento) parece estar bien. Definitivamente no estoy tratando de sugerir que deberíamos hacer cosas que están profundamente anidadas. (Dicho esto, podemos estar en desacuerdo sobre lo que está profundamente anidado; eso no se define aquí. Otros comentarios posteriores implican que los constructores causan un código profundamente anidado, pero en mi experiencia, los constructores son bastante buenos, como se muestra en el código que cité anteriormente).

El segundo punto parece ser directamente un requisito de que no tenemos verbosidad. Pero luego ha explicado varias veces que no se trata de verbosidad.

La siguiente declaración que hace el OP que describe un problema es:

Reutilizar una lógica State en múltiples StatefulWidget es muy difícil, tan pronto como esa lógica se basa en múltiples ciclos de vida.

Honestamente, no sé realmente qué significa esto. "Difícil" para mí generalmente significa que algo involucra mucha lógica complicada que es difícil de entender, pero asignar, eliminar y reaccionar a los eventos del ciclo de vida es muy simple. La siguiente declaración que da un problema (aquí estoy omitiendo el ejemplo que se describe explícitamente como "no complejo" y, por lo tanto, presumiblemente no es una descripción del problema) es:

El problema comienza cuando queremos escalar ese enfoque.

Esto me sugirió que por "muy difícil" querías decir "muy detallado" y que la dificultad provenía de que había muchas ocurrencias de código similar, ya que la única diferencia entre el ejemplo "no complejo" que das y el "muy difícil "El resultado de escalar el ejemplo es literalmente que el mismo código ocurre muchas veces (es decir, verbosidad, código repetitivo).

Esto está respaldado por la siguiente declaración que describe un problema:

Copiar y pegar esta lógica en todas partes "funciona", pero crea una debilidad en nuestro código:

  • puede ser fácil olvidarse de reescribir uno de los pasos (como olvidar llamar a dispose )

Entonces, presumiblemente, es muy difícil porque la verbosidad hace que sea fácil cometer un error al copiar y pegar el código. Pero de nuevo, cuando traté de abordar este problema, que describiría como "verbosidad", dijiste que el problema no es verbosidad.

  • agrega mucho ruido en el código

Una vez más, esto me suena a decir verbosidad / repetitivo, pero de nuevo me has explicado que no es eso.

El resto del OP solo describe soluciones que no le gustan, por lo que presumiblemente no describe el problema.

¿Explica esto cómo el OP no explica el problema? Todo en el OP que realmente describe un problema parece estar describiendo verbosidad, pero cada vez que sugiero que ese es el problema, dices que no lo es y que hay otro problema.

Creo que el malentendido se reduce al significado de la palabra.
Por ejemplo:

agrega mucho ruido en el código

Una vez más, esto me suena a decir verbosidad / repetitivo, pero de nuevo me has explicado que no es eso.

Este punto no se trata de la cantidad de controller.dispose() , sino del valor que estas líneas de código aportan al lector.
Esa línea siempre debe estar ahí y siempre es la misma. Como tal, su valor para el lector es casi nulo.

Lo que importa no es la presencia de esta línea, sino su ausencia.

El problema es que cuanto más de esos controller.dispose() tengamos, es más probable que pasemos por alto un problema real en nuestro método de eliminación.
Si tenemos 1 controlador y 0 desechamos, es fácil de atrapar
Si tenemos 100 controladores y 99 desechamos, encontrar el que falta es difícil.

Entonces tenemos:

Entonces, presumiblemente, es muy difícil porque la verbosidad hace que sea fácil cometer un error al copiar y pegar el código. Pero de nuevo, cuando traté de abordar este problema, que describiría como "verbosidad", dijiste que el problema no es verbosidad.

Como mencioné en el punto anterior, no todas las líneas de códigos son iguales.

Si comparamos:

+ T state;

<strong i="24">@override</strong>
void initState() {
  super.initState();
+  state = widget.valueNotifier.value;
+  widget.valueNotifier.addListener(_listener);
}

+ void _listener() => seState(() => state = widget.valueNotifier.value);

void dispose() {
+ widget.valueNotifier.removeListener(_listener);
  super.dispose();
}

vs:

+ ValueListenableBuilder<T>(
+   valueListenable: widget.valueNotifier,  
+   builder: (context, value, child) {

+    },
+ );

entonces, ambos fragmentos tienen el mismo número de líneas y hacen lo mismo.
Pero es preferible ValueListenableBuilder .

La razón de esto es que no es el número de líneas lo que importa, sino cuáles son estas líneas.

El primer fragmento tiene:

  • 1 declaración de propiedad
  • 1 declaración de método
  • 1 tarea
  • 2 llamadas a métodos
  • todos los cuales se distribuyen en 2 ciclos de vida diferentes. 3 si incluimos build

El segundo fragmento tiene:

  • Instanciación de 1 clase
  • 1 función anónima
  • sin ciclo de vida. 1 si incluimos build

Lo que hace que ValueListenableBuilder sea _simpler_.

También hay lo que estas líneas no dicen:
ValueListenableBuilder maneja valueListenable cambiando con el tiempo.
Incluso en el escenario en el que widget.valueNotifier no cambia con el tiempo mientras hablamos, no está de más.
Algún día, esa declaración puede cambiar. En cuyo caso, ValueListenableBuilder maneja con gracia el nuevo comportamiento, mientras que, con el primer fragmento, ahora tenemos un error.

Por lo tanto, ValueListenableBuilder no solo es más simple, sino que también es más resistente a los cambios en el código, para el mismo número exacto de líneas.


Con eso, creo que ambos podemos estar de acuerdo en que ValueListenableBuilder es preferible.
La pregunta es entonces, "¿Por qué no tener un equivalente a ValueListenableBuilder para cada lógica de estado reutilizable?"

Por ejemplo, en lugar de:

final controller = TextEditingController(text: 'hello world');
...
controller.dispose();

tendríamos:

TextEditingControllerBuilder(
  initialText: 'hello world',
  builder: (context, controller) {

  },
);

con el beneficio adicional que cambia a initialText se puede recargar en caliente.

Este ejemplo puede ser un poco trivial, pero podríamos usar este principio para lógicas de estado reutilizables un poco más avanzadas (como su ModeratorBuilder ).

Esto está "bien" en pequeños fragmentos. Pero causa algunos problemas ya que queremos escalar el enfoque:

  • Los constructores regresan al tema del "demasiado ruido".

Por ejemplo, he visto a algunas personas administrar su modelo de esta manera:

class User {
  final ValueNotifier<String> name;
  final ValueNotifier<int> age;
  final ValueNotifier<Gender> gender;
}

Pero entonces, un widget puede querer escuchar tanto name , age como gender todos a la vez.
Lo que significa que tendríamos que hacer:

return ValueListenableBuilder<String>(
  valueListenable: user.name,
  builder: (context, userName, _) {
    return ValueListenableBuilder<int>(
      valueListenable: user.age,
      builder: (context, userAge, _) {
        return ValueListenableBuilder<Gender>(
          valueListenable: user.gender,
          builder: (context, userGender, _) {
            return Text('$userGender. $userName ($userAge)');
          },
        );
      },
    );
  },
);

Obviamente, esto no es lo ideal. Eliminamos la contaminación dentro de initState / dispose para contaminar nuestro método build .

(ignoremos Listenable.merge por el bien del ejemplo. No importa aquí; se trata más de la composición)

Si usamos Builders extensamente, es fácil vernos en este escenario exacto, y sin equivalente a Listenable.merge (no es que me guste este constructor, para empezar 😛)

  • Escribir un constructor personalizado es tedioso

    No existe una solución fácil para crear un constructor. Ninguna herramienta de refactorización nos ayudará aquí, no podemos simplemente "extraer como constructor".
    Además, no es necesariamente intuitivo. Hacer un constructor personalizado no es lo primero en lo que la gente pensará, especialmente porque muchos estarán en contra del texto estándar (aunque yo no).

    Es más probable que las personas creen una solución de administración de estado personalizada y, potencialmente, terminen con un código incorrecto.

  • Manipular un árbol de constructores es tedioso

    Digamos que queríamos eliminar un ValueListenableBuilder en nuestro ejemplo anterior o agregar uno nuevo, no es fácil.
    Podemos pasar unos minutos atascados contando () y {} para entender por qué nuestro código no se compila.


Los Hooks están ahí para resolver los problemas de Builder que acabamos de mencionar.

Si refactorizamos el ejemplo anterior a ganchos, tendríamos:

final userName = useValueListenable(user.name);
final useAge = useValueListenable(user.age);
final useGender = useValueListenable(user.gender);

return Text('$userGender. $userName ($userAge)');

Es idéntico al comportamiento anterior, pero el código ahora tiene una sangría lineal.
Lo que significa:

  • el código drásticamente más legible
  • es más fácil de editar. No necesitamos temer () {}; para agregar una nueva línea.

Ese es uno de los principales provider . Eliminó una gran cantidad de anidamiento al introducir MultiProvider .

De manera similar, a diferencia del enfoque initState / dispose , nos beneficiamos de la recarga en caliente.
Si agregamos un nuevo useValueListenable , el cambio se aplicaría inmediatamente.

Y, por supuesto, todavía tenemos la capacidad de extraer primitivas reutilizables:

String useUserLabel(User user) {
  final userName = useValueListenable(user.name);
  final useAge = useValueListenable(user.age);
  final useGender = useValueListenable(user.gender);

  return '$userGender. $userName ($userAge)';
}

Widget build(context) {
  final label = useUserLabel(user);
  return Text(label);
}

y dicho cambio se puede automatizar con extract as function , que funcionaría en la mayoría de los escenarios.


Eso responde tu pregunta?

Seguro. Sin embargo, el problema con algo así es que simplemente no tiene suficiente información para hacer lo correcto. Por ejemplo:

Widget build(context) {
  if (random.nextBool())
    final title = useLabel(title);
  final label = useLabel(name);
  return Text(label);
}

... terminaría teniendo errores de formas realmente confusas.

Puede solucionar eso con la magia del compilador (así es como lo hace Compose) pero para Flutter eso viola algunas de nuestras decisiones fundamentales de diseño. Puede solucionarlo con claves, pero luego el rendimiento sufre mucho (ya que la búsqueda de variables termina involucrando búsquedas de mapas, hashes, etc.), lo que para Flutter viola algunos de nuestros objetivos de diseño fundamentales.

La solución de propiedades que sugerí anteriormente, o algo derivado de eso, parece que evita la magia del compilador y, al mismo tiempo, logra los objetivos que ha descrito de tener todo el código en un solo lugar. Realmente no entiendo por qué no funcionaría para esto. (Obviamente, se ampliaría para conectarse también a didChangeDependencies y así sucesivamente para ser una solución completa). (No pondríamos esto en el marco base porque violaría nuestros requisitos de rendimiento).

Precisamente por los bugs que pueden ocurrir, como dices, es la razón por la que los hooks no deben llamarse condicionalmente. Consulte el documento Rules of Hooks de ReactJS para obtener más detalles. La esencia básica es que, dado que en su implementación se realiza un seguimiento por orden de llamada, su uso condicional romperá ese orden de llamada y, por lo tanto, no permitirá que se realice un seguimiento correcto. Para usar correctamente el gancho, los llama en el nivel superior en build sin ninguna lógica condicional. En la versión JS, regresas

const [title, setTitle] = useLabel("title");

El equivalente de Dart puede ser similar, solo que es más largo debido a que no tiene desempaquetado como lo hace JS:

var titleHook = useLabel("title");
String title = titleHook.property;
Function setTitle = titleHook.setter;

Si desea lógica condicional, puede decidir usar title en el método de compilación _ después de haberlos llamado en el nivel superior_, porque ahora el orden de llamada aún se conserva. Muchos de estos problemas que plantea se han explicado en el documento de ganchos que vinculé anteriormente.

Seguro. Y puede hacerlo en un paquete. Solo digo que ese tipo de requisito violaría nuestra filosofía de diseño, por lo que no agregaríamos eso a Flutter en el marco. (Específicamente, optimizamos para legibilidad y depuración; tener un código que parece que funciona pero, debido a un condicional (que puede no ser obvio en el código) a veces no funciona, no es algo que queramos fomentar o habilitar en el marco central.)

El comportamiento de depuración / condicional no es un problema. Por eso es importante un complemento de analizador. Tal complemento:

  • advertir si una función usa un gancho sin ser nombrada useMyFunction
  • advertir si un gancho se usa condicionalmente
  • advierte si se utiliza un gancho en un bucle / devolución de llamada.

Esto cubre todos los errores potenciales. React demostró que esto es factible.

Entonces nos quedamos con los beneficios:

  • código más legible (como se mostró anteriormente)
  • mejor recarga en caliente
  • código más reutilizable / componible
  • más flexible: podemos hacer estados calculados fácilmente.

Acerca de los estados calculados, los ganchos son bastante poderosos para almacenar en caché la instancia de un objeto. Esto se puede usar para reconstruir un widget solo cuando cambia su parámetro.

Por ejemplo, podemos tener:

class Example extends HookWidget {
  final int userId;

  Widget build(context) {
    // Calls fetchUser whenever userId changes
    // It is the equivalent to both initState and didUpdateWidget
    final future = useMemo1(() => fetchUser(userId), userId);

    final snapshot = useFuture(future);
    if (!snapshot.hasData)
      return Text('loading');
    return Text(snapshot.data.name);
  }  
}

Dicho gancho useMemo permite optimizaciones de rendimiento sencillas y maneja tanto init + update de forma declarativa, lo que también evita errores.

Esto es algo que la propuesta Property / context.onDispose pasa por alto.
Son difíciles de usar para estados declarativos sin acoplar estrechamente la lógica a un ciclo de vida o complejizar el código con ValueNotifier .

Más sobre por qué la propuesta ValueGetter no es práctica:

Es posible que queramos refactorizar:

final int userId;

Widget build(context) {
  final future = useMemo1(() => fetchUser(userId), userId);

dentro:

Widget build(context) {
  final userId = Model.of(context).userId;
  final future = useMemo1(() => fetchUser(userId), userId);

Con los ganchos, este cambio funciona perfectamente, ya que useMemo no está vinculado a ningún ciclo de vida.

Pero con Property + ValueGetter , tendríamos que cambiar la implementación de Property para que esto funcione, lo cual no es deseado ya que el código Property puede ser reutilizado en varios lugares. Así que volvimos a perder la capacidad de reutilización.

FWIW este fragmento es equivalente a:

class Example extends StatefulWidget {
  final int userId;
  <strong i="45">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  Future<User> future;

  <strong i="46">@override</strong>
  void initState() {
    super.initState();
    future = fetchUser(widget.userId);
  }

  <strong i="47">@override</strong>
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.userId != widget.userId) {
      future = fetchUser(widget.userId);
    }
  }

  <strong i="48">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: future,
      builder: (context, snapshot) {
        if (!snapshot.hasData)
          return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

Supongo que tendremos que encontrar una solución que resuelva los mismos problemas que menciona @rrousselGit, pero que también tenga en mente la legibilidad y la depuración. Vue tiene su propia implementación que podría estar más en línea con lo que está buscando, donde los condicionales o el orden de llamada no causan errores como en React.

Tal vez el siguiente paso sea crear una solución única para Flutter que sea la versión de los ganchos de este framework, dadas las limitaciones de Flutter, tal como Vue ha hecho su versión dadas las limitaciones de Vue. Utilizo los ganchos de React con regularidad y diría que tener un complemento de analizador a veces puede no ser suficiente, probablemente debería estar más integrado en el lenguaje.

En cualquier caso, no creo que lleguemos nunca a un consenso. Parece que no estamos de acuerdo incluso en lo que se puede leer

Como recordatorio, estoy compartiendo esto solo porque sé que la comunidad tiene algunos problemas con este problema. Personalmente, no me importa si Flutter no hace nada al respecto (aunque me parece un poco triste), siempre que tengamos:

  • un sistema de complementos de analizador adecuado
  • la capacidad de usar paquetes dentro de dartpad

Si desea continuar con el complemento hooks, que recomiendo encarecidamente, pero tiene algunos problemas, le recomiendo presentar problemas para esos problemas y presentar PR para solucionar esos problemas. Estamos más que felices de trabajar con usted en eso.

Aquí hay una nueva versión de la idea anterior Property . Maneja didUpdateWidget y eliminación (y se puede hacer fácilmente para manejar otras cosas como didChangeDependencies); admite recarga en caliente (puede cambiar el código que registra la propiedad y recargar en caliente, y hará lo correcto); es seguro para tipos sin necesidad de tipos explícitos (se basa en la inferencia); tiene todo en un solo lugar, excepto la declaración de propiedad y el uso, y el rendimiento debe ser razonablemente bueno (aunque no tan bueno como las formas más detalladas de hacer las cosas).

Propiedad / PropertyManager:

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';

typedef InitStateCallback<T> = T Function(T oldValue);
typedef DidUpdateWidgetCallback<T, W extends StatefulWidget> = T Function(W oldWidget);

class Property<T, W extends StatefulWidget> {
  Property({
    T value,
    this.initState,
    this.didUpdateWidget,
    this.dispose,
  }) : _value = value;

  T get value {
    assert(_value != null);
    return _value;
  }
  T _value;

  final InitStateCallback<T> initState;
  void _initState(Property<T, W> oldProperty) {
    if (initState != null)
      _value = initState(oldProperty?.value);
    assert(_value != null);
  }

  final DidUpdateWidgetCallback<T, W> didUpdateWidget;
  void _didUpdateWidget(StatefulWidget oldWidget) {
    if (didUpdateWidget != null) {
      final T newValue = didUpdateWidget(oldWidget);
      if (newValue != null)
        _value = newValue;
    }
  }

  final ValueSetter<T> dispose;
  void _dispose() {
    if (dispose != null)
      dispose(value);
  }
}

mixin PropertyManager<W extends StatefulWidget> on State<W> {
  final Set<Property<Object, W>> _properties = <Property<Object, W>>{};
  bool _ready = false;

  Property<T, W> register<T>(Property<T, W> oldProperty, Property<T, W> property) {
    assert(_ready);
    if (oldProperty != null) {
      assert(_properties.contains(oldProperty));
      _properties.remove(oldProperty);
    }
    assert(property._value == null);
    property._initState(oldProperty);
    _properties.add(property);
    return property;
  }

  <strong i="9">@override</strong>
  void initState() {
    super.initState();
    _ready = true;
    initProperties();
  }

  <strong i="10">@override</strong>
  void reassemble() {
    super.reassemble();
    initProperties();
  }

  <strong i="11">@protected</strong>
  <strong i="12">@mustCallSuper</strong>
  void initProperties() { }

  <strong i="13">@override</strong>
  void didUpdateWidget(W oldWidget) {
    super.didUpdateWidget(oldWidget);
    for (Property<Object, W> property in _properties)
      property._didUpdateWidget(oldWidget);
  }

  <strong i="14">@override</strong>
  void dispose() {
    _ready = false;
    for (Property<Object, W> property in _properties)
      property._dispose();
    super.dispose();
  }
}

Así es como lo usaría:

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'properties.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  <strong i="18">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Example(userId: 1),
    );
  }
}

class User {
  User(this.name);
  final String name;
}

Future<User> fetchUser(int userId) async {
  await Future.delayed(const Duration(seconds: 2));
  return User('user$userId');
}

class Example extends StatefulWidget {
  Example({ Key key, this.userId }): super(key: key);

  final int userId;

  <strong i="19">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with PropertyManager {
  Property future;

  <strong i="20">@override</strong>
  void initProperties() {
    super.initProperties();
    future = register(future, Property(
      initState: (_) => fetchUser(widget.userId),
      didUpdateWidget: (oldWidget) {
        if (oldWidget.userId != widget.userId)
          return fetchUser(widget.userId);
      }
    ));
  }

  <strong i="21">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: future.value,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

Para mayor comodidad, puede crear subclases de propiedades preparadas para cosas como AnimationControllers, etc.

Probablemente pueda hacer una versión de esto que también funcione en los métodos State.build ...

Comparto algunas de las dudas que trae @Hixie a la mesa. Por otro lado, veo las claras ventajas que tiene Hooks y parece que a muchos desarrolladores les gusta.
Mi problema con el enfoque de paquete que propuso @timsneath es que el código que usa Hooks se ve dramáticamente diferente del código sin. Si no los incluyen en el canon oficial, terminaremos con un código de Flutter que no es legible para las personas que solo siguen el canon de Flutter.
Si los paquetes comienzan a implementar cosas que deberían ser la capacidad de respuesta del marco, obtendremos muchos dialectos de Flutter diferentes, lo que dificulta el aprendizaje de nuevas bases de código. Entonces, para mí, probablemente comenzaría a usar ganchos en el momento en que se convierta en parte de Flutter.
Es muy similar a mi opinión actual sobre el paquete congelado. Me encanta la funcionalidad, pero a menos que las Uniones y las clases de datos no formen parte de Dart, no quiero incluirlas en mi base de código porque haría más difícil que la gente lea mi código.

@escamoteur Para que yo lo entienda, ¿estás sugiriendo que cambiemos fundamentalmente el funcionamiento de los widgets? ¿O estás sugiriendo que debería haber algunas nuevas habilidades específicas? Dado que cosas como Hooks y la propuesta de propiedad anterior son posibles sin ningún cambio en el marco principal, no me queda claro qué es lo que realmente le gustaría cambiar.

Es ortogonal a la conversación sobre cualquier cambio propuesto en sí, pero creo que lo que he escuchado de @escamoteur , @rrousselGit y otros, tanto aquí como en otros lugares, es que estar _en_ ​​el marco se percibe como una forma importante de establecer la legitimidad de un determinado Acercarse. Corrígeme si no estás de acuerdo.

Entiendo esa línea de pensamiento, ya que hay muchas cosas que surgen de estar en el marco (por ejemplo, DartPad no admite paquetes de terceros en la actualidad, algunos clientes desconfían de cuántos paquetes dependen después de ser quemados con NPM, se siente más 'oficial', está garantizado que avanzará con cambios como seguridad nula).

Pero también hay costos significativos de ser incluido: en particular, osifica un enfoque y una API. Es por eso que ambos mantenemos un listón muy alto en lo que agregamos, particularmente cuando no hay un acuerdo unánime (cf gestión estatal), donde existe la probabilidad de evolución, o donde podemos agregar algo tan fácilmente como un paquete.

Me pregunto si necesitamos documentar nuestra filosofía del paquete primero, pero de nuevo, _dónde_ va está separado de una discusión sobre _qué_ podríamos querer cambiar para mejorar la reutilización de la lógica de estado.

Nuestra política de paquetes está documentada aquí: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#deciding -where-to-put-code

Entiendo completamente el enfoque del paquete primero y estoy de acuerdo en que es algo importante.
Pero también creo que algunos problemas deben resolverse en el núcleo, no mediante paquetes.

Es por eso que no estoy argumentando que provider debería fusionarse en Flutter, sino que también creo que este problema describe un problema que Flutter debería resolver de forma nativa (no necesariamente con ganchos, por supuesto).

Con Provider, Flutter envía una primitiva incorporada para resolver este tipo de problema: InheritedWidgets.
El proveedor solo agrega una capa obstinada en la parte superior para que sea "" más agradable "".

Los ganchos son diferentes. Ellos _son_ los primitivos. Son una solución de bajo nivel sin opinión para un problema específico: reutilización de la lógica en varios estados.
No son el producto final, sino algo que se espera que la gente use para crear paquetes personalizados (como hice yo con hooks_riverpod )

Sería útil para mí (en términos de comprender los deseos aquí y las necesidades que satisface los ganchos, etc.) si alguien pudiera proporcionar una revisión detallada de cómo el enfoque de propiedad que garabateé anteriormente se compara con los ganchos. (Mi objetivo con la idea de Propiedad es en gran medida superponer la opinión sobre el marco para resolver el problema de cómo reutilizar la lógica en múltiples estados).

Creo que la propuesta de Propiedad no resuelve un objetivo clave de este tema: la lógica del Estado no debería preocuparse de dónde vienen los parámetros y en qué situación se están actualizando.

Esta propuesta aumenta la legibilidad hasta cierto punto al reagrupar toda la lógica en un solo lugar; pero no resuelve el problema de la reutilización

Más específicamente, no podemos extraer:

Property(
  initState: (_) => fetchUser(widget.userId),
  didUpdateWidget: (oldWidget) {
    if (oldWidget.userId != widget.userId)
      return fetchUser(widget.userId);
  }
)

de _ExampleState y reutilizarlo en un widget diferente, ya que la lógica está vinculada a Example y initState + didUpdateWidget

¿Qué aspecto tendría con los ganchos?

Estoy de acuerdo con @timsneath después de haber visto algo similar en la comunidad de Rust. Es muy difícil extraer algo del núcleo una vez que está dentro. El patrón BLoC se especificó antes de que apareciera el proveedor, pero ahora el proveedor es la versión recomendada. Quizás flutter_hooks pueda ser la versión "bendecida" de la misma manera. Digo esto porque en el futuro puede haber mejoras sobre los ganchos que se le ocurren a la gente. Reaccionar, habiendo tenido ganchos ahora, realmente no puede cambiarlos o salir de ellos. Deben apoyarlos, al igual que lo hacen con los componentes de la clase, esencialmente para siempre, ya que están en el núcleo. Por tanto, estoy de acuerdo con la filosofía del paquete.

El problema parece ser que la adopción será baja y las personas usarán lo que les convenga. Esto se puede resolver, como digo, recomendando a las personas que usen flutter_hooks. Esto también podría no ser un gran problema si analizamos análogamente cuántas soluciones de administración estatal existen, incluso si muchas personas usan el proveedor. También he experimentado algunos problemas y "trampas" con ganchos en otros marcos que deberían abordarse para crear una solución superior a la lógica del ciclo de vida componible y reutilizable.

¿Qué aspecto tendría con los ganchos?

Sin usar ningún gancho primitivo enviado por React / flutter_hooks, podríamos tener:

class FetchUser extends Hook<AsyncSnapshot<User>> {
  const FetchUser(this.userId);
  final int userId;

  <strong i="8">@override</strong>
  _FetchUserState createState() => _FetchUserState();
}

class _FetchUserState extends HookState<AsyncSnapshot<User>, FetchUser> {
  Future<User> userFuture;

  <strong i="9">@override</strong>
  void initHook() {
    userFuture = fetchUser(hook.userId);
  }  

  void didUpdateHook(FetchUser oldHook) {
    if (oldHook.userId != hook.userId)
      userFuture = fetchUser(hook.userId);
  }


  <strong i="10">@override</strong>
  User build() {
    return useFuture(userFuture);
  }
}

Luego usó:

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

  final int userId;

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    AsyncSnapshot<User> user = use(FetchUser(userId));

    if (!user.hasData)
      return CircularProgressIndicator();
    return Text(user.data.name);
  }
}

En esta situación, la lógica es completamente independiente de Example y los ciclos de vida de un StatefulWidget .
Entonces podríamos reutilizarlo en un widget diferente que administra su userId diferente. Tal vez ese otro widget sea un StatefulWidget que gestione su userId internamente. Quizás obtenga el userId de un InheritedWidget en su lugar.

Esta sintaxis debería hacer evidente que los ganchos son como objetos State independientes con sus propios ciclos de vida.

Como nota al margen, un inconveniente del enfoque de paquete primero es: es menos probable que los autores de paquetes publiquen paquetes que se basen en ganchos para resolver problemas.

Por ejemplo, un problema común al que se enfrentan los usuarios de proveedores es que quieren deshacerse automáticamente del estado de un proveedor cuando ya no se utiliza.
El problema es que a los usuarios del proveedor también les gusta mucho la sintaxis context.watch / context.select , en oposición a la sintaxis detallada Consumer(builder: ...) / Selector(builder:...) .
Pero no podemos tener esta agradable sintaxis _y_ resolver el problema mencionado anteriormente sin ganchos (o https://github.com/flutter/flutter/pull/33213, que fue rechazado).

El problema es:
El proveedor no puede depender de flutter_hooks para resolver este problema.
Debido a lo popular que es el Proveedor, no sería razonable depender de los ganchos.

Así que al final, opté por:

  • Proveedor de bifurcación (bajo el nombre en clave de Riverpod )
  • perder voluntariamente el "favorito de Flutter" / recomendación de Google como consecuencia
  • resolver este problema (y algunos más)
  • agregue una dependencia de los ganchos para ofrecer una sintaxis que les gustaría a las personas que disfrutan de context.watch .

Estoy bastante satisfecho con lo que se me ocurrió, ya que creo que aporta una mejora significativa con respecto a Provider (hace que InheritedWidgets sea seguro para la compilación).
Pero la forma de llegar me dejó mal regusto.

Básicamente, hay tres diferencias, por lo que puedo decir, entre la versión de ganchos y la versión de propiedad:

  • La versión de Hooks es mucho más código de respaldo
  • La versión Property es mucho más código repetitivo
  • La versión de Hooks tiene el problema de los métodos de compilación en los que, si llama a los hooks en el orden incorrecto, las cosas van mal y no hay forma de verlo de inmediato desde el código.

¿Es el código repetitivo realmente tan importante? Quiero decir, puede reutilizar fácilmente la Propiedad ahora, el código está todo en un solo lugar. Así que ahora es _sólo_ un argumento de verbosidad.

Creo que una buena solución no debería depender de que otros paquetes la conozcan. No debería importar si está en el marco o no. Las personas que no lo utilicen no deberían ser un problema. Si la gente que no lo usa es un problema, entonces eso, en mi humilde opinión, es una señal de alerta para la API.

Quiero decir, puede reutilizar fácilmente la Propiedad ahora, el código está todo en un solo lugar.

Que el código esté en un solo lugar no significa que sea reutilizable.
¿Le importaría crear un widget secundario que reutilice el código que se encuentra actualmente dentro de _ExampleState en un widget diferente?
Con un giro: ese nuevo widget debería administrar su ID de usuario internamente dentro de su Estado, de modo que tenemos:

class _WhateverState extends State with PropertyManager {
  // may change over time after some setState calls
  int userId;
}

Si la gente que no lo usa es un problema, entonces eso, en mi humilde opinión, es una señal de alerta para la API.

La gente que no usa algo porque no es oficial no significa que la API sea mala.

Es totalmente legítimo no querer agregar dependencias adicionales porque es un trabajo adicional de mantener (debido al control de versiones, la licencia, la depreciación y otras cosas).
Por lo que recuerdo, Flutter tiene el requisito de tener la menor cantidad de dependencias posible.

Incluso con Provider, que es ampliamente aceptado y casi oficial ahora, he visto a personas decir "Prefiero usar los InheritedWidgets integrados para evitar agregar una dependencia".

¿Le importaría crear un widget secundario que reutilice el código que se encuentra actualmente dentro de _ExampleState en un widget diferente?

El código en cuestión se trata de obtener un userId de un widget y pasarlo a un método fetchUser. El código para administrar el ID de usuario que cambia localmente en el mismo objeto sería diferente. ¿Eso parece estar bien? No estoy seguro del problema que intentas resolver aquí.

Para el registro, no usaría Propiedad para hacer lo que describe, simplemente se vería así:

class Example extends StatefulWidget {
  Example({ Key key }): super(key: key);

  <strong i="10">@override</strong>
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends State<Example> with PropertyManager {
  int _userId;
  Future<User> _future;

  void _setUserId(int newId) {
    if (newId == _userId)
      return;
    setState(() {
      _future = fetchUser(_userId);
    });
  }

  // ...code that uses _setUserId...

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    return FutureBuilder<User>(
      future: _future.value,
      builder: (context, snapshot) {
        if (!snapshot.hasData) return Text('loading');
        return Text(snapshot.data.name);
      },
    );
  }
}

La gente que no usa algo porque no es oficial no significa que la API sea mala.

Acordado.

El hecho de que la gente no use algo en sí mismo como malo es lo que significa que la API es mala. Cuando dice "Es menos probable que los autores de paquetes publiquen paquetes que se basen en ganchos para resolver problemas", eso indica que los ganchos dependen de que otras personas los utilicen para que le resulten útiles. Una buena API, en mi humilde opinión, no se vuelve mala si nadie más la adopta; debería mantenerse incluso si nadie más lo sabe. Por ejemplo, el ejemplo de Property anterior no depende de que otros paquetes lo usen para ser útil.

Incluso con Provider, que es ampliamente aceptado y casi oficial ahora, he visto a personas decir "Prefiero usar los InheritedWidgets integrados para evitar agregar una dependencia".

¿Qué pasa con las personas que prefieren usar InheritedWidget? No quiero forzar una solución a la gente. Deben usar lo que quieran usar. Literalmente estás describiendo un no problema. La solución para las personas que prefieren usar InheritedWidget es salirse de su camino y permitirles usar InheritedWidget.

. Una buena API, en mi humilde opinión, no se vuelve mala si nadie más la adopta; debería mantenerse incluso si nadie más lo sabe. Por ejemplo, el ejemplo de Propiedad anterior no depende de que otros paquetes lo usen para ser útil.

Hay un mal entendido.

El problema no es que las personas no utilicen anzuelos en general.
Se trata de que el proveedor no pueda usar los ganchos para solucionar problemas porque los ganchos no son oficiales, mientras que el proveedor sí lo es.


El código para administrar el ID de usuario que cambia localmente en el mismo objeto sería diferente. ¿Eso parece estar bien? No estoy seguro del problema que intentas resolver aquí.

Para el registro, no usaría Propiedad para hacer lo que describe, simplemente se vería así:

Esto no responde a la pregunta. Pregunté esto específicamente para comparar la reutilización del código entre los ganchos y la propiedad.

Con los ganchos, podríamos reutilizar FetchUser :

class _WhateverState extends State with PropertyManager {
  // may change over time after some setState calls
  int userId;

  Widget build(context) {
    final user = use(FetchUser(userId));
  }
}

Con los ganchos, podríamos reutilizar FetchUser :

No entiendo por qué esto es deseable. FetchUser no tiene ningún código interesante, es solo un adaptador de Hooks a la función fetchUser . ¿Por qué no llamar directamente a fetchUser ? El código que está reutilizando no es un código interesante.

Se trata de que el proveedor no pueda usar los ganchos para solucionar problemas porque los ganchos no son oficiales, mientras que el proveedor sí lo es.

En mi humilde opinión, el proveedor no necesitaría adoptar una buena solución al problema de reutilización del código. Serían conceptos totalmente ortogonales. Esto es algo de lo que habla la guía de estilo de Flutter bajo el título "evitar completar".

No entiendo por qué esto es deseable. FetchUser no tiene ningún código interesante, es solo un adaptador de Hooks a la función fetchUser. ¿Por qué no llamar a fetchUser directamente? El código que está reutilizando no es un código interesante.

No importa. Estamos intentando demostrar la posibilidad de reutilizar el código. fetchUser podría ser cualquier cosa, incluido ChangeNotifier.addListener por ejemplo.

Podríamos tener una implementación alternativa que no dependa de fetchUser , y simplemente proporcionar una API para realizar la búsqueda de datos implícita:

int userId;

Widget build(context) {
  AsyncSnapshot<User> user = use(ImplicitFetcher<User>(userId, fetch: () => fetchUser(userId));
}

En mi humilde opinión, el proveedor no necesitaría adoptar una buena solución al problema de reutilización del código. Serían conceptos totalmente ortogonales. Esto es algo de lo que habla la guía de estilo de Flutter bajo el título "evitar completar".

Por eso mencioné que los ganchos son un primitivo

Como metáfora:
package:animations depende de Animation . Pero eso no es un problema, porque se trata de un núcleo primitivo.
Sería diferente si en su lugar package:animations estuviera usando una bifurcación de Animation mantenida por la comunidad

@escamoteur Para que yo lo entienda, ¿estás sugiriendo que cambiemos fundamentalmente el funcionamiento de los widgets? ¿O estás sugiriendo que debería haber algunas nuevas habilidades específicas? Dado que cosas como Hooks y la propuesta de propiedad anterior son posibles sin ningún cambio en el marco principal, no me queda claro qué es lo que realmente le gustaría cambiar.

@Hixie no, mi punto es que si los ganchos se
Comparto mucho tus preocupaciones, pero por otro lado, un Widget con ganchos se ve realmente elegante.
No prohibiría hacer las cosas como antes.

No prohibiría hacer las cosas como antes.

Creo que sí, no creo que sea una buena idea que el equipo de Flutter diga "oye, ahora recomendamos los flutter hooks, pero aún puedes hacer las cosas como antes", la gente se confundirá al respecto. Además, si el equipo de Flutter recomienda ganchos en el futuro, también deberán dejar de publicar el código de flutter real como ejemplos.

La gente siempre sigue la "forma oficial" de hacer las cosas y creo que no debería haber dos formas oficiales de usar Flutter.

No importa. Estamos intentando demostrar la posibilidad de reutilizar el código. fetchUser podría ser cualquier cosa, incluido ChangeNotifier.addListener por ejemplo.

Seguro. Para eso son buenas las funciones: abstraer código. Pero ya tenemos funciones. El código de propiedad anterior y el código _setUserId anterior muestran que puede llevar todo el código que llama a esas funciones a un lugar sin necesidad de ninguna ayuda particular del marco. ¿Por qué necesitamos Hooks para envolver las llamadas a esas funciones?

En mi humilde opinión, el proveedor no necesitaría adoptar una buena solución al problema de reutilización del código. Serían conceptos totalmente ortogonales. Esto es algo de lo que habla la guía de estilo de Flutter bajo el título "evitar completar".

Por eso mencioné que los ganchos son un primitivo

Son una conveniencia, no son primitivos. Si fueran primitivos, la pregunta "cuál es el problema" sería mucho más fácil de responder. Dirías "aquí hay una cosa que quiero hacer y no puedo".

Como metáfora:
package:animations depende de Animation . Pero eso no es un problema, porque se trata de un núcleo primitivo.
Sería diferente si en su lugar package:animations estuviera usando una bifurcación de Animation mantenida por la comunidad

La jerarquía de clases de Animación hace algo fundamental: introduce tickers y una forma de controlarlos y suscribirse a ellos. Sin la jerarquía de clases de Animación, tienes que inventar algo como la jerarquía de clases de Animación para hacer animaciones. (Idealmente algo mejor. No es nuestro mejor trabajo). Hooks no introduce una nueva característica fundamental. Simplemente proporciona una forma de escribir el mismo código de manera diferente. Puede ser que ese código sea más simple, o se factorice de manera diferente de lo que sería de otra manera, pero no es un primitivo. No necesita un marco similar a Hooks para escribir código que haga lo mismo que hace el código que usa Hooks.


Básicamente, no creo que el problema descrito en este número sea algo que el marco deba solucionar. Diferentes personas tendrán necesidades muy diferentes sobre cómo abordarlo. Hay muchas formas de solucionarlo, ya hemos discutido varias en este error; algunas de las formas son muy simples y se pueden escribir en unos minutos, por lo que no es un problema tan difícil de resolver que nos brinde valor para poseer y mantener la solución. Cada una de las propuestas tiene fortalezas y debilidades; las debilidades son, en cada caso, cosas que podrían ser un obstáculo para que alguien las use. Ni siquiera está realmente claro que todos estén de acuerdo en que el problema debe solucionarse.

Los ganchos _son_ primitivos
Aquí hay un hilo de Dan: https://twitter.com/dan_abramov/status/1093698629708251136 explicando esto. Algunas redacciones difieren, pero la lógica se aplica principalmente a Flutter debido a la similitud entre los componentes de la clase React y Flutter StatefulWidgets

Más específicamente, podría pensar en flutter_hooks como mixins dinámicos de estado.

Si fueran primitivos, la pregunta "cuál es el problema" sería mucho más fácil de responder. Dirías "aquí hay una cosa que quiero hacer y no puedo".

Está en el OP:

Es difícil reutilizar la lógica estatal. O terminamos con un método de construcción complejo y profundamente anidado o tenemos que copiar y pegar la lógica en múltiples widgets.
No es posible reutilizar dicha lógica a través de mixins ni funciones.

Puede ser que ese código sea más simple, o se factorice de manera diferente de lo que sería de otra manera, pero no es un primitivo. No necesita un marco similar a Hooks para escribir código que haga lo mismo que hace el código que usa Hooks.

No necesitas clases para escribir un programa. Pero las clases le permiten estructurar su código y factorizarlo de una manera significativa.
Y las clases son primitivas.

Lo mismo con los mixins, que también son primitivos.

Los ganchos son lo mismo.

¿Por qué necesitamos Hooks para envolver las llamadas a esas funciones?

Para cuando necesitemos llamar a esta lógica no en _un_ lugar sino en _dos_ lugares.

No es posible reutilizar dicha lógica a través de mixins ni funciones.

Por favor, deme un ejemplo concreto en el que este sea el caso. Hasta ahora, todos los ejemplos que hemos estudiado han sido simples sin ganchos.

Hasta ahora, en este hilo, no he visto ninguna otra solución que los ganchos de @rrousselGit que resuelvan y faciliten la reutilización y la composición de la lógica de estado.

De acuerdo, no he estado haciendo muchos dardos y aleteo últimamente, por lo que es posible que me falten cosas en los ejemplos de código de solución de propiedad anteriores, pero ¿hay alguna solución? ¿Cuáles son las opciones de hoy que no requieren copiar y pegar en lugar de reutilizar?
¿Cuáles son la respuesta a la pregunta de @rrousselGit ?

¿Le importaría crear un widget secundario que reutilice el código que se encuentra actualmente dentro de _ExampleState en un widget diferente?
Con un giro: ese nuevo widget debería administrar su ID de usuario internamente dentro de su estado

Si no es posible reutilizar una lógica de estado tan sencilla con la solución de propiedad anterior, ¿cuáles son las otras opciones?
¿La respuesta es simplemente que no debería ser fácil de reutilizar en flutter? Lo cual está totalmente bien, pero un poco triste en mi humilde opinión.

Por cierto, ¿SwiftUI lo hace de una manera nueva o inspiradora? ¿O también carecen de la misma capacidad de reutilización de la lógica estatal? Yo mismo no he usado swiftui. ¿Quizás es demasiado diferente?

Todos los constructores, básicamente. Los constructores son la única forma de reutilizar el estado en este momento.
Hooks hace que los constructores sean más legibles y fáciles de crear


Aquí hay una colección de ganchos personalizados que yo o algunos clientes hicimos el mes pasado para diferentes proyectos:

  • useQuery - que es equivalente al gancho ImplicitFetcher que di anteriormente, pero en su lugar hace una consulta GraphQL.
  • useOnResume que devuelve la llamada para realizar una acción personalizada en AppLifecycleState.resumed sin tener que hacerlo
    tomarse la molestia de hacer un WidgetsBindingObserver
  • useDebouncedListener que escucha un escuchable (generalmente TextField o ScrollController), pero con un rebote en el oyente
  • useAppLinkService que permite que los widgets realicen cierta lógica en un evento personalizado similar a AppLifecycleState.resumed pero con reglas comerciales
  • useShrinkUIForKeyboard para manejar sin problemas la apariencia del teclado. Devuelve un booleano que indica si la interfaz de usuario debe adaptarse al relleno inferior o no (que se basa en escuchar un focusNode)
  • useFilter , que combina useDebouncedListener y useState (un gancho primitivo que declara una sola propiedad) para exponer un filtro para una barra de búsqueda.
  • useImplicitlyAnimated<Int/Double/Color/...> - equivalente a TweenAnimationBuilder como gancho

Las aplicaciones también usan muchos ganchos de bajo nivel para diferentes lógicas.

Por ejemplo, en lugar de:

Whatever whatever;

initState() {
  whatever = doSomething(widget.id);
}

didUpdateWidget(oldWidget) {
  if (oldWidget.id != widget.id)
    whatever = doSomething(widget.id);
}

Ellas hacen:

Widget build(context) {
  final whatever = useUnaryEvent<Whatever, int>(widget.id, (int id) => doSomething(id));
}

Esto evita la duplicación entre initState / didUpdateWidget / didChangeDependencies .

También usan una gran cantidad de useProvider , de Riverpod, que de otro modo tendría que ser un StreamBuilder / ValueListenableBuilder


La parte importante es que los widgets rara vez usan "solo un gancho".
Por ejemplo, un widget puede hacer

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    final filter = useFilter(debounceDuration: const Duration(seconds: 2));
    final userId = useProvider(authProvider).userId;
    final chatId = useProvider(selectedChatProvider);
    final chat = useQuery(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

Es conciso y muy legible (asumiendo que tienes un conocimiento básico de la API, por supuesto).
Toda la lógica se puede leer de arriba a abajo; no hay salto entre métodos para comprender el código.
Y todos los ganchos que se utilizan aquí se reutilizan en varios lugares de la base de código.

Si hiciéramos exactamente lo mismo sin ganchos, tendríamos:

class ChatScreen extends StatefulWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="20">@override</strong>
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  String filter;
  Timer timer;

  <strong i="21">@override</strong>
  void dispose() {
    timer?.cancel();
    super.dispose();
  }

  <strong i="22">@override</strong>
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      provider: authProvider,
      builder: (context, auth, child) {
        return Consumer<int>(
          provider: selectedChatProvider,
          builder: (context, chatId, child) {
            return GraphQLBuilder<Chat>(
              query: ChatQuery(
                userId: auth.userId,
                chatId: chatId,
                filter: filter.value,
              ),
              builder: (context, chat, child) {
                return Column(
                  children: [
                    Searchbar(
                      onChanged: (value) {
                        timer?.cancel();
                        timer = Timer(const Duration(seconds: 2), () {
                          filter = value;
                        });
                      },
                    ),
                    Expanded(
                      child: ChatList(chat: chat),
                    ),
                  ],
                );
              },
            );
          },
        );
      },
    );
  }
}

Esto es significativamente menos legible.

  • Tenemos 10 niveles de sangría - 12 si hacemos un FilterBuilder para reutilizar la lógica del filtro
  • La lógica del filtro no es reutilizable tal como está.

    • es posible que olvidemos cancelar el timer por error

  • la mitad del método build no es útil para el lector. The Builders nos distrae de lo que importa
  • Perdí unos buenos 5 minutos tratando de entender por qué el código no se compila debido a que falta un paréntesis

Como usuario de flutter_hooks yo mismo, aportaré mi opinión. Antes de usar ganchos, estaba contento con Flutter. No vi la necesidad de algo así. Después de leer y ver un video de YouTube, todavía no estaba convencido, se veía genial, pero necesitaba algo de práctica o ejemplos para motivarlo realmente. Pero luego noté algo. Estaba evitando los widgets con estado a toda costa, solo había un montón de repetición involucrada y saltando la clase tratando de encontrar cosas. Debido a eso, había movido la mayor parte de mi estado efímero a una solución de administración de estado junto con el resto del estado de la aplicación, y solo usé widgets sin estado. Sin embargo, esto hace que la lógica empresarial dependa rápidamente de Flutter debido a la dependencia de obtener Navigator , o BuildContext para acceder a InheritedWidget s / Providers más el árbol. Sin decir que fue un buen enfoque de gestión estatal, sé que no lo fue. Pero hice todo lo que pude para no tener que preocuparme por la gestión del estado en la interfaz de usuario.

Después de usar ganchos por un tiempo, me encontré mucho más productivo, mucho más feliz usando Flutter, colocando el estado efímero en el lugar correcto (junto con la interfaz de usuario) en lugar del estado de la aplicación.

Para mí, es como un recolector de basura para controladores / estados efímeros. No tengo que acordarme de deshacerme de todas las suscripciones en la interfaz de usuario, aunque todavía soy muy consciente del hecho de que esto es lo que flutter_hooks hace por mí. También hace que sea mucho más fácil mantener y refactorizar mi código. Hablando de escribir ~ 10 aplicaciones en el último año para mi investigación de posgrado y diversión.

Como otros, no sé exactamente cuál debería ser la motivación principal para incluirlo en el propio Flutter SDK. Sin embargo, aquí hay dos pensamientos sobre ese tema.

  1. De vez en cuando, haré un gancho para facilitar el uso de un paquete que tiene controladores que deben inicializarse / eliminarse. (Por ejemplo golden_layout o zefyr ). Creo que los otros usuarios que usan flutter_hooks se beneficiarían de un paquete de este tipo. Sin embargo, parece que no puedo justificar la publicación de un paquete que contiene literalmente 1-3 funciones. La alternativa sería crear un paquete de fregadero de cocina que contenga muchos ganchos para varios paquetes que uso, luego puedo usar una dependencia de git, pero cualquiera que use esos otros paquetes + flutter_hooks tendría que depender de mi git en para beneficiarse (que es menos detectable y probablemente contiene dependencias de paquetes que no les interesan), o en un paquete que contiene 3 funciones o publico un paquete garden-sink en pub.dev. Todas las ideas parecen ridículas y poco fáciles de descubrir. Los otros usuarios de flutter_hooks podrían copiar y pegar fácilmente esas funciones en su código o intentar descifrar la lógica ellos mismos, pero eso pierde totalmente el sentido de compartir código / paquetes. Es mucho mejor que las funciones se incluyan en los paquetes originales y no en algún "paquete de extensión". Si flutter_hooks fuera parte del marco, o incluso solo un paquete usado o exportado desde el marco como characters , entonces los autores del paquete original probablemente aceptarían una solicitud de extracción para un gancho simple funciones, y no tendremos un lío de 1-3 paquetes de funciones.
    Si Flutter no adopta flutter_hooks preveo un montón de 1-3 paquetes de funciones saturando los resultados de búsqueda de pub.dev. El hecho de que estos paquetes sean realmente pequeños me hace estar realmente de acuerdo con @rrousselGit en que se trata de una primitiva. Si las 1228 estrellas en el repositorio flutter_hooks no son un indicio de que resuelva los problemas mencionados por @rrousselGit , no sé qué es.

  2. Estaba viendo un video de YouTube sobre cómo contribuir al repositorio de Flutter ya que estaba interesado en ver qué podía hacer para ayudar. Mientras miraba, la persona que creó el video agregó en la nueva propiedad con bastante facilidad, pero casi se olvidó de actualizar dispose , didUpdateWidget y debugFillProperties . Ver todas las complejidades de un widget con estado nuevamente y lo fácil que es perder algo me hizo desconfiar de ellos nuevamente y no me emocionó tanto por contribuir al repositorio principal de Flutter. Sin decir que me disuadió por completo, todavía estoy interesado en contribuir, pero siento que estaría creando un código estándar que es difícil de mantener y revisar. No se trata de la complejidad de escribir el código, sino de la complejidad de leer el código y verificar que ha eliminado y cuidado adecuadamente el estado efímero.

Perdón por la respuesta tan larga, sin embargo, he estado analizando este problema de vez en cuando y estoy algo desconcertado por la respuesta del equipo de Flutter. Parece que no se ha tomado el tiempo para probar una aplicación en ambos sentidos y ver la diferencia por sí mismo. Entiendo el deseo de no mantener una dependencia adicional o integrarla demasiado en el marco. Sin embargo, la parte central del marco flutter_hook son solo 500 líneas de código bastante bien documentado. Nuevamente, lo siento si esto es tangencial a la conversación y espero no ofender a nadie por dar mis dos centavos y hablar. No hablé antes porque sentí que @rrousselGit estaba haciendo muy buenos puntos y siendo claro.

Perdón por la respuesta tan larga, sin embargo, he estado analizando este problema de vez en cuando y estoy algo desconcertado por la respuesta del equipo de Flutter. Parece que no se ha tomado el tiempo para probar una aplicación en ambos sentidos y ver la diferencia por sí mismo.

Para ser justos, este es un hilo increíblemente largo y el fundador del marco ha contribuido activamente varias veces al día, con varias soluciones, solicitó comentarios sobre ellas, se involucró con ellas y trabajó para comprender lo que se solicita. Honestamente, me cuesta pensar en un ejemplo más claro de ayuda de un mantenedor.

Desearía tener un poco más de paciencia con este problema; no entiendo los ganchos más profundamente después de leer este hilo, aparte de que son otra forma de vincular la vida útil de los desechables a un estado. No prefiero ese enfoque estilísticamente, y creo que hay algo fundamentalmente defectuoso si la posición es 'simplemente tómese el tiempo para escribir una aplicación completamente nueva en el paradigma, entonces comprenderá por qué necesita ser calzada en el marco! ' - como señaló el ingeniero de React en este hilo, realmente no sería recomendable para Flutter, y los beneficios descritos en este hilo son pequeños en comparación con el costo del tipo de recableado que significa que necesita una base de código completamente nueva para ver el beneficio.

Honestamente, me cuesta pensar en un ejemplo más claro de ayuda de un mantenedor.

Acordado. Agradezco que Hixie se haya tomado el tiempo de participar en esta discusión.

No entiendo los ganchos más profundamente después de leer este hilo.

Para ser justos, este problema consiste en tratar explícitamente de evitar hablar específicamente sobre los ganchos.
Se trata más de tratar de explicar el problema que de la solución.

¿Sientes que no logra hacer eso?

Puedo sentir ambos lados ( @rrousselGit y @Hixie) aquí y quería dejar algunos comentarios desde (mi) lado / perspectiva de uso del marco Flutter.

El enfoque flutter_hooks reduce bastante el texto estándar (solo a partir de los ejemplos que se muestran aquí, ya que podemos reutilizar tales configuraciones de estado) y reduce la complejidad al no tener que pensar activamente en inicializar / eliminar los recursos. En términos generales, hace un buen trabajo mejorando y apoyando el flujo / velocidad de desarrollo ... aunque no encaja tan bien en el "núcleo" de Flutter en sí (subjetivamente).

Como al menos> 95% del código que escribo da como resultado que el método de compilación sea solo declarativo, sin variables locales o llamadas fuera del subárbol de widgets devuelto, toda la parte lógica está dentro de esas funciones de estado para inicializar, asignar y eliminar recursos y agregar oyentes (en mi caso reacciones de MobX) y esas cosas lógicas. Dado que este es también el enfoque en su mayor parte en Flutter, se siente muy nativo. Hacerlo también le da al desarrollador la oportunidad de ser siempre explícito y abierto sobre lo que hace; me obliga a convertir siempre dichos widgets para que sean StatefulWidget y escribir un código similar en initState / dispose, pero también siempre resulta en escribir exactamente lo que pretende hacer directamente en el widget que se está utilizando. Para mí, personalmente, como @Hixie ya se mencionó a sí mismo, no me molesta de ninguna manera escribir este tipo de código repetitivo y me permite, como desarrollador, decidir cómo manejarlo correctamente en lugar de depender de algo como flutter_hooks para hacerlo por mí y no entender por qué algo podría comportarse como lo hace. La extracción de widgets en pequeños bits también garantiza que ese tipo de texto estándar sea adecuado en el caso de uso para el que se está utilizando. Con flutter_hooks todavía tendría que pensar en qué tipo de estados vale la pena escribir para ser un gancho y, por lo tanto, reutilizarlo; diferentes sabores pueden dar como resultado varios ganchos de uso "único" o ningún gancho, ya que podría no reutiliza configuraciones con demasiada frecuencia, pero tiende a escribir más configuraciones personalizadas.

No me malinterpreten, el enfoque en tales ganchos parece muy agradable y útil, pero para mí se siente como un concepto muy fundamental que cambia el concepto central de cómo manejar esto. Se siente muy bien como paquete en sí mismo para dar a los desarrolladores la oportunidad de usar este tipo de enfoque si no están contentos con cómo hacerlo "de forma nativa", pero hacerlo parte del propio marco de Flutter sería, al menos, limpio / unificado, resulta en reescribir grandes partes de Flutter para hacer uso de este concepto (mucho trabajo) o usarlo para cosas futuras / seleccionadas (que puede ser confuso tener enfoques tan mixtos).

Si se integrara en el marco de Flutter en sí y se respaldara / usara activamente, obviamente me lanzaría a esto. Dado que entiendo e incluso me gusta el enfoque actual y veo las (posibles) acciones necesarias para implementar esto de forma nativa, puedo entender la duda y / o por qué no debería hacerse y más bien mantenerlo como un paquete.

Corrígeme si me equivoco, pero este hilo trata sobre los problemas de reutilizar la lógica de estado en varios widgets de una manera legible y componible. No enganches específicamente. Creo que este hilo se abrió debido a los deseos de tener una discusión sobre el problema con un enfoque abierto sobre cuál debería ser la solución.

Sin embargo, se mencionan los ganchos ya que son una solución y creo que @rrousselGit los ha estado usando aquí para tratar de explicar el problema / problema que resuelven (ya que son una solución) para que pueda surgir otra solución tal vez más nativa de flutter. con y presentado. Hasta ahora, que yo sepa, no se han presentado otras soluciones en este hilo, aunque que resuelvan los problemas de reutilización.

Dicho esto, no sé a dónde va el hilo en este momento.
Creo que el problema realmente existe. ¿O estamos debatiendo esto?
Si todos estamos de acuerdo en que es difícil reutilizar la lógica de estado de una manera componible en múltiples widgets con el núcleo de flutter hoy en día, ¿qué soluciones existen que podrían ser una solución central? ya que los constructores realmente son (para citar)

significativamente menos legible

La solución de propiedad no parece ser tan fácilmente reutilizable o es una conclusión incorrecta que he sacado (?) Ya que no hubo respuesta sobre cómo usarla para:

haciendo un widget secundario que reutiliza el código que se encuentra actualmente dentro de _ExampleState en un widget diferente?
Con un giro: ese nuevo widget debería administrar su ID de usuario internamente dentro de su estado

Estaría dispuesto a ayudar con un documento de diseño como sugirió @timsneath . Creo que probablemente sea un mejor formato para explicar el problema con algunos ejemplos de casos de estudio, así como mencionar las diferentes soluciones y explorar si podemos encontrar una solución que se ajuste a flutter y dónde se encuentra. Estoy de acuerdo en que la discusión sobre el tema se está perdiendo un poco.

Soy bastante escéptico sobre la idea de hacer un documento de diseño en este momento.
Es evidente que por ahora @Hixie está en contra de resolver este problema dentro de Flutter directamente.

Para mí, parece que no estamos de acuerdo sobre la importancia del problema y el papel de Google en la solución de ese problema.
Si ambas partes no están de acuerdo en esto, no veo cómo podemos tener una discusión productiva sobre cómo abordar este problema, cualquiera que sea la solución.

Este tema fue una lectura muy interesante y me alegra ver que el intercambio de puntos de vista se ha mantenido civilizado. Sin embargo, estoy un poco sorprendido por el estancamiento actual.

Cuando se trata de ganchos, mi opinión es que, si bien Flutter no necesita necesariamente la solución de ganchos específica presentada por @rrousselGit , tampoco lo está diciendo. Flutter necesita una solución que ofrezca beneficios similares a los de Hooks, exactamente por todas las razones que mencionan Remi y otros defensores. @ emanuel-lundman resumió bien los argumentos anteriores y estoy de acuerdo con sus puntos de vista.

A falta de otras propuestas viables que ofrezcan las mismas capacidades y dado que los ganchos tienen un historial bien probado en React, y que existe una solución existente en la que podría basarse para Flutter, no creo que lo sea. una mala elección para hacerlo. No creo que el concepto de ganchos, como un elemento primitivo que también se incluye en el SDK de Flutter (o incluso en una versión inferior), le quitaría nada a Flutter. En mi opinión, solo lo enriquecería y facilitaría aún más el desarrollo de aplicaciones agradables y fáciles de mantener con Flutter.

Si bien el argumento de que los ganchos está disponible como un paquete para aquellos que quieren aprovechar sus beneficios, es un punto válido, creo que no es óptimo para un primitivo como los ganchos. He aquí por qué.

Muy a menudo, al hacer incluso paquetes reutilizables internamente, debatimos si el paquete debe ser "puro", en el sentido de que solo puede depender del SDK de Dart + Flutter, o si permitimos algunos otros paquetes en él y, de ser así, cuál unos. Incluso Provider busca paquetes "puros", pero a menudo se permite para paquetes de nivel superior. Para una aplicación siempre existe el mismo debate, qué paquetes están bien y cuáles no. El proveedor es verde, pero algo como Hooks sigue siendo un signo de interrogación como paquete.

Si una solución similar a ganchos fuera parte del SDK, sería una opción obvia utilizar las capacidades que ofrece. Si bien quiero usar Hooks y permitirlo ahora como paquete, también me preocupa que cree un estilo de código Flutter e introduzca conceptos que podrían no ser familiares para los desarrolladores de Flutter que no lo usan. Se siente un poco como una bifurcación en el camino si recorremos este camino sin soporte en el SDK. Para proyectos personales más pequeños, es una opción fácil usar Hooks. Recomiendo probarlo junto con Riverpod.

(Nuestro conservadurismo de paquetes proviene, supongo, de haber sido quemado por paquetes y el desorden de dependencia de otros administradores de paquetes en el pasado, probablemente no sea el único).

No estoy diciendo que los ganchos sean la única forma de resolver el problema actual, incluso si es la única solución demostrada que funciona hasta ahora. Sin duda, podría ser un enfoque interesante y válido investigar opciones a un nivel más genérico antes de comprometerse con una solución. Para que eso suceda, debe haber un reconocimiento de que Flutter SDK _actualmente tiene una falla cuando se trata de una lógica de estado fácil de reutilizar_, que a pesar de las explicaciones elaboradas actualmente no parece haber.

Para mí, hay dos razones principales para no solo poner Hooks en el marco principal. La primera es que la API contiene trampas peligrosas. Principalmente, si de alguna manera terminas llamando a los ganchos en el orden incorrecto, las cosas se romperán. Esto me parece un problema fatal. Entiendo que con disciplina y siguiendo la documentación puede evitarlo, pero en mi humilde opinión, una buena solución a este problema de reutilización de código no tendría ese defecto.

La segunda es que realmente no debería haber ninguna razón para que la gente no pueda usar Hooks (u otra biblioteca) para resolver este problema. Ahora, con Hooks específicamente eso no funciona, como la gente ha discutido, porque escribir el gancho es lo suficientemente pesado como para que la gente desee que las bibliotecas no relacionadas admitan los hooks. Pero creo que una buena solución a este problema no necesitaría eso. Una buena solución sería independiente y no necesitaría que todas las demás bibliotecas la conozcan.

Recientemente agregamos RestorableProperties al marco. Sería interesante ver si podrían aprovecharse aquí de alguna manera ...

Estoy de acuerdo con @Hixie en que la API tiene problemas ocultos que requieren un analizador o linter para resolverlos. Creo que nosotros, como quien quiera participar, deberíamos buscar varias soluciones, tal vez a través de la sugerencia del documento de diseño, o de otra manera, sobre el problema de la gestión del ciclo de vida de los reutilizables. Idealmente, sería más específico de Flutter y aprovecharía las API de Flutter al mismo tiempo que resolvería los problemas que hace la API de gancho. Creo que la versión de Vue es un buen modelo para comenzar, como mencioné antes, ya que no depende del orden de llamada de gancho. ¿Alguien más está interesado en investigar conmigo?

@Hixie, pero ¿está de acuerdo con el problema de que no hay una buena manera de reutilizar la lógica de estado de una manera componible entre widgets? ¿Es por eso que comenzó a pensar en aprovechar ResuableProperties de alguna manera?

Para mí, hay dos razones principales para no solo poner Hooks en el marco principal. La primera es que la API contiene trampas peligrosas. Principalmente, si de alguna manera terminas llamando a los ganchos en el orden incorrecto, las cosas se romperán. Esto me parece un problema fatal. Entiendo que con disciplina y siguiendo la documentación puede evitarlo, pero en mi humilde opinión, una buena solución a este problema de reutilización de código no tendría ese defecto.

Por haber trabajado con ganchos y haber trabajado con otras personas que usan ganchos, esto realmente no es un gran problema en mi humilde opinión. Y en absoluto en comparación con todas las grandes ganancias (grandes ganancias en velocidad de desarrollo, reutilización, componibilidad y código fácilmente legible) que aportan.
Un gancho es un gancho, como una clase es una clase, no solo una función, y no se puede usar condicionalmente. Aprendes tan rápido. Y su editor también puede ayudar con este problema.

La segunda es que realmente no debería haber ninguna razón para que la gente no pueda usar Hooks (u otra biblioteca) para resolver este problema. Ahora, con Hooks específicamente eso no funciona, como la gente ha discutido, porque escribir el gancho es lo suficientemente pesado como para que la gente desee que las bibliotecas no relacionadas admitan los hooks. Pero creo que una buena solución a este problema no necesitaría eso. Una buena solución sería independiente y no necesitaría que todas las demás bibliotecas la conozcan.

Los ganchos de escribir no son una carga.
Todavía es más fácil que las soluciones disponibles ahora en mi humilde opinión (para usar esa frase de nuevo 😉).
Quizás estoy malinterpretando lo que estás escribiendo. ¿Pero no creo que nadie haya dicho eso?
Lo leí como si la gente realmente apreciara todas las ganancias que aporta la solución de gancho y desearía poder usarla en todas partes. Para cosechar todos los beneficios. Dado que un gancho es reutilizable, sería genial si los desarrolladores externos pudieran sentirse seguros para codificar y enviar sus propios ganchos sin que todos tengan que escribir sus propios contenedores. Obtenga el beneficio de la reutilización de la lógica estatal.
Creo que @rrousselGit y @gaearon ya han explicado lo primitivo. Así que no entraré en eso.
Tal vez no entiendo esta declaración porque no veo que sea un buen resumen de lo que la gente ha escrito en este hilo. Lo siento.

Espero que haya un camino a seguir. Pero creo que es hora de al menos estar de acuerdo en que esto es un problema y seguir adelante con soluciones alternativas que sean mejores, ya que los ganchos no parecen estar ni siquiera sobre la mesa.
O simplemente decida omitir la solución del problema en Flutter Core.

¿Quién decide el camino a seguir?
Cual es el siguiente paso?

Esto me parece un problema fatal. Entiendo que con disciplina y siguiendo la documentación puede evitarlo, pero en mi humilde opinión, una buena solución a este problema de reutilización de código no tendría ese defecto.

En React, resolvemos esto con un linter - análisis estático. En nuestra experiencia, esta falla no ha sido importante incluso en una base de código grande. Hay otros problemas que podríamos considerar fallas, pero solo quería señalar que si bien la dependencia del orden de llamadas persistente es lo que la gente piensa intuitivamente que será un problema, el equilibrio termina siendo bastante diferente en la práctica.

Sin embargo, la verdadera razón por la que estoy escribiendo este comentario es que Flutter usa un lenguaje compilado. "Linting" no es opcional. Por lo tanto, si existe una alineación entre el lenguaje anfitrión y el marco de la interfaz de usuario, definitivamente es posible hacer cumplir ese problema "condicional" que nunca surge de manera estática. Pero eso solo funciona cuando el marco de la interfaz de usuario puede motivar cambios de idioma (por ejemplo, Compose + Kotlin).

@Hixie, pero ¿está de acuerdo con el problema de que no hay una buena manera de reutilizar la lógica de estado de una manera componible entre widgets? ¿Es por eso que comenzó a pensar en aprovechar ResuableProperties de alguna manera?

Ciertamente es algo que la gente ha mencionado. No es algo con lo que tenga una experiencia visceral. No es algo que haya sentido que sea un problema al escribir mis propias aplicaciones con Flutter. Sin embargo, eso no significa que no sea un problema real para algunas personas.

Dado que un gancho es reutilizable, sería genial si los desarrolladores externos pudieran sentirse seguros para codificar y enviar sus propios ganchos sin requerir que todos escriban sus propios contenedores.

Mi punto es que una buena solución aquí no requeriría que nadie escribiera envoltorios.

Cual es el siguiente paso?

Hay muchos pasos siguientes, por ejemplo:

  • Si hay problemas específicos con Flutter de los que no hemos hablado aquí, archiva los problemas y describe los problemas.
  • Si tiene una buena idea sobre cómo resolver el problema de este problema de una manera mejor que los Hooks, cree un paquete que lo haga.
  • Si hay cosas que se pueden hacer para mejorar Hooks, hágalo.
  • Si hay problemas con Flutter que impiden que Hooks alcance su máximo potencial, archívelos como problemas nuevos.
    etc.

Este tema fue una lectura muy interesante y me alegra ver que el intercambio de puntos de vista se ha mantenido civilizado.

Odiaría ver cómo se ve un hilo descortés entonces. Hay tan poca empatía en este hilo que ha sido difícil de leer y seguir desde el margen.

Mi punto es que una buena solución aquí no requeriría que nadie escribiera envoltorios.

Sin embargo, no tienes que escribir envoltorios. Pero es posible que desee obtener los beneficios y las cosas de reutilización en su propio código al que se ha acostumbrado. Seguro que todavía puedes usar las bibliotecas tal como están. Si escribe algo que envuelva un gancho (si es posible) probablemente no sea porque crea que es una carga, sino que es mejor que la alternativa.

En realidad, esa es una buena razón y una razón mencionada por la cual una solución al problema en este hilo sería excelente en el núcleo. Una solución de lógica de estado componible reutilizable en el núcleo significaría que la gente no tendría que escribir envoltorios, ya que dicha lógica reutilizable podría enviarse de manera segura en todos los paquetes sin agregar dependencias.

Una solución de lógica de estado componible reutilizable en el núcleo significaría que la gente no tendría que escribir envoltorios, ya que dicha lógica reutilizable podría enviarse de manera segura en todos los paquetes sin agregar dependencias.

El punto que estoy tratando de hacer es que, en mi humilde opinión, una buena solución no requeriría que nadie escribiera esa lógica. Simplemente no habría ninguna lógica redundante para reutilizar. Por ejemplo, mirando el ejemplo "fetchUser" de antes, nadie tendría que escribir un gancho, o su equivalente, para llamar a la función "fetchUser", simplemente llamaría directamente a la función "fetchUser". De manera similar, "fetchUser" no necesitaría saber nada sobre los ganchos (o lo que sea que usemos) y los ganchos (o lo que sea que usemos) no necesitaría saber nada sobre "fetchUser". Todo mientras mantienes la lógica de que escribes trivial, como ocurre con los ganchos.

Las restricciones actuales se deben al hecho de que los ganchos son un parche en la parte superior de las limitaciones del idioma.

En algunos lenguajes, los ganchos son una construcción del lenguaje, como:

state count = 0;

return RaisedButton(
  onPressed: () => count++,
  child: Text('clicked $count times'),
)

Esta sería una variante de las funciones asíncronas / sincronizadas , que pueden conservar algún estado en todas las llamadas.

Ya no requiere un uso no condicional ya que, como parte del lenguaje, podemos diferenciar cada variable por su número de línea en lugar de por su tipo.

Agregaría que las limitaciones de los ganchos son similares a las limitaciones de --track-widget-creation.

Esta bandera rompe la canonalización constante del constructor para widgets. Pero eso no es un problema ya que los widgets son declarativos.

En ese sentido, los ganchos son iguales. Las limitaciones realmente no importan, ya que se manipulan de forma declarativa.
No obtendremos un gancho muy específico sin leer los demás.

Quizás el ejemplo de fetchuser no sea el ideal.
Pero useStream, useAnimstion o useStreamCintroller hacen que el árbol de widgets sea mucho más limpio y evita que se olvide de desechar o cuidar dudChangeDependencues.
Por lo tanto, la forma actual tiene trampas en las que puede quedar atrapado. Así que supongo que el problema potencial con la secuencia de llamadas no es tan grande como esos.
No estoy seguro de si empezaría a escribir mis propios ganchos, pero sería bueno tener una colección de los que a menudo se necesitan listos para usar dentro del marco.
Solo sería una forma alternativa de lidiar con ellos.

@Hixie , realmente lo siento por no poder comprender lo que estás tratando de describir, lo culpo de que sea tarde en la noche aquí, pero probablemente sea solo yo 😳 .. Pero en la buena solución que describes, ¿dónde estarían los valores estatales, ¿Reside la lógica de negocios estatal y la lógica de eventos de por vida que la solución al problema envuelve / encapsula para que se pueda componer y compartir fácilmente entre los widgets? ¿Podría explicar un poco qué hace una buena solución y cómo cree que funcionaría idealmente?

Solo intervengo aquí un poco, viendo que hay menciones sobre la cortesía de esta discusión. Personalmente, no creo que nadie aquí esté siendo descortés.

Dicho esto, creo que vale la pena señalar que este es un tema que preocupa profundamente a la gente, en todos los lados.

  • @rrousselGit ha estado respondiendo preguntas de principiantes sobre la administración estatal en StackOverflow y en el rastreador de problemas package:provider durante años. Estoy siguiendo solo la última de estas mangueras, y no tengo nada más que respeto por la diligencia y paciencia de Remi.
  • @Hixie y otros en el equipo de Flutter se preocupan profundamente por la API de Flutter, su estabilidad, superficie, facilidad de mantenimiento y legibilidad. Es gracias a esto que la experiencia de desarrollador de Flutter está donde está hoy.
  • Los desarrolladores de Flutter se preocupan profundamente por la gestión estatal, porque eso es lo que hacen una parte importante de su tiempo de desarrollo.

Está claro que todas las partes en esta discusión tienen una buena razón para defender lo que hacen. También es comprensible que se necesite tiempo para transmitir los mensajes.

Entonces, estaría feliz si la discusión continuara, ya sea aquí o de alguna otra forma. Si puedo ayudar de alguna manera, como con un documento formal, hágamelo saber.

Si, por otro lado, la gente piensa que la discusión aquí se está saliendo de control, hagamos una pausa y veamos si hay una mejor manera de comunicarnos.

(Por otra parte, quiero agradecer a

@ emanuel-lundman

Pero en la buena solución que describe, ¿dónde residirían los valores de estado, la lógica de negocios del estado y la lógica de eventos de por vida que la solución al problema envuelve / encapsula para que se pueda componer y compartir fácilmente entre los widgets? ¿Podría explicar un poco qué hace una buena solución y cómo cree que funcionaría idealmente?

Desafortunadamente, no puedo dar más detalles porque no lo sé. :-)

@escamoteur

Quizás el ejemplo de fetchuser no sea el ideal.
Pero useStream, useAnimstion o useStreamCintroller hacen que el árbol de widgets sea mucho más limpio y evita que se olvide de desechar o cuidar dudChangeDependencues.

Una de las dificultades en este tema ha sido "mover los postes de la portería" donde se describe un problema, luego cuando se analiza, el problema se descarta como no el problema real y se describe un nuevo problema, y ​​así sucesivamente. Lo que podría ser realmente útil sería proponer algunos ejemplos canónicos, por ejemplo, una aplicación de demostración de Flutter que tenga un ejemplo real del problema, uno que no esté demasiado simplificado en aras de la exposición. Entonces podríamos volver a implementar eso usando Hooks y otras propuestas, y realmente evaluarlas unas contra otras en términos concretos. (Haría esto yo mismo, excepto que realmente no entiendo exactamente cuál es el problema, por lo que probablemente sea mejor si alguien que defiende a Hooks lo haga).

Lo que podría ser realmente útil sería proponer algunos ejemplos canónicos, por ejemplo, una aplicación de demostración de Flutter que tenga un ejemplo real del problema, uno que no esté demasiado simplificado por el bien de la exposición.

¿Qué opinas del ejemplo que di aquí? https://github.com/flutter/flutter/issues/51752#issuecomment -669626522

Este es un fragmento de código del mundo real.

Creo que sería un gran comienzo. ¿Podemos ponerlo en un estado en el que se ejecute como una aplicación independiente, con una versión que no usa ganchos y una versión que sí?

Lo siento, me refiero a un fragmento de código, no a una aplicación.

Creo que uno de los problemas con la idea de la "aplicación demo de Flutter" es que los ejemplos hechos en este hilo son muy reales.
No están demasiado simplificados.
El caso de uso principal de los ganchos es factorizar microestados, como supresión de rebotes, controladores de eventos, suscripciones o efectos secundarios implícitos, que se combinan para lograr lógicas más útiles.

Tengo algunos ejemplos en Riverpod, como https://marvel.riverpod.dev/#/ donde el código fuente está aquí: https://github.com/rrousselGit/river_pod/tree/master/examples/marvel/lib
Pero eso no va a ser muy diferente de lo que se mencionó hasta ahora.

@Hixie

Realmente tengo problemas para entender por qué esto es un problema. He escrito muchas aplicaciones de Flutter, pero ¿realmente no parece un gran problema? Incluso en el peor de los casos, son cuatro líneas para declarar una propiedad, inicializarla, desecharla e informarla a los datos de depuración (y en realidad suele ser menos, porque normalmente puede declararla en la misma línea en la que la inicializa, las aplicaciones en general no necesita preocuparse por agregar estado a las propiedades de depuración, y muchos de estos objetos no tienen un estado que deba eliminarse).

Estoy en el mismo barco.
Lo admito, tampoco entiendo realmente los problemas descritos aquí. Ni siquiera entiendo cuál es la "lógica estatal" a la que se refiere la gente, que necesita ser reutilizable.

Tengo muchos widgets de formulario con estado, algunos que tienen decenas de campos de formulario, y tengo que administrar los controladores de texto y los nodos de enfoque yo mismo. Los creo y elimino en los métodos de ciclo de vida del widget sin estado. Si bien es bastante tedioso, no tengo ningún widget que use la misma cantidad de controladores / focusNodes o para el mismo caso de uso. Lo único en común entre ellos es el concepto general de ser estado y ser una forma. El hecho de que sea un patrón no significa que el código se repita.
Quiero decir, en muchas partes de mi código tengo que recorrer las matrices, no llamaría hacer "para (var cosa en las cosas)" en todo el código de repetición de mi aplicación.

Me encanta el poder de StatefulWidget que proviene de la simplicidad de su API de ciclo de vida. Me permite escribir StatefulWidgets que hacen una cosa y lo hacen de forma aislada del resto de la aplicación. El "estado" de mis widgets es siempre privado para ellos mismos, por lo que reutilizar mis widgets no es un problema, ni tampoco lo es la reutilización de código.

Tengo un par de problemas con los ejemplos presentados aquí, que están de alguna manera en línea con sus puntos:

  • La creación de múltiples widgets con estado con la misma "lógica de estado" parece simplemente incorrecta y contraria a la idea de tener widgets autónomos. Pero de nuevo, estoy confundido en cuanto a lo que la gente entiende por "lógica estatal" común.
  • los ganchos no parecen hacer nada que yo no pueda hacer usando dardos simples y conceptos básicos de programación (como funciones)
  • los problemas parecen estar relacionados o causados ​​por un cierto estilo de programación, un estilo que parece favorecer el "estado global reutilizable".
  • abstraer un par de líneas de código huele a "optimización de código prematura" y agrega complejidad para resolver un problema que tiene poco o nada que ver con el marco y mucho que ver con la forma en que la gente lo usa.

Esto es significativamente menos legible.

  • Tenemos 10 niveles de sangría - 12 si hacemos un FilterBuilder para reutilizar la lógica del filtro
  • La lógica del filtro no es reutilizable tal como está.

    • es posible que olvidemos cancelar el timer por error
  • la mitad del método build no es útil para el lector. The Builders nos distrae de lo que importa
  • Perdí unos buenos 5 minutos tratando de entender por qué el código no se compila debido a que falta un paréntesis

Su ejemplo es más una muestra de cuán detallado es Provider y por qué abusar de InheritedWidgets para todo es algo malo, en lugar de un problema real con la API StatelessWidget y State Lifecycle de Flutter.

@rrousselGit Lo siento si no estaba claro. La sugerencia que estaba haciendo anteriormente fue específicamente crear aplicaciones vanilla Flutter (usando StatefulWidget, etc.) que sean realistas y muestren los problemas que está describiendo, para que luego podamos hacer propuestas basadas en una aplicación completa real. Los ejemplos concretos que hemos discutido aquí, como el ejemplo de "fetchUser", siempre han terminado con una discusión del tipo "bueno, podría manejar ese caso de esta manera y sería simple y no necesitaría Hooks". por "bueno, eso está muy simplificado, en el mundo real necesitas Hooks". Entonces, mi punto es, creemos un ejemplo del mundo real que realmente _necesita Hooks, que no esté demasiado simplificado, que muestre la dificultad de reutilizar el código, de modo que podamos ver si es posible evitar estos problemas sin usar ningún código nuevo , o si necesitamos código nuevo, y en el último caso, si tiene que tener la forma de Hooks o si podemos encontrar una solución aún mejor.

Ni siquiera entiendo cuál es la "lógica estatal" a la que se refiere la gente, que necesita ser reutilizable.

Lo suficientemente justo
Un paralelo a la lógica de estado sería la lógica de la interfaz de usuario y lo que aportan los widgets.

Técnicamente, podríamos eliminar la capa de widgets. En esa situación, lo que quedaría sería RenderObjects.

Por ejemplo, podríamos tener un mostrador minimalista:

var counter = 0;

final counterLabel = RenderParagraph(
  TextSpan(text: '$counter'),
  textDirection: TextDirection.ltr,
);

final button = RenderPointerListener(
  onPointerUp: (_) {
    counter++;
    counterLabel.text = TextSpan(text: '$counter');
  },
  child: counterLabel,
);

Eso no es necesariamente complejo. Pero es propenso a errores. Tenemos un duplicado en el renderizado counterLabel

Con los widgets, tenemos:

class _CounterState exends State {
  int counter = 0;

  Widget build(context ) {
    return GestureDetector(
      onTap: () => setState(() => counter++);
      child: Text('$counter'),
    );
  }
}

Lo único que hizo fue factorizar la lógica Text , haciéndola declarativa.
Es un cambio minimalista. Pero en una base de código grande, eso es una simplificación significativa .

Los ganchos hacen exactamente lo mismo.
Pero en lugar de Text , obtienes ganchos personalizados para las lógicas estatales. Que incluye oyentes, eliminación de rebotes, realización de solicitudes HTTP, ...


Su ejemplo es más una muestra de cuán detallado es Provider y por qué abusar de InheritedWidgets para todo es algo malo, en lugar de un problema real con la API StatelessWidget y State Lifecycle de Flutter.

Esto no está relacionado con el proveedor (este código no usa el proveedor después de todo).
En todo caso, el proveedor lo tiene mejor porque tiene context.watch lugar de Consumer .

La solución estándar sería reemplazar Consumer con ValueListenableBuilder , lo que conduce exactamente al mismo problema.

Estoy de acuerdo @Hixie , creo que necesitamos dos comparaciones lado a lado para juzgar la efectividad de solo Flutter versus con ganchos. Esto también ayudaría a convencer a los demás de si los hooks son mejores o no, o tal vez otra solución es incluso mejor si la aplicación básica se crea con esta tercera solución. Este concepto de aplicación de vainilla ha existido por un tiempo, con cosas como TodoMVC que muestran la diferencia entre varios marcos de front-end, por lo que no es necesariamente nuevo. Puedo ayudar con la creación de estas aplicaciones de ejemplo.

@satvikpendem
Estaría dispuesto a ayudar.
Creo que la aplicación de ejemplo en el repositorio flutter_hooks probablemente muestre varios ganchos diferentes y lo que facilitan / los problemas que resuelven, y sería un buen punto de partida.

También creo que también podríamos utilizar algunos de los ejemplos y enfoques que se presentan en este número.

Actualización: el código está aquí, https://github.com/TimWhiting/local_widget_state_approaches
No estoy seguro del nombre correcto del repositorio, así que no asuma que ese es el problema que estamos tratando de resolver. He hecho la aplicación de contador básica en stateful y hooks. No tengo mucho tiempo más tarde esta noche, pero intentaré agregar más casos de uso que sean más ilustrativos de cuál podría ser el problema. Cualquiera que quiera contribuir, solicite acceso.

Los ejemplos concretos que hemos discutido aquí, como el ejemplo de "fetchUser", siempre han terminado con una discusión del tipo "bueno, podría manejar ese caso de esta manera y sería simple y no necesitaría Hooks". por "bueno, eso está muy simplificado, en el mundo real necesitas Hooks".

Estoy en desacuerdo. No creo haber visto algo así como "podrías manejar ese caso de esta manera" y estar de acuerdo en que el código resultante era mejor que la variante de gancho.

Mi punto desde el principio fue que si bien podemos hacer las cosas de manera diferente, el código resultante es propenso a errores y / o menos legible.
Esto también se aplica a fetchUser

Los ganchos hacen exactamente lo mismo.
Pero en lugar de Text , obtienes ganchos personalizados para las lógicas estatales. Que incluye oyentes, eliminación de rebotes, realización de solicitudes HTTP, ...

No, todavía no entiendo lo que se supone que es esta lógica estatal común. Quiero decir, tengo muchos widgets que leen desde una base de datos en sus métodos "initState / didUpdateDependency", pero no puedo encontrar dos widgets que hagan exactamente la misma consulta, por lo tanto, su "lógica" no es la misma.

Usando el ejemplo de hacer una solicitud HTTP. Suponiendo que tengo un "makeHTTPRequest (url, paramters)" en algún lugar de mi clase de servicio que algunos de mis widgets necesitan usar, ¿por qué debería usar un gancho en lugar de simplemente llamar al método directamente cuando lo necesite? ¿En qué se diferencia el uso de ganchos de las llamadas a métodos simples en este caso?

Oyentes. No tengo widgets que escuchen las mismas cosas. Cada uno de mis widgets es responsable de suscribirse a lo que necesiten y asegurarse de que se den de baja. Los ganchos pueden ser azúcar sintáctico para la mayoría de las cosas, pero dado que mis widgets no escucharían la misma combinación de objetos, los ganchos tendrían que ser "parametrizados" de alguna manera. Nuevamente, ¿en qué se diferencian los ganchos de una función simple y antigua?


Esto no está relacionado con el proveedor (este código no usa el proveedor después de todo).
En todo caso, el proveedor lo tiene mejor porque tiene context.watch lugar de Consumer .

¿Eh? Su contraejemplo de lo que se supone que debe resolver su HookWidget "ChatScreen" fue este:

<strong i="19">@override</strong>
  Widget build(BuildContext context) {
    return Consumer<Auth>(
      provider: authProvider,
      builder: (context, auth, child) {
        return Consumer<int>(
          provider: selectedChatProvider,
          builder: (context, chatId, child) {

¿Cómo es esto sin relación con el proveedor? Estoy confundido. No soy un experto en Provider, pero esto definitivamente parece un código que usa Provider.

Me gustaría insistir en el hecho de que este tema no se trata de estados complejos.
Se trata de pequeños incrementos que se pueden aplicar a todo el código base.

Si no estamos de acuerdo con el valor de los ejemplos que se dan aquí, una aplicación no aportará nada a la conversación, ya que no hay nada que podamos hacer con los ganchos que no podamos hacer con StatefulWidget.

Mi recomendación sería comparar micro-fragmentos uno al lado del otro como ImplicitFetcher , y _objetivamente_ determinar qué código es mejor usando métricas medibles, y hacerlo para una amplia variedad de pequeños fragmentos.


¿Cómo es esto sin relación con el proveedor? Estoy confundido. No soy un experto en Provider, pero esto definitivamente parece un código que usa Provider.

Este código no es de Provider sino de un proyecto diferente que no usa InheritedWidgets.
El Consumer del proveedor no tiene un parámetro provider .

Y como mencioné, podría reemplazar Consumer -> ValueListenableBuilder / StreamBuilder / BlocBuilder / Observer / ...

en sus métodos "initState / didUpdateDependency", pero no puedo encontrar dos widgets que hagan exactamente la misma consulta, por lo tanto, su "lógica" no es la misma.

La lógica de estado que queremos reutilizar no es "hacer una consulta" sino "hacer algo cuando x cambie". El "hacer algo" puede cambiar, pero el "cuando x cambia" es común

Ejemplo concreto:
Es posible que deseemos que un widget realice una solicitud HTTP siempre que cambie el ID que recibe.
También queremos cancelar las solicitudes pendientes usando CancelableOperation de package:async .

Ahora, tenemos dos widgets que desean hacer exactamente lo mismo, pero con una solicitud HTTP diferente.
Al final, tenemos:

CancelableOperation<User> pendingUserRequest;

initState() {
  pendingUserRequest = fetchUser(widget.userId);
}

didUpdateWidget(oldWidget) {
  if (widget.userId != oldWidget.userId) {
      pendingUserRequest.cancel();
      pendingUserRequest = fetchUser(widget.userId);
  }
}

dispose() {
  pendingUserRequest.cancel();
}

VS:

CancelableOperation<Message> pendingMessageRequest;

initState() {
  pendingMessageRequest = fetchMessage(widget.messageId);
}

didUpdateWidget(oldWidget) {
  if (widget.userId != oldWidget.messageId) {
      pendingMessageRequest.cancel();
      pendingMessageRequest = fetchMessage(widget.messageId);
      message = pendingMessageRequest.value;
  }
}

dispose() {
  pendingMessageRequest.cancel();
}

La única diferencia es que cambiamos fetchUser por fetchMessage . Por lo demás, la lógica es 100% la misma. Pero no podemos reutilizarlo, que es propenso a errores.

Con los ganchos, podríamos factorizar esto en un gancho useUnaryCancelableOperation .

Lo que significa que con los mismos dos widgets haría en su lugar:

Widget build(context) {
  Future<User> user = useUnaryCancelableOperation(userId, fetchUser);
}

VS

Widget build(context) {
  Future<Message> message = useUnaryCancelableOperation(messageId, fetchMessage);
}

En este escenario, se comparte toda la lógica relacionada con la realización de la solicitud y su cancelación. Solo nos queda una diferencia significativa, que es fetchUser vs fetchMessage .
Incluso podríamos hacer un paquete con este useUnaryCancelableOperation , y ahora todos pueden reutilizarlo en su aplicación.

Si no estamos de acuerdo con el valor de los ejemplos que se dan aquí, una aplicación no aportará nada a la conversación, ya que no hay nada que podamos hacer con los ganchos que no podamos hacer con StatefulWidget.

Si ese es realmente el caso, supongo que deberíamos cerrar este error, porque ya hemos discutido los ejemplos que se dan aquí y no han sido convincentes. Sin embargo, realmente me gustaría comprender mejor la situación, y según los comentarios anteriores sobre este error, sonaba como si los beneficios estuvieran en el nivel de la aplicación, de ahí mi sugerencia de que estudiemos ejemplos de aplicaciones.

La única diferencia es que cambiamos fetchUser por fetchMessage . Por lo demás, la lógica es 100% la misma. Pero no podemos reutilizarlo, que es propenso a errores.

¿Qué es propenso a errores y qué hay para reutilizar? Implementar una capa completamente nueva de abstracción y jerarquía de clases solo para que no tengamos que implementar tres métodos en una clase y es demasiado.

Nuevamente, el hecho de que algo sea un patrón común no significa que deba crear una nueva función para él. Además, si desea reducir el código repetitivo en este caso, puede simplemente extender la clase StatefulWidget * y anular los métodos initstate / didUpdateWidget con los bits comunes.

Con los ganchos, podríamos factorizar esto en un gancho useUnaryCancelableOperation .

Lo que significa que con los mismos dos widgets haría en su lugar:

Widget build(context) {
  Future<User> user = useUnaryCancelableOperation(userId, fetchUser);
}

VS

Widget build(context) {
  Future<Message> message = useUnaryCancelableOperation(messageId, fetchMessage);
}

En este escenario, se comparte toda la lógica relacionada con la realización de la solicitud y su cancelación. Solo nos queda una diferencia significativa, que es fetchUser vs fetchMessage .
Incluso podríamos hacer un paquete con este useUnaryCancelableOperation , y ahora todos pueden reutilizarlo en su aplicación.

Lo siento, pero definitivamente eso es un gran no de mi parte. Aparte del hecho de que solo ahorra una pequeña cantidad de redundancia de código, "factorizar" un código que conceptualmente pertenece a los métodos del ciclo de vida "initState" y "actualizar", en el método de compilación es un gran no.

Espero que mis métodos de compilación solo compilen el diseño y nada más. Configurar y eliminar dependencias definitivamente no pertenece al método de compilación, y estoy bastante feliz de tener que reescribir explícitamente el mismo tipo de código para que sea explícito para mí y para otros lo que está haciendo mi widget. Y no peguemos todo en el método de construcción.

Si ese es realmente el caso, supongo que deberíamos cerrar este error.

@Hixie Por favor no lo hagas. La gente se preocupa por este tema. He hablado contigo en reddit sobre lo mismo , pero en el contexto de SwiftUI: https://github.com/szotp/SwiftUI_vs_Flutter

No se trata de ganchos, se trata de mejorar de alguna manera los widgets con estado. La gente simplemente odia escribir texto repetitivo. Para los desarrolladores que escriben código SwiftUI, que están acostumbrados a RAII y copian la semántica de Vistas, la administración manual de los desechables parece estar fuera de lugar.

Así que animo al equipo de Flutter a que al menos vea esto como un problema y piense en soluciones / mejoras alternativas.

Espero que mis métodos de compilación solo compilen el diseño y nada más. Configurar y eliminar dependencias definitivamente no pertenece al método de compilación,
Ese es un punto importante. Los métodos de construcción deben ser puros. Aún así, deseaba que pudiéramos tener las ventajas sin las dificultades.

Realmente no entiendo el impulso de más ejemplos aquí. Está claro en su rostro.

El problema resuelto por hooks es simple y obvio, mantiene el código SECO. Los beneficios de esto son los obvios, menos código == menos errores, el mantenimiento es más fácil, menos lugares para que se oculten los errores, menor recuento de líneas en general aumenta la legibilidad, los programadores junior están mejor aislados, etc.

Si está hablando de un caso de uso del mundo real, es una aplicación en la que está configurando y derribando 12 controladores de animador en 12 vistas diferentes, cada vez, dejando abierta la puerta para perder una llamada dispose () o anulando algunas otro método de ciclo de vida. luego aplíquelo a docenas de otras instancias con estado, y verá fácilmente cientos o miles de líneas de código sin sentido.

Flutter está lleno de estos casos en los que necesitamos repetirnos constantemente, configurando y derribando el estado de pequeños objetos, que crean todo tipo de oportunidades para errores que no necesitan existir, pero que existen, porque actualmente no hay un enfoque elegante para compartiendo esta lógica de configuración / desmontaje / sincronización de memoria.

Puede ver este problema literalmente en _cualquier_ estado que tenga una fase de configuración y desmontaje, o que tenga algún enlace de ciclo de vida al que siempre deba vincularse.

Yo mismo encuentro que usar widgets es el mejor enfoque, rara vez uso AnimatorController, por ejemplo, porque la configuración / desmontaje es tan molesto, detallado y propenso a errores, en lugar de usar TweenAnimationBuilder en todos los lugares que puedo. Pero este enfoque tiene sus limitaciones a medida que se llega a un mayor número de objetos con estado en una vista determinada, lo que obliga al anidamiento y la verbosidad donde realmente no debería requerirse ninguno.

@szotp No tengo ... Preferiría que establezcamos una o más aplicaciones de referencia que demuestren el problema para que podamos evaluar las soluciones. Lo haría yo mismo, pero no entiendo exactamente qué es lo que estamos tratando de resolver, así que no soy la persona adecuada para hacerlo.

Las aplicaciones de

@esDotDev Hemos discutido casos como este en este error hasta ahora, pero cada vez que lo hemos hecho, las soluciones distintas de Hooks se descartan porque no resuelven algún problema que no se incluyó en el ejemplo que abordó la solución. Por lo tanto, las descripciones simples de los problemas no parecen ser suficientes para captar el alcance completo. Por ejemplo, el caso de los "12 controladores de animación" podría resolverse mediante una serie de controladores de animación y las características funcionales de Dart. TweenAnimationBuilder podría ser otra solución. Ninguno de los dos involucra a Hooks. Pero estoy seguro de que si sugiero eso, alguien señalará algo que me he perdido y dirá "no funciona porque ..." y sacará a relucir ese (nuevo, en el contexto del ejemplo) problema. De ahí la necesidad de una aplicación básica que todos estemos de acuerdo que cubra toda la extensión del problema.

Si alguien quisiera impulsar esto, realmente creo que la mejor manera de hacerlo es lo que describí anteriormente (https://github.com/flutter/flutter/issues/51752#issuecomment-670249755 y https://github.com / flutter / flutter / issues / 51752 # issuecomment-670232842). Eso nos dará un punto de partida en el que todos podemos estar de acuerdo en que representa el alcance del problema que estamos tratando de resolver; entonces, podemos diseñar soluciones que aborden estos problemas de manera que todos los deseos de dirección (por ejemplo @rrousselGit Necesita para su reutilización, @Rudiksz 's necesidad de métodos de generación limpia, etc), y lo más importante que puede evaluar las soluciones en el contexto de las aplicaciones de línea de base.

Creo que todos podríamos estar de acuerdo con bastante facilidad en el problema:
_No hay una forma elegante de compartir las tareas de instalación / desmontaje asociadas con cosas como Streams, AnimatorControllers, etc. Esto conduce a una verbosidad innecesaria, aperturas para errores y una legibilidad reducida.

¿Alguien no está de acuerdo con eso? ¿No podemos empezar por ahí y seguir adelante en busca de una solución? Primero tenemos que estar de acuerdo en que es un problema central, que parece que todavía no lo hacemos.

Sin embargo, mientras escribo eso, me di cuenta de que coincide exactamente con el nombre del problema, que es abierto y deja espacio para la discusión:
" Reutilizar la lógica de estado es demasiado detallado o demasiado difícil "

Para mí, ese es un problema muy obvio, y deberíamos pasar rápidamente de la etapa de debate y hacer una lluvia de ideas sobre lo que funcionaría, si no los ganchos, entonces qué. Necesitamos micro-estados que sean reutilizables ... Estoy seguro de que podemos encontrar algo. Realmente limpiaría muchas vistas de Flutter al final del día y las haría más sólidas.

@Hixie Por favor no lo hagas. La gente se preocupa por este tema. He hablado contigo en reddit sobre lo mismo , pero en el contexto de SwiftUI: https://github.com/szotp/SwiftUI_vs_Flutter

Su ejemplo de SwiftUI se puede replicar en dardo en unas pocas líneas de código, simplemente extendiendo la clase StatefulWidget.

Tengo StatefulWidgets que no se suscriben a ningún notificador y / o hacen llamadas externas, y de hecho, la mayoría de ellos son así. Tengo alrededor de 100 widgets personalizados (aunque no todos con estado), y tal vez 15 de ellos tienen algún tipo de "lógica de estado común" como se describe en los ejemplos aquí.

A largo plazo, escribir unas pocas líneas de código (también conocido como repetitivo) es una pequeña compensación para evitar gastos generales innecesarios. Y nuevamente, este problema de tener que implementar los métodos initState / didUpdate parece demasiado exagerado. Cuando creo un widget que usa cualquiera de los patrones descritos aquí, quizás dedico los primeros 5-10 minutos a "implementar" los métodos del ciclo de vida y luego unos días escribiendo y puliendo el widget en sí mismo sin tocar dichos métodos del ciclo de vida. La cantidad de tiempo que dedico a escribir el código de instalación / desmontaje repetitivo es minúscula en comparación con el código de mi aplicación.

Como dije, el hecho de que StatefulWidgets haga tan pocas suposiciones sobre para qué se utilizan es lo que los hace tan poderosos y eficientes.

Agregar un nuevo tipo de widget a Flutter que subclasifique StatefulWidget (o no) para este caso de uso en particular estaría bien, pero no lo integremos en el StatefulWidget en sí. Tengo muchos widgets que no necesitan la sobrecarga que vendría con un sistema de "ganchos" o microestados.

@esDotDev Estoy de acuerdo en que ese es un problema al que se enfrentan algunas personas; Incluso propuse algunas soluciones anteriormente en este número (busque mis diversas versiones de una clase Property , podría estar enterrada ahora ya que a GitHub no le gusta mostrar todos los comentarios). La dificultad es que esas propuestas fueron descartadas porque no resolvieron problemas específicos (por ejemplo, en un caso, no manejó la recarga en caliente, en otro, no manejó didUpdateWidget). Entonces hice más propuestas, pero luego esas fueron nuevamente descartadas porque no manejaban otra cosa (me olvido de qué). Por eso es importante tener algún tipo de línea de base que estemos de acuerdo que representa el problema completo para que podamos encontrar soluciones para ese problema.

El objetivo nunca ha cambiado. Las críticas que se hacen son que las soluciones propuestas carecen de flexibilidad. Ninguno de ellos continúa funcionando fuera del fragmento para el que se implementaron.

Es por eso que el título de este número menciona "Difícil": porque actualmente no hay flexibilidad en la forma en que actualmente resolvemos los problemas.


Otra forma de verlo es:

Este problema básicamente está argumentando que necesitamos implementar una capa de widget, para la lógica de estado.
Las soluciones sugeridas son "Pero puedes hacerlo con RenderObjects".

_A la larga, escribir unas pocas líneas de código (también conocido como repetitivo) es una pequeña compensación para evitar gastos generales innecesarios.

Par de liendres con esta declaración:

  1. No son realmente unas pocas líneas, si toma corchetes, interlineado @overides , etc. en cuenta, puede estar buscando 10-15 + líneas para un controlador de animador simple. Eso no es trivial en mi mente ... como mucho más que trivial. 3 líneas para hacer esto me molestan (en Unity es Thing.DOTween() ). 15 es ridículo.
  1. No se trata de escribir, aunque eso es un fastidio. Se trata de la estupidez de tener una clase de 50 líneas, donde 30 líneas son repetitivas. Su ofuscación. Se trata del hecho de que si no escribes el texto estándar, no hay advertencias ni nada, simplemente agregaste un error.
  2. No veo ninguna sobrecarga que valga la pena discutir con alguien como Hooks. Estamos hablando de una serie de objetos, con un puñado de fxns en cada uno. En Dart, que es muy rápido. Esta es una pista falsa en mi opinión.

@esDotDev

Para mí, ese es un problema muy obvio, y deberíamos pasar rápidamente de la etapa de debate y hacer una lluvia de ideas sobre lo que funcionaría, si no los ganchos, entonces qué.

Ampliación de widgets. Al igual que la forma en que ValueNotifier extiende ChangeNotifier para simplificar un patrón de uso común, todos pueden escribir sus propios tipos de StatelessWidgets para sus casos de uso específicos.

Sí, estoy de acuerdo en que es un enfoque eficaz, pero deja mucho que desear. Si tengo 1 animador, entonces puedo usar un TweenAnimationBuilder. Genial, todavía son como 5 líneas de código, pero lo que sea. Funciona ... no DEMASIADO mal. ¿Pero si tengo 2 o 3? Ahora estoy en el infierno de anidación, si tengo un cpl otros objetos con estado por alguna razón, bueno, todo se convierte en un desastre de sangría, o estoy creando un widget muy específico que encapsula una colección aleatoria de configuración, actualización y desmontaje lógica.

Ampliación de widgets. Al igual que la forma en que ValueNotifier extiende ChangeNotifier para simplificar un patrón de uso común, todos pueden escribir sus propios tipos de StatelessWidgets para sus casos de uso específicos.

Puede ampliar solo una clase base a la vez. Que no escala

Los mixins son el próximo intento lógico. Pero como menciona el OP, tampoco escalan.

@esDotDev

o estoy creando un widget muy específico que encapsula una colección aleatoria de lógica de configuración, actualización y desmontaje.

Un tipo de widget que tiene que configurar 3-4 tipos de AnimationControllers suena como un caso de uso muy específico y el soporte de una colección aleatoria de lógica de instalación / desmontaje definitivamente no debería ser parte de un marco. De hecho, es por eso que los métodos initState / didUpdateWidget están expuestos en primer lugar, para que pueda hacer su colección aleatoria de configuración según sus deseos.

Mi método initState más largo es de 5 líneas de código, mis widgets no sufren de un anidamiento excesivo, por lo que definitivamente tenemos diferentes necesidades y casos de uso. O un estilo de desarrollo diferente.

@esDotDev

3. No veo ninguna sobrecarga que valga la pena discutir con alguien como Hooks. Estamos hablando de una serie de objetos, con un puñado de fxns en cada uno. En Dart, que es muy rápido. Esta es una pista falsa en mi opinión.

Si la solución propuesta es algo como el paquete flutter_hooks, eso es totalmente falso. Sí, conceptualmente es una matriz con funciones, pero la implementación no es ni de lejos trivial o eficiente.

Quiero decir, puedo estar equivocado, pero parece que HookElement comprueba si debería reconstruirse a sí mismo en su propio método de compilación.
También comprobar si los ganchos deben inicializarse, reiniciarse o eliminarse en cada compilación de un widget parece una sobrecarga significativa. Simplemente no se siente bien, así que espero estar equivocado.

¿Tendría sentido tomar uno de los ejemplos de arquitectura de @brianegan como aplicación base para una comparación?

Si puedo intervenir aquí, no estoy seguro de si esto ya se dijo. Pero en React realmente no pensamos en el ciclo de vida con ganchos, y eso puede sonar aterrador si así es como está acostumbrado a construir componentes / widgets, pero aquí está la razón por la que el ciclo de vida realmente no importa.

La mayoría de las veces, cuando está construyendo componentes / widgets con estado o acciones para realizar en función de los accesorios, desea que algo suceda cuando ese estado / accesorios cambie (por ejemplo, como vi mencionado en este hilo, querrá volver a -obtener los detalles de un usuario cuando la propiedad userId ha cambiado). Por lo general, es mucho más natural pensar en eso como un "efecto" del cambio de ID de usuario, en lugar de algo que sucede cuando cambian todos los accesorios del Widget.

Lo mismo para la limpieza, por lo general es mucho más natural pensar "Necesito limpiar este estado / escucha / controlador cuando esta propiedad / estado cambia" en lugar de "Necesito recordar limpiar X cuando todas las propiedades / estado cambian o cuando todo el componente se destruye ".

No he escrito Flutter por un tiempo, así que no estoy tratando de sonar como si conociera el clima actual o las limitaciones que este enfoque tendría en la mentalidad actual de Flutter, estoy abierto a diferentes opiniones. Creo que muchas personas que no están familiarizadas con los ganchos de React están teniendo la misma confusión que yo tenía cuando me los presentaron porque mi mentalidad estaba muy arraigada en el paradigma del ciclo de vida.

@escamoteur @Rudiksz @Hixie ha habido un proyecto de GitHub creado por @TimWhiting al que me han invitado donde estamos comenzando a crear estos ejemplos. Cada persona / grupo puede crear cómo resolvería un problema predefinido. No son aplicaciones completas, más bien páginas, pero también podemos agregar aplicaciones, si sirven para mostrar ejemplos más complejos. Entonces podemos discutir problemas y crear una mejor API. @TimWhiting puede invitar a cualquier persona interesada, supongo.

https://github.com/TimWhiting/local_widget_state_approaches

Jetpack compose también es similar a los ganchos, que se comparó con reaccionar aquí .

@satvikpendem @TimWhiting ¡ Eso es genial! Gracias.

@esDotDev
Un caso de uso muy específico y el soporte de una colección aleatoria de lógica de configuración / desmontaje definitivamente no deberían ser parte de un marco.

Este es el clavo que pega en la cabeza. Cada tipo de objeto es responsable de su propia configuración y desmontaje. Los animadores saben cómo crearse, actualizarse y destruirse a sí mismos, al igual que las transmisiones, etc. Hooks resuelve específicamente este problema de 'colecciones aleatorias' de andamios estatales esparcidos por su vista. Permite que la mayor parte del código de vista se centre en la lógica empresarial y el formato de diseño, lo cual es una ventaja. No lo obliga a crear widgets personalizados, solo para ocultar un texto estándar genérico que es el mismo en todos los proyectos.

Mi método initState más largo es de 5 líneas de código, mis widgets no sufren de un anidamiento excesivo, por lo que definitivamente tenemos diferentes necesidades y casos de uso. O un estilo de desarrollo diferente.

Mío también. Pero es initState () + dispose () + didUpdateDependancies (), y faltar cualquiera de los dos últimos puede causar errores.

Creo que el ejemplo canónico sería algo como: Escriba una vista que use 1 controlador de flujo y 2 controladores de animador.

Tienes 3 opciones por lo que puedo ver:

  1. Agregue aproximadamente 30 líneas de texto repetitivo a su clase y algunos mixins. Lo cual no solo es detallado, sino que inicialmente es bastante difícil de seguir.
  2. Use 2 TweenAnimationBuilders y un StreamBuilder, para aproximadamente 15 niveles de sangría, incluso antes de llegar a su código de vista, y todavía tiene un montón de texto estándar para Stream.
  3. Agregue como 6 líneas de código sin sangría en la parte superior de build (), para obtener sus 3 subobjetos con estado y defina cualquier código personalizado de inicio / destrucción

Tal vez haya una cuarta opción que sea SingleStreamBuilderDoubleAnimationWidget, pero esto es solo una cosa de trabajo para los desarrolladores y bastante molesto en general.

También vale la pena señalar que la carga cognitiva de 3 es significativamente menor que la de los otros 2 para un nuevo desarrollador. La mayoría de los desarrolladores nuevos ni siquiera saben que existe TweenAnimationBuilder, y simplemente aprender el concepto de SingleTickerProvider es una tarea en sí misma. Simplemente decir, "Dame un animador, por favor", es un enfoque más sencillo y sólido.

Intentaré codificar algo hoy.

2. Utilice 2 TweenAnimationBuilders y un StreamBuilder, para aproximadamente 15 niveles de sangría, incluso antes de llegar a su código de vista, y todavía tiene un montón de texto estándar para Stream.

Derecha. Muéstranos un ejemplo de código del mundo real que usa 15 niveles de sangría.

¿Cómo se reduce la carga cognitiva al reemplazar 30 líneas de código con 6 líneas + cientos de líneas en una biblioteca? Sí, puedo ignorar la "magia" que hace la biblioteca, pero no sus reglas. Por ejemplo, el paquete de ganchos le dice en términos inequívocos que los ganchos deben usarse solo en los métodos de construcción. Ahora tiene una restricción adicional de la que preocuparse.

Probablemente tengo menos de 200 líneas de código que involucran focusnodes, textcontrollers, singletickerproviders o los diversos métodos de ciclo de vida de mis statefulwidgets, en un proyecto con 15k líneas de código. ¿De qué sobrecarga cognitiva estás hablando?

@Rudiksz por favor deja de ser pasivo-agresivo.
Podemos estar en desacuerdo sin pelear.


Las limitaciones de Hooks son la menor de mis preocupaciones.

No estamos hablando de ganchos específicamente, sino del problema.
Si es necesario, podemos encontrar una solución diferente.

Lo que importa es el problema que queremos resolver.

Además, los widgets solo se pueden usar dentro de la construcción e incondicionalmente (o estamos cambiando la profundidad del árbol, lo cual es imposible)

Eso es idéntico a las limitaciones de los ganchos, pero no creo que la gente se queje de ello.

Además, los widgets solo se pueden usar dentro de la construcción e incondicionalmente (o estamos cambiando la profundidad del árbol, lo cual es imposible)

Eso es idéntico a las limitaciones de los ganchos, pero no creo que la gente se queje de ello.

No, no es idéntico. El problema que se presenta aquí parece estar relacionado con el código que _preparará_ los widgets _antes_ de ser (re) construido. Preparar estados, dependencias, flujos, controladores, todo eso y manejar cambios en la estructura del árbol. Ninguno de estos debería estar en el método de compilación, incluso si está oculto detrás de una única llamada de función.
El punto de entrada para esa lógica nunca debería estar en el método de construcción.

Obligarme a poner lógica de inicialización de cualquier tipo en el método de compilación no es en ninguna parte lo mismo que "obligarme" a componer un árbol de widgets en el método de compilación. El motivo principal del método de compilación es tomar un estado existente (conjunto de variables) y producir un árbol de widgets que luego se pinta.

Por el contrario, también estaría en contra de obligarme a agregar código que construya widgets dentro de los métodos initState / didUpdateWidget.

Tal como está ahora, los métodos de ciclo de vida de statefulwidget tienen un papel muy claro y hacen que sea muy fácil y directo separar el código con preocupaciones completamente diferentes.

Conceptualmente estoy empezando a comprender los problemas que se describen aquí, pero sigo sin verlo como un problema real. Tal vez algunos ejemplos reales (que no sean la aplicación de contador) puedan ayudarme a cambiar de opinión.

Como nota al margen ,

Por ejemplo, resuelve:

Consumer(
  provider: provider,
  builder: (context, value) {
    return Consumer(
      provider: provider2,
      builder: (context, value2) {
        return Text('$value $value2');
      },
    );
  },
)

por tener:

Consumer(
  builder (context, watch) {
    final value = watch(provider);
    final value2 = watch(provider2);
  },
)

Donde watch se puede llamar condicionalmente:

Consumer(
  builder: (context, watch) {
    final value = watch(provider);
    if (something) {
      final value2 = watch(provider2);
    }
  },
)

Incluso podríamos deshacernos de Consumer por completo al tener una clase base personalizada StatelessWidget / StatefulWidget :

class Example extends ConsumerStatelessWidget {
  <strong i="21">@override</strong>
  Widget build(ConsumerBuildContext context) {
    final value = context.watch(provider);
    final value2 = context.watch(provider2);
  }
}

El problema principal es que esto es específico de un tipo de objeto y funciona basándose en el hecho de que la instancia del objeto tiene un código hash coherente en todas las reconstrucciones.

Entonces todavía estamos lejos de la flexibilidad de los ganchos.

@rrousselGit Creo que sin extender StatelessWidget / StatefulWidget clases y crear algo como ConsumerStatelessWidget, es posible tener algo como context.watch usando métodos de extensión en BuildContext class y que el proveedor proporcione la función de vigilancia con InheritedWidgets.

Ese es un tema diferente. Pero tl; dr, no podemos confiar en InheritedWidgets como solución a este problema: https://github.com/flutter/flutter/issues/30062

Para resolver ese problema, usar InheritedWidgets nos bloquearía debido a https://github.com/flutter/flutter/issues/12992 y https://github.com/flutter/flutter/pull/33213

Conceptualmente estoy empezando a comprender los problemas que se describen aquí, pero sigo sin verlo como un problema real.

Comparando Flutter con SwiftUI, para mí es obvio que hay un problema real, o más bien, las cosas no son tan buenas como podrían ser.

Puede ser difícil de ver, porque Flutter y otros trabajaron duro para solucionarlo: tenemos envoltorios para cada caso específico: AnimatedBuilder, StreamBuilder, Consumer, AnimatedOpacity, etc. StatefulWidget funciona muy bien para implementar estas pequeñas utilidades reutilizables, pero es un nivel demasiado bajo para componentes específicos de dominio no reutilizables, donde puede tener multitud de controladores de texto, animaciones o lo que requiera la lógica empresarial. La solución habitual es morder la bala y escribir todo ese texto repetitivo, o hacer un árbol cuidadosamente construido de proveedores y oyentes. Ninguno de los dos enfoques es satisfactorio.

Es como dice @rrousselGit , en los viejos tiempos (UIKit) nos vimos obligados a administrar manualmente nuestras UIViews (el equivalente de RenderObjects), y recordar copiar los valores del modelo a la vista y viceversa, eliminar las vistas no utilizadas, reciclar, etc. Esto no fue una ciencia espacial, y mucha gente no vio este viejo problema, pero creo que todos aquí estarían de acuerdo en que Flutter claramente mejoró la situación.
Con statefulness, el problema es muy similar: es un trabajo aburrido y propenso a errores que podría automatizarse.

Y, por cierto, no creo que los ganchos resuelvan esto en absoluto. Es solo que los ganchos son el único enfoque que es posible sin cambiar las partes internas de flutter.

StatefulWidget funciona muy bien para implementar estas pequeñas utilidades reutilizables, pero es un nivel demasiado bajo para componentes específicos de dominio no reutilizables, donde puede tener una multitud de controladores de texto, animaciones o lo que requiera la lógica empresarial.

Me confunde cuando dice que para construir sus componentes específicos de dominio no reutilizables necesita un widget de alto nivel. Por lo general, es exactamente lo contrario.

AnimatedBuilder, StreamBuilder, Consumer, AnimatedOpacity son todos widgets que implementan un caso de uso determinado. Cuando necesito un widget que tiene una lógica tan específica que no puedo usar ninguno de estos widgets de nivel superior , es cuando entro a una API de nivel inferior para poder escribir mi propio caso de uso específico. El llamado modelo estándar está implementando cómo mi widget único administra su combinación única de flujos, llamadas de red, controladores y todo eso.

Abogar por ganchos, comportamientos similares a ganchos o incluso simplemente "automatización" es como decir que necesitamos un widget de bajo nivel que pueda manejar una lógica no reutilizable de

Con statefulness, el problema es muy similar: es un trabajo aburrido y propenso a errores que podría automatizarse.

De nuevo. ¿Desea automatizar __ "componentes específicos de dominio no reutilizables, donde puede tener una multitud de controladores de texto, animaciones o cualquier lógica comercial que requiera" __ ?!

Es como dice @rrousselGit , en los viejos tiempos (UIKit) nos vimos obligados a administrar manualmente nuestras UIViews (el equivalente de RenderObjects), y recordar copiar los valores del modelo a la vista y viceversa, eliminar las vistas no utilizadas, reciclar, etc. Esto no fue una ciencia espacial, y mucha gente no vio este viejo problema, pero creo que todos aquí estarían de acuerdo en que Flutter claramente mejoró la situación.

Hice el desarrollo de iOS y Android hace 6-7 años (aproximadamente cuando Android cambió a su diseño de material) y no recuerdo que ninguna de estas vistas de gestión y reciclaje sea un problema y Flutter no parece ni mejor ni peor. No puedo hablar de la actualidad, lo dejé cuando se lanzaron Swift y Kotlin.

El texto estándar que me veo obligado a escribir en mis StatefulWidgets es aproximadamente el 1% de mi código base. ¿Es propenso a errores? Cada línea de código es una fuente potencial de errores, así que seguro. ¿Es engorroso? 200 líneas de código fuera de 15000? Realmente no lo creo, pero esa es solo mi opinión. Los controladores de texto / animación de Flutter, los nodos de enfoque tienen problemas que se pueden mejorar, pero ser detallado no lo es.

Tengo mucha curiosidad por ver qué están desarrollando las personas que necesitan tanto texto estándar.

Escuchar algunos de los comentarios aquí parece que administrar 5 líneas de código en lugar de 1 es como 5 veces más difícil. No es.

Sin embargo, ¿no estaría de acuerdo en que en lugar de configurar initState y dispose para cada AnimationController, por ejemplo, puede ser más propenso a errores que simplemente hacerlo una vez y reutilizar esa lógica? Mismo principio que el uso de funciones, reutilización. Estoy de acuerdo en que es problemático poner ganchos en la función de compilación, sin embargo, definitivamente hay una mejor manera.

Realmente parece que la diferencia entre aquellos que ven el problema y los que no ven el problema aquí es que los primeros han usado construcciones similares a ganchos antes, como en React, Swift y Kotlin, y los segundos no lo han hecho, como trabajar en Java puro. o Android. Creo que la única forma de estar convencido, en mi experiencia, es probar los ganchos y ver si puedes volver a la forma estándar. A menudo, muchas personas no pueden, de nuevo, en mi experiencia. Lo sabes cuando lo usas.

Con ese fin, animo a las personas escépticas a usar flutter_hooks para un proyecto pequeño y ver cómo le va, luego rehacerlo de la manera predeterminada. No es suficiente que simplemente creemos versiones de la aplicación para que uno lea como en la sugerencia de @Hixie (aunque también lo haremos, por supuesto), creo que cada persona debe usar los ganchos para ver la diferencia.

No es suficiente que simplemente creemos versiones de la aplicación para que uno lea como en la sugerencia de @Hixie (aunque también lo haremos, por supuesto), creo que cada persona debe usar los ganchos para ver la diferencia.

Perdí días probando el proveedor, incluso más días probando el bloque, no encontré que ninguno de ellos fuera una buena solución. Si funciona para ti, genial.

Con el fin para mí incluso intentar su propuesta de solución a un problema que usted está teniendo, es necesario demostrar sus ventajas. Miré ejemplos con ganchos de aleteo y miré su implementación. Simplemente no.

Cualquiera que sea el código de reducción repetitivo que se agregue al marco, espero que los Stateful / StatelessWidgets no se modifiquen. No hay mucho más que pueda agregar a esta conversación.

Empecemos de nuevo, en un mundo hipotético donde podemos cambiar a Dart, sin hablar de anzuelos.

El problema debatido es:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

Este código no es legible.

Podríamos solucionar el problema de legibilidad introduciendo una nueva palabra clave que cambia la sintaxis a:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

Este código es significativamente más legible, no está relacionado con los ganchos y no adolece de sus limitaciones.
La mejora de la legibilidad no depende mucho del número de líneas, sino del formato y las sangrías.


Pero, ¿qué pasa cuando el Builder no es el widget raíz?

Ejemplo:

Widget build(context) {
  return Scaffold(
    body: StreamBuilder(
      stream: stream,
      builder: (context, value) {
        return Consumer<Value2>(
          builder: (context, value2, child) {
            return Text('${value.data} $value2');
          },
        );
      },
    ),
  );
}

Nosotros podríamos tener:

Widget build(context) {
  return Scaffold(
    body: {
      final value = keyword StreamBuilder(stream: stream);
      final value2 = keyword Consumer<Value2>();
      return Text('${value.data} $value2');
    }
  );
}

Pero, ¿cómo se relaciona esto con el problema de la reutilización?

La razón por la que esto está relacionado es que los constructores son técnicamente una forma de reutilizar la lógica de estado. Pero su problema es que hacen que el código no sea muy legible si planeamos usar _many_ constructores, como en este comentario https://github.com/flutter/flutter/issues/51752#issuecomment -669626522

Con esta nueva sintaxis, solucionamos el problema de legibilidad. Como tal, podemos extraer más cosas en Builders.

Entonces, por ejemplo, el useFilter mencionado en este comentario podría ser:

FilterBuilder(
  debounceDuration: const Duration(seconds: 2),
  builder: (context, filter) {
    return TextField(onChange: (value) => filter.value = value);
  }
)

Que luego podemos usar con la nueva palabra clave:

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    final filter = keyword FilterBuilder(debounceDuration: const Duration(seconds: 2));
    final userId = keyword Consumer(authProvider).userId;
    final chatId = keyword Consumer(selectedChatProvider);
    final chat = keyword QueryBuilder(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

¿Qué pasa con la "extracción como función" de la que hablamos con ganchos, para crear ganchos / constructores personalizados?

Podríamos hacer lo mismo con dicha palabra clave, extrayendo una combinación de Constructores en una función:

Builder<Chat> ChatBuilder()  {
  final filter = keyword FilterBuilder(debounceDuration: const Duration(seconds: 2));
  final userId = keyword Consumer(authProvider).userId;
  final chatId = keyword Consumer(selectedChatProvider);
  final chat = keyword QueryBuilder(ChatQuery(userId: userId, chatId: chatId, filter: filter.value));

  return Builder(chat);
}

class ChatScreen extends HookWidget {
  const ChatScreen({Key key}) : super(key: key);

  <strong i="21">@override</strong>
  Widget build(BuildContext context) {
    final chat = keyword ChatBuilder();

    return Column(
      children: [
        Searchbar(onChanged: (value) => filter.value = value),
        Expanded(
          child: ChatList(chat: chat),
        ),
      ],
    );
  }
}

Obviamente, no se pensó mucho en todas las implicaciones de tal sintaxis. Pero esa es la idea básica.


Los ganchos son esta característica.
Las limitaciones de los ganchos existen porque se implementan como un paquete en lugar de una característica de idioma.

Y la palabra clave es use , de modo que keyword StreamBuilder convierte en use StreamBuilder , que finalmente se implementa como useStream

Este código es significativamente más legible

Creo que es una cuestión de opinión. Estoy de acuerdo en que algunas personas piensan que las versiones que usted describe como más legibles son mejores; personalmente prefiero las versiones sin magia mucho más explícitas. Pero no tengo ninguna objeción a hacer posible el segundo estilo.

Dicho esto, el siguiente paso es trabajar en la aplicación de @TimWhiting (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/stateful/counter.dart) para convertirlo en algo que tenga todos los problemas que queremos resolver.

Por lo que vale, https://github.com/flutter/flutter/issues/51752#issuecomment -670959424 es bastante similar a la inspiración para Hooks en React. El patrón Builder parece idéntico al patrón Render Props que solía prevalecer en React (pero conducía a árboles igualmente profundos). Más tarde, @trueadm sugirió azúcar de sintaxis para Render Props, y más tarde eso llevó a Hooks (para eliminar la sobrecarga innecesaria del tiempo de ejecución).

`Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}`

Si el problema es la legibilidad y la sangría, esto se puede reescribir como

  <strong i="8">@override</strong>
  Widget build(context) {
    return ValueListenableBuilder<String>(
      valueListenable: someValueListenable,
      builder: (context, value, _) => buildStreamBuilder(value),
    );
  }

  StreamBuilder<int> buildStreamBuilder(String value) => StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) => buildTweenAnimationBuilder(value, value2),
      );

  Widget buildTweenAnimationBuilder(String value, AsyncSnapshot<int> value2) =>
      TweenAnimationBuilder<double>(
        duration: Duration(milliseconds: 500),
        tween: Tween(),
        builder: (context, value3, _) => Text('$value $value2 $value3'),
      );

Si las funciones no son lo tuyo, o necesitas reutilización, extráelas como widgets

class NewWidget extends StatelessWidget {
  var someValueListenable;

  var someStream;

  <strong i="12">@override</strong>
  Widget build(context) {
    return ValueListenableBuilder<String>(
      valueListenable: someValueListenable,
      builder: (context, value, _) {
        return MyStreamedWidget(value, someStream);
      },
    );
  }
}

class MyStreamedWidget extends StatelessWidget {
  const MyStreamedWidget(
    this.value,
    this.someStream, {
    Key key,
  }) : super(key: key);

  final String value;
  final Stream someStream;

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: someStream,
      builder: (context, value2) => MyAnimatedWidget(value, value2),
    );
  }
}

class MyAnimatedWidget extends StatelessWidget {
  final String value;
  final AsyncSnapshot<int> value2;

  const MyAnimatedWidget(
    this.value,
    this.value2, {
    Key key,
  }) : super(key: key);

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
      tween: Tween(),
      builder: (context, value3, _) {
        return Text('$value $value2 $value3');
      },
    );
  }
}

No hay nada en su ejemplo que justifique una nueva palabra clave o función.

Sé lo que vas a decir. La variable 'valor' debe pasarse a través de todos los widgets / llamadas a funciones, pero eso es solo el resultado de cómo diseña su aplicación. Divido mi código usando métodos de "compilación" y widgets personalizados, según el caso de uso, y nunca tengo que pasar la misma variable a una cadena de tres llamadas.

El código reutilizable es reutilizable cuando se basa lo menos posible en efectos secundarios externos (como InheritedWidgets o estado (semi) global).

@Rudiksz No creo que estés agregando nada a la discusión aquí. Somos conscientes de las estrategias para mitigar estos problemas porque las hacemos durante todo el día. Si no siente que sea un problema, simplemente puede continuar usando las cosas como están y esto no lo afectará en absoluto.

Claramente, hay muchas personas que ven esto como un punto de dolor fundamental, y simplemente andar en bicicleta de un lado a otro no tiene sentido. No vas, a través de varios argumentos, a convencer a la gente de que no quieren lo que quieren ni a cambiar la opinión de nadie aquí. Todos en esta discusión claramente tienen cientos o miles de horas en Flutter en su haber, y no se espera que todos estemos de acuerdo en las cosas.

Creo que es una cuestión de opinión. Estoy de acuerdo en que algunas personas piensan que las versiones que usted describe como más legibles son mejores; personalmente prefiero las versiones sin magia mucho más explícitas. Pero no tengo ninguna objeción a hacer posible el segundo estilo.

Si se trata de una cuestión de opinión, supongo que está bastante sesgado en una dirección.

  1. Ambos tienen magia. No sé necesariamente qué están haciendo estos constructores internamente. La versión no mágica está escribiendo el texto estándar real dentro de estos constructores. Usar un mixin SingleAnimationTIckerProvider también es mágico para el 95% de los desarrolladores de Flutter.
  2. Uno oculta nombres de variables muy importantes que se usarán más adelante en el árbol, que es value1 y value2 , el otro los tiene al frente y al centro en la parte superior de la compilación. Esta es una clara victoria de análisis / mantenimiento.
  3. Uno tiene 6 niveles de indendación antes de que comience el árbol de widgets, el otro tiene 0
  4. Una es 5 líneas verticales, la otra es 15
  5. Uno muestra la pieza de contenido real de manera prominente (Texto ()) y el otro lo esconde, anidado, hacia abajo en el árbol. Otra clara victoria de análisis.

En un sentido general, pude ver que esto tal vez sea una cuestión de gustos. Pero Flutter específicamente tiene un problema con el recuento de líneas, la indención y la relación señal: ruido en general. Si bien me encanta la capacidad de formar un árbol de manera declarativa en el código de Dart, puede llevar a un código extremadamente ilegible / detallado, especialmente cuando confías en múltiples capas de constructores envueltos. Entonces, en el contexto de Flutter en sí, donde continuamente estamos librando esta batalla, este tipo de optimización es una característica excelente, porque nos proporciona una herramienta realmente excelente para combatir este problema algo omnipresente de verbosidad general.

TL; DR: todo lo que reduzca significativamente la sangría y el recuento de líneas en Flutter es doblemente valioso, debido a los recuentos de líneas generalmente altos y la sangría inherentes a Flutter.

@Rudiksz No creo que estés agregando nada a la discusión aquí. Somos conscientes de las estrategias para mitigar estos problemas porque las hacemos durante todo el día. Si no siente que sea un problema, simplemente puede continuar usando las cosas como están y esto no lo afectará en absoluto.

Excepto si se trata de un cambio en el marco principal, entonces sí me afecta, ¿no?

Claramente, hay muchas personas que ven esto como un punto de dolor fundamental, y simplemente andar en bicicleta de un lado a otro no tiene sentido. No vas, a través de varios argumentos, a convencer a la gente de que no quieren lo que quieren ni a cambiar la opinión de nadie aquí. Todos en esta discusión claramente tienen cientos o miles de horas en Flutter en su haber, y no se espera que todos estemos de acuerdo en las cosas.

Bien, este caballo ya fue golpeado hasta la muerte en innumerables ocasiones, así que no caeré en la trampa de responder más comentarios.

Los constructores son técnicamente una forma de reutilizar la lógica de estado. Pero su problema es que hacen que el código no sea muy legible.

Esto lo dice perfectamente. Para pensarlo en términos de Flutter, necesitamos constructores de una sola línea.

Constructores que no requieren docenas de pestañas y líneas, pero que aún permiten incorporar un texto estándar personalizado en el ciclo de vida del widget.

El mantra "todo es un widget" específicamente no es algo bueno aquí. El código relevante en un constructor suele ser solo los accesorios de configuración y el estado que devuelve y que necesita la compilación fxn. Cada salto de línea y tabulación es básicamente inútil.

Excepto si se trata de un cambio en el marco principal, entonces sí me afecta, ¿no?

@Rudiksz No creo que nadie esté proponiendo que cambiemos los widgets con estado. Siempre puede usarlos en su forma actual si lo desea. Cualquiera que sea la solución que se nos ocurra, utilizará widgets con estado sin cambios u otro tipo de widget por completo. No estamos diciendo que los widgets con estado sean malos, solo que nos gustaría otro tipo de widget que permita un estado de widget más componible. Piense en él como un widget con estado que, en lugar de un objeto de estado asociado con él, contiene varios objetos de estado y una función de compilación que está separada pero tiene acceso a esos objetos de estado. De esta manera, puede reutilizar bits de estado común (junto con la lógica de estado asociada con ese estado) con sus initState y dispose ya implementados para usted. Estado esencialmente más modular, que se puede componer de diferentes formas en diferentes situaciones. Una vez más, esta no es una propuesta concreta, sino quizás otra forma de pensarlo. Tal vez podría convertirse en una solución más parecida a flutter , pero no lo sé.

Dicho esto, el siguiente paso es trabajar en la aplicación de @TimWhiting (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/stateful/counter.dart) para convertirlo en algo que tenga todos los problemas que queremos resolver.

Esto es complicado porque este problema es inherentemente uno de muerte por mil cortes. Simplemente agrega hinchazón y disminuye la legibilidad en todo el código base. Su impacto se siente peor en widgets pequeños, donde toda la clase tiene menos de 100 líneas y la mitad se dedica a administrar un controlador de animación. Entonces, no sé qué mostrará al ver 30 de estos ejemplos, que 1 no.

Realmente es la diferencia entre esto:

<strong i="10">@override</strong>
  Widget build(BuildContext context) {
    final controller = get AnimationController(vsync: this, duration: widget.duration);
    //do stuff
  }

Y esto:

  AnimationController _controller;

  <strong i="14">@override</strong>
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this, duration: widget.duration);
  }

  <strong i="15">@override</strong>
  void didUpdateWidget(Example oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.duration != oldWidget.duration) {
      _controller.duration = widget.duration;
    }
  }

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

  <strong i="17">@override</strong>
  Widget build(BuildContext context) {
    //Do Stuff
  }

Simplemente, no hay mejor manera de ilustrar que esto. Puede extender este caso de uso a cualquier objeto de tipo controlador. AnimatorController, FocusController y TextEditingController son probablemente los más comunes y molestos de administrar en el uso diario. Ahora solo imagina 50, o 100 de estos esparcidos por todo mi código base.

  • Tiene alrededor de 1000-2000 líneas que podrían simplemente desaparecer.
  • Probablemente tenga docenas de errores y RTE (en varios puntos de desarrollo) que nunca necesitaron existir porque faltaba alguna anulación u otra.
  • Tienes widgets que, cuando se miran con ojos fríos, son mucho más difíciles de asimilar de un vistazo. Necesito leer cada una de estas anulaciones, no puedo simplemente asumir que son estándar.

Y, por supuesto, puede extender esto a los controladores personalizados. Todo el concepto de usar controladores es menos atractivo en Flutter porque sabes que tendrás que arrancarlos, administrarlos y destruirlos de esta manera, lo cual es molesto y propenso a errores. Te lleva a evitar hacer el tuyo propio y, en su lugar, crear StatefulWidgets / Builders personalizados. Sería bueno si los objetos de tipo controlador fueran más fáciles de usar y más robustos, ya que los constructores tienen problemas de legibilidad (o al menos, son significativamente más detallados y cargados de espacios en blanco).

Esto es complicado

Sí, el diseño de API es complicado. Bienvenido a mi vida.

Entonces, no sé qué mostrará al ver 30 de estos ejemplos, que 1 no.

No son 30 ejemplos los que ayudarán, es 1 ejemplo que es lo suficientemente elaborado como para que no se pueda simplificar de manera que luego diga "bueno, claro, para este ejemplo que funciona, pero no para un ejemplo _real_".

No son 30 ejemplos los que ayudarán, es 1 ejemplo que es lo suficientemente elaborado como para que no se pueda simplificar de manera que luego dirías "bueno, claro, para este ejemplo que funciona, pero no para un ejemplo real".

Ya lo he dicho varias veces, pero esa forma de juzgar los ganchos es injusta.
Los ganchos no se tratan de hacer posible algo que antes era imposible. Se trata de proporcionar una API coherente para resolver este tipo de problemas.

Solicitar una aplicación que muestre algo que no se puede simplificar de otra manera es juzgar a un pez por su capacidad para trepar a los árboles.

No solo estamos tratando de juzgar los ganchos, estamos tratando de evaluar diferentes soluciones para ver si hay alguna que aborde las necesidades de todos.

(Tengo curiosidad por saber de qué manera evaluaría las diferentes propuestas aquí, si no fuera escribiendo aplicaciones en cada una de las propuestas y comparándolas. ¿Qué métrica de evaluación propondría en su lugar?)

Una forma adecuada de juzgar la solución a este problema no es una aplicación (ya que cada uso individual de la API se descartará como los ejemplos aquí).

Sobre lo que deberíamos juzgar la solución propuesta es:

  • ¿Es el código resultante objetivamente mejor que la sintaxis predeterminada?

    • ¿Evita errores?

    • ¿Es más legible?

    • ¿Es más fácil escribir?

  • ¿Qué tan reutilizable es el código producido?
  • ¿Cuántos problemas se pueden solucionar con esta API?

    • ¿Perdemos algunos beneficios por algunos problemas específicos?

Cuando se evalúa en esta cuadrícula, la propuesta Property / addDispose puede tener un buen "¿es mejor el código resultante?" puntuar, pero evaluar mal tanto la reutilización como la flexibilidad.

No sé cómo responder a esas preguntas sin ver realmente cada propuesta en uso real.

¿Por qué?

No necesitaba crear aplicaciones usando Property para saber que esta propuesta tendrá dificultades para producir código verdaderamente reutilizable y resolver muchos problemas.

Podemos tomar cualquier * Builder existente e intentar volver a implementarlo usando la solución propuesta.
También podemos intentar reimplementar los ganchos enumerados en este hilo, o algunos ganchos hechos en la comunidad de React (hay numerosas compilaciones de ganchos disponibles en línea).

No necesitaba crear aplicaciones usando Property para saber que esta propuesta tendrá dificultades para producir código verdaderamente reutilizable y resolver muchos problemas.

Desafortunadamente, no comparto sus instintos aquí (como lo demuestra el hecho de que pensé que Property (https://github.com/flutter/flutter/issues/51752#issuecomment-667737471) funcionó muy bien hasta que dijo que tenía que manejar valores tanto desde un widget como desde un estado local con la misma API, que no sabía que era una limitación hasta que lo mencionaste). Si proporciono una versión de Property que también resuelve ese problema, ¿definitivamente estará bien o habrá algún problema nuevo que no cubra? Sin un objetivo, todos estamos de acuerdo en que es el objetivo, no sé para qué estamos diseñando soluciones.

Podemos tomar cualquier * Builder existente e intentar volver a implementarlo usando la solución propuesta.

Claramente no _cualquier_. Por ejemplo, dio uno en el OP, y cuando hice mi primera propuesta de propiedad (https://github.com/flutter/flutter/issues/51752#issuecomment-664787791) señaló problemas con él que no fueron ilustrados por el constructor original.

También podemos intentar reimplementar los ganchos enumerados en este hilo, o algunos ganchos hechos en la comunidad de React (hay numerosas compilaciones de ganchos disponibles en línea).

No me importa por dónde empecemos. Si tiene un ejemplo particularmente bueno que cree que ilustra el problema y usa ganchos, entonces genial, agreguemos eso al repositorio de

No son 30 ejemplos los que ayudarán, es 1 ejemplo que es lo suficientemente elaborado como para que no se pueda simplificar de manera que luego diga "bueno, claro, para este ejemplo que funciona, pero no para un ejemplo _real_".

Nunca habrá nada más elaborado que el simple deseo de usar un AnimatorController (o cualquier otro componente con estado reutilizable que se te ocurra) sin un constructor ilegible o un montón de código repetitivo del ciclo de vida propenso a errores.

No se ha propuesto una solución que aborde los beneficios de legibilidad y robustez solicitados, de manera general.

Insisto en el hecho de que _cualquier_ constructor servirá, ya que este problema podría cambiarse de nombre a "Necesitamos azúcar sintáctica para los constructores" y conducir a la misma discusión.

Todos los demás argumentos presentados (como crear y eliminar un AnimationController ) se basan en que también podrían extraerse en un constructor:

Widget build(context) {
  return AnimationControllerBuilder(
    duration: Duration(seconds: 2),
    builder: (context, animationController) {

    }
  );
}

Al final, creo que el ejemplo perfecto es intentar reimplementar StreamBuilder en su totalidad y probarlo en diferentes escenarios:

  • la secuencia proviene de Widget
  • // de un InheritedWidget
  • del estado local

y pruebe cada caso individual contra "la transmisión puede cambiar con el tiempo", por lo que:

  • didUpdateWidget con una nueva secuencia
  • el InheritedWidget actualizado
  • llamamos setState

Esto no se puede resolver actualmente con Property o onDispose

@esDotDev ¿Puede enumerar "los beneficios de legibilidad y robustez solicitados"? Si alguien hace una propuesta que maneja AnimationController con esos beneficios de legibilidad y robustez, ¿hemos terminado aquí?

@rrousselGit No estoy defendiendo la propiedad, como dijiste antes, no resuelve tus problemas. Pero si alguien creara una solución que hiciera todo lo que hace StreamBuilder, pero sin la sangría, ¿sería eso? ¿Estarías feliz?

Pero si alguien creara una solución que hiciera todo lo que hace StreamBuilder, pero sin la sangría, ¿sería eso? ¿Estarías feliz?

Más probable es que sí

Necesitaríamos comparar esta solución con otras soluciones, por supuesto. Pero eso alcanzaría el nivel aceptable.

@esDotDev ¿Puede enumerar "los beneficios de legibilidad y robustez solicitados"?

Robustez, ya que puede encapsular completamente el texto estándar en torno a las dependencias y el ciclo de vida. es decir. No debería tener que decirle a fetchUser cada vez que probablemente debería reconstruirse cuando cambie la identificación, ya que sabe hacerlo internamente. No debería tener que decirle a una animación que se elimine cada vez que se elimine su widget principal, etc. (no entiendo completamente si la propiedad puede hacer esto). Esto evita que los desarrolladores cometan errores en las tareas de memoria, en todo el código base (uno de los principales beneficios de usar Builders actualmente en mi opinión)

La legibilidad es que podemos obtener la cosa con estado con una sola línea de código sin sangría, y la variable para la cosa está levantada y claramente visible.

@esDotDev Si alguien hace una propuesta que maneja AnimationController con esos beneficios de legibilidad y robustez, ¿hemos terminado aquí?

Si te refieres específicamente a AnimationController no. Si te refieres a cualquier objeto tipo AnimationController / FocusController / TextEdiitingController, entonces sí.

Tener una API similar a una función que devuelve un valor que tiene un tiempo de vida definido que no está claro es

Creo que este es un malentendido clave. La vida útil de un gancho es clara, porque son, por definición, subestados. Ellos _siempre_ existen durante la vida del Estado que los "usa". En realidad, no podría ser más claro. La sintaxis tal vez sea extraña y desconocida, pero ciertamente no le falta claridad.

Similar a cómo la vida útil de un TweenAnimationBuilder () también es clara. Desaparecerá cuando su padre se vaya. Como un widget secundario, estos son estados secundarios. Son "componentes" de estado totalmente independientes, podemos ensamblar y reutilizar con facilidad y explícitamente no administramos su vida porque queremos que esté vinculado naturalmente al estado en el que está declarado, una característica, no un error.

@esDotDev

etc

¿Puedes ser mas específico? (Es por eso que sugerí crear una aplicación de demostración que cubriera todas las bases. Sigo pensando que es la mejor manera de hacerlo). ¿Hay características que importan además de llamar al inicializador a menos que la configuración haya cambiado y se elimine automáticamente recursos asignados cuando se elimina el elemento host?

Objeto tipo TextEdiitingController

¿Puedes ser mas específico? ¿TextEditingController es más elaborado que AnimationController de alguna manera?


@rrousselGit

Pero si alguien creara una solución que hiciera todo lo que hace StreamBuilder, pero sin la sangría, ¿sería eso? ¿Estarías feliz?

Más probable es que sí

Aquí hay una solución que hace todo lo que hace StreamBuilder, sin ninguna sangría:

Widget build(context) {
  var result = Text('result:');
  var builder1 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Row(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream1, builder: builder1);
  var builder2 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Column(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream2, builder: builder2);
}

Sin embargo, supongo que esto viola alguna otra restricción. Es por eso que preferiría tener algo en lo que todos podamos estar de acuerdo es una descripción completa del problema antes de intentar resolverlo.

Son simplemente las mismas restricciones que los constructores tienen @Hixie, nadie está pidiendo más que eso. Un constructor puede vincularse con widget.Whatever, un constructor puede administrar completamente cualquier estado interno que se requiera en el contexto del árbol de widgets. Eso es todo lo que puede hacer un gancho, y todo lo que alguien está pidiendo por un microestado o como quieras llamarlo.

¿Puedes ser mas específico? ¿TextEditingController es más elaborado que AnimationController de alguna manera?

No, pero podría hacer cosas diferentes en init / dispose, o se unirá a diferentes propiedades, y me gustaría encapsular ese texto repetitivo específico.

@esDotDev Entonces, ¿quieres lo mismo que un constructor, pero sin la sangría, y en una línea (menos la devolución de llamada del constructor, presumiblemente)? El ejemplo que acabo de publicar (https://github.com/flutter/flutter/issues/51752#issuecomment-671004483) lo hace con los constructores de hoy, por lo que presumiblemente hay restricciones adicionales más allá de eso.

(FWIW, no creo que los constructores, o algo como los constructores pero en una línea, sean una buena solución, porque requieren que cada tipo de datos tenga su propio constructor creado para él; no hay una buena manera de simplemente construir uno sobre la marcha .)

(FWIW, no creo que los constructores, o algo como los constructores pero en una línea, sean una buena solución, porque requieren que cada tipo de datos tenga su propio constructor creado para él; no hay una buena manera de simplemente construir uno sobre la marcha .)

No entiendo lo que esto significa. ¿Podrías reformularlo? 🙏

Tienes que crear un AnimationBuilder para animaciones y un StreamBuilder para Streams y así sucesivamente. En lugar de tener un solo Builder y decir "así es como obtienes uno nuevo, así es como lo desechas, así es como obtienes los datos", etc. cuando creas tu StatefulWidget.

Sin embargo, supongo que esto viola alguna otra restricción. Es por eso que preferiría tener algo en lo que todos podamos estar de acuerdo es una descripción completa del problema antes de intentar resolverlo.

Creo que es bastante obvio que viola cualquier solicitud de código legible, que es en última instancia el objetivo aquí; de lo contrario, todos usaríamos un millón de constructores específicamente tipados, los anidaríamos para siempre y lo llamaríamos un día.

Creo que lo que se solicita es algo como (tengan paciencia conmigo, no lo hago, uso mucho Streams):

Widget build(context) {
   var snapshot1 = get AsyncSnapshot<int>(stream1);
   var snapshot2 = get AsyncSnapshot<int>(stream2);
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

Este es todo el código. No habría nada más, ya que la corriente se crea para nosotros, la corriente está dispuesta para nosotros, no podemos dispararnos en el pie Y el código es mucho más legible.

Tienes que crear un AnimationBuilder para animaciones y un StreamBuilder para Streams y así sucesivamente.

No veo eso como un problema. Ya tenemos RestorableInt vs RestorableString vs RestorableDouble

Y los genéricos pueden resolver eso:

GenericBuilder<Stream<int>>(
  create: (ref) {
    var controller = StreamController<int>();
    ref.onDispose(controller.close);
    return controller.stream;
  }
  builder: (context, Stream<int> stream) {

  }
)

De manera similar, Flutter o Dart podrían incluir una interfaz Disposable si eso realmente es un problema.

@esDotDev

Creo que lo que se solicita es algo como:

Eso violaría algunas de las restricciones bastante razonables que otros han enumerado (por ejemplo, @Rudiksz), es decir, garantizar que no se produzca ningún código de inicialización durante la llamada al método de compilación.

@rrousselGit

No veo eso como un problema. Ya tenemos RestorableInt vs RestorableString vs RestorableDouble

Y tenemos AnimationBuilder y StreamBuilder y así sucesivamente, sí. En ambos casos es lamentable.

Generador genérico

Eso es similar a lo que propuse para Propiedad, pero si entendí sus preocupaciones allí, creyó que era demasiado detallado.

Anteriormente, dijiste que si alguien creara una solución que hiciera todo lo que hace StreamBuilder, pero sin la sangría, probablemente estarías feliz. No ha comentado sobre mi intento de hacer eso (https://github.com/flutter/flutter/issues/51752#issuecomment-671004483). ¿Estás contento con esa solución?

@esDotDev

Creo que lo que se solicita es algo como:

Eso violaría algunas de las restricciones bastante razonables que otros han enumerado (por ejemplo, @Rudiksz), es decir, garantizar que no se produzca ningún código de inicialización durante la llamada al método de compilación.

No es importante que este código esté en construcción. La parte importante es que

  1. No me veo obligado a sangrar mi árbol ni a agregar un montón de líneas adicionales.
  2. El código del ciclo de vida específico de esta cosa está encapsulado.

Esto sería asombroso:

AsyncSnapshot<int> snapshot1 = createLifecycleState(widget.stream1);
AsyncSnapshot<int> snapshot2 = createLifecycleState(widget.stream2);
AniamtorController animator = createLifecycleState(duration: Duration(seconds: 1), (a)=>a.forward());

Widget build(context) {
   return Opacity(opacity: animator.value, child: Column(children: [Text(snapshot1.data), Text(snapshot2.data)]));
}

O, no tan sucinto, pero aún mucho más legible que usar constructores, y menos detallado y propenso a errores que hacerlo directamente:

AsyncSnapshot<int> stream1;
AsyncSnapshot<int> stream2;
<strong i="18">@override</strong> 
void initState(){
    snapshot1 = createLifecycleState(widget.stream1);
    snapshot2 = createLifecycleState(widget.stream2);
   super.initState();
}

Widget build(context) {
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

No entiendo por qué seguimos volviendo a la verbosidad.
Dije explícitamente varias veces que ese no es el problema y que el problema es la reutilización frente a la legibilidad frente a la flexibilidad.

Incluso hice una cuadrícula para evaluar la solución https://github.com/flutter/flutter/issues/51752#issuecomment -671000137 y un caso de prueba https://github.com/flutter/flutter/issues/51752#issuecomment - 671002248


Anteriormente, dijiste que si alguien creara una solución que hiciera todo lo que hace StreamBuilder, pero sin la sangría, probablemente estarías feliz. No ha comentado sobre mi intento de hacer eso (# 51752 (comentario)). ¿Estás contento con esa solución?

Que alcance el nivel mínimo aceptable de flexibilidad.

Evaluarlo según https://github.com/flutter/flutter/issues/51752#issuecomment -671000137 da:

  • ¿Es el código resultante objetivamente mejor que la sintaxis predeterminada?

    • ¿Evita errores?

      _La sintaxis predeterminada (StreamBuilder sin piratería) es menos propensa a errores. Esta solución no evita errores, crea algunos_ ❌

    • ¿Es más legible?

      _Es evidente que no es más legible_ ❌

    • ¿Es más fácil escribir?

      _No es fácil escribir_ ❌

  • ¿Qué tan reutilizable es el código producido?
    _StreamBuilder no está vinculado al widget / estado / ciclos de vida, por lo que este paso_ ✅
  • ¿Cuántos problemas se pueden solucionar con esta API?
    _Podemos hacer constructores personalizados y usar este patrón. Entonces este pase_. ✅
  • ¿Perdemos algunos beneficios por algunos problemas específicos?
    _No, la sintaxis es relativamente consistente_. ✅
  1. Esta característica IMO podría extenderse a todos los widgets de construcción, incluido LayoutBuilder, por ejemplo.
  2. Es necesario que haya una forma de deshabilitar la escucha, de modo que pueda crear controladores 10x y pasarlos a las hojas para su reconstrucción, o Flutter necesita saber de alguna manera qué parte del árbol usó el valor obtenido por el constructor.
  3. Usar esto no debería ser mucho más detallado que enganches.
  4. El compilador debe extenderse para manejar esto correctamente.
  5. Se necesitan ayudantes de depuración. Digamos que pones breakpoins en uno de tus widgets que usa esto. Al alcanzar un punto de interrupción dentro de un método de compilación, debido a que uno de los compiladores se activó, IDE podría mostrar información adicional para cada compilador que se utilizó:
Widget build(context) {
   // this builder is not highlighted, but you can hover over it to see how often it rebuilds, how heavy were those rebuilds, and when was the last rebuild
   var snapshot1 = keyword StreamBuilder(stream1);
   // this builder will be highlighted because it triggered the rebuild
   var constraints = keyword LayoutBuilder(); 

   // <-- I had a breakpoint here and parent constraint changed, breakpoints got reached.
   return Column(children: [Text(snapshot1.data), Text(snapshot2.data)]);
}

Además, @Hixie

Eso violaría algunas de las restricciones bastante razonables que otros han enumerado (por ejemplo, @Rudiksz), es decir, garantizar que no se produzca ningún código de inicialización durante la llamada al método de compilación.

Ya lo estamos haciendo implícitamente mediante el uso de * Builders. Solo necesitamos un azúcar de sintaxis para desindenarlos. Es muy parecido a async / await y futuros, creo.

@esDotDev Lo que describe suena muy similar a lo que propuse anteriormente con Property (consulte, por ejemplo, https://github.com/flutter/flutter/issues/51752#issuecomment-664787791 o https://github.com/flutter/flutter/ issues / 51752 # issuecomment-667737471). ¿Hay algo que impida que este tipo de solución se cree como paquete? Es decir, ¿qué cambio debería haber realizado el marco central para permitirle utilizar este tipo de función?

@rrousselGit Al igual que con Shawn, te preguntaría lo mismo entonces. Si la única diferencia entre la función StreamBuilder actual y una que satisfaría sus necesidades es una sintaxis diferente, ¿qué es lo que necesita de la sintaxis principal para poder utilizar dicha función? ¿No sería suficiente crear la sintaxis que preferiría y usarla?

El problema que tengo con su cuadrícula es que si lo aplicara a las soluciones hasta ahora, obtendría esto, que supongo que es muy diferente de lo que obtendría:

StatefulWidget

  • ¿Es el código resultante objetivamente mejor que la sintaxis predeterminada?

    • ¿Evita errores?

      _Es la misma que la sintaxis predeterminada, que no es particularmente propensa a errores ._ 🔷

    • ¿Es más legible?

      _Es lo mismo, por lo que es igualmente legible, que es razonablemente legible._ 🔷

    • ¿Es más fácil escribir?

      _Es lo mismo, por lo que es igualmente fácil de escribir, lo cual es razonablemente fácil._ 🔷

  • ¿Qué tan reutilizable es el código producido?
    _Hay muy poco código para reutilizar_ ✅
  • ¿Cuántos problemas se pueden solucionar con esta API?
    _Esta es la línea de base._ 🔷
  • ¿Perdemos algunos beneficios por algunos problemas específicos?
    _No parece así._ ✅

Variaciones en la propiedad

  • ¿Es el código resultante objetivamente mejor que la sintaxis predeterminada?

    • ¿Evita errores?

      _ Mueve el código a un lugar diferente, pero no reduce particularmente el número de errores ._ 🔷

    • ¿Es más legible?

      _Pone el código de inicialización y el código de limpieza y otro código del ciclo de vida en el mismo lugar, por lo que es menos claro ._ ❌

    • ¿Es más fácil escribir?

      _Mezcla código de inicialización y código de limpieza y otro código del ciclo de vida, por lo que es más difícil de escribir_ ❌

  • ¿Qué tan reutilizable es el código producido?
    _Exactamente tan reutilizable como StatefulWidget, solo que en diferentes lugares_ ✅
  • ¿Cuántos problemas se pueden solucionar con esta API?
    _Esto es azúcar sintáctico para StatefulWidget, así que no hay diferencia ._ 🔷
  • ¿Perdemos algunos beneficios por algunos problemas específicos?
    _El rendimiento y el uso de la memoria se verían afectados levemente ._ ❌

Variaciones sobre constructores

  • ¿Es el código resultante objetivamente mejor que la sintaxis predeterminada?

    • ¿Evita errores?

      _Es básicamente la solución StatefulWidget pero descartada; los errores deben ser equivalentes ._ 🔷

    • ¿Es más legible?

      _Los métodos de construcción son más complejos, el resto de la lógica se mueve a un widget diferente, por lo que casi lo mismo ._ 🔷

    • ¿Es más fácil escribir?

      _Más difícil de escribir la primera vez (creando el widget del constructor), un poco más fácil a partir de entonces, así que casi lo mismo ._ 🔷

  • ¿Qué tan reutilizable es el código producido?
    _Exactamente tan reutilizable como StatefulWidget, solo que en diferentes lugares_ ✅
  • ¿Cuántos problemas se pueden solucionar con esta API?
    _Este es un azúcar sintáctico para StatefulWidget, por lo que en su mayoría no hay diferencia. En algunos aspectos es realmente mejor, por ejemplo, reduce la cantidad de código que tiene que ejecutarse cuando se maneja un cambio de dependencia ._ ✅
  • ¿Perdemos algunos beneficios por algunos problemas específicos?
    _No parece así._ ✅

Soluciones en forma de gancho

  • ¿Es el código resultante objetivamente mejor que la sintaxis predeterminada?

    • ¿Evita errores?

      _Fomenta malos patrones (por ejemplo, construcción en el método de construcción), se arriesga a errores si se usa accidentalmente con condicionales ._ ❌

    • ¿Es más legible?

      _Aumenta la cantidad de conceptos que se deben conocer para comprender un método de construcción_ ❌

    • ¿Es más fácil escribir?

      _El desarrollador tiene que aprender a escribir ganchos, que es un concepto nuevo, muy difícil ._ ❌

  • ¿Qué tan reutilizable es el código producido?
    _Exactamente tan reutilizable como StatefulWidget, solo que en diferentes lugares_ ✅
  • ¿Cuántos problemas se pueden solucionar con esta API?
    _Esto es azúcar sintáctico para StatefulWidget, así que no hay diferencia ._ 🔷
  • ¿Perdemos algunos beneficios por algunos problemas específicos?
    _El rendimiento y el uso de la memoria sufren ._ ❌

No entiendo por qué seguimos volviendo a la verbosidad.
Dije explícitamente varias veces que ese no es el problema y que el problema es la reutilización frente a la legibilidad frente a la flexibilidad.

Disculpas, no recordaba quién fue el que dijo que Property era demasiado detallado. Tiene razón, su preocupación era solo que había un nuevo caso de uso que no se había enumerado antes y que no manejó, aunque creo que sería trivial extender Property para manejar ese caso de uso también (no he No lo he intentado, parece mejor esperar hasta que tengamos una aplicación de demostración clara para que podamos resolver las cosas de una vez por todas en lugar de tener que iterar repetidamente a medida que se ajustan los requisitos).

@szotp

  1. Esta característica IMO podría extenderse a todos los widgets de construcción, incluido LayoutBuilder, por ejemplo.

LayoutBuilder es un widget muy diferente a la mayoría de los constructores, FWIW. Ninguna de las propuestas que se han discutido hasta ahora funcionaría para problemas similares a LayoutBuilder, y ninguno de los requisitos descritos antes de su comentario incluye LayoutBuilder. Si también debemos usar esta nueva característica para manejar LayoutBuilder, es importante saberlo; Recomiendo trabajar con @TimWhiting para asegurarnos de que la aplicación de muestra en la que vamos a basar las propuestas incluya constructores de diseño como ejemplo.

  1. Es necesario que haya una forma de deshabilitar la escucha, de modo que pueda crear controladores 10x y pasarlos a las hojas para su reconstrucción, o Flutter necesita saber de alguna manera qué parte del árbol usó el valor obtenido por el constructor.

No estoy seguro de lo que esto significa exactamente. Por lo que puedo decir, puede hacer esto hoy con oyentes y constructores (por ejemplo, yo uso ValueListenableBuilder en la aplicación que cité anteriormente para hacer exactamente esto).

Eso violaría algunas de las restricciones bastante razonables que otros han enumerado (por ejemplo, @Rudiksz), es decir, garantizar que no se produzca ningún código de inicialización durante la llamada al método de compilación.

Ya lo estamos haciendo implícitamente mediante el uso de * Builders.

No creo que eso sea exacto. Depende del constructor, pero algunos trabajan muy duro para separar initState, didChangeDependencies, didUpdateWidget y la lógica de compilación, de modo que solo se necesita la cantidad mínima de código para ejecutar cada compilación en función de lo que ha cambiado. Por ejemplo, ValueListenableBuilder solo registra los oyentes cuando se crea por primera vez, su constructor puede volver a ejecutar sin que se vuelva a ejecutar el padre o el initState del constructor. Esto no es algo que Hooks pueda hacer.

@esDotDev Lo que usted describe suena muy similar a lo que propuse anteriormente con Property (vea, por ejemplo, # 51752 (comentario) o # 51752 (comentario) ).

Si entiendo bien, podríamos hacer UserProperty que maneje automáticamente DidDependancyChange para UserId, o AnimationProperty , o cualquier otra propiedad que necesitemos para manejar init / update / dispose para ese tipo. Entonces esto me parece lindo. Los casos de uso más comunes podrían crearse rápidamente.

Lo único que me desconcierta es el futuro constructor aquí. Pero creo que esto se debe solo al ejemplo que has elegido.

Por ejemplo, ¿podría crear esto?

class _ExampleState extends State<Example> with PropertyManager {
  AnimationProperty animProp1;
  AnimationProperty animProp2;

  <strong i="15">@override</strong>
  void initProperties() {
    super.initProperties();
    anim1= register(anim1, AnimationProperty (
      AnimationController(duration: Duration(seconds: 1)),
      initState: (animator) => animator.forward()
      // Not dealing with updates or dispose here, cause AnimationProperty handles it
    ));
   anim2 = register(anim2, AnimationProperty(
       AnimationController(duration: Duration(seconds: 2))..forward(),
   ));
  }

  <strong i="16">@override</strong>
  Widget build(BuildContext context) {
    return Column(children: [
       FadeTransition(opacity: anim1, child: ...),
       FadeTransition(opacity: anim2, child: ...),
   ])
  }
}

Si es así, ¡esto es totalmente LGTM! En términos de agregar al marco, es un caso de si esto debe promoverse a un enfoque sintáctico de primera clase (lo que significa que se convierte en práctica general en un año más o menos), o si existe como un complemento que algún porcentaje de desarrolladores de un solo dígito usar.

Es una cuestión de si desea poder actualizar los ejemplos detallados y (¿un poco?) Propensos a errores con una sintaxis mejor y más concisa. Tener que sincronizar propiedades manualmente y desechar () cosas manualmente genera errores y carga cognitiva.

Imo sería bueno si un desarrollador pudiera usar animadores, con didUpdate y dispose y debugFillProperties adecuados y todo el trabajo, sin tener que pensarlo dos veces (exactamente como lo hacemos cuando usamos TweenAnimationBuilder ahora, que es la razón principal por la que recomendamos todos nuestros desarrolladores lo usan de forma predeterminada en lugar de administrar animadores manualmente).

Si es así, ¡esto es totalmente LGTM! En términos de agregar al marco, es un caso de si esto debe promoverse a un enfoque sintáctico de primera clase (lo que significa que se convierte en práctica general en un año más o menos), o si existe como un complemento que algún porcentaje de desarrolladores de un solo dígito usar.

Dado lo trivial que es Property , mi recomendación para alguien a quien le guste ese estilo sería simplemente crear el suyo propio (tal vez comenzando con mi código si eso ayuda) y usarlo directamente en su aplicación como mejor le parezca, ajustándolo para atender sus necesidades. Podría convertirse en un paquete si a mucha gente también le gusta, aunque de nuevo, para algo tan trivial, no me queda claro cuánto es beneficioso en comparación con simplemente copiarlo en el código de uno y ajustarlo según sea necesario.

Lo único que me desconcierta es el futuro constructor aquí. Pero creo que esto se debe solo al ejemplo que has elegido.

Estaba tratando de abordar un ejemplo que dio @rrousselGit . En principio, se puede adaptar para que funcione para cualquier cosa.

Por ejemplo, ¿podría crear esto?

Querría mover el constructor AnimationController a un cierre que se llamaría, en lugar de llamarlo cada vez, ya que se llama a initProperties durante la recarga en caliente para obtener los nuevos cierres, pero normalmente no desea crear un nuevo controlador durante la recarga en caliente (por ejemplo, restablecería las animaciones). Pero sí, aparte de eso, parece estar bien. Incluso podría hacer un AnimationControllerProperty que tome los argumentos del constructor AnimationController y haga lo correcto con ellos (por ejemplo, actualizar la duración de la recarga en caliente si cambia).

Imo sería bueno si un desarrollador pudiera usar animadores, con didUpdate y dispose y debugFillProperties adecuados y todo el trabajo, sin tener que pensarlo dos veces (exactamente como lo hacemos cuando usamos TweenAnimationBuilder ahora, que es la razón principal por la que recomendamos todos nuestros desarrolladores lo usan de forma predeterminada en lugar de administrar animadores manualmente).

Mi preocupación acerca de que los desarrolladores no piensen en ello es que si no piensas en cuándo se asignan y eliminan las cosas, es más probable que termines asignando muchas cosas que no siempre necesitas o ejecutando una lógica que no lo hace. No es necesario ejecutarlo o hacer otras cosas que conduzcan a un código menos eficiente. Esa es una de las razones por las que sería reacio a hacer de este un estilo recomendado por defecto.

Incluso podría crear una propiedad AnimationControllerProperty que tome los argumentos del constructor AnimationController y haga lo correcto con ellos (por ejemplo, actualizar la duración de la recarga en caliente si cambia).

Gracias @Hixie, eso es realmente genial y creo que aborda el problema bastante bien.

No estoy sugiriendo que los desarrolladores nunca deberían pensar en estas cosas, pero creo que el caso de uso del 99% de estas cosas casi siempre están vinculadas al StatefulWidget en el que se usan, y hacer algo más que eso ya lo está llevando a un terreno de desarrollador intermedio.

Una vez más, no veo cómo esto es fundamentalmente diferente a recomendar TweenAnimationBuilder sobre AnimatorController sin formato. Básicamente, es la idea de que SI desea que el estado esté totalmente contenido / administrado dentro de este otro estado (y eso suele ser lo que desea), hágalo de esta manera, es más simple y más robusto.

En este punto, deberíamos organizar una llamada y discutirla junto con los diferentes interesados.
Porque esta discusión no avanza, ya que estamos respondiendo la misma pregunta una y otra vez.

No entiendo cómo después de una discusión tan larga, con tantos argumentos hechos, todavía podemos argumentar que los constructores no evitan errores en comparación con StatefulWidget, o que los ganchos no son más reutilizables que los StatefulWidgets sin procesar.

Es especialmente frustrante argumentar teniendo en cuenta que todos los principales marcos declarativos (React, Vue, Swift UI, Jetpack Compose) tienen una forma u otra de resolver este problema de forma nativa.
Parece que solo Flutter se niega a considerar este problema.

@esDotDev La razón principal en mi humilde opinión para usar un AnimationBuilder o TweenAnimationBuilder o ValueListenableBuilder es que se reconstruyen solo cuando cambia el valor, sin reconstruir el resto de su widget de host . Es una cosa de rendimiento. En realidad, no se trata de verbosidad o reutilización de código. Quiero decir, está bien usarlos también por esos motivos, si los encuentra útiles por esos motivos, pero ese no es el caso de uso principal, al menos para mí. También es algo que Property (o Hooks) no te da. Con ellos, terminas reconstruyendo el widget _entire_ cuando algo cambia, lo que no es bueno para el rendimiento.

@rrousselGit

Parece que solo Flutter se niega a considerar este problema.

He pasado literalmente horas de mi propio tiempo este fin de semana, sin mencionar muchas horas del tiempo de Google antes de eso, considerando este problema, describiendo posibles soluciones y tratando de explicar con precisión lo que estamos tratando de resolver. No confunda la falta de comprensión sobre lo que es un problema con la negativa a considerarlo. Especialmente cuando ya he descrito lo mejor que se puede hacer para avanzar (crear una aplicación de demostración que tenga toda la lógica de estado que es "demasiado detallada o demasiado difícil", para citar el título del problema, para reutilizar), que otros en este error lo han asumido como una tarea, y usted se ha negado a participar.

The main reason IMHO to use an AnimationBuilder or TweenAnimationBuilder or ValueListenableBuilder is that they rebuild only when the value changes, without rebuilding the rest of their host widget. It's a performance thing.

Interesante. Para nosotros, realmente nunca hemos medido ni observado una mejora en el rendimiento al salvar pequeñas reconstrucciones como esta. Es mucho más importante que una gran base de código de aplicación para mantener el código conciso, legible y libre de errores de rutina que pueden ocurrir cuando se eliminan cientos de archivos de clase cada dos semanas.

Según nuestra experiencia, el costo de volver a pintar los píxeles, que parece suceder en todo el árbol a menos que tenga un propósito en la definición de sus Límites de Repaint, es un factor mucho más importante en el rendimiento del mundo real que los costos parciales de diseño de widgets. Especialmente cuando entras en el rango de monitores de 4k.

Pero este es un buen ejemplo de cuándo los constructores realmente tienen sentido para este tipo de cosas. Si quiero crear un subcontexto, entonces un constructor tiene sentido y es una buena forma de llegar allí.

Muchas veces no lo hacemos, y en este caso, Builder solo está agregando desorden, pero lo aceptamos, porque la alternativa es solo un tipo diferente de desorden, y al menos con Builder, las cosas están más o menos garantizadas sin errores. En los casos en los que la vista completa se reconstruye, o no hay necesariamente reconstrucciones de la vista (TextEditingController, FocusController), usar un constructor tiene poco sentido y, en todos los casos, enrollar el texto estándar a mano no es fundamentalmente SECO.

Sin duda, es muy específico de la situación, como suelen ser los problemas de rendimiento. Creo que tiene sentido que la gente use algo como Hooks o Property si les gusta ese estilo. Eso es posible hoy y no parece necesitar nada adicional del marco (y como muestra Property, realmente no requiere mucho código).

No, pero es un poco como pedirle a la comunidad que construya TweenAnimationBuilder y ValueListenableBuilder y no proporcionarles un StatefulWidget para construir.

No es que lo esté preguntando, pero uno de los principales beneficios de este tipo de arquitectura es que, naturalmente, se presta a componentes diminutos que se pueden compartir fácilmente. Si pones una pequeña pieza fundamental en su lugar ...

StatefulWidget es un _ lote_ de código, en comparación con Property, y no es trivial (a diferencia de Property, que es principalmente código de pegamento). Dicho esto, si la propiedad es algo que tiene sentido para reutilizar ampliamente (en lugar de crear versiones a medida para cada aplicación o equipo en función de sus necesidades precisas), entonces recomendaría a alguien que defienda su uso que cree un paquete y lo cargue en pub . Lo mismo se aplica, de hecho, a Hooks. Si es algo que le gusta a la comunidad, será muy útil, al igual que Provider. No está claro por qué tendríamos que poner algo así en el marco en sí.

Supongo que porque esto es inherentemente extensible. El proveedor no lo es, es solo una herramienta simple. Esto es algo que está hecho para ser extendido, al igual que StatefulWidget, pero para StatefulComponents. ¿El hecho de que sea relativamente trivial no debe necesariamente ser criticado?

Una nota sobre "los que prefieren este estilo". Si puede guardar 3 anulaciones y entre 15 y 30 líneas, será una ganancia de legibilidad en la mayoría de los casos. Hablando objetivamente en mi opinión. También elimina objetivamente 2 clases completas de errores (olvidarse de deshacerse de las cosas, olvidarse de actualizar los departamentos).

Muchas gracias por la increíble discusión, emocionado de ver a dónde va esto, definitivamente lo dejaré aquí :)

Lamento decir que este hilo me desilusiona al volver a la confusión, que era el plan al terminar otro proyecto en el que estoy trabajando. También siento la frustración por

Es especialmente frustrante argumentar teniendo en cuenta que todos los principales marcos declarativos (React, Vue, Swift UI, Jetpack Compose) tienen una forma u otra de resolver este problema de forma nativa.

Estoy de acuerdo con @rrousselGit en que no creo que debamos dedicar tiempo a crear aplicaciones de ejemplo de flutter, ya que el problema se ha descrito claramente una y otra vez en este hilo. No veo cómo no obtendría la misma respuesta. Porque serán las mismas cosas que se han presentado aquí. Mi opinión de este hilo es que, desde el punto de vista de los frameworks de flutter, es mejor para los desarrolladores de flutter repetir el mismo código de por vida en varios widgets en lugar de escribirlo una vez y terminar con él.
Además, no podemos escribir una aplicación en flutter si estamos buscando una solución, ya que necesitamos las soluciones para escribir una aplicación. Dado que las personas que se mueven en esta conversación, al menos han sido bastante claras, no les gustan los ganchos. Y Flutter simplemente no tiene otra solución al problema como se describe en el OP. ¿Cómo debería escribirse?

Siente (al menos para mí) que esto no se toma en serio, lo siento @Hixie , quiero decir que no se toma en serio en el sentido de: We understand the problem and want to solve it . Al igual que otros marcos declarativos aparentemente parecen haberlo hecho.
En otra nota, pero algo similar, cosas como esta:

¿Es más fácil escribir?
El desarrollador tiene que aprender a escribir ganchos, que es un concepto nuevo, por lo que es más difícil

Me pone triste. ¿Por qué mejorar o cambiar algo alguna vez? siempre puedes hacer ese argumento pase lo que pase. Incluso si las nuevas soluciones son mucho más fáciles y agradables una vez aprendidas. Puede reemplazar los ganchos en esa declaración con muchas cosas. Mi madre podría haber usado ese tipo de oración sobre microondas hace 30 años. Por ejemplo, funciona igual si reemplaza "ganchos" en la oración con "aleteo" o "dardo".

¿Es más fácil escribir?
Es lo mismo, por lo que es igualmente fácil de escribir, lo cual es razonablemente fácil.

No creo que lo que @rrousselGit signifique con is it easier to write? (una pregunta de respuesta booleana) sea que si es la misma, la respuesta no es false / undefined .

No veo cómo seremos capaces de llegar a alguna parte, ya que ni siquiera estamos de acuerdo en que haya un problema, solo que para mucha gente esto es un problema. P.ej:

Ciertamente es algo que la gente ha mencionado. No es algo con lo que tenga una experiencia visceral. No es algo que haya sentido que sea un problema al escribir mis propias aplicaciones con Flutter. Sin embargo, eso no significa que no sea un problema real para algunas personas.

Y aunque muchos han proporcionado muchos argumentos muchas veces por qué una solución al OP debe estar en el núcleo.
Por ejemplo, debe estar en el núcleo para que los terceros puedan usarlo con la misma facilidad y naturalidad que usan y crean widgets en la actualidad. Y muchas otras razones. El mantra parece ser, simplemente ponerlo en un paquete. Pero ya hay paquetes como el de los ganchos. Si eso es lo que se ha decidido, ¿por qué no simplemente cerrar el hilo?

Realmente espero que acepte su oferta de

De todos modos, me estoy dando de baja ahora porque me pongo un poco triste en vivo, siguiendo este hilo ya que no veo que vaya a ninguna parte. Pero espero que este hilo llegue al estado de acuerdo en que hay un problema para que pueda enfocarse en posibles soluciones al OP. Dado que parece un poco inútil proponer soluciones si no comprendes el problema que enfrentan las personas, como @Hixie probablemente esté de acuerdo, quiero decir, ya que las personas con los problemas te dirán por qué la solución no funciona después.

Realmente te deseo la mejor de las suertes al terminar este hilo simplemente diciendo rotundamente que el aleteo no debería resolver este problema en el núcleo a pesar de que la gente lo quiera. O encontrando una solución. 😘

LayoutBuilder es un widget muy diferente a la mayoría de los constructores, FWIW. Ninguna de las propuestas que se han discutido hasta ahora funcionaría para problemas similares a LayoutBuilder, y ninguno de los requisitos descritos antes de su comentario incluye LayoutBuilder. Si también debemos usar esta nueva característica para manejar LayoutBuilder, es importante saberlo; Recomiendo trabajar con @TimWhiting para asegurarnos de que la aplicación de muestra en la que vamos a basar las propuestas incluya constructores de diseño como ejemplo.

@Hixie Sí, definitivamente necesitamos algunas muestras. Prepararé algo (pero sigo pensando que se necesitan cambios en el compilador, por lo que la muestra puede estar incompleta). La idea general es: un azúcar de sintaxis que aplana los constructores y no se preocupa por cómo se implementa el constructor.

Aún así, tengo la impresión de que nadie en el equipo de Flutter miró más profundamente a SwiftUI, creo que nuestras preocupaciones serían fáciles de entender de otra manera. Es importante para el futuro del framework que las personas que migren desde otras plataformas tengan un viaje lo más fluido posible y, por lo tanto, se necesita una buena comprensión de otras plataformas y conocimiento de los pros y los contras. Y ver si algunos de los inconvenientes de Flutter se pueden arreglar. Obviamente, Flutter tomó muchas buenas ideas de React y yo podría hacer lo mismo con marcos más nuevos.

@ emanuel-lundman

Siente (al menos para mí) que esto no se toma en serio, lo siento @Hixie , quiero decir que no se toma en serio en el sentido de: We understand the problem and want to solve it . Al igual que otros marcos declarativos aparentemente parecen haberlo hecho.

Estoy completamente de acuerdo en que no entiendo el problema. Por eso sigo participando en este tema, tratando de entenderlo. Es por eso que he sugerido crear una aplicación de demostración que resuma el problema. Si es algo que al final decidimos arreglar cambiando fundamentalmente el marco, o agregando una pequeña característica al marco, o mediante un paquete, o nada, realmente depende de cuál sea el problema en realidad.

@szotp

Aún así, tengo la impresión de que nadie en el equipo de Flutter miró más profundamente a SwiftUI, creo que nuestras preocupaciones serían fáciles de entender de otra manera.

He estudiado Swift UI. Ciertamente es menos detallado escribir código Swift UI que código Flutter, pero el costo de legibilidad es muy alto en mi humilde opinión. Hay mucha "magia" (en el sentido de lógica que funciona de formas que no son obvias en el código del consumidor). Puedo creer totalmente que es un estilo que algunas personas prefieren, pero también creo que uno de los puntos fuertes de Flutter es que tiene muy poca magia. Eso significa que a veces escribes más código, pero también significa que depurar ese código es _mucho_ más fácil.

Creo que hay espacio para muchos estilos de marcos. Estilo MVC, estilo React, súper conciso mágico, sin magia pero detallado ... Una de las ventajas de la arquitectura de Flutter es que el aspecto de portabilidad está completamente separado del marco en sí, por lo que es posible aprovechar todas nuestras herramientas - soporte multiplataforma, recarga en caliente, etc. - pero crea un marco completamente nuevo. (Ya existen otros marcos de Flutter, por ejemplo, flutter_sprites). De manera similar, el marco en sí está diseñado en capas para que, por ejemplo, pueda reutilizar toda nuestra lógica de RenderObject pero con un reemplazo para la capa de Widgets, por lo que si la verbosidad de la Widgets es demasiado, alguien podría crear un marco alternativo que lo reemplace. Y, por supuesto, está el sistema de empaquetado para que se puedan agregar funciones sin perder el código del marco existente.

De todos modos, el punto de mi digresión aquí es simplemente que esto no es todo o nada. Incluso si a largo plazo no terminamos adoptando una solución que lo haga feliz, eso no significa que no pueda continuar beneficiándose de las partes de la oferta que sí le gustan.


Insto a las personas interesadas en este problema a trabajar con @TimWhiting para crear una aplicación que demuestre por qué querría reutilizar el código y cómo se ve hoy cuando no puede (https://github.com/TimWhiting/local_widget_state_approaches). Esto nos ayudará directamente a crear propuestas sobre cómo abordar este problema de una manera que aborde las necesidades de _todas_ las personas que están comentando aquí (incluidos aquellos a los que les gustan los Hooks y los que no les gustan los Hooks).

Realmente no puede ser tan difícil de entender por qué "_un azúcar sintáctico que aplana los constructores y no se preocupa por cómo se implementa el constructor_" es deseado por los desarrolladores como una característica de primera clase. Hemos resumido los problemas con los enfoques alternativos una y otra vez.

En resumen, los constructores resuelven el problema de la reutilización, pero son difíciles de leer y redactar. El "problema" es simplemente que nos gustaría una funcionalidad similar a la de un constructor que sea significativamente más fácil de leer.

Ninguna aplicación puede mostrar eso con mayor claridad, si fundamentalmente no está de acuerdo en que 3 constructores anidados son difíciles de leer, o que los constructores en general no tienen un propósito de reutilización de código. Más importante es escuchar que a muchos de nosotros realmente nos gusta reducir el anidamiento, y realmente no nos gusta duplicar el código en toda nuestra aplicación, por lo que estamos atrapados entre 2 opciones no ideales.

He pasado literalmente horas de mi propio tiempo este fin de semana, sin mencionar muchas horas del tiempo de Google antes de eso, considerando este problema, describiendo posibles soluciones y tratando de sacar con precisión lo que estamos tratando de resolver.

Estoy agradecido por eso

No confunda la falta de comprensión sobre lo que es un problema con la negativa a considerarlo.

Estoy bien con la falta de comprensión, pero la situación actual parece desesperada.
Todavía estamos debatiendo sobre los puntos que se hicieron al comienzo de la discusión.

Desde mi perspectiva, siento que pasé horas escribiendo comentarios detallados mostrando los diferentes problemas y respondiendo preguntas, pero mis comentarios fueron descartados y se volvió a hacer la misma pregunta.

Por ejemplo, la falta de legibilidad de la sintaxis actual está en el centro de la discusión.
Hice varios análisis del problema de legibilidad para respaldar esto:

Estos análisis tienen un número significativo de 👍 y otros pueblos parecen estar de acuerdo

Y, sin embargo, de acuerdo con su respuesta reciente, no hay ningún problema de legibilidad: https://github.com/flutter/flutter/issues/51752#issuecomment -671009593

También sugirió:

Widget build(context) {
  var result = Text('result:');
  var builder1 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Row(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream1, builder: builder1);
  var builder2 = (BuildContext context, AsyncSnapshot<int> snapshot) {
    return Column(children: [result, Text(snapshot.data)]);
  };
  result = StreamBuilder(stream: _stream2, builder: builder2);
}

sabiendo que esto no es legible

De estos dos comentarios, podemos concluir que:

  • no estamos de acuerdo con que haya un problema de legibilidad
  • todavía no está claro si la legibilidad es parte del alcance de este problema o no

Es desalentador escuchar esto, considerando que el único propósito de los ganchos es mejorar la sintaxis de los constructores, que se encuentran en la cima de la reutilización pero tienen una legibilidad / escritura deficiente.
Si no estamos de acuerdo en un hecho tan básico, no estoy seguro de qué podemos hacer.

@Hixie gracias, esto ayuda mucho a entender tu punto de vista. Estoy de acuerdo en que pueden haberse excedido con la magia del código, pero estoy seguro de que hay al menos algunas cosas que hicieron bien.

Y me gusta mucho la arquitectura en capas de Flutter. También me gustaría seguir usando widgets. Entonces, tal vez la respuesta sea simplemente mejorar la extensibilidad de Dart & Flutter, que para mí sería:

Haga que la generación de código sea más fluida: puede ser posible implementar la magia de SwiftUI en Dart, pero la configuración habitual requerida es demasiado grande y demasiado lenta.

Si usar la generación de código fuera tan simple como importar un paquete y agregar algunas anotaciones, entonces las personas que tienen el problema discutido simplemente lo harían y dejarían de quejarse. El resto continuaría usando los viejos StatefulWidgets directamente.

EDITAR: Creo que flutter generate fue un paso en buena dirección, lástima que se haya eliminado.

Creo que esta sería una pregunta muy interesante para hacer en la próxima encuesta para desarrolladores de Flutter.

Sería un buen comienzo. Divida este problema en diferentes partes / preguntas y vea si es un problema real que los desarrolladores de Flutter desean resolver.

Una vez que esté claro, esta conversación será más fluida y enriquecedora.

Desde mi perspectiva, siento que pasé horas escribiendo comentarios detallados mostrando los diferentes problemas y respondiendo preguntas, pero mis comentarios fueron descartados y se volvió a hacer la misma pregunta.

Si hago las mismas preguntas es porque no entiendo las respuestas.

Por ejemplo, volviendo a su comentario anterior (https://github.com/flutter/flutter/issues/51752#issuecomment-670959424):

El problema debatido es:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

Este código no es legible.

Realmente no veo lo que no es legible al respecto. Explica exactamente lo que está sucediendo. Hay cuatro widgets, tres de los widgets tienen métodos de creación, uno solo tiene una cadena. Personalmente, no omitiría los tipos, creo que eso hace que sea más difícil de leer porque no puedo decir cuáles son todas las variables, pero no es un gran problema.

¿Por qué es ilegible?

Para ser claros, claramente lo encuentras ilegible, no estoy tratando de decir que estás equivocado. Simplemente no entiendo por qué.

Podríamos solucionar el problema de legibilidad introduciendo una nueva palabra clave que cambia la sintaxis a:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

Este código es significativamente más legible, no está relacionado con los ganchos y no adolece de sus limitaciones.

Ciertamente es menos detallado. No estoy seguro de que sea más legible, al menos para mí. Hay más conceptos (ahora tenemos ambos widgets y esta función de "palabra clave"); más conceptos significa más carga cognitiva. (También es potencialmente menos eficiente, dependiendo de la independencia de estos objetos; por ejemplo, si la animación siempre cambia con más frecuencia que el valor escuchable y el flujo, ahora estamos reconstruyendo ValueListenableBuilder y StreamBuilder aunque normalmente no se activarían ; también la lógica del inicializador ahora debe ingresarse y omitirse en cada compilación).

Has dicho que la verbosidad no es el problema, por lo que ser más conciso no es la razón por la que es más legible, supongo (aunque también estoy confundido porque pusiste "demasiado detallado" en el título del problema y en descripción original del problema). Usted mencionó querer menos sangría, pero describió la versión de usar constructores sin sangría como ilegible también, por lo que presumiblemente no es la sangría en el original el problema.

Dices que los constructores son la cima de la reutilización y que solo quieres una sintaxis alternativa, pero las propuestas que has sugerido no se parecen en nada a los constructores (no crean widgets o elementos), por lo que no es específicamente el aspecto del constructor lo que necesitas. estan buscando.

¿Tiene una solución que le gusta (Hooks), que por lo que puedo decir funciona muy bien, pero quiere que se cambie algo en el marco para que la gente use Hooks? Lo cual tampoco entiendo, porque si a la gente no le gustan los Hooks lo suficiente como para usarlo como un paquete, probablemente tampoco sea una buena solución para el marco (en general, nos estamos moviendo más hacia el uso de paquetes, incluso funciones el equipo de Flutter crea, por lo que vale).

Entiendo que existe el deseo de una reutilización de código más fácil. Simplemente no sé lo que eso significa.

¿Cómo se compara lo siguiente en legibilidad con las dos versiones anteriores?

Widget build(context) {
  return
    ValueListenableBuilder(valueListenable: someValueListenable, builder: (context, value, _) =>
    StreamBuilder(stream: someStream, builder: (context, value2) =>
    TweenAnimationBuilder(tween: Tween(...), builder: (context, value3) =>
    Text('$value $value2 $value3'),
  )));
}

@szotp Si existe demasiada fricción en torno a nuestra solución de codegen actual, no dude en presentar un error solicitando mejoras allí.

@jamesblasco No creo que haya ninguna duda de que hay un problema real aquí que la gente quiere que se resuelva. La pregunta para mí es exactamente cuál es ese problema, para que podamos diseñar una solución.

Podría responder a las preocupaciones sobre las fallas de los ganchos o el deseo de ser incluido en el código, pero no creo que eso sea en lo que debamos enfocarnos en este momento.

Primero deberíamos ponernos de acuerdo sobre cuál es el problema. Si no lo hacemos, no veo cómo podríamos ponernos de acuerdo sobre otros temas.

Realmente no veo lo que no es legible al respecto. Explica exactamente lo que está sucediendo. Hay cuatro widgets, tres de los widgets tienen métodos de creación, uno solo tiene una cadena. Personalmente, no omitiría los tipos, creo que eso hace que sea más difícil de leer porque no puedo decir cuáles son todas las variables, pero no es un gran problema.

Creo que una gran parte del problema aquí es que la forma en que codifica es drásticamente diferente de la forma en que la mayoría de la gente codifica.

Por ejemplo, Flutter y el ejemplo de la aplicación que le diste a ambos:

  • no use dartfmt
  • use always_specify_types

Con solo estos dos puntos, me sorprendería si eso representara más del 1% de la comunidad.

Como tal, lo que evalúa como legible probablemente sea muy diferente de lo que la mayoría de la gente piensa que es legible.

Realmente no veo lo que no es legible al respecto. Explica exactamente lo que está sucediendo. Hay cuatro widgets, tres de los widgets tienen métodos de creación, uno solo tiene una cadena. Personalmente, no omitiría los tipos, creo que eso hace que sea más difícil de leer porque no puedo decir cuáles son todas las variables, pero no es un gran problema.

¿Por qué es ilegible?

Mi recomendación sería analizar hacia dónde mira su ojo cuando busca algo específico y cuántos pasos se necesitan para llegar allí.

Hagamos un experimento:
Te daré dos árboles de widgets. Uno usa una sintaxis lineal, el otro con una sintaxis anidada.
También te daré cosas específicas que debes buscar en ese fragmento.

¿Es más fácil encontrar la respuesta al usar la sintaxis lineal o la sintaxis anidada?

Las preguntas:

  • ¿Cuál es el widget no constructor devuelto por este método de construcción?
  • ¿Quién crea la variable bar ?
  • ¿Cuántos constructores tenemos?

Usando constructores:

 Construcción de widget (contexto) {
 devolver ValueListenableBuilder(
 valueListenable: someValueListenable,
 constructor: (contexto, foo, _) {
 volver StreamBuilder(
 corriente: someStream,
 constructor: (contexto, baz) {
 volver TweenAnimationBuilder(
 interpolación: interpolación (...),
 constructor: (contexto, barra) {
 return Container ();
 },
 );
 },
 );
 },
 );
 }

Usando una sintaxis lineal:

 Construcción de widget (contexto) {
 foo final = palabra clave ValueListenableBuilder (valueListenable: someValueListenable);
 barra final = palabra clave StreamBuilder (flujo: someStream);
 final baz = palabra clave TweenAnimationBuilder (interpolación: Tween (...));

return Image (); }


En mi caso, me cuesta mirar el código anidado para encontrar la respuesta.
Por otro lado, encontrar la respuesta con el árbol lineal es instantáneo

Mencionaste querer menos sangría, pero describiste la versión de usar constructores sin sangría como ilegible también, así que presumiblemente, no es la sangría en el original el problema.

¿El StreamBuilder dividido en múltiples variables fue una sugerencia seria?
Según tengo entendido, esta fue una sugerencia sarcástica para argumentar. ¿No fue así? ¿De verdad crees que este patrón conduciría a un código más legible, incluso en widgets grandes?

Ignorando el hecho de que el ejemplo no funciona, no me importa desglosarlo para explicar por qué no es legible. ¿Sería valioso eso?

dardo
Construcción de widget (contexto) {
regreso
ValueListenableBuilder (valueListenable: someValueListenable, constructor: (contexto, valor, _) =>
StreamBuilder (flujo: someStream, constructor: (contexto, valor2) =>
TweenAnimationBuilder (interpolación: Tween (...), constructor: (contexto, valor3) =>
Texto ('$ valor $ valor2 $ valor3'),
)));
}

Eso se ve mejor.
Pero eso es asumiendo que la gente no usa dartfmt

Con dartfmt, tenemos:

Widget build(context) {
  return ValueListenableBuilder(
      valueListenable: someValueListenable,
      builder: (context, value, _) => StreamBuilder(
          stream: someStream,
          builder: (context, value2) => TweenAnimationBuilder(
                tween: Tween(),
                builder: (context, value3) => Text('$value $value2 $value3'),
              )));
}

que casi no es diferente del código original.

Dices que los constructores son la cima de la reutilización y que solo quieres una sintaxis alternativa, pero las propuestas que has sugerido no se parecen en nada a los constructores (no crean widgets o elementos), por lo que no es específicamente el aspecto del constructor lo que necesitas. estan buscando.

Ese es un detalle de implementación.
No hay ninguna razón en particular para tener un elemento o no.
De hecho, puede ser interesante tener un elemento, de modo que podamos incluir LayoutBuilder y potencialmente GestureDetector .

Creo que es de baja prioridad. Pero en la comunidad de React, entre las diferentes bibliotecas de hooks, he visto:

  • useIsHovered: devuelve un valor booleano que indica si el widget está suspendido
  • useSize - (Probablemente debería ser useContraints en Flutter) que da el tamaño de la interfaz de usuario asociada.

(También es potencialmente menos eficiente, dependiendo de la independencia de estos objetos; por ejemplo, si la animación siempre cambia con más frecuencia que el valor escuchable y el flujo, ahora estamos reconstruyendo ValueListenableBuilder y StreamBuilder aunque normalmente no se activarían ; también la lógica del inicializador ahora debe ingresarse y omitirse en cada compilación).

Eso depende de cómo se resuelva la solución.

Si buscamos una solución de idioma, este problema no sería un problema en absoluto.

Podríamos hacer eso:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

"compila" en:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

Si usamos ganchos, flutter_hooks viene con un widget HookBuilder , para que podamos dividir las cosas cuando lo necesitemos.
De manera similar, necesitaría puntos de referencia adecuados para determinar si realmente es un problema, especialmente en el ejemplo que se presenta aquí.

Con los ganchos, estamos reconstruyendo solo un Elemento.
Con Builders, la reconstrucción se divide en varios elementos. Eso también agrega algo de sobrecarga, incluso si es pequeña.

No es imposible que sea más rápido reevaluar todos los ganchos. Parece que esta fue la conclusión a la que se le ocurrió el equipo de React cuando diseñaron ganchos.
Sin embargo, es posible que esto no se aplique a Flutter.

¿Por qué es ilegible?

Debido al anidamiento, el anidamiento hace que sea más difícil escanear rápidamente y saber qué partes puede ignorar y cuáles son esenciales para comprender lo que está sucediendo. El código es un poco "secuencial" por naturaleza, pero el anidamiento lo oculta. El anidamiento también dificulta trabajar con él, imagina que quieres reordenar dos cosas, o inyectar algo nuevo entre dos, trivial en un código verdaderamente secuencial, pero difícil cuando necesitas trabajar con anidamiento.

Esto es muy similar a async / await sugar vs trabajar con Future API sin procesar, el concepto basado en la continuación de dame debajo (e incluso los argumentos a favor y en contra son muy similares): sí, Future API se puede usar directamente y no oculta nada, pero la legibilidad y el mantenimiento ciertamente no es bueno: async / await es un ganador allí.

Mi recomendación sería analizar hacia dónde mira su ojo cuando busca algo específico y cuántos pasos se necesitan para llegar allí.

He estado programando durante 25 años en más de 10 lenguajes diferentes y esas son fácilmente las peores formas de evaluar qué hace que un código sea legible. La legibilidad del código fuente es complicada, pero se trata más de qué tan bien expresa los conceptos de programación y la lógica, en lugar de "dónde miran mis ojos" o cuántas líneas de código usa.

O más bien, me parece que ustedes se están enfocando demasiado en la legibilidad y menos en la mantenibilidad .

Sus ejemplos son menos legibles porque la __intendencia__ del código es menos evidente y las diferentes preocupaciones que se ocultan en el mismo lugar dificulta su mantenimiento.


final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);

¿Cuál sería el valor? ¿Un widget? ¿Una variable de cadena? Quiero decir que se usa dentro de un
return Text('$value $value2 $value3');

Básicamente, lo que desea es que al hacer referencia a la variable A en el método de compilación del widget B, ¿debería hacer que B se reconstruya siempre que cambie el valor de A? Eso es, literalmente, lo que hace mobx, y lo hace exactamente con la cantidad correcta de magia / repetición.


final value2 = keyword StreamBuilder(stream: someStream);

¿Qué debería devolver esto? ¿Un widget? ¿Un arroyo? ¿Un valor de cadena?

Nuevamente, parece un valor de cadena. Por lo tanto, desea poder simplemente hacer referencia a una transmisión en un método de compilación, hacer que ese widget se reconstruya cada vez que la transmisión emita un valor y tener acceso al valor emitido y crear / actualizar / eliminar la transmisión cada vez que se crea / actualiza el widget. /¿destruido? ¿En una sola línea de código? ¿Dentro del método de construcción?

Sí, con mobx puede hacer que sus métodos de compilación se vean exactamente como su ejemplo "más legible" (excepto que hace referencia a observables). Todavía tiene que escribir el código real que hace todo el trabajo, al igual que lo hace con los ganchos. El código real es de aproximadamente 10 líneas y es reutilizable en cualquier widget.


final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

¿Una clase llamada "TweenAnimationBuilder" devuelve una cadena? Ni siquiera me acerco a por qué esta es una idea terrible.

No hay diferencia en sangría / legibilidad entre:

Future<double> future;

AsyncSnapshot<double> value = keyword FutureBuilder<double>(future: future);

y:

Future<double> future;

double value = await future;

Ambos hacen exactamente lo mismo: escuchar un objeto y desenvolver su valor.

Realmente no veo lo que no es legible al respecto. Explica exactamente lo que está sucediendo. Hay cuatro widgets, tres de los widgets tienen métodos de creación, uno solo tiene una cadena. Personalmente, no omitiría los tipos, creo que eso hace que sea más difícil de leer porque no puedo decir cuáles son todas las variables, pero no es un gran problema.

El mismo argumento podría aplicarse a las cadenas Promise / Future.

foo().then(x =>
  bar(x).then(y =>
    baz(y).then(z =>
      qux(z)
    )
  )
)

vs

let x = await foo();
let y = await bar(x);
let z = await baz(y);
await qux(z);

Se podría decir que la primera forma de escribir deja en claro que las Promesas se están creando bajo el capó y cómo se forma exactamente la cadena. Me pregunto si se suscribe a eso, o si considera que las Promesas son fundamentalmente diferentes de los Constructores en que merecen una sintaxis.

¿Una clase llamada "TweenAnimationBuilder" devuelve una cadena? Ni siquiera me acerco a por qué esta es una idea terrible.

Puede hacer el mismo argumento sobre Promesas / Futuros y decir que await oculta el hecho de que devuelve una Promesa.

Debo señalar que la idea de "desenvolver" cosas a través de la sintaxis no es nueva. Sí, en los lenguajes principales llegó a través de async / await, pero, por ejemplo, F # tiene expresiones computacionales , similares a la notación do en algunos lenguajes FP incondicionales. Allí, tiene mucho más poder y está generalizado para trabajar con cualquier envoltorio que satisfaga ciertas leyes. No estoy sugiriendo agregar Monads a Dart, pero creo que vale la pena mencionar que definitivamente hay un precedente para la sintaxis de tipo seguro para "desenvolver" cosas que no necesariamente corresponden a llamadas asincrónicas.

Dando un paso atrás, creo que una cosa con la que mucha gente aquí está luchando (incluido yo mismo) es esta pregunta sobre la legibilidad. Como ha mencionado @rrousselGit, ha habido muchos ejemplos a lo largo de este hilo de problemas de legibilidad con el enfoque actual basado en Builder . Para muchos de nosotros, parece evidente que esto:

Widget build(context) {
  return ValueListenableBuilder<String>(
    valueListenable: someValueListenable,
    builder: (context, value, _) {
      return StreamBuilder<int>(
        stream: someStream,
        builder: (context, value2) {
          return TweenAnimationBuilder<double>(
            tween: Tween(...),
            builder: (context, value3) {
              return Text('$value $value2 $value3');
            },
          );
        },
      );
    },
  );
}

es significativamente menos legible que esto:

Widget build(context) {
  final value = keyword ValueListenableBuilder(valueListenable: someValueListenable);
  final value2 = keyword StreamBuilder(stream: someStream);
  final value3 = keyword TweenAnimationBuilder(tween: Tween(...));

  return Text('$value $value2 $value3');
}

Pero claramente no es evidente, ya que @Hixie y @Rudiksz no están convencidos (o se oponen activamente) a la idea de que el segundo es más legible que el primero.

Así que aquí está mi desglose (por la pequeña cantidad que valga) sobre por qué el segundo bloque de código es más legible que el primero:

1. El primer bloque de código tiene mucha más sangría que el segundo.

En mi experiencia, la sangría normalmente equivale a asincronicidad, ramificación o devoluciones de llamada, todo lo cual requiere más carga cognitiva para pensar que el código lineal sin sangría. El primer bloque de código tiene varias capas de sangría y, como tal, me toma una cantidad de tiempo no trivial trabajar en lo que está sucediendo aquí y en lo que finalmente se representa (un solo Text ). Quizás otras personas son mejores para trabajar con esa hendidura en sus mentes.


En el segundo bloque de código, no hay sangría que alivie el problema.

2. El primer bloque de código requiere más sintaxis para expresar su intención.

En el primer bloque de código, hay tres declaraciones return , tres declaraciones de constructor, tres encabezados lambda, tres contextos y finalmente tres valores. En última instancia, lo que nos importa son esos tres valores; el resto es estándar para llevarnos allí. De hecho, encuentro que esta es la parte más desafiante de este bloque de código. Están sucediendo muchas cosas, y las partes que realmente me importan (los valores que devuelven los constructores) son una parte tan pequeña que dedico la mayor parte de mi energía mental a asimilar la repetición en lugar de concentrarme en las partes que realmente necesito ( nuevamente, los valores).


En el segundo bloque de código, hay una gran reducción del texto estándar para poder concentrarme en la parte que me importa, nuevamente, los valores.

3. El primer bloque de código oculta la parte más importante del método build en la parte más profunda del anidamiento.

Reconozco que todas las partes de este método build son importantes, pero he descubierto que cuando leo este estilo de código de IU declarativo, lo que normalmente busco es lo que se muestra en el usuario, que en este caso es el widget Text incrustado en el constructor anidado más profundo. En lugar de estar al frente y al centro, ese widget Text está enterrado en múltiples capas de sangría, sintaxis e intención. Si arroja un Column o un Row en una de estas capas, se anida aún más profundamente, y en ese punto ni siquiera tiene el beneficio de simplemente rastrear hasta la sección más sangrada .


En el segundo bloque de código, el valor real de renderizado Widget se devuelve se encuentra en la parte inferior de la función, que es inmediatamente evidente. Además, descubrí que cuando tienes algo como la sintaxis OP propuesta, puedes contar con el visual Widget siempre al final de la función, lo que hace que el código sea mucho más predecible y fácil de leer.

Con respecto al anidamiento, hay una diferencia entre anidar que expresa un _árbol_ y anidar que expresa una _secuencia_ .

En el caso del anidamiento normal de View -> Text y demás, el anidamiento es importante porque representa las relaciones entre padres e hijos en la pantalla. Para características como Contexto (no estoy seguro de si Flutter lo tiene), representa el alcance de los contextos. Por lo tanto, el anidamiento en sí tiene un significado semántico importante en esos casos y no se puede ignorar. No puede simplemente intercambiar el lugar de padres e hijos y esperar que el resultado sea el mismo.

Sin embargo, con el anidamiento de Builders (también conocido como "Render Props" en React), o el anidado de Promises, se supone que el anidamiento comunica una secuencia de transformaciones / aumentos . El árbol en sí no es tan importante; por ejemplo, cuando se anidan ABuilder -> BBuilder -> CBuilder independientes, sus relaciones entre padres e hijos no transmiten un significado adicional.

Siempre que los tres valores estén disponibles en el alcance a continuación, su estructura de árbol no es realmente relevante. Es conceptualmente "plano", y el anidamiento es solo un artefacto de la sintaxis. Por supuesto, pueden usar los valores de los demás (en cuyo caso su orden es importante), pero este también es el caso de las llamadas de función secuenciales, y eso se puede hacer sin ningún anidamiento.

Es por eso que async/await es convincente. Elimina información adicional (relación padre-hijo de Promesas) que describe un mecanismo de bajo nivel y, en cambio, le permite centrarse en la intención de alto nivel (que describe una secuencia).

Un árbol es una estructura más flexible que una lista. Pero cuando cada padre solo tiene un hijo, se convierte en un árbol patológico , esencialmente, una lista. Async / Await y Hooks reconocen que estamos desperdiciando sintaxis en algo que no transmite información y lo eliminan.

Esto es realmente interesante porque antes dije "esto no se trata de la repetición" y ahora parece que me estoy contradiciendo. Creo que aquí hay dos cosas.

Por sí mismos, los constructores (o al menos los accesorios de renderizado en React) son la solución (AFAIK) al problema de la "lógica reutilizable". Es solo que no son muy ergonómicos si usa muchos de ellos. Naturalmente, la sangría le desanima a usar más de 4 o 5 de ellos en el mismo componente. Cada siguiente nivel es un éxito de legibilidad.

Entonces, la parte que me parece sin resolver es reducir el costo de anidación del lector. Y ese argumento es precisamente el mismo argumento que para async / await .

No es tan legible por las siguientes razones:

  • El uso excesivo de espacios en blanco no se adapta bien. Tenemos solo un número limitado de líneas en nuestro monitor, y forzar el desplazamiento reduce la legibilidad y aumenta la carga congitiva. Imagina que ya tenemos un árbol de widgets de 60 líneas, y me obligaste a añadir 15 adicionales para los constructores, no lo ideal.
  • Desperdicia el espacio Hz, que estamos limitados a dar lugar a un envoltorio adicional, lo que desperdicia aún más el espacio de la línea.
  • Empuja el nodo de la hoja, también conocido como el contenido, más lejos del lado izquierdo y hacia el árbol donde es más difícil de detectar con un vistazo.
  • Es significativamente más difícil identificar de un vistazo a los "jugadores clave" o "no estándar". Tengo que "encontrar las cosas importantes" antes de que mi razonamiento pueda siquiera comenzar.

Otra forma de ver esto es simplemente resaltar el código que no es estándar, y si está agrupado para que su ojo se deleite fácilmente o disperso por todas partes para que su ojo tenga que escanear:

Con el resaltado, esto es muy fácil de razonar. Sin él, necesito leer toda la verbosidad antes de poder averiguar quién está usando qué y dónde:
image

Ahora compare con esto, el resaltado es básicamente redundante, porque no hay ningún otro lugar al que pueda ir mi ojo:
image

Vale la pena señalar que probablemente haya un desacuerdo en la legibilidad frente a la asimilación. @Hixie probablemente pasa su tiempo en archivos de clases monolíticos, donde debe leer y comprender constantemente árboles masivos, mientras que su desarrollador de aplicaciones típico se trata mucho más de construir cientos de clases más pequeñas, y cuando administra muchas clases pequeñas, la capacidad de asimilación es clave . No se trata tanto de que el código sea legible cuando disminuyes la velocidad y lo lees, pero puedo decir qué está haciendo esto de un vistazo, para que pueda saltar y modificar o arreglar algo.

Como referencia, el equivalente de Context en React es InheritedWidgets / Provider

La única diferencia entre ellos es que, en React before hooks, tuvimos que usar el patrón Builder para consumir un Context / Inheritedwidget.

Mientras que Flutter tiene una forma de vincular la reconstrucción con una simple llamada de función.
Por lo tanto, no es necesario utilizar ganchos para aplanar árboles utilizando InheritedWidgets, lo que soluciona el problema de los constructores.

Esa es probablemente una de las razones por las que la discusión es más difícil, ya que necesitamos constructores con menos frecuencia.

Pero vale la pena mencionar que la introducción de una solución similar a un gancho resolvería tanto https://github.com/flutter/flutter/issues/30062
y https://github.com/flutter/flutter/issues/12992

También parece que @Hixie está más acostumbrado a leer árboles anidados profundos porque Flutter es básicamente todos los árboles, mucho más que otros idiomas en mi opinión. Como uno de los principales desarrolladores de Flutter, por supuesto, tendría mucha más experiencia con eso. Flutter esencialmente se puede considerar como un marco de trabajo de izquierda a derecha , con un anidamiento profundo, muy parecido al HTML con el que supongo que @Hixie ha tenido experiencia, después de haber creado la especificación HTML5. Es decir, el punto más a la derecha del bloque de código es donde residen la lógica principal y el valor de retorno.

Sin embargo, la mayoría de los desarrolladores no lo son, o provienen de más lenguajes de arriba a abajo , donde la lógica, nuevamente, se lee de arriba a abajo, en lugar de en árboles anidados; está en el punto más inferior del bloque de código. Por lo tanto, lo que él puede leer no es necesariamente así con muchos otros desarrolladores, que es potencialmente la razón por la que ves la dicotomía de opiniones sobre la legibilidad aquí.

Otra forma de verlo es cuánto código necesita mi cerebro para redactar visualmente. Para mí, esto representa con precisión el "trabajo duro" que debe hacer mi cerebro antes de poder analizar lo que se devuelve del árbol:
image

En pocas palabras, la versión del constructor tiene una huella vertical 4 veces más alta sin agregar literalmente información o contexto adicional, Y empaqueta el código de una manera mucho más dispersa / dispersa. En mi opinión, ese es un caso abierto y cerrado, objetivamente menos legible por esa sola razón, y eso ni siquiera considera la carga cognitiva adicional alrededor de la sangría y la alineación de llaves con las que todos hemos tratado en aleteo.

Piense en mi ojo como una CPU hambrienta, que está más optimizada para procesar. :)

En el caso del anidamiento normal View -> Text y demás, el anidamiento es importante porque representa _las relaciones entre padres e hijos_ en la pantalla. Para características como Contexto (no estoy seguro de si Flutter lo tiene), representa el alcance de los contextos. Por lo tanto, el anidamiento en sí tiene un significado semántico importante en esos casos y no se puede ignorar. No puede simplemente intercambiar el lugar de padres e hijos y esperar que el resultado sea el mismo.

Totalmente de acuerdo y lo mencioné antes. Semánticamente, no tiene sentido crear capas de contexto adicionales en un árbol de visualización visual, porque estoy usando controladores no visuales adicionales que tienen estado. Con 5 animadores, ¿ahora tu widget tiene 5 capas de profundidad? Solo a ese alto nivel, el enfoque actual huele un poco.

Hay dos problemas que se me ocurren aquí.

  1. Sospecho que hay algún desacuerdo sobre qué tan difícil / explícito debería ser cuando se usa algún recurso costoso. La filosofía de Flutter es que deberían ser más difíciles / explícitos para que el desarrollador piense seriamente en cuándo y cómo usarlos. Las transmisiones, las animaciones, los creadores de diseños, etc.representan costos no triviales que podrían usarse de manera ineficiente si son demasiado fáciles.

  2. La compilación es sincronizada, pero las cosas más interesantes con las que te enfrentas como desarrollador de aplicaciones son las asincrónicas. Por supuesto que no podemos hacer que la compilación sea asincrónica. Creamos estas comodidades como Stream / Animation / FutureBuilder, pero no siempre funcionan lo suficientemente bien para lo que necesita un desarrollador. Probablemente sea revelador que no usemos mucho Stream o FutureBuilder en el marco.

No creo que la solución sea decirle a los desarrolladores que siempre escriban objetos de renderizado personalizados cuando trabajen con operaciones asíncronas, por supuesto. Pero en los ejemplos que estoy viendo en este error, hay mezclas de trabajo de sincronización y asíncrono que no podemos esperar. Build tiene que producir algo en cada llamada.

fwiw, el equipo de React abordó la reutilización del problema de la legibilidad como la primera motivación:
Motivación de Hooks: es difícil reutilizar la lógica con estado entre componentes
React no ofrece una forma de "adjuntar" un comportamiento reutilizable a un componente ... es posible que esté familiarizado con los patrones ... que intentan resolver esto. Pero estos patrones requieren que reestructure sus componentes cuando los use, lo que puede ser engorroso y hacer que el código sea más difícil de seguir .

Esto es muy similar a cómo Flutter actualmente no nos ofrece ninguna forma de 'componer estado' de forma nativa. También se hace eco de lo que sucede cuando nosotros, los constructores, está modificando nuestro árbol de diseño y haciéndolo más engorroso trabajar con él y "más difícil de seguir", dicho árbol.

@dnfield si se debe build cada vez, tal vez podamos hacer que los ganchos no estén en el método build para que la compilación siempre esté sincronizada, es decir, ponerlos dentro de la clase donde initState y dispose son. ¿Hay problemas para hacerlo, de aquellos que escriben ganchos?

Puede hacer el mismo argumento sobre Promesas / Futuros y decir que await oculta el hecho de que devuelve una Promesa.

No, no lo haces. Await es literalmente azúcar sintáctico para una sola característica. Si utiliza Futures prolijos, o la sintaxis declarativa, el __intent__ del código es el mismo.

Las demandas aquí son mover el código fuente que se ocupa de preocupaciones completamente diferentes bajo el mismo paraguas, ocultar todo tipo de comportamiento diferente detrás de una sola palabra clave y afirmar que de alguna manera reduce la carga cognitiva.

Eso es completamente falso, porque ahora, cada vez que uso esa palabra clave, necesito preguntarme si el resultado realizará una operación asincrónica, activará reconstrucciones innecesarias, inicializará objetos de larga duración, realizará llamadas de red, leerá archivos del disco o simplemente devolverá un valor estático. . Todas estas son situaciones muy diferentes y tendría que estar familiarizado con el sabor del anzuelo que estoy usando.

Entiendo por la discusión que a la mayoría de los desarrolladores aquí no les gusta molestarse con este tipo de detalles y quieren un desarrollo fácil, simplemente con poder usar estos "ganchos" sin tener que preocuparse por los detalles de implementación.
El uso de estos llamados "ganchos" sin entender su total implicación conducirá a un código ineficiente y malo, y hará que las personas se disparen en el pie, por lo tanto, ni siquiera resuelve el problema de "proteger a los desarrolladores principiantes".

Si sus casos de uso son simples, entonces sí, puede usar ganchos de cualquier manera. Puede usar y anidar constructores todo lo que quiera, pero a medida que su aplicación se vuelve compleja y tiene dificultades para reutilizar el código, creo que se justifica tener que prestar más atención a su propio código y arquitectura. Si estuviera creando una aplicación para potencialmente millones de usuarios, sería muy reacio a usar la "magia" que me abstrae de detalles importantes. En este momento, encuentro que la API de Flutter es perfecta para casos de uso muy simples, y aún flexible para permitir que cualquiera implemente cualquier tipo de lógica compleja de una manera muy eficiente.

@Rudiksz Una

Y de todos modos, las personas aún pueden escribir código eficiente una vez que ven que varios ganchos de alguna manera están bloqueando su aplicación; lo verán cuando perfilen o incluso simplemente ejecuten la aplicación, como lo haría con el estilo actual.

@Rudiksz Una

Dios mío, este mismo argumento se aplica también a las personas que se quejan de problemas con el marco. Nadie los está obligando a no usar el paquete de ganchos.

Voy a ser muy directo aquí.
Este problema realmente no se trata de hooks, statefulwidget y quién usa qué, sino de revertir décadas de mejores prácticas para que algunas personas puedan escribir 5 líneas de código menos.

Tu argumento realmente no funciona. La razón por la que se creó este problema fue porque el paquete flutter_hooks no hace todo lo que podría ser posible con tener algo en el marco, mientras que el modelo actual sí, en virtud de que ya está en el marco de forma nativa. El argumento es mover características de flutter_hooks al marco de forma nativa. Su argumento postula que todo lo que pueda hacer con el modelo actual, también lo puedo hacer con el paquete hooks, lo cual no es cierto, parece de otros en esta discusión. Si fueran ciertos, entonces funcionaría, lo que también significaría que los ganchos estaban en el marco de forma nativa y, por lo tanto, nuevamente, dado que los ganchos y los no ganchos serían equivalentes, puede usar el modelo actual tan bien como los ganchos basados ​​en modelo, que es lo que estaba argumentando.

No estoy seguro de dónde provienen sus mejores prácticas, ya que sé que mantener el código fácilmente legible es una mejor práctica, y que el anidamiento excesivo es un patrón anti. ¿A qué mejores prácticas se refiere exactamente?

fwiw, el equipo de React abordó la reutilización del problema de la legibilidad como la primera motivación:
Motivación de Hooks: es difícil reutilizar la lógica con estado entre componentes
React no ofrece una forma de "adjuntar" un comportamiento reutilizable a un componente ... es posible que esté familiarizado con los patrones ... que intentan resolver esto. Pero estos patrones requieren que reestructure sus componentes cuando los use, lo que puede ser engorroso y hacer que el código sea más difícil de seguir .

Escucho a todo el mundo hablar sobre cómo Flutter es mucho más asombroso que React. ¿Quizás es porque no hace todo como lo hace React? No puede tener ambas cosas, no puede decir que Flutter está muy por delante de React y también pedir que haga todo exactamente como lo hace React.

Cualquiera que sea la solución que Flutter decida utilizar para un problema dado, debe basarse en sus propios méritos. No estoy familiarizado con React, pero aparentemente me estoy perdiendo una pieza de tecnología realmente sorprendente. : /

No creo que nadie esté argumentando que Flutter debería hacer todo como React.

Pero el hecho es que la capa de widgets de Flutter está muy inspirada en React. Eso se indica en la documentación oficial.
Y como consecuencia, los widgets tienen los mismos beneficios y los mismos problemas que los componentes de React.

Esto también significa que React tiene más experiencia que Flutter en el manejo de estos problemas.
Los enfrentó por más tiempo y los comprende mejor.

Por lo tanto, no debería sorprender que las soluciones a los problemas de Flutter sean similares a las soluciones a los problemas de React.

La API de usuario de @Rudiksz Flutter es muy similar al modelo basado en clases de React, incluso cuando la API interna puede ser diferente (no sé si difieren, realmente no me encuentro con la API interna mucho). Te animo a que pruebes React with hooks para ver cómo es, como dije anteriormente, parece haber una dicotomía de opiniones basadas casi exclusivamente en aquellos que han usado y no han usado constructos en forma de gancho en otros frameworks.

Dada su similitud, no debería sorprender que las soluciones a los problemas sean similares, como se dijo anteriormente.

Por favor, hagamos nuestro mejor esfuerzo para no pelear entre nosotros.

A lo único que nos llevará la lucha es a acabar con esta discusión y no encontrar una solución.

Escucho a todo el mundo hablar sobre cómo Flutter es mucho más asombroso que React. ¿Quizás es porque no hace todo como lo hace React? No puede tener ambas cosas, no puede decir que Flutter está muy por delante de React y también pedir que haga todo exactamente como lo hace React.

Señalar que el equipo de React tenía motivaciones similares cuando se les ocurrieron los ganchos, valida las preocupaciones que expresamos aquí. Ciertamente valida que existe un problema al reutilizar y combinar la lógica de estado común dentro de este tipo de marco basado en componentes, y también hasta cierto punto valida la discusión sobre legibilidad, anidamiento y el problema general con "desorden" en sus vistas.

No estoy entusiasmado con nada, nunca he trabajado en React, y amo a Flutter. Puedo ver fácilmente el problema aquí.

@Rudiksz no podemos estar seguros de si funciona en la práctica hasta que lo pongamos en práctica. No es muy fácil decidirse ahora.

@Hixie, este es un ejemplo de un viaje que un usuario común de flutter puede tener para implementar un widget para mostrar el apodo del usuario desde userId con HookWidget y StatefulWidget .

__ widget de gancho__

String useUserNickname(Id userid) {
  final name = useState("");
  useEffect(async () {
    name.value = "Loading...";
    name.value = await fetchNicknames()[userId;
  }, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__ widget de estado__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";

   initState() {
     super.initState();
     fetchAndUpdate();
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      final result = await fetchNicknames()[widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

hasta ahora nada interesante. ambas soluciones son bastante aceptables, sencillas y eficaces.
ahora queremos usar UserNickname dentro de ListView . como puede ver, fetchNicknames devuelve un mapa de apodos, no solo un apodo. así que llamarlo cada vez es redundante. Algunas soluciones que podemos aplicar aquí:

  • mueva la llamada lógica fetchNicknames() al widget principal y guarde el resultado.
  • usando un administrador de caché.

La primera solución es aceptable pero tiene 2 problemas.
1: hace que UserNickname inútil porque ahora es solo un widget de texto y si desea usarlo en otro lugar, debe repetir lo que hizo en el widget principal (que tiene el ListView ) . la lógica para mostrar el apodo pertenece al UserNickname pero tenemos que moverlo por separado.
2 - podemos usar fetchNicknames() en muchos otros subárboles y es mejor almacenarlo en caché para toda la aplicación, no solo para una parte de la aplicación.

así que imagina que elegimos el administrador de caché y proporcionamos una clase CacheManager con InheritedWidgets o Provider .

después de agregar soporte para el almacenamiento en caché:

__ widget de gancho__

String useUserNickname(Id userid) {
  final context = useContext();
  final cache = Provider.of<CacheManager>(context);
  final name = useState("");
  useEffect(async () {
    name.value = "Loading...";
    var cachedValue = cache.get("nicknames");
    if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        cache.set("nicknames", result );
        cachedValue = result ;
    }
    final result = cachedValue[widget.userId];
    name.value = result ;
  }, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__ widget de estado__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";
  CacheManager cache;

   initState() {
     super.initState();
     fetchAndUpdate();
     this.cache = Provider.of<CacheManager>(context);
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      var cachedValue = this.cache.get("nicknames");
      if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        this.cache.set("nicknames", result );
        cachedValue = result ;
      }
      final result = cachedValue [widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

tenemos un servidor de socket que notifica a los clientes cuando los apodos cambian.

__ widget de gancho__

String useUserNickname(Id userid) {
  final context = useContext();
  final cache = Provider.of<CacheManager>(context);
  final notifications = Provider.of<ServiceNotifications>(context);
  final name = useState("");

  fetchData() async {
    name.value = "Loading...";
    var cachedValue = cache.get("nicknames");
    if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        cache.set("nicknames", result );
        cachedValue = result ;
    }
    final result = cachedValue[widget.userId];
    name.value = result ;
   }

  useEffect(() {
     final sub = notifications.on("nicknameChanges", fetchData);
     return () => sub.unsubscribe();
   }, [])

  useEffect(fetchData, [userId]);
  return name.value;
}

class UserNickname extends HookWidget {

  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    return Text(nickname);
  }
}

__ widget de estado__

class UserNickname extends Widget {
  final userId;
  // ... createState() ...
}

class UserNicknameState extends State {

  String nickname= "";
  CacheManager cache;
  ServerNotification notifications;
  CancelableOperation sub;

   initState() {
     super.initState();
     fetchAndUpdate();
     this.cache = Provider.of<CacheManager>(context);
     this.notifications = Provider.of<ServerNotification>(context);
     this.sub = notifications.on("nicknameChanges", fetchAndUpdate);
   }

   dispose() {
      super.dispose();
      this.sub.unsubscribe();
   }

   fetchAndUpdate() async {
      setState(() { this.nickname = "Loading..." });
      var cachedValue = this.cache.get("nicknames");
      if (cachedValue == null || cachedValue[widget.userId] == null) {
        final result = await fetchNicknames();
        this.cache.set("nicknames", result );
        cachedValue = result ;
      }
      final result = cachedValue [widget.userId];
      setState(() { this.nickname = result });
    }


 void didUpdateWidget(oldWidget) { 
     if (oldWidget.userId != widget.userId) {
        fetchAndUpdate();
     }
   }

  Widget build(BuildContext context) {
    return Text(this.nickname);
  }
}

hasta ahora, ambas implementaciones son aceptables y buenas. En mi opinión, el texto estándar en el estándar no es ningún problema. el problema surge cuando necesitamos un widget como UserInfo que tiene tanto el apodo como el avatar del usuario. tampoco podemos usar el widget UserNickname porque necesitamos mostrarlo en una oración como "Bienvenido [nombre de usuario]".

__ widget de gancho__

useFetchUserNickname(userId) // old code
useUserAvatar(userId) // implementation like `useFetchUserNickname`

class UserNickname extends HookWidget {
  final userId;

  Widget build(BuildContext context) {
    final nickname = useUserNickname(userId);
    final avatar = useUserAvatar(userId);
    return Row(
      children: [Image.network(avatar), Text(nickname)],
    );
  }
}

pero para el __stateful widget__ no podemos simplemente usar la lógica que escribimos. tenemos que mover la lógica a una clase (como Property que sugirió) y aún tenemos que escribir el pegamento del widget con la clase de propiedad nuevamente en el nuevo widget.

Si ve los cambios en los primeros 3 ejemplos, no cambiamos el widget en sí en absoluto porque los únicos cambios necesarios estaban en la lógica de estado y el único lugar que cambió fue todo, la lógica de estado.
esto nos dio una lógica de estado limpia (obstinada), componible y totalmente reutilizable que podemos usar en cualquier lugar.

En mi humilde opinión, el único problema es que llamar a useUserNickname da miedo porque una sola función puede hacer tanto.
pero en mis años de experiencia en reaccionar y usar flutter_hooks en 2 aplicaciones que están en producción rn (que usan ganchos en gran medida) demuestra que no tener una buena administración del estado (también probé MobX y otras soluciones de administración del estado, pero el pegamento en el widget siempre está ahí) es mucho más aterrador. No necesito escribir documentos de 5 páginas para cada pantalla en una aplicación de interfaz, es posible que deba agregar alguna característica pequeña en unos meses después del primer lanzamiento para que comprenda cómo funciona una página de mi aplicación. ¿La aplicación llama demasiado al servidor? tarea fácil Voy al gancho relacionado y lo cambio y toda la aplicación se corrige porque toda la aplicación usa ese gancho. podemos tener cosas similares en las aplicaciones sin usar ganchos con una buena abstracción, pero lo que estoy diciendo es que los ganchos son esa buena abstracción.

Estoy bastante seguro de que @gaearon puede

Al ver el ejemplo anterior, ninguno de los métodos anteriores (stateful y hook widget) es más eficaz que el otro. pero el punto es que uno de ellos anima a la gente a escribir el código de rendimiento.

también es posible actualizar solo el subárbol que necesitamos actualizar como StreamBuilder cuando hay demasiadas actualizaciones (por ejemplo, animaciones) con:

1 - simplemente creando un nuevo widget que sea una opción totalmente viable tanto para HookWidget como para StatefulWidget/StatelessWidget
2 - usando algo similar a HookWidgetBuilder en el paquete flutter_hooks porque los datos de los widgets padre e hijo están muy estrechamente acoplados.

Nota al margen : Realmente aprecio a @rrousselGit por discutir este tema y poner tanta energía en este tema. Realmente espero con ansias el resultado de estas conversaciones.

Creo que se me ocurre algo bastante genial / elegante, basado en el punto de partida de @Hixie . Todavía no estoy listo para compartir, pero me permite crear algunas muestras de código bastante decentes, que creo que serán más fáciles de comparar manzanas: manzanas en lugar de ganchos que parecen tan extraños.

Entonces, imagina que tenemos un StatefulWidget con esta firma:

class ExampleSimple extends StatefulWidget {
  final Duration duration1;
  final Duration duration2;
  final Duration duration3;

  const ExampleSimple({Key key, this.duration1, this.duration2, this.duration3}) : super(key: key);

  <strong i="9">@override</strong>
  _ExampleSimpleState createState() => _ExampleSimpleState();
}

Si tuviéramos que implementar el estado usando controladores animadores vanilla, obtendríamos algo como:

class _ExampleSimpleVanillaState extends State<ExampleSimpleVanilla> with SingleTickerProviderStateMixin {
  AnimationController _anim1;
  AnimationController _anim2;
  AnimationController _anim3;

  <strong i="13">@override</strong>
  void initState() {
    _anim1 = AnimationController(duration: widget.duration1, vsync: this);
    _anim1.forward();
    _anim2 = AnimationController(duration: widget.duration2, vsync: this);
    _anim2.forward();
    _anim3 = AnimationController(duration: widget.duration3, vsync: this);
    _anim3.forward();
    super.initState();
  }

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: _anim2.value * 20, horizontal: _anim3.value * 30,),
      color: Colors.red.withOpacity(_anim1.value),
    );
  }

  <strong i="15">@override</strong>
  void didUpdateWidget(ExampleSimpleVanilla oldWidget) {
    if (oldWidget.duration1 != widget.duration1) {
      _anim1.duration = widget.duration1;
    }
    if (oldWidget.duration2 != widget.duration2) {
      _anim1.duration = widget.duration1;
    }
    if (oldWidget.duration3 != widget.duration3) {
      _anim1.duration = widget.duration1;
    }
    super.didUpdateWidget(oldWidget);
  }

  <strong i="16">@override</strong>
  void dispose() {
    _anim1.dispose();
    _anim2.dispose();
    _anim3.dispose();
    super.dispose();
  }
}

Si lo creamos usando StatefulProperty, obtenemos algo más como esto:

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulAnimationProperty _anim1;
  StatefulAnimationProperty _anim2;
  StatefulAnimationProperty _anim3;

  <strong i="6">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _anim1 = initProperty(_anim1, StatefulAnimationProperty(duration: widget.duration1, playOnInit: true));
    _anim2 = initProperty(_anim2, StatefulAnimationProperty(duration: widget.duration2, playOnInit: true));
    _anim3 = initProperty(_anim3, StatefulAnimationProperty(duration: widget.duration3, playOnInit: true));
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="7">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: _anim2.controller.value * 20, horizontal: _anim3.controller.value * 30,),
      color: Colors.red.withOpacity(_anim1.controller.value),
    );
  }
}

Algunas notas sobre las diferencias aquí:

  1. En la parte superior, una tiene 20 líneas, la otra 45. Una tiene 1315 caracteres y la otra 825. Solo 3 líneas y 200 caracteres importan en esta clase (lo que está sucediendo en la construcción), por lo que esto ya es una mejora masiva en la señal. : relación de
  2. Las opciones de vainilla tienen múltiples puntos donde se pueden crear errores. Olvídese de deshacerse, u olvide manejar didChange, o cometa un error en didChange, y tiene un error en su código base. Esto empeora cuando se utilizan varios tipos de controladores. Luego, tiene funciones únicas que derriban objetos de todos los tipos diferentes, que no se nombrarán de manera agradable y secuencial de esta manera. Eso se complica y es bastante fácil cometer errores o perder entradas.
  3. La opción de vainilla no proporciona ningún método para reutilizar patrones o lógica comunes, como playOnInit, así que tengo que duplicar esta lógica o crear alguna función personalizada en una sola clase que quiera usar un Animator.
  4. No hay necesidad de entender SingleTickerProviderMixin aquí, que es 'mágico' y ofuscó lo que es un Ticker para mí durante meses (en retrospectiva, debería haber leído la clase, pero cada tutorial solo dice: Agregue esta mezcla mágica). Aquí puede mirar directamente el código fuente de StatefulAnimationProperty y ver cómo los controladores de animación usan un proveedor de ticker directamente y en contexto.

En su lugar, debe comprender lo que hace StatefulPropertyManager, pero lo que es crucial, esto se aprendió una vez y se aplicó a objetos de cualquier tipo, SingleTickerProviderMixin es principalmente específico para usar Animator Controllers, y cada controlador puede tener su propia mezcla para facilitar el uso, lo que se vuelve complicado. El solo hecho de tener "StatefulObjects" discretos que sepan todo esto (¡como lo hace un constructor!), Es mucho más limpio y escala mejor.

El código de StatefulAnimationProperty se vería así:

class StatefulAnimationProperty extends BaseStatefulProperty<StatefulAnimationProperty> implements TickerProvider {
  final Duration duration;
  final TickerProvider vsync;
  final bool playOnInit;

  StatefulAnimationProperty({<strong i="7">@required</strong> this.duration, <strong i="8">@required</strong> this.vsync, this.playOnInit = false});

  AnimationController get controller => _controller;
  AnimationController _controller;

  Ticker _ticker;

  <strong i="9">@override</strong>
  Ticker createTicker(onTick) {
    _ticker ??= Ticker((elapsed)=>handleTick(elapsed, onTick));
    return _ticker;
  }

  handleTick(Duration elapsed, TickerCallback onTick) {
    managerWidget.buildWidget(); //TODO: This just calls setState() on the host widget. Is there some better way to do this?
    onTick(elapsed);
  }

  <strong i="10">@override</strong>
  void init(StatefulAnimationProperty old) {
    _controller = old?.controller ??
        AnimationController(
          duration: duration ?? Duration(seconds: 1),
          vsync: vsync ?? this,
        );
    if (playOnInit && old?.controller == null) {
      _controller.forward();
    }
    super.init(old);
  }

  <strong i="11">@override</strong>
  void update(StatefulAnimationProperty old) {
    if (duration != old.duration) {
      _controller.duration = duration;
    }
    if (vsync != old.vsync) {
      _controller.resync(vsync);
    }
    super.update(old);
  }

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

Finalmente, vale la pena señalar que la legibilidad se puede mejorar aún más con el uso de extensiones, por lo que podríamos tener algo como:

  void initStatefulProperties({bool firstRun = false}) {
    _anim1.init(duration: widget.duration1, playOnInit: true);
    _anim2.init(duration: widget.duration2, playOnInit: true);
    _anim3.init(duration: widget.duration3, playOnInit: true);
    super.initStatefulProperties(firstRun: firstRun);
  }

[Editar] Como para hacer mi propio punto, mi ejemplo de vainilla tiene un error. Olvidé pasar la duración correcta a cada animador en didUpdateWidget. ¿Cuánto tiempo nos habría llevado encontrar ese error en la naturaleza, si nadie lo hubiera notado en la revisión del código? ¿Alguien no lo vio mientras leía? Dejarlo porque es un ejemplo perfecto de lo que sucede en el mundo real.

Aquí hay una vista de pájaro, con el texto estándar marcado en rojo:
image

Esto no sería tan malo si fuera pura plantilla y el compilador le gritara si faltaba. ¡Pero todo es opcional! Y cuando se omite, crea errores. Así que esta es una práctica muy mala y no SECA en absoluto. Aquí es donde entran los constructores, pero solo son buenos para casos de uso simplistas.

Lo que creo que es muy interesante acerca de esto es cómo cien líneas y una simple combinación de State hacen que un montón de clases existentes sean redundantes. Prácticamente no hay necesidad de usar TickerProviderMixins, por ejemplo. TweenAnimationBuilder casi nunca necesita ser usado, a menos que realmente _desea_ crear un subcontexto. Muchos puntos débiles tradicionales, como la gestión de controladores de enfoque y controladores de entrada de texto, se alivian sustancialmente. El uso de Streams se vuelve mucho más atractivo y menos complicado. En toda la base del código, el uso de Builders podría reducirse en general, lo que conducirá a árboles más fáciles de asimilar.

También hace que sea _extremely_ fácil crear sus propios objetos de estado personalizados, como el ejemplo de FetchUser mencionado anteriormente, que actualmente básicamente requiere un constructor.

Creo que esta sería una pregunta muy interesante para hacer en la próxima encuesta para desarrolladores de Flutter.

Sería un buen comienzo. Divida este problema en diferentes partes / preguntas y vea si es un problema real que los desarrolladores de Flutter desean resolver.

Una vez que esté claro, esta conversación será más fluida y enriquecedora.

Las reacciones de Emoji debajo de cada comentario dan una idea clara de si la comunidad ve esto como un problema o no. La opinión de los desarrolladores que leyeron más de 250 comentarios largos sobre este tema significa mucho en mi humilde opinión.

@esDotDev Eso es similar a algunas de las ideas con las que he estado jugando, aunque me gusta su idea de que la propiedad en sí sea el proveedor de ticker, no lo había considerado. Una cosa que falta en su implementación y que creo que deberíamos agregar es el manejo de TickerMode (que es el punto de TickerProviderStateMixin).

Lo principal con lo que estoy luchando es cómo hacer esto de una manera eficiente. Por ejemplo, ValueListenableBuilder toma un argumento secundario que se puede usar para mejorar el rendimiento de manera mensurable. No veo una forma de hacer eso con el enfoque de propiedad.

@Hixie
Entiendo que las pérdidas de eficiencia con enfoques como este parecen ser inevitables. Pero me gusta la mentalidad de Flutter de optimizar después de que perfilas tu código. Hay muchas aplicaciones que se beneficiarían de la claridad y concisión del enfoque de propiedad. La opción de perfilar su código y refactorizar en constructores o separar una parte del widget en su propio widget siempre está ahí.

La documentación solo debería reflejar las mejores prácticas y aclarar las compensaciones.

Lo principal con lo que estoy luchando es cómo hacer esto de una manera eficiente. Por ejemplo, ValueListenableBuilder toma un argumento secundario que se puede usar para mejorar el rendimiento de manera mensurable. No veo una forma de hacer eso con el enfoque de propiedad.

Hm, creo que todo el objetivo de las Propiedades es para objetos no visuales. Si algo quiere tener un espacio de contexto en el árbol, entonces esa cosa debería ser un constructor (en realidad, ¿estas son las únicas cosas que ahora deberían ser constructores, creo?)

Entonces tendríamos una propiedad StatefulValueListenableProperty que usamos la mayor parte del tiempo cuando solo queremos vincular la vista completa. Luego también tenemos un ValueListenableBuilder en la posibilidad de que queramos que se reconstruya alguna subsección de nuestro árbol.

Esto también aborda el problema del anidamiento, ya que usar un constructor como nodo hoja no es tan perjudicial para la legibilidad como anidar 2 o 3 en la parte superior de su árbol de widgets.

@TimWhiting Una gran parte de la filosofía de diseño de Flutter es guiar a las personas hacia la elección correcta. Me gustaría evitar alentar a las personas a seguir un estilo del que luego tendrían que alejarse para obtener un mejor desempeño. Puede ser que no haya forma de abordar todas las necesidades a la vez, pero definitivamente deberíamos intentarlo.

@Hixie
¿Qué pasa con algo como esto para los constructores?

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulAnimationProperty _anim1;
  StatefulAnimationBuilderProperty _anim2;

  <strong i="7">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _anim1 = initProperty(_anim1, StatefulAnimationProperty(duration: widget.duration1, playOnInit: true));
    _anim2 = initProperty(_anim3, StatefulAnimationBuilderProperty(duration: widget.duration3, playOnInit: true));
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="8">@override</strong>
  Widget build(BuildContext context) {
    return Container(
      color: Colors.red.withOpacity(_anim1.controller.value),
      child: _anim2(child: SomeChildWidget()),
    );
  }
}

¿Puedes elaborar? No estoy seguro de entender la propuesta.

Creo que está diciendo que StatefulProperty podría proporcionar un método de compilación opcional para propiedades que tienen algún componente visual:

return Column(
   children: [
      TopContent(),
      _valueProperty.build(SomeChildWidget()),
   ]
)

Que es bastante 🔥 imo,

Sí, no sé si eso funcionaría, pero el método de construcción tomaría un niño como un constructor normal, excepto que las otras propiedades del constructor están establecidas por la propiedad.
Si necesita el contexto del constructor, el método de construcción acepta un argumento del constructor que proporciona el contexto.

Bajo el capó, el método podría simplemente crear un constructor normal con las propiedades especificadas, y pasar el argumento secundario al constructor normal y devolverlo.

Suponga que tiene este código:

Widget build(BuildContext context) {
  return ExpensiveParent(
    child: ValueListenableBuilder(
      valueListenable: foo,
      child: ExpensiveChild(),
      builder: (BuildContext context, value, Widget child) {
        return SomethingInTheMiddle(
          value: value,
          child: child,
        );
      }
    ),
  );
}

... ¿cómo convertirías eso?

@esDotDev Me gusta tu idea de que la propiedad en sí sea el proveedor de ticker, no lo había considerado.

Uno de los aspectos más fuertes de este enfoque de estilo gancho es que puede encapsular _completamente_ la lógica con estado, sea lo que sea. Entonces, en estos casos, la lista completa para AC es:

  1. Crea el aire acondicionado
  2. Dale un ticker
  3. Manejar cambios de widgets
  4. Manejar la limpieza de ac y ticker
  5. Reconstruir vista en tick

Actualmente las cosas están divididas con los desarrolladores manejando (o no manejando) 1,3,4 de forma manual y repetitiva, y el SingleTickerProviderMixin semi-mágico se encarga del 2 y 5 (con nosotros pasando 'this' como vsync, ¡lo que me confundió durante meses! ). Y SingleTickerProviderMixin en sí es claramente un intento de solución para este tipo de problema, de lo contrario, ¿por qué no ir hasta el final y hacer que implementemos TickerProvider para cada clase? Sería mucho más claro.

Suponga que tiene este código:

Widget build(BuildContext context) {
  return ExpensiveParent(
    child: ValueListenableBuilder(
      valueListenable: foo,
      child: ExpensiveChild(),
      builder: (BuildContext context, value, Widget child) {
        return SomethingInTheMiddle(
          value: value,
          child: child,
        );
      }
    ),
  );
}

... ¿cómo convertirías eso?

class _ExampleSimpleState extends State<ExampleSimple> with StatefulPropertyManager {
  StatefulValueListenableBuilder _fooBuilder;

  <strong i="10">@override</strong>
  void initStatefulProperties({bool firstRun = false}) {
    _fooBuilder = initProperty(StatefulValueListenableProperty(valueListenable: widget.foo)); 
    super.initStatefulProperties(firstRun: firstRun);
  }

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    return ExpensiveParent(
      child: SomethingInTheMiddle(
        _fooBuilder.value,
        _fooBuilder.builder(childBuilder: () => ExpensiveChild()),
      ),
    );
  }
}

@Hixie
Gracias por el ejemplo. Hice mi mejor intento. Podría haberme perdido algo.

Es importante tener en cuenta que el constructor almacena en caché al niño. La pregunta sería ¿cuándo es necesario reconstruir realmente al niño? Creo que esa era la pregunta que estabas tratando de plantear.

@Hixie has visto https://github.com/flutter/flutter/issues/51752#issuecomment -671104377
Creo que hay algunos puntos realmente buenos.
Hoy construyo algo con mucho ValueListenableBuilder y solo puedo decir que no es agradable de leer.

@Hixie
Gracias por el ejemplo. Hice mi mejor intento. Podría haberme perdido algo.

No creo que esto funcione porque la propiedad se une al estado en el que está definida, por lo que ExpensiveParent siempre se está reconstruyendo aquí. Entonces creo que el almacenamiento en caché del niño también es problemático, ya que en el ejemplo de Builder solo sabría reconstruir el niño cuando se construye el estado principal, pero en este método, la propiedad no sabe cuándo invalidar su caché (pero tal vez esto es solucionable?)

Pero tbh, este es el caso de uso perfecto para los constructores, cuando desea introducir un nuevo contexto. Creo que es bastante elegante tener el contexto de StatefulProperties (estado puro) y StatefulWidgets (combinación de estado y diseño).

Cada vez que esté creando intencionalmente un subcontexto, por definición lo hará más abajo en su árbol, lo que ayuda a combatir uno de los principales inconvenientes para los constructores (anidación forzada en todo el árbol)

@escamoteur (y @sahandevs que escribieron ese comentario) sí, estaba estudiando eso antes. Creo que ciertamente ayuda a mostrar el tipo de lógica que la gente quiere eliminar. Sin embargo, creo que el ejemplo en sí es un poco dudoso, ya que esperaría que la mayor parte de la lógica (por ejemplo, todo lo relacionado con el almacenamiento en caché) esté en la lógica de negocios del estado de la aplicación y no se acerque a los widgets. Tampoco veo una buena manera de obtener la sintaxis tan breve como se propone en ese comentario sin interrumpir la recarga en caliente (por ejemplo, si cambia la cantidad de ganchos que está usando, no está claro cómo podrían mantenerse con estado en una recarga ).

Dicho esto, creo que el trabajo que @esDotDev y @TimWhiting muestran arriba es muy interesante y podría resolver estos problemas. No es tan breve como Hooks, pero es más confiable. Creo que tendría mucho sentido empaquetar algo así, incluso podría ser un favorito de Flutter si funciona bien. No estoy seguro de que tenga sentido como característica central del marco porque la mejora no es _ tan_ sustancial una vez que se tiene en cuenta la complejidad en torno a las propiedades del edificio y el impacto en el rendimiento, y cómo diferentes personas preferirían diferentes estilos. Al final del día, debería estar bien que diferentes personas usen diferentes estilos, pero no queremos que el marco principal tenga varios estilos, eso es engañoso para los nuevos desarrolladores.

También se puede argumentar que la forma correcta de aprender Flutter es primero comprender los widgets y luego aprender las herramientas que los abstraen (Hooks o lo que sea), en lugar de saltar directamente a la sintaxis abstracta. De lo contrario, perderá un componente clave de cómo funciona el sistema que probablemente lo desvíe en términos de escritura de código de rendimiento.

Tampoco veo una buena manera de obtener la sintaxis tan breve como se propone en ese comentario sin interrumpir la recarga en caliente (por ejemplo, si cambia la cantidad de ganchos que está usando, no está claro cómo podrían mantenerse con estado en una recarga ).

Los ganchos funcionan con la recarga en caliente sin problemas.
El primer gancho con un runtimeType no coincidente hace que se destruyan todos los ganchos posteriores.

Esto admite agregar, eliminar y reordenar.

Creo que hay un argumento de que la abstracción completa es preferible a la parcial que existe actualmente.

Si quiero entender cómo funciona Animator en el contexto de una propiedad, lo ignoro por completo o me meto en él y todo está ahí, autónomo y coherente.

Si quiero comprender cómo funciona AnimatorController en el contexto de un StatefulWidget, necesito (me veo obligado) a comprender los ganchos básicos del ciclo de vida, pero luego me libero de comprender cómo funciona el mecanismo de tick subyacente. Esto es lo peor de ambos mundos en cierto sentido. No hay suficiente magia para que 'simplemente funcione', pero sí lo suficiente para confundir a los nuevos usuarios y obligarlos a confiar ciegamente en algún mixin (que en sí mismo es un concepto nuevo para la mayoría) y una propiedad mágica de vsync.

No estoy seguro de otros ejemplos en la base del código, pero esto se aplicaría a cualquier situación en la que se hayan proporcionado algunos mixins auxiliares para StatefulWidget, pero todavía hay algún otro bootstrapping que siempre debe realizarse. Los desarrolladores aprenderán el bootstraping (la parte aburrida) e ignorarán Mixin (la parte interesante / compleja)

Dicho esto, creo que el trabajo que @esDotDev y @TimWhiting muestran arriba es muy interesante y podría resolver estos problemas. No es tan breve como Hooks, pero es más confiable

¿Cómo es esto más confiable?

Todavía no podemos crear / actualizar propiedades condicionalmente o fuera de su ciclo de vida, ya que podríamos entrar en un mal estado. Por ejemplo, llamar a una propiedad de forma condicional no eliminará la propiedad cuando la condición sea falsa.
Y todas las propiedades aún se reevalúan en cada reconstrucción.

Pero causa múltiples problemas, como obligar a los usuarios a usar ! todas partes después de NNBD o potencialmente permitir que los usuarios accedan a una propiedad antes de que se actualice.

Por ejemplo, ¿qué pasa si alguien lee una propiedad dentro de didUpdateWidget ?

  • ¿Se ejecutó initProperties antes del ciclo de vida? Pero eso significa que es posible que tengamos que actualizar las propiedades varias veces por compilación.
  • ¿Se ejecutó initProperties después de didUpdateWidget? Luego, el uso de propiedades dentro de didUpdateWidget puede llevar a un estado desactualizado

Entonces, al final, tenemos todos los problemas de los ganchos, pero:

  • no podemos usar Propiedades dentro de `StatelessWidget. Entonces, la legibilidad de StreamBuilder / ValueListenableBuilder / ... sigue siendo un problema, que era la principal preocupación.
  • hay numerosos casos extremos
  • es más difícil crear propiedades personalizadas (no podemos simplemente extraer un montón de propiedades en una función)
  • es más difícil optimizar las reconstrucciones

Al final, el ejemplo dado no es diferente en comportamiento de:

class Example extends StatelessWidget {
  <strong i="29">@override</strong>
  Widget build(context) {
    final value1 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));
    final value2 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));
    final value3 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    return Container(
     margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
     color: Colors.red.withOpacity(value1),
      child: _anim2(child: SomeChildWidget()),
    );
  }
}

Pero esta sintaxis admite muchas más cosas, como:

Devoluciones anticipadas:

class Example extends StatelessWidget {
  <strong i="34">@override</strong>
  Widget build(context) {
    final value1 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    if (condition) {
      return Container();
    }

    final value2 = keyword TweenAnimationBuilder(tween: Tween(begin: 0, end: 1));

    ...
  }
}

que eliminaría value2 cuando condition cambie a falso

Extraer paquetes de constructores en una función:

Widget build(context) {
  final foo = keyword FooBuilder();
  final bar = keyword BarBuilder();

  return Text('$foo $bar');
}

se puede cambiar a:

Builder<String> LabelBuilder() builder* {
  final foo = keyword FooBuilder();
  final bar = keyword BarBuilder();

  return '$foo $bar';
}

Widget build(context) {
  final label = keyword LabelBuilder();

  return Text(label);
}

Optimizar reconstrucciones

El parámetro child todavía es factible:

Widget build(context) {
  final value = keyword StreamBuilder();

  return Builder(
    builder: (context, child) {
      final value2 = keyword TweenAnimationBuilder();
      final value = keyword ValueListenableBuilder();

      return Whatever(child: child);
    },
    child: ExpensiveChild()
  );
}

Como parte del lenguaje, incluso podríamos tener azúcar de sintaxis para esto:

Widget build(context) {
  return Scaffold(
    body: {
      final value = keyword TweenAnimationBuilder();
      final value2 = keyword ValueListenableBuilder();

      return Text();
    },
  );
}

Bonificación: como función de idioma, se admiten llamadas condicionales

Como parte del lenguaje, podemos soportar tal escenario:

Widget build(context) {
  String label;

  if (condition) {
    label = keyword LabelBuilder();
  } else {
    label = keyword AnotherBuilder();
  }

  final value2 = keyword WhateverBuilder();

  return ...
}

No es muy útil, pero es compatible, ya que, dado que la sintaxis está compilada, puede diferenciar cada uso de keyword basándose en metadatos que de otro modo no están disponibles.

Con respecto a la legibilidad de los constructores, aquí está el ejemplo anterior, pero hecho con los constructores. Resuelve todas las necesidades de confiabilidad y uso del código, pero mira lo que le ha hecho a mi pobre árbol de widgets: '(

class _ExampleSimpleBuilderState extends State<ExampleSimpleBuilder> {
  <strong i="6">@override</strong>
  Widget build(BuildContext context) {
    return TweenAnimationBuilder<double>(
        tween: Tween(begin: 0, end: 1),
        duration: widget.duration1,
        builder: (_, value1, __) {
          return TweenAnimationBuilder<double>(
              tween: Tween(begin: 0, end: 1),
              duration: widget.duration2,
              builder: (_, value2, __) {
                return TweenAnimationBuilder<double>(
                    tween: Tween(begin: 0, end: 1),
                    duration: widget.duration3,
                    builder: (_, value3, __) {
                      return Container(
                        margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
                        color: Colors.red.withOpacity(value1),
                      );
                    });
              });
        });
  }
}

Es mucho más difícil (al menos para mi ojo) detectar el código que importa. Además, tuve que empezar de nuevo como 3 veces al escribir esto, ya que continuamente me confundía en cuanto a qué corchete pertenecía dónde, dónde deberían ir mis semi-colans, etc. Los constructores anidados no son divertidos para escribir o trabajar en ellos. Un punto y coma incorrecto y dartfmt resbala completamente aplastando todo.

¿Cómo es esto más confiable?

Este es un ejemplo perfecto de por qué este _debería_ ser un complemento principal en mi opinión. El conocimiento de dominio requerido aquí es _profundo_. Tengo el conocimiento de scripting para implementar un sistema de almacenamiento en caché simple como este, ni siquiera tengo el conocimiento de dominio para conocer todos los casos extremos que pueden suceder, o los malos estados en los que podemos entrar. Aparte de Remi, creo que hay como 4 desarrolladores en el mundo fuera del equipo de Flutter que saben estas cosas. (exagerando obviamente).

El tema de la compatibilidad con los widgets sin estado es bueno. Por un lado, lo entiendo, los StatefulWidgets son extrañamente detallados. Por otro lado, aquí realmente estamos hablando de pura verbosidad. No hay errores que puedan ocurrir por tener que definir 2 clases, no hay forma de que lo estropees, el compilador no te lo permite, nunca hay nada interesante que quiera hacer en StatelessWidget. Así que no estoy convencido de que este sea un problema importante ... Ciertamente sería bueno tenerlo, pero es el último 5% en mi opinión, no es algo en lo que quedarse atascado.

Por otro lado ... esa sintaxis de remi con soporte de palabras clave es absolutamente hermosa e increíblemente flexible / poderosa. Y si te brinda soporte de StatelessWidget de forma gratuita, eso es solo un extra 🔥

Apoyar StatelessWidget es un gran problema en mi opinión. Opcional, pero muy bueno.

Si bien estoy de acuerdo en que no es crítico, la gente ya se está peleando por usar funciones en lugar de StatelessWidget.
Requerir que las personas usen un StatefulWidget para usar Builders (ya que la mayoría de los Builders probablemente tendrían una propiedad equivalente) solo profundizaría el conflicto.

No solo eso, sino que, en un mundo donde podemos crear funciones de orden superior en dart (https://github.com/dart-lang/language/issues/418), podríamos deshacernos de las clases por completo:

<strong i="9">@StatelessWidget</strong>
Widget Example(BuildContext context, {Key key, String param}) {
  final value = keyword StreamBuilder();

  return Text('$value');
}

luego se usa como:

Widget build(context) {
  // BuildContext and Key are automatically injected
  return Example(param: 'hello');
}

Esto es algo que es compatible con funcional_widget , que es un generador de código en el que escribe una función y genera una clase para usted, que también admite HookWidget .

La diferencia es que tener soporte para funciones de orden superior en Dart eliminaría la necesidad de generar código para admitir dicha sintaxis.

Supongo que lo que @Hixie quiso decir con más confiable, es que no sufre del orden de operaciones / problema condicional que tienen los ganchos, ya que eso es muy 'poco confiable' desde un punto de vista arquitectónico (aunque me doy cuenta de que es una regla fácil de aprender y no violar una vez aprendido).

Pero tampoco su propuesta con la palabra clave. Creo que el caso de una nueva palabra clave es bastante sólido:

  • Más flexible y componible que injertar en State
  • Sintaxis aún más sucinta
  • Funciona en Stateless, que es una muy buena opción para tener

Lo que no me gusta de esto es que nos preocupamos por el costo de establecer propiedades en un objeto simple varias veces / compilación, pero luego abogamos por una solución que básicamente creará un millón de niveles de contexto y un montón de costos de diseño. ¿Estoy entendiendo mal?

La otra desventaja es esta idea de magia. Pero si va a hacer algo mágico, creo que una nueva palabra clave es una forma eficaz de hacerlo, ya que facilita destacar y llamar a la comunidad, y explicar qué es y cómo funciona. Básicamente, sería de lo que todos hablarían durante el próximo año en Flutter y estoy seguro de que veríamos una explosión de complementos geniales que surgirán de él.

Supongo que lo que @Hixie quiso decir con más confiable, es que no sufre del orden de operaciones / problema condicional que tienen los ganchos, ya que eso es muy 'poco confiable' desde un punto de vista arquitectónico (aunque me doy cuenta de que es una regla fácil de aprender y no violar una vez aprendido).

Pero los ganchos tampoco sufren este problema, ya que son analizables estáticamente y, por lo tanto, podemos tener un error de compilación cuando se utilizan incorrectamente.

Esto no es un problema

De manera similar, si los errores personalizados no son aceptables, entonces, como mencioné anteriormente, Property sufre exactamente el mismo problema.
No podemos escribir razonablemente:

Property property;

<strong i="12">@override</strong>
void initProperties() {
  if (condition) {
    property = init(property, MyProperty());
  }
}

ya que cambiar condition de verdadero a falso no eliminará la propiedad.

Realmente tampoco podemos llamarlo en un bucle. Realmente no tiene sentido, ya que es una tarea de una sola vez. ¿Cuál es el caso de uso de ejecutar la propiedad en un bucle?

Y el hecho de que podamos leer las propiedades en cualquier orden suena peligroso
Por ejemplo, podríamos escribir:

Property first;
Property second;

<strong i="20">@override</strong>
void initProperties() {
  // The state of first depends on second, but second is updated after first
  // So we could end up in a bad state, similar to how the build method of a Widget should depend
  // on the context.size
  first = init(property, MyProperty(second?.value));

  second = init(property, Whatever());
}
> class _ExampleSimpleBuilderState extends State<ExampleSimpleBuilder> {
>   <strong i="5">@override</strong>
>   Widget build(BuildContext context) {
>     return TweenAnimationBuilder<double>(
>         tween: Tween(begin: 0, end: 1),
>         duration: widget.duration1,
>         builder: (_, value1, __) {
>           return TweenAnimationBuilder<double>(
>               tween: Tween(begin: 0, end: 1),
>               duration: widget.duration2,
>               builder: (_, value2, __) {
>                 return TweenAnimationBuilder<double>(
>                     tween: Tween(begin: 0, end: 1),
>                     duration: widget.duration3,
>                     builder: (_, value3, __) {
>                       return Container(
>                         margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30),
>                         color: Colors.red.withOpacity(value1),
>                       );
>                     });
>               });
>         });
>   }
> }

Este es un ejemplo tan extraño. ¿Estás seguro de que AnimatedContainer ya no puede hacer esto?

Por supuesto. El ejemplo aquí es utilizar 3 animaciones en algún widget para hacer "X". La X se simplifica intencionalmente en el ejemplo para resaltar la cantidad de repetición.

No se concentre en cómo los estoy usando. En un ejemplo real, el "núcleo" del widget sería de cien líneas o algo así, las propiedades animadas no serían tan simples y tendríamos múltiples manejadores y otras funciones definidas. Supongamos que estoy haciendo algo que no es manejado por uno de los widgets implícitos (no es difícil ya que aparte de AnimatedContainer, son extremadamente de un solo propósito).

El punto es que cuando se construye algo como esto, los constructores no funcionan muy bien, ya que, para empezar, lo colocan en un agujero de legibilidad (y capacidad de escritura), por lo que son muy adecuados para casos de uso simples, no "componen" bien. Componer siendo la composición de 2 o más cosas.

No se concentre en cómo los estoy usando. En un ejemplo real, ...

... y de vuelta al punto de partida. ¿Por qué no traes un ejemplo real ?

¿Necesita un ejemplo real del uso de animaciones complejas?
https://github.com/gskinnerTeam/flutter_vignettes

Mostrar alguna animación compleja arbitraria no haría más que ofuscar el ejemplo. Es suficiente decir que hay muchos casos de uso para usar múltiples animadores (o cualquier otro objeto con estado que pueda imaginar) dentro de algún widget

Por supuesto. El ejemplo aquí es utilizar 3 animaciones en algún widget para hacer "X". La X se simplifica intencionalmente en el ejemplo para resaltar la cantidad de repetición.

el "núcleo" del widget sería de cien líneas o algo así

En otra publicación publicaste un ejemplo con texto estándar que oscurece el "núcleo", pero ¿ahora nos dices que el núcleo sería de cientos de líneas? Entonces, en realidad, ¿el texto estándar sería minúsculo en comparación con el núcleo? No puedes tener las dos cosas.
Estás cambiando constantemente tus argumentos.

No se concentre en cómo los estoy usando. En un ejemplo real, ...

... y de vuelta al punto de partida. ¿Por qué no traes un ejemplo real ?

Probablemente porque se necesita mucho tiempo para crear un ejemplo real cuando se juega con varias ideas. La intención es que el lector imagine cómo podría usarse en una situación real, sin mencionar que hay formas de evitarlo. Por supuesto, se puede usar un contenedor animado, pero ¿y si no se pudiera? ¿Y si fuera demasiado complejo para hacer solo con un contenedor animado?

Ahora, que los escritores no usan ejemplos verdaderamente reales, que se pueden demostrar que son buenos o malos, no tengo ninguna opinión al respecto, solo estoy comentando la tendencia en este hilo de traer mejoras a problemas que no Resuelva completamente el problema en cuestión. Esto parece ser una fuente importante de confusión entre los defensores de los ganchos y los oponentes, ya que cada uno parece estar hablando más allá del otro hasta cierto punto, por lo que apoyo la propuesta de Hixie de crear algunas aplicaciones reales de modo que un oponente no pueda decir que un ejemplo "real" fue no se muestra y un proponente no puede decir que uno simplemente debería imaginar un escenario del mundo real.

Creo que dije que sería una tontería tener una clase de 100 líneas, donde la mitad es estándar. Que es exactamente lo que estoy describiendo aquí. El núcleo, por grande que sea, no debería quedar oculto por un montón de ruido, lo que sin duda ocurre cuando se utilizan varios constructores.

Y la razón es la capacidad de escaneo, la legibilidad y el mantenimiento en una gran base de código. No es la escritura de las líneas, aunque escribir en constructores es una pérdida de productividad debido a la tendencia a meterse en el infierno de las llaves.

En otra publicación publicaste un ejemplo con texto estándar que oscurece el "núcleo", pero ¿ahora nos dices que el núcleo sería de cientos de líneas? Entonces, en realidad, ¿el texto estándar sería minúsculo en comparación con el núcleo? No puedes tener las dos cosas.
Estás cambiando constantemente tus argumentos.

Una vez más, este problema no se trata de un texto estándar, sino de legibilidad y reutilización.
No importa si tenemos 100 líneas.
Lo que importa es cuán legibles / mantenibles / reutilizables son estas líneas.

Incluso si el argumento fuera sobre repetición, ¿por qué debería yo, el usuario, tolerar tal repetición en cualquier caso, dada una forma suficientemente equivalente de expresar lo mismo? La programación se trata de crear abstracciones y automatizar el trabajo, realmente no veo el sentido de rehacer lo mismo una y otra vez en varias clases y archivos.

¿Necesita un ejemplo real del uso de animaciones complejas?
https://github.com/gskinnerTeam/flutter_vignettes

Seguramente, no puede esperar que profundice en todo su proyecto. ¿Qué archivo debo mirar exactamente?

Mostrar alguna animación compleja arbitraria no haría más que ofuscar el ejemplo.

Exactamente lo contrario. Mostrando un poco de animación compleja arbitraria que no puede ser resuelta por ninguna solución existente sería el ejemplo, y eso es lo que hace que pedir Hixie, creo.

Simplemente escanee los gifs y comience a imaginar cómo podría construir algunas de esas cosas. Ese repositorio es en realidad 17 aplicaciones independientes. Tampoco puede esperar que le escriba alguna animación arbitraria solo para demostrarle que pueden existir animaciones complejas. Los he estado construyendo durante 20 años comenzando en Flash, cada uno diferente al anterior. Y no es específico de las animaciones de todos modos, son solo la API más simple y familiar para ilustrar un punto más amplio.

Como sabes, cuando usas un animador, hay como 6 cosas que debes hacer cada vez, ¿pero también necesita ganchos del ciclo de vida? Ok, ahora extiende eso a CUALQUIER COSA que tenga 6 pasos que debes hacer cada vez ... Y debes usarlo en 2 lugares. O necesita usar 3 de ellos a la vez. Es tan obvio que es un problema a primera vista, no sé qué más puedo agregar para explicarlo.

La programación se trata de crear abstracciones y automatizar el trabajo, realmente no veo el sentido de rehacer lo mismo una y otra vez en varias clases y archivos.

__Todos__? Entonces, ¿el rendimiento, la mantenibilidad no es relevante?

Llega un momento en que "automatizar" un trabajo y "hacer" un trabajo es lo mismo.

Amigos, está bien si no tienen el tiempo o la inclinación para crear ejemplos reales, pero por favor, si no están interesados ​​en crear ejemplos para explicar el problema, tampoco deben esperar que las personas se sientan obligadas a resolver el problema ( que es mucho más trabajo que crear ejemplos para mostrar el problema). Nadie aquí está obligado a hacer nada por nadie, es un proyecto de código abierto en el que todos intentamos ayudarnos unos a otros.

@TimWhiting, ¿le importaría poner un archivo de licencia en su https://github.com/TimWhiting/local_widget_state_approaches repositorio? Algunas personas no pueden contribuir sin una licencia aplicable (BSD, MIT o similar idealmente).

¿Por qué debería yo, el usuario, tolerar tal repetición en cualquier caso, dada una forma suficientemente equivalente de expresar lo mismo?

Sí, la capacidad de mantenimiento y el rendimiento son importantes. Quiero decir, cuando hay una solución equivalente, deberíamos elegir la que tenga menos repetición, sea más fácil de leer, sea más reutilizable, etc. Eso no quiere decir que los ganchos sean la respuesta, ya que no he medido su rendimiento, por ejemplo, pero son más fáciles de mantener en mi experiencia. Todavía no estoy seguro de su argumento sobre cómo afectaría su trabajo si se colocara una construcción en forma de gancho en el núcleo de Flutter.

Simplemente escanee los gifs y comience a imaginar cómo podría construir algunas de esas cosas.

Escaneé los gifs. No usaría widgets de construcción.
Muchas de las animaciones son tan complejas que si supiera que las implementaste usando los constructores de nivel superior, probablemente no usaría tu paquete.

De todos modos, esta discusión parece irse de las manos con más desacuerdos personales. Deberíamos centrarnos en la tarea principal que tenemos entre manos. No estoy seguro de cómo los defensores del gancho pueden mostrar ejemplos más pequeños si los oponentes, como dije antes, encontrarán mejoras que no resuelvan realmente el problema planteado. Creo que deberíamos contribuir al repositorio de

El ejemplo sería mostrar una animación compleja arbitraria que no puede resolverse con ninguna solución existente, y eso es lo que Hixie sigue preguntando, creo.

Mostrar ejemplos de algo que hoy no es posible está fuera del alcance de este tema.
Este problema trata de mejorar la sintaxis de lo que ya es factible, no de desbloquear algunas cosas que hoy no son posibles.

Cualquier solicitud de proporcionar algo que no es posible hoy está fuera de tema.

@TimWhiting, ¿le importaría poner un archivo de licencia en su https://github.com/TimWhiting/local_widget_state_approaches repositorio? Algunas personas no pueden contribuir sin una licencia aplicable (BSD, MIT o similar idealmente).

Hecho. Lo siento, no he tenido mucho tiempo para trabajar en los ejemplos, pero probablemente llegue a eso en algún momento de esta semana.

El ejemplo sería mostrar una animación compleja arbitraria que no puede resolverse con ninguna solución existente, y eso es lo que Hixie sigue preguntando, creo.

Mostrar ejemplos de algo que hoy no es posible está fuera del alcance de este tema.
Este problema trata de mejorar la sintaxis de lo que ya es factible, no de desbloquear algunas cosas que hoy no son posibles.

Cualquier solicitud de proporcionar algo que no es posible hoy está fuera de tema.

Déjame reformular lo que dije.

Mostrar algún caso de uso complejo arbitrario que difíciles de escribir y que se pueden mejorar significativamente sin afectar el rendimiento sería el ejemplo, y eso es lo que Hixie sigue preguntando, creo.

Entiendo el impulso de menos repetición, más reutilización, más magia. También me gusta tener que escribir menos código, y el lenguaje / marco que hace más trabajo es bastante apetitoso.
Hasta ahora, ninguno de los ejemplos / combinaciones de soluciones presentados aquí mejoraría drásticamente el código. Es decir, si nos importa algo más que cuántas líneas de códigos tenemos que escribir.

Amigos, está bien si no tienen el tiempo o la inclinación para crear ejemplos reales, pero por favor, si no están interesados ​​en crear ejemplos para explicar el problema, tampoco deben esperar que las personas se sientan obligadas a resolver el problema ( que es mucho más trabajo que crear ejemplos para mostrar el problema). Nadie aquí está obligado a hacer nada por nadie, es un proyecto de código abierto en el que todos intentamos ayudarnos unos a otros.

@TimWhiting, ¿le importaría poner un archivo de licencia en su https://github.com/TimWhiting/local_widget_state_approaches repositorio? Algunas personas no pueden contribuir sin una licencia aplicable (BSD, MIT o similar idealmente).

Pasé aproximadamente 6 horas creando estos diversos ejemplos y fragmentos de código. Pero realmente no veo ningún sentido en proporcionar ejemplos concretos de animaciones complejas solo para demostrar que pueden existir.

La solicitud es básicamente convertir esto en algo que AnimatedContainer no pueda manejar:

Container(margin: EdgeInsets.symmetric(vertical: value2 * 20, horizontal: value3 * 30), color: Colors.red.withOpacity(value1));

Esto es tan trivial hasta el punto de ser casi intencionalmente obtuso al tema. ¿Es tan difícil imaginar que podría tener un par de botones pulsantes, algunas partículas en movimiento, tal vez algunos campos de texto que se desvanecen al escalar, o algunas tarjetas que se voltean? Tal vez estoy haciendo una barra de sonido con 15 barras independientes, tal vez estoy deslizando un menú pero también necesito la capacidad de deslizar elementos individuales hacia afuera. Y sigue y sigue y sigue. Y esto es solo para animaciones. Se aplica a cualquier caso de uso que sea oneroso en el contexto de un widget.

Creo que proporcioné excelentes ejemplos cananónicos del problema con ambos constructores y la reutilización del estado de vainilla:
https://github.com/flutter/flutter/issues/51752#issuecomment -671566814
https://github.com/flutter/flutter/issues/51752#issuecomment -671489384

Simplemente tiene que imaginar muchas de estas instancias (elija su veneno), esparcidas a lo largo y ancho de un proyecto de más de 1000 archivos de clase, y obtendrá la imagen perfecta de los problemas de legibilidad y mantenibilidad que estamos tratando de evitar.

¿Las imágenes de ejemplo que @esDotDev proporcionó, que muestran cómo el anidamiento hace que el código sea más difícil de leer, no serían suficientes para ti, @Rudiksz ? ¿Qué les falta? Supongo que no hay métricas de rendimiento allí, pero @rrousselGit seguro que no tienen menos rendimiento que los constructores.

@esDotDev Creo que el punto es tener un ejemplo canónico singular a partir del cual se puedan comparar todas las soluciones de gestión del ciclo de vida (no solo los ganchos, sino también otros en el futuro). Es el mismo principio que TodoMVC, no necesariamente señalaría varias otras implementaciones en React, Vue, Svelte, etc. para mostrar la diferencia entre ellas, querría que todas implementaran la misma aplicación, entonces, puede comparar.

Eso tiene sentido para mí, pero no entiendo por qué necesita ser más grande que una sola página.

La gestión de varias animaciones es el ejemplo perfecto de algo que es común, requiere un montón de repetición, es propenso a errores y no tiene una buena solución actualmente. Si eso no es el punto, si la gente va a decir que ni siquiera entienden cómo las animaciones pueden ser complejas, entonces claramente cualquier caso de uso será derribado por el contexto del caso de uso, y no por el problema arquitectónico que tenemos. estoy tratando de ilustrar.

Claro, el repositorio de @TimWhiting no tiene una aplicación completa, tiene páginas singulares como ejemplos, como usted dice, si puede hacer un ejemplo canónico de animación para ese repositorio desde el cual otros podrían implementar su solución, eso funcionaría.

Tampoco creo que necesitemos una aplicación enorme ni nada, pero debería haber una complejidad suficiente similar a la de TodoMVC. Básicamente tiene que ser suficiente para que tus oponentes no puedan decir "bueno, podría hacerlo mejor de tal o cual manera".

@Hixie La solicitud de aplicaciones reales para comparar enfoques es defectuosa.

Hay dos fallas:

  • Aún no estamos de acuerdo con el problema, como tú mismo dijiste no lo entiendes
  • No podemos implementar ejemplos en condiciones reales de producción, ya que tendremos piezas faltantes.

Por ejemplo, no podemos escribir una aplicación usando:

final snapshot = keyword StreamBuilder();

ya que esto no está implementado.

Tampoco podemos juzgar el rendimiento, ya que se trata de comparar un código de producción con un POC.

Tampoco podemos evaluar si algo como "los ganchos no se pueden llamar condicionalmente" es propenso a errores, ya que no hay integración del compilador para señalar errores cuando hay un mal uso.

Juzgar el rendimiento de los diseños, evaluar la usabilidad de la API, implementar cosas antes de que tengamos implementaciones ... todo eso es parte del diseño de la API. Bienvenidos a mi trabajo. :-) (Trivia de Flutter: ¿sabías que las primeras miles de líneas de RenderObject y RenderBox et al se implementaron antes de que creáramos dart: ui?)

Eso no cambia el hecho de que estás pidiendo lo imposible.

Algunas de las propuestas que aquí se hacen son parte del lenguaje o del analizador. Es imposible para la comunidad implementar eso.

No estoy tan seguro, otros frameworks y lenguajes diseñan API todo el tiempo, no creo que sea muy diferente aquí, o que Flutter tenga algunas diferencias o dificultades abrumadoras para el diseño de API que otros lenguajes. Por ejemplo, lo hacen sin tener soporte de compilador o analizador, son solo pruebas de concepto.

He reunido un ejemplo de un escenario de animación 'complejo' que hace un buen uso de 3 animaciones y está bastante cargado de texto estándar y cruft.

Es importante tener en cuenta que podría haber hecho cualquier animación que necesite un ajuste rápido a la posición inicial (eliminando todos los widgets implícitos), o rotación en el eje z, o escala en un solo eje, o cualquier otro caso de uso no cubierto por IW. Me preocupaba que no se tomaran en serio (aunque mis diseñadores me entregarían estas cosas todo el día), así que construí algo más del "mundo real".

Así que aquí hay un andamio simple, tiene 3 paneles que se deslizan para abrirlos y cerrarlos. Utiliza 3 animadores con estados discretos. En este caso, realmente no necesito el control total de AnimatorController, TweenAnimationBuilder estaría bien, pero el anidamiento resultante en mi árbol sería muy indeseable. No puedo anidar las TAB en el árbol, ya que los paneles dependen de los valores de los demás. AnimatedContainer no es una opción aquí ya que cada panel debe deslizarse fuera de la pantalla, no se "aplastan".
https://i.imgur.com/BW6M3uM.gif
image

class _SlidingPanelViewState extends State<SlidingPanelView> with TickerProviderStateMixin {
  AnimationController leftMenuAnim;
  AnimationController btmMenuAnim;
  AnimationController rightMenuAnim;

  <strong i="12">@override</strong>
  void initState() {
    // Here I have to pass vsync to AnimationController, so I have to include a SingleTickerProviderMixin and somewhat magically pass 'this' as vsync.
    leftMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    btmMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    rightMenuAnim = AnimationController(duration: widget.slideDuration, vsync: this);
    // Here I have to call forward 3 times, cause there's no way to automate this common setup behavior
    leftMenuAnim.forward();
    btmMenuAnim.forward();
    rightMenuAnim.forward();
    // Here I have to manually bind to build, cause there is encapsulate this common setup behavior
    leftMenuAnim.addListener(() => setState(() {}));
    btmMenuAnim.addListener(() => setState(() {}));
    rightMenuAnim.addListener(() => setState(() {}));
    super.initState();
  }

  // Following 2 fxn are a blind spot as far as compiler is concerned.
  // Things may, or may not be implemented correctly, no warnings, no errors.
  <strong i="13">@override</strong>
  void dispose() {
    btmMenuAnim.dispose();
    leftMenuAnim.dispose();
    rightMenuAnim.dispose();
    super.dispose();
  }

  <strong i="14">@override</strong>
  void didUpdateWidget(SlidingPanelView oldWidget) {
    if (leftMenuAnim.duration != widget.slideDuration) {
      leftMenuAnim.duration = widget.slideDuration;
      btmMenuAnim.duration = widget.slideDuration;
      rightMenuAnim.duration = widget.slideDuration;
    }
    super.didUpdateWidget(oldWidget);
  }

  // End error-prone blind spot without a single line of unique code
  // ~50 lines in we can start to see some unique code

  void _toggleMenu(AnimationController anim) {
    bool isOpen = anim.status == AnimationStatus.forward || anim.value == 1;
    isOpen ? anim.reverse() : anim.forward();
  }

  <strong i="15">@override</strong>
  Widget build(BuildContext context) {
    double leftPanelSize = 320;
    double leftPanelPos = -leftPanelSize * (1 - leftMenuAnim.value);
    double rightPanelSize = 230;
    double rightPanelPos = -rightPanelSize * (1 - rightMenuAnim.value);
    double bottomPanelSize = 80;
    double bottomPanelPos = -bottomPanelSize * (1 - btmMenuAnim.value);
    return Stack(
      children: [
        //Bg
        Container(color: Colors.white),
        //Content Panel
        Positioned(
          top: 0,
          left: leftPanelPos + leftPanelSize,
          bottom: bottomPanelPos + bottomPanelSize,
          right: rightPanelPos + rightPanelSize,
          child: ChannelInfoView(),
        ),
        //Left Panel
        Positioned(
          top: 0,
          left: leftPanelPos,
          bottom: bottomPanelPos + bottomPanelSize,
          width: leftPanelSize,
          child: ChannelMenu(),
        ),
        //Bottom Panel
        Positioned(
          left: 0,
          right: 0,
          bottom: bottomPanelPos,
          height: bottomPanelSize,
          child: NotificationsBar(),
        ),
        //Right Panel
        Positioned(
          top: 0,
          right: rightPanelPos,
          bottom: bottomPanelPos + bottomPanelSize,
          width: rightPanelSize,
          child: SettingsMenu(),
        ),
        // Buttons
        Row(
          children: [
            Button("left", ()=>_toggleMenu(leftMenuAnim)),
            Button("btm", ()=>_toggleMenu(btmMenuAnim)),
            Button("right", ()=>_toggleMenu(rightMenuAnim)),
          ],
        )
      ],
    );
  }
}

//Demo helpers
Widget Button(String lbl, VoidCallback action) => FlatButton(child: Text(lbl), onPressed: action, color: Colors.grey);
Widget ChannelInfoView() => _buildPanel(Colors.red);
Widget ChannelMenu() => _buildPanel(Colors.pink);
Widget SettingsMenu() => _buildPanel(Colors.blue);
Widget NotificationsBar() => _buildPanel(Colors.grey);
Widget _buildPanel(Color c) => Container(color: c, child: Container(color: Colors.white.withOpacity(.5)), padding: EdgeInsets.all(10));

Entonces, de las 100 líneas más o menos en ese cuerpo, aproximadamente el 40% más o menos es pura repetición. 15 líneas en particular, donde algo faltante o mal escrito, podría causar errores difíciles de detectar.

Si usamos algo como StatefulProperty, reduciría el texto estándar a un 15% más o menos (ahorra alrededor de 25 líneas). Fundamentalmente, esto resolvería por completo el problema de los errores furtivos y la lógica empresarial duplicada, pero todavía es un poco detallado, especialmente porque requiere StatefulWidget, que es un éxito de 10 líneas desde la parte superior.

Si usamos algo como 'palabra clave', reducimos las líneas estándar a esencialmente 0%. Todo el enfoque de la clase podría estar en la lógica empresarial (única) y los elementos del árbol visual. Hacemos que el uso de StatefulWIdgets sea mucho más raro en general, la gran mayoría de las vistas se vuelven un 10 o 20% menos detalladas y más enfocadas.

Además, vale la pena señalar que el escenario del Panel anterior es del mundo real, y en el mundo real, obviamente, este enfoque realmente no es agradable, por lo que no lo usamos y no lo vería en la base del código. Ni lo que utilizar constructores anidados, porque los bruto expresión por lo que no verá que tampoco.

Creamos un widget SlidingPanel dedicado, que toma una propiedad IsOpen y se abre y se cierra. Esta es generalmente la solución en cada uno de estos casos de uso en los que necesita un comportamiento específico, mueve la lógica con estado hacia abajo en un widget ultraespecífico y lo usa. Básicamente, escribes tu propio ImplicitlyAnimatedWidget.

Esto generalmente funciona bien, pero sigue siendo tiempo y esfuerzo, y literalmente la ÚNICA razón por la que existe es porque usar animaciones es muy difícil (lo cual existe porque reutilizar componentes con estado en general es muy difícil). En Unity o AIR, por ejemplo, no crearía una clase dedicada simplemente para mover un panel en un solo eje, solo sería una sola línea de código para abrir o cerrar, no habría nada que hacer para un widget dedicado. En Flutter tenemos que crear un widget dedicado, porque es literalmente la única forma razonable de encapsular el arranque y el desmontaje de AnimatorController (a menos que queramos anidar, anidar, anidar con TAB)

Mi punto principal aquí es que este tipo de cosas es por qué los ejemplos del mundo real son tan difíciles de encontrar. Como desarrolladores, no podemos permitir que estas cosas existan demasiado en nuestras bases de código, por lo que las solucionamos con soluciones menos que ideales pero efectivas. Luego, cuando mira el código base, solo ve estas soluciones en efecto y todo parece estar bien, pero puede que no sea lo que el equipo quería hacer, puede que les haya tomado un 20% más de tiempo llegar allí, puede haber sido un total dolor para depurar, nada de esto es evidente mirando el código.

Para completar, aquí está el mismo caso de uso, hecho con constructores. El recuento de líneas se reduce considerablemente, no hay posibilidad de errores, no es necesario aprender el concepto extraño RE TickerProviderMixin ... pero ese anidamiento es tristeza, y la forma en que las diversas variables se esparcen por todo el árbol (valores finales dinámicos, valor1, valor2, etc. ) hace que la lógica empresarial sea mucho más difícil de leer de lo necesario.

dardo
class _SlidingPanelViewState extiende el estado{
bool isLeftMenuOpen = true;
bool isRightMenuOpen = true;
bool isBtmMenuOpen = true;

@anular
Compilación del widget (contexto BuildContext) {
volver TweenAnimationBuilder(
interpolación: interpolación (inicio: 0, final: isLeftMenuOpen? 1: 0),
duración: widget.slideDuration,
constructor: (_, leftAnimValue, __) {
volver TweenAnimationBuilder(
interpolación: interpolación (comienzo: 0, final: isRightMenuOpen? 1: 0),
duración: widget.slideDuration,
constructor: (_, rightAnimValue, __) {
volver TweenAnimationBuilder(
interpolación: interpolación (inicio: 0, final: isBtmMenuOpen? 1: 0),
duración: widget.slideDuration,
constructor: (_, btmAnimValue, __) {
double leftPanelSize = 320;
double leftPanelPos = -leftPanelSize * (1 - leftAnimValue);
double rightPanelSize = 230;
double rightPanelPos = -rightPanelSize * (1 - rightAnimValue);
double bottomPanelSize = 80;
double bottomPanelPos = -bottomPanelSize * (1 - btmAnimValue);
pila de devoluciones (
niños: [
// Bg
Contenedor (color: Colors.white),
// Área de contenido principal
Posicionado (
arriba: 0,
left: leftPanelPos + leftPanelSize,
bottom: bottomPanelPos + bottomPanelSize,
right: rightPanelPos + rightPanelSize,
niño: ChannelInfoView (),
),
//Panel izquierdo
Posicionado (
arriba: 0,
left: leftPanelPos,
bottom: bottomPanelPos + bottomPanelSize,
width: leftPanelSize,
niño: ChannelMenu (),
),
//Panel inferior
Posicionado (
izquierda: 0,
derecha: 0,
bottom: bottomPanelPos,
height: bottomPanelSize,
niño: NotificationsBar (),
),
// Panel derecho
Posicionado (
arriba: 0,
right: rightPanelPos,
bottom: bottomPanelPos + bottomPanelSize,
width: rightPanelSize,
niño: SettingsMenu (),
),
// Botones
Hilera(
niños: [
Botón ("izquierda", () => setState (() => isLeftMenuOpen =! IsLeftMenuOpen)),
Botón ("btm", () => setState (() => isBtmMenuOpen =! IsBtmMenuOpen)),
Button ("derecha", () => setState (() => isRightMenuOpen =! IsRightMenuOpen)),
],
)
],
);
},
);
},
);
},
);
}
}

El último es interesante ... Originalmente iba a sugerir que los constructores deberían estar alrededor de los widgets posicionados en lugar de la pila (y aún sugeriría eso para los paneles izquierdo y derecho) pero luego me di cuenta de que el inferior afecta los tres, y me di cuenta de que el constructor solo te da un argumento child en realidad no es suficiente, porque realmente quieres mantener tanto el Container como el Row constantes en todo construye. Supongo que puedes crearlos encima del primer constructor.

Mi punto principal aquí es que este tipo de cosas es por qué los ejemplos del mundo real son tan difíciles de encontrar. Como desarrolladores, no podemos permitir que estas cosas existan demasiado en nuestras bases de código, por lo que las solucionamos con soluciones menos que ideales pero efectivas. Luego, cuando mira el código base, solo ve estas soluciones en efecto y todo parece estar bien, pero puede que no sea lo que el equipo quería hacer, puede que les haya tomado un 20% más de tiempo llegar allí, puede haber sido un total dolor para depurar, nada de esto es evidente mirando el código.

Para que conste, esto no es un accidente. Esto es muy por diseño. Tener estos widgets como sus propios widgets mejora el rendimiento. Nuestra intención era que esta fuera la forma en que la gente usaba Flutter. Esto es a lo que me refería anteriormente cuando mencioné que "una gran parte de la filosofía de diseño de Flutter es guiar a las personas hacia la elección correcta".

Originalmente iba a sugerir que los constructores deberían estar alrededor de los widgets posicionados en lugar de la pila (y aún sugeriría eso para los paneles izquierdo y derecho)

De hecho, lo omití en aras de la concisión, pero normalmente querría un contenedor de contenido aquí, y usaría los tamaños de los 3 menús para definir su propia posición. Lo que significa que los 3 deben estar en la parte superior del árbol, y si hay alguna reconstrucción, necesito la vista completa para reconstruir, sin necesidad de evitarlo. Este es básicamente su andamio clásico de estilo de escritorio.

Por supuesto, podríamos comenzar a destrozar el árbol, siempre es una opción, lo usamos mucho para widgets más grandes, pero nunca he visto que realmente mejore la capacidad de asimilación de un vistazo, hay un paso cognitivo adicional que el lector debe hacer en eso. punto. En el momento en que rompes el árbol, de repente estoy siguiendo un rastro de migas de pan de asignaciones de variables para averiguar qué se está pasando aquí y cómo todo encaja y se ocluye. Presentar el árbol como un árbol digerible siempre es más fácil de razonar desde mi experiencia.

Probablemente sea un buen caso de uso para un RenderObject dedicado.

O 3 AnimatorObjects fácilmente administrados que pueden conectarse al ciclo de vida del widget: D

Tener estos widgets como sus propios widgets mejora el rendimiento.

Ya que nos estamos poniendo concretos aquí: en este caso, debido a que se trata de un andamio, cada subvista ya es su propio widget, este tipo es responsable de simplemente diseñar y traducir a los niños. El niño sería BottomMenu (), ChannelMenu (), SettingsView (), MainContent (), etc.

En este caso, estamos envolviendo un montón de widgets autónomos, con otra capa de widgets autónomos, simplemente para administrar el texto estándar para moverlos. ¿No creo que esto sea una victoria de rendimiento? En este caso, estamos siendo empujados a lo que el marco _piensa_ que queremos hacer, y no a lo que realmente queremos hacer, que es escribir una visión igualmente eficaz de una manera más concisa y coherente.

[Editar] Actualizaré los ejemplos para agregar este contexto.

La razón por la que sugiero un RenderObject dedicado es que mejoraría el diseño. Estoy de acuerdo en que el aspecto de la animación estaría en un widget con estado, simplemente pasaría los tres 0..1 dobles (valor1, valor2, valor3) al objeto de renderizado en lugar de tener las matemáticas de la pila. Pero eso es principalmente un espectáculo secundario para esta discusión; en el punto en el que está haciendo esto, todavía querrá hacer la simplificación que ofrecen Hooks o algo similar en ese widget con estado.

En una nota más relevante, tuve la oportunidad de crear una demostración para el proyecto de https://github.com/TimWhiting/local_widget_state_approaches/pull/1
Tengo curiosidad por saber cómo se vería una versión de Hooks. No estoy seguro de poder ver una buena manera de hacerlo más simple, especialmente manteniendo las características de rendimiento (o mejorándolas; hay un comentario allí que muestra un lugar donde actualmente no es óptimo).

(También tengo mucha curiosidad si este es el tipo de cosas en las que si encontramos una manera de simplificarlo, estaríamos aquí, o si faltan cosas críticas que tendríamos que resolver antes de terminar).

¿Las cosas de restauración son críticas para este ejemplo? Estoy teniendo dificultades para seguir debido a eso, y realmente no sé qué hace RestorationMixin. Supongo que ... me va a costar un poco entender esto. Estoy seguro de que Remi arrancará la versión de ganchos en 4 segundos :)

El uso de la API de restauración con HookWidget lugar de StatefulHookWidget no se admite en este momento.

Idealmente deberíamos poder cambiar

final value = useState(42);

dentro:

final value = useRestorableInt(42);

Pero es necesario pensar un poco, ya que la API de restauración actual no se diseñó realmente teniendo en cuenta los ganchos.

Como nota al margen, React hooks viene con una función "clave", que generalmente se usa así:

int userId;

Future<User> user = useMemo(() => fetchUser(id), [id]);

donde este código significa "almacenar en caché el resultado de la devolución de llamada y volver a evaluar la devolución de llamada siempre que cambie algo dentro de la matriz"

Flutter_hooks ha hecho una reimplementación 1to1 de esto (ya que es solo un puerto), pero no creo que eso sea lo que quisiéramos hacer para un código optimizado de Flutter.

Probablemente querríamos:

int userId;

Future<User> user = useMemo1(id, (id) => fetchUser(id));

que haría lo mismo, pero eliminar la presión de la memoria evitando una asignación de lista y utilizando una función de corte

No es crítico en esta etapa, pero vale la pena mencionarlo si planeamos usar flutter_hooks como ejemplos.

@Hixie porté tu ejemplo de animación a hooks

Fue un ejemplo interesante, ¡felicitaciones por pensar en ello!
Es un buen ejemplo de que, de forma predeterminada, la implementación de "activo" y "duración" está por todas partes (y depende entre sí).
Hasta el punto en que hay numerosas llamadas "if (active)" / "controller.repeat"

Mientras que con los ganchos, toda la lógica se maneja de forma declarativa y se concentra en un solo lugar, sin duplicados.

El ejemplo también muestra cómo los ganchos se pueden usar para almacenar fácilmente objetos en caché, lo que solucionó el problema de la reconstrucción de ExpensiveWidget con demasiada frecuencia.
Obtenemos los beneficios de los constructores const, pero funciona con parámetros dinámicos.

También obtenemos una mejor recarga en caliente. Podemos cambiar la duración periódica del temporizador para el color de fondo y ver inmediatamente los cambios en efecto.

@rrousselGit ¿tienes un enlace? No vi nada nuevo en el repositorio de @TimWhiting .

El uso de la API de restauración con HookWidget lugar de StatefulHookWidget no se admite en este momento.

Cualquiera que sea la solución que se nos ocurra, debemos asegurarnos de que no es necesario que conozca hasta la última mezcla. Si alguien quiere usar nuestra solución en combinación con algún otro paquete que presenta un mixin como TickerProviderStateMixin o RestorationMixin, debería poder hacerlo.

https://github.com/TimWhiting/local_widget_state_approaches/pull/3

De acuerdo, pero eso no me preocupa. useAnimationController no requiere que los usuarios se preocupen por SingleTickerProvider, por ejemplo.

AutomaritKeepAlive podría beneficiarse del mismo tratamiento.
Una de las cosas que estaba pensando es tener un gancho "useKeepAlive (bool)"

Eso evita tanto el mixin como el "super.build (context)" (este último es bastante confuso)

Otro punto interesante son los cambios necesarios durante la refactorización.

Por ejemplo, podemos comparar la diferencia entre los cambios necesarios para implementar TickerMode para el enfoque en bruto frente a los ganchos:

Algunas otras cosas están mezcladas en la diferencia, pero podemos ver que:

  • Se requiere StatefulWidget para mover la lógica a un ciclo de vida completamente diferente
  • Los cambios de ganchos son puramente aditivos. Las líneas existentes no se editaron / movieron.

En mi opinión, esto es muy importante y una ventaja clave de este estilo de objetos de estado autónomos. Tener todo basado en contextos anidados en un árbol es fundamentalmente más difícil y complicado de refactorizar y cambiar, lo que en el transcurso de un proyecto tiene un efecto invisible pero definido en la calidad final del código base.

¡Acordado!
Eso también hace que las revisiones de código sean mucho más fáciles de leer:

final value = useSomething();
+ final value2 = useSomethingElse();

return Container(
  color: value.color,
-  child: Text('${value.name}'),
+  child: Text('${value.name} $value2'),
);

vs:

return SomethingBuilder(
  builder: (context, value) {
-    return Container(
-      color: value.color,
-      child: Text('$value'),
+    return SomethingElseBuilder(
+      builder: (context, value2) {
+        return Container(
+          color: value.color,
+          child: Text('${value.name} $value2'),
+        );
+      }
    );
  },
);

No está claro en la segunda diferencia que Container ha cambiado y que solo el Text cambiado

@rrousselGit ¿Crees que tiene sentido intentar hacer una versión que represente cómo podría verse con el soporte de palabras clave? ¿Es sustancialmente similar a los ganchos con solo una palabra clave dedicada de 'uso', o se vuelve aún más fácil de seguir? Es difícil comparar el enfoque del gancho en pie de igualdad porque tiene tantos conceptos extraños como useEffect, useMemo, etc., que creo que lo hacen parecer más mágico de lo que es.

Otra cosa a tener en cuenta es que donde todo esto realmente impactaría, es si tuvieras múltiples widgets, todos necesitaban compartir esta lógica de 'pasos de color', pero usaban el color resultante de formas totalmente diferentes. Con el enfoque de estilo hooks, simplemente agrupamos cualquier lógica que tenga sentido para reutilizar, y simplemente la usamos. No hay rincones arquitectónicos a los que nos veamos obligados, es verdaderamente agnóstico y flexible.

En el enfoque Stateful, nos vemos obligados a

  • copiar y pegar la lógica (muy insostenible)
  • usando un constructor (no súper legible, especialmente cuando se usa anidamiento)
  • mezcla (no se compone bien, es muy fácil que diferentes mezclas entren en conflicto en su estado compartido)

Creo que la clave es que en este último, inmediatamente tienes un problema arquitectónico, ¿dónde debo poner esto en mi árbol, cómo puedo encapsularlo mejor, debería ser un constructor o tal vez un widget personalizado? Con el primero, la única decisión es en qué archivo guardar este trozo de lógica reutilizada, no hay ningún impacto en su árbol. Esto es muy agradable desde el punto de vista arquitectónico cuando desea usar algunas de estas encapsulaciones lógicas juntas, moverlas hacia arriba y hacia abajo en su jerarquía o moverse a un widget hermano, etc.

No soy, de ninguna manera, un desarrollador experto. Pero este nuevo estilo de reutilizar la lógica y escribirla en un solo lugar es realmente útil.

No he usado Vue.js en mucho tiempo, pero incluso tienen su propia API (inspirada en Hooks) para su próxima versión, podría valer la pena echarle un vistazo.

Y CMIIW, creo que las reglas de react hooks (no use condicional), no se aplica con Composition API. Por lo tanto, no es necesario utilizar linter para hacer cumplir las reglas.

Nuevamente, la sección de motivación refuerza fuertemente el OP aquí:

MOTIVACIÓN
El código de componentes complejos se vuelve más difícil de razonar a medida que las características crecen con el tiempo. Esto sucede particularmente cuando los desarrolladores leen código que no escribieron ellos mismos.
[Hubo una] Falta de un mecanismo limpio y gratuito para extraer y reutilizar la lógica entre múltiples componentes.

Las palabras clave son "limpio" y "gratuito". Los mixins son gratuitos pero no están limpios. Los constructores son limpios, pero no son gratuitos en el sentido de la legibilidad, ni en el sentido de la arquitectura del widget, ya que es más difícil moverse por el árbol y razonar en términos de jerarquía.

También creo que es importante tener en cuenta esto en la discusión sobre legibilidad: "_ sucede particularmente cuando los desarrolladores están leyendo un código que no escribieron_". Por supuesto, su constructor anidado puede ser fácil de leer, sabe lo que hay y puede omitirlo cómodamente, está leyendo el código de otra persona, como lo hace en cualquier proyecto más grande, o su propio código de hace semanas / meses, cuando se vuelve bastante molesto / difícil de analizar y refactorizar estas cosas.

Algunos otros apartados especialmente relevantes.

Por qué simplemente tener componentes no es suficiente:

La creación de ... componentes nos permite extraer partes repetibles de la interfaz junto con su funcionalidad en piezas de código reutilizables. Esto por sí solo puede llevar nuestra aplicación bastante lejos en términos de facilidad de mantenimiento y flexibilidad. Sin embargo, nuestra experiencia colectiva ha demostrado que esto por sí solo podría no ser suficiente, especialmente cuando su aplicación se está volviendo realmente grande; piense en varios cientos de componentes. Cuando se trata de aplicaciones tan grandes, compartir y reutilizar código se vuelve especialmente importante.

Sobre por qué reducir la fragmentación lógica y encapsular las cosas con más fuerza es una victoria:

la fragmentación es lo que dificulta la comprensión y el mantenimiento de un componente complejo. La separación de opciones oscurece las preocupaciones lógicas subyacentes. Además, cuando trabajamos en una única preocupación lógica, tenemos que "saltar" constantemente alrededor de bloques de opciones para el código relevante. Sería mucho mejor si pudiéramos colocar código relacionado con la misma preocupación lógica.

Tengo curiosidad por saber si hay otras propuestas de ejemplos canónicos que estamos tratando de mejorar además de la que presenté.
Si es el punto de partida que la gente quiere usar, entonces es genial. Sin embargo, diría que el mayor problema con esto en este momento es la verbosidad; no hay mucho código para reutilizar en ese ejemplo. Por lo tanto, no me queda claro si es una buena representación del problema como se describe en el OP.

He estado pensando un poco más en cómo expresar las características que personalmente buscaría en una solución, y me hizo darme cuenta de uno de los grandes problemas que veo con la propuesta actual de Hooks, que es una de las razones por las que no lo haría. quieren fusionarlo en el marco de Flutter: Locality and Encapsulation, o más bien, la falta de los mismos. El diseño de Hooks usa el estado global (por ejemplo, la estática para rastrear qué widget se está construyendo actualmente). En mi humilde opinión, esta es una característica de diseño que debemos evitar. En general, tratamos de hacer que las API sean autónomas, por lo que si llama a una función con un parámetro, debe estar seguro de que no podrá hacer nada con valores fuera de ese parámetro (es por eso que pasamos el BuildContext , en lugar de tener el equivalente a useContext ). No estoy diciendo que esta sea una característica que todos querrían necesariamente; y, por supuesto, la gente puede usar Hooks si eso no es un problema para ellos. Solo que es algo que me gustaría evitar hacer más en Flutter. Cada vez que hemos tenido un estado global (por ejemplo, en los enlaces) hemos terminado arrepintiéndonos.

Los ganchos probablemente podrían ser métodos en contexto (creo que estaban en la versión anterior), pero honestamente, no veo mucho valor en fusionarlos como son actualmente. Merge necesitaría tener algunas ventajas para separar el paquete, como un mayor rendimiento o herramientas de depuración específicas. De lo contrario, solo aumentarían la confusión, por ejemplo, tendría 3 formas oficiales de escuchar un escuchable: AnimatedBuilder, StatefulWidget y useListenable.

Entonces, para mí, el camino a seguir es mejorar la generación de código; he propuesto algunos cambios: https://github.com/flutter/flutter/issues/63323

Si estas sugerencias se implementaran realmente, las personas que quisieran soluciones mágicas similares a SwiftUI en su aplicación podrían simplemente hacer un paquete y no molestar a nadie más.

Discutir la validez de los ganchos está un poco fuera de tema en esta etapa, ya que, hasta donde yo sé, todavía no estamos de acuerdo con el problema.

Como se indicó varias veces en este número, existen muchas otras soluciones, muchas de las cuales son funciones de idioma.
Los Hooks son simplemente un puerto de una solución existente de otra tecnología que es relativamente barata de implementar en su forma más básica.

Esta característica podría tomar un camino completamente diferente a los ganchos, como lo que hacen SwiftUI o Jetpack Compose; la propuesta "named mixin", o el azúcar sintáctico para la propuesta Builders.

Insisto en el hecho de que este problema en su esencia está pidiendo una simplificación de patrones como StreamBuilder:

  • StreamBuilder tiene una mala legibilidad / escritura debido a su anidación
  • Los mixins y las funciones no son una alternativa posible a StreamBuilder
  • Copiar y pegar la implementación de StreamBuilder en todos los StatefulWidgets no es razonable

Todos los comentarios hasta ahora mencionaban alternativas de StreamBuilder, tanto para diferentes comportamientos (crear un objeto desechable, realizar solicitudes HTTP, ...) como proponer diferentes sintaxis.

No estoy seguro de qué más puedo decir, así que no veo cómo podemos progresar más.
¿Qué es esto con lo que no entiendes / no estás de acuerdo en esta declaración @Hixie?

@rrousselGit ¿Podría crear una aplicación de demostración que muestre esto? Traté de crear una aplicación de demostración que mostrara cuál entendí que era el problema, pero aparentemente no lo hice bien. (No estoy seguro de cuál es la diferencia entre "patrones como StreamBuilder" y lo que hice en la aplicación de demostración).

  • StreamBuilder tiene una mala legibilidad / escritura debido a su anidación

Ya dijiste que la verbosidad no es el problema. Anidar es solo otro aspecto de ser detallado. Si el anidamiento es realmente un problema, deberíamos ir tras Padding, Expanded, Flexible, Center, SizedBox y todos los demás widgets que agregan anidación sin una razón real. Pero el anidamiento se puede resolver fácilmente dividiendo los widgets monolíticos.

Copiar y pegar la implementación de StreamBuilder en todos los StatefulWidgets no es razonable

¿Te refieres a copiar y pegar las líneas de código que crean y eliminan los flujos que StatefulWidgets necesitan para crear y eliminar? Sí, es absolutamente razonable.

Si tiene 10 o Dios no lo quiera cientos de _diferentes_ StatefulWidgets personalizados que necesitan crear / eliminar sus propios flujos ("usar" en la terminología de los ganchos), tiene problemas más grandes de los que preocuparse que la "lógica" reutilización o anidamiento. Me preocuparía sobre por qué mi aplicación tiene que crear tantas transmisiones diferentes en primer lugar.

Para ser justos, creo que está bien que alguien piense que un patrón en particular no es razonable en su aplicación. (Eso no significa necesariamente que el marco tenga que admitir eso de forma nativa, pero sería bueno al menos permitir que un paquete lo resuelva). Si alguien no quiere usar stream.listen o StreamBuilder(stream) , es su derecho, y tal vez como resultado podamos encontrar un patrón que sea mejor para todos.

Para ser justos, creo que está bien que alguien piense que un patrón en particular no es razonable en su aplicación.

Estoy 100% en la misma página que tú.
Claro, la gente puede hacer lo que quiera en sus aplicaciones. A lo que estoy tratando de llegar es que todos los problemas y dificultades descritos en este hilo son resultado de malos hábitos de programación y, de hecho, tienen muy poco que ver con Dart o Flutter. Es como, solo mi opinión, pero yo diría que si alguien está escribiendo una aplicación que crea docenas de transmisiones por todas partes, tal vez debería revisar el diseño de su aplicación antes de pedir que se "mejore" el marco.

Por ejemplo, la implementación del gancho que lo hizo en el repositorio de ejemplo.

  <strong i="10">@override</strong>
  Widget build(BuildContext context) {
    final value = useAnimation(animation);

    final screenHeight = MediaQuery.of(context).size.height;
    final textHeight =
        useMemoized(() => math.sqrt(screenHeight), [screenHeight]);

    return Text(
      'Change Duration',
      style: TextStyle(fontSize: 10.0 + value * textHeight),
    );
  }

Tengo un mal presentimiento sobre esto, así que verifiqué algunos de los componentes internos y agregué algunas impresiones de depuración para verificar qué está pasando.
Puede ver en el resultado a continuación que el gancho Listenable verifica si se ha actualizado en cada tic de animación. Como en, ¿se actualizó el widget que "usa" ese escuchable? ¿Se modificó la duración? ¿Fue reemplazada la instancia?
El gancho memorizado, ni siquiera sé cuál es el trato con eso. Probablemente esté destinado a almacenar en caché un objeto, pero en cada compilación el widget verifica si el objeto cambió. ¿Qué? ¿Por qué? Por supuesto, debido a que se usa dentro de un widget con estado y algún otro widget en el árbol puede cambiar el valor, por lo que necesitamos sondear los cambios. Este comportamiento de sondeo literal es exactamente lo opuesto a la programación "reactiva".

Lo que es peor, los ganchos "nuevos" y "antiguos" tienen el mismo tipo de instancia, ambos tienen los mismos valores y, sin embargo, la función itera sobre los valores para comprobar si han cambiado. En _ cada tic de animación_.

Este es el resultado que obtengo, ad infinitum.

/flutter (28121): Use hook:_ListenableHook
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_ListenableHook'
I/flutter (28121): Hook1 keys:null
I/flutter (28121): Hook2 :Instance of '_ListenableHook'
I/flutter (28121): Hook2 keys:null
I/flutter (28121): 2. Shoud we preserve the  state of _ListenableHook:true
I/flutter (28121): 3: --------------
I/flutter (28121): checking if the listenable did change
I/flutter (28121): Did the listenable change?false
I/flutter (28121): Use hook:_MemoizedHook<double>
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_MemoizedHook<double>'
I/flutter (28121): Hook1 keys:[1232.0]
I/flutter (28121): Hook2 :Instance of '_MemoizedHook<double>'
I/flutter (28121): Hook2 keys:[1232.0]
I/flutter (28121): iterating over the hooks keys
I/flutter (28121): 2. Shoud we preserve the  state of _MemoizedHook<double>:true
I/flutter (28121): Use hook:_ListenableHook
I/flutter (28121): Is this the current hook:false
I/flutter (28121): 1: --- inside shouldPreserveState ----
I/flutter (28121): Hook1:Instance of '_ListenableHook'
I/flutter (28121): Hook1 keys:null
I/flutter (28121): Hook2 :Instance of '_ListenableHook'
I/flutter (28121): Hook2 keys:null
I/flutter (28121): 2. Shoud we preserve the  state of _ListenableHook:true

Todo este trabajo se realiza en cada tick de animación. Si agrego otro gancho como "color final = useAnimation(animationColor); ", para animar el color también, ahora el widget verifica _dos_ veces si se actualizó.

Estoy sentado aquí viendo un texto animado de un lado a otro sin cambios en el estado de la aplicación o ninguno de los widgets o el árbol de widgets, y aún así los ganchos están comprobando constantemente si el árbol / los widgets se actualizaron. Tener todos los widgets que "usan" estos ganchos en particular hacen este comportamiento de sondeo es malo.

Manejar la lógica de inicialización / actualización / eliminación de objetos de estado dentro del método de construcción es simplemente un mal diseño. Ninguna cantidad de mejoras obtenidas en reutilización, recarga en caliente o carga cognitiva justifica el impacto en el rendimiento.
De nuevo, en mi opinión. Como los ganchos son un paquete, cualquiera puede usarlos si cree que las ganancias justifican los gastos generales.

Además, no creo que ninguna cantidad de características del lenguaje, magia del compilador o abstracción pueda evitar tales comprobaciones innecesarias, si empezamos a intentar abstraer todo dentro del proceso de construcción. Así que nos quedamos con alternativas como extender StatefulWidget. Algo que ya se puede hacer y que fue descartado en innumerables ocasiones.

@Hixie, no has respondido la pregunta. ¿Qué es lo que no comprende / con lo que no está de acuerdo en la lista de viñetas que se enumera anteriormente?

No puedo dar un ejemplo sin saber lo que quieres que demuestre.

@Rudiksz Seguro que cualquier solución que consideremos debería ser perfilada y comparada para asegurarnos de que no empeora las cosas. Parte de la forma en que construí la aplicación de demostración que envié a

@rrousselGit Realmente no quería meterme en la maleza sobre esto porque es muy subjetivo, pero ya que preguntas:

  • Primero, evitaría usar Streams en general. ValueListenable es, en mi humilde opinión, un patrón mucho mejor para más o menos los mismos casos de uso.
  • No creo que StreamBuilder sea particularmente difícil de leer o escribir; pero, como @satvikpendem comentó anteriormente , mi historia es con árboles profundamente anidados en HTML, y he estado mirando árboles de Flutter durante 4 años (he dicho antes que la competencia principal de Flutter es cómo caminar eficientemente por árboles gigantes), así que probablemente tengo una tolerancia más alta que la mayoría de las personas, y mi opinión aquí no es realmente relevante.
  • En cuanto a si los mixins y las funciones podrían ser una posible alternativa a StreamBuilder, creo que Hooks demuestra bastante bien que definitivamente puedes usar funciones para escuchar streams, y los mixins claramente pueden hacer cualquier cosa que las clases puedan hacer aquí, así que no veo por qué no lo harían. tampoco será una solución.
  • Finalmente, con respecto a las implementaciones de copiar y pegar, eso es un asunto subjetivo. Personalmente, no copio y pego la lógica en initState / didUpdateWidget / dispose / build, la escribo de nuevo cada vez y parece estar bien. Cuando se "sale de control", lo factorizo ​​en un widget como StreamBuilder. Así que, de nuevo, mi opinión probablemente no sea relevante aquí.

Como regla general, no es relevante si tengo el problema que estás viendo o no. Tu experiencia es válida independientemente de mi opinión. Me complace trabajar para encontrar soluciones a los problemas que experimenta, incluso si no siento esos problemas. El único efecto de que yo mismo no experimente el problema es que me resulta más difícil entender bien el problema, como hemos visto en esta discusión.

Lo que me gustaría que demostraras es el patrón de codificación que crees que es inaceptable, en una demostración que es lo suficientemente significativa como para que cuando alguien crea una solución, no la descartes diciendo que puede resultar difícil de usar en una situación diferente. (es decir, incluya todas las situaciones relevantes, por ejemplo, asegúrese de incluir partes de "actualización" o cualquier otra parte que crea que es importante manejar), o decir que la solución funciona en un caso pero no en el caso general (por ejemplo, tomar sus comentarios anteriores, asegurándose de que los parámetros provengan de múltiples lugares y actualizándose en múltiples situaciones para que sea obvio que una solución como la Propiedad anterior no descartaría el código común).

El problema es que estás pidiendo ejemplos para algo que está en el dominio de "obvio" para mí.

No me importa hacer algunos ejemplos, pero no tengo ni idea de lo que esperas, ya que no entiendo lo que no entiendes.

Ya he dicho todo lo que tenía que decir.
Lo único que puedo hacer sin entender lo que tú no entiendes es repetirme.

Puedo hacer que se ejecuten algunos de los fragmentos aquí, pero eso equivale a repetirme.
Si el fragmento no fuera útil, no veo por qué poder ejecutarlo cambiaría algo.

En cuanto a si los mixins y las funciones podrían ser una posible alternativa a StreamBuilder, creo que Hooks demuestra bastante bien que definitivamente puedes usar funciones para escuchar streams, y los mixins claramente pueden hacer cualquier cosa que las clases puedan hacer aquí, así que no veo por qué no lo harían. tampoco será una solución.

Los ganchos no deben considerarse funciones.
Son una nueva construcción de lenguaje similar a Iterable / Stream

Las funciones no pueden hacer lo que hacen los ganchos: no tienen un estado o la capacidad de hacer que los widgets se reconstruyan.

El problema con los mixins se demuestra en el PO. TL; DR: El nombre de las variables entra en conflicto y es imposible reutilizar la misma mezcla varias veces.

@rrousselGit Bueno, ya que no le importa hacer algunos ejemplos, y dado que los ejemplos que estoy pidiendo son obvios, comencemos con algunos de estos ejemplos obvios e iteremos desde allí.

No dije que los ejemplos son obvios, pero el problema es.
Lo que quise decir es que no puedo crear nuevos ejemplos. Todo lo que tengo que decir ya está en este hilo:

No puedo pensar en nada que agregar a estos ejemplos.

Pero FWIW estoy trabajando en una aplicación meteorológica de código abierto usando Riverpod. Lo vincularé aquí cuando esté listo.


Hice una encuesta en Twitter con algunas preguntas sobre los constructores relacionadas con los problemas que se comentan aquí:

https://twitter.com/remi_rousselet/status/1295453683640078336

La encuesta aún está pendiente, pero aquí están los números actuales:

Screenshot 2020-08-18 at 07 01 44

El hecho de que el 86% de 200 personas deseen una forma de escribir Builders que no implique anidar habla por sí solo.

Para ser claros, nunca he sugerido que no deberíamos abordar este problema. Si pensara que no deberíamos abordarlo, el problema se cerraría.

Supongo que intentaré hacer un ejemplo que use los fragmentos a los que vinculó.

Puedo ayudarlo a crear ejemplos basados ​​en los fragmentos vinculados, pero necesito saber por qué estos fragmentos no fueron lo suficientemente buenos.
De lo contrario, lo único que puedo hacer es compilar estos fragmentos, pero dudo que eso sea lo que quieres.

Por ejemplo, aquí hay una idea general sobre los numerosos ValueListenableBuilder + TweenAnimationBuilder https://gist.github.com/rrousselGit/a48f541ffaaafe257994c6f98992fa73

Por ejemplo, aquí hay una idea general sobre los numerosos ValueListenableBuilder + TweenAnimationBuilder https://gist.github.com/rrousselGit/a48f541ffaaafe257994c6f98992fa73

FWIW, este ejemplo en particular se puede implementar más fácilmente en mobx.
En realidad, es más corto que la implementación de sus ganchos.

Los observables de Mobx son ValueNotifiers con esteroides y su widget Observer es la evolución del ValueListenableBuilder de Flutter: puede escuchar más de un ValueNotifier.
Ser un reemplazo directo del combo ValueNotifier / ValueListenableBuilder, significa que aún escribes código idiomático de Flutter, que en realidad es un factor importante.

Dado que todavía usa el constructor Tween incorporado de Flutter, no es necesario aprender / implementar nuevos widgets / ganchos (en otras palabras, no necesita nuevas funciones) y no tiene ninguno de los impactos de rendimiento de los ganchos.

import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'counters.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Home1(),
    );
  }
}

var counters = Counters();

class Home1 extends StatelessWidget {
  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 5),
                curve: Curves.easeOut,
                tween: IntTween(end: counters.firstCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
            RaisedButton(
              onPressed: () => counters.firstCounter += 100,
              child: Text('+'),
            ),
            // Both counters have voluntarily a different Curve and duration
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 2),
                curve: Curves.easeInOut,
                tween: IntTween(end: counters.secondCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
            RaisedButton(
              onPressed: () => counters.secondCounter += 100,
              child: Text('+'),
            ),
            const Text('total:'),
            // The total must take into account the animation of both counters
            Observer(
              builder: (context) => TweenAnimationBuilder<int>(
                duration: const Duration(seconds: 5),
                curve: Curves.easeOut,
                tween: IntTween(
                    end: counters.firstCounter + counters.secondCounter),
                builder: (context, value, child) {
                  return Text('$value');
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Counters.dart es tan simple como ...

part 'counters.g.dart';

class Counters = _Counters with _$Counters;
abstract class _Counters with Store {
  <strong i="18">@observable</strong>
  int firstCounter = 0;

  <strong i="19">@observable</strong>
  int secondCounter = 0;
}

Aquí hay otra implementación que ni siquiera necesita creadores de animaciones. El método de construcción del widget es tan puro como puede ser, casi como un archivo html semántico ... como una plantilla.

https://gist.github.com/Rudiksz/cede1a5fe88e992b158ee3bf15858bd9

@Rudiksz El comportamiento del campo "total" está roto en su fragmento. No coincide con el ejemplo, donde ambos contadores pueden animarse juntos pero terminan de animarse en diferentes momentos y con diferentes curvas.
De manera similar, no estoy seguro de qué agrega este ejemplo a la variante ValueListenableBuilder .

En cuanto a su última esencia, TickerProvider está roto ya que no admite TickerMode , ni se eliminan los oyentes ni se eliminan los controladores.

Y Mobx probablemente esté fuera de tema. No estamos discutiendo cómo implementar el estado ambiental / ValueListenable vs Stores vs Streams, sino cómo tratar con los constructores anidados / estatales locales, que Mobx no resuelve de ninguna manera

––––

Además, tenga en cuenta que en el ejemplo de los ganchos, useAnimatedInt podría / debería extraerse en un paquete y que no hay un duplicado de la duración / curva entre la animación de texto individual y el total.

En cuanto a las actuaciones, con Hooks estamos reconstruyendo solo un elemento, mientras que con Builders estamos reconstruyendo 2-4 Builders.
Así que Hooks bien podría ser más rápido.

El comportamiento del campo "total" está roto en su fragmento. No coincide con el ejemplo, donde ambos contadores pueden animarse juntos pero terminan de animarse en diferentes momentos y con diferentes curvas.

Claramente , ni siquiera intentaste ejecutar el ejemplo. Se comporta exactamente como su código.

En cuanto a su última esencia, TickerProvider está roto ya que no es compatible con TickerMode.

No sé a qué te refieres con esto. Refactoricé _su_ ejemplo, que no usa TickerMode. De nuevo estás cambiando los requisitos.

En cuanto a las actuaciones, con Hooks estamos reconstruyendo solo un elemento, mientras que con Builders estamos reconstruyendo 2-4 Builders. Así que Hooks bien podría ser más rápido.

No simplemente no. Sus widgets de gancho sondean constantemente los cambios en cada compilación. Los constructores basados ​​en objetos valiosos son "reactivos".

Del mismo modo, no estoy seguro de qué agrega este ejemplo a la variante ValueListenableBuilder .

Y Mobx probablemente esté fuera de tema. No estamos discutiendo cómo implementar el estado ambiental / ValueListenable vs Stores vs Streams, sino cómo tratar con los constructores anidados / estatales locales, que Mobx no resuelve de ninguna manera

Debes estar bromeando. ¡¿Tomé su ejemplo y "traté" con ValueListenableBuilders * anidados
Pero esta oración aquí, describe toda su actitud hacia esta discusión. Si no son ganchos, está "fuera de tema", pero dices que no te importa si son ganchos los que se utilizarán como solución.
Dáme un respiro.

Claramente, ni siquiera intentaste ejecutar el ejemplo. Se comporta exactamente como su código.

No es así. El primer contador se anima durante 5 segundos y el segundo durante 2 segundos, y ambos también usan una curva diferente.

Con los dos fragmentos que proporcioné, podría incrementar ambos contadores al mismo tiempo, y durante cada fotograma de la animación, el "total" sería correcto. Incluso cuando el segundo contador deja de animarse mientras el primer contador todavía se está animando

Por otro lado, su implementación no considera este caso, porque fusionó los 2 TweenAnimationBuilders en uno.
Para solucionarlo, tendríamos que escribir:

Observer(
  builder: (context) {
    return TweenAnimationBuilder<int>(
      duration: const Duration(seconds: 5),
      curve: Curves.easeOut,
      tween: IntTween(end: counters.firstCounter),
      builder: (context) {
        return Observer(
          valueListenable: secondCounter,
          builder: (context, value2, child) {
            return TweenAnimationBuilder<int>(
              duration: const Duration(seconds: 2),
              curve: Curves.easeInOut,
              tween: IntTween(end: counters.secondCounter),
              builder: (context, value2, child) {
                return Text('${value + value2}');
              },
            );
          },
        );
      },
    );
  },
)

Los dos TweenAnimationBuilders son necesarios para respetar el hecho de que ambos contadores pueden animarse individualmente. Y los dos Observer son necesarios porque los primeros Observer no pueden observar counters.secondCounter


No simplemente no. Sus widgets de gancho sondean constantemente los cambios en cada compilación. Los constructores basados ​​en objetos valiosos son "reactivos".

Está ignorando lo que hace Element , que resulta ser lo mismo que lo que hacen los ganchos: comparar runtimeType y claves, y decidir si crear un nuevo elemento o actualizar el existente

Tomé su ejemplo y "traté" con ValueListenableBuilders * y constructores de interpolaciones anidados

Suponiendo que se solucionó el problema con el recuento total, ¿qué anidamiento se eliminó?

Observer(
  builder: (context) => TweenAnimationBuilder<int>(
    duration: const Duration(seconds: 5),
    curve: Curves.easeOut,
    tween: IntTween(end: counters.firstCounter),
    builder: (context, value, child) {
      return Text('$value');
    },
  ),
),

no es diferente de:

ValueListenableBuilder<int>(
  valueListenable: firstCounter,
  builder: (context, value, child) => TweenAnimationBuilder<int>(
    duration: const Duration(seconds: 5),
    curve: Curves.easeOut,
    tween: IntTween(end: value),
    builder: (context, value, child) {
      return Text('$value');
    },
  ),
),

en términos de anidación.

Si se está refiriendo a su esencia, entonces, como mencioné antes, este enfoque rompe TickerProvider / TickerMode . El vsync debe obtenerse usando SingleTickerProviderClientStateMixin o de lo contrario no es compatible con la lógica de silenciamiento, lo que puede causar problemas de rendimiento.
He explicado esto en un artículo mío: https://dash-overflow.net/articles/why_vsync/

Y con este enfoque, tenemos que volver a implementar la lógica de Tween en todas las ubicaciones que originalmente querrían un TweenAnimationBuilder. Eso conduce a un duplicado significativo, especialmente considerando que la lógica no es tan trivial.

Con los dos fragmentos que proporcioné, podría incrementar ambos contadores al mismo tiempo, y durante cada fotograma de la animación, el "total" sería correcto. Incluso cuando el segundo contador deja de animarse mientras el primer contador todavía se está animando

Por otro lado, su implementación no considera este caso, porque fusionó los 2 TweenAnimationBuilders en uno.

Sí, esa es una compensación que estaba dispuesto a hacer. Fácilmente podría imaginar un caso en el que la animación sería solo una retroalimentación visual del cambio que está ocurriendo y la precisión no es importante. Todo depende de los requisitos.

Sin embargo, sospeché que objetaría, de ahí la segunda versión que resuelve este problema exacto, al tiempo que hace que el código sea aún más limpio.

Si se refiere a su esencia, entonces, como mencioné antes, este enfoque rompe TickerProvider / TickerMode. El vsync debe obtenerse mediante SingleTickerProviderClientStateMixin o, de lo contrario, no admite la lógica de silenciamiento, lo que puede causar problemas de rendimiento.

Entonces crea el tickerprovider en el widget y lo pasa a los contadores. Tampoco me deshice de los controladores de animación. Estos detalles son tan triviales de implementar que no sentí que agregarían nada al ejemplo. Pero aquí los estamos criticando.

Implementé la clase Counter () para hacer lo que hace tu ejemplo y nada más.

Y con este enfoque, tenemos que volver a implementar la lógica de Tween en todas las ubicaciones que originalmente querrían un TweenAnimationBuilder. Eso conduce a un duplicado significativo, especialmente considerando que la lógica no es tan trivial.

¿Qué? ¿Por qué? Por favor, explique, ¿por qué no pude crear más de una instancia de la clase Counter y usarlas en varios widgets?

@Rudiksz No estoy seguro de que sus soluciones realmente resuelvan los problemas expuestos. Tu dices

Implementé la clase Counter () para hacer lo que hace tu ejemplo y nada más.

y todavía

Sí, esa es una compensación que estaba dispuesto a hacer. Fácilmente podría imaginar un caso en el que la animación sería solo una retroalimentación visual del cambio que está ocurriendo y la precisión no es importante. Todo depende de los requisitos.

Entonces crea el tickerprovider en el widget y lo pasa a los contadores. Tampoco me deshice de los controladores de animación. Estos detalles son tan triviales de implementar que no sentí que agregarían nada al ejemplo. Pero aquí los estamos criticando.

Usted proporcionó un código que solo es aparentemente equivalente a la versión del gancho de @rrousselGit , pero en realidad no es equivalente, ya que omite las partes que incluye la versión del gancho. En ese caso, no son realmente comparables, ¿verdad? Si desea comparar su solución con la sugerida, sería mejor hacer que coincida exactamente con los requisitos en lugar de hablar sobre por qué no los incluyó. Esto es algo que es la razón para hacer el repositorio de @TimWhiting . Puede enviar su solución allí si cree que ha cubierto todos los requisitos.

Claramente, ni siquiera intentaste ejecutar el ejemplo. Se comporta exactamente como su código.

No simplemente no.

Debes estar bromeando. Tomé su ejemplo y "traté" con ValueListenableBuilders * y constructores de interpolaciones anidados

Por favor, absténgase de acusaciones como estas, solo sirven para hacer que este hilo sea virulento sin resolver los problemas subyacentes. Puedes exponer tus puntos sin ser acusador, despectivo o enojado con la otra parte, que es el efecto de los comentarios que veo en este hilo, no veo ese comportamiento de los demás hacia ti. Tampoco estoy seguro de cuál es el efecto de reaccionar emoji a tu propia publicación.

@rrousselGit

Puedo ayudarlo a crear ejemplos basados ​​en los fragmentos vinculados, pero necesito saber por qué estos fragmentos no fueron lo suficientemente buenos.
De lo contrario, lo único que puedo hacer es compilar estos fragmentos, pero dudo que eso sea lo que quieres.

La razón por la que estoy pidiendo una aplicación más elaborada que solo un fragmento es que cuando publiqué un ejemplo que manejaba uno de tus fragmentos, dijiste que eso no era lo suficientemente bueno porque tampoco manejaba algún otro caso que no era en su fragmento (por ejemplo, no manejó didUpdateWidget, o no manejó tener dos de estos fragmentos uno al lado del otro, u otras cosas perfectamente razonables que le gustaría manejar). Espero que con una aplicación más elaborada podamos obtenerla lo suficientemente elaborada como para que, una vez que tengamos una solución, no haya un momento de "atrapamiento" en el que surja algún problema nuevo que también necesite ser manejado. Obviamente, todavía es posible que todos perdamos algo que necesita ser manejado, pero la idea es minimizar la posibilidad.

Con respecto a las publicaciones recientes menos que totalmente acogedoras, por favor, concéntrese en crear ejemplos en el repositorio de

Puede enviar su solución allí si cree que ha cubierto todos los requisitos.

Los requisitos se cambiaron después del hecho. Proporcioné dos soluciones. Uno que hace un compromiso (uno muy razonable) y uno que implementa el comportamiento exacto del ejemplo proporcionado.

Proporcionaste un código que solo es aparentemente equivalente a la versión del gancho de @rrousselGit , pero en realidad no es equivalente,

No implementé la "solución de ganchos", implementé el ejemplo de ValueListenableBuilder, enfocándome específicamente en el "problema de anidamiento". No hace todo lo que hacen los ganchos, simplemente mostré cómo un elemento de la lista de quejas se puede simplificar utilizando una solución alternativa.

Si se le permite traer paquetes externos a la discusión, yo también.

Reutilización: eche un vistazo al ejemplo en el repositorio a continuación
https://github.com/Rudiksz/cbl_example

Nota:

  • está pensado como una demostración de cómo encapsular la "lógica" fuera de los widgets y tener widgets que son esbeltos, casi de aspecto html
  • no cubre todo lo que cubren los ganchos. Ese no es el punto. Pensé que estábamos discutiendo alternativas al marco de acciones Flutter, y no al paquete de ganchos.
  • No cubre todos los casos de uso que pueden plantear todos los equipos de marketing.
  • pero, el objeto Counter en sí es bastante flexible, se puede usar de forma independiente (ver el título de la AppBar), como parte de un widget complejo que necesita calcular los totales de diferentes contadores, reactivarse o responder a las entradas del usuario.
  • Depende de los widgets consumidores personalizar la cantidad, el valor inicial, la duración y el tipo de animación de los contadores que desean usar.
  • los detalles de la forma en que se manejan las animaciones pueden tener inconvenientes. Si el equipo de Flutter dice que sí, usar controladores de animación e interpolaciones fuera de los widgets rompe de alguna manera el marco, lo reconsideraré. Definitivamente hay cosas que mejorar. Reemplazar el tickerprovider personalizado que uso con uno creado por el mixin del widget consumidor es trivial. Yo no lo he hecho.
  • Esta es solo una solución alternativa más al patrón "Builder". Si dice que necesita o desea utilizar Builders, nada de esto se aplica.
  • Aún así, el hecho es que hay formas de simplificar el código, sin funciones adicionales. Si no le gusta, no lo compre. No estoy abogando por nada de esto para que se incorpore al marco.

Editar: Este ejemplo tiene un error, si inicia un "incremento" mientras el cambio está animado, el contador se reinicia y se incrementa desde el valor actual. No lo arreglé a propósito, porque no sé los requisitos exactos que podría tener para estos "contadores". Una vez más, es trivial cambiar los métodos de incremento / decremento para solucionar este problema.

Eso es todo.

@Hixie ¿Debo interpretar tu comentario como una forma de decir que mi ejemplo (https://github.com/TimWhiting/local_widget_state_approaches/blob/master/lib/hooks/animated_counter.dart) no es lo suficientemente bueno?

Además, ¿podríamos tener una reunión telefónica de Zoom / Google?

Tampoco estoy seguro de cuál es el efecto de reaccionar emoji a tu propia publicación.

Nada. Es totalmente irrelevante para nada. ¿Por qué lo mencionaste?

@rrousselGit Solo tú puedes saber si es lo suficientemente bueno. Si encontramos una manera de refactorizar ese ejemplo para que sea limpio, corto y no tenga código duplicado, ¿quedará satisfecho? ¿O hay cosas que cree que deberíamos admitir que no se manejan con ese ejemplo y que debemos manejar para satisfacer este error?

Solo tu puedes saber si es lo suficientemente bueno

No puedo ser el juez de eso. Para empezar, no creo que podamos capturar el problema utilizando un conjunto finito de aplicaciones.

No me importa trabajar como usted quiere, pero necesito orientación ya que no entiendo cómo eso nos hará progresar.

Creo que hemos proporcionado un montón de fragmentos de código que muestran el problema desde nuestra perspectiva. Realmente no creo que se vuelva más claro a través de más ejemplos de código, si los que se muestran no lo están haciendo.

Por ejemplo, si ver varios constructores anidados que son horribles de leer, o 50 líneas de texto estándar puro que tiene muchas oportunidades para errores, no demuestra el problema con la suficiente fuerza, no hay ningún lugar adonde ir.

Es muy extraño ofrecer mixins y las funciones son una solución aquí, cuando toda la solicitud está en estado encapsulado , eso es reutilizable. Las funciones no pueden mantener el estado. Los mixins no están encapsulados. Esta sugerencia pierde todo el sentido de todos los ejemplos y la justificación proporcionados y aún muestra un profundo malentendido de la pregunta.

Para mí, creo que hemos golpeado 2 puntos en la tierra, y no creo que podamos discutir seriamente con ninguno de los dos.

  1. Los constructores anidados son intrínsecamente difíciles de leer
  2. Aparte de los constructores anidados, _no hay forma_ de encapsular y compartir el estado que tenga ganchos del ciclo de vida de los widgets.

Como se dijo muchas veces, e incluso en la encuesta de Remi, en pocas palabras: queremos las capacidades del constructor, sin la verbosidad y los cierres anidados del constructor. ¿No lo resume completamente? Estoy totalmente confundido por qué se requiere un ejemplo de código adicional para continuar aquí.

La encuesta de Remi nos muestra que un ~ 80% de los desarrolladores de Flutter preferirían algún tipo de habilidad para evitar constructores anidados en su código cuando sea posible. Esto realmente habla por sí mismo en mi opinión. No es necesario que nos lo quite en este hilo, cuando el sentimiento de la comunidad es tan claro.

Desde mi perspectiva, los problemas son claros, y se aclaran aún más cuando miras los marcos en competencia que dedican párrafos a describir el fundamento aquí. Vue, React, Flutter, todos son primos, todos se derivan de React, y todos enfrentan este problema con el estado de reutilización que tiene que vincularse con el ciclo de vida del widget. Todos describen en detalle por qué han implementado algo como esto. Todo está ahí. Todo es relevante.

@rrousselGit, ¿ podrías dar un ejemplo de tener muchos ganchos múltiples? Por ejemplo, estoy haciendo una animación con posiblemente docenas de AnimationControllers. Con Flutter regular, puedo hacer:

List<AnimationController> controllers = [];
int numAnimationControllers = 50;

<strong i="7">@override</strong>
void initState() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers.add(AnimationController(...));
}

<strong i="8">@override</strong>
void dispose() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers[i].dispose();
}

Pero con los ganchos no puedo llamar a useAnimationController en un bucle. Supongo que este es un ejemplo trivial, pero realmente no pude encontrar la solución en ningún lado para este tipo de caso de uso.

@satvikpendem

algunos ejemplos de mis aplicaciones que están en producción (algunos de los ganchos, como el envío de solicitudes con paginación, se pueden fusionar / refactorizar en un solo gancho, pero eso es irrelevante aquí):

búsqueda de datos simple con paginación:

    final selectedTab = useState(SelectedTab.Wallet);
    final isDrawerOpen = useValueNotifier(false);
    final pageNo = useValueNotifier(0);
    final generalData = useValueNotifier(initialData);
    final services = useXApi();
    final isLoading = useValueNotifier(false);
    final waybillData = useValueNotifier<List<WaybillResponseModel>>([]);
    final theme = useTheme();
    final router = useRouter();

    fetchNextPage() async {
      if (isLoading.value || selectedTab.value != SelectedTab.Wallet) return;
      isLoading.value = true;
      final request = WaybillRequestModel()..pageNo = pageNo.value;
      final result = await services.waybillGetList(model: request);
      if (result.isOk && result.data.length > 0) {
        pageNo.value += 1;
        waybillData.value = [...waybillData.value, ...result.data];
      }
      isLoading.value = false;
    }

    // first fetch
    useEffect(() {
      fetchNextPage();
      return () {};
    }, []);

lógica de formulario (formulario de inicio de sesión con verificación de número de teléfono y temporizador de reenvío):

    final theme = useTheme();
    final loginState = useValueNotifier(LoginState.EnteringNumber);
    final error = useValueNotifier<String>(null);
    final phoneNumberController = useTextEditingController(text: "");
    final phoneNumberFocusNode = useMemoized(() => FocusNode(), []);
    final otpFocusNode = useMemoized(() => FocusNode(), []);
    final otpController = useTextEditingController(text: "");
    final appState = Provider.of<AppStateController>(context);
    final services = useXApi();
    final router = useRouter();
    final resendTimerValue = useValueNotifier(0);
    useEffect(() {
      var timer = Timer.periodic(Duration(seconds: 1), (t) async {
        if (resendTimerValue.value > 0) resendTimerValue.value--;
      });
      return () => timer.cancel();
    }, []);

    final doLogin = () async {
      // login
      loginState.value = LoginState.LoggingIn;
      final loginResult = await services.authLoginOrRegister(
        mobileNumber: phoneNumberController.text,
      );
      if (loginResult.isOk) {
        loginState.value = LoginState.EnteringOtpCode;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          FocusScope.of(context).requestFocus(otpFocusNode);
        });
        resendTimerValue.value = 30;
      } else {
        error.value = loginResult.errorMessage;
        loginState.value = LoginState.EnteringNumber;
        WidgetsBinding.instance.addPostFrameCallback((_) {
          FocusScope.of(context).requestFocus(phoneNumberFocusNode);
        });
      }
    };

para la animación, creo que @rrousselGit ya proporcionó suficientes ejemplos.

No quiero hablar sobre cómo la naturaleza componible de los ganchos puede hacer que la refactorización del código anterior sea mucho más fácil, reutilizable y más limpia, pero si lo desea, también puedo publicar versiones refactorizadas.

Como se dijo muchas veces, e incluso en la encuesta de Remi, en pocas palabras: queremos las capacidades del constructor, sin la verbosidad y los cierres anidados del constructor. ¿No lo resume completamente? Estoy totalmente confundido por qué se requiere un ejemplo de código adicional para continuar aquí.

Literalmente proporcioné ejemplos de cómo puede reducir la verbosidad y evitar el anidamiento de los constructores usando un ejemplo que Remi proporcionó.
Tomé su código, lo pegué en mi aplicación, lo ejecuté y lo reescribí. El resultado final en lo que respecta a la funcionalidad fue casi idéntico, tanto como pude deducir simplemente ejecutando el código, porque el código no venía con requisitos. Claro que podemos discutir los casos extremos y los problemas potenciales, pero en cambio se llamó fuera de tema.

Para casos de uso simples, uso Builders, para casos de uso complejos, no uso Builders. El argumento aquí es que sin usar Builders no hay una manera fácil de escribir código conciso y reutilizable. Implícitamente, eso también significa que los constructores son imprescindibles y la única forma de desarrollar aplicaciones de Flutter. Eso es demostrablemente falso.

Acabo de mostrar un código funcional de prueba de concepto que lo demuestra. No usó Builders ni ganchos, y no cubre el 100% del "conjunto infinito de problemas" que este problema de github en particular parece querer resolver. Fue llamado fuera de tema.
Nota al margen, también es muy eficiente, incluso sin ningún punto de referencia, supongo que incluso supera a los widgets de Builder. Estoy feliz de cambiar de opinión si se demuestra que estoy equivocado, y si alguna vez encuentro que Mobx se convierte en un cuello de botella en el rendimiento, abandonaré Mobx y cambiaré a constructores de vainilla en un santiamén.

Hixie trabaja para Google, tiene que ser paciente y educado contigo y no puede criticarte por tu falta de compromiso. Lo mejor que puede hacer es presionar para obtener más ejemplos.

No llamé a nadie ni provoqué ataques personales. Solo reaccioné a los argumentos presentados aquí, compartí mi opinión
(que sé que es impopular y minoritario) e incluso traté de presentar contraejemplos reales con código. Podría hacer más, estoy dispuesto a discutir dónde mis ejemplos se quedan cortos y ver formas en que podemos mejorarlos, pero sí, que me llamen fuera de tema es algo desagradable.

No tengo nada que perder, aparte de tal vez estar prohibido, así que realmente no me importa llamarte.
Es evidente que ustedes dos están totalmente convencidos de que los ganchos son la única solución ("porque React lo hace") a cualquier problema que experimente y que, a menos que una alternativa cumpla con el 100% del "conjunto infinito de problemas" que está imaginando, ni siquiera considerará participar.

Eso no es razonable y muestra falta de deseo de participar verdaderamente.


Por supuesto, todo lo anterior "es solo mi opinión".

Veo la utilidad de los ganchos en ese ejemplo, pero supongo que no entiendo cómo funcionaría en mi caso, donde parece que querría inicializar muchos objetos a la vez, en este caso AnimationController s pero en realidad podría ser cualquier cosa. ¿Cómo manejan los ganchos este caso?

Básicamente, ¿hay una forma de ganchos de convertir esto?

var x1 = useState(1);
var x2 = useState(2);
var x3 = useState(3);

Dentro

var xs = []
for (int i = 0; i < 3; i++)
     xs[i] = useState(i);

¿Sin violar las reglas del gancho? Porque enumeré el equivalente en Flutter normal. No tengo mucha experiencia con los hooks en Flutter, así que tengan paciencia conmigo.

Quiero simplemente crear una matriz de objetos de gancho (AnimationControllers, por ejemplo) a pedido con todo su initState y eliminarlo ya instanciado, simplemente no estoy seguro de cómo funciona en los ganchos.

@satvikpendem piensa en ganchos como propiedades en una clase. ¿Los define en un bucle o los nombra manualmente uno por uno?

en su ejemplo definiendo así

var x1 = useState(1);
var x2 = useState(2);
var x3 = useState(3);

es útil para este caso de uso:

var isLoading = useState(1);
var selectedTab = useState(2);
var username = useState(3); // text field

¿Ves cómo cada useState está relacionado con una parte nombrada de la lógica de tu estado? (como el useState de isLoading al que está conectado cuando la aplicación está en estado de carga)

en su segundo fragmento, está llamando a useState en un bucle. está pensando en useState como un titular de valor que no forma parte de la lógica de su estado. ¿Es necesaria esta lista para mostrar un montón de elementos en un ListView ? En caso afirmativo, debería pensar en cada elemento de la lista como un estado, no individualmente.

final listData = useState([]);

esto es solo para useState y puedo ver algunos casos de uso (que creo que son muy raros) para llamar a algunos ganchos en una condición o en un bucle. para ese tipo de ganchos, debería haber otro gancho para manejar la lista de datos en lugar de uno. por ejemplo:

var single = useTest("data");
var list = useTests(["data1", "data2"]);
// which is equivalent to
var single1 = useTest("data1");
var single2 = useTest("data2");

Ya veo, entonces con los ganchos parece que necesitamos crear un gancho separado para manejar casos con una variedad de elementos, como múltiples AnimationControllers.

Esto es lo que tenía inicialmente que no parece funcionar:

  final animationControllers = useState<List<AnimationController>>([]);

  animationControllers.value = List<AnimationController>.generate(
    50,
    (_) => useAnimationController(),
  );

pero supongo que si escribo mi propio gancho para manejar varios elementos, esto debería funcionar, ¿verdad?

class _MultipleAnimationControllerHook extends Hook<MultipleAnimationController> {
  const _MultipleAnimationControllerHook({
    this.numControllers,
    this.duration,
    this.debugLabel,
    this.initialValue,
    this.lowerBound,
    this.upperBound,
    this.vsync,
    this.animationBehavior,
    List<Object> keys,
  }) : super(keys: keys);

  /// Take in number of controllers wanted
  /// This hook assumes all `AnimationController`s will have the same parameters
  final int numControllers; 

  final Duration duration;
  final String debugLabel;
  final double initialValue;
  final double lowerBound;
  final double upperBound;
  final TickerProvider vsync;
  final AnimationBehavior animationBehavior;

  <strong i="10">@override</strong>
  _AnimationControllerHookState createState() =>
      _AnimationControllerHookState();
}

class _AnimationControllerHookState
    extends HookState<AnimationController, _AnimationControllerHook> {
  List<AnimationController> _multipleAnimationController; // return a list instead of a singular item

  <strong i="11">@override</strong>
  void initHook() {
    super.initHook();
    for (int i = 0; i < hook.numControllers) // just added loops 
        _multipleAnimationController[i] = AnimationController(
          vsync: hook.vsync,
          duration: hook.duration,
          debugLabel: hook.debugLabel,
          lowerBound: hook.lowerBound,
          upperBound: hook.upperBound,
          animationBehavior: hook.animationBehavior,
          value: hook.initialValue,
        );
  }

  <strong i="12">@override</strong>
  void didUpdateHook(_AnimationControllerHook oldHook) {
      for (int i = 0; i < numControllers; i++) {
        if (hook.vsync != oldHook[i].vsync) {
           _multipleAnimationController[i].resync(hook.vsync);
        }

        if (hook.duration != oldHook[i].duration) {
          _multipleAnimationController[i].duration = hook.duration;
        }
      }
  }

  <strong i="13">@override</strong>
  MultipleAnimationController build(BuildContext context) {
    return _multipleAnimationController;
  }

  <strong i="14">@override</strong>
  void dispose() {
    _multipleAnimationController.map((e) => e.dispose());
  }
}

¿Significa esto que si tenemos una versión singular de un gancho, simplemente no podemos usarlo para una versión con múltiples elementos y en su lugar tenemos que reescribir la lógica? ¿O hay una mejor manera de hacer esto?

Si alguien también quiere dar un ejemplo de no ganchos que también me gustaría saber, me he estado preguntando acerca de esta pieza del rompecabezas de la reutilización. Tal vez haya una manera de encapsular este comportamiento en una clase, que tiene su propio campo AnimationController, pero si se crea dentro de un bucle, entonces el gancho también lo estaría, rompiendo las reglas. Quizás podríamos considerar cómo lo hace Vue, que no se ve afectado por condicionales y bucles para la implementación de sus ganchos.

@satvikpendem

No creo que mi estado de cuenta sea válido para AnimationController o useAnimationController

porque aunque puede tener más de un AnimationController pero no necesariamente los almacena en una matriz para usarlos en el método de clase. por ejemplo:

useSingleTickProvider();
final animation1 = useAnimationController();
final animation2 = useAnimationController();
final animation3 = useAnimationController();
// ...
useEffect(() async {
  await animation1.forward();
  await Future.sleep(100);
  await animation1.reverse();
  await animation2.forward();
  await animation3.forward();
}, []);

(no crea una lista y los hace referencia como animation[0] )

Honestamente, en mi experiencia en reaccionar y aletear con ganchos, rara vez necesitaba llamar a algún tipo de gancho en un bucle. incluso entonces la solución era sencilla y fácil de implementar. ahora que lo pienso, definitivamente podría resolverse de una mejor manera como crear un Componente (widget) para cada uno de ellos que es IMO es la solución "más limpia".

para responder a su pregunta si hay una manera más fácil de manejar múltiples AnimationController , sí, la hay:

final ticker = useTickerProvider();
final controllers = useMemo(() => [AnimationController(ticker), AnimationController(ticker)], []);

useEffect(() {
  controllers.forEach(x => x.resync(ticker));
  return () => controllers.forEach(x => x.dispose());
}, [ticker, controllers]);

  • también puede usar useState si AnimationController s son dinámicos.

(también se vuelve a sincronizar cuando se cambia el ticker)

@rrousselGit, ¿ podrías dar un ejemplo de tener muchos ganchos múltiples? Por ejemplo, estoy haciendo una animación con posiblemente docenas de AnimationControllers. Con Flutter regular, puedo hacer:

List<AnimationController> controllers = [];
int numAnimationControllers = 50;

<strong i="8">@override</strong>
void initState() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers.add(AnimationController(...));
}

<strong i="9">@override</strong>
void dispose() {
    for (int i = 0; i < numAnimationControllers; i++)
        controllers[i].dispose();
}

Pero con los ganchos no puedo llamar a useAnimationController en un bucle. Supongo que este es un ejemplo trivial, pero realmente no pude encontrar la solución en ningún lado para este tipo de caso de uso.

Los Hooks lo hacen de manera diferente.

Ya no creamos una Lista de controladores, sino que bajamos la lógica del controlador al elemento:

Widget build(context) {
  return ListView(
    children: [
      for (var i = 0; i < 50; i++)
        HookBuilder(
          builder: (context) {
            final controller = useAnimationController();
          },
        ),
    ],
  );
}

Seguimos fabricando nuestros 50 controladores de animación, pero pertenecen a un widget diferente.

Tal vez podría compartir un ejemplo de por qué lo necesitaba, y podríamos intentar convertirlo en ganchos y agregarlo al repositorio de Tim.

Hixie trabaja para Google, tiene que ser paciente y educado contigo y no puede criticarte por tu falta de compromiso. Lo mejor que puede hacer es presionar para obtener más ejemplos.

@Hixie , si así es como se siente, dígalo (ya sea aquí o contácteme en privado).

Literalmente proporcioné ejemplos de cómo puede reducir la verbosidad y evitar el anidamiento de los constructores usando un ejemplo que Remi proporcionó.

Gracias, pero no me queda claro cómo extraería un patrón común de este código al aplicar esta lógica a diferentes casos de uso.

En el OP, mencioné que actualmente, tenemos 3 opciones:

  • usar Builders y tener código anidado
  • no factorice el código en absoluto, que no se escala a una lógica de estado más compleja (yo diría que StreamBuilder y su AsyncSnapshot es una lógica de estado compleja).
  • intente crear algo de arquitectura usando mixins / oop / ..., pero termine con una solución demasiado específica para el problema que cualquier caso de uso que sea un poco diferente requerirá una reescritura.

Me parece que usó la tercera opción (que está en la misma categoría que las primeras iteraciones de las propuestas Property o addDispose ).

Previamente hice una cuadrícula de evaluación para juzgar el patrón:

¿Podrías ejecutar tu variante en esto? Especialmente el segundo comentario sobre la implementación de todas las características de StreamBuilder sin duplicar el código si se usa varias veces.

Mi plan en este punto sobre este error es:

  1. Tome los ejemplos de https://github.com/flutter/flutter/issues/51752#issuecomment -675285066 y cree una aplicación usando Flutter puro que muestre esos diversos casos de uso juntos.
  2. Intente diseñar una solución que permita la reutilización de código para esos ejemplos que satisfaga los diversos requisitos clave que se han discutido aquí y que se ajuste a nuestros principios de diseño.

Si alguien quisiera ayudar con cualquiera de estos, definitivamente estoy feliz de tener ayuda. Es poco probable que llegue a esto pronto porque estoy trabajando primero en la transición NNBD.

@rrousselGit Seguro, estoy creando una aplicación en la que muchos Widgets pueden moverse por la pantalla (llamémoslos Box es), y deberían poder moverse independientemente unos de otros (por lo que debe haber al menos un AnimationController por cada Box ). Aquí hay una versión que hice con solo un AnimationController compartido entre los múltiples Widgets, pero en el futuro puedo animar cada Widget de forma independiente, por ejemplo, para hacer transformaciones complicadas, como implementar un CupertinoPicker , con su efecto de rueda de desplazamiento personalizado. .

Hay tres cuadros en una pila que se mueven hacia arriba y hacia abajo cuando hace clic en un FloatingActionButton.

import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';

void main(List<String> args) => runApp(const App());

class App extends HookWidget {
  const App({Key key});

  static const Duration duration = Duration(milliseconds: 500);
  static const Curve curve = Curves.easeOutBack;

  <strong i="11">@override</strong>
  Widget build(BuildContext context) {
    final AnimationController controller =
        useAnimationController(duration: duration);
    final Animation<double> animation = Tween<double>(
      begin: 0,
      end: 300,
    )
        .chain(
          CurveTween(
            curve: curve,
          ),
        )
        .animate(controller);
    final ValueNotifier<bool> isDown = useState<bool>(false);
    final ValueNotifier<int> numBoxes = useState<int>(3);

    return MaterialApp(
      home: SafeArea(
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              if (!isDown.value) {
                controller.forward();
                isDown.value = true;
              } else {
                controller.reverse();
                isDown.value = false;
              }
            },
          ),
          body: AnimatedBuilder(
            animation: animation,
            builder: (_, __) => Boxes(
              numBoxes: numBoxes.value,
              animation: animation,
            ),
          ),
        ),
      ),
    );
  }
}

class Boxes extends StatelessWidget {
  Boxes({
    <strong i="12">@required</strong> this.numBoxes,
    <strong i="13">@required</strong> this.animation,
  });

  final int numBoxes;
  final Animation<double> animation;

  <strong i="14">@override</strong>
  Widget build(BuildContext context) {
    return Stack(
      children: List<Widget>.generate(
        numBoxes,
        (int index) => Positioned(
          top: (animation.value) + (index * (100 + 10)),
          left: (MediaQuery.of(context).size.width - 100) / 2,
          child: Container(
            width: 100,
            height: 100,
            color: Colors.blue,
          ),
        ),
      ),
      // ],
    );
  }
}

En este caso, cada cuadro se mueve al unísono, pero uno puede imaginar un escenario más complejo, como crear una visualización para una función de clasificación, por ejemplo, o mover elementos en una lista animada, donde el widget padre conoce los datos sobre dónde cada Box debería ser y debería poder animar cada uno como mejor le parezca.

El problema parece ser que los AnimationControllers y los Box es que los usan para impulsar su movimiento no están en la misma clase, por lo que uno tendría que pasar por el AnimationController manteniendo una matriz de ellos para usar en un Constructor, o haga que cada Box mantenga su propio AnimationController.

Con los ganchos, dado que Box es y el widget principal no están en la misma clase, ¿cómo haría una lista de AnimationControllers para el primer caso en el que cada Box se pasa en un AnimationController? Esto parece no ser necesario según su respuesta anterior con HookBuilder, pero luego, si me muevo por el estado hacia el widget secundario como usted dice, y elijo hacer que cada Box tenga su propio AnimationController a través de useAnimationController , Me encuentro con otro problema: ¿cómo expondría el AnimationController creado a la clase principal para que coordine y ejecute las animaciones independientes para cada niño?

En Vue, puede emitir un evento al padre a través del patrón emit , por lo que en Flutter, ¿necesito una solución de administración de estado superior como Riverpod o Rx donde el padre actualiza el estado global y el niño escucha el global? ¿estado? Parece que no debería, al menos para un ejemplo simple como este. Gracias por aclarar mis confusiones.

@satvikpendem Lo siento, no estaba claro. ¿Podrías mostrar cómo lo harías sin ganchos, en lugar del problema en el que estás bloqueando con ganchos?

Quiero tener una comprensión clara de lo que está tratando de hacer en lugar de dónde se está estancando.

Pero como una suposición rápida, creo que está buscando la curva de intervalo y tiene un solo controlador de animación.

@rrousselGit Claro, aquí está

import 'package:flutter/material.dart';

void main(List<String> args) => runApp(const App());

class Animator {
  Animator({this.controller, this.animation});
  AnimationController controller;
  Animation<double> animation;
}

class App extends StatefulWidget {
  const App({Key key});

  static const Duration duration = Duration(milliseconds: 500);
  static const Curve curve = Curves.easeOutBack;

  <strong i="7">@override</strong>
  _AppState createState() => _AppState();
}

class _AppState extends State<App> with TickerProviderStateMixin {
  List<Animator> animators = [];
  bool isDown = false;
  int numBoxes = 3;

  <strong i="8">@override</strong>
  void initState() {
    for (int i = 0; i < numBoxes; i++) {
      final AnimationController c = AnimationController(
        duration: App.duration,
        vsync: this,
      );
      animators.add(
        Animator(
          controller: c,
          animation: Tween<double>(
            begin: 0,
            end: 300,
          )
              .chain(
                CurveTween(
                  curve: App.curve,
                ),
              )
              .animate(c),
        ),
      );
    }
    super.initState();
  }

  <strong i="9">@override</strong>
  void dispose() {
    for (int i = 0; i < numBoxes; i++) {
      animators[i].controller.dispose();
    }
    super.dispose();
  }

  <strong i="10">@override</strong>
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SafeArea(
        child: Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              if (!isDown) {
                for (final Animator animator in animators) {
                  animator.controller.forward();
                }
                setState(() {
                  isDown = true;
                });
              } else {
                for (final Animator animator in animators) {
                  animator.controller.reverse();
                }
                setState(() {
                  isDown = false;
                });
              }
            },
          ),
          body: Stack(
            children: List<Box>.generate(
              numBoxes,
              (int index) => Box(
                index: index,
                animation: animators[index].animation,
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class Box extends StatelessWidget {
  Box({
    <strong i="11">@required</strong> this.animation,
    <strong i="12">@required</strong> this.index,
  });

  final int index;
  final Animation<double> animation;

  <strong i="13">@override</strong>
  Widget build(BuildContext context) {
    return Positioned(
      top: (animation.value) + (index * (100 + 10)),
      left: (MediaQuery.of(context).size.width - 100) / 2,
      child: Container(
        width: 100,
        height: 100,
        color: Colors.blue,
      ),
    );
  }
}

De hecho, quiero varios controladores de animación, uno para cada widget, ya que pueden moverse de forma independiente entre sí, con sus propias duraciones, curvas, etc. Tenga en cuenta que el código anterior parece tener un error que no pude averiguar, donde debería animar limpiamente, pero básicamente debería animar 3 cuadros hacia arriba y hacia abajo con un clic de botón. Podemos imaginar un escenario donde en lugar de que cada uno tenga la misma curva, le doy a cada uno una curva diferente, o hago 100 casillas, cada una con una duración mayor o menor que la anterior, o hago subir las pares y los impares bajan, y así sucesivamente.

Con Flutter normal, initState y dispose pueden tener bucles, pero no así parece con los ganchos, así que me pregunto cómo se puede combatir eso. Además, no quiero poner la clase Box dentro del widget principal, ya que no quiero encapsularlos a ambos; Debería poder mantener la lógica principal igual, pero intercambiar Box con Box2 por ejemplo.

¡Gracias!
He enviado su ejemplo al @TimWhiting , con un gancho equivalente

TL; DR, con ganchos (o constructores), pensamos declarativamente en lugar de imperativamente. Entonces, en lugar de tener una lista de controladores en un widget, luego manejarlos imperativamente, lo que mueve el controlador al elemento e implementa una animación implícita.

¡Gracias @rrousselGit! Estuve luchando con este tipo de implementación durante un tiempo después de comenzar a usar ganchos, pero ahora entiendo cómo funciona. Acabo de abrir un PR para una versión con un objetivo diferente para cada controlador de animación, ya que podría ser más convincente para comprender por qué los ganchos son útiles como dije anteriormente:

Podemos imaginar un escenario donde en lugar de que cada uno tenga la misma curva, le doy a cada uno una curva diferente, o hago 100 casillas, cada una con una duración mayor o menor que la anterior, o hago subir las pares y los impares bajan, y así sucesivamente.

Había estado tratando de hacer la versión declarativa, pero supongo que lo que no entendí fue el método de ciclo didUpdateWidget/Hook vida

Encontré un ejemplo del mundo real en mi base de código hoy, así que pensé que también podría compartirlo.

Entonces, en este escenario, estoy trabajando con Firestore y tengo un texto estándar que quiero realizar con cada StreamBuilder, así que hice mi propio constructor personalizado. También necesito trabajar con ValueListenableque permite al usuario reordenar la lista. Por razones de costo monetario relacionadas con Firestore, esto requiere una implementación muy específica (cada artículo no puede almacenar su propio pedido, en su lugar, la lista debe guardarlo como un campo de ID concatenados), esto se debe a que Firestore cobra por cada escritura, por lo que potencialmente puede ahorrar mucho dinero de esta manera. Termina leyendo algo como esto:

return ClipRRect(
      borderRadius: BorderRadius.circular(CornerStyles.dialog),
      child: Scaffold(
        backgroundColor: state.theme.scaffoldBackgroundColor,
        body: FamilyStreamBuilder<DocumentSnapshot>(
          stream: state.listRef.snapshots(),
          builder: (context, AsyncSnapshot<DocumentSnapshot> documentSnapshot) {
            //When a list is updated, we need to update the listOrder
            state.updateListOrderFromSnapshot(documentSnapshot);
            return FamilyStreamBuilder<QuerySnapshot>(
                stream: state.itemsCollection.snapshots(),
                builder: (_, AsyncSnapshot<QuerySnapshot> itemsSnapshot) {
                  //Sort the list items by the idField on the list-doc
                  List<DocumentSnapshot> items = itemsSnapshot.data.documents;
                  state.handleDocsSync(items);
                  return ValueListenableBuilder(
                    valueListenable: state.listOrderNotifier,
                    builder: (_, List<String> listOrder, __) {
                      List<DocumentSnapshot> ordered = state.sortItems(items, listOrder);
                     //Set the firstCompleted item if we have one
                      state.firstCompletedItem = ordered
                          ?.firstWhere((element) => element.data[FireParams.kIsComplete] == true, orElse: () => null)
                          ?.documentID;
                      return _buildItemList(items, ordered);
                    },
                  );
                });
          },
        ),
      ),
    );

Parece que sería mucho más fácil razonar si pudiera escribirlo más como:

    DocumentSnapshot list = useFamilyStream(state.listRef.snapshots());
    List<DocumentSnapshot> items = useFamilyStream(state.itemsCollection.snapshots()).data.documents;
    List<String> listOrder = useValueListenable(state.listOrderNotifier);

    //When a list is updated, we need to update the listOrder
    state.updateListOrderFromSnapshot(list);

   //Sort the list items by the idField on the list-doc
    state.handleDocsSync(items);
    List<DocumentSnapshot> ordered = state.sortItems(items, listOrder);

    //Set the firstCompleted item if we have one
    state.firstCompletedItem = ordered
        ?.firstWhere((element) => element.data[FireParams.kIsComplete] == true, orElse: () => null)
        ?.documentID;

    return ClipRRect(
      borderRadius: BorderRadius.circular(CornerStyles.dialog),
      child: Scaffold(
          backgroundColor: state.theme.scaffoldBackgroundColor,
          body: _buildItemList(items, ordered)
      ));

Esto pierde la optimización con respecto a las reconstrucciones granulares, pero eso no haría ninguna IRL diferente ya que todos los elementos visuales están en el nodo de la hoja más inferior, todos los contenedores son de estado puro.

Al igual que con muchos escenarios del mundo real, el consejo de "Simplemente no use X" no es realista, ya que Firebase solo tiene un método de conexión que es Streams, y cada vez que quiero este comportamiento similar a un socket no tengo más remedio que utilizar un Stream. Así es la vida.

Esto pierde la optimización con respecto a las reconstrucciones granulares, pero eso no haría ninguna IRL diferente ya que todos los elementos visuales están en el nodo de la hoja más inferior, todos los contenedores son de estado puro.

Todavía marca la diferencia. Si un nodo es visual o no, no afecta si cuesta algo reconstruirlo.

Probablemente factorizaría ese ejemplo en diferentes widgets (los IDE tienen herramientas de refactorización con un solo clic para hacerlo realmente fácil). _buildItemList probablemente debería ser un widget, al igual que la parte enraizada en FamilyStreamBuilder .

Realmente no perdemos la reconstrucción granular.
De hecho, los ganchos mejoran ese aspecto, al permitir almacenar en caché fácilmente la instancia del widget usando useMemoized .

Hay algunos ejemplos en el repositorio de Tim que hacen eso.

Probablemente factorizaría ese ejemplo en diferentes widgets (los IDE tienen herramientas de refactorización con un solo clic para hacerlo realmente fácil). _buildItemList probablemente debería ser un widget, al igual que la parte enraizada en FamilyStreamBuilder .

La cosa es que realmente no quiero hacer esto, porque no tengo ninguna preocupación de rendimiento en esta vista. Así que el 100% del tiempo favoreceré la localidad y la coherencia del código sobre la microoptimización como esta. Esta vista solo se reconstruye cuando el usuario inicia una acción (~ avg una vez cada 10 segundos), o cuando los datos del backend cambian y están mirando una lista abierta (casi nunca sucede). También resulta ser una vista simple que es principalmente una lista, y la lista tiene un montón de sus propias optimizaciones internamente. Me doy cuenta de que build () técnicamente puede dispararse en cualquier momento, pero en la práctica, las reconstrucciones aleatorias son bastante raras.

En mi opinión, es mucho más fácil trabajar y depurar esta vista si toda esta lógica está agrupada en un widget, principalmente como un esfuerzo para hacer mi vida más fácil cuando vuelva a ella en el futuro :)

Otra cosa a tener en cuenta es que el anidamiento básicamente "me obligó a salir" del método de construcción, ya que no había forma de que pudiera comenzar a construir mi árbol dentro de 3 cierres y 16 espacios en el hoyo.

Y sí, se podría decir que tiene sentido pasar a un widget separado. Pero, ¿por qué no seguir con el método de construcción? Si pudiéramos reducir el texto estándar a lo que realmente _necesita_ ser, entonces no hay necesidad de tener la molestia de legibilidad y mantenimiento de dividir las cosas en 2 archivos. (Asumir que el rendimiento no es una preocupación, que a menudo simplemente no lo es)

Recuerde que en este escenario ya creé un widget personalizado para manejar mi Stream Builder. ¿Ahora necesitaría hacer otro para manejar la composición de estos constructores? Parece un poco exagerado.

porque no tengo ningún problema de rendimiento en esta vista

Oh, no lo refactorizaría en widgets para mejorar el rendimiento, los desarrolladores ya deberían ocuparse de eso. Lo refactorizaría para mejorar la legibilidad y la reutilización. No estoy diciendo que esa sea la forma "correcta" de hacerlo, solo digo cómo estructuraría el código. De todos modos, eso no es ni aquí ni allá.

de ninguna manera podría comenzar a construir mi árbol dentro de 3 cierres y 16 espacios en el hoyo

Puede que tenga un monitor más ancho que tú ...: - /

entonces no hay necesidad de tener la molestia de legibilidad y mantenimiento de dividir las cosas en 2 archivos

Pondría los widgets en el mismo archivo, FWIW.

De todos modos, es un buen ejemplo, y creo que prefiere usar un widget con una sintaxis diferente que usar más widgets.

Puede que tenga un monitor más ancho que tú ...: - /

Tengo un ultra ancho: D, pero dartfmt obviamente nos limita a todos a 80. Así que perder 16 es significativo. El problema principal es que el final de mi declaración es que },);});},),),); no es realmente divertido cuando algo se estropea. Tengo que tener mucho cuidado cada vez que edito esta jerarquía, y los ayudantes IDE comunes como swap with parent dejan de funcionar.

Pondría los widgets en el mismo archivo, FWIW.

100%, pero todavía encuentro que saltar verticalmente en un solo archivo es más difícil de mantener. Inevitable, por supuesto, pero tratamos de reducir cuando es posible y 'mantener las cosas juntas'.

Sin embargo, de manera crucial, incluso si refactorizo ​​la lista principal en su propio widget (que estoy de acuerdo, es más legible que un método de compilación anidado), sigue siendo mucho más legible sin el anidamiento en el widget principal. Puedo entrar, entender toda la lógica de un vistazo rápido, ver claramente el widget _MyListView () y saltar directamente a él, seguro de que entiendo el contexto circundante. También puedo agregar / eliminar dependencias adicionales con relativa facilidad, por lo que se escala muy bien.

dartfmt obviamente nos limita a todos a 80

Quiero decir, esa es una de las razones por las que generalmente no uso dartfmt, y cuando lo hago, lo configuro en 120 o 180 caracteres ...

Tu experiencia aquí es totalmente válida.

Yo también, en realidad, 120 todo el día :) Pero pub.dev rebaja activamente los complementos que no están formateados en 80, y tengo la impresión de que yo (nosotros) somos minoría cuando cambiamos este valor.

Bueno, eso es absurdo, deberíamos arreglar eso.

pub.dev no rebaja los complementos que no respeten dartfmt. Solo muestra un comentario en la página de puntuación, pero la puntuación no se ve afectada
Pero podría decirse que hay más problemas con dartfmt que solo la longitud de la línea.

Una longitud de línea demasiado grande conduce a cosas que son más legibles en varias líneas para estar en una sola línea, como:

object
  ..method()
  ..method2();

que puede convertirse en:

object..method()..method2();

Estoy viendo esto?
image
Paquete en cuestión: https://pub.dev/packages/sized_context/score

Interesante: definitivamente no era así antes, ya que el proveedor no usó dartfmt durante un tiempo.
Me quedo corregido.

Sí, definitivamente es un comportamiento nuevo, cuando publiqué originalmente la primavera pasada, me aseguré de marcar todas las casillas y no se requería dartfmt.

Después de todas estas discusiones, espero que veamos soporte nativo para una solución similar a un gancho en Flutter. ya sea useHook o use Hook o cualquier cosa que el equipo de Flutter pueda sentir que su característica no es como React 😁🤷‍♂️

usamos ganchos de una manera como final controller = useAnimationController(duration: Duration(milliseconds: 800));
¿No es mejor usar la nueva función del programa Darts _Extension_ copiada de kotlin / swift a una hermosa sintaxis?

algo como: final controller = AnimationController.use(duration: Duration(milliseconds: 800));
con este enfoque, cuando el equipo de flutter / dart decide agregar use Hook lugar de la sintaxis actualmente disponible useHook , creo que un Annotation a esa función de extensión hizo que se leyera para usar como
final controller = use AnimationController(duration: Duration(milliseconds: 800));

también es comprensible / significativo tener use palabra clave utilizada como const y new :
new Something
const Something
use Something

Como beneficio adicional a esa recomendación, creo que por fin incluso las funciones de constructor / generador pueden usar / beneficiarse de la propuesta Annotation . luego, el compilador de dardos con algo de personalización lo convierte para admitir la palabra clave use .

Tan hermosa y característica específica de aleteo / dardo 😉

¿Estoy en lo cierto al suponer que los ejemplos en https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful ahora son representativos de los problemas que la gente quiere resolver?

No estoy seguro de cómo se sienten los demás, pero creo que los problemas están representados de alguna manera allí (lo que significa que no puedo estar seguro porque alguien podría señalar algo que no está representado).

He intentado una solución intermedia en ese repositorio. Es componible como ganchos, pero no depende del orden de las llamadas a funciones o no permite bucles, etc. Utiliza StatefulWidgets directamente. Implica un mixin, así como propiedades con estado que se identifican de forma única por claves. No estoy tratando de promover esto como la solución definitiva, sino como un término medio entre los dos enfoques.

Lo he llamado el enfoque lifecycleMixin, está muy cerca del enfoque LateProperty que se discutió aquí, pero la principal diferencia es que tiene más ciclos de vida implementados y se puede componer fácilmente. (en la parte de ciclos de vida, no he usado ciclos de vida de widgets que no sean initState y desecho mucho, por lo que podría haberme equivocado por completo).

Me gusta este enfoque porque:

  1. Tiene muy poca penalización por tiempo de ejecución.
  2. No hay lógica / funciones que creen o administren el estado en la ruta de compilación (las compilaciones pueden ser puras, solo el estado de recuperación).
  3. La gestión del ciclo de vida es más clara cuando se optimizan las reconstrucciones a través de un constructor. (Pero no sacrifica la capacidad de reutilización y componibilidad de pequeños fragmentos de estado).
  4. Dado que puede reutilizar la creación de bits de estado, se puede hacer una biblioteca de bits de estado comunes que deben crearse y eliminarse de ciertas maneras, por lo que hay menos repetición en su propio código.

No me gusta este enfoque (en comparación con los ganchos) por las siguientes razones:

  1. No sé si cubre todo lo que pueden hacer los ganchos.
  2. Usted tiene que utilizar llaves para identificar de forma única las propiedades. (Entonces, al componer las piezas de lógica que construyen algún estado, debe agregar a la clave para identificar de manera única cada parte del estado; hacer que la clave sea un parámetro posicional requerido ayuda, pero me encantaría una solución de nivel de idioma para acceder a un id único para una variable).
  3. Utiliza en gran medida extensiones para crear funciones reutilizables para crear bits de estado comunes. Y las extensiones no pueden ser importadas automáticamente por los IDE.
  4. Puede estropearse si mezcla ciclos de vida de diferentes widgets / accede a ellos entre widgets sin administrarlos explícitamente correctamente.
  5. La sintaxis del constructor es un poco extraña, por lo que el estado creado está dentro del alcance de la función de compilación, pero deja la función de compilación pura.
  6. Todavía no he implementado todos los ejemplos, por lo que podría haber un caso de uso que no pueda cubrir.

Ejemplo de contador simple .
Ejemplo de contadores animados

estructura
bits comunes de lógica de composición de estado reutilizable

No estoy seguro de cuánto tiempo tengo, los estudios de posgrado siempre me mantienen ocupado, pero me encantaría recibir comentarios. @rrousselGit ¿

No estoy tratando de promover mi solución, sino de fomentar una discusión positiva en un punto intermedio. Si podemos ponernos de acuerdo sobre lo que falta o lo que nos brinda esta solución, creo que estaremos haciendo un buen progreso hacia adelante.

@TimWhiting El principal problema que tengo con este enfoque es la falta de solidez. Un gran impulsor aquí es la necesidad de confiabilidad de los constructores, en forma sucinta. La identificación mágica y la capacidad de chocar en el ciclo de vida crean nuevos vectores para que ocurran errores, y continuaría recomendando a mi equipo que usen constructores, ya que a pesar de ser bastante desagradables de leer, al menos sabemos que son 100. % Libre de errores.

Con respecto a los ejemplos, sigo pensando que el ejemplo perfecto es simplemente usar un AnimationController, con un valor de duración vinculado al widget. Lo mantiene simple y familiar. No hay necesidad de ser más esotérico que eso, es un pequeño caso de uso perfecto para repetición reutilizable, necesita ganchos de ciclo de vida y todas las soluciones podrían juzgarse fácilmente por su capacidad para usar varias animaciones de manera sucinta.

Todo lo demás es solo una variación de este mismo caso de uso de 'Stateful Controller'. Quiero hacer X en initState e Y en estado de disposición, y actualizar Z cuando cambien mis dependencias. No importa lo que sean X, Y y Z.

Me pregunto si @rrousselGit podría proporcionar alguna información aquí, o si tiene algún dato sobre qué ganchos se utilizan más actualmente. Supongo que es 80% Stream y Animaciones, pero sería bueno saber realmente qué es lo que más usa la gente.

Con respecto a la reconstrucción de partes del árbol, los constructores se adaptan naturalmente a esta tarea de todos modos, simplemente deberíamos dejar que lo hagan. De todos modos, un controlador con estado se puede conectar fácilmente a renderizadores sin estado si eso es lo que desea (hola a todas las clases de transición).

Como podríamos hacer nosotros:

var anim = get AnimationController();
return Column(
  _someExpensiveBuildMethod(),
  FadeTransition(opacity: anim, child: ...)
)

Siempre podríamos hacer:

var foo = get ComplicatedThingController();
return Column(
  _someExpensiveBuildMethod(),
  ComplicatedThing(controller: foo, child: ...)
)

@esDotDev Estoy de acuerdo, las claves y la sintaxis del constructor son el principal inconveniente del enfoque lifecycleMixin. No sé si puede evitar eso, excepto mediante el uso de un enfoque de estilo de ganchos con sus restricciones asociadas, o un cambio de idioma para poder asociar declaraciones de variables con bits de estado con ciclos de vida. Es por eso que seguiré usando ganchos y dejaré que otros usen widgets con estado, a menos que surja una mejor solución. Sin embargo, creo que es una alternativa interesante para aquellos a los que no les gustan las restricciones de los ganchos, aunque tiene sus propias restricciones.

¿Estoy en lo cierto al suponer que los ejemplos en https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful ahora son representativos de los problemas que la gente quiere resolver?

Sinceramente, no estoy seguro.
Yo diría que sí_. Pero eso realmente depende de cómo interprete estos ejemplos.

En este hilo, tenemos un historial de no entendernos, por lo que no puedo garantizar que esto no vuelva a suceder.

En parte, es por eso que no me gusta usar ejemplos de código y sugerí extraer un conjunto de reglas en su lugar.
Los ejemplos son subjetivos y tienen múltiples soluciones, algunas de las cuales pueden no resolver el problema más amplio.

Me pregunto si @rrousselGit podría proporcionar alguna información aquí, o si tiene algún dato sobre qué ganchos se utilizan más actualmente. Supongo que es 80% Stream y Animaciones, pero sería bueno saber realmente qué es lo que más usa la gente.

Creo que es muy homogéneo.

Aunque, en todo caso, useStream y las animaciones son probablemente los menos utilizados:

  • useStream generalmente tiene un mejor equivalente dependiendo de su arquitectura. Podría usar context.watch , useBloc , useProvider , ...
  • pocas personas se toman el tiempo para hacer animaciones. Rara vez esa es la prioridad, y TweenAnimationBuilder otros widgets animados implícitamente cubren una gran parte de la necesidad.
    Quizás eso cambiaría si agregara mis ganchos useImplicitlyAnimatedInt en flutter_hooks.

@esDotDev Acaba de eliminar la necesidad de claves / identificadores en el enfoque lifecycleMixin. Todavía es un poco incómodo en la sintaxis del constructor. Pero posiblemente eso también podría ayudarse eventualmente. El único problema con el que me encuentro es con el sistema de tipos. Intenta emitir cosas de ciertas formas que no funcionan. Pero probablemente solo necesite un poco de dominio cuidadoso del lanzamiento o del sistema de tipos. En lo que respecta a la mezcla de ciclos de vida, creo que podría mejorarse lanzando algunas excepciones razonables cuando el ciclo de vida de ese widget no puede acceder a una parte del estado en particular. O una pelusa que dentro de un generador de ciclo de vida solo debe acceder al ciclo de vida del generador.

Gracias Remi, eso me sorprende, creo que la gente usaría Animación con mucha frecuencia para manejar la gran colección de widgets de transición en el núcleo, pero supongo que la mayoría de la gente solo usa los diversos Implicit, ya que son bastante agradables de leer y no tienen anidamiento. .

Aún así, a pesar de que AnimatorController está muy bien servido con un conjunto de widgets implícitos y explícitos, sigo pensando que es un gran ejemplo de algo que necesita mantener el estado y vincularse con los parámetros y el ciclo de vida de los widgets. Y sirve como un pequeño ejemplo perfecto del problema a resolver (el hecho es que está totalmente resuelto en Flutter con una docena de widgets a pesar de ello), que todos podemos discutir y mantenernos enfocados en la arquitectura y no en el contenido.

Por ejemplo, considere cómo, si var anim = AnimationController.use(context, duration: widget.duration ?? _duration); fuera un ciudadano de primera clase, prácticamente ninguna de estas animaciones implícitas o explícitas realmente necesita existir. Los hace redundantes, ya que todos se crearon para gestionar el problema central: componer fácilmente una cosa con estado (AnimationController) dentro del contexto de un widget. TAB se vuelve casi inútil, ya que puedes hacer lo mismo con AnimatedBuilder + AnimatorController.use() .

Realmente ilustra la necesidad del caso de uso general si observa la enorme masa de widgets que han surgido alrededor de las animaciones. Precisamente porque es tan engorroso / propenso a errores reutilizar la lógica de configuración / desmontaje del núcleo, tenemos más de 15 widgets que manejan cosas muy específicas, pero la mayoría de cada uno de ellos está repitiendo la misma plantilla de animación con solo un puñado de líneas de código únicas en muchos casos.

Sirve para mostrar que sí, también podríamos hacer esto para reutilizar nuestra propia lógica con estado: hacer un widget para cada permutación de uso. ¡Pero qué molestia y dolor de cabeza de mantenimiento! Es mucho más agradable tener una forma fácil de componer pequeños objetos con estado, con ganchos de ciclo de vida, y si queremos hacer widgets dedicados para renderizar, o un constructor reutilizable, podemos fácilmente colocarlos encima.

Por lo que vale, utilizo algo como useAnimation en gran medida en mi aplicación en lugar de los widgets de animación normales. Esto se debe a que estoy usando SpringAnimation que no es compatible con widgets como AnimatedContainer, por ejemplo; todos asumen una animación basada en el tiempo, con curve y duration lugar de una animación basada en simulación, que aceptaría un argumento Simulation .

Hice una abstracción sobre useAnimation pero con resortes, así que lo llamé useSpringAnimation . El widget de envoltura con el que usé este gancho es similar a AnimatedContainer pero fue mucho más fácil de hacer porque pude reutilizar todo el código de animación como dices @esDotDev , ya que gran parte de la lógica es la misma. Incluso podría hacer mi propia versión de todos los widgets animados usando de nuevo useSpringAnimation pero no necesariamente lo necesitaba para mi proyecto. Esto muestra una vez más el poder de la reutilización de la lógica del ciclo de vida que proporcionan los ganchos.

Por ejemplo, considere cómo, si var anim = AnimationController.use (contexto, duración: widget.duration ?? _duration); era un ciudadano de primera clase, prácticamente ninguna de estas animaciones implícitas o explícitas realmente necesita existir. Los hace redundantes, ya que todos se crearon para gestionar el problema central: componer fácilmente una cosa con estado (AnimationController) dentro del contexto de un widget. TAB se vuelve casi inútil, ya que puede hacer lo mismo con AnimatedBuilder + AnimatorController.use ().

Al leer mis comentarios anteriores, esto parece ser básicamente exactamente lo que hice con mi gancho de animación de primavera. Encapsulé la lógica y luego simplemente usé AnimatedBuilder. Para hacerlos implícitos, de modo que cuando cambie el accesorio como se hace en AnimatedContainer, se animaría, acabo de agregar el método didUpdateWidget (llamado didUpdateHook en flutter_hooks ) a ejecute la animación desde el valor anterior al nuevo valor.

¿Estoy en lo cierto al suponer que los ejemplos en https://github.com/TimWhiting/local_widget_state_approaches/tree/master/lib/stateful ahora son representativos de los problemas que la gente quiere resolver?

Sinceramente, no estoy seguro.
Yo diría que sí_. Pero eso realmente depende de cómo interprete estos ejemplos.

En este hilo, tenemos un historial de no entendernos, por lo que no puedo garantizar que esto no vuelva a suceder.

En parte, es por eso que no me gusta usar ejemplos de código y sugerí extraer un conjunto de reglas en su lugar.
Los ejemplos son subjetivos y tienen múltiples soluciones, algunas de las cuales pueden no resolver el problema más amplio.

También diría que deberíamos incluir todos los ejemplos de código en este tema que se discutieron, creo que hay una lista arriba en alguna parte que hizo @rrousselGit . Podría hacer un PR agregándolos al repositorio local_state, pero no todos son ejemplos de código completos, por lo que es posible que no todos se compilen y ejecuten. Pero muestran los problemas potenciales al menos.

Podría hacer un PR agregándolos al repositorio local_state

Sería muy útil.

Me gustaría señalar que este hilo no ha definido la reutilización o cómo se ve la reutilización. Creo que deberíamos ser dolorosamente específicos al definir eso, para que la conversación no pierda el enfoque.

Solo hemos mostrado lo que la reutilización no es en lo que respecta a Flutter.

Ha habido bastantes ejemplos de uso, y los ganchos proporcionan claramente un ejemplo total de reutilización del estado de los widgets. No estoy seguro de dónde proviene la confusión, ya que parece sencillo a primera vista.

La reutilización se puede definir simplemente como: _Cualquier cosa que pueda hacer un widget de construcción_

La solicitud es para algún objeto con estado que pueda existir dentro de cualquier widget, que:

  • Encapsula su propio estado
  • Puede configurarse / desmontarse a sí mismo de acuerdo con initState / dispose llamadas
  • Puede reaccionar cuando las dependencias cambian en el widget.

Y lo hace de una manera concisa, fácil de preparar y sin repetición, como:
AnimationController anim = AnimationController.stateful(duration: widget.duration);
Si esto funciona en widgets sin estado y con estado. Si se reconstruye cuando cambia widget.something, si puede ejecutar su propio init () y dispose (), entonces básicamente tienes un ganador y estoy seguro de que todos lo apreciarán.

Lo principal con lo que estoy luchando es cómo hacer esto de una manera eficiente. Por ejemplo, ValueListenableBuilder toma un argumento secundario que se puede usar para mejorar el rendimiento de manera mensurable. No veo una forma de hacer eso con el enfoque de propiedad.

Estoy bastante seguro de que esto no es un problema. Haríamos esto de la misma manera que los widgets XTransition funcionan ahora. Si tengo algún estado complejo y quisiera que tuviera un hijo caro, simplemente haría un pequeño widget de envoltura para él. Como podríamos hacer:
FadeTransition(opacity: anim, child: someChild)

Podemos hacer eso fácilmente con cualquier cosa que queramos renderizar, pasando la 'cosa' a un Widget para volver a renderizarla.
MyThingRenderer(value: thing, child: someChild)

  • Esto no _requiere_ anidamiento como lo hace el constructor, pero opcionalmente lo admite (.child podría ser un fxn de compilación)
  • Conserva la capacidad de usarse directamente sin un widget de envoltura
  • Siempre podemos hacer un constructor y usar esta sintaxis dentro del constructor para mantenerlo más limpio. También abre la puerta a múltiples tipos de constructores, construidos alrededor del mismo objeto central, que no implica copiar código pegado por todas partes.

De acuerdo con @esDotDev. Como mencioné anteriormente, un título alternativo para esto sería "Azúcar de sintaxis para constructores".

Lo principal con lo que estoy luchando es cómo hacer esto de una manera eficiente. Por ejemplo, ValueListenableBuilder toma un argumento secundario que se puede usar para mejorar el rendimiento de manera mensurable. No veo una forma de hacer eso con el enfoque de propiedad.

Estoy bastante seguro de que esto no es un problema. Haríamos esto de la misma manera que los widgets XTransition funcionan ahora. Si tengo algún estado complejo y quisiera que tuviera un hijo caro, simplemente haría un pequeño widget de envoltura para él. Como podríamos hacer:

No hay necesidad de eso.
Uno de los beneficios de esta característica es que podemos tener una lógica de estado que es "almacenar en caché la instancia del widget si sus parámetros no cambiaron".

Con ganchos, eso sería useMemo en React:

<insert whatever>
final myWidget = useMemo(() => MyWidget(pameter: value), [value]);

Con este código, myWidget reconstruirá _sólo_ cuando cambie value . Incluso si el widget que llama a useMemo reconstruye por otras razones.

Es similar a un constructor const para widgets, pero permite parámetros dinámicos.

Hay un ejemplo que hace eso en el repositorio de Tim.

La solicitud es para algún objeto con estado que pueda existir dentro de cualquier widget, que:

  • Encapsula su propio estado
  • Puede configurarse / desmontarse a sí mismo de acuerdo con initState / dispose llamadas
  • Puede reaccionar cuando las dependencias cambian en el widget.

Supongo que me cuesta ver por qué, según esos parámetros, StatefulWidget no hace el trabajo mejor de lo que lo hace. Es por eso que hice la pregunta sobre lo que realmente buscamos aquí en una solución. Como alguien que usa flutter_hooks , encuentro que es más divertido trabajar con ellos que con StatefulWidget , pero eso es solo para evitar la verbosidad, no porque piense en términos de ganchos. De hecho, encuentro difícil razonar sobre las actualizaciones de la interfaz de usuario con los ganchos en comparación con Widget s.

  • Puede reaccionar cuando las dependencias cambian en el widget.

¿Te refieres a una dependencia que se creó / adquirió dentro del widget? ¿O una dependencia muy por debajo del widget en el árbol?

No niego que hay un problema que causa verbosidad / confusión en Flutter, solo dudo en confiar en que todos tengan el mismo modelo mental de lo que es la "reutilización". Estoy muy agradecido por la explicación; y cuando las personas tienen diferentes modelos, crean diferentes soluciones.

Porque usar un SW para hacer esto está bien para un caso de uso específico, pero no es bueno para abstraer la lógica reutilizable del caso de uso en muchos SW. Tome la configuración / desmontaje de Animación como ejemplo. Esto no es un SW en sí mismo, es algo que queremos usar en ellos. Sin soporte de primera clase para compartir el estado encapsulado, termina teniendo que hacer un constructor, es decir, TweenAnimationBuilder, o hacer una tonelada de Widgets específicos, es decir, AnimatedContainer, etc. Realmente mucho más elegante si puede agrupar esa lógica y reutilizarla. de la forma que desee dentro de un árbol.

En términos de dependencia del widget, solo me refiero a que si widget.foo cambia, la cosa con estado tiene la oportunidad de hacer cualquier actualización que necesite hacer. En el caso de stateful AnimationController, verificaría si la duración cambió y, si lo hiciera, actualizaría su instancia interna de AnimatorController. Esto evita que todos los implementadores de la animación tengan que manejar el cambio de propiedad.

<insert whatever>
final myWidget = useMemo(() => MyWidget(pameter: value), [value]);

Con este código, myWidget reconstruirá _sólo_ cuando cambie value . Incluso si el widget que llama a useMemo reconstruye por otras razones.

Ah, ya veo, Memoized devuelve un widget en sí, y luego pasas [valor] como disparador de reconstrucción, ¡genial!

La clave de AnimatedOpacity no es la reconstrucción del padre ni del hijo. De hecho, cuando activa una animación con AnimatedOpacity, literalmente, nada se reconstruye después del primer fotograma en el que activa la animación. Saltamos la fase de construcción por completo y lo hacemos todo en el objeto de renderizado (y en el árbol de renderizado, solo se vuelve a pintar, no se retransmite, y de hecho se usa una Capa, por lo que incluso la pintura es bastante mínima). Hace una diferencia significativa en el rendimiento y el uso de la batería. Cualquiera que sea la solución que se nos ocurra aquí, debe ser capaz de mantener ese tipo de rendimiento si queremos integrarlo en el marco principal.

Desafortunadamente, no he tenido tiempo de recopilar los ejemplos de este número en el repositorio estatal local, mi culpa. Es posible que no pueda hacerlo en el corto plazo, así que si alguien más quiere retomarlo, estaría bien con eso.

Con respecto al rendimiento de tener ganchos definidos dentro del método de compilación / renderización (que creo que alguien mencionó anteriormente en este número), estaba leyendo los documentos de React y vi estas preguntas frecuentes, que podrían ser útiles. Básicamente pregunta si los ganchos son lentos debido a la creación de funciones en cada renderizado, y dicen que no debido a algunas razones, una de las cuales es poder memorizar funciones usando un gancho como useMemo o useCallback .

https://reactjs.org/docs/hooks-faq.html#are -hooks-slow-because-of-creation-functions-in-render

Básicamente pregunta si los ganchos son lentos debido a la creación de funciones en cada renderizado, y dicen que no debido a algunas razones, una de las cuales es poder memorizar funciones usando un gancho como useMemo o useCallback .

La preocupación no es el costo de crear cierres, estos son de hecho relativamente baratos. Es la diferencia entre ejecutar cualquier código y no ejecutar ningún código lo que es clave para el rendimiento que exhibe Flutter en los casos óptimos de hoy. Hemos dedicado mucho esfuerzo a la creación de algoritmos que literalmente evitan ejecutar ciertas rutas de código (por ejemplo, la fase de compilación se omite por completo para AnimatedOpacity, o la forma en que evitamos caminar por el árbol para realizar actualizaciones y, en cambio, solo apuntar a los nodos afectados).

Estoy de acuerdo. No estoy muy versado en los componentes internos de Flutter ni en los componentes internos de los ganchos, pero tienes razón en que los ganchos necesitarán (si aún no lo han hecho) averiguar cuándo deben ejecutarse y cuándo no, y el rendimiento no debe retroceder.

Es la diferencia entre ejecutar cualquier código y no ejecutar ningún código lo que es clave para el rendimiento que exhibe Flutter en los casos óptimos de hoy.

Como se mencionó anteriormente algunas veces, los ganchos mejoran eso.
El ejemplo animado del repositorio de Tim es prueba de ello. La variante hooks se reconstruye con menos frecuencia que la variante StatefulWidget gracias a useMemo

Dado que se está discutiendo sobre soluciones para este problema en algún lugar de este hilo, también lo etiquetaré como propuesta.

Realmente me gustaría ver ganchos incorporados en flutter como se hizo con react. Miro el estado en aleteo de la misma manera que solía hacerlo cuando usé reaccionar por primera vez. Desde que usé ganchos, personalmente nunca volvería.

Es mucho más legible en mi opinión. Actualmente, debe declarar dos clases con un widget con estado frente a los ganchos en los que simplemente coloca usestate.

También aportaría algo de familiaridad a flutter que los desarrolladores de reacciones a menudo no tienen cuando miran el código de flutter. Obviamente, comparar flutter con react es un camino peligroso a seguir, pero realmente creo que mi experiencia de desarrollador con hooks es mejor que mi experiencia sin ellos.

Por cierto, no odio Flutter, en realidad es mi marco favorito, pero creo que esta es una muy buena oportunidad para aumentar la legibilidad y la experiencia de desarrollo.

Creo que definitivamente hay una oportunidad para mejorar las convenciones de nomenclatura y hacerlas más similares.

Cosas como UseMemoized y UseEffect suenan bastante extrañas, y parece que queremos alguna forma de no tener que ejecutar el código init () en la compilación fxn.

Actualmente, la inicialización con ganchos es así (¿creo?):

Widget build(){
   useEffect(
      (){
          // Do init stuff
         return (){  //Do dispose stuff };
      }, [ ] ) //<-- pass an empty list of rebuild triggers, so this can only fire once. Passing null here would let it fire every time.
   );
}

Aprecio la brevedad de este código, pero ciertamente es mucho menos que ideal desde el punto de vista de la legibilidad y el "código autodocumentado". Hay mucha magia implícita aquí. Idealmente, tenemos algo que es explícito sobre sus ganchos init / dispose, y no se fuerza a sí mismo en la compilación cuando se usa con un widget sin estado.

Cosas como useMemoized y useEffect tal vez se podrían nombrar mejor de manera más explícita hook ComputedValue() y hook SideEffect()

Widget build(BuildContext context){
   List<int> volumes = hook ComputedValue(
        execute: ()=>_getVolumeFromAudioSamples(widget.bytes), 
        dependencies: [ widget.bytes ]);

   hook SideEffect(
       execute: ()=>_recordSongChangedAnalytics()
       dependencies: [ widget.songUrl ]);
   )

   return SongVisualizer(volumes: volumes);
}

Sin embargo, me gusta, no estoy seguro de cómo me siento sobre el uso de la palabra clave hook , y no creo que resuelva el problema de los conceptos extraños. La introducción de nuevas palabras clave no me parece el mejor enfoque, ¿ withSideEffect o withComputedValue ? No soy un diseñador de idiomas, por lo que mis palabras no valen nada.

Siento que la funcionalidad similar a un gancho en Flutter será de gran ayuda para suavizar la curva de aprendizaje para los desarrolladores de React, que es realmente el público objetivo cuando las empresas toman la decisión entre ReactNative y Flutter.

Haciendo eco de @lemusthelroy , Flutter es, con mucho, mi marco favorito y estoy más que emocionado de ver las direcciones que toma. Pero creo que los conceptos de programación funcional podrían ser de gran ayuda para hacer crecer el marco en una dirección aún relativamente inexplorada. Creo que algunas personas están descartando la idea con el objetivo de distanciarse de React, lo cual es desafortunado, pero comprensible.

Creo que esa moneda tiene dos caras. Una palabra clave nueva es un evento importante, por lo que la propagación del conocimiento sería muy rápida, pero el otro lado es ciertamente que ahora es algo nuevo para todos. ¡Si es posible sin eso también es genial! No estoy seguro de que lo sea ... al menos no con tanta elegancia.

Opinión: La inclinación de la comunidad a nombrar los ganchos como la solución de facto a este problema tiene su origen en un sesgo por las funciones. Las funciones son más sencillas de componer que los objetos, especialmente en un lenguaje de tipado estático. Creo que el modelo mental de Widgets para muchos desarrolladores es efectivamente solo el método build .

Creo que si enmarca el problema en términos básicos, es más probable que diseñe una solución que funcione bien en el resto de la biblioteca.

En cuanto a la palabra clave hook en términos de los conceptos básicos; uno podría verlo como declarar y definir una función a partir de algún tipo de plantilla (una macro), y el prefijo hook realidad solo indica que la función incorporada tiene un estado interno (estática de estilo c. )

Me pregunto si no hay algún tipo de arte previo en Swift FunctionBuilders.

Mientras soñamos, aclararé mi conjetura sobre cuál sería el código necesario:

Hook SideEffect(void Function() execute, List<Object> dependencies) {
  // Whatever happens each build.
}

Widget build(BuildContext context){
   List<int> volumes = hook ComputedValue(
        execute: ()=>_getVolumeFromAudioSamples(widget.bytes), 
        dependencies: [ widget.bytes ]);

   SideEffect(
       execute: ()=>_recordSongChangedAnalytics()
       dependencies: [ widget.songUrl ]);
   )

   return SongVisualizer(volumes: volumes);
}

Donde Hook es un truco a nivel de sistema de tipos que ayuda a analizar estáticamente que el gancho resultante se llamó de acuerdo con lo que los desarrolladores familiares de ganchos conocen como leyes de gancho. Como ese tipo de cosas, el tipo Hook se puede documentar como algo que se parece mucho a una función, pero tiene un estado mutable interno estático.

Me estremezco un poco mientras escribo esto porque es una rareza desde la perspectiva del lenguaje. Por otra parte, Dart es el lenguaje nacido para escribir interfaces de usuario. Si este tipo de rareza existiera en algún lugar, tal vez este sea el lugar. Pero no esta rareza en particular.

Opinión: La inclinación de la comunidad a nombrar los ganchos como la solución de facto a este problema tiene su origen en un sesgo por las funciones. Las funciones son más sencillas de componer que los objetos, especialmente en un lenguaje de tipado estático. Creo que el modelo mental de Widgets para muchos desarrolladores es efectivamente solo el método de construcción.

No estoy seguro de lo que quieres decir con eso. El enfoque de gancho que también utilizo con mi get_it_mixin solo hace que el árbol de widgets sea más fácil de leer que usar un Builder.

Interesante artículo sobre React hooks

@ nt4f04uNd Todos sus puntos se abordaron anteriormente, incluido el rendimiento, por qué debe ser una característica principal, widgets funcionales frente a estilo de clase, y por qué otras cosas además de los ganchos no parecen funcionar. Le sugiero que lea toda la conversación para comprender los distintos puntos.

Le sugiero que lea toda la conversación para comprender los distintos puntos.

Esto es justo decirlo considerando que no leyeron todo el hilo, pero no estoy seguro de que aclare las cosas al leer el resto del hilo. Hay personas cuya prioridad es mantener los widgets como están, y otro grupo que quiere hacer algo completamente diferente o hacer que los widgets sean más modulares.

Si bien eso puede ser cierto, este problema muestra que hay problemas que no se pueden resolver con los widgets como son actualmente, por lo que si queremos resolver los problemas, no tenemos más remedio que hacer algo nuevo. Este es el mismo concepto que tener Future sy luego introducir la sintaxis async/await , este último hace posible cosas que simplemente no serían posibles sin una nueva sintaxis.

Sin embargo, la gente _está_ sugiriendo que lo hagamos parte del marco. React no puede agregar una nueva sintaxis a Javascript porque no es el único marco disponible (bueno, puede hacerlo a través de transformaciones de Babel), pero Dart está diseñado específicamente para funcionar con Flutter (Dart 2 al menos, no la versión original) por lo que tenemos un mucha más capacidad para hacer que los ganchos funcionen junto con el lenguaje subyacente. React, por ejemplo, necesita Babel para JSX, y tiene que usar un linter para errores useEffect , mientras que podríamos convertirlo en un error de tiempo de compilación. Tener un paquete hace que la adopción sea mucho más difícil, ya que se puede imaginar la tracción que los ganchos de React (no) habrían obtenido si hubiera sido un paquete de terceros.

No habría ningún problema si pudiera haber un tercer tipo de widget, es decir, HookWidget, además de los widgets Stateless y Stateful actuales. Deje que la comunidad decida cuál usar. Ya existe un paquete de Remi pero inevitablemente tiene limitaciones. Lo probé y redujo significativamente el texto estándar, pero tuve que dejarlo desafortunadamente debido a limitaciones. Tengo que crear widgets con estado solo para usar el método init. Podría haber grandes beneficios adicionales si es parte del marco central con el soporte del idioma. Además, un HookWidget puede permitir a la comunidad crear aplicaciones más óptimas y de mayor rendimiento.

Tengo que crear widgets con estado solo para usar el método init.

En realidad, no tiene que hacer esto, useEffect () es capaz de hacer initCall dentro de la compilación. Los documentos no hacen ningún esfuerzo para explicar esto aunque, básicamente, asumen que eres un desarrollador de React que ya sabe cómo funcionan los hooks.

Estaba usando de esa manera, pero tuve algunos otros problemas con las limitaciones del paquete y no recuerdo exactamente cuáles eran.

¿Fue útil esta página
0 / 5 - 0 calificaciones