Experimento de pensamiento: aleteo en marcha

Más recientemente, descubrí Flutter , un nuevo marco de trabajo de Google para desarrollar aplicaciones móviles multiplataforma, e incluso tuve la oportunidad de mostrar los conceptos básicos de Flutter a una persona que nunca antes había programado. Flutter en sí fue escrito en Dart, un lenguaje que nació en el navegador Chrome y escapó al mundo de las consolas, y eso me hizo pensar "hmm, pero Flutter bien podría escribirse en Go!".


Por que no Tanto Go como Dart fueron creados por Google, ambos lenguajes compilados mecanografiados: cambie algunos de los eventos de manera un poco diferente, Go sería un excelente candidato para implementar un proyecto a gran escala como Flutter. Alguien dirá: no hay clases, genéricos y excepciones en Go, por lo que no encaja.


Así que supongamos que Flutter ya está escrito en Go. ¿Cómo se verá el código y, en general, funcionará?



¿Qué le pasa a Dart?


He seguido este lenguaje desde su inicio como una alternativa a JavaScript en los navegadores. Dart se incorporó al navegador Chrome durante algún tiempo y se esperaba que suplantara a JS. Fue increíblemente triste leer en marzo de 2015 que el soporte de Dart fue eliminado de Chrome .


Dart en sí es genial! Bueno, básicamente, después de JavaScript, cualquier lenguaje es genial, pero después, digamos, Go, Dart no es tan hermoso. pero bastante bien Tiene todas las características concebibles e inconcebibles: clases, genéricos, excepciones, futuros, espera asíncrona, bucle de eventos, JIT / AOT, recolector de basura, sobrecarga de funciones: nombra cualquier característica conocida de la teoría de los lenguajes de programación y en Dart tendrá una alta proporción probabilidades Dart tiene una sintaxis especial para casi cualquier chip: una sintaxis especial para getters / setters, una sintaxis especial para constructores abreviados, una sintaxis especial para una sintaxis especial y mucho más.


Esto hace que Dart sea familiar desde el primer vistazo para las personas que ya han programado en cualquier lenguaje de programación antes, y eso es genial. Pero al tratar de explicar toda esta abundancia de características especiales en un simple ejemplo de "Hola, mundo", descubrí que, por el contrario, complica el desarrollo.


  • todas las características "especiales" del lenguaje eran confusas : "un método especial llamado constructor", "sintaxis especial para inicialización automática", "sintaxis especial para parámetros con nombre", etc.
  • todo lo "oculto" era confuso : "¿de qué importancia es esta función? Está oculto, mirando el código que no puede encontrar", "¿por qué hay un constructor en esta clase, pero no en esta clase? Está ahí, pero está oculto", etc.
  • todo "ambiguo" se confunde : "¿aquí para crear los parámetros de la función con o sin nombres?", "¿debería ser const o final?", "aquí use la sintaxis de la función normal o 'acortado con flecha'", etc.

En principio, esta trinidad - "especial", "oculta" y "ambigua" - no está mal captura la esencia de lo que la gente llama "magia" en los lenguajes de programación. Estas son características creadas para simplificar la escritura de código, pero de hecho complican su lectura y comprensión.


Y esta es exactamente el área donde Go toma una posición fundamentalmente diferente de otros idiomas, y mantiene la defensa ferozmente. Go es un lenguaje casi sin magia: la cantidad de "oculto", "especial" y "ambiguo" se minimiza. Pero Go tiene sus inconvenientes.


¿Qué le pasa a Go?


Dado que estamos hablando de Flutter, que es un marco de UI, consideremos Go como una herramienta para describir y trabajar con UI. En general, los marcos de UI son un gran desafío y casi siempre requieren soluciones especializadas. Uno de los enfoques más frecuentes en la interfaz de usuario es la creación de DSL , lenguajes específicos de dominio, implementados en forma de bibliotecas o marcos diseñados específicamente para las necesidades de la interfaz de usuario. Y lo más frecuente es que puedas escuchar la opinión de que Go es objetivamente un mal lenguaje para DSL.


En esencia, DSL significa crear un nuevo lenguaje, términos y verbos, en el que el desarrollador pueda operar. El código en él debe describir claramente las características principales de la interfaz gráfica y sus componentes, ser lo suficientemente flexible como para dar rienda suelta a la imaginación del diseñador y, al mismo tiempo, ser lo suficientemente rígido como para restringirlo de acuerdo con ciertas reglas. Por ejemplo, debería poder colocar los botones en algún contenedor y colocar el icono en el lugar correcto en este botón, pero el compilador debería devolver un error si intenta insertar el botón en, digamos, texto.


Además, los lenguajes para describir la interfaz de usuario a menudo son declarativos, lo que brinda la oportunidad de describir la interfaz en forma de "lo que me gustaría ver" y dejar que el propio marco entienda desde qué código y cómo ejecutarlo.


Algunos lenguajes se desarrollaron originalmente con tales tareas a la vista, pero no Go. ¡Parece que escribir Flutter on Go será otra tarea!


Oda Flutter


Si no está familiarizado con Flutter, le recomiendo pasar el próximo fin de semana viendo videos educativos o leyendo tutoriales, de los cuales hay muchos. Porque Flutter, sin lugar a dudas, invierte las reglas del juego en el desarrollo de aplicaciones móviles. Y, muy probablemente, no solo para dispositivos móviles, ya hay renderizadores (en términos de Flutter, embebidores) para lanzar aplicaciones de Flutter como aplicaciones de escritorio nativas y como aplicaciones web .


Es fácil de aprender, es lógico, viene con una gran biblioteca de hermosos widgets sobre Diseño de materiales (y no solo), tiene una gran y gran comunidad y excelente ajuste (si te gusta la facilidad de trabajar con go build/run/test en Go, luego en Flutter Obtendrás una experiencia similar).


Hace un año, necesitaba escribir una pequeña aplicación móvil (para iOS y Android, por supuesto), y me di cuenta de que la complejidad de desarrollar una aplicación de alta calidad para ambas plataformas es demasiado grande (la aplicación no era la tarea principal). Tuve que externalizar y pagar dinero por ello. De hecho, escribir una aplicación sencilla pero de alta calidad y trabajar en todos los dispositivos era una tarea imposible incluso para una persona con casi 20 años de experiencia en programación. Y siempre ha sido una tontería para mí.


Con Flutter, reescribí esta aplicación a las 3 pm, mientras aprendía el framework desde cero. Si alguien me dijera que esto podría ser un poco antes, no lo creería.


La última vez que vi un aumento similar en la productividad con el descubrimiento de nueva tecnología fue hace 5 años cuando descubrí Go. Ese momento cambió mi vida.


Así que recomiendo comenzar a aprender Flutter y este tutorial es muy bueno .


"Hola, mundo" en Flutter


Cuando crea una nueva aplicación a través de flutter create , obtendrá un programa con título, texto, contador y un botón que incrementa el contador.



Creo que este es un gran ejemplo. para escribirlo en nuestro imaginario Flutter on Go. Tiene casi todos los conceptos básicos del marco en el que puede probar la idea. Veamos el código (este es un archivo):


 import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { MyHomePage({Key key, this.title}) : super(key: key); final String title; @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override 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 button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); } } 

Analicemos el código en partes, analicemos qué y cómo encaja en Go, y echemos un vistazo a las diversas opciones que tenemos.


Traducimos el código en Go


El comienzo será simple y directo: importe la dependencia y ejecute la función main() . Nada complicado o interesante aquí, el cambio es casi sintáctico:


 package hello import "github.com/flutter/flutter" func main() { app := NewApp() flutter.Run(app) } 

La única diferencia es que, en lugar de iniciar MyApp() , una función que es un constructor, que es una función especial que está oculta dentro de una clase llamada MyApp, simplemente llamamos a la función explícita y no oculta NewApp() . Ella hace lo mismo, pero es mucho más claro explicar y comprender qué es, cómo comienza y cómo funciona.


Clases de widgets


En Flutter, todo consiste en widgets. En la versión Dart de Flutter, cada widget se implementa como una clase que hereda clases especiales para widgets de Flutter.


No hay clases en Go y, por lo tanto, no hay jerarquía de clases, porque el mundo no está orientado a objetos, y aún menos es jerárquico. Para programadores familiarizados solo con el modelo orientado a clases OOP, esto puede ser una revelación, pero realmente no lo es. El mundo es un gráfico gigante entretejido de conceptos, procesos e interacciones. No está perfectamente estructurado, pero tampoco es caótico, y tratar de incluirlo en la jerarquía de clases es la forma más confiable de hacer que la base de código sea ilegible y torpe, exactamente lo que la mayoría de las bases de código son por ahora.



Realmente aprecio Go porque sus creadores se tomaron la molestia de repensar este concepto omnipresente de clases e implementaron en Go un concepto OOP mucho más simple y poderoso, que, por casualidad, resultó estar más cerca de lo que el creador de OOP, Alan Kay, tenía en mente .


En Go, representamos cualquier abstracción en forma de un tipo específico - estructura:


 type MyApp struct { // ... } 

En la versión Dart de Flutter, MyApp debe heredar StatelessWidget y anular el método de build . Esto es necesario para resolver dos problemas:


  1. dar a nuestro widget ( MyApp ) algunas propiedades / métodos especiales
  2. habilite Flutter para llamar a nuestro código en el proceso de compilación / renderización

No conozco las partes internas de Flutter, así que digamos que el elemento número 1 no está en cuestión, y solo tenemos que hacerlo. Go tiene una solución tan única y obvia para esto: tipos de incrustación :


 type MyApp struct { flutter.Core // ... } 

Este código agregará todas flutter.Core propiedades y métodos de flutter.Core a nuestro tipo MyApp . Lo llamé Core lugar de Widget , porque, en primer lugar, la incrustación de tipos aún no convierte a MyApp widget, y en segundo lugar, este nombre se usa muy bien en el marco GopherJS Vecty (algo así como React, solo para Go). Más adelante tocaré el tema de la similitud entre Vecty y Flutter.


El segundo punto, la implementación del método build() , que podrá usar el motor Flutter, también se resuelve de manera simple e inequívoca en Go. Solo necesitamos agregar un método con una firma específica que satisfaga una determinada interfaz definida en algún lugar de nuestra biblioteca ficticia Flutter en Go:


flutter.go:


 type Widget interface { Build(ctx BuildContext) Widget } 

Y ahora nuestro main.go:


 type MyApp struct { flutter.Core // ... } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.MaterialApp() } 

Podemos notar algunas diferencias aquí:


  • el código es algo más detallado: BuildContext , Widget y MaterialApp apuntan a flutter importaciones frente a ellos.
  • el código es un poco menos infundado: no hay palabras como extends Widget o @override
  • El método Build() comienza con una letra mayúscula, porque significa la "publicidad" del método en Go. En Dart, la publicidad está determinada por si el nombre comienza con un guión bajo (_) o no.

Entonces, para hacer un widget en nuestro Flutter on Go, necesitamos incrustar el flutter.Core Tipo de flutter.Core e implementar la interfaz flutter.Widget . Lo descubrimos, cavamos más.


Condición


Esta fue una de las cosas que realmente me confundió en Flutter. Hay dos clases diferentes: StatelessWidget y StatefulWidget . En cuanto a mí, un "widget sin estado" es el mismo widget, solo que sin, hmm, data, state. ¿Por qué crear una nueva clase? Pero está bien, puedo vivir con eso.


Pero además, más, no puedes simplemente heredar otra clase ( StatefulWidget ), sino que debes escribir tal magia (el IDE lo hará por ti, pero no el punto):


 class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold() } } 

Hmm, veamos qué pasa aquí.


Básicamente, la tarea es esta: agregar un estado al widget (el contador, en nuestro caso) y dejar que el motor Flutter sepa cuándo cambiamos el estado para volver a dibujar el widget. Esta es la verdadera complejidad del problema (complejidad esencial en términos de Brooks).


Todo lo demás es complejidad accidental. Flutter on Dart presenta una nueva clase de State que usa genéricos y toma un widget como parámetro de tipo. A continuación, se _MyHomePageState clase _MyHomePageState , que hereda el State MyApp ... bueno, todavía puede digerirlo de alguna manera. Pero, ¿por qué el método build() definido por la clase State y no la clase que es el widget? Brrr ....


La respuesta a esta pregunta se encuentra en las preguntas frecuentes de Flutter y la respuesta breve se considera con suficiente detalle aquí , para evitar una cierta clase de errores al heredar StatefulWidget . En otras palabras, esta es una solución para resolver el problema del diseño orientado a clases OOP. Chic


¿Cómo haríamos esto en Go?


En primer lugar, personalmente personalmente preferiría no crear una entidad separada para el "estado" - State . Después de todo, ya tenemos un estado en cada tipo en particular: estos son solo campos de la estructura. El lenguaje ya nos ha dado esta esencia, por así decirlo. Crear otra entidad similar solo confundirá al programador.


El desafío, por supuesto, es darle a Flutter la capacidad de responder a los cambios de estado (después de todo, esta es la esencia de la programación reactiva). Y si podemos "pedirle" al desarrollador que use una función especial ( setState() ), de la misma manera podemos pedirle que use una función especial para decirle al motor cuándo volver a dibujar y cuándo no. Al final, no todos los cambios de estado requieren un nuevo dibujo, y aquí tendremos aún más control:


 type MyHomePage struct { flutter.Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.Scaffold() } // incrementCounter increments widgets's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) // or m.Rerender() // or m.NeedsUpdate() } 

Puede jugar con diferentes opciones de nomenclatura: me gusta NeedsUpdate() por su carácter directo y el hecho de que esta es una propiedad de widget (obtenida de flutter.Core ), pero el flutter.Rerender() global flutter.Rerender() también se ve bien. Es cierto, da una falsa sensación de que el widget se volverá a dibujar inmediatamente, pero no es así: se volverá a dibujar en la próxima actualización de fotogramas, y la frecuencia de llamada al método puede ser mucho más alta que la frecuencia de representación, pero nuestro motor Flutter ya debería ser capaz de lidiar con esto.


Pero la idea es que resolvimos el problema necesario sin agregar:


  • nuevo tipo
  • genéricos
  • reglas especiales para leer / escribir estado
  • nuevos métodos especiales anulados

Además, la API es mucho más clara y comprensible: solo aumente el contador (como lo haría en cualquier otro programa) y pídale a Flutter que vuelva a dibujar el widget. Esto es algo que no es muy obvio si solo llamamos setState , que no es solo una función especial para establecer el estado, es una función que devuelve una función (¿wtf?) En la que ya estamos haciendo algo con el estado. Una vez más, la magia oculta en lenguajes y marcos hace que sea muy difícil de entender y leer el código.


En nuestro caso, resolvimos el mismo problema, el código es más simple y dos veces más corto.


Estado de widgets en otros widgets


Como continuación lógica del tema, echemos un vistazo a cómo se utiliza el "widget de estado" en otro widget en Flutter:


 @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: MyHomePage(title: 'Flutter Demo Home Page'), ); } 

MyHomePage aquí es un "widget de estado" (tiene un contador), y lo creamos llamando al constructor MyHomePage() durante la compilación ... Espera, ¿qué?


Se llama a build() para volver a dibujar el widget, posiblemente muchas veces por segundo. ¿Por qué deberíamos crear un widget, especialmente con un estado, cada vez durante el renderizado? No tiene sentido


Resulta que Flutter usa esta separación entre Widget y State para ocultar esta inicialización / gestión de estado del programador (¡más cosas ocultas, más!). Crea un nuevo widget cada vez, pero el estado, si ya se ha creado, se encuentra automáticamente y se adjunta al widget. Esta magia ocurre de manera invisible y no tengo idea de cómo funciona: debes leer el código.


Considero que es un verdadero mal en la programación esconderse del programador tanto como sea posible, justificándolo con ergonomía. Estoy seguro de que el programador estadístico promedio no leerá el código Flutter para comprender cómo funciona esta magia, y es poco probable que comprenda cómo y qué está interconectado.


Para la versión Go, definitivamente no querría una brujería tan oculta, y preferiría una inicialización explícita y visible, incluso si eso significa un código un poco más infundado. El enfoque de Flutter para Dart también se puede implementar, pero me encanta Go para minimizar la magia, y me gustaría ver la misma filosofía en los marcos. Por lo tanto, escribiría mi código para widgets con estado en el árbol de widgets como este:


 // MyApp is our top application widget. type MyApp struct { flutter.Core homePage *MyHomePage } // NewMyApp instantiates a new MyApp widget func NewMyApp() *MyApp { app := &MyApp{} app.homePage = &MyHomePage{} return app } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx flutter.BuildContext) flutter.Widget { return m.homePage } // MyHomePage is a home page widget. type MyHomePage struct { flutter.Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return flutter.Scaffold() } // incrementCounter increments app's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) } 

Este código pierde la versión de Dart porque si quiero eliminar homePage del árbol de widgets y reemplazarlo con otra cosa, entonces tengo que eliminarlo en tres lugares, en lugar de uno. Pero a cambio, obtenemos una imagen completa de qué, dónde y cómo sucede, dónde se asigna la memoria, quién llama a quién, etc., el código en la palma de su mano es claro y fácil de leer.


Por cierto, Flutter también tiene algo como StatefulBuilder , que agrega aún más magia y te permite crear widgets con estado sobre la marcha.


DSL


Ahora tomemos la parte divertida. ¿Cómo vamos a representar el árbol de widgets en Go? Queremos que se vea conciso, limpio, fácil de refactorizar y cambiar, describir las relaciones espaciales entre widgets (widgets que están visualmente cerca, deben estar cerca y en la descripción), y al mismo tiempo lo suficientemente flexible como para describir arbitrariamente código como manejadores de eventos.


Me parece que la opción en Dart es bastante hermosa y elocuente:


 return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Text('You have pushed the button this many times:'), Text( '$_counter', style: Theme.of(context).textTheme.display1, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), ), ); 

Cada widget tiene un constructor que acepta parámetros opcionales, y lo que hace que el registro sea realmente agradable aquí son los parámetros con nombre de las funciones .


Parámetros nombrados


En caso de que no esté familiarizado con este término, en muchos idiomas los parámetros de la función se denominan "posicionales", ya que su posición es importante para la función:


 Foo(arg1, arg2, arg3) 

, y en el caso de parámetros con nombre, todo se decide por su nombre en la llamada:


 Foo(name: arg1, description: arg2, size: arg3) 

Esto agrega texto, pero guarda clics y se mueve alrededor del código, en un intento por comprender qué significan los parámetros.


En el caso del árbol de widgets, juegan un papel clave en la legibilidad. Compare el mismo código que el anterior, pero sin parámetros con nombre:


 return Scaffold( AppBar( Text(widget.title), ), Center( Column( MainAxisAlignment.center, <Widget>[ Text('You have pushed the button this many times:'), Text( '$_counter', Theme.of(context).textTheme.display1, ), ], ), ), FloatingActionButton( _incrementCounter, 'Increment', Icon(Icons.add), ), ); 

No es eso derecho? No solo es más difícil de entender (debe tener en cuenta lo que significa cada parámetro y cuál es su tipo, y esta es una carga cognitiva significativa), sino que tampoco nos da libertad para elegir qué parámetros queremos transferir. Por ejemplo, es posible que no desee un FloatingActionButton para su aplicación Material, por lo que simplemente no lo especifique en los parámetros. Sin parámetros con nombre, tendremos que forzar la especificación de todos los widgets posibles, o recurrir a la magia con reflexión para descubrir qué widgets se transfirieron.


Y dado que no hay sobrecarga de funciones y parámetros con nombre en Go, esta no será una tarea fácil para Go.


Ir al árbol de widgets


Versión 1


Echemos un vistazo más de cerca al objeto Scaffold , que es un contenedor conveniente para una aplicación móvil. Tiene varias propiedades: appBar, drawe, home, bottomNavigationBar, floatingActionBar, y todos estos son widgets. Al crear un árbol de widgets, en realidad tenemos que inicializar de alguna manera este objeto, pasándole las propiedades del widget antes mencionadas. Bueno, esto no es muy diferente de la creación e inicialización habituales de objetos.


Probemos el enfoque de la frente:


 return flutter.NewScaffold( flutter.NewAppBar( flutter.Text("Flutter Go app", nil), ), nil, nil, flutter.NewCenter( flutter.NewColumn( flutter.MainAxisCenterAlignment, nil, []flutter.Widget{ flutter.Text("You have pushed the button this many times:", nil), flutter.Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), flutter.FloatingActionButton( flutter.NewIcon(icons.Add), "Increment", m.onPressed, nil, nil, ), ) 

No es el código de interfaz de usuario más hermoso, definitivamente. La palabra flutter todas partes y pregunta. para ocultarlo (en realidad, tuve que nombrar el material del paquete, no el flutter , pero no la esencia), los parámetros anónimos son completamente obvios, y estos nil son abiertamente confusos en todas partes.


Versión 2


Sin embargo, dado que la mayoría del código usará uno u otro tipo / función del paquete flutter , podemos usar el formato de "importación de puntos" para importar el paquete a nuestro espacio de nombres y, por lo tanto, "ocultar" el nombre del paquete:


 import . "github.com/flutter/flutter" 

Ahora, en lugar de flutter.Text . Text podemos escribir solo Text . Esto suele ser una mala práctica, pero trabajamos con el marco, y esta importación será literalmente en cada línea. Desde mi práctica, este es exactamente el caso para el cual dicha importación es aceptable, por ejemplo, como cuando se usa el maravilloso marco para probar GoConvey .


Veamos cómo se verá el código:


 return NewScaffold( NewAppBar( Text("Flutter Go app", nil), ), nil, nil, NewCenter( NewColumn( MainAxisCenterAlignment, nil, []Widget{ Text("You have pushed the button this many times:", nil), Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), FloatingActionButton( NewIcon(icons.Add), "Increment", m.onPressed, nil, nil, ), ) 

Ya es mejor, pero estos parámetros nulos y sin nombre ...


Versión 3


Veamos cómo se verá el código si usamos la reflexión (la capacidad de inspeccionar el código mientras el programa se está ejecutando) para analizar los parámetros pasados. Este enfoque se usa en varios marcos HTTP iniciales en Go ( martini , por ejemplo), y se considera una práctica muy mala: es inseguro, pierde la conveniencia del sistema de tipos, es relativamente lento y agrega magia al código, pero en aras del experimento, puede intentar:


 return NewScaffold( NewAppBar( Text("Flutter Go app"), ), NewCenter( NewColumn( MainAxisCenterAlignment, []Widget{ Text("You have pushed the button this many times:"), Text(fmt.Sprintf("%d", m.counter), ctx.Theme.textTheme.display1), }, ), ), FloatingActionButton( NewIcon(icons.Add), "Increment", m.onPressed, ), ) 

No está mal, y se parece a la versión original de Dart, pero la falta de parámetros con nombre todavía duele mucho.


Versión 4


Retrocedamos un poco y preguntémonos qué estamos tratando de hacer. No tenemos que copiar a ciegas el enfoque Dart (aunque será una buena ventaja, hay menos para enseñar a las personas que ya están familiarizadas con Flutter on Dart). De hecho, solo creamos nuevos objetos y les asignamos propiedades.


Puede intentar de esta manera?


 scaffold := NewScaffold() scaffold.AppBar = NewAppBar(Text("Flutter Go app")) column := NewColumn() column.MainAxisAlignment = MainAxisCenterAlignment counterText := Text(fmt.Sprintf("%d", m.counter)) counterText.Style = ctx.Theme.textTheme.display1 column.Children = []Widget{ Text("You have pushed the button this many times:"), counterText, } center := NewCenter() center.Child = column scaffold.Home = center icon := NewIcon(icons.Add), fab := NewFloatingActionButton() fab.Icon = icon fab.Text = "Increment" fab.Handler = m.onPressed scaffold.FloatingActionButton = fab return scaffold 

, " ", . -, – , . -, , .


, UI GTK Qt . , , Qt 5:


  QGridLayout *layout = new QGridLayout(this); layout->addWidget(new QLabel(tr("Object name:")), 0, 0); layout->addWidget(m_objectName, 0, 1); layout->addWidget(new QLabel(tr("Location:")), 1, 0); m_location->setEditable(false); m_location->addItem(tr("Top")); m_location->addItem(tr("Left")); m_location->addItem(tr("Right")); m_location->addItem(tr("Bottom")); m_location->addItem(tr("Restore")); layout->addWidget(m_location, 1, 1); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); layout->addWidget(buttonBox, 2, 0, 1, 2); 

, - . , , , .


5


, – -. Por ejemplo:


 func Build() Widget { return NewScaffold(ScaffoldParams{ AppBar: NewAppBar(AppBarParams{ Title: Text(TextParams{ Text: "My Home Page", }), }), Body: NewCenter(CenterParams{ Child: NewColumn(ColumnParams{ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text(TextParams{ Text: "You have pushed the button this many times:", }), Text(TextParams{ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton( FloatingActionButtonParams{ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon(IconParams{ Icon: Icons.add, }), }, ), }) } 

! , . ...Params , . , , Go , , .


-, ...Params , . (proposal) — " " . , FloatingActionButtonParameters{...} {...} . :


 func Build() Widget { return NewScaffold({ AppBar: NewAppBar({ Title: Text({ Text: "My Home Page", }), }), Body: NewCenter({ Child: NewColumn({ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text({ Text: "You have pushed the button this many times:", }), Text({ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton({ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon({ Icon: Icons.add, }), }, ), }) } 

Dart! .


6


, . , , , , .


, , , -, – :


 button := NewButton(). WithText("Click me"). WithStyle(MyButtonStyle1) 

o


 button := NewButton(). Text("Click me"). Style(MyButtonStyle1) 

Scaffold- :


 // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx flutter.BuildContext) flutter.Widget { return NewScaffold(). AppBar(NewAppBar(). Text("Flutter Go app")). Child(NewCenter(). Child(NewColumn(). MainAxisAlignment(MainAxisCenterAlignment). Children([]Widget{ Text("You have pushed the button this many times:"), Text(fmt.Sprintf("%d", m.counter)). Style(ctx.Theme.textTheme.display1), }))). FloatingActionButton(NewFloatingActionButton(). Icon(NewIcon(icons.Add)). Text("Increment"). Handler(m.onPressed)) } 

Go – , . Dart-, :


  • ""

New...() – , . , — " , , , , , , – " .


, , 5- 6- .



"hello, world" Flutter Go:


main.go


 package hello import "github.com/flutter/flutter" func main() { flutter.Run(NewMyApp()) } 

app.go:


 package hello import . "github.com/flutter/flutter" // MyApp is our top application widget. type MyApp struct { Core homePage *MyHomePage } // NewMyApp instantiates a new MyApp widget func NewMyApp() *MyApp { app := &MyApp{} app.homePage = &MyHomePage{} return app } // Build renders the MyApp widget. Implements Widget interface. func (m *MyApp) Build(ctx BuildContext) Widget { return m.homePage } 

home_page.go:


 package hello import ( "fmt" . "github.com/flutter/flutter" ) // MyHomePage is a home page widget. type MyHomePage struct { Core counter int } // Build renders the MyHomePage widget. Implements Widget interface. func (m *MyHomePage) Build(ctx BuildContext) Widget { return NewScaffold(ScaffoldParams{ AppBar: NewAppBar(AppBarParams{ Title: Text(TextParams{ Text: "My Home Page", }), }), Body: NewCenter(CenterParams{ Child: NewColumn(ColumnParams{ MainAxisAlignment: MainAxisAlignment.center, Children: []Widget{ Text(TextParams{ Text: "You have pushed the button this many times:", }), Text(TextParams{ Text: fmt.Sprintf("%d", m.counter), Style: ctx.textTheme.display1, }), }, }), }), FloatingActionButton: NewFloatingActionButton( FloatingActionButtonParameters{ OnPressed: m.incrementCounter, Tooltip: "Increment", Child: NewIcon(IconParams{ Icon: Icons.add, }), }, ), }) } // incrementCounter increments app's counter by one. func (m *MyHomePage) incrementCounter() { m.counter++ flutter.Rerender(m) } 

!


Conclusión


Vecty


, , Vecty . , , , , Vecty DOM/CSS/JS, Flutter , 120 . , Vecty , Flutter Go Vecty .


Flutter


– , . Flutter, .


Go


" Flutter Go?" "" , , , , , Flutter, , , "" . , Go .


, Go . . Go, , , -. – , , .


Go. – , .


Flutter


, Flutter , , . "/ " , Dart ( , , ). Dart, , (, ) DartVM V8, Flutter – Flutter -.


, . . , , 1.0 . , - .


game changer, Flutter , , .


UI – Flutter, .


Referencias


Source: https://habr.com/ru/post/es435960/


All Articles