Plus récemment, j'ai découvert Flutter - un nouveau cadre de Google pour développer des applications mobiles multiplateformes - et j'ai même eu l'occasion de montrer les bases de Flutter à une personne qui n'avait jamais programmé auparavant. Flutter lui-même a été écrit en Dart - une langue née dans le navigateur Chrome et s'est échappée dans le monde de la console - et cela m'a fait penser "hmm, mais Flutter pourrait très bien être écrit en Go!".
Pourquoi pas? Go et Dart ont tous deux été créés par Google, deux langues compilées typées - tourner autour de certains événements un peu différemment, Go serait un excellent candidat pour la mise en œuvre d'un projet à grande échelle comme Flutter. Quelqu'un dira - il n'y a pas de classes, de génériques et d'exceptions dans Go, donc cela ne convient pas.
Imaginons donc que Flutter soit déjà écrit en Go. À quoi ressemblera le code et, en général, fonctionnera-t-il?

Quel est le problème avec Dart?
J'ai suivi ce langage depuis sa création comme alternative à JavaScript dans les navigateurs. Dart a été intégré au navigateur Chrome pendant un certain temps et l'espoir était qu'il supplanterait JS. Il était incroyablement triste de lire en mars 2015 que la prise en charge de Dart avait été supprimée de Chrome .
Dart lui-même est super! Eh bien, fondamentalement, après JavaScript, n'importe quel langage est génial, mais après, disons, Go, Dart n'est pas si beau. mais ça va. Il a toutes les fonctionnalités imaginables et inconcevables - classes, génériques, exceptions, futurs, asynchrone, boucle d'événement, JIT / AOT, garbage collector, surcharge de fonction - nommez toute caractéristique connue de la théorie des langages de programmation et dans Dart ce sera avec une proportion élevée probabilités. Dart a une syntaxe spéciale pour presque toutes les puces - une syntaxe spéciale pour les getters / setters, une syntaxe spéciale pour les constructeurs abrégés, une syntaxe spéciale pour une syntaxe spéciale et bien plus encore.
Cela rend Dart dès le premier coup d'œil familier aux personnes qui ont déjà programmé dans n'importe quel langage de programmation auparavant, et c'est génial. Mais en essayant d'expliquer toute cette abondance de fonctionnalités spéciales dans un simple exemple "Hello, world", j'ai trouvé que cela, au contraire, le rend difficile à maîtriser.
- toutes les caractéristiques "spéciales" du langage prêtaient à confusion - "une méthode spéciale appelée constructeur", "une syntaxe spéciale pour l'initialisation automatique", "une syntaxe spéciale pour les paramètres nommés", etc.
- tout ce qui était "caché" prêtait à confusion - "de quelle importation est cette fonction? elle est cachée, en regardant le code que vous ne pouvez pas découvrir", "pourquoi y a-t-il un constructeur dans cette classe, mais pas dans cette classe? il est là, mais il est caché" et ainsi de suite
- tout "ambigu" confondu - "donc ici pour créer les paramètres de la fonction avec ou sans noms?", "devrait-il être const ou final?", "utilisez la syntaxe normale de la fonction ici ou '' raccourci avec flèche ''", etc.
En principe, cette trinité - "spéciale", "cachée" et "ambiguë" - pas mal capture l'essence de ce que les gens appellent "magie" dans les langages de programmation. Ce sont des fonctionnalités créées pour simplifier l'écriture du code, mais en fait compliquer sa lecture et sa compréhension.
Et c'est exactement le domaine dans lequel Go prend une position fondamentalement différente des autres langues et tient farouchement la défense. Go est une langue avec presque aucune magie - la quantité de "caché", "spécial" et "ambigu" est minimisée. Mais Go a ses inconvénients.
Quel est le problème avec Go?
Étant donné que nous parlons de Flutter, et qu'il s'agit d'un cadre d'interface utilisateur, considérons Go comme un outil pour décrire et travailler avec l'interface utilisateur. En général, les cadres d'interface utilisateur représentent un énorme défi et nécessitent presque toujours des solutions spécialisées. L'une des approches les plus courantes dans l'interface utilisateur est la création de DSL - langages spécifiques au domaine - implémentés sous la forme de bibliothèques ou de cadres adaptés spécifiquement aux besoins de l'interface utilisateur. Et le plus souvent, vous pouvez entendre l'opinion que Go est objectivement un mauvais langage pour DSL.
En substance, DSL signifie créer un nouveau langage - termes et verbes - sur lequel le développeur peut opérer. Le code doit décrire clairement les principales caractéristiques de l'interface graphique et de ses composants, être suffisamment flexible pour laisser libre cours à l'imagination du concepteur, et en même temps être suffisamment rigide pour la restreindre conformément à certaines règles. Par exemple, vous devriez pouvoir placer les boutons sur un conteneur et placer l'icône au bon endroit dans ce bouton, mais le compilateur doit renvoyer une erreur si vous essayez d'insérer le bouton dans, disons, du texte.
De plus, les langages de description de l'interface utilisateur sont souvent déclaratifs - donnant la possibilité de décrire l'interface sous la forme de «ce que j'aimerais voir», et de laisser le framework lui-même comprendre à partir de quel code et comment l'exécuter.
Certaines langues ont été initialement développées avec de telles tâches en vue, mais pas Go. Il semble que l'écriture de Flutter on Go sera une autre tâche!
Oda Flutter
Si vous ne connaissez pas Flutter, je vous recommande fortement de passer le week-end prochain à regarder des vidéos éducatives ou à lire des tutoriels, dont il existe de nombreux. Car Flutter, sans aucun doute, renverse les règles du jeu dans le développement d'applications mobiles. Et, très probablement, non seulement mobile - il existe déjà des moteurs de rendu (en termes de Flutter, incorporeurs) pour lancer des applications Flutter en tant qu'applications dekstop natives et en tant qu'applications Web .
Il est facile à apprendre, il est logique, il est livré avec une énorme bibliothèque de beaux widgets sur Material Design (et pas seulement), il a une grande et grande communauté et un excellent réglage (si vous aimez la facilité de travailler avec go build/run/test
dans Go, puis dans Flutter vous obtiendrez une expérience similaire).
Il y a un an, j'avais besoin d'écrire une petite application mobile (pour iOS et Android, bien sûr), et j'ai réalisé que la complexité du développement d'une application de haute qualité pour les deux plates-formes est trop grande (l'application n'était pas la tâche principale) - j'ai dû externaliser et payer pour cela. En fait, écrire une application simple mais de haute qualité et travailler sur tous les appareils était une tâche impossible, même pour une personne avec près de 20 ans d'expérience en programmation. Et cela a toujours été absurde pour moi.
Avec Flutter, j'ai réécrit cette application en 15 heures, tout en apprenant le framework lui-même à partir de zéro. Si quelqu'un me disait que cela pourrait être un peu plus tôt, je ne le croirais pas.
La dernière fois que j'ai vu une augmentation similaire de la productivité avec la découverte de nouvelles technologies, c'était il y a 5 ans lorsque j'ai découvert Go. Ce moment a changé ma vie.
Je recommande donc de commencer à apprendre Flutter et ce tutoriel est très bon .
"Bonjour, le monde" sur Flutter
Lorsque vous créez une nouvelle application via flutter create
, vous obtiendrez exactement un tel programme avec un titre, du texte, un compteur et un bouton qui incrémente le compteur.

Je pense que c'est un excellent exemple. pour l'écrire sur notre imaginaire Flutter on Go. Il contient presque tous les concepts de base du cadre sur lesquels vous pouvez tester l'idée. Regardons le code (c'est un fichier):
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), ), ); } }
Analysons le code en plusieurs parties, analysons ce qu'il contient et comment il s'insère sur Go, et examinons les différentes options dont nous disposons.
Nous traduisons le code sur Go
Le début sera simple et direct - importez la dépendance et lancez la fonction main()
. Rien de compliqué ou d'intéressant ici, le changement est presque syntaxique:
package hello import "github.com/flutter/flutter" func main() { app := NewApp() flutter.Run(app) }
La seule différence est qu'au lieu de lancer MyApp()
- une fonction qui est un constructeur qui est une fonction spéciale qui est cachée dans une classe appelée MyApp - nous appelons simplement la fonction explicite et non cachée NewApp()
. Elle fait la même chose, mais il est beaucoup plus clair d'expliquer et de comprendre ce que c'est, comment ça commence et comment ça marche.
Classes de widgets
Dans Flutter, tout se compose de widgets. Dans la version Dart de Flutter, chaque widget est implémenté comme une classe qui hérite des classes spéciales pour les widgets de Flutter.
Il n'y a pas de classes dans Go, et donc pas de hiérarchie de classes, car le monde n'est pas orienté objet, et encore moins hiérarchique. Pour les programmeurs familiarisés uniquement avec le modèle OOP orienté classe, cela peut être une révélation, mais ce n'est vraiment pas le cas. Le monde est un graphique géant entrelacé de concepts, de processus et d'interactions. Il n'est pas parfaitement structuré, mais pas chaotique, et essayer de le presser dans une hiérarchie de classes est le moyen le plus fiable de rendre la base de code illisible et maladroite - exactement ce que la plupart des bases de code sont pour l'instant.

J'apprécie vraiment Go parce que ses créateurs ont pris la peine de repenser ce concept omniprésent de classes et ont implémenté dans Go un concept OOP beaucoup plus simple et plus puissant, qui, par hasard, s'est avéré plus proche de ce que le créateur de OOP, Alan Kay, avait en tête .
Dans Go, nous représentons toute abstraction sous la forme d'un type-structure spécifique:
type MyApp struct {
Dans la version Dart de Flutter, MyApp
doit hériter StatelessWidget
et remplacer la méthode de build
. Cela est nécessaire pour résoudre deux problèmes:
- donner à notre widget (
MyApp
) des propriétés / méthodes spéciales - permettre à Flutter d'appeler notre code dans le processus de génération / rendu
Je ne connais pas les éléments internes de Flutter, alors disons que l'article numéro 1 n'est pas en cause, et nous devons simplement le faire. Go a une solution unique et évidente pour cela: les types d' intégration :
type MyApp struct { flutter.Core
Ce code ajoutera toutes les propriétés et méthodes MyApp
à notre type MyApp
. Je l'ai appelé Core
au lieu de Widget
, car, d'une part, l'incorporation de type ne fait pas encore de MyApp
widget, et d'autre part, ce nom est très bien utilisé dans le framework GopherJS Vecty (quelque chose comme React, uniquement pour Go). J'aborderai le sujet de la similitude entre Vecty et Flutter un peu plus tard.
Le deuxième point - l'implémentation de la méthode build()
, qui pourra utiliser le moteur Flutter - est également résolu simplement et sans ambiguïté dans Go. Nous avons juste besoin d'ajouter une méthode avec une signature spécifique qui satisfait une certaine interface définie quelque part dans notre bibliothèque fictive Flutter sur Go:
flutter.go:
type Widget interface { Build(ctx BuildContext) Widget }
Et maintenant notre main.go:
type MyApp struct { flutter.Core
Nous pouvons remarquer quelques différences ici:
- le code est un peu plus détaillé -
BuildContext
, Widget
et MaterialApp
pointent pour flutter
importations devant eux. - le code est légèrement moins infondé - il n'y a pas de mots comme
extends Widget
@override
ou @override
- La méthode
Build()
commence par une majuscule, car elle signifie la "publicité" de la méthode dans Go. Dans Dart, la publicité est déterminée par le fait que le nom commence par un trait de soulignement (_) ou non.
Donc, pour créer un widget dans notre Flutter on Go, nous devons intégrer le type flutter.Core
et implémenter l'interface flutter.Widget
. Nous l'avons compris, creusons plus loin.
Condition
C'était l'une des choses qui m'ont vraiment dérouté à Flutter. Il existe deux classes différentes - StatelessWidget
et StatefulWidget
. Quant à moi, un "widget sans état" est le même widget, juste sans, hmm, données, état - pourquoi trouver une nouvelle classe? Mais d'accord, je peux vivre avec.
Mais plus loin - plus, vous ne pouvez pas simplement hériter d'une autre classe ( StatefulWidget
), mais vous devez écrire une telle magie (l'EDI le fera pour vous, mais pas le point):
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, voyons ce qui se passe ici.
Fondamentalement, la tâche est la suivante: ajouter un état au widget - un compteur, dans notre cas - et informer le moteur Flutter lorsque nous avons changé d'état pour redessiner le widget. C'est la vraie complexité du problème (complexité essentielle en termes de Brooks).
Tout le reste est une complexité accidentelle. Flutter on Dart propose une nouvelle classe State
qui utilise des génériques et prend un widget comme paramètre de type. Ensuite, la classe _MyHomePageState
est _MyHomePageState
, qui hérite de l' State MyApp
... d'accord, vous pouvez toujours le digérer d'une manière ou d'une autre. Mais pourquoi la méthode build()
définie par la classe State et non par la classe du widget? Brrr ....
La réponse à cette question se trouve dans la FAQ Flutter et la réponse courte est considérée suffisamment en détail ici - pour éviter une certaine classe de bogues lors de l'héritage de StatefulWidget
. En d'autres termes, il s'agit d'une solution de contournement pour résoudre le problème de la conception OOP orientée classe. Chic.
Comment ferions-nous cela dans Go?
Premièrement, personnellement, je préférerais personnellement ne pas créer une entité distincte pour «l'État» - l' State
. Après tout, nous avons déjà un état dans chaque type particulier - ce ne sont que des champs de la structure. Le langage nous a déjà donné cette essence, pour ainsi dire. La création d'une autre entité similaire ne fera que confondre le programmeur.
Le défi, bien sûr, est de donner à Flutter la capacité de répondre aux changements d'état (c'est l'essence de la programmation réactive, après tout). Et si nous pouvons «demander» au développeur d'utiliser une fonction spéciale ( setState()
), nous pouvons également demander à utiliser une fonction spéciale afin de dire au moteur quand redessiner et quand ne pas le faire. En fin de compte, tous les changements d'état ne nécessitent pas d'être redessinés, et ici nous aurons encore plus de contrôle:
type MyHomePage struct { flutter.Core counter int }
Vous pouvez jouer avec différentes options de dénomination - j'aime NeedsUpdate()
pour sa simplicité et le fait qu'il s'agit d'une propriété de widget (obtenue à partir de flutter.Core
), mais la flutter.Rerender()
globale flutter.Rerender()
semble également bonne. Certes, cela donne une fausse impression que le widget sera immédiatement redessiné immédiatement, mais ce n'est pas le cas - il sera redessiné lors de la prochaine mise à jour de la trame, et la fréquence d'appel de méthode peut être beaucoup plus élevée que la fréquence de rendu - mais notre moteur Flutter devrait déjà être capable de gérer cela.
Mais l'idée est que nous venons de résoudre le problème nécessaire sans ajouter:
- nouveau type
- génériques
- règles spéciales pour l'état de lecture / écriture
- nouvelles méthodes spéciales remplacées
De plus, l'API est beaucoup plus claire et plus compréhensible - augmentez simplement le compteur (comme vous le feriez dans n'importe quel autre programme) et demandez à Flutter de redessiner le widget. C'est juste ce qui n'est pas très évident si nous appelons simplement setState
- qui n'est pas seulement une fonction spéciale pour définir l'état, c'est une fonction qui renvoie une fonction (wtf?) Dans laquelle nous faisons déjà quelque chose avec l'état. Encore une fois, la magie cachée dans les langages et les frameworks rend très difficile la compréhension et la lecture du code.
Dans notre cas, nous avons résolu le même problème, le code est plus simple et deux fois plus court.
Widgets d'état dans d'autres widgets
Comme suite logique du sujet, examinons comment le «widget d'état» est utilisé dans un autre widget de Flutter:
@override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', home: MyHomePage(title: 'Flutter Demo Home Page'), ); }
MyHomePage
est ici un "widget d'état" (il a un compteur), et nous le créons en appelant le constructeur MyHomePage()
pendant la construction ... Attendez, quoi?
build()
est appelé pour redessiner le widget, très probablement plusieurs fois par seconde. Pourquoi devrions-nous créer un widget, en particulier avec un état, à chaque fois pendant le rendu? Cela n'a aucun sens.
Il s'avère que Flutter utilise cette séparation entre Widget
et State
afin de cacher cette initialisation / gestion d'état au programmeur (plus de choses cachées, plus!). Il crée un nouveau widget à chaque fois, mais l'état, s'il a déjà été créé, est automatiquement trouvé et attaché au widget. Cette magie se produit de manière invisible et je n'ai aucune idée de comment cela fonctionne - vous devez lire le code.
Je considère comme un véritable mal en programmation de se cacher autant que possible du programmeur, en le justifiant par l'ergonomie. Je suis sûr que le programmeur statistique moyen ne lira pas le code Flutter pour comprendre comment cette magie fonctionne, et il est peu probable qu'il comprenne comment et ce qui est interconnecté.
Pour la version Go, je ne voudrais certainement pas une telle sorcellerie cachée et préférerais une initialisation explicite et visible, même si cela signifie un code légèrement plus infondé. L'approche de Flutter à Dart peut également être implémentée, mais j'aime Go pour minimiser la magie, et j'aimerais voir la même philosophie dans les frameworks. Par conséquent, j'écrirais mon code pour les widgets avec l'état dans l'arborescence des widgets comme ceci:
Ce code perd la version Dart en ce que si je veux supprimer homePage
de l'arborescence des widgets et le remplacer par autre chose, je devrai le supprimer à trois endroits, au lieu d'un. Mais en retour, nous obtenons une image complète de quoi, où et comment cela se produit, où la mémoire est allouée, qui appelle qui, et ainsi de suite - le code dans la paume de votre main est clair et facile à lire.
Soit dit en passant, Flutter a également une chose telle que StatefulBuilder , qui ajoute encore plus de magie et vous permet de créer des widgets avec état à la volée.
DSL
Prenons maintenant la partie amusante. Comment représenterons-nous l'arbre des widgets sur Go? Nous voulons qu'il soit concis, propre, facile à refactoriser et à changer, décrire les relations spatiales entre les widgets (les widgets qui sont visuellement proches, doivent être proches et dans la description), et en même temps suffisamment flexible pour décrire arbitraire code comme les gestionnaires d'événements.
Il me semble que l'option sur Dart est assez belle et éloquente:
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), ), );
Chaque widget a un constructeur qui accepte des paramètres facultatifs, et ce qui rend l'enregistrement vraiment agréable, ce sont les paramètres nommés des fonctions .
Paramètres nommés
Dans le cas où vous n'êtes pas familier avec ce terme, alors dans de nombreuses langues les paramètres de la fonction sont appelés "positionnels", car leur position est importante pour la fonction:
Foo(arg1, arg2, arg3)
, et dans le cas de paramètres nommés, tout est décidé par leur nom dans l'appel:
Foo(name: arg1, description: arg2, size: arg3)
Cela ajoute du texte, mais enregistre les clics et se déplace dans le code, afin de comprendre ce que signifient les paramètres.
Dans le cas de l'arborescence des widgets, ils jouent un rôle clé dans la lisibilité. Comparez le même code que ci-dessus, mais sans paramètres nommés:
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), ), );
Pas ça. non? Il est non seulement plus difficile à comprendre (vous devez garder à l'esprit ce que chaque paramètre signifie et quel est son type, et c'est une charge cognitive importante), mais ne nous donne pas non plus la liberté de choisir les paramètres que nous voulons transférer. Par exemple, il se peut que vous ne souhaitiez pas un FloatingActionButton
pour votre application Material, vous ne le spécifiez donc tout simplement pas dans les paramètres. Sans paramètres nommés, nous devrons soit forcer la spécification de tous les widgets possibles, soit recourir à la magie avec réflexion pour savoir quels widgets ont été transférés.
Et comme il n'y a pas de surcharge de fonctions et de paramètres nommés dans Go, ce ne sera pas une tâche facile pour Go.
Aller à l'arborescence des widgets
Version 1
Examinons de plus près l'objet Scaffold , qui est un wrapper pratique pour une application mobile. Il a plusieurs propriétés - appBar, drawe, home, bottomNavigationBar, floatingActionBar - et ce sont tous des widgets. Lors de la création d'une arborescence de widgets, nous devons en fait initialiser en quelque sorte cet objet, en lui passant les propriétés de widget susmentionnées. Eh bien, ce n'est pas trop différent de la création et de l'initialisation habituelles d'objets.
Essayons l'approche frontale:
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, ), )
Pas le plus beau code d'interface utilisateur, certainement. Le mot flutter
partout et demande. pour le cacher (en fait, j'aurais dû appeler le material
du paquet, pas du flutter
, mais pas l'essence), les paramètres anonymes ne sont absolument pas évidents, et ces nil
sont ouvertement déroutants partout.
Version 2
Étant donné que la plupart du code utilisera toujours l'un ou l'autre type / fonction du package flutter
, nous pouvons utiliser le format «dot import» pour importer le package dans notre espace de noms et ainsi «masquer» le nom du package:
import . "github.com/flutter/flutter"
Maintenant, au lieu de flutter.Text
nous pouvons écrire uniquement du Text
. C'est généralement une mauvaise pratique, mais nous travaillons avec le framework, et cette importation se fera littéralement sur chaque ligne. D'après ma pratique, c'est exactement le cas pour lequel une telle importation est acceptable - par exemple, comme lors de l'utilisation du merveilleux cadre pour tester GoConvey .
Voyons à quoi ressemblera le code:
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, ), )
Déjà mieux, mais ces paramètres nuls et sans nom ....
Version 3
Voyons à quoi ressemblera le code si nous utilisons la réflexion (la capacité d'inspecter le code pendant l'exécution du programme) pour analyser les paramètres passés. Cette approche est utilisée dans plusieurs premiers frameworks HTTP sur Go ( martini , par exemple), et est considérée comme une très mauvaise pratique - elle n'est pas sûre, perd la commodité du système de type, est relativement lente et ajoute de la magie au code - mais pour le plaisir de l'expérience, vous pouvez essayer:
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, ), )
Pas mal, et cela ressemble à la version originale de Dart, mais le manque de paramètres nommés fait toujours mal à l'œil.
Version 4
Revenons un peu en arrière et demandons-nous ce que nous essayons de faire. Nous n'avons pas à copier aveuglément l'approche Dart (bien que ce soit un bon bonus - il y en a moins à enseigner aux personnes qui connaissent déjà Flutter on Dart). En fait, nous créons simplement de nouveaux objets et leur assignons des propriétés.
Pouvez essayer de cette façon?
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
, – -. Par exemple:
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)
ou
button := NewButton(). Text("Click me"). Style(MyButtonStyle1)
Scaffold- :
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"
home_page.go:
package hello import ( "fmt" . "github.com/flutter/flutter" )
!
Conclusion
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, .
Les références